2022-06-27 22:21:31 +00:00
|
|
|
// @ts-ignore
|
2022-07-20 05:55:44 +00:00
|
|
|
import DhtNode from "@hyperswarm/dht-relay";
|
2022-06-27 22:21:31 +00:00
|
|
|
// @ts-ignore
|
|
|
|
import Stream from "@hyperswarm/dht-relay/ws";
|
2023-01-31 10:10:12 +00:00
|
|
|
import { createClient } from "@lumeweb/kernel-peer-discovery-client";
|
|
|
|
import type {
|
|
|
|
PeerDiscoveryClient,
|
|
|
|
Peer,
|
|
|
|
} from "@lumeweb/kernel-peer-discovery-client";
|
|
|
|
import { load } from "@lumeweb/libkernel-universal";
|
2022-07-20 05:55:44 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
// @ts-ignore
|
|
|
|
import Hyperswarm from "hyperswarm";
|
2022-07-26 23:27:59 +00:00
|
|
|
import randomNumber from "random-number-csprng";
|
2023-01-31 10:43:29 +00:00
|
|
|
import EventEmitter from "eventemitter2";
|
2023-02-01 13:15:16 +00:00
|
|
|
import { Mutex } from "async-mutex";
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
export default class HyperswarmWeb extends EventEmitter {
|
2022-07-27 01:39:32 +00:00
|
|
|
private _options: any;
|
2023-01-31 10:10:12 +00:00
|
|
|
private _relays: Set<string> = new Set();
|
|
|
|
private _activeRelay: Hyperswarm;
|
|
|
|
private _discovery: PeerDiscoveryClient;
|
2023-01-31 11:58:03 +00:00
|
|
|
private _queuedEmActions: [string, any][] = [];
|
2023-02-01 13:15:16 +00:00
|
|
|
|
|
|
|
private _connectionMutex: Mutex = new Mutex();
|
2023-01-31 10:10:12 +00:00
|
|
|
constructor(opts: any = {}) {
|
|
|
|
super();
|
2022-07-27 01:39:32 +00:00
|
|
|
opts.custodial = false;
|
|
|
|
this._options = opts;
|
2023-01-31 10:10:12 +00:00
|
|
|
this._discovery = createClient();
|
2022-07-27 01:39:32 +00:00
|
|
|
}
|
|
|
|
|
2023-02-01 13:22:30 +00:00
|
|
|
init(): Promise<void> {
|
2023-01-31 10:10:12 +00:00
|
|
|
return this.ensureConnection();
|
2022-08-14 11:25:16 +00:00
|
|
|
}
|
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
private async ensureConnection(): Promise<any> {
|
|
|
|
const logErr = (await load()).logErr;
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-02-01 13:15:16 +00:00
|
|
|
await this._connectionMutex.waitForUnlock();
|
|
|
|
this._connectionMutex.acquire();
|
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
if (this._activeRelay) {
|
|
|
|
return;
|
2022-07-27 01:39:32 +00:00
|
|
|
}
|
2022-07-20 05:55:44 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
const relays = this.relays;
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-02-01 13:15:16 +00:00
|
|
|
if (relays.length > 0) {
|
|
|
|
do {
|
|
|
|
const index =
|
|
|
|
relays.length > 1 ? await randomNumber(0, relays.length - 1) : 0;
|
|
|
|
const relay = relays[index];
|
|
|
|
|
|
|
|
let ret;
|
|
|
|
try {
|
|
|
|
ret = await this._discovery.discover(relay);
|
|
|
|
} catch (e) {
|
|
|
|
logErr(e);
|
|
|
|
relays.splice(index, 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
relays.splice(index, 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = ret as Peer;
|
|
|
|
|
|
|
|
const connection = `wss://${ret.host}:${ret.port}`;
|
|
|
|
|
|
|
|
if (!(await this.isServerAvailable(connection))) {
|
|
|
|
relays.splice(index, 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._activeRelay = new Hyperswarm({
|
|
|
|
dht: new DhtNode(
|
|
|
|
new Stream(true, new WebSocket(connection)),
|
|
|
|
this._options
|
|
|
|
),
|
|
|
|
keyPair: this._options.keyPair,
|
|
|
|
});
|
|
|
|
|
|
|
|
this._activeRelay.on("close", () => {
|
|
|
|
this._activeRelay = undefined;
|
|
|
|
});
|
|
|
|
} while (relays.length > 0 && !this._activeRelay);
|
|
|
|
}
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
if (!this._activeRelay) {
|
2023-02-01 13:15:16 +00:00
|
|
|
this._connectionMutex.release();
|
2023-01-31 10:10:12 +00:00
|
|
|
throw new Error("Failed to find an available relay");
|
2022-07-20 05:55:44 +00:00
|
|
|
}
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-01-31 11:58:03 +00:00
|
|
|
this._processQueuedActions();
|
2023-02-01 10:00:51 +00:00
|
|
|
await this._activeRelay.dht.ready();
|
2023-02-01 13:15:16 +00:00
|
|
|
this._connectionMutex.release();
|
2023-02-01 13:29:35 +00:00
|
|
|
|
|
|
|
this.emit("ready");
|
2022-07-27 01:39:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private async isServerAvailable(connection: string): Promise<boolean> {
|
|
|
|
return new Promise<boolean>((resolve) => {
|
|
|
|
const ws = new WebSocket(connection);
|
|
|
|
ws.addEventListener("open", () => {
|
|
|
|
ws.close();
|
|
|
|
resolve(true);
|
|
|
|
});
|
|
|
|
ws.addEventListener("error", () => {
|
|
|
|
resolve(false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
async connect(pubkey: string, options = {}): Promise<DhtNode> {
|
2023-01-31 10:10:12 +00:00
|
|
|
if (!this._activeRelay) {
|
|
|
|
await this.ensureConnection();
|
2022-07-27 04:37:30 +00:00
|
|
|
}
|
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
return this._activeRelay.connect(pubkey, options);
|
2022-07-27 01:39:32 +00:00
|
|
|
}
|
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
get relays(): string[] {
|
|
|
|
return [...this._relays.values()];
|
|
|
|
}
|
2022-07-27 01:39:32 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
public async addRelay(pubkey: string): Promise<void> {
|
|
|
|
this._relays.add(pubkey);
|
|
|
|
}
|
2022-07-27 01:39:32 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
public removeRelay(pubkey: string): boolean {
|
|
|
|
if (!this._relays.has(pubkey)) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-27 01:39:32 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
this._relays.delete(pubkey);
|
2022-08-14 00:04:35 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
return true;
|
|
|
|
}
|
2022-07-27 01:39:32 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
public clearRelays(): void {
|
|
|
|
this._relays.clear();
|
2022-07-27 01:39:32 +00:00
|
|
|
}
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
on(
|
|
|
|
eventName: string | symbol,
|
|
|
|
listener: (...args: any[]) => void
|
|
|
|
): Hyperswarm {
|
2023-02-01 09:50:55 +00:00
|
|
|
return this._processOrQueueAction("on", ...arguments);
|
2023-01-31 10:10:12 +00:00
|
|
|
}
|
|
|
|
addListener(
|
|
|
|
eventName: string | symbol,
|
|
|
|
listener: (...args: any[]) => void
|
|
|
|
): this {
|
|
|
|
return this.on(eventName, listener);
|
|
|
|
}
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
off(
|
|
|
|
eventName: string | symbol,
|
|
|
|
listener: (...args: any[]) => void
|
|
|
|
): Hyperswarm {
|
2023-02-01 09:50:55 +00:00
|
|
|
return this._processOrQueueAction("off", ...arguments);
|
2023-01-31 10:10:12 +00:00
|
|
|
}
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
removeListener(
|
|
|
|
eventName: string | symbol,
|
|
|
|
listener: (...args: any[]) => void
|
|
|
|
): this {
|
2023-01-31 11:58:03 +00:00
|
|
|
return this.off(eventName, listener);
|
2023-01-31 10:10:12 +00:00
|
|
|
}
|
|
|
|
emit(eventName: string | symbol, ...args: any[]): boolean {
|
2023-02-01 09:50:55 +00:00
|
|
|
return this._processOrQueueAction("emit", ...arguments);
|
2023-01-31 10:10:12 +00:00
|
|
|
}
|
2022-06-27 22:21:31 +00:00
|
|
|
|
2023-01-31 10:10:12 +00:00
|
|
|
once(eventName: string | symbol, listener: (...args: any[]) => void): this {
|
2023-02-01 09:50:55 +00:00
|
|
|
return this._processOrQueueAction("once", ...arguments);
|
2023-01-31 11:58:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private _processOrQueueAction(method: string, ...args: any[]) {
|
|
|
|
if (this._activeRelay) {
|
|
|
|
return this._activeRelay[method](...args);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._queuedEmActions.push([method, args]);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _processQueuedActions(): void {
|
|
|
|
for (const action of this._queuedEmActions) {
|
|
|
|
this._activeRelay[action[0]](...action[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._queuedEmActions = [];
|
2022-07-27 01:39:32 +00:00
|
|
|
}
|
2022-06-27 22:21:31 +00:00
|
|
|
}
|