@keyban/sdk-base
Version:
Keyban Javascript SDK provides core functionalities for the MPC wallet solution, supporting web and Node.js apps with TypeScript, custom storage, and Ethereum blockchain integration.
1 lines • 11.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/rpc.ts"],"names":["RpcServer","_RpcServer","services","domains","#definitions","event","IFrameRpcError","service","method","fn","result","error","err","KeybanBaseError","SdkError","RpcClient","_RpcClient","#iframeUrl","#iframe","#instances","#getIframeUrl","apiUrl","appId","network","iframeUrl","options","key","resolve","reject","iframe","params","channel","message"],"mappings":"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","file":"chunk-DWBGILVH.mjs","sourcesContent":["/*\n * @module RPC services\n */\n\nimport { IFrameRpcError, KeybanBaseError, SdkError } from \"~/errors\";\nimport type { AuthConnection, KeybanNetwork, KeybanUser } from \"~/index\";\n\ntype Hex = `0x${string}`;\n\nexport interface IKeybanAuth {\n isAuthenticated(): Promise<boolean>;\n getUser(): Promise<KeybanUser | null>;\n getLoginUrl(connection?: AuthConnection): Promise<string>;\n getLogoutUrl(redirect?: string): Promise<string>;\n\n passwordLogin(username: string, password: string): Promise<void>;\n\n passwordlessStart(\n connection: \"email\" | \"sms\",\n username: string,\n ): Promise<void>;\n\n passwordlessLogin(\n connection: \"email\" | \"sms\",\n username: string,\n otp: string,\n ): Promise<void>;\n}\n\nexport interface IKeybanSigner {\n dkg(): Promise<string>;\n sign(clientShare: string, message: string): Promise<Hex>;\n publicKey(clientShare: string): Promise<Hex>;\n}\n\nexport interface IKeybanAccount {\n getAddress(): Promise<string>;\n}\n\nexport interface IKeybanLoyalty {\n initAccountSetup(): Promise<string>;\n submitAccountSetup(rawTransaction: string): Promise<void>;\n}\n\nexport interface IKeybanClientShareStorage {\n get(key: string): Promise<string | null>;\n set(key: string, clientShare: string): Promise<void>;\n}\n\nexport interface IKeybanTpp {\n claim(tppId: string, recipient: string): Promise<{ transactionHash: string }>;\n}\n\n/*\n * RPC types\n */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype CastFn<T> = T extends (...args: any[]) => any ? T : never;\n\ninterface IRpc {\n auth: IKeybanAuth;\n ecdsa: IKeybanSigner;\n eddsa: IKeybanSigner;\n account: IKeybanAccount;\n loyalty: IKeybanLoyalty;\n clientShareStorage: IKeybanClientShareStorage;\n tpp: IKeybanTpp;\n}\n\ntype Service = keyof IRpc;\ntype Method<S extends Service> = keyof IRpc[S] & string;\ntype ClassMethod<S extends Service, M extends Method<S>> = CastFn<IRpc[S][M]>;\n\ntype RpcCall<\n S extends Service,\n M extends Method<S>,\n CM extends ClassMethod<S, M> = ClassMethod<S, M>,\n> = {\n __KEYBAN_RPC: true;\n\n service: S;\n method: M;\n params: Parameters<CM>;\n};\n\ntype RpcResult<\n S extends Service,\n M extends Method<S>,\n CM extends ClassMethod<S, M> = ClassMethod<S, M>,\n> = [Awaited<ReturnType<CM>>, null] | [null, string];\n\n/*\n * RPC implementation\n */\n\nexport class RpcServer implements IRpc {\n auth!: IKeybanAuth;\n ecdsa!: IKeybanSigner;\n eddsa!: IKeybanSigner;\n account!: IKeybanAccount;\n loyalty!: IKeybanLoyalty;\n clientShareStorage!: IKeybanClientShareStorage;\n tpp!: IKeybanTpp;\n\n domains: Promise<string[]>;\n\n constructor(services: IRpc, domains: Promise<string[]>) {\n Object.assign(this, services);\n\n this.domains = domains;\n\n window.addEventListener(\"message\", this.handleMessage);\n }\n\n // Forced validation of service's methods, see listener below\n static #definitions: {\n [S in Service]: {\n [M in Method<S>]: true;\n };\n } = {\n auth: {\n isAuthenticated: true,\n getUser: true,\n getLoginUrl: true,\n getLogoutUrl: true,\n passwordLogin: true,\n passwordlessStart: true,\n passwordlessLogin: true,\n },\n ecdsa: {\n dkg: true,\n sign: true,\n publicKey: true,\n },\n eddsa: {\n dkg: true,\n sign: true,\n publicKey: true,\n },\n account: {\n getAddress: true,\n },\n loyalty: {\n initAccountSetup: true,\n submitAccountSetup: true,\n },\n clientShareStorage: {\n get: true,\n set: true,\n },\n tpp: {\n claim: true,\n },\n };\n\n handleMessage = async <S extends Service, M extends Method<S>>(\n event: MessageEvent<RpcCall<S, M>>,\n ) => {\n if (!event.data.__KEYBAN_RPC) return;\n\n try {\n // Check the message originated from an allowed domain for this\n // specific application.\n const domains = await this.domains;\n if (!domains.includes(event.origin))\n throw new IFrameRpcError(\n IFrameRpcError.types.InvalidOrigin,\n \"RpcServer\",\n );\n\n const { service, method } = event.data;\n\n // An attacker could possibly try to call a method on the service\n // object that is not intended to be exposed. This ensures the\n // method is effectively allowed.\n if (!RpcServer.#definitions[service]?.[method])\n throw new IFrameRpcError(\n IFrameRpcError.types.InvalidCall,\n `RpcServer:${service}.${method}`,\n );\n\n const fn = this[service]?.[method] as ClassMethod<S, M>;\n if (!fn)\n throw new IFrameRpcError(\n IFrameRpcError.types.InvalidCall,\n `RpcServer:${service}.${method}`,\n );\n\n const result = await fn.apply(this[service], event.data.params);\n\n event.ports[0].postMessage([result, null]);\n } catch (error) {\n let err = error;\n\n if (!(err instanceof KeybanBaseError)) {\n console.error(error);\n err = new SdkError(\n SdkError.types.UnknownIframeRpcError,\n \"RpcServer\",\n error as Error,\n );\n }\n\n event.ports[0].postMessage([null, JSON.stringify(err)]);\n }\n };\n}\n\n/*\n * RPC client\n */\n\ntype RpcClientOptions = {\n apiUrl: URL | string;\n appId: string;\n network: KeybanNetwork;\n};\n\nexport class RpcClient {\n #iframeUrl: URL;\n #iframe: Promise<HTMLIFrameElement>;\n\n static #instances: Map<string, RpcClient> = new Map();\n\n static #getIframeUrl({ apiUrl, appId, network }: RpcClientOptions) {\n const iframeUrl = new URL(\"/signer-client/\", apiUrl);\n iframeUrl.searchParams.set(\"appId\", appId);\n iframeUrl.searchParams.set(\"network\", network);\n return iframeUrl;\n }\n\n static getInstance(options: RpcClientOptions): RpcClient {\n const key = RpcClient.#getIframeUrl(options).toString();\n if (!RpcClient.#instances.has(key))\n RpcClient.#instances.set(key, new RpcClient(options));\n\n return RpcClient.#instances.get(key)!;\n }\n\n private constructor(options: RpcClientOptions) {\n this.#iframeUrl = RpcClient.#getIframeUrl(options);\n\n this.#iframe = new Promise((resolve, reject) => {\n const iframe = Object.assign(document.createElement(\"iframe\"), {\n src: this.#iframeUrl,\n hidden: true,\n onload: () => resolve(iframe),\n onerror: reject,\n });\n\n document.body.appendChild(iframe);\n });\n }\n\n destroy() {\n this.#iframe.then((iframe) => document.body.removeChild(iframe));\n }\n\n async call<\n S extends Service,\n M extends Method<S>,\n CM extends ClassMethod<S, M> = ClassMethod<S, M>,\n >(\n service: S,\n method: M,\n ...params: Parameters<CM>\n ): Promise<Awaited<ReturnType<CM>>> {\n const iframe = await this.#iframe;\n const channel = new MessageChannel();\n\n return await new Promise((resolve, reject) => {\n channel.port1.onmessage = ({\n data: [result, error],\n }: MessageEvent<RpcResult<S, M>>) => {\n channel.port1.close();\n return error != null\n ? reject(new KeybanBaseError(JSON.parse(error)))\n : resolve(result);\n };\n\n const message: RpcCall<S, M> = {\n __KEYBAN_RPC: true,\n service,\n method,\n params,\n };\n\n iframe.contentWindow?.postMessage(message, this.#iframeUrl.origin, [\n channel.port2,\n ]);\n });\n }\n}\n"]}