diff --git a/dist/index.d.ts b/dist/index.d.ts
index 904a845..d384bbc 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -2,6 +2,10 @@ 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, };
+import DummySocket from "./proxies/multiSocket/dummySocket.js";
+import TcpSocket from "./proxies/multiSocket/tcpSocket.js";
+import BasicProxy from "./proxies/basic.js";
+import MultiSocketProxy from "./proxies/multiSocket.js";
+export { Proxy, Socket, Server, Peer, DataSocketOptions, PeerOptions, PeerOptionsWithProxy, OnOpen, OnSend, OnReceive, OnClose, DummySocket, TcpSocket, BasicProxy, MultiSocketProxy, };
export declare function createSocket(port: number, host: string): Socket;
export declare function createServer(): Server;
diff --git a/dist/index.js b/dist/index.js
index 07b46df..e66a121 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -3,7 +3,7 @@ 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;
+exports.createServer = exports.createSocket = exports.MultiSocketProxy = exports.BasicProxy = exports.TcpSocket = exports.DummySocket = 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"));
@@ -12,6 +12,14 @@ 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;
+const dummySocket_js_1 = __importDefault(require("./proxies/multiSocket/dummySocket.js"));
+exports.DummySocket = dummySocket_js_1.default;
+const tcpSocket_js_1 = __importDefault(require("./proxies/multiSocket/tcpSocket.js"));
+exports.TcpSocket = tcpSocket_js_1.default;
+const basic_js_1 = __importDefault(require("./proxies/basic.js"));
+exports.BasicProxy = basic_js_1.default;
+const multiSocket_js_1 = __importDefault(require("./proxies/multiSocket.js"));
+exports.MultiSocketProxy = multiSocket_js_1.default;
function createSocket(port, host) {
return new socket_js_1.default({
remotePort: port,
diff --git a/dist/proxies/basic.d.ts b/dist/proxies/basic.d.ts
new file mode 100644
index 0000000..31b7c21
--- /dev/null
+++ b/dist/proxies/basic.d.ts
@@ -0,0 +1,3 @@
+import Proxy from "../proxy.js";
+export default class BasicProxy extends Proxy {
+}
diff --git a/dist/proxies/basic.js b/dist/proxies/basic.js
new file mode 100644
index 0000000..a6ffc4c
--- /dev/null
+++ b/dist/proxies/basic.js
@@ -0,0 +1,9 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const proxy_js_1 = __importDefault(require("../proxy.js"));
+class BasicProxy extends proxy_js_1.default {
+}
+exports.default = BasicProxy;
diff --git a/dist/proxies/multiSocket.d.ts b/dist/proxies/multiSocket.d.ts
new file mode 100644
index 0000000..38be622
--- /dev/null
+++ b/dist/proxies/multiSocket.d.ts
@@ -0,0 +1,33 @@
+///
+import Proxy, { ProxyOptions } from "../proxy.js";
+import type { TcpSocketConnectOpts } from "net";
+import Peer from "../peer.js";
+import { PeerEntity } from "./multiSocket/types.js";
+export interface MultiSocketProxyOptions extends ProxyOptions {
+ socketClass?: any;
+ server: boolean;
+ allowedPorts?: number[];
+}
+export default class MultiSocketProxy extends Proxy {
+ private socketClass;
+ private _peers;
+ private _nextPeer;
+ private _server;
+ private _allowedPorts;
+ constructor(options: MultiSocketProxyOptions);
+ private _socketMap;
+ get socketMap(): Map;
+ private _sockets;
+ get sockets(): Map;
+ handleNewPeerChannel(peer: Peer, channel: any): void;
+ handleClosePeer(peer: Peer): Promise;
+ get(pubkey: Uint8Array): PeerEntity | undefined;
+ update(pubkey: Uint8Array, data: Partial): void;
+ createSocket(options: TcpSocketConnectOpts): Promise;
+ private _registerOpenSocketMessage;
+ private _registerWriteSocketMessage;
+ private _registerCloseSocketMessage;
+ private _registerTimeoutSocketMessage;
+ private _registerErrorSocketMessage;
+ private _toString;
+}
diff --git a/dist/proxies/multiSocket.js b/dist/proxies/multiSocket.js
new file mode 100644
index 0000000..fc5ba85
--- /dev/null
+++ b/dist/proxies/multiSocket.js
@@ -0,0 +1,226 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const proxy_js_1 = __importDefault(require("../proxy.js"));
+const tcpSocket_js_1 = __importDefault(require("./multiSocket/tcpSocket.js"));
+const compact_encoding_1 = require("compact-encoding");
+const serialize_error_1 = require("serialize-error");
+const b4a_1 = __importDefault(require("b4a"));
+const util_js_1 = require("../util.js");
+const dummySocket_js_1 = __importDefault(require("./multiSocket/dummySocket.js"));
+const socketEncoding = {
+ preencode(state, m) {
+ compact_encoding_1.uint.preencode(state, m.id);
+ compact_encoding_1.uint.preencode(state, m.remoteId);
+ },
+ encode(state, m) {
+ compact_encoding_1.uint.encode(state, m.id);
+ compact_encoding_1.uint.encode(state, m.remoteId);
+ },
+ decode(state, m) {
+ return {
+ remoteId: compact_encoding_1.uint.decode(state, m),
+ id: compact_encoding_1.uint.decode(state, m),
+ };
+ },
+};
+const writeSocketEncoding = {
+ preencode(state, m) {
+ socketEncoding.preencode(state, m);
+ compact_encoding_1.raw.preencode(state, m.data);
+ },
+ encode(state, m) {
+ socketEncoding.encode(state, m);
+ compact_encoding_1.raw.encode(state, m.data);
+ },
+ decode(state, m) {
+ const socket = socketEncoding.decode(state, m);
+ return {
+ ...socket,
+ data: compact_encoding_1.raw.decode(state, m),
+ };
+ },
+};
+const errorSocketEncoding = {
+ decode(state, m) {
+ const socket = socketEncoding.decode(state, m);
+ return {
+ ...socket,
+ err: (0, serialize_error_1.deserializeError)(compact_encoding_1.json.decode(state, m)),
+ };
+ },
+};
+const nextSocketId = (0, util_js_1.idFactory)(1);
+class MultiSocketProxy extends proxy_js_1.default {
+ socketClass;
+ _peers = new Map();
+ _nextPeer = (0, util_js_1.roundRobinFactory)(this._peers);
+ _server = false;
+ _allowedPorts = [];
+ constructor(options) {
+ super({
+ createDefaultMessage: false,
+ ...options,
+ });
+ if (options.socketClass) {
+ this.socketClass = options.socketClass;
+ }
+ else {
+ if (options.server) {
+ this.socketClass = tcpSocket_js_1.default;
+ }
+ else {
+ this.socketClass = dummySocket_js_1.default;
+ }
+ }
+ if (options.server) {
+ this._server = true;
+ }
+ }
+ _socketMap = new Map();
+ get socketMap() {
+ return this._socketMap;
+ }
+ _sockets = new Map();
+ get sockets() {
+ return this._sockets;
+ }
+ handleNewPeerChannel(peer, channel) {
+ this.update(peer.socket.remotePublicKey, { peer });
+ this._registerOpenSocketMessage(peer, channel);
+ this._registerWriteSocketMessage(peer, channel);
+ this._registerCloseSocketMessage(peer, channel);
+ this._registerTimeoutSocketMessage(peer, channel);
+ this._registerErrorSocketMessage(peer, channel);
+ }
+ async handleClosePeer(peer) {
+ for (const item of this._sockets) {
+ if (item[1].peer.peer === peer) {
+ item[1].end();
+ }
+ }
+ const pubkey = this._toString(peer.socket.remotePublicKey);
+ if (this._peers.has(pubkey)) {
+ this._peers.delete(pubkey);
+ }
+ }
+ get(pubkey) {
+ if (this._peers.has(this._toString(pubkey))) {
+ return this._peers.get(this._toString(pubkey));
+ }
+ return undefined;
+ }
+ update(pubkey, data) {
+ const peer = this.get(pubkey) ?? {};
+ this._peers.set(this._toString(pubkey), {
+ ...peer,
+ ...data,
+ ...{
+ messages: {
+ ...peer?.messages,
+ ...data?.messages,
+ },
+ },
+ });
+ }
+ async createSocket(options) {
+ if (!this._peers.size) {
+ throw new Error("no peers found");
+ }
+ const peer = this._nextPeer();
+ const socketId = nextSocketId();
+ const socket = new this.socketClass(socketId, this, peer, options);
+ this._sockets.set(socketId, socket);
+ return socket;
+ }
+ _registerOpenSocketMessage(peer, channel) {
+ const self = this;
+ const message = channel.addMessage({
+ encoding: {
+ preencode: compact_encoding_1.json.preencode,
+ encode: compact_encoding_1.json.encode,
+ decode: this._server ? compact_encoding_1.json.encode : socketEncoding.decode,
+ },
+ async onmessage(m) {
+ if (self._allowedPorts.length &&
+ !self._allowedPorts.includes(m.port)) {
+ self.get(peer.socket.remotePublicKey).messages.errorSocket.send({
+ id: m.id,
+ err: new Error(`port ${m.port} not allowed`),
+ });
+ return;
+ }
+ m = m;
+ if (self._server) {
+ new self.socketClass(nextSocketId(), m, self, self.get(peer.socket.remotePublicKey), m).connect();
+ return;
+ }
+ const socket = self._sockets.get(m.id);
+ if (socket) {
+ socket.remoteId = m.remoteId;
+ // @ts-ignore
+ socket.emit("connect");
+ }
+ },
+ });
+ this.update(peer.socket.remotePublicKey, {
+ messages: { openSocket: message },
+ });
+ }
+ _registerWriteSocketMessage(peer, channel) {
+ const self = this;
+ const message = channel.addMessage({
+ encoding: writeSocketEncoding,
+ onmessage(m) {
+ self._sockets.get(m.id)?.push(m.data);
+ },
+ });
+ this.update(peer.socket.remotePublicKey, {
+ messages: { writeSocket: message },
+ });
+ }
+ _registerCloseSocketMessage(peer, channel) {
+ const self = this;
+ const message = channel.addMessage({
+ encoding: socketEncoding,
+ onmessage(m) {
+ self._sockets.get(m.id)?.end();
+ },
+ });
+ this.update(peer.socket.remotePublicKey, {
+ messages: { closeSocket: message },
+ });
+ }
+ _registerTimeoutSocketMessage(peer, channel) {
+ const self = this;
+ const message = channel.addMessage({
+ encoding: socketEncoding,
+ onmessage(m) {
+ // @ts-ignore
+ self._sockets.get(m.id)?.emit("timeout");
+ },
+ });
+ this.update(peer.socket.remotePublicKey, {
+ messages: { timeoutSocket: message },
+ });
+ }
+ _registerErrorSocketMessage(peer, channel) {
+ const self = this;
+ const message = channel.addMessage({
+ encoding: errorSocketEncoding,
+ onmessage(m) {
+ // @ts-ignore
+ self._sockets.get(m.id)?.emit("error", m.err);
+ },
+ });
+ this.update(peer.socket.remotePublicKey, {
+ messages: { errorSocket: message },
+ });
+ }
+ _toString(pubkey) {
+ return b4a_1.default.from(pubkey).toString("hex");
+ }
+}
+exports.default = MultiSocketProxy;
diff --git a/dist/proxies/multiSocket/dummySocket.d.ts b/dist/proxies/multiSocket/dummySocket.d.ts
new file mode 100644
index 0000000..f6cd7b1
--- /dev/null
+++ b/dist/proxies/multiSocket/dummySocket.d.ts
@@ -0,0 +1,20 @@
+///
+import { Callback, Duplex } from "streamx";
+import { TcpSocketConnectOpts } from "net";
+import MultiSocketProxy from "../multiSocket.js";
+import { PeerEntity } from "./types.js";
+export default class DummySocket extends Duplex {
+ private _options;
+ private _id;
+ private _proxy;
+ private _connectTimeout?;
+ constructor(id: number, manager: MultiSocketProxy, peer: PeerEntity, options: TcpSocketConnectOpts);
+ private _remoteId;
+ set remoteId(value: number);
+ private _peer;
+ get peer(): any;
+ _write(data: any, cb: any): Promise;
+ _destroy(cb: Callback): Promise;
+ connect(): Promise;
+ setTimeout(ms: number, cb: Function): void;
+}
diff --git a/dist/proxies/multiSocket/dummySocket.js b/dist/proxies/multiSocket/dummySocket.js
new file mode 100644
index 0000000..004ad01
--- /dev/null
+++ b/dist/proxies/multiSocket/dummySocket.js
@@ -0,0 +1,64 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const streamx_1 = require("streamx");
+const timers_1 = require("timers");
+const util_js_1 = require("../../util.js");
+class DummySocket extends streamx_1.Duplex {
+ _options;
+ _id;
+ _proxy;
+ _connectTimeout;
+ constructor(id, manager, peer, options) {
+ super();
+ this._id = id;
+ this._proxy = manager;
+ this._peer = peer;
+ this._options = options;
+ // @ts-ignore
+ this.on("timeout", () => {
+ if (this._connectTimeout) {
+ (0, timers_1.clearTimeout)(this._connectTimeout);
+ }
+ });
+ }
+ _remoteId = 0;
+ set remoteId(value) {
+ this._remoteId = value;
+ this._proxy.socketMap.set(this._id, value);
+ }
+ _peer;
+ get peer() {
+ return this._peer;
+ }
+ async _write(data, cb) {
+ (await (0, util_js_1.maybeGetAsyncProperty)(this._peer.messages.writeSocket))?.send({
+ id: this._id,
+ remoteId: this._remoteId,
+ data,
+ });
+ cb();
+ }
+ async _destroy(cb) {
+ (await (0, util_js_1.maybeGetAsyncProperty)(this._peer.messages.closeSocket))?.send({
+ id: this._id,
+ remoteId: this._remoteId,
+ });
+ this._proxy.socketMap.delete(this._id);
+ this._proxy.sockets.delete(this._id);
+ }
+ async connect() {
+ (await (0, util_js_1.maybeGetAsyncProperty)(this._peer.messages.openSocket))?.send({
+ ...this._options,
+ id: this._id,
+ });
+ }
+ setTimeout(ms, cb) {
+ if (this._connectTimeout) {
+ (0, timers_1.clearTimeout)(this._connectTimeout);
+ }
+ this._connectTimeout = setTimeout(() => {
+ cb && cb();
+ }, ms);
+ }
+}
+exports.default = DummySocket;
diff --git a/dist/proxies/multiSocket/tcpSocket.d.ts b/dist/proxies/multiSocket/tcpSocket.d.ts
new file mode 100644
index 0000000..f5004de
--- /dev/null
+++ b/dist/proxies/multiSocket/tcpSocket.d.ts
@@ -0,0 +1,19 @@
+///
+import { Callback, Duplex } from "streamx";
+import { TcpSocketConnectOpts } from "net";
+import MultiSocketProxy from "../multiSocket.js";
+import { PeerEntity } from "./types.js";
+export default class TcpSocket extends Duplex {
+ private _options;
+ private _id;
+ private _remoteId;
+ private _proxy;
+ private _socket?;
+ constructor(id: number, remoteId: number, manager: MultiSocketProxy, peer: PeerEntity, options: TcpSocketConnectOpts);
+ private _peer;
+ get peer(): any;
+ _write(data: any, cb: any): void;
+ _destroy(cb: Callback): void;
+ connect(): void;
+ private _getSocketRequest;
+}
diff --git a/dist/proxies/multiSocket/tcpSocket.js b/dist/proxies/multiSocket/tcpSocket.js
new file mode 100644
index 0000000..af5dddc
--- /dev/null
+++ b/dist/proxies/multiSocket/tcpSocket.js
@@ -0,0 +1,89 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+ o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+ if (mod && mod.__esModule) return mod;
+ var result = {};
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const streamx_1 = require("streamx");
+const net = __importStar(require("net"));
+class TcpSocket extends streamx_1.Duplex {
+ _options;
+ _id;
+ _remoteId;
+ _proxy;
+ _socket;
+ constructor(id, remoteId, manager, peer, options) {
+ super();
+ this._remoteId = remoteId;
+ this._proxy = manager;
+ this._id = id;
+ this._peer = peer;
+ this._options = options;
+ this._proxy.sockets.set(this._id, this);
+ this._proxy.socketMap.set(this._id, this._remoteId);
+ }
+ _peer;
+ get peer() {
+ return this._peer;
+ }
+ _write(data, cb) {
+ this._peer.messages.writeSocket?.send({
+ ...this._getSocketRequest(),
+ data,
+ });
+ cb();
+ }
+ _destroy(cb) {
+ this._proxy.sockets.delete(this._id);
+ this._proxy.socketMap.delete(this._id);
+ this._peer.messages.closeSocket?.send(this._getSocketRequest());
+ }
+ connect() {
+ this.on("error", (err) => {
+ this._peer.messages.errorSocket?.send({
+ ...this._getSocketRequest(),
+ err,
+ });
+ });
+ // @ts-ignore
+ this.on("timeout", () => {
+ this._peer.messages.timeoutSocket?.send(this._getSocketRequest());
+ });
+ // @ts-ignore
+ this.on("connect", () => {
+ this._peer.messages.openSocket?.send(this._getSocketRequest());
+ });
+ this._socket = net.connect(this._options);
+ ["timeout", "error", "connect", "end", "destroy", "close"].forEach((event) => {
+ this._socket?.on(event, (...args) => this.emit(event, ...args));
+ });
+ this._socket.pipe(this);
+ this.pipe(this._socket);
+ }
+ _getSocketRequest() {
+ return {
+ id: this._id,
+ remoteId: this._remoteId,
+ };
+ }
+}
+exports.default = TcpSocket;
diff --git a/dist/proxies/multiSocket/types.d.ts b/dist/proxies/multiSocket/types.d.ts
new file mode 100644
index 0000000..e8bcdb2
--- /dev/null
+++ b/dist/proxies/multiSocket/types.d.ts
@@ -0,0 +1,28 @@
+import Peer from "../../peer.js";
+export interface SocketRequest {
+ remoteId: number;
+ id: number;
+}
+export type CloseSocketRequest = SocketRequest;
+export interface WriteSocketRequest extends SocketRequest {
+ data: Uint8Array;
+}
+export interface ErrorSocketRequest extends SocketRequest {
+ err: Error;
+}
+type Message = {
+ send: (pubkey: Uint8Array | any) => void;
+};
+export interface PeerEntityMessages {
+ keyExchange: Message;
+ openSocket: Message;
+ writeSocket: Message;
+ closeSocket: Message;
+ timeoutSocket: Message;
+ errorSocket: Message;
+}
+export interface PeerEntity {
+ messages: PeerEntityMessages | Partial;
+ peer: Peer;
+}
+export {};
diff --git a/dist/proxies/multiSocket/types.js b/dist/proxies/multiSocket/types.js
new file mode 100644
index 0000000..c8ad2e5
--- /dev/null
+++ b/dist/proxies/multiSocket/types.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/dist/proxy.d.ts b/dist/proxy.d.ts
index 4496e9f..1bd61c1 100644
--- a/dist/proxy.d.ts
+++ b/dist/proxy.d.ts
@@ -5,7 +5,7 @@ export interface ProxyOptions extends DataSocketOptions {
listen?: boolean;
autostart?: boolean;
}
-export default class Proxy {
+export default abstract class Proxy {
private _listen;
private _socketOptions;
private _autostart;
diff --git a/dist/util.d.ts b/dist/util.d.ts
new file mode 100644
index 0000000..8aa6fce
--- /dev/null
+++ b/dist/util.d.ts
@@ -0,0 +1,4 @@
+export declare function idFactory(start: number, step?: number, limit?: number): () => number;
+export declare function roundRobinFactory(list: Map): () => T;
+export declare function maybeGetAsyncProperty(object: any): Promise;
+export declare function isPromise(obj: Promise): boolean;
diff --git a/dist/util.js b/dist/util.js
new file mode 100644
index 0000000..a0dca59
--- /dev/null
+++ b/dist/util.js
@@ -0,0 +1,41 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.isPromise = exports.maybeGetAsyncProperty = exports.roundRobinFactory = exports.idFactory = void 0;
+function idFactory(start, step = 1, limit = 2 ** 32) {
+ let id = start;
+ return function nextId() {
+ const nextId = id;
+ id += step;
+ if (id >= limit)
+ id = start;
+ return nextId;
+ };
+}
+exports.idFactory = idFactory;
+function roundRobinFactory(list) {
+ let index = 0;
+ return () => {
+ const keys = [...list.keys()].sort();
+ if (index >= keys.length) {
+ index = 0;
+ }
+ return list.get(keys[index++]);
+ };
+}
+exports.roundRobinFactory = roundRobinFactory;
+async function maybeGetAsyncProperty(object) {
+ if (typeof object === "function") {
+ object = object();
+ }
+ if (isPromise(object)) {
+ object = await object;
+ }
+ return object;
+}
+exports.maybeGetAsyncProperty = maybeGetAsyncProperty;
+function isPromise(obj) {
+ return (!!obj &&
+ (typeof obj === "object" || typeof obj === "function") &&
+ typeof obj.then === "function");
+}
+exports.isPromise = isPromise;