diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..904a845 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,7 @@ +import Proxy from "./proxy.js"; +import Socket from "./socket.js"; +import Peer, { DataSocketOptions, PeerOptions, PeerOptionsWithProxy, OnOpen, OnSend, OnReceive, OnClose } from "./peer.js"; +import Server from "./server.js"; +export { Proxy, Socket, Server, Peer, DataSocketOptions, PeerOptions, PeerOptionsWithProxy, OnOpen, OnSend, OnReceive, OnClose, }; +export declare function createSocket(port: number, host: string): Socket; +export declare function createServer(): Server; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..07b46df --- /dev/null +++ b/dist/index.js @@ -0,0 +1,25 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createServer = exports.createSocket = exports.Peer = exports.Server = exports.Socket = exports.Proxy = void 0; +const proxy_js_1 = __importDefault(require("./proxy.js")); +exports.Proxy = proxy_js_1.default; +const socket_js_1 = __importDefault(require("./socket.js")); +exports.Socket = socket_js_1.default; +const peer_js_1 = __importDefault(require("./peer.js")); +exports.Peer = peer_js_1.default; +const server_js_1 = __importDefault(require("./server.js")); +exports.Server = server_js_1.default; +function createSocket(port, host) { + return new socket_js_1.default({ + remotePort: port, + remoteAddress: host, + }); +} +exports.createSocket = createSocket; +function createServer() { + return new server_js_1.default(); +} +exports.createServer = createServer; diff --git a/dist/peer.d.ts b/dist/peer.d.ts new file mode 100644 index 0000000..02e239d --- /dev/null +++ b/dist/peer.d.ts @@ -0,0 +1,36 @@ +import Proxy from "./proxy.js"; +import Socket from "./socket.js"; +export type OnOpen = (socket: Socket, data: any) => { + connect: boolean; +} | Promise<{ + connect: boolean; +}> | Promise | void; +export type OnData = (data: any) => void; +export type OnReceive = OnData; +export type OnClose = OnData; +export type OnSend = OnData; +export interface DataSocketOptions { + onopen?: OnOpen; + onreceive?: OnReceive; + onsend?: OnSend; + onclose?: OnClose; +} +export interface PeerOptions { + peer: any; + muxer: any; +} +export interface PeerOptionsWithProxy extends PeerOptions { + proxy: Proxy; +} +export default class Peer { + private _proxy; + private _peer; + private _muxer; + private _socket?; + private _onopen; + private _onreceive; + private _onsend; + private _onclose; + constructor({ proxy, peer, muxer, onopen, onreceive, onsend, onclose, }: PeerOptionsWithProxy & DataSocketOptions); + init(): Promise; +} diff --git a/dist/peer.js b/dist/peer.js new file mode 100644 index 0000000..02d0888 --- /dev/null +++ b/dist/peer.js @@ -0,0 +1,65 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const socket_js_1 = __importDefault(require("./socket.js")); +class Peer { + _proxy; + _peer; + _muxer; + _socket; + _onopen; + _onreceive; + _onsend; + _onclose; + constructor({ proxy, peer, muxer, onopen, onreceive, onsend, onclose, }) { + this._proxy = proxy; + this._peer = peer; + this._muxer = muxer; + this._onopen = onopen; + this._onreceive = onreceive; + this._onsend = onsend; + this._onclose = onclose; + } + async init() { + const write = async (data, cb) => { + pipe.send(data); + await this._onsend?.(data); + cb(); + }; + const self = this; + const channel = this._muxer.createChannel({ + protocol: this._proxy.protocol, + async onopen(m) { + if (!m) { + m = Buffer.from([]); + } + self._socket = new socket_js_1.default({ + remoteAddress: self._peer.rawStream.remoteHost, + remotePort: self._peer.rawStream.remotePort, + write, + }); + self._socket.on("end", () => channel.close()); + let ret = await self._onopen?.(self._socket, m); + if (!ret || (ret && ret.connect === false)) { + // @ts-ignore + self._socket.emit("connect"); + } + self._socket.emit("data", m); + }, + async onclose() { + self._socket?.end(); + await self._onclose?.(self._socket); + }, + }); + const pipe = channel.addMessage({ + async onmessage(m) { + self._socket.emit("data", m); + await self._onreceive?.(m); + }, + }); + await channel.open(); + } +} +exports.default = Peer; diff --git a/dist/proxy.d.ts b/dist/proxy.d.ts new file mode 100644 index 0000000..3d85a7c --- /dev/null +++ b/dist/proxy.d.ts @@ -0,0 +1,21 @@ +import { PeerOptions, DataSocketOptions } from "./peer.js"; +export interface ProxyOptions extends DataSocketOptions { + swarm: any; + protocol: string; + listen?: boolean; + autostart?: boolean; +} +export default class Proxy { + private _listen; + private _socketOptions; + private _autostart; + constructor({ swarm, protocol, onopen, onreceive, onsend, onclose, listen, autostart, }: ProxyOptions); + private _swarm; + get swarm(): any; + private _protocol; + get protocol(): string; + handlePeer({ peer, muxer, ...options }: DataSocketOptions & PeerOptions): void; + protected _init(): void; + private init; + private _handleConnection; +} diff --git a/dist/proxy.js b/dist/proxy.js new file mode 100644 index 0000000..5ceb02e --- /dev/null +++ b/dist/proxy.js @@ -0,0 +1,55 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const protomux_1 = __importDefault(require("protomux")); +const peer_js_1 = __importDefault(require("./peer.js")); +class Proxy { + _listen; + _socketOptions; + _autostart; + constructor({ swarm, protocol, onopen, onreceive, onsend, onclose, listen = false, autostart = false, }) { + this._swarm = swarm; + this._protocol = protocol; + this._listen = listen; + this._autostart = autostart; + this._socketOptions = { onopen, onreceive, onsend, onclose }; + this.init(); + } + _swarm; + get swarm() { + return this._swarm; + } + _protocol; + get protocol() { + return this._protocol; + } + handlePeer({ peer, muxer, ...options }) { + const conn = new peer_js_1.default({ proxy: this, peer, muxer, ...options }); + conn.init(); + } + _init() { + // Implement in subclasses + } + async init() { + if (this._listen) { + this._swarm.on("connection", this._handleConnection.bind(this)); + } + await this._init(); + } + _handleConnection(peer) { + const muxer = protomux_1.default.from(peer); + const handlePeer = this.handlePeer.bind(this, { + peer, + muxer, + ...this._socketOptions, + }); + if (this._autostart) { + handlePeer(); + return; + } + muxer.pair(this._protocol, handlePeer); + } +} +exports.default = Proxy; diff --git a/dist/server.d.ts b/dist/server.d.ts new file mode 100644 index 0000000..39dcbd7 --- /dev/null +++ b/dist/server.d.ts @@ -0,0 +1,22 @@ +/// +import EventEmitter from "events"; +export default class Server extends EventEmitter { + address(): { + address: string; + family: string; + port: number; + }; + close(): Promise; + getConnections(): Promise; + listen(...args: any[]): Promise<{ + address: string; + family: string; + port: number; + }>; + get listening(): boolean; + set listening(value: boolean); + get maxConnections(): any; + set maxConnections(value: any); + ref(): this; + unref(): this; +} diff --git a/dist/server.js b/dist/server.js new file mode 100644 index 0000000..8e7af2e --- /dev/null +++ b/dist/server.js @@ -0,0 +1,41 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = __importDefault(require("events")); +class Server extends events_1.default { + address() { + return { + address: "127.0.0.1", + family: "IPv4", + port: 0, + }; + } + async close() { + return; + } + async getConnections() { + return 0; + } + async listen(...args) { + const address = this.address(); + this.emit("listening", address); + return address; + } + get listening() { + return false; + } + set listening(value) { } + get maxConnections() { + return undefined; + } + set maxConnections(value) { } + ref() { + return this; + } + unref() { + return this; + } +} +exports.default = Server; diff --git a/dist/socket.d.ts b/dist/socket.d.ts new file mode 100644 index 0000000..5ff16c7 --- /dev/null +++ b/dist/socket.d.ts @@ -0,0 +1,33 @@ +import { Duplex, DuplexEvents, Callback } from "streamx"; +type AddressFamily = "IPv6" | "IPv4"; +interface SocketOptions { + allowHalfOpen?: boolean; + remoteAddress?: string; + remotePort?: number; + write?: (this: Duplex>, data: any, cb: Callback) => void; +} +export default class Socket extends Duplex { + private _allowHalfOpen; + remoteAddress: any; + remotePort: any; + remoteFamily: AddressFamily; + bufferSize: any; + constructor({ allowHalfOpen, remoteAddress, remotePort, write, }?: SocketOptions); + private _connecting; + get connecting(): boolean; + get readyState(): string; + listen(): void; + setTimeout(msecs: any, callback: any): void; + _onTimeout(): void; + setNoDelay(enable: boolean): void; + setKeepAlive(setting: any, msecs: number): void; + address(): { + address: any; + port: any; + family: AddressFamily; + }; + static isIP(input: string): number; + static isIPv4(input: string): boolean; + static isIPv6(input: string): boolean; +} +export {}; diff --git a/dist/socket.js b/dist/socket.js new file mode 100644 index 0000000..55e1d54 --- /dev/null +++ b/dist/socket.js @@ -0,0 +1,83 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const streamx_1 = require("streamx"); +const IPV4 = "IPv4"; +const IPV6 = "IPv6"; +class Socket extends streamx_1.Duplex { + _allowHalfOpen; + remoteAddress; + remotePort; + remoteFamily; + bufferSize; + constructor({ allowHalfOpen = false, remoteAddress, remotePort, write, } = {}) { + super({ write }); + this._allowHalfOpen = allowHalfOpen; + this.remoteAddress = remoteAddress; + this.remotePort = remotePort; + if (remoteAddress) { + const type = Socket.isIP(remoteAddress); + if (!type) { + throw Error("invalid remoteAddress"); + } + this.remoteFamily = type === 6 ? IPV4 : IPV6; + } + } + _connecting; + get connecting() { + return this._connecting; + } + get readyState() { + if (this._connecting) { + return "opening"; + } + else if (this.readable && this.writable) { + return "open"; + } + else if (this.readable && !this.writable) { + return "readOnly"; + } + else if (!this.readable && this.writable) { + return "writeOnly"; + } + else { + return "closed"; + } + } + listen() { + throw new Error("Not supported"); + } + setTimeout(msecs, callback) { + throw new Error("Not implemented"); + } + _onTimeout() { + // @ts-ignore + this.emit("timeout"); + } + setNoDelay(enable) { } + setKeepAlive(setting, msecs) { } + address() { + return { + address: this.remoteAddress, + port: this.remotePort, + family: this.remoteFamily, + }; + } + static isIP(input) { + if (Socket.isIPv4(input)) { + return 4; + } + else if (Socket.isIPv6(input)) { + return 6; + } + else { + return 0; + } + } + static isIPv4(input) { + return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(input); + } + static isIPv6(input) { + return /^(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))$/.test(input); + } +} +exports.default = Socket;