// Preloaded data structure embedded in HTML (gzip-compressed, base64-encoded protobuf bytes) export type PreloadedData = { [method: string]: { requestBytes: string; responseBytes: string; }; }; declare global { interface Window { __PRELOADED__?: PreloadedData; __PRELOAD_TIMESTAMP__?: number; } } // RPC declaration for route preloading export type RpcDeclaration = { method: string; params?: Record; }; export type DecoderMap = Record< string, (bytes: Uint8Array) => unknown >; /** * Decode base64 string to Uint8Array */ function base64ToBytes(base64: string): Uint8Array { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } /** * Decompress gzipped bytes using DecompressionStream API */ async function decompressGzip(compressed: Uint8Array): Promise { const ds = new DecompressionStream("gzip"); const writer = ds.writable.getWriter(); writer.write(compressed as unknown as BufferSource); writer.close(); const reader = ds.readable.getReader(); const chunks: Uint8Array[] = []; let totalLength = 0; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); totalLength += value.length; } const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return result; } /** * Decode and dispatch all preloaded RPCs. * Returns array of { method, request, response } for dispatching to stores. * Data is gzip compressed, so decompression is async. */ export async function decodeAllPreloaded( requestDecoders: DecoderMap, responseDecoders: DecoderMap ): Promise< Array<{ method: string; request: unknown; response: unknown; }> > { const preloaded = window.__PRELOADED__; if (!preloaded) { return []; } const results: Array<{ method: string; request: unknown; response: unknown; }> = []; for (const [method, { requestBytes, responseBytes }] of Object.entries( preloaded )) { const reqDecoder = requestDecoders[method]; const resDecoder = responseDecoders[method]; if (!resDecoder) { console.warn(`[Preload] No decoder for method: ${method}`); continue; } try { // Decode request (may be empty for some RPCs) let request: unknown = {}; if (requestBytes && reqDecoder) { const compressedReq = base64ToBytes(requestBytes); const reqBytes = await decompressGzip(compressedReq); request = reqDecoder(reqBytes); } // Decode response (decompress gzipped data) const compressedRes = base64ToBytes(responseBytes); const resBytes = await decompressGzip(compressedRes); const response = resDecoder(resBytes); results.push({ method, request, response }); } catch (err) { console.error(`[Preload] Failed to decode ${method}:`, err); } } // Clear preloaded data delete window.__PRELOADED__; return results; }