UNPKG

@frak-labs/frame-connector

Version:

Generic, type-safe RPC communication layer for bidirectional postMessage communication

252 lines (240 loc) 7.93 kB
/** * Client-side compression middleware * * Compresses outgoing requests and decompresses incoming responses. * Always uses the format: {method: string, params: unknown} * * @example Client side * ```ts * const client = createRpcClient({ * transport: iframe.contentWindow, * targetOrigin: 'https://wallet.frak.id', * middleware: [createClientCompressionMiddleware()] * }) * ``` */ export declare const createClientCompressionMiddleware: <TSchema extends RpcSchema, TContext>() => RpcMiddleware<TSchema, TContext>; /** * Listener-side compression middleware * * Decompresses incoming requests and compresses outgoing responses. * Always uses the format: {method: string, params: unknown} * * @example Listener side * ```ts * const listener = createRpcListener({ * transport: window, * allowedOrigins: ['https://example.com'], * middleware: [createListenerCompressionMiddleware()] * }) * ``` */ export declare const createListenerCompressionMiddleware: <TSchema extends RpcSchema, TContext>() => RpcMiddleware<TSchema, TContext>; /** * Extract method names from a schema * * @typeParam TSchema - The RPC schema type * * @example * ```ts * type Methods = ExtractMethod<MySchema> * // "greet" | "watchTime" * ``` */ declare type ExtractMethod<TSchema extends RpcSchema> = TSchema[number]["Method"]; /** * RPC error object */ declare type RpcError = { code: number; message: string; data?: unknown; }; /** * 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) */ declare type RpcMessage<TMethod extends string = string> = { /** * 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; }; /** * 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<MySchema, WalletContext> = { * 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<MySchema> = { * 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<MySchema> = { * 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 * } * } * ``` */ declare type RpcMiddleware<TSchema extends RpcSchema, TContext = Record<string, never>> = { /** * 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<ExtractMethod<TSchema>>, context: RpcMiddlewareContext<TContext>) => Promise<RpcMiddlewareContext<TContext>> | RpcMiddlewareContext<TContext>; /** * 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<ExtractMethod<TSchema>>, response: RpcResponse, context: RpcMiddlewareContext<TContext>) => Promise<RpcResponse> | RpcResponse; }; /** * 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 } * ``` */ declare type RpcMiddlewareContext<TCustomContext = Record<string, never>> = RpcRequestContext & TCustomContext; /** * Request context for handlers * Contains information about the origin and source of the request */ declare type RpcRequestContext = { /** * Origin of the request */ origin: string; /** * Message source (for responding) */ source: MessageEventSource | null; }; /** * RPC response wrapper * Contains either a successful result or an error */ declare type RpcResponse<TResult = unknown> = { result: TResult; error?: never; } | { result?: never; error: RpcError; }; /** * 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"; * } * ] * ``` */ declare type RpcSchema = readonly RpcSchemaEntry[]; /** * 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" */ declare type RpcSchemaEntry<TMethod extends string = string, TParams = unknown, TReturn = unknown> = { /** * The method name (e.g., "frak_sendInteraction") */ Method: TMethod; /** * The parameters type (undefined if no parameters) */ Parameters?: TParams; /** * The return type */ ReturnType: TReturn; }; export { }