import { Prettify } from "viem"; //#region src/rpc-schema.d.ts /** * Generic shape of a single RPC schema entry * * Each entry defines a method with its parameters, return type, and response kind * * @typeParam TMethod - The method name (string literal) * @typeParam TParams - The parameters type (can be undefined for no parameters) * @typeParam TReturn - The return type * @typeParam TResponseKind - Either "promise" or "stream" */ type RpcSchemaEntry = { /** * The method name (e.g., "frak_sendInteraction") */ Method: TMethod; /** * The parameters type (undefined if no parameters) */ Parameters?: TParams; /** * The return type */ ReturnType: TReturn; }; /** * An RPC schema is a readonly array of schema entries * * @example * ```ts * type MySchema = [ * { * Method: "greet"; * Parameters: [name: string]; * ReturnType: string; * ResponseType: "promise"; * }, * { * Method: "watchTime"; * Parameters?: undefined; * ReturnType: number; * ResponseType: "stream"; * } * ] * ``` */ type RpcSchema = readonly RpcSchemaEntry[]; /** * Extract method names from a schema * * @typeParam TSchema - The RPC schema type * * @example * ```ts * type Methods = ExtractMethod * // "greet" | "watchTime" * ``` */ type ExtractMethod = TSchema[number]["Method"]; /** * Extract a specific schema entry by method name * * @typeParam TSchema - The RPC schema type * @typeParam TMethod - The method name to extract * * @example * ```ts * type GreetEntry = ExtractSchemaEntry * // { Method: "greet"; Parameters: [name: string]; ReturnType: string; ResponseType: "promise" } * ``` */ type ExtractSchemaEntry> = Extract; /** * Extract parameters type for a specific method * * @typeParam TSchema - The RPC schema type * @typeParam TMethod - The method name * * @example * ```ts * type GreetParams = ExtractParams * // [name: string] * ``` */ type ExtractParams> = ExtractSchemaEntry["Parameters"]; /** * Type that extract the possible parameters from a RPC Schema * @ignore */ type ExtractedParametersFromRpc = { [K in keyof TRpcSchema]: Prettify<{ method: TRpcSchema[K] extends TRpcSchema[number] ? TRpcSchema[K]["Method"] : string; } & (TRpcSchema[K] extends TRpcSchema[number] ? TRpcSchema[K]["Parameters"] extends undefined ? { params?: never; } : { params: TRpcSchema[K]["Parameters"]; } : never)> }[number]; /** * Type that extract the possible parameters from a RPC Schema * @ignore */ type ExtractedSpecificParametersFromRpc> = Extract, { method: TMethod; }>; /** * Extract return type for a specific method * * @typeParam TSchema - The RPC schema type * @typeParam TMethod - The method name * * @example * ```ts * type GreetReturn = ExtractReturnType * // string * ``` */ type ExtractReturnType> = ExtractSchemaEntry["ReturnType"]; //#endregion //#region src/types.d.ts /** * Transport interface for RPC communication * Abstracts the underlying message passing mechanism (postMessage, etc) */ type RpcTransport = { /** * Send a message through the transport */ postMessage: (message: RpcMessage, targetOrigin: string) => void; /** * Listen for messages */ addEventListener: (type: "message", listener: (event: MessageEvent) => void) => void; /** * Remove message listener */ removeEventListener: (type: "message", listener: (event: MessageEvent) => void) => void; }; /** * RPC message format (maintains backward compatibility) * This is the exact format sent over the wire * * @typeParam TMethod - The method name type (defaults to string for flexibility) */ type RpcMessage = { /** * Unique message identifier for correlating requests and responses */ id: string; /** * The RPC method name (topic for backward compatibility) */ topic: TMethod; /** * The message payload (compressed data) or raw params */ data: unknown; }; /** * Lifecycle message format for client-to-iframe communication * These messages handle connection lifecycle events (handshake, heartbeat, etc.) */ type ClientLifecycleMessage = { clientLifecycle: string; data?: unknown; }; /** * Lifecycle message format for iframe-to-client communication */ type IFrameLifecycleMessage = { iframeLifecycle: string; data?: unknown; }; /** * Union of all lifecycle message types */ type LifecycleMessage = ClientLifecycleMessage | IFrameLifecycleMessage; /** * Union of all message types that can be received */ type AnyMessage = RpcMessage | LifecycleMessage; /** * RPC response wrapper * Contains either a successful result or an error */ type RpcResponse = { result: TResult; error?: never; } | { result?: never; error: RpcError; }; /** * RPC error object */ type RpcError = { code: number; message: string; data?: unknown; }; /** * Request context for handlers * Contains information about the origin and source of the request */ type RpcRequestContext = { /** * Origin of the request */ origin: string; /** * Message source (for responding) */ source: MessageEventSource | null; }; /** * Middleware context that can be augmented with custom fields * Generic type parameter allows domain-specific context augmentation * * @typeParam TCustomContext - Custom context fields to merge with base context * * @example * ```ts * type WalletContext = RpcMiddlewareContext<{ * productId: string * sourceUrl: string * isAutoContext: boolean * }> * // { origin: string, source: MessageEventSource | null, productId: string, sourceUrl: string, isAutoContext: boolean } * ``` */ type RpcMiddlewareContext> = RpcRequestContext & TCustomContext; /** * Type-safe request parameters * * @typeParam TSchema - The RPC schema type * @typeParam TMethod - The method name from the schema */ type TypedRpcRequest> = { method: TMethod; params: ExtractParams; }; /** * Stream emitter function * Used by stream handlers to emit multiple values */ type StreamEmitter = (chunk: TResult) => void; /** * Lifecycle handler function * Handles lifecycle events using discriminated unions for automatic type narrowing * * @typeParam TLifecycleEvent - The lifecycle event union type (e.g., ClientLifecycleEvent | IFrameLifecycleEvent) * * @param event - The full lifecycle event object with discriminated union * @param context - Request context with origin and source * * @example * ```ts * const handler: LifecycleHandler = (event, context) => { * if (event.clientLifecycle === "modal-css") { * // event.data is automatically typed as { cssLink: string } * console.log(event.data.cssLink) * } * } * ``` */ type LifecycleHandler = (event: TLifecycleEvent, context: RpcRequestContext) => void | Promise; /** * Unified middleware function for RPC requests (both listener and client) * Works on both listener-side (with context augmentation) and client-side (empty context) * * Key features: * - Can mutate message.data directly for efficiency (compression, validation) * - Can mutate response.result directly for transformation * - Listener-side: Can augment context by returning modified context * - Client-side: Uses TContext = {} (empty context), always returns unchanged * * @typeParam TSchema - The RPC schema type * @typeParam TContext - Custom context type to augment base context (empty {} for client-side) * * @example Listener-side with context augmentation * ```ts * type WalletContext = { productId: string, sourceUrl: string } * const contextMiddleware: RpcMiddleware = { * onRequest: async (message, context) => { * // Read from store and augment context * const productId = await getProductId(context.origin) * return { ...context, productId, sourceUrl: context.origin } * } * } * ``` * * @example Client-side (empty context) * ```ts * const compressionMiddleware: RpcMiddleware = { * onRequest: async (message, context) => { * // Mutate message.data directly * message.data = compress(message.data) * return context // Empty context, unchanged * }, * onResponse: async (message, response, context) => { * // Mutate response.result directly * response.result = decompress(response.result) * return response * } * } * ``` * * @example Shared middleware (works on both sides) * ```ts * const loggingMiddleware: RpcMiddleware = { * onRequest: async (message, context) => { * console.log(`[RPC] ${message.topic}`, context.origin || 'client') * return context * }, * onResponse: async (message, response, context) => { * console.log(`[RPC] ${message.topic} completed`) * return response * } * } * ``` */ type RpcMiddleware> = { /** * Called before handler execution (listener) or before sending (client) * * For listener: Can augment context and mutate message * For client: Can mutate message, context is empty {} * * @param message - The RPC message (can be mutated) * @param context - Request context (listener-side) or empty (client-side) * @returns Updated context (listener mutates this, client returns unchanged) * @throws FrakRpcError to reject the request with a specific error code */ onRequest?: (message: RpcMessage>, context: RpcMiddlewareContext) => Promise> | RpcMiddlewareContext; /** * Called after handler execution (listener) or after receiving (client) * * @param message - The original RPC message * @param response - The response (can be mutated) * @param context - Request context (listener-side) or empty (client-side) * @returns Transformed response * @throws Error to send an error response instead */ onResponse?: (message: RpcMessage>, response: RpcResponse, context: RpcMiddlewareContext) => Promise | RpcResponse; }; /** * Promise handler function type * Handles one-shot requests that return a single promise * * @typeParam TSchema - The RPC schema type * @typeParam TMethod - The method name from the schema * @typeParam TContext - Custom context type augmented by middleware */ type RpcPromiseHandler, TContext = Record> = (params: ExtractParams, context: RpcMiddlewareContext) => Promise>; /** * Stream handler function type * Handles streaming requests that can emit multiple values * * @typeParam TSchema - The RPC schema type * @typeParam TMethod - The method name from the schema * @typeParam TContext - Custom context type augmented by middleware */ type RpcStreamHandler, TContext = Record> = (params: ExtractParams, emitter: StreamEmitter>, context: RpcMiddlewareContext) => Promise | void; //#endregion //#region src/client.d.ts /** * RPC Client configuration * * @typeParam TSchema - The RPC schema type * @typeParam TLifecycleEvent - Lifecycle event union type (e.g., ClientLifecycleEvent | IFrameLifecycleEvent) */ type RpcClientConfig = { /** * The transport to use for emitting events (e.g., window or iframe.contentWindow) */ emittingTransport: RpcTransport; /** * The transport to use for listening to events (e.g., window or iframe.contentWindow) */ listeningTransport: RpcTransport; /** * The target origin for postMessage */ targetOrigin: string; /** * Middleware stack (executed in order) * Middleware can transform outgoing requests and incoming responses * Client-side middleware uses empty context {} * * @example * ```ts * middleware: [ * compressionMiddleware, // Compress outgoing, decompress incoming * loggingMiddleware, // Log RPC calls * ] * ``` */ middleware?: RpcMiddleware[]; /** * Lifecycle event handlers * Handles incoming lifecycle events from the server * * @example * ```ts * lifecycleHandlers: { * iframeLifecycle: (event, data) => { * if (event === 'connected') { * console.log('Wallet ready') * } * } * } * ``` */ lifecycleHandlers?: { clientLifecycle?: LifecycleHandler>; iframeLifecycle?: LifecycleHandler>; }; }; /** * RPC Client interface * Provides methods for making RPC calls to the wallet * * @typeParam TSchema - The RPC schema type */ type RpcClient = { /** * Make a one-shot request that returns a promise * Used for methods with ResponseType: "promise" */ request: >(args: ExtractedSpecificParametersFromRpc) => Promise>; /** * Subscribe to a listener method with a callback * Used for methods with ResponseType: "stream" * Returns an unsubscribe function * * @example * ```ts * const unsubscribe = client.listen('frak_listenToWalletStatus', (status) => { * console.log('Status:', status) * }) * * // Later, unsubscribe * unsubscribe() * ``` */ listen: >(args: ExtractedSpecificParametersFromRpc, callback: (result: ExtractReturnType) => void) => () => void; /** * Send a lifecycle event to the server * Bypasses middleware and is used for connection management * * @example * ```ts * client.sendLifecycle({ clientLifecycle: 'heartbeat' }) * client.sendLifecycle({ clientLifecycle: 'modal-css', data: { cssLink: '...' } }) * ``` */ sendLifecycle: (message: TLifecycleEvent) => void; /** * Clean up resources and close connections */ cleanup: () => void; }; /** * Create an RPC client for SDK-side communication * * @typeParam TSchema - The RPC schema type * @typeParam TLifecycleEvent - Lifecycle event union type (e.g., ClientLifecycleEvent | IFrameLifecycleEvent) * @param config - Client configuration * @returns RPC client instance * * @example * ```ts * import type { IFrameRpcSchema, ClientLifecycleEvent, IFrameLifecycleEvent } from '@frak-labs/core-sdk' * * const client = createRpcClient({ * emittingTransport: window, * listeningTransport: window, * targetOrigin: 'https://wallet.frak.id', * lifecycleHandlers: { * iframeLifecycle: (event, data) => { * // event and data are now strongly typed! * } * } * }) * * // One-shot request * const result = await client.request('frak_sendInteraction', [productId, interaction]) * * // Listener * const unsubscribe = client.listen('frak_listenToWalletStatus', (status) => { * console.log('Wallet status:', status) * }) * ``` */ declare function createRpcClient(config: RpcClientConfig): RpcClient; //#endregion //#region src/error.d.ts /** * RPC error codes * Follows JSON-RPC 2.0 specification with Frak-specific extensions */ declare const RpcErrorCodes: { readonly parseError: -32700; readonly invalidRequest: -32600; readonly methodNotFound: -32601; readonly invalidParams: -32602; readonly internalError: -32603; readonly serverError: -32000; readonly clientNotConnected: -32001; readonly configError: -32002; readonly corruptedResponse: -32003; readonly clientAborted: -32004; readonly walletNotConnected: -32005; readonly serverErrorForInteractionDelegation: -32006; readonly userRejected: -32007; }; /** * Generic Frak RPC error * @ignore */ declare class FrakRpcError extends Error { code: number; data?: T | undefined; constructor(code: number, message: string, data?: T | undefined); toJSON(): RpcError; } /** @ignore */ declare class MethodNotFoundError extends FrakRpcError<{ method: string; }> { constructor(message: string, method: string); } /** @ignore */ declare class InternalError extends FrakRpcError { constructor(message: string); } /** @ignore */ declare class ClientNotFound extends FrakRpcError { constructor(); } //#endregion //#region src/listener.d.ts /** * RPC Listener configuration * * @typeParam TSchema - The RPC schema type * @typeParam TContext - Custom context type to augment base context * @typeParam TLifecycleEvent - Lifecycle event union type (e.g., ClientLifecycleEvent | IFrameLifecycleEvent) */ type RpcListenerConfig, TLifecycleEvent extends LifecycleMessage = LifecycleMessage> = { /** * The transport to use for communication (e.g., window) */ transport: RpcTransport; /** * Allowed origins for security * Can be a single origin or array of origins */ allowedOrigins: string | string[]; /** * Middleware stack (executed in order) * Middleware can augment context, validate requests, and transform responses * * Note: Middleware only applies to RPC messages, not lifecycle or custom messages * * @example * ```ts * middleware: [ * loggingMiddleware, * compressionMiddleware, * contextAugmentationMiddleware * ] * ``` */ middleware?: RpcMiddleware[]; /** * Lifecycle event handlers * Handles client-to-iframe and iframe-to-client lifecycle events * * @example * ```ts * lifecycleHandlers: { * clientLifecycle: (event, data, context) => { * if (event === 'heartbeat') { * console.log('Client heartbeat received') * } * } * } * ``` */ lifecycleHandlers?: { clientLifecycle?: LifecycleHandler>; iframeLifecycle?: LifecycleHandler>; }; }; /** * RPC Listener interface * Handles incoming RPC requests from the SDK * * @typeParam TSchema - The RPC schema type * @typeParam TContext - Custom context type augmented by middleware */ type RpcListener> = { /** * Register a handler for a promise-based method */ handle: >(method: TMethod, handler: RpcPromiseHandler) => void; /** * Register a handler for a streaming method */ handleStream: >(method: TMethod, handler: RpcStreamHandler) => void; /** * Unregister a handler */ unregister: (method: ExtractMethod) => void; /** * Clean up resources */ cleanup: () => void; }; /** * Create an RPC listener for Wallet-side communication * * Supports multiple schemas via union types, enabling a single listener to handle * different RPC protocols (e.g., IFrameRpcSchema | SsoRpcSchema). * * @typeParam TSchema - The RPC schema type (can be a union of multiple schemas) * @typeParam TContext - Custom context type augmented by middleware * @typeParam TLifecycleEvent - Lifecycle event union type (e.g., ClientLifecycleEvent | IFrameLifecycleEvent) * @param config - Listener configuration * @returns RPC listener instance * * @example * ```ts * import type { IFrameRpcSchema, SsoRpcSchema, ClientLifecycleEvent, IFrameLifecycleEvent } from '@frak-labs/core-sdk' * * // Single schema * const listener = createRpcListener({ * transport: window, * allowedOrigins: ['https://example.com'] * }) * * // Multiple schemas (union type) with lifecycle events * type CombinedSchema = IFrameRpcSchema | SsoRpcSchema * const listener = createRpcListener({ * transport: window, * allowedOrigins: '*', * middleware: [compressionMiddleware, contextMiddleware], * lifecycleHandlers: { * clientLifecycle: (event, data, context) => { * // event and data are now strongly typed! * } * } * }) * * // Register handlers for IFrame methods * listener.handle('frak_sendInteraction', async (params, context) => { * return { status: 'success', hash: '0x...' } * }) * * // Register handlers for SSO methods * listener.handle('sso_complete', async (params, context) => { * const [session, sdkJwt, ssoId] = params * return { success: true } * }) * ``` */ declare function createRpcListener, TLifecycleEvent extends LifecycleMessage = LifecycleMessage>(config: RpcListenerConfig): RpcListener; //#endregion //#region src/utils/deferred-promise.d.ts /** * Simple deferred promise wrapper * @ignore */ declare class Deferred { private readonly _promise; private _resolve; private _reject; constructor(); get promise(): Promise; resolve: (value: T | PromiseLike) => void; reject: (reason?: unknown) => void; } //#endregion //#region src/utils/json.d.ts /** * Encode data as JSON and convert to Uint8Array * @param data - Data to encode * @returns Uint8Array of JSON-encoded data */ declare function jsonEncode(data: unknown): Uint8Array; /** * Decode Uint8Array as JSON * @param data - Uint8Array to decode * @returns Parsed data or null if decoding fails */ declare function jsonDecode(data: Uint8Array): T | null; //#endregion export { type AnyMessage, type ClientLifecycleMessage, ClientNotFound, Deferred, type ExtractMethod, type ExtractParams, type ExtractReturnType, type ExtractSchemaEntry, type ExtractedParametersFromRpc, FrakRpcError, type IFrameLifecycleMessage, InternalError, type LifecycleHandler, type LifecycleMessage, MethodNotFoundError, type RpcClient, type RpcClientConfig, type RpcError, RpcErrorCodes, type RpcListener, type RpcListenerConfig, type RpcMessage, type RpcMiddleware, type RpcMiddlewareContext, type RpcPromiseHandler, type RpcRequestContext, type RpcResponse, type RpcSchema, type RpcSchemaEntry, type RpcStreamHandler, type RpcTransport, type StreamEmitter, type TypedRpcRequest, createRpcClient, createRpcListener, jsonDecode, jsonEncode };