UNPKG

@web5/agent

Version:
150 lines 7 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { CryptoUtils } from '@web5/crypto'; import IsomorphicWebSocket from 'isomorphic-ws'; import { createJsonRpcSubscriptionRequest, parseJson } from './json-rpc.js'; // These were arbitrarily chosen, but can be modified via connect options const CONNECT_TIMEOUT = 3000; const RESPONSE_TIMEOUT = 30000; /** * JSON RPC Socket Client for WebSocket request/response and long-running subscriptions. * * NOTE: This is temporarily copied over from https://github.com/TBD54566975/dwn-server/blob/main/src/json-rpc-socket.ts * This was done in order to avoid taking a dependency on the `dwn-server`, until a future time when there will be a `clients` package. */ export class JsonRpcSocket { constructor(socket, responseTimeout) { this.socket = socket; this.responseTimeout = responseTimeout; this.messageHandlers = new Map(); } static connect(url, options = {}) { return __awaiter(this, void 0, void 0, function* () { const { connectTimeout = CONNECT_TIMEOUT, responseTimeout = RESPONSE_TIMEOUT, onclose, onerror } = options; const socket = new IsomorphicWebSocket(url); if (!onclose) { socket.onclose = () => { console.info(`JSON RPC Socket close ${url}`); }; } else { socket.onclose = onclose; } if (!onerror) { socket.onerror = (error) => { console.error(`JSON RPC Socket error ${url}`, error); }; } else { socket.onerror = onerror; } return new Promise((resolve, reject) => { socket.addEventListener('open', () => { const jsonRpcSocket = new JsonRpcSocket(socket, responseTimeout); socket.addEventListener('message', (event) => { const jsonRpcResponse = parseJson(event.data); const handler = jsonRpcSocket.messageHandlers.get(jsonRpcResponse.id); if (handler) { handler(event); } }); resolve(jsonRpcSocket); }); socket.addEventListener('error', (error) => { reject(error); }); setTimeout(() => reject, connectTimeout); }); }); } close() { this.socket.close(); } /** * Sends a JSON-RPC request through the socket and waits for a single response. */ request(request) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { var _a; (_a = request.id) !== null && _a !== void 0 ? _a : (request.id = CryptoUtils.randomUuid()); const handleResponse = (event) => { const jsonRpsResponse = parseJson(event.data); if (jsonRpsResponse.id === request.id) { // if the incoming response id matches the request id, we will remove the listener and resolve the response this.messageHandlers.delete(request.id); return resolve(jsonRpsResponse); } }; // add the listener to the map of message handlers this.messageHandlers.set(request.id, handleResponse); this.send(request); // reject this promise if we don't receive any response back within the timeout period setTimeout(() => { this.messageHandlers.delete(request.id); reject(new Error('request timed out')); }, this.responseTimeout); }); }); } /** * Sends a JSON-RPC request through the socket and keeps a listener open to read associated responses as they arrive. * Returns a close method to clean up the listener. */ subscribe(request, listener) { return __awaiter(this, void 0, void 0, function* () { if (!request.method.startsWith('rpc.subscribe.')) { throw new Error('subscribe rpc requests must include the `rpc.subscribe` prefix'); } if (!request.subscription) { throw new Error('subscribe rpc requests must include subscribe options'); } const subscriptionId = request.subscription.id; const socketEventListener = (event) => { const jsonRpcResponse = parseJson(event.data.toString()); if (jsonRpcResponse.id === subscriptionId) { if (jsonRpcResponse.error !== undefined) { // remove the event listener upon receipt of a JSON RPC Error. this.messageHandlers.delete(subscriptionId); this.closeSubscription(subscriptionId); } listener(jsonRpcResponse); } }; this.messageHandlers.set(subscriptionId, socketEventListener); const response = yield this.request(request); if (response.error) { this.messageHandlers.delete(subscriptionId); return { response }; } // clean up listener and create a `rpc.subscribe.close` message to use when closing this JSON RPC subscription const close = () => __awaiter(this, void 0, void 0, function* () { this.messageHandlers.delete(subscriptionId); yield this.closeSubscription(subscriptionId); }); return { response, close }; }); } closeSubscription(id) { const requestId = CryptoUtils.randomUuid(); const request = createJsonRpcSubscriptionRequest(requestId, 'close', id, {}); return this.request(request); } /** * Sends a JSON-RPC request through the socket. You must subscribe to a message listener separately to capture the response. */ send(request) { this.socket.send(JSON.stringify(request)); } } //# sourceMappingURL=json-rpc-socket.js.map