@frak-labs/frame-connector
Version: 
Generic, type-safe RPC communication layer for bidirectional postMessage communication
1 lines • 7.99 kB
JavaScript
import{CborDecoder as e,CborEncoder as r}from"@jsonjoy.com/json-pack/lib/cbor/index.js";import{sha256 as t}from"viem";let o={parseError:-32700,invalidRequest:-32600,methodNotFound:-32601,invalidParams:-32602,internalError:-32603,serverError:-32e3,clientNotConnected:-32001,configError:-32002,corruptedResponse:-32003,clientAborted:-32004,walletNotConnected:-32005,serverErrorForInteractionDelegation:-32006,userRejected:-32007};class n extends Error{code;data;constructor(e,r,t){super(r),this.code=e,this.data=t}toJSON(){return{code:this.code,message:this.message,data:this.data}}}class i extends n{constructor(e,r){super(o.methodNotFound,e,{method:r})}}class a extends n{constructor(e){super(o.internalError,e)}}class s extends n{constructor(){super(o.clientNotConnected,"Client not found")}}class c{_promise;_resolve;_reject;constructor(){this._promise=new Promise((e,r)=>{this._resolve=e,this._reject=r})}get promise(){return this._promise}resolve=e=>{this._resolve?.(e)};reject=e=>{this._reject?.(e)}}function l(e){let{emittingTransport:r,listeningTransport:t,targetOrigin:o,middleware:i=[],lifecycleHandlers:a}=e,s=new Map;async function l(e){try{"clientLifecycle"in e&&a?.clientLifecycle?await a.clientLifecycle(e,{origin:o,source:null}):"iframeLifecycle"in e&&a?.iframeLifecycle&&await a.iframeLifecycle(e,{origin:o,source:null})}catch(e){console.error("[RPC Client] Lifecycle handler error:",e)}}async function d(e){let r={origin:o,source:null};for(let t of i)t.onRequest&&await t.onRequest(e,r);return e}async function u(e,r){let t={origin:o,source:null},n=r;for(let r of i)r.onResponse&&(n=await r.onResponse(e,n,t));return n}async function f(e){var r,t;let n;try{let r=new URL(e.origin).origin.toLowerCase(),t=new URL(o).origin.toLowerCase();if(r!==t)return void console.log("Not expected origin",r,t)}catch(e){console.error("[RPC Client] Invalid origin",e);return}if("object"==typeof(r=e.data)&&r&&("clientLifecycle"in r||"iframeLifecycle"in r))return void await l(e.data);if(!("object"==typeof(t=e.data)&&t&&"id"in t&&"topic"in t&&"data"in t))return;try{let r=e.data.data,t=r instanceof Uint8Array||ArrayBuffer.isView(r);n=await u(e.data,t?{result:r}:r)}catch(e){console.error("[RPC Client] Middleware error on response:",e);return}let i=s.get(e.data.id);i&&i(n)}async function p(e){let t=e;try{t=await d(e)}catch(e){throw console.error("[RPC Client] Middleware error on request:",e),e}r.postMessage(t,o)}function m(){return`${Date.now()}-${Math.random().toString(36).substring(2,9)}`}return t.addEventListener("message",f),{request:function(e){let r=m(),t=new c;return s.set(r,e=>{e.error?t.reject(new n(e.error.code,e.error.message,e.error.data)):t.resolve(e.result),s.delete(r)}),p({id:r,topic:e.method,data:{method:e.method,params:e.params}}).catch(e=>{s.delete(r),t.reject(e)}),t.promise},listen:function(e,r){let t=m();return s.set(t,e=>{e.error?(console.error("[RPC Client] Listener error:",e.error),s.delete(t)):r(e.result)}),p({id:t,topic:e.method,data:{method:e.method,params:e.params}}).catch(e=>{console.error("[RPC Client] Failed to send listener request:",e),s.delete(t)}),()=>{s.delete(t)}},sendLifecycle:function(e){r.postMessage(e,o)},cleanup:function(){t.removeEventListener("message",f),s.clear()}}}function d(e){let{transport:r,allowedOrigins:t,middleware:i=[],lifecycleHandlers:a}=e,s=Array.isArray(t)?t:[t],c=new Map,l=new Map;async function d(e,r){let t=r;for(let r of i)r.onRequest&&(t=await r.onRequest(e,t));return t}async function u(e,r,t){let o=r;for(let r of i)r.onResponse&&(o=await r.onResponse(e,o,t));return o}function f(e,r,t,o,n){if(!e)return void console.error("[RPC Listener] No source to send response to");let i={id:t,topic:o,data:!n.error&&(n.result instanceof Uint8Array||ArrayBuffer.isView(n.result))?n.result:n};"postMessage"in e&&"function"==typeof e.postMessage&&e.postMessage(i,{targetOrigin:r})}function p(e,r,t,i,a){f(e,r,t,i,{error:a instanceof n?a.toJSON():{code:o.internalError,message:a.message}})}async function m(e,r){try{"clientLifecycle"in e&&a?.clientLifecycle?await a.clientLifecycle(e,r):"iframeLifecycle"in e&&a?.iframeLifecycle&&await a.iframeLifecycle(e,r)}catch(e){console.error("[RPC Listener] Lifecycle handler error:",e)}}async function h(e){var r,t;let o;if(!function(e){try{if(s.includes("*"))return!0;let r=new URL(e).origin.toLowerCase();return s.some(e=>{let t=new URL(e).origin.toLowerCase();return r===t})}catch(e){return console.error("[RPC Listener] Invalid origin",e),!1}}(e.origin))return void console.warn("[RPC Listener] Message from disallowed origin:",e.origin);let n={origin:e.origin,source:e.source};if("object"==typeof(r=e.data)&&r&&("clientLifecycle"in r||"iframeLifecycle"in r))return void await m(e.data,n);if("object"==typeof(t=e.data)&&t&&"id"in t&&"topic"in t&&"data"in t){try{o=await d(e.data,n)}catch(r){p(e.source,e.origin,e.data.id,e.data.topic,r instanceof Error?r:Error(String(r)));return}try{await y(e,o)}catch(r){p(e.source,e.origin,e.data.id,e.data.topic,r instanceof Error?r:Error(String(r)))}}}async function y(e,r){let{id:t,topic:i,data:a}=e.data,s=c.get(i);if(s){let o=await s(a,r),n=await u(e.data,{result:o},r);f(e.source,e.origin,t,i,n);return}let d=l.get(i);if(d){let o=async o=>{let n;try{n=await u(e.data,{result:o},r)}catch(e){console.error("[RPC Listener] Middleware failed on stream chunk:",e);return}f(e.source,e.origin,t,i,n)};await d(a,o,r);return}console.error("[RPC Listener] No handler found for method:",{topic:i,handlers:l.keys(),promiseHandler:c.keys()}),p(e.source,e.origin,t,i,new n(o.methodNotFound,`Method not found: ${i}`))}return r.addEventListener("message",h),{handle:function(e,r){c.set(e,r)},handleStream:function(e,r){l.set(e,r)},unregister:function(e){c.delete(e),l.delete(e)},cleanup:function(){r.removeEventListener("message",h),c.clear(),l.clear()}}}let u=new r,f=new e;function p(e){let r={...e,validationHash:w(e??{})};return u.encode(r)}function m(e){if(!e.length)throw new n(o.corruptedResponse,"Missing compressed data");let r=y(e);if(!r)throw new n(o.corruptedResponse,"Invalid compressed data");if(!r?.validationHash)throw new n(o.corruptedResponse,"Missing validation hash");let{validationHash:t,...i}=r,a=w(i);if(a!==r.validationHash)throw console.warn("Validation error",{validationHash:t,rawResultData:i,parsedData:r,expectedValidationHashes:a,recomputedHash:w(void 0)}),new n(o.corruptedResponse,"Invalid validation hash");return r}function h(e){return u.encode(e)}function y(e){try{return f.decode(e)}catch(r){return console.error("Invalid compressed data",{e:r,data:e}),null}}function w(e){return t(u.encode(e))}let g=()=>({onRequest:(e,r)=>{if(e.data instanceof Uint8Array||ArrayBuffer.isView(e.data))return r;try{e.data=p(e.data)}catch(e){console.error("[Compression Middleware] Failed to compress request",e)}return r},onResponse:(e,r,t)=>{if(r.error||!(r.result instanceof Uint8Array||ArrayBuffer.isView(r.result)))return r;try{let{validationHash:e,...t}=m(r.result);"object"==typeof t&&null!==t&&"result"in t?r.result=t.result:r.result=t}catch(e){console.error("[Compression Middleware] Failed to decompress response",e)}return r}}),L=()=>({onRequest:(e,r)=>{if(!(e.data instanceof Uint8Array||ArrayBuffer.isView(e.data)))return r;try{let{validationHash:r,...t}=m(e.data);"object"==typeof t&&"params"in t?e.data=t.params:e.data=t}catch(e){throw console.error("[Compression Middleware] Failed to decompress request",e),e}return r},onResponse:(e,r,t)=>{if(r.error||r.result instanceof Uint8Array||ArrayBuffer.isView(r.result))return r;try{let t={method:e.topic,result:r.result};r.result=p(t)}catch(e){console.error("[Compression Middleware] Failed to compress response",e)}return r}});export{s as ClientNotFound,c as Deferred,n as FrakRpcError,a as InternalError,i as MethodNotFoundError,o as RpcErrorCodes,h as compressJson,g as createClientCompressionMiddleware,L as createListenerCompressionMiddleware,l as createRpcClient,d as createRpcListener,m as decompressDataAndCheckHash,y as decompressJson,p as hashAndCompressData};