diff --git a/src/client.ts b/src/client.ts index 6bfd0bc..7313aa9 100644 --- a/src/client.ts +++ b/src/client.ts @@ -30,7 +30,7 @@ export class RpcError extends Error { export type RpcTransport = ( req: JsonRpcRequest, abortSignal: AbortSignal -) => Promise; +) => Promise; type RpcClientOptions = | string @@ -78,7 +78,7 @@ export function rpcClient(options: RpcClientOptions) { args: any[], signal: AbortSignal ) => { - const req = createRequest(method, args); + const req = createRequest(Date.now(), method, args); const raw = await transport(serialize(req as any), signal); const res = deserialize(raw); if ("result" in res) { @@ -90,6 +90,20 @@ export function rpcClient(options: RpcClientOptions) { throw new TypeError("Invalid response"); }; + async function sendNotification(method: string, ...args: any[]): Promise> { + const req = createRequest(undefined, method, args); + const ac = new AbortController(); + const promise = transport(serialize(req as any), ac.signal); + abortControllers.set(promise, ac); + promise + .finally(() => { + // Remove the + abortControllers.delete(promise); + }) + .catch(() => {}); + return promise as Promise; + } + // Map of AbortControllers to abort pending requests const abortControllers = new WeakMap, AbortController>(); @@ -101,6 +115,7 @@ export function rpcClient(options: RpcClientOptions) { const ac = abortControllers.get(promise); ac?.abort(); }, + $notify: sendNotification, }; return new Proxy(target, { @@ -130,13 +145,16 @@ export function rpcClient(options: RpcClientOptions) { /** * Create a JsonRpcRequest for the given method. */ -export function createRequest(method: string, params?: any[]): JsonRpcRequest { +function createRequest(id: JsonRpcRequest['id'], method: string, params?: any[]): JsonRpcRequest { const req: JsonRpcRequest = { jsonrpc: "2.0", - id: Date.now(), method, }; + if (id) { + req.id = id; + } + if (params?.length) { req.params = removeTrailingUndefs(params); } diff --git a/src/test/client.ts b/src/test/client.ts index 5197524..bc1da30 100644 --- a/src/test/client.ts +++ b/src/test/client.ts @@ -116,3 +116,28 @@ tap.test("should not relay internal methods", async (t) => { //@ts-expect-error client[Symbol()]; }); + +tap.test("should not notify", async (t) => { + let client: ReturnType>; + + const request = new Promise((resolve) => { + client = rpcClient({ + url: 'n/a', + transport: async (req) => { + resolve(req); + }, + }); + }); + + const res = await Promise.all([ + client!.$notify("hello", "world"), + request, + ]); + t.equal(res[0], undefined); + t.same(res[1], { + "jsonrpc": "2.0", + "method": "hello", + "params": ["world"], + // no id + }); +});