UNPKG

@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 12.1 kB
{"version":3,"sources":["../src/rpc.ts"],"names":["RpcServer","_RpcServer","#handleMessage","#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":"yCA6GO,IAAeA,CAAAA,CAAf,MAAeC,CAA0B,CAS9C,aAAc,CACZ,MAAA,CAAO,gBAAA,CAAiB,SAAA,CAAW,IAAA,CAAKC,EAAc,EACxD,CAGA,MAAOC,EAAAA,CAIH,CACF,IAAA,CAAM,CACJ,gBAAiB,IAAA,CACjB,OAAA,CAAS,IAAA,CACT,WAAA,CAAa,IAAA,CACb,YAAA,CAAc,KACd,aAAA,CAAe,IAAA,CACf,kBAAmB,IAAA,CACnB,iBAAA,CAAmB,IACrB,CAAA,CACA,KAAA,CAAO,CACL,GAAA,CAAK,IAAA,CACL,IAAA,CAAM,KACN,SAAA,CAAW,IACb,CAAA,CACA,KAAA,CAAO,CACL,GAAA,CAAK,KACL,IAAA,CAAM,IAAA,CACN,SAAA,CAAW,IACb,CAAA,CACA,OAAA,CAAS,CACP,UAAA,CAAY,IACd,EACA,OAAA,CAAS,CACP,iBAAkB,IAAA,CAClB,kBAAA,CAAoB,IAAA,CACpB,kBAAA,CAAoB,IAAA,CACpB,oBAAA,CAAsB,IACxB,CAAA,CACA,kBAAA,CAAoB,CAClB,GAAA,CAAK,IAAA,CACL,GAAA,CAAK,IACP,CAAA,CACA,GAAA,CAAK,CACH,KAAA,CAAO,IACT,CACF,EAIAD,EAAAA,CAAiB,MACfE,KACG,CACH,GAAKA,IAAM,IAAA,CAAK,YAAA,CAEhB,GAAI,CAIF,GAAI,CADkB,MAAM,IAAA,CAAK,gBAAA,CAAiBA,GAAAA,CAAM,MAAM,CAAA,CAE5D,MAAM,IAAIC,CAAAA,CACRA,CAAAA,CAAe,KAAA,CAAM,aAAA,CACrB,WACF,CAAA,CAEF,GAAM,CAAE,OAAA,CAAAC,EAAS,MAAA,CAAAC,CAAO,EAAIH,GAAAA,CAAM,IAAA,CAKlC,GAAI,CAACH,CAAAA,CAAUE,EAAAA,CAAaG,CAAO,CAAA,GAAIC,CAAM,CAAA,CAC3C,MAAM,IAAIF,CAAAA,CACRA,EAAe,KAAA,CAAM,WAAA,CACrB,CAAA,UAAA,EAAaC,CAAO,CAAA,CAAA,EAAIC,CAAM,EAChC,CAAA,CAEF,IAAMC,EAAK,IAAA,CAAKF,CAAO,IAAIC,CAAM,CAAA,CACjC,GAAI,CAACC,CAAAA,CACH,MAAM,IAAIH,CAAAA,CACRA,CAAAA,CAAe,KAAA,CAAM,WAAA,CACrB,CAAA,UAAA,EAAaC,CAAO,IAAIC,CAAM,CAAA,CAChC,CAAA,CAEF,IAAME,CAAAA,CAAS,MAAMD,EAAG,KAAA,CAAM,IAAA,CAAKF,CAAO,CAAA,CAAGF,GAAAA,CAAM,KAAK,MAAM,CAAA,CAE9DA,GAAAA,CAAM,KAAA,CAAM,CAAC,CAAA,CAAE,YAAY,CAACK,CAAAA,CAAQ,IAAI,CAAC,EAC3C,CAAA,MAASC,EAAO,CACd,IAAIC,CAAAA,CAAMD,CAAAA,CAEJC,CAAAA,YAAeC,CAAAA,GACnB,QAAQ,KAAA,CAAMF,CAAK,CAAA,CACnBC,CAAAA,CAAM,IAAIE,CAAAA,CACRA,EAAS,KAAA,CAAM,qBAAA,CACf,WAAA,CACAH,CACF,CAAA,CAAA,CAGFN,GAAAA,CAAM,MAAM,CAAC,CAAA,CAAE,WAAA,CAAY,CAAC,IAAA,CAAM,IAAA,CAAK,UAAUO,CAAG,CAAC,CAAC,EACxD,CACF,CACF,EAYaG,CAAAA,CAAN,MAAMC,CAAU,CACrBC,EAAAA,CACAC,GAEA,MAAOC,EAAAA,CAAqC,IAAI,GAAA,CAEhD,MAAOC,EAAAA,CAAc,CAAE,MAAA,CAAAC,CAAAA,CAAQ,KAAA,CAAAC,CAAAA,CAAO,OAAA,CAAAC,CAAQ,EAAqB,CACjE,IAAMC,CAAAA,CAAY,IAAI,GAAA,CAAI,iBAAA,CAAmBH,CAAM,CAAA,CACnD,OAAAG,EAAU,YAAA,CAAa,GAAA,CAAI,QAASF,CAAK,CAAA,CACzCE,CAAAA,CAAU,YAAA,CAAa,GAAA,CAAI,SAAA,CAAWD,CAAO,CAAA,CACtCC,CACT,CAEA,OAAO,WAAA,CAAYC,CAAAA,CAAsC,CACvD,IAAMC,CAAAA,CAAMV,CAAAA,CAAUI,EAAAA,CAAcK,CAAO,CAAA,CAAE,UAAS,CACtD,OAAKT,EAAUG,EAAAA,CAAW,GAAA,CAAIO,CAAG,CAAA,EAC/BV,CAAAA,CAAUG,EAAAA,CAAW,GAAA,CAAIO,CAAAA,CAAK,IAAIV,EAAUS,CAAO,CAAC,CAAA,CAE/CT,CAAAA,CAAUG,EAAAA,CAAW,GAAA,CAAIO,CAAG,CACrC,CAEQ,WAAA,CAAYD,CAAAA,CAA2B,CAC7C,IAAA,CAAKR,GAAaD,CAAAA,CAAUI,EAAAA,CAAcK,CAAO,CAAA,CAEjD,IAAA,CAAKP,GAAU,IAAI,OAAA,CAAQ,CAACS,CAAAA,CAASC,CAAAA,GAAW,CAC9C,IAAMC,CAAAA,CAAS,MAAA,CAAO,MAAA,CAAO,QAAA,CAAS,aAAA,CAAc,QAAQ,EAAG,CAC7D,GAAA,CAAK,IAAA,CAAKZ,EAAAA,CACV,MAAA,CAAQ,IAAA,CACR,OAAQ,IAAMU,CAAAA,CAAQE,CAAM,CAAA,CAC5B,OAAA,CAASD,CACX,CAAC,CAAA,CAED,QAAA,CAAS,IAAA,CAAK,WAAA,CAAYC,CAAM,EAClC,CAAC,EACH,CAEA,OAAA,EAAU,CACR,IAAA,CAAKX,GAAQ,IAAA,CAAMW,CAAAA,EAAW,QAAA,CAAS,IAAA,CAAK,WAAA,CAAYA,CAAM,CAAC,EACjE,CAEA,MAAM,IAAA,CAKJtB,CAAAA,CACAC,KACGsB,CAAAA,CAC+B,CAClC,IAAMD,CAAAA,CAAS,MAAM,IAAA,CAAKX,GACpBa,CAAAA,CAAU,IAAI,cAAA,CAEpB,OAAO,MAAM,IAAI,QAAQ,CAACJ,CAAAA,CAASC,CAAAA,GAAW,CAC5CG,CAAAA,CAAQ,KAAA,CAAM,UAAY,CAAC,CACzB,KAAM,CAACrB,CAAAA,CAAQC,CAAK,CACtB,CAAA,IACEoB,CAAAA,CAAQ,KAAA,CAAM,KAAA,EAAM,CACbpB,GAAS,IAAA,CACZiB,CAAAA,CAAO,IAAIf,CAAAA,CAAgB,IAAA,CAAK,KAAA,CAAMF,CAAK,CAAC,CAAC,CAAA,CAC7CgB,CAAAA,CAAQjB,CAAM,CAAA,CAAA,CAGpB,IAAMsB,CAAAA,CAAyB,CAC7B,YAAA,CAAc,IAAA,CACd,OAAA,CAAAzB,CAAAA,CACA,OAAAC,CAAAA,CACA,MAAA,CAAAsB,CACF,CAAA,CAEAD,CAAAA,CAAO,aAAA,EAAe,YAAYG,CAAAA,CAAS,IAAA,CAAKf,EAAAA,CAAW,MAAA,CAAQ,CACjEc,CAAAA,CAAQ,KACV,CAAC,EACH,CAAC,CACH,CACF","file":"chunk-3UK7MQXP.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 type PasswordLoginInput = {\n usernameInputName: string;\n passwordInputName: string;\n};\n\nexport type PasswordlessStartInput =\n | {\n connection: \"email\";\n emailInputName: string;\n }\n | {\n connection: \"sms\";\n phoneCallingCode: string;\n phoneInputName: string;\n };\n\nexport type PasswordlessLoginInput = PasswordlessStartInput & {\n otpInputName: string | string[];\n};\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(input: PasswordLoginInput): Promise<void>;\n passwordlessStart(input: PasswordlessStartInput): Promise<void>;\n passwordlessLogin(input: PasswordlessLoginInput): 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 initAccountDestroy(): Promise<string>;\n submitAccountSetup(rawTransaction: string): Promise<void>;\n submitAccountDestroy(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 IKeybanDpp {\n claim(dppId: 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 dpp: IKeybanDpp;\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 abstract class RpcServer implements IRpc {\n abstract auth: IKeybanAuth;\n abstract ecdsa: IKeybanSigner;\n abstract eddsa: IKeybanSigner;\n abstract account: IKeybanAccount;\n abstract loyalty: IKeybanLoyalty;\n abstract clientShareStorage: IKeybanClientShareStorage;\n abstract dpp: IKeybanDpp;\n\n constructor() {\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 initAccountDestroy: true,\n submitAccountSetup: true,\n submitAccountDestroy: true,\n },\n clientShareStorage: {\n get: true,\n set: true,\n },\n dpp: {\n claim: true,\n },\n };\n\n abstract checkEventOrigin(eventOrigin: string): Promise<boolean>;\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 originAllowed = await this.checkEventOrigin(event.origin);\n if (!originAllowed)\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(\"/sdk-client/rpc\", 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"]}