Compare commits
103 Commits
Author | SHA1 | Date |
---|---|---|
Derrick Hammer | 85cc802977 | |
Derrick Hammer | eb65447adb | |
Derrick Hammer | c2031b4c7f | |
Derrick Hammer | ec937e786b | |
Derrick Hammer | f3e5cebe45 | |
Derrick Hammer | 9cb6d0b6e4 | |
Derrick Hammer | e3e6757e47 | |
Derrick Hammer | 3a1ea39a79 | |
Derrick Hammer | e19e4c1744 | |
Derrick Hammer | 3a41474b26 | |
Derrick Hammer | 339dbba08c | |
Derrick Hammer | c4c1fd8f6b | |
Derrick Hammer | 623601df27 | |
Derrick Hammer | cfd5f69cfb | |
Derrick Hammer | bc85076e7f | |
Derrick Hammer | 85ff38d871 | |
Derrick Hammer | 39499397ea | |
Derrick Hammer | 1882752839 | |
Derrick Hammer | 9791e7c4a1 | |
Derrick Hammer | d30528caa0 | |
Derrick Hammer | b3d6fd4668 | |
Derrick Hammer | b04db8668b | |
Derrick Hammer | 35bf05c25e | |
Derrick Hammer | f842cd84c4 | |
Derrick Hammer | c3c8e6fb3b | |
Derrick Hammer | 96dd1ad46e | |
Derrick Hammer | 10a7b4ebc6 | |
Derrick Hammer | 1c9a430d5e | |
Derrick Hammer | 8b678e81e8 | |
Derrick Hammer | 13ff64002d | |
Derrick Hammer | 40e139df0d | |
Derrick Hammer | 518bdca8ad | |
Derrick Hammer | 84bc6ce1cb | |
Derrick Hammer | 605f760fe3 | |
Derrick Hammer | a8419313b5 | |
Derrick Hammer | f11e3fed78 | |
Derrick Hammer | 95d866c69e | |
Derrick Hammer | 5fa5385249 | |
Derrick Hammer | 774e84996e | |
Derrick Hammer | 4b1b828c69 | |
Derrick Hammer | 8ea1ad3006 | |
Derrick Hammer | ddf6b2c9d8 | |
Derrick Hammer | 422c11b9b8 | |
Derrick Hammer | b2b041c3a1 | |
Derrick Hammer | d63fa22d00 | |
Derrick Hammer | f8b8633287 | |
Derrick Hammer | c01d866d8b | |
Derrick Hammer | 22023baedc | |
Derrick Hammer | 94342e6929 | |
Derrick Hammer | 6099a6c4f6 | |
Derrick Hammer | 5c666d38c0 | |
Derrick Hammer | 94e817f045 | |
Derrick Hammer | 92245a6c1c | |
Derrick Hammer | 8e0edc4bcd | |
Derrick Hammer | c1d495a54b | |
Derrick Hammer | 27261fedd2 | |
Derrick Hammer | 2e639eb92a | |
Derrick Hammer | 619fe7913c | |
Derrick Hammer | 7bbfe577c3 | |
Derrick Hammer | 41984ae588 | |
Derrick Hammer | 6c6f6c4954 | |
Derrick Hammer | b14509d6f1 | |
Derrick Hammer | b3a7c0b4e1 | |
Derrick Hammer | baf0ac31e6 | |
Derrick Hammer | ae2ee61bec | |
Derrick Hammer | b9611cecd7 | |
Derrick Hammer | e5f4f59477 | |
Derrick Hammer | c9cfb64c80 | |
Derrick Hammer | 4220037402 | |
Derrick Hammer | 1d4cb917c5 | |
Derrick Hammer | 08b4bbc893 | |
Derrick Hammer | 3b7be7e892 | |
Derrick Hammer | f89e2716f5 | |
Derrick Hammer | 84f2f96e12 | |
Derrick Hammer | b1ad8399d3 | |
Derrick Hammer | 7980aa29c7 | |
Derrick Hammer | d94eec7c7e | |
Derrick Hammer | 68cd8008a6 | |
Derrick Hammer | 6b42c2400f | |
Derrick Hammer | bdacd01849 | |
Derrick Hammer | 603d7c4e14 | |
Derrick Hammer | 4d75c11299 | |
Derrick Hammer | 29c6a909b8 | |
Derrick Hammer | 68c12f22be | |
Derrick Hammer | 11550659d0 | |
Derrick Hammer | c90e022795 | |
Derrick Hammer | 39471ad9c2 | |
Derrick Hammer | 8bae1c11dd | |
Derrick Hammer | ab98103503 | |
Derrick Hammer | 8734d8180c | |
Derrick Hammer | 9a73a2625a | |
Derrick Hammer | 0a35d7f062 | |
Derrick Hammer | 5e41c10ab6 | |
Derrick Hammer | 10d3c1fde8 | |
Derrick Hammer | 108e1b3dd8 | |
Derrick Hammer | 3ce2313c5a | |
Derrick Hammer | 47c93b1f1a | |
Derrick Hammer | 035be11686 | |
Derrick Hammer | d48dde7bd1 | |
Derrick Hammer | 5b87187bc0 | |
Derrick Hammer | b4b30cbdea | |
Derrick Hammer | 81559342be | |
Derrick Hammer | 4e12788db5 |
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
Copyright (c) 2023 Hammer Technologies LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
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";
|
||||
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;
|
|
@ -0,0 +1,33 @@
|
|||
"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.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"));
|
||||
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;
|
||||
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,
|
||||
remoteAddress: host,
|
||||
});
|
||||
}
|
||||
exports.createSocket = createSocket;
|
||||
function createServer() {
|
||||
return new server_js_1.default();
|
||||
}
|
||||
exports.createServer = createServer;
|
|
@ -0,0 +1,59 @@
|
|||
import Proxy from "./proxy.js";
|
||||
import Socket from "./socket.js";
|
||||
export type OnOpen = (peer: Peer, socket: Socket, data: any) => {
|
||||
connect: boolean;
|
||||
} | Promise<{
|
||||
connect: boolean;
|
||||
}> | Promise<void> | void;
|
||||
export type OnData = (peer: Peer, data: any) => void;
|
||||
export type OnReceive = OnData;
|
||||
export type OnClose = OnData;
|
||||
export type OnSend = OnData;
|
||||
export type OnChannel = (peer: Peer, channel: any) => void;
|
||||
export type OnOpenBound = (socket: Socket, data: any) => {
|
||||
connect: boolean;
|
||||
} | Promise<{
|
||||
connect: boolean;
|
||||
}> | Promise<void> | void;
|
||||
export type OnDataBound = (data: any) => void;
|
||||
export type OnReceiveBound = OnDataBound;
|
||||
export type OnCloseBound = OnDataBound;
|
||||
export type OnSendBound = OnDataBound;
|
||||
export type OnChannelBound = (channel: any) => void;
|
||||
export interface DataSocketOptions {
|
||||
onopen?: OnOpen;
|
||||
onreceive?: OnReceive;
|
||||
onsend?: OnSend;
|
||||
onclose?: OnClose;
|
||||
onchannel?: OnChannel;
|
||||
emulateWebsocket?: boolean;
|
||||
}
|
||||
export interface PeerOptions {
|
||||
peer: any;
|
||||
muxer: any;
|
||||
}
|
||||
export interface PeerOptionsWithProxy extends PeerOptions {
|
||||
proxy: Proxy;
|
||||
}
|
||||
export default abstract class Peer {
|
||||
protected _proxy: Proxy;
|
||||
protected _peer: any;
|
||||
protected _muxer: any;
|
||||
protected _onopen: OnOpenBound;
|
||||
protected _onreceive: OnReceiveBound;
|
||||
protected _onsend: OnSendBound;
|
||||
protected _onclose: OnCloseBound;
|
||||
protected _onchannel: OnChannelBound;
|
||||
protected _emulateWebsocket: boolean;
|
||||
constructor({ proxy, peer, muxer, onopen, onreceive, onsend, onclose, onchannel, emulateWebsocket, }: PeerOptionsWithProxy & DataSocketOptions);
|
||||
protected _socket?: Socket;
|
||||
get socket(): Socket;
|
||||
protected _channel?: any;
|
||||
get channel(): any;
|
||||
protected abstract initSocket(): any;
|
||||
protected abstract handleChannelOnOpen(m: any): Promise<void>;
|
||||
protected abstract handleChannelOnClose(socket: Socket): Promise<void>;
|
||||
protected initChannel(): Promise<void>;
|
||||
init(): Promise<void>;
|
||||
protected initMessages(): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class Peer {
|
||||
_proxy;
|
||||
_peer;
|
||||
_muxer;
|
||||
_onopen;
|
||||
_onreceive;
|
||||
_onsend;
|
||||
_onclose;
|
||||
_onchannel;
|
||||
_emulateWebsocket;
|
||||
constructor({ proxy, peer, muxer, onopen, onreceive, onsend, onclose, onchannel, emulateWebsocket = false, }) {
|
||||
this._proxy = proxy;
|
||||
this._peer = peer;
|
||||
this._muxer = muxer;
|
||||
this._onopen = onopen?.bind(undefined, this);
|
||||
this._onreceive = onreceive?.bind(undefined, this);
|
||||
this._onsend = onsend?.bind(undefined, this);
|
||||
this._onclose = onclose?.bind(undefined, this);
|
||||
this._onchannel = onchannel?.bind(undefined, this);
|
||||
this._emulateWebsocket = emulateWebsocket;
|
||||
}
|
||||
_socket;
|
||||
get socket() {
|
||||
return this._socket;
|
||||
}
|
||||
_channel;
|
||||
get channel() {
|
||||
return this._channel;
|
||||
}
|
||||
async initChannel() {
|
||||
const self = this;
|
||||
this._channel = await this._muxer.createChannel({
|
||||
protocol: this._proxy.protocol,
|
||||
onopen: async (m) => {
|
||||
await this.handleChannelOnOpen(m);
|
||||
// @ts-ignore
|
||||
await this._onopen?.(this._channel);
|
||||
},
|
||||
onclose: async (socket) => {
|
||||
await this.handleChannelOnClose(socket);
|
||||
// @ts-ignore
|
||||
await this._onclose?.();
|
||||
},
|
||||
});
|
||||
await this.initMessages();
|
||||
await this._onchannel?.(this._channel);
|
||||
await this._channel.open();
|
||||
this._proxy.emit("peerChannelOpen", this);
|
||||
}
|
||||
async init() {
|
||||
await this.initSocket();
|
||||
await this.initChannel();
|
||||
}
|
||||
async initMessages() { }
|
||||
}
|
||||
exports.default = Peer;
|
|
@ -0,0 +1,5 @@
|
|||
import Proxy from "../proxy.js";
|
||||
import { DataSocketOptions, PeerOptions } from "../peer.js";
|
||||
export default class BasicProxy extends Proxy {
|
||||
protected handlePeer({ peer, muxer, ...options }: DataSocketOptions & PeerOptions): void;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
"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 peer_js_1 = __importDefault(require("./basic/peer.js"));
|
||||
class BasicProxy extends proxy_js_1.default {
|
||||
handlePeer({ peer, muxer, ...options }) {
|
||||
const conn = new peer_js_1.default({ proxy: this, peer, muxer, ...options });
|
||||
conn.init();
|
||||
}
|
||||
}
|
||||
exports.default = BasicProxy;
|
|
@ -0,0 +1,9 @@
|
|||
import BasePeer from "../../peer.js";
|
||||
import Socket from "../../socket.js";
|
||||
export default class Peer extends BasePeer {
|
||||
private _pipe?;
|
||||
protected initSocket(): Promise<void>;
|
||||
protected handleChannelOnOpen(m: any): Promise<void>;
|
||||
protected handleChannelOnClose(socket: Socket): Promise<void>;
|
||||
protected initMessages(): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const peer_js_1 = __importDefault(require("../../peer.js"));
|
||||
const util_js_1 = require("../../util.js");
|
||||
const socket_js_1 = __importDefault(require("../../socket.js"));
|
||||
const buffer_1 = require("buffer");
|
||||
class Peer extends peer_js_1.default {
|
||||
_pipe;
|
||||
async initSocket() {
|
||||
const self = this;
|
||||
const raw = await (0, util_js_1.maybeGetAsyncProperty)(self._peer.rawStream);
|
||||
this._socket = new socket_js_1.default({
|
||||
remoteAddress: raw.remoteHost,
|
||||
remotePort: raw.remotePort,
|
||||
remotePublicKey: await (0, util_js_1.maybeGetAsyncProperty)(self._peer.remotePublicKey),
|
||||
async write(data, cb) {
|
||||
self._pipe?.send(data);
|
||||
await self._onsend?.(data);
|
||||
cb();
|
||||
},
|
||||
emulateWebsocket: self._emulateWebsocket,
|
||||
});
|
||||
}
|
||||
async handleChannelOnOpen(m) {
|
||||
if (!m) {
|
||||
m = buffer_1.Buffer.from([]);
|
||||
}
|
||||
if (m instanceof Uint8Array) {
|
||||
m = buffer_1.Buffer.from(m);
|
||||
}
|
||||
this._socket?.on("end", () => this._channel.close());
|
||||
let ret = await this._onopen?.(this._socket, m);
|
||||
if (!ret || (ret && ret.connect === false)) {
|
||||
// @ts-ignore
|
||||
self._socket?.emit("connect");
|
||||
}
|
||||
this._socket?.emit("data", m);
|
||||
}
|
||||
async handleChannelOnClose(socket) {
|
||||
this._socket?.destroy();
|
||||
}
|
||||
async initMessages() {
|
||||
const self = this;
|
||||
this._pipe = await this._channel.addMessage({
|
||||
async onmessage(m) {
|
||||
if (m instanceof Uint8Array) {
|
||||
m = buffer_1.Buffer.from(m);
|
||||
}
|
||||
self._socket.emit("data", m);
|
||||
await self._onreceive?.(m);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.default = Peer;
|
|
@ -0,0 +1,36 @@
|
|||
/// <reference types="node" />
|
||||
import Proxy, { ProxyOptions } from "../proxy.js";
|
||||
import type { TcpSocketConnectOpts } from "net";
|
||||
import { DataSocketOptions, PeerOptions } from "../peer.js";
|
||||
import { PeerEntity } from "./multiSocket/types.js";
|
||||
import Peer from "./multiSocket/peer.js";
|
||||
export interface MultiSocketProxyOptions extends ProxyOptions {
|
||||
socketClass?: any;
|
||||
server: boolean;
|
||||
allowedPorts?: number[];
|
||||
}
|
||||
export default class MultiSocketProxy extends Proxy {
|
||||
handlePeer({ peer, muxer, ...options }: DataSocketOptions & PeerOptions): Promise<void>;
|
||||
private socketClass;
|
||||
private _peers;
|
||||
private _nextPeer;
|
||||
private _server;
|
||||
private _allowedPorts;
|
||||
constructor(options: MultiSocketProxyOptions);
|
||||
private _socketMap;
|
||||
get socketMap(): Map<number, number>;
|
||||
private _sockets;
|
||||
get sockets(): Map<number, any>;
|
||||
handleNewPeerChannel(peer: Peer): Promise<void>;
|
||||
handleClosePeer(peer: Peer): Promise<void>;
|
||||
get(pubkey: Uint8Array): PeerEntity | undefined;
|
||||
update(pubkey: Uint8Array, data: Partial<PeerEntity>): void;
|
||||
createSocket(options: TcpSocketConnectOpts): typeof this.socketClass;
|
||||
private _registerOpenSocketMessage;
|
||||
private _registerWriteSocketMessage;
|
||||
private _registerCloseSocketMessage;
|
||||
private _registerTimeoutSocketMessage;
|
||||
private _registerErrorSocketMessage;
|
||||
private _toString;
|
||||
private _getPublicKey;
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
"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 peer_js_1 = __importDefault(require("./multiSocket/peer.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 = {
|
||||
preencode(state, m) {
|
||||
socketEncoding.preencode(state, m);
|
||||
compact_encoding_1.json.preencode(state, (0, serialize_error_1.serializeError)(m.err));
|
||||
},
|
||||
encode(state, m) {
|
||||
socketEncoding.encode(state, m);
|
||||
compact_encoding_1.json.encode(state, (0, serialize_error_1.serializeError)(m.err));
|
||||
},
|
||||
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 {
|
||||
async handlePeer({ peer, muxer, ...options }) {
|
||||
const conn = new peer_js_1.default({
|
||||
...this.socketOptions,
|
||||
proxy: this,
|
||||
peer,
|
||||
muxer,
|
||||
...options,
|
||||
});
|
||||
await conn.init();
|
||||
this.emit("peer", conn);
|
||||
}
|
||||
socketClass;
|
||||
_peers = new Map();
|
||||
_nextPeer;
|
||||
_server = false;
|
||||
_allowedPorts = [];
|
||||
constructor(options) {
|
||||
super(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;
|
||||
}
|
||||
this._nextPeer = (0, util_js_1.roundRobinFactory)(this._peers);
|
||||
}
|
||||
_socketMap = new Map();
|
||||
get socketMap() {
|
||||
return this._socketMap;
|
||||
}
|
||||
_sockets = new Map();
|
||||
get sockets() {
|
||||
return this._sockets;
|
||||
}
|
||||
async handleNewPeerChannel(peer) {
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
peer,
|
||||
});
|
||||
await this._registerOpenSocketMessage(peer);
|
||||
await this._registerWriteSocketMessage(peer);
|
||||
await this._registerCloseSocketMessage(peer);
|
||||
await this._registerTimeoutSocketMessage(peer);
|
||||
await this._registerErrorSocketMessage(peer);
|
||||
}
|
||||
async handleClosePeer(peer) {
|
||||
for (const item of this._sockets) {
|
||||
if (item[1].peer.peer === peer) {
|
||||
item[1].end();
|
||||
}
|
||||
}
|
||||
const pubkey = this._toString(await this._getPublicKey(peer));
|
||||
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
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;
|
||||
}
|
||||
async _registerOpenSocketMessage(peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: {
|
||||
preencode: this._server ? socketEncoding.preencode : compact_encoding_1.json.preencode,
|
||||
encode: this._server ? socketEncoding.encode : compact_encoding_1.json.encode,
|
||||
decode: this._server ? compact_encoding_1.json.decode : socketEncoding.decode,
|
||||
},
|
||||
async onmessage(m) {
|
||||
if (self._server) {
|
||||
if (self._allowedPorts.length &&
|
||||
!self._allowedPorts.includes(m.port)) {
|
||||
self.get(await self._getPublicKey(peer)).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.id, self, self.get(await self._getPublicKey(peer)), m).connect();
|
||||
return;
|
||||
}
|
||||
const socket = self._sockets.get(m.id);
|
||||
if (socket) {
|
||||
socket.remoteId = m.remoteId;
|
||||
// @ts-ignore
|
||||
socket.emit("connect");
|
||||
}
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { openSocket: message },
|
||||
});
|
||||
}
|
||||
async _registerWriteSocketMessage(peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: writeSocketEncoding,
|
||||
onmessage(m) {
|
||||
self._sockets.get(m.id)?.push(b4a_1.default.from(m.data));
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { writeSocket: message },
|
||||
});
|
||||
}
|
||||
async _registerCloseSocketMessage(peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: socketEncoding,
|
||||
onmessage(m) {
|
||||
self._sockets.get(m.id)?.end();
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { closeSocket: message },
|
||||
});
|
||||
}
|
||||
async _registerTimeoutSocketMessage(peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: socketEncoding,
|
||||
onmessage(m) {
|
||||
// @ts-ignore
|
||||
self._sockets.get(m.id)?.emit("timeout");
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { timeoutSocket: message },
|
||||
});
|
||||
}
|
||||
async _registerErrorSocketMessage(peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: errorSocketEncoding,
|
||||
onmessage(m) {
|
||||
// @ts-ignore
|
||||
self._sockets.get(m.id)?.emit("error", m.err);
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { errorSocket: message },
|
||||
});
|
||||
}
|
||||
_toString(pubkey) {
|
||||
return b4a_1.default.from(pubkey).toString("hex");
|
||||
}
|
||||
async _getPublicKey(peer) {
|
||||
return (0, util_js_1.maybeGetAsyncProperty)(peer.stream.remotePublicKey);
|
||||
}
|
||||
}
|
||||
exports.default = MultiSocketProxy;
|
|
@ -0,0 +1,21 @@
|
|||
/// <reference types="node" />
|
||||
import { Callback } from "streamx";
|
||||
import { TcpSocketConnectOpts } from "net";
|
||||
import MultiSocketProxy from "../multiSocket.js";
|
||||
import { PeerEntity } from "./types.js";
|
||||
import Socket, { SocketOptions } from "../../socket.js";
|
||||
export default class DummySocket extends Socket {
|
||||
private _options;
|
||||
private _id;
|
||||
private _proxy;
|
||||
private _connectTimeout?;
|
||||
constructor(id: number, manager: MultiSocketProxy, peer: PeerEntity, connectOptions: TcpSocketConnectOpts, socketOptions: SocketOptions);
|
||||
private _remoteId;
|
||||
set remoteId(value: number);
|
||||
private _peer;
|
||||
get peer(): any;
|
||||
_write(data: any, cb: any): Promise<void>;
|
||||
_destroy(cb: Callback): Promise<void>;
|
||||
connect(): Promise<void>;
|
||||
setTimeout(ms: number, cb: Function): void;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const timers_1 = require("timers");
|
||||
const util_js_1 = require("../../util.js");
|
||||
const socket_js_1 = __importDefault(require("../../socket.js"));
|
||||
class DummySocket extends socket_js_1.default {
|
||||
_options;
|
||||
_id;
|
||||
_proxy;
|
||||
_connectTimeout;
|
||||
constructor(id, manager, peer, connectOptions, socketOptions) {
|
||||
super(socketOptions);
|
||||
this._id = id;
|
||||
this._proxy = manager;
|
||||
this._peer = peer;
|
||||
this._options = connectOptions;
|
||||
// @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;
|
|
@ -0,0 +1,11 @@
|
|||
import BasePeer from "../../peer.js";
|
||||
import Socket from "../../socket.js";
|
||||
import MultiSocketProxy from "../multiSocket.js";
|
||||
export default class Peer extends BasePeer {
|
||||
protected initMessages(): Promise<void>;
|
||||
protected _proxy: MultiSocketProxy;
|
||||
protected initSocket(): Promise<void>;
|
||||
get stream(): any;
|
||||
protected handleChannelOnClose(socket: Socket): Promise<void>;
|
||||
protected handleChannelOnOpen(m: any): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const peer_js_1 = __importDefault(require("../../peer.js"));
|
||||
class Peer extends peer_js_1.default {
|
||||
async initMessages() {
|
||||
await this._proxy.handleNewPeerChannel(this);
|
||||
}
|
||||
async initSocket() { }
|
||||
get stream() {
|
||||
return this._muxer.stream;
|
||||
}
|
||||
async handleChannelOnClose(socket) {
|
||||
return this._proxy.handleClosePeer(this);
|
||||
}
|
||||
async handleChannelOnOpen(m) { }
|
||||
}
|
||||
exports.default = Peer;
|
|
@ -0,0 +1,20 @@
|
|||
/// <reference types="node" />
|
||||
import { Callback } from "streamx";
|
||||
import { TcpSocketConnectOpts } from "net";
|
||||
import MultiSocketProxy from "../multiSocket.js";
|
||||
import { PeerEntity } from "./types.js";
|
||||
import BaseSocket from "../../socket.js";
|
||||
export default class TcpSocket extends BaseSocket {
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
"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;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const net = __importStar(require("net"));
|
||||
const socket_js_1 = __importDefault(require("../../socket.js"));
|
||||
class TcpSocket extends socket_js_1.default {
|
||||
_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;
|
|
@ -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<PeerEntityMessages>;
|
||||
peer: Peer;
|
||||
}
|
||||
export {};
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@ -0,0 +1,24 @@
|
|||
/// <reference types="node" />
|
||||
import { DataSocketOptions, PeerOptions } from "./peer.js";
|
||||
import EventEmitter from "events";
|
||||
export interface ProxyOptions extends DataSocketOptions {
|
||||
swarm: any;
|
||||
protocol: string;
|
||||
listen?: boolean;
|
||||
autostart?: boolean;
|
||||
}
|
||||
export default abstract class Proxy extends EventEmitter {
|
||||
protected _listen: any;
|
||||
protected _autostart: boolean;
|
||||
constructor({ swarm, protocol, onopen, onreceive, onsend, onclose, onchannel, listen, autostart, emulateWebsocket, }: ProxyOptions);
|
||||
protected _socketOptions: DataSocketOptions;
|
||||
get socketOptions(): DataSocketOptions;
|
||||
private _swarm;
|
||||
get swarm(): any;
|
||||
private _protocol;
|
||||
get protocol(): string;
|
||||
protected abstract handlePeer({ peer, muxer, ...options }: DataSocketOptions & PeerOptions): any;
|
||||
protected _init(): void;
|
||||
private init;
|
||||
private _handleConnection;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
"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 events_1 = __importDefault(require("events"));
|
||||
class Proxy extends events_1.default {
|
||||
_listen;
|
||||
_autostart;
|
||||
constructor({ swarm, protocol, onopen, onreceive, onsend, onclose, onchannel, listen = false, autostart = false, emulateWebsocket = false, }) {
|
||||
super();
|
||||
this._swarm = swarm;
|
||||
this._protocol = protocol;
|
||||
this._listen = listen;
|
||||
this._autostart = autostart;
|
||||
this._socketOptions = {
|
||||
onopen,
|
||||
onreceive,
|
||||
onsend,
|
||||
onclose,
|
||||
onchannel,
|
||||
emulateWebsocket,
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
_socketOptions;
|
||||
get socketOptions() {
|
||||
return this._socketOptions;
|
||||
}
|
||||
_swarm;
|
||||
get swarm() {
|
||||
return this._swarm;
|
||||
}
|
||||
_protocol;
|
||||
get protocol() {
|
||||
return this._protocol;
|
||||
}
|
||||
_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;
|
|
@ -0,0 +1,22 @@
|
|||
/// <reference types="node" />
|
||||
import EventEmitter from "events";
|
||||
export default class Server extends EventEmitter {
|
||||
address(): {
|
||||
address: string;
|
||||
family: string;
|
||||
port: number;
|
||||
};
|
||||
close(): Promise<void>;
|
||||
getConnections(): Promise<number>;
|
||||
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;
|
||||
}
|
|
@ -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;
|
|
@ -0,0 +1,43 @@
|
|||
import { Duplex, DuplexEvents, Callback } from "streamx";
|
||||
type AddressFamily = "IPv6" | "IPv4";
|
||||
export interface SocketOptions {
|
||||
allowHalfOpen?: boolean;
|
||||
remoteAddress?: string;
|
||||
remotePort?: number;
|
||||
remotePublicKey?: Uint8Array;
|
||||
write?: (this: Duplex<any, any, any, any, true, true, DuplexEvents<any, any>>, data: any, cb: Callback) => void;
|
||||
emulateWebsocket?: boolean;
|
||||
}
|
||||
export default class Socket extends Duplex {
|
||||
private _allowHalfOpen;
|
||||
remoteAddress: any;
|
||||
remotePort: any;
|
||||
remoteFamily: AddressFamily;
|
||||
bufferSize: any;
|
||||
readable: true;
|
||||
writable: true;
|
||||
remotePublicKey: Uint8Array;
|
||||
private _emulateWebsocket;
|
||||
addEventListener?: typeof this.addListener;
|
||||
removeEventListener?: typeof this.removeListener;
|
||||
send?: typeof this.write;
|
||||
close?: typeof this.end;
|
||||
constructor({ allowHalfOpen, remoteAddress, remotePort, remotePublicKey, write, emulateWebsocket, }?: SocketOptions);
|
||||
private _connecting;
|
||||
get connecting(): boolean;
|
||||
get readyState(): string | number;
|
||||
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 {};
|
|
@ -0,0 +1,107 @@
|
|||
"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;
|
||||
remotePublicKey;
|
||||
_emulateWebsocket;
|
||||
constructor({ allowHalfOpen = false, remoteAddress, remotePort, remotePublicKey, write, emulateWebsocket = false, } = {}) {
|
||||
super({ write });
|
||||
this._allowHalfOpen = allowHalfOpen;
|
||||
this.remoteAddress = remoteAddress;
|
||||
this.remotePort = remotePort;
|
||||
this.remotePublicKey = remotePublicKey;
|
||||
this._emulateWebsocket = emulateWebsocket;
|
||||
if (remoteAddress) {
|
||||
const type = Socket.isIP(remoteAddress);
|
||||
if (!type) {
|
||||
throw Error("invalid remoteAddress");
|
||||
}
|
||||
this.remoteFamily = type === 6 ? IPV6 : IPV4;
|
||||
}
|
||||
if (this._emulateWebsocket) {
|
||||
this.addEventListener = this.addListener;
|
||||
this.removeEventListener = this.removeListener;
|
||||
this.send = this.write;
|
||||
this.close = this.end;
|
||||
this.addEventListener("data", (data) =>
|
||||
// @ts-ignore
|
||||
this.emit("message", new MessageEvent("data", { data })));
|
||||
}
|
||||
}
|
||||
_connecting;
|
||||
get connecting() {
|
||||
return this._connecting;
|
||||
}
|
||||
get readyState() {
|
||||
if (this._emulateWebsocket) {
|
||||
if (this._connecting) {
|
||||
return 0;
|
||||
}
|
||||
else if (this.readable && this.writable) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
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;
|
|
@ -0,0 +1,4 @@
|
|||
export declare function idFactory(start: number, step?: number, limit?: number): () => number;
|
||||
export declare function roundRobinFactory<T>(list: Map<string, any>): () => T;
|
||||
export declare function maybeGetAsyncProperty(object: any): Promise<any>;
|
||||
export declare function isPromise(obj: Promise<any>): boolean;
|
|
@ -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;
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@lumeweb/libhyperproxy",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/index.js",
|
||||
"devDependencies": {
|
||||
"@types/b4a": "^1.6.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/streamx": "^2.9.1",
|
||||
"prettier": "^2.8.2",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"b4a": "^1.6.3",
|
||||
"buffer": "^6.0.3",
|
||||
"compact-encoding": "^2.11.0",
|
||||
"protomux": "^3.4.0",
|
||||
"serialize-error": "^11.0.0",
|
||||
"streamx": "^2.13.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
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";
|
||||
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 function createSocket(port: number, host: string): Socket {
|
||||
return new Socket({
|
||||
remotePort: port,
|
||||
remoteAddress: host,
|
||||
});
|
||||
}
|
||||
|
||||
export function createServer(): Server {
|
||||
return new Server();
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
import Proxy from "./proxy.js";
|
||||
import Socket from "./socket.js";
|
||||
import { Buffer } from "buffer";
|
||||
import { maybeGetAsyncProperty } from "./util.js";
|
||||
export type OnOpen = (
|
||||
peer: Peer,
|
||||
socket: Socket,
|
||||
data: any
|
||||
) =>
|
||||
| { connect: boolean }
|
||||
| Promise<{ connect: boolean }>
|
||||
| Promise<void>
|
||||
| void;
|
||||
export type OnData = (peer: Peer, data: any) => void;
|
||||
export type OnReceive = OnData;
|
||||
export type OnClose = OnData;
|
||||
export type OnSend = OnData;
|
||||
export type OnChannel = (peer: Peer, channel: any) => void;
|
||||
|
||||
export type OnOpenBound = (
|
||||
socket: Socket,
|
||||
data: any
|
||||
) =>
|
||||
| { connect: boolean }
|
||||
| Promise<{ connect: boolean }>
|
||||
| Promise<void>
|
||||
| void;
|
||||
export type OnDataBound = (data: any) => void;
|
||||
export type OnReceiveBound = OnDataBound;
|
||||
export type OnCloseBound = OnDataBound;
|
||||
export type OnSendBound = OnDataBound;
|
||||
|
||||
export type OnChannelBound = (channel: any) => void;
|
||||
|
||||
export interface DataSocketOptions {
|
||||
onopen?: OnOpen;
|
||||
onreceive?: OnReceive;
|
||||
onsend?: OnSend;
|
||||
onclose?: OnClose;
|
||||
onchannel?: OnChannel;
|
||||
emulateWebsocket?: boolean;
|
||||
}
|
||||
|
||||
export interface PeerOptions {
|
||||
peer: any;
|
||||
muxer: any;
|
||||
}
|
||||
|
||||
export interface PeerOptionsWithProxy extends PeerOptions {
|
||||
proxy: Proxy;
|
||||
}
|
||||
|
||||
export default abstract class Peer {
|
||||
protected _proxy: Proxy;
|
||||
protected _peer: any;
|
||||
protected _muxer: any;
|
||||
protected _onopen: OnOpenBound;
|
||||
protected _onreceive: OnReceiveBound;
|
||||
protected _onsend: OnSendBound;
|
||||
protected _onclose: OnCloseBound;
|
||||
protected _onchannel: OnChannelBound;
|
||||
protected _emulateWebsocket: boolean;
|
||||
|
||||
constructor({
|
||||
proxy,
|
||||
peer,
|
||||
muxer,
|
||||
onopen,
|
||||
onreceive,
|
||||
onsend,
|
||||
onclose,
|
||||
onchannel,
|
||||
emulateWebsocket = false,
|
||||
}: PeerOptionsWithProxy & DataSocketOptions) {
|
||||
this._proxy = proxy;
|
||||
this._peer = peer;
|
||||
this._muxer = muxer;
|
||||
this._onopen = onopen?.bind(undefined, this);
|
||||
this._onreceive = onreceive?.bind(undefined, this);
|
||||
this._onsend = onsend?.bind(undefined, this);
|
||||
this._onclose = onclose?.bind(undefined, this);
|
||||
this._onchannel = onchannel?.bind(undefined, this);
|
||||
this._emulateWebsocket = emulateWebsocket;
|
||||
}
|
||||
|
||||
protected _socket?: Socket;
|
||||
|
||||
get socket(): Socket {
|
||||
return this._socket;
|
||||
}
|
||||
|
||||
protected _channel?: any;
|
||||
|
||||
get channel(): any {
|
||||
return this._channel;
|
||||
}
|
||||
|
||||
protected abstract initSocket();
|
||||
|
||||
protected abstract handleChannelOnOpen(m: any): Promise<void>;
|
||||
protected abstract handleChannelOnClose(socket: Socket): Promise<void>;
|
||||
|
||||
protected async initChannel() {
|
||||
const self = this;
|
||||
|
||||
this._channel = await this._muxer.createChannel({
|
||||
protocol: this._proxy.protocol,
|
||||
onopen: async (m: any) => {
|
||||
await this.handleChannelOnOpen(m);
|
||||
// @ts-ignore
|
||||
await this._onopen?.(this._channel);
|
||||
},
|
||||
onclose: async (socket: Socket) => {
|
||||
await this.handleChannelOnClose(socket);
|
||||
// @ts-ignore
|
||||
await this._onclose?.();
|
||||
},
|
||||
});
|
||||
|
||||
await this.initMessages();
|
||||
|
||||
await this._onchannel?.(this._channel);
|
||||
await this._channel.open();
|
||||
this._proxy.emit("peerChannelOpen", this);
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.initSocket();
|
||||
await this.initChannel();
|
||||
}
|
||||
|
||||
protected async initMessages() {}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import Proxy from "../proxy.js";
|
||||
import { DataSocketOptions, PeerOptions } from "../peer.js";
|
||||
import BasicPeer from "./basic/peer.js";
|
||||
|
||||
export default class BasicProxy extends Proxy {
|
||||
protected handlePeer({
|
||||
peer,
|
||||
muxer,
|
||||
...options
|
||||
}: DataSocketOptions & PeerOptions) {
|
||||
const conn = new BasicPeer({ proxy: this, peer, muxer, ...options });
|
||||
conn.init();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import BasePeer from "../../peer.js";
|
||||
import { maybeGetAsyncProperty } from "../../util.js";
|
||||
import Socket from "../../socket.js";
|
||||
import { Buffer } from "buffer";
|
||||
|
||||
export default class Peer extends BasePeer {
|
||||
private _pipe?: any;
|
||||
protected async initSocket() {
|
||||
const self = this;
|
||||
|
||||
const raw = await maybeGetAsyncProperty(self._peer.rawStream);
|
||||
this._socket = new Socket({
|
||||
remoteAddress: raw.remoteHost,
|
||||
remotePort: raw.remotePort,
|
||||
remotePublicKey: await maybeGetAsyncProperty(self._peer.remotePublicKey),
|
||||
async write(data: any, cb: Function) {
|
||||
self._pipe?.send(data);
|
||||
await self._onsend?.(data);
|
||||
cb();
|
||||
},
|
||||
emulateWebsocket: self._emulateWebsocket,
|
||||
});
|
||||
}
|
||||
|
||||
protected async handleChannelOnOpen(m: any) {
|
||||
if (!m) {
|
||||
m = Buffer.from([]);
|
||||
}
|
||||
|
||||
if (m instanceof Uint8Array) {
|
||||
m = Buffer.from(m);
|
||||
}
|
||||
|
||||
this._socket?.on("end", () => this._channel.close());
|
||||
let ret = await this._onopen?.(this._socket, m);
|
||||
if (!ret || (ret && ret.connect === false)) {
|
||||
// @ts-ignore
|
||||
self._socket?.emit("connect");
|
||||
}
|
||||
|
||||
this._socket?.emit("data", m);
|
||||
}
|
||||
|
||||
protected async handleChannelOnClose(socket: Socket): Promise<void> {
|
||||
this._socket?.destroy();
|
||||
}
|
||||
|
||||
protected async initMessages(): Promise<void> {
|
||||
const self = this;
|
||||
this._pipe = await this._channel.addMessage({
|
||||
async onmessage(m: any) {
|
||||
if (m instanceof Uint8Array) {
|
||||
m = Buffer.from(m);
|
||||
}
|
||||
self._socket.emit("data", m);
|
||||
await self._onreceive?.(m);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
import Proxy, { ProxyOptions } from "../proxy.js";
|
||||
import TcpSocket from "./multiSocket/tcpSocket.js";
|
||||
import { json, raw, uint } from "compact-encoding";
|
||||
import { deserializeError, serializeError } from "serialize-error";
|
||||
import b4a from "b4a";
|
||||
import type { TcpSocketConnectOpts } from "net";
|
||||
import { DataSocketOptions, PeerOptions } from "../peer.js";
|
||||
import {
|
||||
roundRobinFactory,
|
||||
idFactory,
|
||||
maybeGetAsyncProperty,
|
||||
} from "../util.js";
|
||||
import {
|
||||
CloseSocketRequest,
|
||||
ErrorSocketRequest,
|
||||
PeerEntity,
|
||||
SocketRequest,
|
||||
WriteSocketRequest,
|
||||
} from "./multiSocket/types.js";
|
||||
import DummySocket from "./multiSocket/dummySocket.js";
|
||||
import Peer from "./multiSocket/peer.js";
|
||||
|
||||
export interface MultiSocketProxyOptions extends ProxyOptions {
|
||||
socketClass?: any;
|
||||
server: boolean;
|
||||
allowedPorts?: number[];
|
||||
}
|
||||
|
||||
const socketEncoding = {
|
||||
preencode(state: any, m: SocketRequest) {
|
||||
uint.preencode(state, m.id);
|
||||
uint.preencode(state, m.remoteId);
|
||||
},
|
||||
encode(state: any, m: SocketRequest) {
|
||||
uint.encode(state, m.id);
|
||||
uint.encode(state, m.remoteId);
|
||||
},
|
||||
decode(state: any, m: any): SocketRequest {
|
||||
return {
|
||||
remoteId: uint.decode(state, m),
|
||||
id: uint.decode(state, m),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const writeSocketEncoding = {
|
||||
preencode(state: any, m: WriteSocketRequest) {
|
||||
socketEncoding.preencode(state, m);
|
||||
raw.preencode(state, m.data);
|
||||
},
|
||||
encode(state: any, m: WriteSocketRequest) {
|
||||
socketEncoding.encode(state, m);
|
||||
raw.encode(state, m.data);
|
||||
},
|
||||
decode(state: any, m: any): WriteSocketRequest {
|
||||
const socket = socketEncoding.decode(state, m);
|
||||
return {
|
||||
...socket,
|
||||
data: raw.decode(state, m),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const errorSocketEncoding = {
|
||||
preencode(state: any, m: ErrorSocketRequest) {
|
||||
socketEncoding.preencode(state, m);
|
||||
json.preencode(state, serializeError(m.err));
|
||||
},
|
||||
encode(state: any, m: ErrorSocketRequest) {
|
||||
socketEncoding.encode(state, m);
|
||||
json.encode(state, serializeError(m.err));
|
||||
},
|
||||
decode(state: any, m: any): ErrorSocketRequest {
|
||||
const socket = socketEncoding.decode(state, m);
|
||||
return {
|
||||
...socket,
|
||||
err: deserializeError(json.decode(state, m)),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const nextSocketId = idFactory(1);
|
||||
|
||||
export default class MultiSocketProxy extends Proxy {
|
||||
async handlePeer({
|
||||
peer,
|
||||
muxer,
|
||||
...options
|
||||
}: DataSocketOptions & PeerOptions) {
|
||||
const conn = new Peer({
|
||||
...this.socketOptions,
|
||||
proxy: this,
|
||||
peer,
|
||||
muxer,
|
||||
...options,
|
||||
});
|
||||
await conn.init();
|
||||
this.emit("peer", conn);
|
||||
}
|
||||
|
||||
private socketClass: any;
|
||||
private _peers: Map<string, PeerEntity> = new Map<string, PeerEntity>();
|
||||
private _nextPeer;
|
||||
private _server = false;
|
||||
private _allowedPorts = [];
|
||||
|
||||
constructor(options: MultiSocketProxyOptions) {
|
||||
super(options);
|
||||
if (options.socketClass) {
|
||||
this.socketClass = options.socketClass;
|
||||
} else {
|
||||
if (options.server) {
|
||||
this.socketClass = TcpSocket;
|
||||
} else {
|
||||
this.socketClass = DummySocket;
|
||||
}
|
||||
}
|
||||
if (options.server) {
|
||||
this._server = true;
|
||||
}
|
||||
this._nextPeer = roundRobinFactory(this._peers);
|
||||
}
|
||||
|
||||
private _socketMap = new Map<number, number>();
|
||||
|
||||
get socketMap(): Map<number, number> {
|
||||
return this._socketMap;
|
||||
}
|
||||
|
||||
private _sockets = new Map<number, typeof this.socketClass>();
|
||||
|
||||
get sockets(): Map<number, any> {
|
||||
return this._sockets;
|
||||
}
|
||||
|
||||
async handleNewPeerChannel(peer: Peer) {
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
peer,
|
||||
});
|
||||
|
||||
await this._registerOpenSocketMessage(peer);
|
||||
await this._registerWriteSocketMessage(peer);
|
||||
await this._registerCloseSocketMessage(peer);
|
||||
await this._registerTimeoutSocketMessage(peer);
|
||||
await this._registerErrorSocketMessage(peer);
|
||||
}
|
||||
|
||||
async handleClosePeer(peer: Peer) {
|
||||
for (const item of this._sockets) {
|
||||
if (item[1].peer.peer === peer) {
|
||||
item[1].end();
|
||||
}
|
||||
}
|
||||
|
||||
const pubkey = this._toString(await this._getPublicKey(peer));
|
||||
|
||||
if (this._peers.has(pubkey)) {
|
||||
this._peers.delete(pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
public get(pubkey: Uint8Array): PeerEntity | undefined {
|
||||
if (this._peers.has(this._toString(pubkey))) {
|
||||
return this._peers.get(this._toString(pubkey)) as PeerEntity;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public update(pubkey: Uint8Array, data: Partial<PeerEntity>): void {
|
||||
const peer = this.get(pubkey) ?? ({} as PeerEntity);
|
||||
|
||||
this._peers.set(this._toString(pubkey), {
|
||||
...peer,
|
||||
...data,
|
||||
...{
|
||||
messages: {
|
||||
...peer?.messages,
|
||||
...data?.messages,
|
||||
},
|
||||
},
|
||||
} as PeerEntity);
|
||||
}
|
||||
|
||||
public createSocket(options: TcpSocketConnectOpts): typeof this.socketClass {
|
||||
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;
|
||||
}
|
||||
|
||||
private async _registerOpenSocketMessage(peer: Peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: {
|
||||
preencode: this._server ? socketEncoding.preencode : json.preencode,
|
||||
encode: this._server ? socketEncoding.encode : json.encode,
|
||||
decode: this._server ? json.decode : socketEncoding.decode,
|
||||
},
|
||||
async onmessage(m: SocketRequest | TcpSocketConnectOpts) {
|
||||
if (self._server) {
|
||||
if (
|
||||
self._allowedPorts.length &&
|
||||
!self._allowedPorts.includes((m as TcpSocketConnectOpts).port)
|
||||
) {
|
||||
self.get(await self._getPublicKey(peer)).messages.errorSocket.send({
|
||||
id: (m as SocketRequest).id,
|
||||
err: new Error(
|
||||
`port ${(m as TcpSocketConnectOpts).port} not allowed`
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m = m as SocketRequest;
|
||||
|
||||
if (self._server) {
|
||||
new self.socketClass(
|
||||
nextSocketId(),
|
||||
m.id,
|
||||
self,
|
||||
self.get(await self._getPublicKey(peer)) as PeerEntity,
|
||||
m
|
||||
).connect();
|
||||
return;
|
||||
}
|
||||
|
||||
const socket = self._sockets.get(m.id);
|
||||
if (socket) {
|
||||
socket.remoteId = m.remoteId;
|
||||
// @ts-ignore
|
||||
socket.emit("connect");
|
||||
}
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { openSocket: message },
|
||||
});
|
||||
}
|
||||
|
||||
private async _registerWriteSocketMessage(peer: Peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: writeSocketEncoding,
|
||||
onmessage(m: WriteSocketRequest) {
|
||||
self._sockets.get(m.id)?.push(b4a.from(m.data));
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { writeSocket: message },
|
||||
});
|
||||
}
|
||||
|
||||
private async _registerCloseSocketMessage(peer: Peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: socketEncoding,
|
||||
onmessage(m: CloseSocketRequest) {
|
||||
self._sockets.get(m.id)?.end();
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { closeSocket: message },
|
||||
});
|
||||
}
|
||||
|
||||
private async _registerTimeoutSocketMessage(peer: Peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: socketEncoding,
|
||||
onmessage(m: SocketRequest) {
|
||||
// @ts-ignore
|
||||
self._sockets.get(m.id)?.emit("timeout");
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { timeoutSocket: message },
|
||||
});
|
||||
}
|
||||
|
||||
private async _registerErrorSocketMessage(peer: Peer) {
|
||||
const self = this;
|
||||
const message = await peer.channel.addMessage({
|
||||
encoding: errorSocketEncoding,
|
||||
onmessage(m: ErrorSocketRequest) {
|
||||
// @ts-ignore
|
||||
self._sockets.get(m.id)?.emit("error", m.err);
|
||||
},
|
||||
});
|
||||
this.update(await this._getPublicKey(peer), {
|
||||
messages: { errorSocket: message },
|
||||
});
|
||||
}
|
||||
|
||||
private _toString(pubkey: Uint8Array) {
|
||||
return b4a.from(pubkey).toString("hex");
|
||||
}
|
||||
|
||||
private async _getPublicKey(peer: Peer) {
|
||||
return maybeGetAsyncProperty(peer.stream.remotePublicKey);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
import { Callback, Duplex } from "streamx";
|
||||
import { TcpSocketConnectOpts } from "net";
|
||||
import { clearTimeout } from "timers";
|
||||
import MultiSocketProxy from "../multiSocket.js";
|
||||
import { PeerEntity, SocketRequest, WriteSocketRequest } from "./types.js";
|
||||
import { maybeGetAsyncProperty } from "../../util.js";
|
||||
import Socket, { SocketOptions } from "../../socket.js";
|
||||
|
||||
export default class DummySocket extends Socket {
|
||||
private _options: TcpSocketConnectOpts;
|
||||
private _id: number;
|
||||
private _proxy: MultiSocketProxy;
|
||||
|
||||
private _connectTimeout?: number;
|
||||
|
||||
constructor(
|
||||
id: number,
|
||||
manager: MultiSocketProxy,
|
||||
peer: PeerEntity,
|
||||
connectOptions: TcpSocketConnectOpts,
|
||||
socketOptions: SocketOptions
|
||||
) {
|
||||
super(socketOptions);
|
||||
this._id = id;
|
||||
this._proxy = manager;
|
||||
this._peer = peer;
|
||||
this._options = connectOptions;
|
||||
|
||||
// @ts-ignore
|
||||
this.on("timeout", () => {
|
||||
if (this._connectTimeout) {
|
||||
clearTimeout(this._connectTimeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _remoteId = 0;
|
||||
|
||||
set remoteId(value: number) {
|
||||
this._remoteId = value;
|
||||
this._proxy.socketMap.set(this._id, value);
|
||||
}
|
||||
|
||||
private _peer;
|
||||
|
||||
get peer() {
|
||||
return this._peer;
|
||||
}
|
||||
|
||||
public async _write(data: any, cb: any): Promise<void> {
|
||||
(await maybeGetAsyncProperty(this._peer.messages.writeSocket))?.send({
|
||||
id: this._id,
|
||||
remoteId: this._remoteId,
|
||||
data,
|
||||
} as WriteSocketRequest);
|
||||
cb();
|
||||
}
|
||||
|
||||
public async _destroy(cb: Callback) {
|
||||
(await maybeGetAsyncProperty(this._peer.messages.closeSocket))?.send({
|
||||
id: this._id,
|
||||
remoteId: this._remoteId,
|
||||
} as SocketRequest);
|
||||
this._proxy.socketMap.delete(this._id);
|
||||
this._proxy.sockets.delete(this._id);
|
||||
}
|
||||
|
||||
public async connect() {
|
||||
(await maybeGetAsyncProperty(this._peer.messages.openSocket))?.send({
|
||||
...this._options,
|
||||
id: this._id,
|
||||
});
|
||||
}
|
||||
|
||||
public setTimeout(ms: number, cb: Function) {
|
||||
if (this._connectTimeout) {
|
||||
clearTimeout(this._connectTimeout);
|
||||
}
|
||||
|
||||
this._connectTimeout = setTimeout(() => {
|
||||
cb && cb();
|
||||
}, ms) as any;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import BasePeer from "../../peer.js";
|
||||
import Socket from "../../socket.js";
|
||||
import MultiSocketProxy from "../multiSocket.js";
|
||||
|
||||
export default class Peer extends BasePeer {
|
||||
protected async initMessages(): Promise<void> {
|
||||
await this._proxy.handleNewPeerChannel(this);
|
||||
}
|
||||
protected declare _proxy: MultiSocketProxy;
|
||||
protected async initSocket() {}
|
||||
|
||||
get stream(): any {
|
||||
return this._muxer.stream;
|
||||
}
|
||||
|
||||
protected async handleChannelOnClose(socket: Socket): Promise<void> {
|
||||
return this._proxy.handleClosePeer(this);
|
||||
}
|
||||
|
||||
protected async handleChannelOnOpen(m: any): Promise<void> {}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import { Callback, Duplex } from "streamx";
|
||||
import { Socket, TcpSocketConnectOpts } from "net";
|
||||
import MultiSocketProxy from "../multiSocket.js";
|
||||
import { PeerEntity, SocketRequest, WriteSocketRequest } from "./types.js";
|
||||
import * as net from "net";
|
||||
import BaseSocket from "../../socket.js";
|
||||
|
||||
export default class TcpSocket extends BaseSocket {
|
||||
private _options;
|
||||
private _id: number;
|
||||
private _remoteId: number;
|
||||
private _proxy: MultiSocketProxy;
|
||||
|
||||
private _socket?: Socket;
|
||||
|
||||
constructor(
|
||||
id: number,
|
||||
remoteId: number,
|
||||
manager: MultiSocketProxy,
|
||||
peer: PeerEntity,
|
||||
options: TcpSocketConnectOpts
|
||||
) {
|
||||
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);
|
||||
}
|
||||
|
||||
private _peer;
|
||||
|
||||
get peer() {
|
||||
return this._peer;
|
||||
}
|
||||
|
||||
public _write(data: any, cb: any): void {
|
||||
this._peer.messages.writeSocket?.send({
|
||||
...this._getSocketRequest(),
|
||||
data,
|
||||
} as WriteSocketRequest);
|
||||
cb();
|
||||
}
|
||||
|
||||
public _destroy(cb: Callback) {
|
||||
this._proxy.sockets.delete(this._id);
|
||||
this._proxy.socketMap.delete(this._id);
|
||||
this._peer.messages.closeSocket?.send(this._getSocketRequest());
|
||||
}
|
||||
|
||||
public connect() {
|
||||
this.on("error", (err: Error) => {
|
||||
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: any) =>
|
||||
this.emit(event as any, ...args)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
this._socket.pipe(this as any);
|
||||
this.pipe(this._socket);
|
||||
}
|
||||
|
||||
private _getSocketRequest(): SocketRequest {
|
||||
return {
|
||||
id: this._id,
|
||||
remoteId: this._remoteId,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import { ProxyOptions } from "../../proxy.js";
|
||||
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<PeerEntityMessages>;
|
||||
peer: Peer;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
import Protomux from "protomux";
|
||||
import { DataSocketOptions, PeerOptions } from "./peer.js";
|
||||
import EventEmitter from "events";
|
||||
|
||||
export interface ProxyOptions extends DataSocketOptions {
|
||||
swarm: any;
|
||||
protocol: string;
|
||||
listen?: boolean;
|
||||
autostart?: boolean;
|
||||
}
|
||||
|
||||
export default abstract class Proxy extends EventEmitter {
|
||||
protected _listen: any;
|
||||
protected _autostart: boolean;
|
||||
|
||||
constructor({
|
||||
swarm,
|
||||
protocol,
|
||||
onopen,
|
||||
onreceive,
|
||||
onsend,
|
||||
onclose,
|
||||
onchannel,
|
||||
listen = false,
|
||||
autostart = false,
|
||||
emulateWebsocket = false,
|
||||
}: ProxyOptions) {
|
||||
super();
|
||||
this._swarm = swarm;
|
||||
this._protocol = protocol;
|
||||
this._listen = listen;
|
||||
this._autostart = autostart;
|
||||
this._socketOptions = {
|
||||
onopen,
|
||||
onreceive,
|
||||
onsend,
|
||||
onclose,
|
||||
onchannel,
|
||||
emulateWebsocket,
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
protected _socketOptions: DataSocketOptions;
|
||||
|
||||
get socketOptions(): DataSocketOptions {
|
||||
return this._socketOptions;
|
||||
}
|
||||
|
||||
private _swarm: any;
|
||||
|
||||
get swarm(): any {
|
||||
return this._swarm;
|
||||
}
|
||||
|
||||
private _protocol: string;
|
||||
|
||||
get protocol(): string {
|
||||
return this._protocol;
|
||||
}
|
||||
|
||||
protected abstract handlePeer({
|
||||
peer,
|
||||
muxer,
|
||||
...options
|
||||
}: DataSocketOptions & PeerOptions);
|
||||
|
||||
protected _init() {
|
||||
// Implement in subclasses
|
||||
}
|
||||
|
||||
private async init() {
|
||||
if (this._listen) {
|
||||
this._swarm.on("connection", this._handleConnection.bind(this));
|
||||
}
|
||||
await this._init();
|
||||
}
|
||||
|
||||
private _handleConnection(peer: any) {
|
||||
const muxer = Protomux.from(peer);
|
||||
const handlePeer = this.handlePeer.bind(this, {
|
||||
peer,
|
||||
muxer,
|
||||
...this._socketOptions,
|
||||
});
|
||||
|
||||
if (this._autostart) {
|
||||
handlePeer();
|
||||
return;
|
||||
}
|
||||
|
||||
muxer.pair(this._protocol, handlePeer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import EventEmitter from "events";
|
||||
|
||||
export default class Server extends EventEmitter {
|
||||
address() {
|
||||
return {
|
||||
address: "127.0.0.1",
|
||||
family: "IPv4",
|
||||
port: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async close() {
|
||||
return;
|
||||
}
|
||||
|
||||
async getConnections() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
async listen(...args: any[]) {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
import { Duplex, DuplexEvents, Callback } from "streamx";
|
||||
import { write } from "fs";
|
||||
|
||||
const IPV4 = "IPv4";
|
||||
const IPV6 = "IPv6";
|
||||
|
||||
type AddressFamily = "IPv6" | "IPv4";
|
||||
|
||||
export interface SocketOptions {
|
||||
allowHalfOpen?: boolean;
|
||||
remoteAddress?: string;
|
||||
remotePort?: number;
|
||||
remotePublicKey?: Uint8Array;
|
||||
write?: (
|
||||
this: Duplex<any, any, any, any, true, true, DuplexEvents<any, any>>,
|
||||
data: any,
|
||||
cb: Callback
|
||||
) => void;
|
||||
emulateWebsocket?: boolean;
|
||||
}
|
||||
|
||||
export default class Socket extends Duplex {
|
||||
private _allowHalfOpen: boolean;
|
||||
public remoteAddress: any;
|
||||
public remotePort: any;
|
||||
public remoteFamily: AddressFamily;
|
||||
|
||||
public bufferSize;
|
||||
|
||||
declare readable: true;
|
||||
declare writable: true;
|
||||
public remotePublicKey: Uint8Array;
|
||||
private _emulateWebsocket: boolean;
|
||||
|
||||
declare addEventListener?: typeof this.addListener;
|
||||
declare removeEventListener?: typeof this.removeListener;
|
||||
declare send?: typeof this.write;
|
||||
declare close?: typeof this.end;
|
||||
|
||||
constructor({
|
||||
allowHalfOpen = false,
|
||||
remoteAddress,
|
||||
remotePort,
|
||||
remotePublicKey,
|
||||
write,
|
||||
emulateWebsocket = false,
|
||||
}: SocketOptions = {}) {
|
||||
super({ write });
|
||||
this._allowHalfOpen = allowHalfOpen;
|
||||
this.remoteAddress = remoteAddress;
|
||||
this.remotePort = remotePort;
|
||||
this.remotePublicKey = remotePublicKey;
|
||||
this._emulateWebsocket = emulateWebsocket;
|
||||
|
||||
if (remoteAddress) {
|
||||
const type = Socket.isIP(remoteAddress);
|
||||
if (!type) {
|
||||
throw Error("invalid remoteAddress");
|
||||
}
|
||||
|
||||
this.remoteFamily = type === 6 ? IPV6 : IPV4;
|
||||
}
|
||||
|
||||
if (this._emulateWebsocket) {
|
||||
this.addEventListener = this.addListener;
|
||||
this.removeEventListener = this.removeListener;
|
||||
this.send = this.write;
|
||||
this.close = this.end;
|
||||
this.addEventListener("data", (data: any) =>
|
||||
// @ts-ignore
|
||||
this.emit("message", new MessageEvent("data", { data }))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _connecting: boolean;
|
||||
|
||||
get connecting(): boolean {
|
||||
return this._connecting;
|
||||
}
|
||||
|
||||
get readyState(): string | number {
|
||||
if (this._emulateWebsocket) {
|
||||
if (this._connecting) {
|
||||
return 0;
|
||||
} else if (this.readable && this.writable) {
|
||||
return 1;
|
||||
} else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
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: boolean) {}
|
||||
|
||||
setKeepAlive(setting: any, msecs: number) {}
|
||||
|
||||
address() {
|
||||
return {
|
||||
address: this.remoteAddress,
|
||||
port: this.remotePort,
|
||||
family: this.remoteFamily,
|
||||
};
|
||||
}
|
||||
|
||||
static isIP(input: string): number {
|
||||
if (Socket.isIPv4(input)) {
|
||||
return 4;
|
||||
} else if (Socket.isIPv6(input)) {
|
||||
return 6;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static isIPv4(input: string) {
|
||||
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: string) {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
export function idFactory(start: number, step = 1, limit = 2 ** 32) {
|
||||
let id = start;
|
||||
|
||||
return function nextId() {
|
||||
const nextId = id;
|
||||
id += step;
|
||||
if (id >= limit) id = start;
|
||||
return nextId;
|
||||
};
|
||||
}
|
||||
export function roundRobinFactory<T>(list: Map<string, any>) {
|
||||
let index = 0;
|
||||
|
||||
return (): T => {
|
||||
const keys = [...list.keys()].sort();
|
||||
if (index >= keys.length) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
return list.get(keys[index++]);
|
||||
};
|
||||
}
|
||||
export async function maybeGetAsyncProperty(object: any) {
|
||||
if (typeof object === "function") {
|
||||
object = object();
|
||||
}
|
||||
|
||||
if (isPromise(object)) {
|
||||
object = await object;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
export function isPromise(obj: Promise<any>) {
|
||||
return (
|
||||
!!obj &&
|
||||
(typeof obj === "object" || typeof obj === "function") &&
|
||||
typeof obj.then === "function"
|
||||
);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue