UNPKG

nimiq-rpc-client-ts

Version:
177 lines (171 loc) 6.22 kB
'use strict'; const WebSocket = require('isomorphic-ws'); const client = require('./client.cjs'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } const WebSocket__default = /*#__PURE__*/_interopDefaultCompat(WebSocket); const DEFAULT_TIMEOUT = 3e4; const DEFAULT_RECONNECT = { retries: 3, delay: 1e3, onFailed: () => { } }; async function rpcSubscribe(method, params = [], options = {}) { const baseUrl = await client.__getBaseUrl(); const wsUrl = new URL(baseUrl.toString()); wsUrl.protocol = wsUrl.protocol.replace(/^http/, "ws"); wsUrl.pathname = "/ws"; const { once, filter, timeout = DEFAULT_TIMEOUT, autoReconnect = DEFAULT_RECONNECT, onError } = options; let ws = null; let open = false; let closed = false; let idCounter = 1; let reconnects = 0; let subscriptionId = 0; const pending = /* @__PURE__ */ new Map(); const emitter = new EventTarget(); emitter.getId = () => subscriptionId; emitter.isOpen = () => open; emitter.context = { method, params, url: wsUrl.href.split("?")[0], timestamp: Date.now() }; function scheduleReconnect() { const settings = typeof autoReconnect === "boolean" ? DEFAULT_RECONNECT : { ...DEFAULT_RECONNECT, ...autoReconnect }; const { retries, delay, onFailed: failCb } = settings; const canRetry = typeof retries === "number" ? retries < 0 || reconnects < retries : retries(); if (canRetry) { reconnects++; ws = null; setTimeout(() => { rpcSubscribe(method, params, options).then((newSub) => { newSub.addEventListener("open", (e) => emitter.dispatchEvent(e)); newSub.addEventListener("data", (e) => emitter.dispatchEvent(e)); newSub.addEventListener("error", (e) => emitter.dispatchEvent(e)); newSub.addEventListener("close", (e) => emitter.dispatchEvent(e)); }).catch((err) => onError?.(err)); }, delay); } else { failCb?.(); } } function handleError(err) { onError?.(err); emitter.dispatchEvent(new CustomEvent("error", { detail: err })); } async function handleMessage(raw) { try { const Buffer = typeof window === "undefined" ? (await import('node:buffer')).Buffer : globalThis.Buffer; if (raw instanceof Blob) raw = await raw.arrayBuffer().then((buffer) => new TextDecoder().decode(buffer)); else if (raw instanceof ArrayBuffer) raw = new TextDecoder().decode(raw); else if (raw instanceof Buffer) raw = raw.toString(); if (raw instanceof ArrayBuffer) { if (typeof TextDecoder !== "undefined") { raw = new TextDecoder().decode(raw); } else { const { Buffer: Buffer2 } = await import('node:buffer'); raw = Buffer2.from(raw).toString(); } } const msg = JSON.parse(raw); if (msg.method && msg.params && msg.params.subscription && msg.params.result) { const { data: payload, metadata: meta } = msg.params.result; let formattedPayload = payload; if (msg.method === "subscribeForHeadBlockHash" && typeof payload === "string") { formattedPayload = [payload]; } if (!filter || filter(formattedPayload)) { emitter.dispatchEvent(new CustomEvent("data", { detail: { data: formattedPayload, metadata: meta } })); if (once) close(); } return; } if ("id" in msg && pending.has(msg.id)) { const { resolve, reject } = pending.get(msg.id); if (msg.error) { reject(msg.error); } else { resolve(msg.result); } pending.delete(msg.id); } } catch (e) { handleError(e); } } function connect() { return new Promise((resolve) => { if (!ws) { ws = new WebSocket__default(wsUrl.href); ws.onopen = () => { open = true; reconnects = 0; emitter.dispatchEvent(new Event("open")); resolve(); }; ws.onmessage = ({ data }) => handleMessage(data); ws.onerror = (e) => handleError(new Error(String(e))); ws.onclose = (ev) => { open = false; emitter.dispatchEvent(new CustomEvent("close", { detail: { code: ev.code, reason: ev.reason } })); if (!closed && autoReconnect) scheduleReconnect(); }; } else if (open) { resolve(); } }); } async function sendRequest() { await connect(); return new Promise((res, rej) => { const id = idCounter++; const payload = { jsonrpc: "2.0", id, method, params }; let timer; if (timeout !== false) { timer = setTimeout(() => { pending.delete(id); rej(new Error(`Timeout after ${timeout}ms`)); }, timeout); } pending.set(id, { resolve: (val) => { clearTimeout(timer); res(val); }, reject: (err) => { clearTimeout(timer); rej(err); } }); ws?.send(JSON.stringify(payload)); }); } function close() { closed = true; ws?.close(); pending.clear(); emitter.dispatchEvent(new Event("close")); } await connect(); subscriptionId = await sendRequest(); return emitter; } function subscribeForHeadBlock(fullBlock = false, opts) { return rpcSubscribe("subscribeForHeadBlock", [fullBlock], opts); } function subscribeForHeadBlockHash(opts) { return rpcSubscribe("subscribeForHeadBlockHash", [], opts); } function subscribeForLogsByAddressesAndTypes(addresses = [], logTypes = [], opts) { return rpcSubscribe( "subscribeForLogsByAddressesAndTypes", [addresses, logTypes], opts ); } function subscribeForValidatorElectionByAddress(address, opts) { return rpcSubscribe("subscribeForValidatorElectionByAddress", [address], opts); } exports.rpcSubscribe = rpcSubscribe; exports.subscribeForHeadBlock = subscribeForHeadBlock; exports.subscribeForHeadBlockHash = subscribeForHeadBlockHash; exports.subscribeForLogsByAddressesAndTypes = subscribeForLogsByAddressesAndTypes; exports.subscribeForValidatorElectionByAddress = subscribeForValidatorElectionByAddress;