hyperswarm-web/src/index.ts

185 lines
4.4 KiB
TypeScript
Raw Normal View History

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";
import randomNumber from "random-number-csprng";
2023-01-31 10:43:29 +00:00
import EventEmitter from "eventemitter2";
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;
private _queuedEmActions: [string, any][] = [];
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
}
ready(): 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-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-01-31 10:10:12 +00:00
do {
const index =
relays.length > 1 ? await randomNumber(0, relays.length - 1) : 0;
2023-01-31 10:10:12 +00:00
const relay = relays[index];
2022-07-20 05:55:44 +00:00
2023-01-31 10:10:12 +00:00
let ret;
try {
ret = await this._discovery.discover(relay);
} catch (e) {
logErr(e);
relays.splice(index, 1);
continue;
}
2022-07-20 05:55:44 +00:00
if (!ret) {
relays.splice(index, 1);
continue;
}
2023-01-31 10:10:12 +00:00
ret = ret as Peer;
2022-06-27 22:21:31 +00:00
2023-01-31 10:10:12 +00:00
const connection = `wss://${ret.host}:${ret.port}`;
2022-07-20 05:55:44 +00:00
2023-01-31 10:10:12 +00:00
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,
});
2022-07-20 05:55:44 +00:00
2023-01-31 10:10:12 +00:00
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) {
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
this._processQueuedActions();
await this._activeRelay.dht.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();
}
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);
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 {
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);
}
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
}