rpc-ws
Version:
JSON-RPC 2.0 implementation with websockets
146 lines (145 loc) • 3.81 kB
JavaScript
// lib/client-browser.ts
async function Client(endpoint, opts) {
let call_id = 0;
let connected = false;
const timeout = opts?.timeout || 1e4;
const version = "2.0";
const ws = new WebSocket(endpoint);
const events = /* @__PURE__ */ new Map();
const emitter = new EventTarget();
const queue = /* @__PURE__ */ new Map();
await setup();
function assertConnection() {
if (!connected)
throw new Error("WebSocket connection not estabilished");
}
async function setup() {
connected = await Promise.race([
new Promise(
(resolve) => ws.addEventListener("open", () => resolve(true))
),
new Promise((_, reject) => setTimeout(() => reject(false), timeout))
]);
assertConnection();
ws.addEventListener("message", (e) => {
try {
const payload = JSON.parse(e.data);
if (payload.notification && events.has(payload.notification)) {
const cb = events.get(payload.notification);
cb(Array.isArray(payload.params) ? payload.params : [payload.params]);
return;
}
if (!payload.id)
return;
const event = queue.get(payload.id);
if (!event)
return;
if (payload.error) {
queue.set(payload.id, {
...event,
error: payload.error
});
} else {
queue.set(payload.id, { ...event, result: payload.result });
}
emitter.dispatchEvent(new Event(String(payload.id)));
} catch (error) {
console.error(error);
}
});
}
function _send(request) {
return new Promise((resolve, reject) => {
const callTimeout = setTimeout(() => {
queue.set(request.id, {
type: "request",
error: {
code: -32e3,
message: "Request timed out"
}
});
emitter.dispatchEvent(new Event(String(request.id)));
}, timeout);
try {
ws.send(JSON.stringify(request));
} catch (error) {
return reject({
id: request.id,
jsonrpc: request.jsonrpc,
error: {
code: -32700,
message: "Parse error"
}
});
}
emitter.addEventListener(String(request.id), () => {
clearTimeout(callTimeout);
const response = queue.get(request.id);
queue.delete(request.id);
return resolve({
id: request.id,
jsonrpc: request.jsonrpc,
result: response.result,
error: response.error
});
});
});
}
function subscribe(namespace, cb) {
assertConnection();
const request = {
jsonrpc: version,
method: "rpc.on",
params: [namespace],
id: ++call_id
};
events.set(namespace, cb);
queue.set(request.id, { type: "notification" });
return _send(request);
}
function unsubscribe(namespace) {
assertConnection();
const request = {
jsonrpc: version,
method: "rpc.off",
params: [namespace],
id: ++call_id
};
events.delete(namespace);
queue.set(request.id, { type: "notification" });
return _send(request);
}
function send(method, ...params) {
assertConnection();
const request = {
jsonrpc: version,
method,
params,
id: ++call_id
};
queue.set(request.id, { type: "request" });
return _send(request);
}
function close() {
ws.close();
}
const base = {
subscribe,
unsubscribe,
close
};
return new Proxy(base, {
get(target, prop) {
if (prop === "then") {
return void 0;
}
if (prop in base) {
return Reflect.get(target, prop);
}
return (...args) => send(prop, ...args);
}
});
}
export {
Client as BrowserClient
};