@metamask/kernel-rpc-methods
Version:
Utilities for implementing Ocap Kernel JSON-RPC methods
1 lines • 8.94 kB
Source Map (JSON)
{"version":3,"file":"RpcClient.mjs","sourceRoot":"","sources":["../src/RpcClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { makePromiseKit } from '@endo/promise-kit';\nimport { makeCounter, stringify } from '@metamask/kernel-utils';\nimport type { PromiseCallbacks } from '@metamask/kernel-utils';\nimport { Logger } from '@metamask/logger';\nimport { assert as assertStruct } from '@metamask/superstruct';\nimport { isJsonRpcFailure, isJsonRpcSuccess } from '@metamask/utils';\nimport type {\n JsonRpcNotification,\n JsonRpcRequest,\n JsonRpcSuccess,\n} from '@metamask/utils';\n\nimport type {\n MethodSpec,\n ExtractParams,\n ExtractResult,\n MethodSpecRecord,\n ExtractNotification,\n ExtractRequest,\n} from './types.ts';\n\nexport type SendMessage = (\n payload: JsonRpcRequest | JsonRpcNotification,\n) => Promise<void>;\n\nexport class RpcClient<\n // The class picks up its type from the `methods` argument,\n // so using `any` in this constraint is safe.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Methods extends MethodSpecRecord<MethodSpec<string, any, any>>,\n> {\n readonly #methods: Methods;\n\n readonly #prefix: string;\n\n readonly #unresolvedMessages = new Map<string, PromiseCallbacks>();\n\n readonly #messageCounter = makeCounter();\n\n readonly #sendMessage: SendMessage;\n\n readonly #logger: Logger;\n\n constructor(\n methods: Methods,\n sendMessage: SendMessage,\n prefix: string,\n logger: Logger = new Logger('rpc-client'),\n ) {\n this.#methods = methods;\n this.#sendMessage = sendMessage;\n this.#prefix = prefix;\n this.#logger = logger;\n }\n\n async #call<Method extends ExtractRequest<Methods>>(\n method: Method,\n params: ExtractParams<Method, Methods>,\n id: string,\n ): Promise<ExtractResult<Method, Methods>> {\n const response = await this.#createMessage(id, {\n id,\n jsonrpc: '2.0',\n method,\n params,\n });\n\n this.#assertResult(method, response.result);\n return response.result;\n }\n\n /**\n * Calls a JSON-RPC method and returns the result.\n *\n * @param method - The method to call.\n * @param params - The parameters to pass to the method.\n * @returns A promise that resolves to the result.\n */\n async call<Method extends ExtractRequest<Methods>>(\n method: Method,\n params: ExtractParams<Method, Methods>,\n ): Promise<ExtractResult<Method, Methods>> {\n return await this.#call(method, params, this.#nextMessageId());\n }\n\n /**\n * Sends a JSON-RPC notification. Recall that we do not receive responses to notifications\n * for any reason.\n *\n * @param method - The method to notify.\n * @param params - The parameters to pass to the method.\n */\n async notify<Method extends ExtractNotification<Methods>>(\n method: Method,\n params: ExtractParams<Method, Methods>,\n ): Promise<void> {\n await this.#sendMessage({\n jsonrpc: '2.0',\n method,\n params,\n }).catch((error) =>\n this.#logger.error(`Failed to send notification`, error),\n );\n }\n\n /**\n * Calls a JSON-RPC method and returns the message id and the result.\n *\n * @param method - The method to call.\n * @param params - The parameters to pass to the method.\n * @returns A promise that resolves to a tuple of the message id and the result.\n */\n async callAndGetId<Method extends ExtractRequest<Methods>>(\n method: Method,\n params: ExtractParams<Method, Methods>,\n ): Promise<[string, ExtractResult<Method, Methods>]> {\n const id = this.#nextMessageId();\n return [id, await this.#call(method, params, id)];\n }\n\n #assertResult<Method extends ExtractRequest<Methods>>(\n method: Method,\n result: unknown,\n ): asserts result is ExtractResult<Method, Methods> {\n try {\n // @ts-expect-error: For unknown reasons, TypeScript fails to recognize that\n // `Method` must be a key of `this.#methods`.\n assertStruct(result, this.#methods[method].result);\n } catch (error) {\n throw new Error(`Invalid result: ${(error as Error).message}`);\n }\n }\n\n async #createMessage(\n messageId: string,\n payload: JsonRpcRequest,\n ): Promise<JsonRpcSuccess> {\n const { promise, reject, resolve } = makePromiseKit<JsonRpcSuccess>();\n\n this.#unresolvedMessages.set(messageId, {\n resolve: resolve as (value: unknown) => void,\n reject,\n });\n\n await this.#sendMessage(payload);\n return promise;\n }\n\n /**\n * Handles a JSON-RPC response to a previously made method call.\n *\n * @param messageId - The id of the message to handle.\n * @param response - The response to handle.\n */\n handleResponse(messageId: string, response: unknown): void {\n const requestCallbacks = this.#unresolvedMessages.get(messageId);\n if (requestCallbacks === undefined) {\n this.#logger.debug(\n `Received response with unexpected id \"${messageId}\".`,\n );\n } else {\n this.#unresolvedMessages.delete(messageId);\n if (isJsonRpcSuccess(response)) {\n requestCallbacks.resolve(response);\n } else if (isJsonRpcFailure(response)) {\n requestCallbacks.reject(response.error);\n } else {\n requestCallbacks.reject(\n new Error(`Invalid JSON-RPC response: ${stringify(response)}`),\n );\n }\n }\n }\n\n /**\n * Rejects all unresolved messages with an error.\n *\n * @param error - The error to reject the messages with.\n */\n rejectAll(error: Error): void {\n for (const [messageId, promiseCallback] of this.#unresolvedMessages) {\n promiseCallback?.reject(error);\n this.#unresolvedMessages.delete(messageId);\n }\n }\n\n #nextMessageId(): string {\n return `${this.#prefix}${this.#messageCounter()}`;\n }\n}\n"]}