UNPKG

@polkadot/api

Version:

Promise and RxJS wrappers around the Polkadot JS RPC

1,249 lines (1,232 loc) 730 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@polkadot/keyring'), require('@polkadot/util'), require('@polkadot/types'), require('@polkadot/util-crypto')) : typeof define === 'function' && define.amd ? define(['exports', '@polkadot/keyring', '@polkadot/util', '@polkadot/types', '@polkadot/util-crypto'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.polkadotApi = {}, global.polkadotKeyring, global.polkadotUtil, global.polkadotTypes, global.polkadotUtilCrypto)); })(this, (function (exports, keyring, util, types, utilCrypto) { 'use strict'; const global = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : window; var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function evaluateThis(fn) { return fn('return this'); } const xglobal = (typeof globalThis !== 'undefined' ? globalThis : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : evaluateThis(Function)); const fetch = xglobal.fetch; const UNKNOWN = -99999; function extend(that, name, value) { Object.defineProperty(that, name, { configurable: true, enumerable: false, value }); } class RpcError extends Error { code; data; message; name; stack; constructor(message = '', code = UNKNOWN, data) { super(); extend(this, 'message', String(message)); extend(this, 'name', this.constructor.name); extend(this, 'data', data); extend(this, 'code', code); if (util.isFunction(Error.captureStackTrace)) { Error.captureStackTrace(this, this.constructor); } else { const { stack } = new Error(message); stack && extend(this, 'stack', stack); } } static CODES = { ASSERT: -90009, INVALID_JSONRPC: -99998, METHOD_NOT_FOUND: -32601, UNKNOWN }; } function formatErrorData(data) { if (util.isUndefined(data)) { return ''; } const formatted = `: ${util.isString(data) ? data.replace(/Error\("/g, '').replace(/\("/g, '(').replace(/"\)/g, ')').replace(/\(/g, ', ').replace(/\)/g, '') : util.stringify(data)}`; return formatted.length <= 256 ? formatted : `${formatted.substring(0, 255)}…`; } function checkError(error) { if (error) { const { code, data, message } = error; throw new RpcError(`${code}: ${message}${formatErrorData(data)}`, code, data); } } class RpcCoder { __internal__id = 0; decodeResponse(response) { if (!response || response.jsonrpc !== '2.0') { throw new Error('Invalid jsonrpc field in decoded object'); } const isSubscription = !util.isUndefined(response.params) && !util.isUndefined(response.method); if (!util.isNumber(response.id) && (!isSubscription || (!util.isNumber(response.params.subscription) && !util.isString(response.params.subscription)))) { throw new Error('Invalid id field in decoded object'); } checkError(response.error); if (response.result === undefined && !isSubscription) { throw new Error('No result found in jsonrpc response'); } if (isSubscription) { checkError(response.params.error); return response.params.result; } return response.result; } encodeJson(method, params) { const [id, data] = this.encodeObject(method, params); return [id, util.stringify(data)]; } encodeObject(method, params) { const id = ++this.__internal__id; return [id, { id, jsonrpc: '2.0', method, params }]; } } const HTTP_URL = 'http://127.0.0.1:9933'; const WS_URL = 'ws://127.0.0.1:9944'; const defaults = { HTTP_URL, WS_URL }; const DEFAULT_CAPACITY = 128; class LRUNode { key; next; prev; constructor(key) { this.key = key; this.next = this.prev = this; } } class LRUCache { capacity; __internal__data = new Map(); __internal__refs = new Map(); __internal__length = 0; __internal__head; __internal__tail; constructor(capacity = DEFAULT_CAPACITY) { this.capacity = capacity; this.__internal__head = this.__internal__tail = new LRUNode('<empty>'); } get length() { return this.__internal__length; } get lengthData() { return this.__internal__data.size; } get lengthRefs() { return this.__internal__refs.size; } entries() { const keys = this.keys(); const count = keys.length; const entries = new Array(count); for (let i = 0; i < count; i++) { const key = keys[i]; entries[i] = [key, this.__internal__data.get(key)]; } return entries; } keys() { const keys = []; if (this.__internal__length) { let curr = this.__internal__head; while (curr !== this.__internal__tail) { keys.push(curr.key); curr = curr.next; } keys.push(curr.key); } return keys; } get(key) { const data = this.__internal__data.get(key); if (data) { this.__internal__toHead(key); return data; } return null; } set(key, value) { if (this.__internal__data.has(key)) { this.__internal__toHead(key); } else { const node = new LRUNode(key); this.__internal__refs.set(node.key, node); if (this.length === 0) { this.__internal__head = this.__internal__tail = node; } else { this.__internal__head.prev = node; node.next = this.__internal__head; this.__internal__head = node; } if (this.__internal__length === this.capacity) { this.__internal__data.delete(this.__internal__tail.key); this.__internal__refs.delete(this.__internal__tail.key); this.__internal__tail = this.__internal__tail.prev; this.__internal__tail.next = this.__internal__head; } else { this.__internal__length += 1; } } this.__internal__data.set(key, value); } __internal__toHead(key) { const ref = this.__internal__refs.get(key); if (ref && ref !== this.__internal__head) { ref.prev.next = ref.next; ref.next.prev = ref.prev; ref.next = this.__internal__head; this.__internal__head.prev = ref; this.__internal__head = ref; } } } const ERROR_SUBSCRIBE = 'HTTP Provider does not have subscriptions, use WebSockets instead'; const l$7 = util.logger('api-http'); class HttpProvider { __internal__callCache = new LRUCache(); __internal__coder; __internal__endpoint; __internal__headers; __internal__stats; constructor(endpoint = defaults.HTTP_URL, headers = {}) { if (!/^(https|http):\/\//.test(endpoint)) { throw new Error(`Endpoint should start with 'http://' or 'https://', received '${endpoint}'`); } this.__internal__coder = new RpcCoder(); this.__internal__endpoint = endpoint; this.__internal__headers = headers; this.__internal__stats = { active: { requests: 0, subscriptions: 0 }, total: { bytesRecv: 0, bytesSent: 0, cached: 0, errors: 0, requests: 0, subscriptions: 0, timeout: 0 } }; } get hasSubscriptions() { return !!false; } clone() { return new HttpProvider(this.__internal__endpoint, this.__internal__headers); } async connect() { } async disconnect() { } get stats() { return this.__internal__stats; } get isClonable() { return !!true; } get isConnected() { return !!true; } on(_type, _sub) { l$7.error('HTTP Provider does not have \'on\' emitters, use WebSockets instead'); return util.noop; } async send(method, params, isCacheable) { this.__internal__stats.total.requests++; const [, body] = this.__internal__coder.encodeJson(method, params); const cacheKey = isCacheable ? `${method}::${util.stringify(params)}` : ''; let resultPromise = isCacheable ? this.__internal__callCache.get(cacheKey) : null; if (!resultPromise) { resultPromise = this.__internal__send(body); if (isCacheable) { this.__internal__callCache.set(cacheKey, resultPromise); } } else { this.__internal__stats.total.cached++; } return resultPromise; } async __internal__send(body) { this.__internal__stats.active.requests++; this.__internal__stats.total.bytesSent += body.length; try { const response = await fetch(this.__internal__endpoint, { body, headers: { Accept: 'application/json', 'Content-Length': `${body.length}`, 'Content-Type': 'application/json', ...this.__internal__headers }, method: 'POST' }); if (!response.ok) { throw new Error(`[${response.status}]: ${response.statusText}`); } const result = await response.text(); this.__internal__stats.total.bytesRecv += result.length; const decoded = this.__internal__coder.decodeResponse(JSON.parse(result)); this.__internal__stats.active.requests--; return decoded; } catch (e) { this.__internal__stats.active.requests--; this.__internal__stats.total.errors++; throw e; } } async subscribe(_types, _method, _params, _cb) { l$7.error(ERROR_SUBSCRIBE); throw new Error(ERROR_SUBSCRIBE); } async unsubscribe(_type, _method, _id) { l$7.error(ERROR_SUBSCRIBE); throw new Error(ERROR_SUBSCRIBE); } } function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var eventemitter3 = {exports: {}}; (function (module) { var has = Object.prototype.hasOwnProperty , prefix = '~'; function Events() {} if (Object.create) { Events.prototype = Object.create(null); if (!new Events().__proto__) prefix = false; } function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; } function addListener(emitter, event, fn, context, once) { if (typeof fn !== 'function') { throw new TypeError('The listener must be a function'); } var listener = new EE(fn, context || emitter, once) , evt = prefix ? prefix + event : event; if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); else emitter._events[evt] = [emitter._events[evt], listener]; return emitter; } function clearEvent(emitter, evt) { if (--emitter._eventsCount === 0) emitter._events = new Events(); else delete emitter._events[evt]; } function EventEmitter() { this._events = new Events(); this._eventsCount = 0; } EventEmitter.prototype.eventNames = function eventNames() { var names = [] , events , name; if (this._eventsCount === 0) return names; for (name in (events = this._events)) { if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); } if (Object.getOwnPropertySymbols) { return names.concat(Object.getOwnPropertySymbols(events)); } return names; }; EventEmitter.prototype.listeners = function listeners(event) { var evt = prefix ? prefix + event : event , handlers = this._events[evt]; if (!handlers) return []; if (handlers.fn) return [handlers.fn]; for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { ee[i] = handlers[i].fn; } return ee; }; EventEmitter.prototype.listenerCount = function listenerCount(event) { var evt = prefix ? prefix + event : event , listeners = this._events[evt]; if (!listeners) return 0; if (listeners.fn) return 1; return listeners.length; }; EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if (listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch (len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for (i = 1, args = new Array(len -1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { var length = listeners.length , j; for (i = 0; i < length; i++) { if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); switch (len) { case 1: listeners[i].fn.call(listeners[i].context); break; case 2: listeners[i].fn.call(listeners[i].context, a1); break; case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; default: if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { args[j - 1] = arguments[j]; } listeners[i].fn.apply(listeners[i].context, args); } } } return true; }; EventEmitter.prototype.on = function on(event, fn, context) { return addListener(this, event, fn, context, false); }; EventEmitter.prototype.once = function once(event, fn, context) { return addListener(this, event, fn, context, true); }; EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return this; if (!fn) { clearEvent(this, evt); return this; } var listeners = this._events[evt]; if (listeners.fn) { if ( listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context) ) { clearEvent(this, evt); } } else { for (var i = 0, events = [], length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]); } } if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; else clearEvent(this, evt); } return this; }; EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { var evt; if (event) { evt = prefix ? prefix + event : event; if (this._events[evt]) clearEvent(this, evt); } else { this._events = new Events(); this._eventsCount = 0; } return this; }; EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.addListener = EventEmitter.prototype.on; EventEmitter.prefixed = prefix; EventEmitter.EventEmitter = EventEmitter; { module.exports = EventEmitter; } } (eventemitter3)); var eventemitter3Exports = eventemitter3.exports; const EventEmitter = getDefaultExportFromCjs(eventemitter3Exports); function healthChecker() { let checker = null; let sendJsonRpc = null; return { responsePassThrough: (jsonRpcResponse) => { if (checker === null) { return jsonRpcResponse; } return checker.responsePassThrough(jsonRpcResponse); }, sendJsonRpc: (request) => { if (!sendJsonRpc) { throw new Error('setSendJsonRpc must be called before sending requests'); } if (checker === null) { sendJsonRpc(request); } else { checker.sendJsonRpc(request); } }, setSendJsonRpc: (cb) => { sendJsonRpc = cb; }, start: (healthCallback) => { if (checker !== null) { throw new Error("Can't start the health checker multiple times in parallel"); } else if (!sendJsonRpc) { throw new Error('setSendJsonRpc must be called before starting the health checks'); } checker = new InnerChecker(healthCallback, sendJsonRpc); checker.update(true); }, stop: () => { if (checker === null) { return; } checker.destroy(); checker = null; } }; } class InnerChecker { __internal__healthCallback; __internal__currentHealthCheckId = null; __internal__currentHealthTimeout = null; __internal__currentSubunsubRequestId = null; __internal__currentSubscriptionId = null; __internal__requestToSmoldot; __internal__isSyncing = false; __internal__nextRequestId = 0; constructor(healthCallback, requestToSmoldot) { this.__internal__healthCallback = healthCallback; this.__internal__requestToSmoldot = (request) => requestToSmoldot(util.stringify(request)); } sendJsonRpc = (request) => { let parsedRequest; try { parsedRequest = JSON.parse(request); } catch { return; } if (parsedRequest.id) { const newId = 'extern:' + util.stringify(parsedRequest.id); parsedRequest.id = newId; } this.__internal__requestToSmoldot(parsedRequest); }; responsePassThrough = (jsonRpcResponse) => { let parsedResponse; try { parsedResponse = JSON.parse(jsonRpcResponse); } catch { return jsonRpcResponse; } if (parsedResponse.id && this.__internal__currentHealthCheckId === parsedResponse.id) { this.__internal__currentHealthCheckId = null; if (!parsedResponse.result) { this.update(false); return null; } this.__internal__healthCallback(parsedResponse.result); this.__internal__isSyncing = parsedResponse.result.isSyncing; this.update(false); return null; } if (parsedResponse.id && this.__internal__currentSubunsubRequestId === parsedResponse.id) { this.__internal__currentSubunsubRequestId = null; if (!parsedResponse.result) { this.update(false); return null; } if (this.__internal__currentSubscriptionId) { this.__internal__currentSubscriptionId = null; } else { this.__internal__currentSubscriptionId = parsedResponse.result; } this.update(false); return null; } if (parsedResponse.params && this.__internal__currentSubscriptionId && parsedResponse.params.subscription === this.__internal__currentSubscriptionId) { this.update(true); return null; } if (parsedResponse.id) { const id = parsedResponse.id; if (!id.startsWith('extern:')) { throw new Error('State inconsistency in health checker'); } const newId = JSON.parse(id.slice('extern:'.length)); parsedResponse.id = newId; } return util.stringify(parsedResponse); }; update = (startNow) => { if (startNow && this.__internal__currentHealthTimeout) { clearTimeout(this.__internal__currentHealthTimeout); this.__internal__currentHealthTimeout = null; } if (!this.__internal__currentHealthTimeout) { const startHealthRequest = () => { this.__internal__currentHealthTimeout = null; if (this.__internal__currentHealthCheckId) { return; } this.__internal__currentHealthCheckId = `health-checker:${this.__internal__nextRequestId}`; this.__internal__nextRequestId += 1; this.__internal__requestToSmoldot({ id: this.__internal__currentHealthCheckId, jsonrpc: '2.0', method: 'system_health', params: [] }); }; if (startNow) { startHealthRequest(); } else { this.__internal__currentHealthTimeout = setTimeout(startHealthRequest, 1000); } } if (this.__internal__isSyncing && !this.__internal__currentSubscriptionId && !this.__internal__currentSubunsubRequestId) { this.startSubscription(); } if (!this.__internal__isSyncing && this.__internal__currentSubscriptionId && !this.__internal__currentSubunsubRequestId) { this.endSubscription(); } }; startSubscription = () => { if (this.__internal__currentSubunsubRequestId || this.__internal__currentSubscriptionId) { throw new Error('Internal error in health checker'); } this.__internal__currentSubunsubRequestId = `health-checker:${this.__internal__nextRequestId}`; this.__internal__nextRequestId += 1; this.__internal__requestToSmoldot({ id: this.__internal__currentSubunsubRequestId, jsonrpc: '2.0', method: 'chain_subscribeNewHeads', params: [] }); }; endSubscription = () => { if (this.__internal__currentSubunsubRequestId || !this.__internal__currentSubscriptionId) { throw new Error('Internal error in health checker'); } this.__internal__currentSubunsubRequestId = `health-checker:${this.__internal__nextRequestId}`; this.__internal__nextRequestId += 1; this.__internal__requestToSmoldot({ id: this.__internal__currentSubunsubRequestId, jsonrpc: '2.0', method: 'chain_unsubscribeNewHeads', params: [this.__internal__currentSubscriptionId] }); }; destroy = () => { if (this.__internal__currentHealthTimeout) { clearTimeout(this.__internal__currentHealthTimeout); this.__internal__currentHealthTimeout = null; } }; } const l$6 = util.logger('api-substrate-connect'); const subscriptionUnsubscriptionMethods = new Map([ ['author_submitAndWatchExtrinsic', 'author_unwatchExtrinsic'], ['chain_subscribeAllHeads', 'chain_unsubscribeAllHeads'], ['chain_subscribeFinalizedHeads', 'chain_unsubscribeFinalizedHeads'], ['chain_subscribeFinalisedHeads', 'chain_subscribeFinalisedHeads'], ['chain_subscribeNewHeads', 'chain_unsubscribeNewHeads'], ['chain_subscribeNewHead', 'chain_unsubscribeNewHead'], ['chain_subscribeRuntimeVersion', 'chain_unsubscribeRuntimeVersion'], ['subscribe_newHead', 'unsubscribe_newHead'], ['state_subscribeRuntimeVersion', 'state_unsubscribeRuntimeVersion'], ['state_subscribeStorage', 'state_unsubscribeStorage'] ]); const scClients = new WeakMap(); class ScProvider { __internal__Sc; __internal__coder = new RpcCoder(); __internal__spec; __internal__sharedSandbox; __internal__subscriptions = new Map(); __internal__resubscribeMethods = new Map(); __internal__requests = new Map(); __internal__wellKnownChains; __internal__eventemitter = new EventEmitter(); __internal__chain = null; __internal__isChainReady = false; constructor(Sc, spec, sharedSandbox) { if (!util.isObject(Sc) || !util.isObject(Sc.WellKnownChain) || !util.isFunction(Sc.createScClient)) { throw new Error('Expected an @substrate/connect interface as first parameter to ScProvider'); } this.__internal__Sc = Sc; this.__internal__spec = spec; this.__internal__sharedSandbox = sharedSandbox; this.__internal__wellKnownChains = new Set(Object.values(Sc.WellKnownChain)); } get hasSubscriptions() { return !!true; } get isClonable() { return !!false; } get isConnected() { return !!this.__internal__chain && this.__internal__isChainReady; } clone() { throw new Error('clone() is not supported.'); } async connect(config, checkerFactory = healthChecker) { if (this.isConnected) { throw new Error('Already connected!'); } if (this.__internal__chain) { await this.__internal__chain; return; } if (this.__internal__sharedSandbox && !this.__internal__sharedSandbox.isConnected) { await this.__internal__sharedSandbox.connect(); } const client = this.__internal__sharedSandbox ? scClients.get(this.__internal__sharedSandbox) : this.__internal__Sc.createScClient(config); if (!client) { throw new Error('Unknown ScProvider!'); } scClients.set(this, client); const hc = checkerFactory(); const onResponse = (res) => { const hcRes = hc.responsePassThrough(res); if (!hcRes) { return; } const response = JSON.parse(hcRes); let decodedResponse; try { decodedResponse = this.__internal__coder.decodeResponse(response); } catch (e) { decodedResponse = e; } if (response.params?.subscription === undefined || !response.method) { return this.__internal__requests.get(response.id)?.(decodedResponse); } const subscriptionId = `${response.method}::${response.params.subscription}`; const callback = this.__internal__subscriptions.get(subscriptionId)?.[0]; callback?.(decodedResponse); }; const addChain = this.__internal__sharedSandbox ? (async (...args) => { const source = this.__internal__sharedSandbox; return (await source.__internal__chain).addChain(...args); }) : this.__internal__wellKnownChains.has(this.__internal__spec) ? client.addWellKnownChain : client.addChain; this.__internal__chain = addChain(this.__internal__spec, onResponse).then((chain) => { hc.setSendJsonRpc(chain.sendJsonRpc); this.__internal__isChainReady = false; const cleanup = () => { const disconnectionError = new Error('Disconnected'); this.__internal__requests.forEach((cb) => cb(disconnectionError)); this.__internal__subscriptions.forEach(([cb]) => cb(disconnectionError)); this.__internal__subscriptions.clear(); }; const staleSubscriptions = []; const killStaleSubscriptions = () => { if (staleSubscriptions.length === 0) { return; } const stale = staleSubscriptions.pop(); if (!stale) { throw new Error('Unable to get stale subscription'); } const { id, unsubscribeMethod } = stale; Promise .race([ this.send(unsubscribeMethod, [id]).catch(util.noop), new Promise((resolve) => setTimeout(resolve, 500)) ]) .then(killStaleSubscriptions) .catch(util.noop); }; hc.start((health) => { const isReady = !health.isSyncing && (health.peers > 0 || !health.shouldHavePeers); if (this.__internal__isChainReady === isReady) { return; } this.__internal__isChainReady = isReady; if (!isReady) { [...this.__internal__subscriptions.values()].forEach((s) => { staleSubscriptions.push(s[1]); }); cleanup(); this.__internal__eventemitter.emit('disconnected'); } else { killStaleSubscriptions(); this.__internal__eventemitter.emit('connected'); if (this.__internal__resubscribeMethods.size) { this.__internal__resubscribe(); } } }); return util.objectSpread({}, chain, { remove: () => { hc.stop(); chain.remove(); cleanup(); }, sendJsonRpc: hc.sendJsonRpc.bind(hc) }); }); try { await this.__internal__chain; } catch (e) { this.__internal__chain = null; this.__internal__eventemitter.emit('error', e); throw e; } } __internal__resubscribe = () => { const promises = []; this.__internal__resubscribeMethods.forEach((subDetails) => { if (subDetails.type.startsWith('author_')) { return; } try { const promise = new Promise((resolve) => { this.subscribe(subDetails.type, subDetails.method, subDetails.params, subDetails.callback).catch((error) => console.log(error)); resolve(); }); promises.push(promise); } catch (error) { l$6.error(error); } }); Promise.all(promises).catch((err) => l$6.log(err)); }; async disconnect() { if (!this.__internal__chain) { return; } const chain = await this.__internal__chain; this.__internal__chain = null; this.__internal__isChainReady = false; try { chain.remove(); } catch (_) { } this.__internal__eventemitter.emit('disconnected'); } on(type, sub) { if (type === 'connected' && this.isConnected) { sub(); } this.__internal__eventemitter.on(type, sub); return () => { this.__internal__eventemitter.removeListener(type, sub); }; } async send(method, params) { if (!this.isConnected || !this.__internal__chain) { throw new Error('Provider is not connected'); } const chain = await this.__internal__chain; const [id, json] = this.__internal__coder.encodeJson(method, params); const result = new Promise((resolve, reject) => { this.__internal__requests.set(id, (response) => { (util.isError(response) ? reject : resolve)(response); }); try { chain.sendJsonRpc(json); } catch (e) { this.__internal__chain = null; try { chain.remove(); } catch (_) { } this.__internal__eventemitter.emit('error', e); } }); try { return await result; } finally { this.__internal__requests.delete(id); } } async subscribe(type, method, params, callback) { if (!subscriptionUnsubscriptionMethods.has(method)) { throw new Error(`Unsupported subscribe method: ${method}`); } const id = await this.send(method, params); const subscriptionId = `${type}::${id}`; const cb = (response) => { if (response instanceof Error) { callback(response, undefined); } else { callback(null, response); } }; const unsubscribeMethod = subscriptionUnsubscriptionMethods.get(method); if (!unsubscribeMethod) { throw new Error('Invalid unsubscribe method found'); } this.__internal__resubscribeMethods.set(subscriptionId, { callback, method, params, type }); this.__internal__subscriptions.set(subscriptionId, [cb, { id, unsubscribeMethod }]); return id; } unsubscribe(type, method, id) { if (!this.isConnected) { throw new Error('Provider is not connected'); } const subscriptionId = `${type}::${id}`; if (!this.__internal__subscriptions.has(subscriptionId)) { return Promise.reject(new Error(`Unable to find active subscription=${subscriptionId}`)); } this.__internal__resubscribeMethods.delete(subscriptionId); this.__internal__subscriptions.delete(subscriptionId); return this.send(method, [id]); } } const WebSocket = xglobal.WebSocket; const known = { 1000: 'Normal Closure', 1001: 'Going Away', 1002: 'Protocol Error', 1003: 'Unsupported Data', 1004: '(For future)', 1005: 'No Status Received', 1006: 'Abnormal Closure', 1007: 'Invalid frame payload data', 1008: 'Policy Violation', 1009: 'Message too big', 1010: 'Missing Extension', 1011: 'Internal Error', 1012: 'Service Restart', 1013: 'Try Again Later', 1014: 'Bad Gateway', 1015: 'TLS Handshake' }; function getWSErrorString(code) { if (code >= 0 && code <= 999) { return '(Unused)'; } else if (code >= 1016) { if (code <= 1999) { return '(For WebSocket standard)'; } else if (code <= 2999) { return '(For WebSocket extensions)'; } else if (code <= 3999) { return '(For libraries and frameworks)'; } else if (code <= 4999) { return '(For applications)'; } } return known[code] || '(Unknown)'; } const ALIASES = { chain_finalisedHead: 'chain_finalizedHead', chain_subscribeFinalisedHeads: 'chain_subscribeFinalizedHeads', chain_unsubscribeFinalisedHeads: 'chain_unsubscribeFinalizedHeads' }; const RETRY_DELAY = 2500; const DEFAULT_TIMEOUT_MS = 60 * 1000; const TIMEOUT_INTERVAL = 5000; const l$5 = util.logger('api-ws'); function eraseRecord(record, cb) { Object.keys(record).forEach((key) => { if (cb) { cb(record[key]); } delete record[key]; }); } function defaultEndpointStats() { return { bytesRecv: 0, bytesSent: 0, cached: 0, errors: 0, requests: 0, subscriptions: 0, timeout: 0 }; } class WsProvider { __internal__callCache; __internal__coder; __internal__endpoints; __internal__headers; __internal__eventemitter; __internal__handlers = {}; __internal__isReadyPromise; __internal__stats; __internal__waitingForId = {}; __internal__autoConnectMs; __internal__endpointIndex; __internal__endpointStats; __internal__isConnected = false; __internal__subscriptions = {}; __internal__timeoutId = null; __internal__websocket; __internal__timeout; constructor(endpoint = defaults.WS_URL, autoConnectMs = RETRY_DELAY, headers = {}, timeout, cacheCapacity) { const endpoints = Array.isArray(endpoint) ? endpoint : [endpoint]; if (endpoints.length === 0) { throw new Error('WsProvider requires at least one Endpoint'); } endpoints.forEach((endpoint) => { if (!/^(wss|ws):\/\//.test(endpoint)) { throw new Error(`Endpoint should start with 'ws://', received '${endpoint}'`); } }); this.__internal__callCache = new LRUCache(cacheCapacity || DEFAULT_CAPACITY); this.__internal__eventemitter = new EventEmitter(); this.__internal__autoConnectMs = autoConnectMs || 0; this.__internal__coder = new RpcCoder(); this.__internal__endpointIndex = -1; this.__internal__endpoints = endpoints; this.__internal__headers = headers; this.__internal__websocket = null; this.__internal__stats = { active: { requests: 0, subscriptions: 0 }, total: defaultEndpointStats() }; this.__internal__endpointStats = defaultEndpointStats(); this.__internal__timeout = timeout || DEFAULT_TIMEOUT_MS; if (autoConnectMs && autoConnectMs > 0) { this.connectWithRetry().catch(util.noop); } this.__internal__isReadyPromise = new Promise((resolve) => { this.__internal__eventemitter.once('connected', () => { resolve(this); }); }); } get hasSubscriptions() { return !!true; } get isClonable() { return !!true; } get isConnected() { return this.__internal__isConnected; } get isReady() { return this.__internal__isReadyPromise; } get endpoint() { return this.__internal__endpoints[this.__internal__endpointIndex]; } clone() { return new WsProvider(this.__internal__endpoints); } selectEndpointIndex(endpoints) { return (this.__internal__endpointIndex + 1) % endpoints.length; } async connect() { if (this.__internal__websocket) { throw new Error('WebSocket is already connected'); } try { this.__internal__endpointIndex = this.selectEndpointIndex(this.__internal__endpoints); this.__internal__websocket = typeof xglobal.WebSocket !== 'undefined' && util.isChildClass(xglobal.WebSocket, WebSocket) ? new WebSocket(this.endpoint) : new WebSocket(this.endpoint, undefined, { headers: this.__internal__headers }); if (this.__internal__websocket) { this.__internal__websocket.onclose = this.__internal__onSocketClose; this.__internal__websocket.onerror = this.__internal__onSocketError; this.__internal__websocket.onmessage = this.__internal__onSocketMessage; this.__internal__websocket.onopen = this.__internal__onSocketOpen; } this.__internal__timeoutId = setInterval(() => this.__internal__timeoutHandlers(), TIMEOUT_INTERVAL); } catch (error) { l$5.error(error); this.__internal__emit('error', error); throw error; } } async connectWithRetry() { if (this.__internal__autoConnectMs > 0) { try { await this.connect(); } catch { setTimeout(() => { this.connectWithRetry().catch(util.noop); }, this.__internal__autoConnectMs); } } } async disconnect() { this.__internal__autoConnectMs = 0; try { if (this.__internal__websocket) { this.__internal__websocket.close(1000); } } catch (error) { l$5.error(error); this.__internal__emit('error', error); throw error; } } get stats() { return { active: { requests: Object.keys(this.__internal__handlers).length, subscriptions: Object.keys(this.__internal__subscriptions).length }, total: this.__internal__stats.total }; } get endpointStats() { return this.__internal__endpointStats; } on(type, sub) { this.__internal__eventemitter.on(type, sub); return () => { this.__internal__eventemitter.removeListener(type, sub); }; } send(method, params, isCacheable, subscription) { this.__internal__endpointStats.requests++; this.__internal__stats.total.requests++; const [id, body] = this.__internal__coder.encodeJson(method, params); const cacheKey = isCacheable ? `${method}::${util.stringify(params)}` : ''; let resultPromise = isCacheable ? this.__internal__callCache.get(cacheKey) : null; if (!resultPromise) { resultPromise = this.__internal__send(id, body, method, params, subscription); if (isCacheable) { this.__internal__callCache.set(cacheKey, resultPromise); } } else { this.__internal__endpointStats.cached++; this.__internal__stats.total.cached++; } return resultPromise; } async __internal__send(id, body, method, params, subscription) { return new Promise((resolve, reject) => { try { if (!this.isConnected || this.__internal__websocket === null) { throw new Error('WebSocket is not connected'); } const callback = (error, result) => { error ? reject(error) : resolve(result); }; l$5.debug(() => ['calling', method, body]); this.__internal__handlers[id] = { callback, method, params, start: Date.now(), subscription }; const bytesSent = body.length; this.__internal__endpointStats.bytesSent += bytesSent; this.__internal__stats.total.bytesSent += bytesSent; this.__internal__websocket.send(body); } catch (error) { this.__internal__endpointStats.errors++; this.__internal__stats.total.errors++; reject(error); } }); } subscribe(type, method, params, callback) { this.__internal__endpointStats.subscriptions++; this.__internal__stats.total.subscriptions++; return this.send(method, params, false, { callback, type }); } async unsubscribe(type, method, id) { const subscription = `${type}::${id}`; if (util.isUndefined(this.__internal__subscriptions[subscription])) { l$5.debug(() => `Unable to find active subscription=${subscription}`); return false; } delete this.__internal__subscriptions[subscription]; try { return this.isConnected && !util.isNull(this.__internal__websocket) ? this.send(method, [id]) : true; } catch { return false; } } __internal__emit = (type, ...args) => { this.__internal__eventemitter.emit(type, ...args); }; __internal__onSocketClose = (event) => { const error = new Error(`disconnected from ${this.endpoint}: ${event.code}:: ${event.reason || getWSErrorString(event.code)}`); if (this.__internal__autoConnectMs > 0) { l$5.error(error.message); } this.__internal__isConnected = false; if (this.__internal__websocket) { this.__internal__websocket.onclose = null; this.__internal__websocket.onerror = null; this.__internal__websocket.onmessage = null; this.__internal__websocket.onopen = null; this.__internal__websocket = null; } if (this.__internal__timeoutId) { clearInterval(this.__internal__timeoutId); this.__internal__timeoutId = null; } eraseRecord(this.__internal__handlers, (h) => { try { h.callback(error, undefined); } catch (err) { l$5.error(err);