@frak-labs/frame-connector 0.0.1-beta.f259d7fc → 0.1.0-beta.00226d62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,402 @@
1
+ import { Prettify } from "viem";
2
+
3
+ //#region src/rpc-schema.d.ts
4
+
5
+ /**
6
+ * Generic shape of a single RPC schema entry
7
+ *
8
+ * Each entry defines a method with its parameters, return type, and response kind
9
+ *
10
+ * @typeParam TMethod - The method name (string literal)
11
+ * @typeParam TParams - The parameters type (can be undefined for no parameters)
12
+ * @typeParam TReturn - The return type
13
+ * @typeParam TResponseKind - Either "promise" or "stream"
14
+ */
15
+ type RpcSchemaEntry<TMethod extends string = string, TParams = unknown, TReturn = unknown> = {
16
+ /**
17
+ * The method name (e.g., "frak_sendInteraction")
18
+ */
19
+ Method: TMethod;
20
+ /**
21
+ * The parameters type (undefined if no parameters)
22
+ */
23
+ Parameters?: TParams;
24
+ /**
25
+ * The return type
26
+ */
27
+ ReturnType: TReturn;
28
+ };
29
+ /**
30
+ * An RPC schema is a readonly array of schema entries
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * type MySchema = [
35
+ * {
36
+ * Method: "greet";
37
+ * Parameters: [name: string];
38
+ * ReturnType: string;
39
+ * ResponseType: "promise";
40
+ * },
41
+ * {
42
+ * Method: "watchTime";
43
+ * Parameters?: undefined;
44
+ * ReturnType: number;
45
+ * ResponseType: "stream";
46
+ * }
47
+ * ]
48
+ * ```
49
+ */
50
+ type RpcSchema = readonly RpcSchemaEntry[];
51
+ /**
52
+ * Extract method names from a schema
53
+ *
54
+ * @typeParam TSchema - The RPC schema type
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * type Methods = ExtractMethod<MySchema>
59
+ * // "greet" | "watchTime"
60
+ * ```
61
+ */
62
+ type ExtractMethod<TSchema extends RpcSchema> = TSchema[number]["Method"];
63
+ /**
64
+ * Extract a specific schema entry by method name
65
+ *
66
+ * @typeParam TSchema - The RPC schema type
67
+ * @typeParam TMethod - The method name to extract
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * type GreetEntry = ExtractSchemaEntry<MySchema, "greet">
72
+ * // { Method: "greet"; Parameters: [name: string]; ReturnType: string; ResponseType: "promise" }
73
+ * ```
74
+ */
75
+ type ExtractSchemaEntry<TSchema extends RpcSchema, TMethod extends ExtractMethod<TSchema>> = Extract<TSchema[number], {
76
+ Method: TMethod;
77
+ }>;
78
+ /**
79
+ * Extract parameters type for a specific method
80
+ *
81
+ * @typeParam TSchema - The RPC schema type
82
+ * @typeParam TMethod - The method name
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * type GreetParams = ExtractParams<MySchema, "greet">
87
+ * // [name: string]
88
+ * ```
89
+ */
90
+ type ExtractParams<TSchema extends RpcSchema, TMethod extends ExtractMethod<TSchema>> = ExtractSchemaEntry<TSchema, TMethod>["Parameters"];
91
+ /**
92
+ * Type that extract the possible parameters from a RPC Schema
93
+ * @ignore
94
+ */
95
+ type ExtractedParametersFromRpc<TRpcSchema extends RpcSchema> = { [K in keyof TRpcSchema]: Prettify<{
96
+ method: TRpcSchema[K] extends TRpcSchema[number] ? TRpcSchema[K]["Method"] : string;
97
+ } & (TRpcSchema[K] extends TRpcSchema[number] ? TRpcSchema[K]["Parameters"] extends undefined ? {
98
+ params?: never;
99
+ } : {
100
+ params: TRpcSchema[K]["Parameters"];
101
+ } : never)> }[number];
102
+ /**
103
+ * Type that extract the possible parameters from a RPC Schema
104
+ * @ignore
105
+ */
106
+ type ExtractedSpecificParametersFromRpc<TRpcSchema extends RpcSchema, TMethod extends ExtractMethod<TRpcSchema>> = Extract<ExtractedParametersFromRpc<TRpcSchema>, {
107
+ method: TMethod;
108
+ }>;
109
+ /**
110
+ * Extract return type for a specific method
111
+ *
112
+ * @typeParam TSchema - The RPC schema type
113
+ * @typeParam TMethod - The method name
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * type GreetReturn = ExtractReturnType<MySchema, "greet">
118
+ * // string
119
+ * ```
120
+ */
121
+ type ExtractReturnType<TSchema extends RpcSchema, TMethod extends ExtractMethod<TSchema>> = ExtractSchemaEntry<TSchema, TMethod>["ReturnType"];
122
+ //#endregion
123
+ //#region src/types.d.ts
124
+ /**
125
+ * Transport interface for RPC communication
126
+ * Abstracts the underlying message passing mechanism (postMessage, etc)
127
+ */
128
+ type RpcTransport = {
129
+ /**
130
+ * Send a message through the transport
131
+ */
132
+ postMessage: (message: RpcMessage, targetOrigin: string) => void;
133
+ /**
134
+ * Listen for messages
135
+ */
136
+ addEventListener: (type: "message", listener: (event: MessageEvent<RpcMessage>) => void) => void;
137
+ /**
138
+ * Remove message listener
139
+ */
140
+ removeEventListener: (type: "message", listener: (event: MessageEvent<RpcMessage>) => void) => void;
141
+ };
142
+ /**
143
+ * RPC message format (maintains backward compatibility)
144
+ * This is the exact format sent over the wire
145
+ *
146
+ * @typeParam TMethod - The method name type (defaults to string for flexibility)
147
+ */
148
+ type RpcMessage<TMethod extends string = string> = {
149
+ /**
150
+ * Unique message identifier for correlating requests and responses
151
+ */
152
+ id: string;
153
+ /**
154
+ * The RPC method name (topic for backward compatibility)
155
+ */
156
+ topic: TMethod;
157
+ /**
158
+ * The message payload (compressed data) or raw params
159
+ */
160
+ data: unknown;
161
+ };
162
+ /**
163
+ * Lifecycle message format for client-to-iframe communication
164
+ * These messages handle connection lifecycle events (handshake, heartbeat, etc.)
165
+ */
166
+ type ClientLifecycleMessage = {
167
+ clientLifecycle: string;
168
+ data?: unknown;
169
+ };
170
+ /**
171
+ * Lifecycle message format for iframe-to-client communication
172
+ */
173
+ type IFrameLifecycleMessage = {
174
+ iframeLifecycle: string;
175
+ data?: unknown;
176
+ };
177
+ /**
178
+ * Union of all lifecycle message types
179
+ */
180
+ type LifecycleMessage = ClientLifecycleMessage | IFrameLifecycleMessage;
181
+ /**
182
+ * Union of all message types that can be received
183
+ */
184
+ type AnyMessage = RpcMessage | LifecycleMessage;
185
+ /**
186
+ * RPC response wrapper
187
+ * Contains either a successful result or an error
188
+ */
189
+ type RpcResponse<TResult = unknown> = {
190
+ result: TResult;
191
+ error?: never;
192
+ } | {
193
+ result?: never;
194
+ error: RpcError;
195
+ };
196
+ /**
197
+ * RPC error object
198
+ */
199
+ type RpcError = {
200
+ code: number;
201
+ message: string;
202
+ data?: unknown;
203
+ };
204
+ /**
205
+ * Request context for handlers
206
+ * Contains information about the origin and source of the request
207
+ */
208
+ type RpcRequestContext = {
209
+ /**
210
+ * Origin of the request
211
+ */
212
+ origin: string;
213
+ /**
214
+ * Message source (for responding)
215
+ */
216
+ source: MessageEventSource | null;
217
+ };
218
+ /**
219
+ * Middleware context that can be augmented with custom fields
220
+ * Generic type parameter allows domain-specific context augmentation
221
+ *
222
+ * @typeParam TCustomContext - Custom context fields to merge with base context
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * type WalletContext = RpcMiddlewareContext<{
227
+ * productId: string
228
+ * sourceUrl: string
229
+ * isAutoContext: boolean
230
+ * }>
231
+ * // { origin: string, source: MessageEventSource | null, productId: string, sourceUrl: string, isAutoContext: boolean }
232
+ * ```
233
+ */
234
+ type RpcMiddlewareContext<TCustomContext = Record<string, never>> = RpcRequestContext & TCustomContext;
235
+ /**
236
+ * Type-safe request parameters
237
+ *
238
+ * @typeParam TSchema - The RPC schema type
239
+ * @typeParam TMethod - The method name from the schema
240
+ */
241
+ type TypedRpcRequest<TSchema extends RpcSchema, TMethod extends ExtractMethod<TSchema>> = {
242
+ method: TMethod;
243
+ params: ExtractParams<TSchema, TMethod>;
244
+ };
245
+ /**
246
+ * Stream emitter function
247
+ * Used by stream handlers to emit multiple values
248
+ */
249
+ type StreamEmitter<TResult> = (chunk: TResult) => void;
250
+ /**
251
+ * Lifecycle handler function
252
+ * Handles lifecycle events using discriminated unions for automatic type narrowing
253
+ *
254
+ * @typeParam TLifecycleEvent - The lifecycle event union type (e.g., ClientLifecycleEvent | IFrameLifecycleEvent)
255
+ *
256
+ * @param event - The full lifecycle event object with discriminated union
257
+ * @param context - Request context with origin and source
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * const handler: LifecycleHandler<ClientLifecycleEvent> = (event, context) => {
262
+ * if (event.clientLifecycle === "modal-css") {
263
+ * // event.data is automatically typed as { cssLink: string }
264
+ * console.log(event.data.cssLink)
265
+ * }
266
+ * }
267
+ * ```
268
+ */
269
+ type LifecycleHandler<TLifecycleEvent = unknown> = (event: TLifecycleEvent, context: RpcRequestContext) => void | Promise<void>;
270
+ /**
271
+ * Unified middleware function for RPC requests (both listener and client)
272
+ * Works on both listener-side (with context augmentation) and client-side (empty context)
273
+ *
274
+ * Key features:
275
+ * - Can mutate message.data directly for efficiency (compression, validation)
276
+ * - Can mutate response.result directly for transformation
277
+ * - Listener-side: Can augment context by returning modified context
278
+ * - Client-side: Uses TContext = {} (empty context), always returns unchanged
279
+ *
280
+ * @typeParam TSchema - The RPC schema type
281
+ * @typeParam TContext - Custom context type to augment base context (empty {} for client-side)
282
+ *
283
+ * @example Listener-side with context augmentation
284
+ * ```ts
285
+ * type WalletContext = { productId: string, sourceUrl: string }
286
+ * const contextMiddleware: RpcMiddleware<MySchema, WalletContext> = {
287
+ * onRequest: async (message, context) => {
288
+ * // Read from store and augment context
289
+ * const productId = await getProductId(context.origin)
290
+ * return { ...context, productId, sourceUrl: context.origin }
291
+ * }
292
+ * }
293
+ * ```
294
+ *
295
+ * @example Client-side (empty context)
296
+ * ```ts
297
+ * const compressionMiddleware: RpcMiddleware<MySchema> = {
298
+ * onRequest: async (message, context) => {
299
+ * // Mutate message.data directly
300
+ * message.data = compress(message.data)
301
+ * return context // Empty context, unchanged
302
+ * },
303
+ * onResponse: async (message, response, context) => {
304
+ * // Mutate response.result directly
305
+ * response.result = decompress(response.result)
306
+ * return response
307
+ * }
308
+ * }
309
+ * ```
310
+ *
311
+ * @example Shared middleware (works on both sides)
312
+ * ```ts
313
+ * const loggingMiddleware: RpcMiddleware<MySchema> = {
314
+ * onRequest: async (message, context) => {
315
+ * console.log(`[RPC] ${message.topic}`, context.origin || 'client')
316
+ * return context
317
+ * },
318
+ * onResponse: async (message, response, context) => {
319
+ * console.log(`[RPC] ${message.topic} completed`)
320
+ * return response
321
+ * }
322
+ * }
323
+ * ```
324
+ */
325
+ type RpcMiddleware<TSchema extends RpcSchema, TContext = Record<string, never>> = {
326
+ /**
327
+ * Called before handler execution (listener) or before sending (client)
328
+ *
329
+ * For listener: Can augment context and mutate message
330
+ * For client: Can mutate message, context is empty {}
331
+ *
332
+ * @param message - The RPC message (can be mutated)
333
+ * @param context - Request context (listener-side) or empty (client-side)
334
+ * @returns Updated context (listener mutates this, client returns unchanged)
335
+ * @throws FrakRpcError to reject the request with a specific error code
336
+ */
337
+ onRequest?: (message: RpcMessage<ExtractMethod<TSchema>>, context: RpcMiddlewareContext<TContext>) => Promise<RpcMiddlewareContext<TContext>> | RpcMiddlewareContext<TContext>;
338
+ /**
339
+ * Called after handler execution (listener) or after receiving (client)
340
+ *
341
+ * @param message - The original RPC message
342
+ * @param response - The response (can be mutated)
343
+ * @param context - Request context (listener-side) or empty (client-side)
344
+ * @returns Transformed response
345
+ * @throws Error to send an error response instead
346
+ */
347
+ onResponse?: (message: RpcMessage<ExtractMethod<TSchema>>, response: RpcResponse, context: RpcMiddlewareContext<TContext>) => Promise<RpcResponse> | RpcResponse;
348
+ };
349
+ /**
350
+ * Promise handler function type
351
+ * Handles one-shot requests that return a single promise
352
+ *
353
+ * @typeParam TSchema - The RPC schema type
354
+ * @typeParam TMethod - The method name from the schema
355
+ * @typeParam TContext - Custom context type augmented by middleware
356
+ */
357
+ type RpcPromiseHandler<TSchema extends RpcSchema, TMethod extends ExtractMethod<TSchema>, TContext = Record<string, never>> = (params: ExtractParams<TSchema, TMethod>, context: RpcMiddlewareContext<TContext>) => Promise<ExtractReturnType<TSchema, TMethod>>;
358
+ /**
359
+ * Stream handler function type
360
+ * Handles streaming requests that can emit multiple values
361
+ *
362
+ * @typeParam TSchema - The RPC schema type
363
+ * @typeParam TMethod - The method name from the schema
364
+ * @typeParam TContext - Custom context type augmented by middleware
365
+ */
366
+ type RpcStreamHandler<TSchema extends RpcSchema, TMethod extends ExtractMethod<TSchema>, TContext = Record<string, never>> = (params: ExtractParams<TSchema, TMethod>, emitter: StreamEmitter<ExtractReturnType<TSchema, TMethod>>, context: RpcMiddlewareContext<TContext>) => Promise<void> | void;
367
+ //#endregion
368
+ //#region src/middleware/compression.d.ts
369
+ /**
370
+ * Client-side compression middleware
371
+ *
372
+ * Compresses outgoing requests and decompresses incoming responses.
373
+ * Always uses the format: {method: string, params: unknown}
374
+ *
375
+ * @example Client side
376
+ * ```ts
377
+ * const client = createRpcClient({
378
+ * transport: iframe.contentWindow,
379
+ * targetOrigin: 'https://wallet.frak.id',
380
+ * middleware: [createClientCompressionMiddleware()]
381
+ * })
382
+ * ```
383
+ */
384
+ declare const createClientCompressionMiddleware: <TSchema extends RpcSchema, TContext>() => RpcMiddleware<TSchema, TContext>;
385
+ /**
386
+ * Listener-side compression middleware
387
+ *
388
+ * Decompresses incoming requests and compresses outgoing responses.
389
+ * Always uses the format: {method: string, params: unknown}
390
+ *
391
+ * @example Listener side
392
+ * ```ts
393
+ * const listener = createRpcListener({
394
+ * transport: window,
395
+ * allowedOrigins: ['https://example.com'],
396
+ * middleware: [createListenerCompressionMiddleware()]
397
+ * })
398
+ * ```
399
+ */
400
+ declare const createListenerCompressionMiddleware: <TSchema extends RpcSchema, TContext>() => RpcMiddleware<TSchema, TContext>;
401
+ //#endregion
402
+ export { ExtractedParametersFromRpc as C, RpcSchemaEntry as E, ExtractSchemaEntry as S, RpcSchema as T, StreamEmitter as _, IFrameLifecycleMessage as a, ExtractParams as b, RpcError as c, RpcMiddlewareContext as d, RpcPromiseHandler as f, RpcTransport as g, RpcStreamHandler as h, ClientLifecycleMessage as i, RpcMessage as l, RpcResponse as m, createListenerCompressionMiddleware as n, LifecycleHandler as o, RpcRequestContext as p, AnyMessage as r, LifecycleMessage as s, createClientCompressionMiddleware as t, RpcMiddleware as u, TypedRpcRequest as v, ExtractedSpecificParametersFromRpc as w, ExtractReturnType as x, ExtractMethod as y };