UNPKG

@ckb-ccc/core

Version:

Core of CCC - CKBer's Codebase

115 lines (105 loc) 3.16 kB
import WebSocket from "isomorphic-ws"; import { JsonRpcPayload, Transport } from "./transport.js"; export class TransportWebSocket implements Transport { private ongoing: Map< number, [ (response: unknown) => unknown, (error: unknown) => unknown, ReturnType<typeof setTimeout>, ] > = new Map(); private socket?: WebSocket; private openSocket?: Promise<WebSocket>; constructor( private readonly url: string, private readonly timeout = 30000, ) {} request(data: JsonRpcPayload) { const socket = (() => { if ( this.socket && this.socket.readyState !== this.socket.CLOSING && this.socket.readyState !== this.socket.CLOSED && this.openSocket ) { return this.openSocket; } const socket = new WebSocket(this.url); const onMessage = ({ data }: WebSocket.MessageEvent) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const res = JSON.parse(data as string); if ( typeof res !== "object" || res === null || typeof res.id !== "number" ) { throw new Error(`Unknown response ${data as string}`); } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const id: number = res.id; const req = this.ongoing.get(id); if (!req) { return; } const [resolve, _, timeout] = req; clearTimeout(timeout); this.ongoing.delete(id); resolve(res); }; const onClose = () => { this.ongoing.forEach(([_, reject, timeout]) => { clearTimeout(timeout); reject(new Error("Connection closed")); }); this.ongoing.clear(); }; socket.onclose = onClose; socket.onerror = onClose; socket.onmessage = onMessage; this.socket = socket; this.openSocket = new Promise<WebSocket>((resolve) => { if (socket.readyState === socket.OPEN) { resolve(socket); } else { socket.onopen = () => { resolve(socket); }; } }); return this.openSocket; })(); return new Promise((resolve, reject) => { const req: [ (res: unknown) => unknown, (err: unknown) => unknown, ReturnType<typeof setTimeout>, ] = [ resolve, reject, setTimeout(() => { this.ongoing.delete(data.id); void socket .then((socket) => socket.close()) .catch((err) => reject(err)); reject(new Error("Request timeout")); }, this.timeout), ]; this.ongoing.set(data.id, req); void socket .then((socket) => { if ( socket.readyState === socket.CLOSED || socket.readyState === socket.CLOSING ) { clearTimeout(req[2]); this.ongoing.delete(data.id); reject(new Error("Connection closed")); } else { socket.send(JSON.stringify(data)); } }) .catch((err) => reject(err)); }); } }