@web5/agent
Version:
150 lines • 7 kB
JavaScript
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