diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..9d36df6 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,4 @@ +export * from "./rpcNetwork.js"; +export * from "./rpcQuery.js"; +export * from "./types"; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map new file mode 100644 index 0000000..a62e5e0 --- /dev/null +++ b/dist/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..66e765c --- /dev/null +++ b/dist/index.js @@ -0,0 +1,3 @@ +export * from "./rpcNetwork.js"; +export * from "./rpcQuery.js"; +export * from "./types"; diff --git a/dist/rpcNetwork.d.ts b/dist/rpcNetwork.d.ts new file mode 100644 index 0000000..a7a334c --- /dev/null +++ b/dist/rpcNetwork.d.ts @@ -0,0 +1,25 @@ +import RpcQuery from "./rpcQuery.js"; +export default class RpcNetwork { + private _dht; + private _majorityThreshold; + private _maxTtl; + private _queryTimeout; + private _relays; + private _ready; + private _force; + constructor(dht?: any); + get ready(): Promise; + get relays(): string[]; + get dht(): any; + get maxTtl(): number; + set maxTtl(value: number); + get queryTimeout(): number; + set queryTimeout(value: number); + get majorityThreshold(): number; + set majorityThreshold(value: number); + get force(): boolean; + set force(value: boolean); + addRelay(pubkey: string): void; + query(query: string, chain: string, data?: object | any[], force?: boolean): RpcQuery; +} +//# sourceMappingURL=rpcNetwork.d.ts.map \ No newline at end of file diff --git a/dist/rpcNetwork.d.ts.map b/dist/rpcNetwork.d.ts.map new file mode 100644 index 0000000..1755bba --- /dev/null +++ b/dist/rpcNetwork.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"rpcNetwork.d.ts","sourceRoot":"","sources":["../src/rpcNetwork.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,eAAe,CAAC;AAMrC,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAkB;gBAEpB,GAAG,MAAY;IAK3B,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAEzB;IAED,IAAI,MAAM,IAAI,MAAM,EAAE,CAErB;IAED,IAAI,GAAG,QAEN;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAEvB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAE7B;IAED,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,IAAI,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAElC;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,EAEvB;IAEM,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK9B,KAAK,CACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,MAAM,GAAG,GAAG,EAAO,EACzB,KAAK,GAAE,OAAe,GACrB,QAAQ;CAQZ"} \ No newline at end of file diff --git a/dist/rpcNetwork.js b/dist/rpcNetwork.js new file mode 100644 index 0000000..af482b5 --- /dev/null +++ b/dist/rpcNetwork.js @@ -0,0 +1,61 @@ +// tslint:disable:no-var-requires +import { createRequire } from "module"; +import RpcQuery from "./rpcQuery.js"; +const require = createRequire(import.meta.url); +const DHT = require("@hyperswarm/dht"); +export default class RpcNetwork { + constructor(dht = new DHT()) { + this._majorityThreshold = 0.75; + this._maxTtl = 12 * 60 * 60; + this._queryTimeout = 30; + this._relays = []; + this._force = false; + this._dht = dht; + this._ready = this._dht.ready(); + } + get ready() { + return this._ready; + } + get relays() { + return this._relays; + } + get dht() { + return this._dht; + } + get maxTtl() { + return this._maxTtl; + } + set maxTtl(value) { + this._maxTtl = value; + } + get queryTimeout() { + return this._queryTimeout; + } + set queryTimeout(value) { + this._queryTimeout = value; + } + get majorityThreshold() { + return this._majorityThreshold; + } + set majorityThreshold(value) { + this._majorityThreshold = value; + } + get force() { + return this._force; + } + set force(value) { + this._force = value; + } + addRelay(pubkey) { + this._relays.push(pubkey); + this._relays = [...new Set(this._relays)]; + } + query(query, chain, data = {}, force = false) { + return new RpcQuery(this, { + query, + chain, + data, + force: force || this._force, + }); + } +} diff --git a/dist/rpcQuery.d.ts b/dist/rpcQuery.d.ts new file mode 100644 index 0000000..36c4da3 --- /dev/null +++ b/dist/rpcQuery.d.ts @@ -0,0 +1,21 @@ +import RpcNetwork from "./rpcNetwork.js"; +import { RPCRequest } from "./types"; +export default class RpcQuery { + private _network; + private _query; + private _promise?; + private _timeoutTimer?; + private _timeout; + private _completed; + private _responses; + private _promiseResolve?; + constructor(network: RpcNetwork, query: RPCRequest); + get promise(): Promise; + private handeTimeout; + private resolve; + private init; + private queryRelay; + private checkResponses; + private retry; +} +//# sourceMappingURL=rpcQuery.d.ts.map \ No newline at end of file diff --git a/dist/rpcQuery.d.ts.map b/dist/rpcQuery.d.ts.map new file mode 100644 index 0000000..64dffc1 --- /dev/null +++ b/dist/rpcQuery.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"rpcQuery.d.ts","sourceRoot":"","sources":["../src/rpcQuery.ts"],"names":[],"mappings":"AACA,OAAO,UAAU,MAAM,iBAAiB,CAAC;AAEzC,OAAO,EAAC,UAAU,EAAc,MAAM,SAAS,CAAC;AAEhD,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,aAAa,CAAC,CAAM;IAC5B,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,eAAe,CAAC,CAAsB;gBAElC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;IAMlD,IAAI,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,CAE1B;IAED,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,OAAO;YAQD,IAAI;YA2BJ,UAAU;IAiBxB,OAAO,CAAC,cAAc;IA6BtB,OAAO,CAAC,KAAK;CASd"} \ No newline at end of file diff --git a/dist/rpcQuery.js b/dist/rpcQuery.js new file mode 100644 index 0000000..cbb1252 --- /dev/null +++ b/dist/rpcQuery.js @@ -0,0 +1,88 @@ +import { clearTimeout, setTimeout } from "timers"; +import { pack, unpack } from "msgpackr"; +export default class RpcQuery { + constructor(network, query) { + this._timeout = false; + this._completed = false; + this._responses = {}; + this._network = network; + this._query = query; + this.init(); + } + get promise() { + return this._promise; + } + handeTimeout() { + this.resolve(false, true); + } + resolve(data, timeout = false) { + clearTimeout(this._timeoutTimer); + this._timeout = timeout; + this._completed = true; + // @ts-ignore + this._promiseResolve(data); + } + async init() { + this._promise = + this._promise ?? + new Promise((resolve) => { + this._promiseResolve = resolve; + }); + this._timeoutTimer = + this._timeoutTimer ?? + setTimeout(this.handeTimeout.bind(this), this._network.queryTimeout * 1000); + await this._network.ready; + const promises = []; + // tslint:disable-next-line:forin + for (const relay of this._network.relays) { + promises.push(this.queryRelay(relay)); + } + await Promise.allSettled(promises); + this.checkResponses(); + } + async queryRelay(relay) { + const socket = this._network.dht.connect(Buffer.from(relay, "hex")); + return new Promise((resolve, reject) => { + socket.on("data", (res) => { + socket.end(); + const response = unpack(res); + if (response && response.error) { + return reject(response); + } + this._responses[relay] = response; + resolve(null); + }); + socket.on("error", (error) => reject({ error })); + socket.write(pack(this._query)); + }); + } + checkResponses() { + const responses = {}; + const responseStore = this._responses; + const responseStoreKeys = Object.keys(responseStore); + // tslint:disable-next-line:forin + for (const peer in responseStore) { + const responseIndex = responseStoreKeys.indexOf(peer); + responses[responseIndex] = responses[responseIndex] ?? 0; + responses[responseIndex]++; + } + for (const responseIndex in responses) { + if (responses[responseIndex] / responseStoreKeys.length >= this._network.majorityThreshold) { + const response = responseStore[responseStoreKeys[parseInt(responseIndex, 10)]]; + // @ts-ignore + if (null === response || null === response?.data) { + this.retry(); + return; + } + this.resolve(response?.data); + } + } + } + retry() { + this._responses = {}; + if (this._completed) { + return; + } + this.init(); + } +} diff --git a/dist/types.d.ts b/dist/types.d.ts new file mode 100644 index 0000000..70bf4c7 --- /dev/null +++ b/dist/types.d.ts @@ -0,0 +1,13 @@ +export interface RPCRequest { + force: boolean; + chain: string; + query: string; + data: any; +} +export interface RPCResponse { + updated: number; + data: any | { + error: string | boolean; + }; +} +//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/dist/types.d.ts.map b/dist/types.d.ts.map new file mode 100644 index 0000000..9376341 --- /dev/null +++ b/dist/types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,GAAG,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EACE,GAAG,GACH;QACF,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;KAC3B,CAAC;CACL"} \ No newline at end of file diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/types.js @@ -0,0 +1 @@ +export {};