@frak-labs/core-sdk 0.0.19 → 0.1.0-beta.8d103039

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.
Files changed (85) hide show
  1. package/cdn/bundle.js +5 -5
  2. package/dist/actions.cjs +1 -1
  3. package/dist/actions.d.cts +154 -73
  4. package/dist/actions.d.ts +154 -73
  5. package/dist/actions.js +1 -1
  6. package/dist/bundle.cjs +2 -2
  7. package/dist/bundle.d.cts +257 -192
  8. package/dist/bundle.d.ts +257 -192
  9. package/dist/bundle.js +2 -2
  10. package/dist/index.cjs +11 -11
  11. package/dist/index.d.cts +199 -185
  12. package/dist/index.d.ts +199 -185
  13. package/dist/index.js +2 -2
  14. package/package.json +7 -2
  15. package/src/actions/displayEmbeddedWallet.ts +20 -0
  16. package/src/actions/displayModal.ts +131 -0
  17. package/src/actions/getProductInformation.ts +14 -0
  18. package/src/actions/index.ts +29 -0
  19. package/src/actions/openSso.ts +116 -0
  20. package/src/actions/prepareSso.ts +48 -0
  21. package/src/actions/referral/processReferral.ts +230 -0
  22. package/src/actions/referral/referralInteraction.ts +57 -0
  23. package/src/actions/sendInteraction.ts +32 -0
  24. package/src/actions/trackPurchaseStatus.ts +53 -0
  25. package/src/actions/watchWalletStatus.ts +94 -0
  26. package/src/actions/wrapper/modalBuilder.ts +212 -0
  27. package/src/actions/wrapper/sendTransaction.ts +62 -0
  28. package/src/actions/wrapper/siweAuthenticate.ts +94 -0
  29. package/src/bundle.ts +3 -0
  30. package/src/clients/DebugInfo.ts +182 -0
  31. package/src/clients/createIFrameFrakClient.ts +287 -0
  32. package/src/clients/index.ts +3 -0
  33. package/src/clients/setupClient.ts +71 -0
  34. package/src/clients/transports/iframeLifecycleManager.ts +88 -0
  35. package/src/constants/interactionTypes.ts +44 -0
  36. package/src/constants/locales.ts +14 -0
  37. package/src/constants/productTypes.ts +33 -0
  38. package/src/index.ts +103 -0
  39. package/src/interactions/index.ts +5 -0
  40. package/src/interactions/pressEncoder.ts +53 -0
  41. package/src/interactions/purchaseEncoder.ts +94 -0
  42. package/src/interactions/referralEncoder.ts +47 -0
  43. package/src/interactions/retailEncoder.ts +37 -0
  44. package/src/interactions/webshopEncoder.ts +30 -0
  45. package/src/types/client.ts +14 -0
  46. package/src/types/compression.ts +22 -0
  47. package/src/types/config.ts +111 -0
  48. package/src/types/context.ts +13 -0
  49. package/src/types/index.ts +70 -0
  50. package/src/types/lifecycle/client.ts +46 -0
  51. package/src/types/lifecycle/iframe.ts +35 -0
  52. package/src/types/lifecycle/index.ts +2 -0
  53. package/src/types/rpc/displayModal.ts +84 -0
  54. package/src/types/rpc/embedded/index.ts +68 -0
  55. package/src/types/rpc/embedded/loggedIn.ts +55 -0
  56. package/src/types/rpc/embedded/loggedOut.ts +28 -0
  57. package/src/types/rpc/interaction.ts +43 -0
  58. package/src/types/rpc/modal/final.ts +46 -0
  59. package/src/types/rpc/modal/generic.ts +46 -0
  60. package/src/types/rpc/modal/index.ts +20 -0
  61. package/src/types/rpc/modal/login.ts +32 -0
  62. package/src/types/rpc/modal/openSession.ts +25 -0
  63. package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
  64. package/src/types/rpc/modal/transaction.ts +33 -0
  65. package/src/types/rpc/productInformation.ts +59 -0
  66. package/src/types/rpc/sso.ts +80 -0
  67. package/src/types/rpc/walletStatus.ts +35 -0
  68. package/src/types/rpc.ts +158 -0
  69. package/src/types/transport.ts +34 -0
  70. package/src/utils/FrakContext.ts +152 -0
  71. package/src/utils/compression/b64.ts +29 -0
  72. package/src/utils/compression/compress.ts +11 -0
  73. package/src/utils/compression/decompress.ts +11 -0
  74. package/src/utils/compression/index.ts +3 -0
  75. package/src/utils/computeProductId.ts +11 -0
  76. package/src/utils/constants.ts +4 -0
  77. package/src/utils/formatAmount.ts +18 -0
  78. package/src/utils/getCurrencyAmountKey.ts +15 -0
  79. package/src/utils/getSupportedCurrency.ts +14 -0
  80. package/src/utils/getSupportedLocale.ts +16 -0
  81. package/src/utils/iframeHelper.ts +142 -0
  82. package/src/utils/index.ts +21 -0
  83. package/src/utils/sso.ts +119 -0
  84. package/src/utils/ssoUrlListener.ts +60 -0
  85. package/src/utils/trackEvent.ts +26 -0
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import{OpenPanel as e}from"@openpanel/web";import{CborDecoder as t,CborEncoder as n}from"@jsonjoy.com/json-pack/lib/cbor/index.js";import{bytesToHex as r,hexToBytes as o,sha256 as a}from"viem";class i extends Error{code;data;constructor(e,t,n){super(t),this.code=e,this.data=n}}class s extends i{constructor(e){super(l.internalError,e)}}class c extends i{constructor(){super(l.clientNotConnected,"Client not found")}}let l={parseError:-32700,invalidRequest:-32600,methodNotFound:-32601,invalidParams:-32602,internalError:-32603,serverError:-32e3,clientNotConnected:-32001,configError:-32002,corruptedResponse:-32003,clientAborted:-32004,walletNotConnected:-32005,serverErrorForInteractionDelegation:-32006};class d{_promise;_resolve;_reject;constructor(){this._promise=new Promise((e,t)=>{this._resolve=e,this._reject=t})}get promise(){return this._promise}resolve=e=>{this._resolve?.(e)};reject=e=>{this._reject?.(e)}}function u(e){return btoa(Array.from(e,e=>String.fromCharCode(e)).join("")).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function f(e){let t=e.length%4;return Uint8Array.from(atob(e.replace(/-/g,"+").replace(/_/g,"/").padEnd(e.length+(0===t?0:4-t),"=")),e=>e.charCodeAt(0))}let p=new n;function h(e){let t={...e,validationHash:w(e)};return p.encode(t)}function m(e){return p.encode(e)}function g(e){return u(m(e))}function w(e){return a(p.encode(e))}let b=new t;function y(e){if(!e.length)throw new i(l.corruptedResponse,"Missing compressed data");let t=v(e);if(!t)throw new i(l.corruptedResponse,"Invalid compressed data");if(!t?.validationHash)throw new i(l.corruptedResponse,"Missing validation hash");let{validationHash:n,...r}=t;if(w(r)!==t.validationHash)throw new i(l.corruptedResponse,"Invalid data validation hash");return t}function v(e){try{return b.decode(e)}catch(t){return console.error("Invalid compressed data",{e:t,data:e}),null}}function k(e){return v(f(e))}let C="nexus-wallet-backup";class R{config;iframe;isSetupDone=!1;lastResponse=null;lastRequest=null;constructor(e,t){this.config=e,this.iframe=t,this.lastRequest=null,this.lastResponse=null}setLastResponse(e){this.lastResponse={event:e.data,origin:e.origin,timestamp:Date.now()}}setLastRequest(e,t){this.lastRequest={event:e,target:t,timestamp:Date.now()}}updateSetupStatus(e){this.isSetupDone=e}base64Encode(e){try{return btoa(JSON.stringify(e))}catch(e){return console.warn("Failed to encode debug data",e),btoa("Failed to encode data")}}getIframeStatus(){return this.iframe?{loading:this.iframe.hasAttribute("loading"),url:this.iframe.src,readyState:this.iframe.contentDocument?.readyState?+("complete"===this.iframe.contentDocument.readyState):-1,contentWindow:!!this.iframe.contentWindow,isConnected:this.iframe.isConnected}:null}getNavigatorInfo(){return navigator?{userAgent:navigator.userAgent,language:navigator.language,onLine:navigator.onLine,screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio}:null}gatherDebugInfo(e){let t=this.getIframeStatus(),n=this.getNavigatorInfo(),r="Unknown";return e instanceof i?r=`FrakRpcError: ${e.code} '${e.message}'`:e instanceof Error?r=e.message:"string"==typeof e&&(r=e),{timestamp:new Date().toISOString(),encodedUrl:btoa(window.location.href),encodedConfig:this.config?this.base64Encode(this.config):"no-config",navigatorInfo:n?this.base64Encode(n):"no-navigator",iframeStatus:t?this.base64Encode(t):"not-iframe",lastRequest:this.lastRequest?this.base64Encode(this.lastRequest):"No Frak request logged",lastResponse:this.lastResponse?this.base64Encode(this.lastResponse):"No Frak response logged",clientStatus:this.isSetupDone?"setup":"not-setup",error:r}}static empty(){return new R}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
1
+ import{Deferred as e,FrakRpcError as t,RpcErrorCodes as n,compressJson as r,createRpcClient as a,decompressJson as o}from"@frak-labs/frame-connector";import{createClientCompressionMiddleware as i}from"@frak-labs/frame-connector/middleware";import{OpenPanel as s}from"@openpanel/web";import{bytesToHex as c,hexToBytes as l}from"viem";let d="nexus-wallet-backup";class u{config;iframe;isSetupDone=!1;lastResponse=null;lastRequest=null;constructor(e,t){this.config=e,this.iframe=t,this.lastRequest=null,this.lastResponse=null}setLastResponse(e,t){this.lastResponse={message:e,response:t,timestamp:Date.now()}}setLastRequest(e){this.lastRequest={event:e,timestamp:Date.now()}}updateSetupStatus(e){this.isSetupDone=e}base64Encode(e){try{return btoa(JSON.stringify(e))}catch(e){return console.warn("Failed to encode debug data",e),btoa("Failed to encode data")}}getIframeStatus(){return this.iframe?{loading:this.iframe.hasAttribute("loading"),url:this.iframe.src,readyState:this.iframe.contentDocument?.readyState?+("complete"===this.iframe.contentDocument.readyState):-1,contentWindow:!!this.iframe.contentWindow,isConnected:this.iframe.isConnected}:null}getNavigatorInfo(){return navigator?{userAgent:navigator.userAgent,language:navigator.language,onLine:navigator.onLine,screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio}:null}gatherDebugInfo(e){let n=this.getIframeStatus(),r=this.getNavigatorInfo(),a="Unknown";return e instanceof t?a=`FrakRpcError: ${e.code} '${e.message}'`:e instanceof Error?a=e.message:"string"==typeof e&&(a=e),{timestamp:new Date().toISOString(),encodedUrl:btoa(window.location.href),encodedConfig:this.config?this.base64Encode(this.config):"no-config",navigatorInfo:r?this.base64Encode(r):"no-navigator",iframeStatus:n?this.base64Encode(n):"not-iframe",lastRequest:this.lastRequest?this.base64Encode(this.lastRequest):"No Frak request logged",lastResponse:this.lastResponse?this.base64Encode(this.lastResponse):"No Frak response logged",clientStatus:this.isSetupDone?"setup":"not-setup",error:a}}static empty(){return new u}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
2
2
  Debug Information:
3
3
  -----------------
4
4
  Timestamp: ${t.timestamp}
@@ -10,4 +10,4 @@ import{OpenPanel as e}from"@openpanel/web";import{CborDecoder as t,CborEncoder a
10
10
  Last Response: ${t.lastResponse}
11
11
  Client Status: ${t.clientStatus}
12
12
  Error: ${t.error}
13
- `.trim()}}let S={id:"frak-wallet",name:"frak-wallet",title:"Frak Wallet",allow:"publickey-credentials-get *; clipboard-write; web-share *",style:{width:"0",height:"0",border:"0",position:"absolute",zIndex:2000001,top:"-1000px",left:"-1000px",colorScheme:"auto"}};function E({walletBaseUrl:e,config:t}){let n=document.querySelector("#frak-wallet");n&&n.remove();let r=document.createElement("iframe");return r.id=S.id,r.name=S.name,r.allow=S.allow,r.style.zIndex=S.style.zIndex.toString(),I({iframe:r,isVisible:!1}),document.body.appendChild(r),new Promise(n=>{r?.addEventListener("load",()=>n(r)),r.src=`${t?.walletUrl??e??"https://wallet.frak.id"}/listener`})}function I({iframe:e,isVisible:t}){if(!t){e.style.width="0",e.style.height="0",e.style.border="0",e.style.position="fixed",e.style.top="-1000px",e.style.left="-1000px";return}e.style.position="fixed",e.style.top="0",e.style.left="0",e.style.width="100%",e.style.height="100%",e.style.pointerEvents="auto"}function x({config:t,iframe:n}){let r,o=function(){let e=new Map;return{createChannel:t=>{let n=Math.random().toString(36).substring(7);return e.set(n,t),n},getRpcResolver:t=>e.get(t),removeChannel:t=>e.delete(t),destroy:()=>e.clear()}}(),a=function({iframe:e}){let t=new d;return{handleEvent:async n=>{switch(n.iframeLifecycle){case"connected":t.resolve(!0);break;case"do-backup":n.data.backup?localStorage.setItem(C,n.data.backup):localStorage.removeItem(C);break;case"remove-backup":localStorage.removeItem(C);break;case"show":case"hide":I({iframe:e,isVisible:"show"===n.iframeLifecycle});break;case"handshake":e.contentWindow?.postMessage({clientLifecycle:"handshake-response",data:{token:n.data.token,currentUrl:window.location.href}},"*");break;case"redirect":window.location.href=`${n.data.baseRedirectUrl}${encodeURIComponent(window.location.href)}`}},isConnected:t.promise}}({iframe:n}),c=new R(t,n),u=function({frakWalletUrl:e,iframe:t,channelManager:n,iframeLifecycleManager:r,debugInfo:o}){if("undefined"==typeof window)throw new i(l.configError,"iframe client should be used in the browser");if(!t.contentWindow)throw new i(l.configError,"The iframe does not have a product window");let a=t.contentWindow;async function s(t){if(!t.origin)return;try{if(new URL(t.origin).origin.toLowerCase()!==new URL(e).origin.toLowerCase())return}catch(e){console.log("Unable to check frak msg origin",e);return}if("object"!=typeof t.data)return;if(o.setLastResponse(t),"iframeLifecycle"in t.data)return void await r.handleEvent(t.data);if("clientLifecycle"in t.data)return void console.error("Client lifecycle event received on the client side, dismissing it");let a=t.data.id,i=n.getRpcResolver(a);i&&i(t.data)}return window.addEventListener("message",s),{sendEvent:function(t){a.postMessage(t,{targetOrigin:e}),o.setLastRequest(t,e)},cleanup:function(){window.removeEventListener("message",s)}}}({frakWalletUrl:t?.walletUrl??"https://wallet.frak.id",iframe:n,channelManager:o,iframeLifecycleManager:a,debugInfo:c}),f=async e=>{if(!await a.isConnected)throw new i(l.clientNotConnected,"The iframe provider isn't connected yet");let t=new d,n=o.createChannel(e=>{let r=y(e.data);r.error?t.reject(new i(r.error.code,r.error.message,r.error?.data)):t.resolve(r.result),o.removeChannel(n)}),r=h(e);return u.sendEvent({id:n,topic:e.method,data:r}),t.promise},p=async(e,t)=>{if(!await a.isConnected)throw new i(l.clientNotConnected,"The iframe provider isn't connected yet");let n=o.createChannel(e=>{let n=y(e.data);if(n.result)t(n.result);else throw new s("No valid result in the response")}),r=h(e);u.sendEvent({id:n,topic:e.method,data:r})},m=function(e,t){let n,r,o=()=>e.sendEvent({clientLifecycle:"heartbeat"});function a(){n&&clearInterval(n),r&&clearTimeout(r)}return async function(){o(),n=setInterval(o,100),r=setTimeout(()=>{a(),console.log("Heartbeat timeout: connection failed")},3e4),await t.isConnected,a()}(),a}(u,a),g=async()=>{m(),o.destroy(),u.cleanup(),n.remove()};console.log("[Frak SDK] Initializing OpenPanel"),(r=new e({apiUrl:"https://op-api.gcp.frak.id",clientId:"f305d11d-b93b-487c-80d4-92deb7903e98",trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:e,payload:t})=>!("track"===e&&t?.properties)||("sdkVersion"in t.properties||(t.properties={...t.properties,sdkVersion:"0.0.19"}),!0)})).setGlobalProperties({sdkVersion:"0.0.19"}),r.init();let w=L({config:t,messageHandler:u,lifecycleManager:a}).then(()=>c.updateSetupStatus(!0));return{config:t,debugInfo:c,waitForConnection:a.isConnected,waitForSetup:w,request:f,listenerRequest:p,destroy:g,openPanel:r}}async function L({config:e,messageHandler:t,lifecycleManager:n}){async function r(){let n=e.customizations?.css;n&&t.sendEvent({clientLifecycle:"modal-css",data:{cssLink:n}})}async function o(){let n=e.customizations?.i18n;n&&t.sendEvent({clientLifecycle:"modal-i18n",data:{i18n:n}})}async function a(){if("undefined"==typeof window)return;let e=window.localStorage.getItem(C);e&&t.sendEvent({clientLifecycle:"restore-backup",data:{backup:e}})}await n.isConnected,await Promise.allSettled([r(),o(),a()])}let F={eur:"fr-FR",usd:"en-US",gbp:"en-GB"};function U(e){return e&&e in F?e:"eur"}async function D({config:e}){let t=function(e){let t=U(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}(e),n=await E({config:t});if(!n)return void console.error("Failed to create iframe");let r=x({config:t,iframe:n});return(await r.waitForSetup,await r.waitForConnection)?r:void console.error("Failed to connect to client")}let A="fCtx";function N(e){if(e?.r)try{let t=o(e.r);return u(t)}catch(t){console.error("Error compressing Frak context",{e:t,context:e})}}function $(e){if(e&&0!==e.length)try{let t=f(e);return{r:r(t,{size:20})}}catch(t){console.error("Error decompressing Frak context",{e:t,context:e})}}function q({url:e}){if(!e)return null;let t=new URL(e).searchParams.get(A);return t?$(t):null}function j({url:e,context:t}){if(!e)return null;let n=q({url:e}),r=n?{...n,...t}:t;if(!r.r)return null;let o=N(r);if(!o)return null;let a=new URL(e);return a.searchParams.set(A,o),a.toString()}function P(e){let t=new URL(e);return t.searchParams.delete(A),t.toString()}let _={compress:N,decompress:$,parse:q,update:j,remove:P,replaceUrl:function({url:e,context:t}){let n;if(!window.location?.href||"undefined"==typeof window)return void console.error("No window found, can't update context");let r=e??window.location.href;(n=null!==t?j({url:r,context:t}):P(r))&&window.history.replaceState(null,"",n.toString())}};function M(e){return e?F[e]??F.eur:F.eur}function T(e){return e?`${e}Amount`:"eurAmount"}function V(e,t){let n=M(t),r=U(t);return e.toLocaleString(n,{style:"currency",currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function W(e,t,n={}){if(!e)return void console.debug("[Frak] No client provided, skipping event tracking");try{e.openPanel?.track(t,n)}catch(e){console.debug("[Frak] Failed to track event:",t,e)}}let z={dapp:1,press:2,webshop:3,retail:4,referral:30,purchase:31},H=Object.entries(z).reduce((e,[t,n])=>(e[t]=BigInt(1)<<BigInt(n),e),{}),O={press:{openArticle:"0xc0a24ffb",readArticle:"0xd5bd0fbe"},dapp:{proofVerifiableStorageUpdate:"0x2ab2aeef",callableVerifiableStorageUpdate:"0xa07da986"},webshop:{open:"0xb311798f"},referral:{referred:"0x010cc3b9",createLink:"0xb2c0f17c"},purchase:{started:"0xd87e90c3",completed:"0x8403aeb4",unsafeCompleted:"0x4d5b14e0"},retail:{customerMeeting:"0x74489004"}};export{c as ClientNotFound,R as DebugInfoGatherer,d as Deferred,_ as FrakContextManager,i as FrakRpcError,l as RpcErrorCodes,f as base64urlDecode,u as base64urlEncode,S as baseIframeProps,m as compressJson,g as compressJsonToB64,x as createIFrameFrakClient,E as createIframe,y as decompressDataAndCheckHash,v as decompressJson,k as decompressJsonFromB64,V as formatAmount,T as getCurrencyAmountKey,U as getSupportedCurrency,M as getSupportedLocale,h as hashAndCompressData,O as interactionTypes,F as locales,z as productTypes,H as productTypesMask,D as setupClient,W as trackEvent};
13
+ `.trim()}}let f={id:"frak-wallet",name:"frak-wallet",title:"Frak Wallet",allow:"publickey-credentials-get *; clipboard-write; web-share *",style:{width:"0",height:"0",border:"0",position:"absolute",zIndex:2000001,top:"-1000px",left:"-1000px",colorScheme:"auto"}};function p({walletBaseUrl:e,config:t}){let n=document.querySelector("#frak-wallet");n&&n.remove();let r=document.createElement("iframe");return r.id=f.id,r.name=f.name,r.allow=f.allow,r.style.zIndex=f.style.zIndex.toString(),m({iframe:r,isVisible:!1}),document.body.appendChild(r),new Promise(n=>{r?.addEventListener("load",()=>n(r)),r.src=`${t?.walletUrl??e??"https://wallet.frak.id"}/listener`})}function m({iframe:e,isVisible:t}){if(!t){e.style.width="0",e.style.height="0",e.style.border="0",e.style.position="fixed",e.style.top="-1000px",e.style.left="-1000px";return}e.style.position="fixed",e.style.top="0",e.style.left="0",e.style.width="100%",e.style.height="100%",e.style.pointerEvents="auto"}function g(e="/listener"){if(!window.opener)return null;let t=t=>{try{return t.location.origin===window.location.origin&&t.location.pathname===e}catch{return!1}};if(t(window.opener))return window.opener;try{let e=window.opener.frames;for(let n=0;n<e.length;n++)if(t(e[n]))return e[n];return null}catch(t){return console.error(`[findIframeInOpener] Error finding iframe with pathname ${e}:`,t),null}}function h({config:r,iframe:o}){let c,l=r?.walletUrl??"https://wallet.frak.id",f=function({iframe:t}){let n=new e;return{handleEvent:async e=>{if(!("iframeLifecycle"in e))return;let{iframeLifecycle:r,data:a}=e;switch(r){case"connected":n.resolve(!0);break;case"do-backup":a.backup?localStorage.setItem(d,a.backup):localStorage.removeItem(d);break;case"remove-backup":localStorage.removeItem(d);break;case"show":case"hide":m({iframe:t,isVisible:"show"===r});break;case"handshake":t.contentWindow?.postMessage({clientLifecycle:"handshake-response",data:{token:a.token,currentUrl:window.location.href}},"*");break;case"redirect":{let e=new URL(a.baseRedirectUrl);e.searchParams.has("u")?(e.searchParams.delete("u"),e.searchParams.append("u",window.location.href),window.location.href=e.toString()):window.location.href=a.baseRedirectUrl}}},isConnected:n.promise}}({iframe:o}),p=new u(r,o);if(!o.contentWindow)throw new t(n.configError,"The iframe does not have a content window");let g=a({emittingTransport:o.contentWindow,listeningTransport:window,targetOrigin:l,middleware:[{async onRequest(e,r){if(!await f.isConnected)throw new t(n.clientNotConnected,"The iframe provider isn't connected yet");return r}},i(),{onRequest:(e,t)=>(p.setLastRequest(e),t),onResponse:(e,t)=>(p.setLastResponse(e,t),t)}],lifecycleHandlers:{iframeLifecycle:async(e,t)=>{await f.handleEvent(e)}}}),h=function(e,t){let n,r,a=()=>e.sendLifecycle({clientLifecycle:"heartbeat"});function o(){n&&clearInterval(n),r&&clearTimeout(r)}return async function(){a(),n=setInterval(a,1e3),r=setTimeout(()=>{o(),console.log("Heartbeat timeout: connection failed")},3e4),await t.isConnected,o()}(),o}(g,f),y=async()=>{h(),g.cleanup(),o.remove()};console.log("[Frak SDK] Initializing OpenPanel"),(c=new s({apiUrl:"https://op-api.gcp.frak.id",clientId:"6eacc8d7-49ac-4936-95e9-81ef29449570",trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:e,payload:t})=>!("track"===e&&t?.properties)||("sdkVersion"in t.properties||(t.properties={...t.properties,sdkVersion:"0.1.0"}),!0)})).setGlobalProperties({sdkVersion:"0.1.0"}),c.init();let b=w({config:r,rpcClient:g,lifecycleManager:f}).then(()=>p.updateSetupStatus(!0));return{config:r,debugInfo:p,waitForConnection:f.isConnected,waitForSetup:b,request:g.request,listenerRequest:g.listen,destroy:y,openPanel:c}}async function w({config:e,rpcClient:t,lifecycleManager:n}){async function r(){let n=e.customizations?.css;n&&t.sendLifecycle({clientLifecycle:"modal-css",data:{cssLink:n}})}async function a(){let n=e.customizations?.i18n;n&&t.sendLifecycle({clientLifecycle:"modal-i18n",data:{i18n:n}})}async function o(){if("undefined"==typeof window)return;let e=window.localStorage.getItem(d);e&&t.sendLifecycle({clientLifecycle:"restore-backup",data:{backup:e}})}await n.isConnected,function(e,t){if("undefined"==typeof window)return;let n=new URL(window.location.href),r=n.searchParams.get("sso");r&&(t.then(()=>{e.sendLifecycle({clientLifecycle:"sso-redirect-complete",data:{compressed:r}}),console.log("[SSO URL Listener] Forwarded compressed SSO data to iframe")}).catch(e=>{console.error("[SSO URL Listener] Failed to forward SSO data:",e)}),n.searchParams.delete("sso"),window.history.replaceState({},"",n.toString()),console.log("[SSO URL Listener] SSO parameter detected and URL cleaned"))}(t,n.isConnected),await Promise.allSettled([r(),a(),o()])}let y={eur:"fr-FR",usd:"en-US",gbp:"en-GB"};function b(e){return e&&e in y?e:"eur"}async function S({config:e}){let t=function(e){let t=b(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}(e),n=await p({config:t});if(!n)return void console.error("Failed to create iframe");let r=h({config:t,iframe:n});return(await r.waitForSetup,await r.waitForConnection)?r:void console.error("Failed to connect to client")}function k(e){return btoa(Array.from(e,e=>String.fromCharCode(e)).join("")).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function L(e){let t=e.length%4;return Uint8Array.from(atob(e.replace(/-/g,"+").replace(/_/g,"/").padEnd(e.length+(0===t?0:4-t),"=")),e=>e.charCodeAt(0))}function R(e){return k(r(e))}function v(e){return o(L(e))}let I="fCtx";function x(e){if(e?.r)try{let t=l(e.r);return k(t)}catch(t){console.error("Error compressing Frak context",{e:t,context:e})}}function U(e){if(e&&0!==e.length)try{let t=L(e);return{r:c(t,{size:20})}}catch(t){console.error("Error decompressing Frak context",{e:t,context:e})}}function F({url:e}){if(!e)return null;let t=new URL(e).searchParams.get(I);return t?U(t):null}function C({url:e,context:t}){if(!e)return null;let n=F({url:e}),r=n?{...n,...t}:t;if(!r.r)return null;let a=x(r);if(!a)return null;let o=new URL(e);return o.searchParams.set(I,a),o.toString()}function E(e){let t=new URL(e);return t.searchParams.delete(I),t.toString()}let P={compress:x,decompress:U,parse:F,update:C,remove:E,replaceUrl:function({url:e,context:t}){let n;if(!window.location?.href||"undefined"==typeof window)return void console.error("No window found, can't update context");let r=e??window.location.href;(n=null!==t?C({url:r,context:t}):E(r))&&window.history.replaceState(null,"",n.toString())}};function q(e){return e?y[e]??y.eur:y.eur}function D(e){return e?`${e}Amount`:"eurAmount"}function $(e,t){let n=q(t),r=b(t);return e.toLocaleString(n,{style:"currency",currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function O(e,t,n={}){if(!e)return void console.debug("[Frak] No client provided, skipping event tracking");try{e.openPanel?.track(t,n)}catch(e){console.debug("[Frak] Failed to track event:",t,e)}}function A(e,t,n,r,a){var o;let i=R({r:(o={redirectUrl:t.redirectUrl,directExit:t.directExit,lang:t.lang,productId:n,metadata:{name:r,css:a,logoUrl:t.metadata?.logoUrl,homepageLink:t.metadata?.homepageLink}}).redirectUrl,d:o.directExit,l:o.lang,p:o.productId,m:{n:o.metadata?.name,css:o.metadata?.css,l:o.metadata?.logoUrl,h:o.metadata?.homepageLink}}),s=new URL(e);return s.pathname="/sso",s.searchParams.set("p",i),s.toString()}let T={dapp:1,press:2,webshop:3,retail:4,referral:30,purchase:31},N=Object.entries(T).reduce((e,[t,n])=>(e[t]=BigInt(1)<<BigInt(n),e),{}),V={press:{openArticle:"0xc0a24ffb",readArticle:"0xd5bd0fbe"},dapp:{proofVerifiableStorageUpdate:"0x2ab2aeef",callableVerifiableStorageUpdate:"0xa07da986"},webshop:{open:"0xb311798f"},referral:{referred:"0x010cc3b9",createLink:"0xb2c0f17c"},purchase:{started:"0xd87e90c3",completed:"0x8403aeb4",unsafeCompleted:"0x4d5b14e0"},retail:{customerMeeting:"0x74489004"}},z="menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800",W="frak-sso";export{u as DebugInfoGatherer,P as FrakContextManager,L as base64urlDecode,k as base64urlEncode,f as baseIframeProps,R as compressJsonToB64,h as createIFrameFrakClient,p as createIframe,v as decompressJsonFromB64,g as findIframeInOpener,$ as formatAmount,A as generateSsoUrl,D as getCurrencyAmountKey,b as getSupportedCurrency,q as getSupportedLocale,V as interactionTypes,y as locales,T as productTypes,N as productTypesMask,S as setupClient,z as ssoPopupFeatures,W as ssoPopupName,O as trackEvent};
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "0.0.19",
14
+ "version": "0.1.0-beta.8d103039",
15
15
  "description": "Core SDK of the Frak wallet, low level library to interact directly with the frak ecosystem.",
16
16
  "repository": {
17
17
  "url": "https://github.com/frak-id/wallet",
@@ -34,11 +34,13 @@
34
34
  "type": "module",
35
35
  "files": [
36
36
  "/dist",
37
- "/cdn"
37
+ "/cdn",
38
+ "/src"
38
39
  ],
39
40
  "browser": "./cdn/bundle.js",
40
41
  "exports": {
41
42
  ".": {
43
+ "development": "./src/index.ts",
42
44
  "import": {
43
45
  "types": "./dist/index.d.ts",
44
46
  "default": "./dist/index.js"
@@ -49,6 +51,7 @@
49
51
  }
50
52
  },
51
53
  "./actions": {
54
+ "development": "./src/actions/index.ts",
52
55
  "import": {
53
56
  "types": "./dist/actions.d.ts",
54
57
  "default": "./dist/actions.js"
@@ -59,6 +62,7 @@
59
62
  }
60
63
  },
61
64
  "./interactions": {
65
+ "development": "./src/interactions/index.ts",
62
66
  "import": {
63
67
  "types": "./dist/interactions.d.ts",
64
68
  "default": "./dist/interactions.js"
@@ -95,6 +99,7 @@
95
99
  "viem": "^2.23.14"
96
100
  },
97
101
  "dependencies": {
102
+ "@frak-labs/frame-connector": "0.1.0",
98
103
  "@jsonjoy.com/json-pack": "^1.2.0",
99
104
  "@openpanel/web": "^1.0.1"
100
105
  },
@@ -0,0 +1,20 @@
1
+ import type {
2
+ DisplayEmbeddedWalletParamsType,
3
+ DisplayEmbeddedWalletResultType,
4
+ FrakClient,
5
+ } from "../types";
6
+
7
+ /**
8
+ * Function used to display the Frak embedded wallet popup
9
+ * @param client - The current Frak Client
10
+ * @param params - The parameter used to customise the embedded wallet
11
+ */
12
+ export async function displayEmbeddedWallet(
13
+ client: FrakClient,
14
+ params: DisplayEmbeddedWalletParamsType
15
+ ): Promise<DisplayEmbeddedWalletResultType> {
16
+ return await client.request({
17
+ method: "frak_displayEmbeddedWallet",
18
+ params: [params, client.config.metadata],
19
+ });
20
+ }
@@ -0,0 +1,131 @@
1
+ import type {
2
+ DisplayModalParamsType,
3
+ FrakClient,
4
+ ModalRpcStepsResultType,
5
+ ModalStepTypes,
6
+ } from "../types";
7
+
8
+ /**
9
+ * Function used to display a modal
10
+ * @param client - The current Frak Client
11
+ * @param args
12
+ * @param args.steps - The different steps of the modal
13
+ * @param args.metadata - The metadata for the modal (customization, etc)
14
+ * @returns The result of each modal steps
15
+ *
16
+ * @description This function will display a modal to the user with the provided steps and metadata.
17
+ *
18
+ * @remarks
19
+ * - The UI of the displayed modal can be configured with the `customCss` property in the `customizations.css` field of the top-level config.
20
+ * - The `login` and `openSession` steps will be automatically skipped if the user is already logged in or has an active session. It's safe to include these steps in all cases to ensure proper user state.
21
+ * - Steps are automatically reordered in the following sequence:
22
+ * 1. `login` (if needed)
23
+ * 2. `openSession` (if needed)
24
+ * 3. All other steps in the order specified
25
+ * 4. `success` (if included, always last)
26
+ *
27
+ * @example
28
+ * Simple sharing modal with steps:
29
+ * 1. Login (Skipped if already logged in)
30
+ * 2. Open a session (Skipped if already opened)
31
+ * 3. Display a success message with sharing link option
32
+ *
33
+ * ```ts
34
+ * const results = await displayModal(frakConfig, {
35
+ * steps: {
36
+ * // Simple login with no SSO, nor customization
37
+ * login: { allowSso: false },
38
+ * // Simple session opening, with no customization
39
+ * openSession: {},
40
+ * // Success message
41
+ * final: {
42
+ * action: { key: "reward" },
43
+ * // Skip this step, it will be only displayed in the stepper within the modal
44
+ * autoSkip: true,
45
+ * },
46
+ * },
47
+ * });
48
+ *
49
+ * console.log("Login step - wallet", results.login.wallet);
50
+ * console.log("Open session step - start + end", {
51
+ * start: results.openSession.startTimestamp,
52
+ * end: results.openSession.endTimestamp,
53
+ * });
54
+ * ```
55
+ *
56
+ * @example
57
+ * A full modal example, with a few customization options, with the steps:
58
+ * 1. Login (Skipped if already logged in)
59
+ * 2. Open a session (Skipped if already opened)
60
+ * 3. Authenticate via SIWE
61
+ * 4. Send a transaction
62
+ * 5. Display a success message with sharing link options
63
+ *
64
+ * ```ts
65
+ * const results = await displayModal(frakConfig, {
66
+ * steps: {
67
+ * // Login step
68
+ * login: {
69
+ * allowSso: true,
70
+ * ssoMetadata: {
71
+ * logoUrl: "https://my-app.com/logo.png",
72
+ * homepageLink: "https://my-app.com",
73
+ * },
74
+ * },
75
+ * // Simple session opening, with no customisation
76
+ * openSession: {},
77
+ * // Siwe authentication
78
+ * siweAuthenticate: {
79
+ * siwe: {
80
+ * domain: "my-app.com",
81
+ * uri: "https://my-app.com/",
82
+ * nonce: generateSiweNonce(),
83
+ * version: "1",
84
+ * },
85
+ * },
86
+ * // Send batched transaction
87
+ * sendTransaction: {
88
+ * tx: [
89
+ * { to: "0xdeadbeef", data: "0xdeadbeef" },
90
+ * { to: "0xdeadbeef", data: "0xdeadbeef" },
91
+ * ],
92
+ * },
93
+ * // Success message with sharing options
94
+ * final: {
95
+ * action: {
96
+ * key: "sharing",
97
+ * options: {
98
+ * popupTitle: "Share the app",
99
+ * text: "Discover my super app website",
100
+ * link: "https://my-app.com",
101
+ * },
102
+ * },
103
+ * dismissedMetadata: {
104
+ * title: "Dismiss",
105
+ * description: "You won't be rewarded for this sharing action",
106
+ * },
107
+ * },
108
+ * },
109
+ * metadata: {
110
+ * // Header of desktop modals
111
+ * header: {
112
+ * title: "My-App",
113
+ * icon: "https://my-app.com/logo.png",
114
+ * },
115
+ * // Context that will be present in every modal steps
116
+ * context: "My-app overkill flow",
117
+ * },
118
+ * });
119
+ * ```
120
+ */
121
+ export async function displayModal<
122
+ T extends ModalStepTypes[] = ModalStepTypes[],
123
+ >(
124
+ client: FrakClient,
125
+ { steps, metadata }: DisplayModalParamsType<T>
126
+ ): Promise<ModalRpcStepsResultType<T>> {
127
+ return (await client.request({
128
+ method: "frak_displayModal",
129
+ params: [steps, metadata, client.config.metadata],
130
+ })) as ModalRpcStepsResultType<T>;
131
+ }
@@ -0,0 +1,14 @@
1
+ import type { FrakClient, GetProductInformationReturnType } from "../types";
2
+
3
+ /**
4
+ * Function used to get the current product information
5
+ * @param client - The current Frak Client
6
+ * @returns The product information in a promise
7
+ */
8
+ export async function getProductInformation(
9
+ client: FrakClient
10
+ ): Promise<GetProductInformationReturnType> {
11
+ return await client.request({
12
+ method: "frak_getProductInformation",
13
+ });
14
+ }
@@ -0,0 +1,29 @@
1
+ export { watchWalletStatus } from "./watchWalletStatus";
2
+ export { sendInteraction } from "./sendInteraction";
3
+ export { displayModal } from "./displayModal";
4
+ export { displayEmbeddedWallet } from "./displayEmbeddedWallet";
5
+ export { openSso } from "./openSso";
6
+ export { prepareSso } from "./prepareSso";
7
+ export { getProductInformation } from "./getProductInformation";
8
+ // Helper to track the purchase status
9
+ export { trackPurchaseStatus } from "./trackPurchaseStatus";
10
+ // Modal wrappers
11
+ export {
12
+ siweAuthenticate,
13
+ type SiweAuthenticateModalParams,
14
+ } from "./wrapper/siweAuthenticate";
15
+ export {
16
+ sendTransaction,
17
+ type SendTransactionParams,
18
+ } from "./wrapper/sendTransaction";
19
+ export {
20
+ modalBuilder,
21
+ type ModalStepBuilder,
22
+ type ModalBuilder,
23
+ } from "./wrapper/modalBuilder";
24
+ // Referral interaction
25
+ export { referralInteraction } from "./referral/referralInteraction";
26
+ export {
27
+ processReferral,
28
+ type ProcessReferralOptions,
29
+ } from "./referral/processReferral";
@@ -0,0 +1,116 @@
1
+ import type {
2
+ FrakClient,
3
+ OpenSsoParamsType,
4
+ OpenSsoReturnType,
5
+ } from "../types";
6
+ import { computeProductId } from "../utils/computeProductId";
7
+ import { generateSsoUrl } from "../utils/sso";
8
+
9
+ // SSO popup configuration
10
+ export const ssoPopupFeatures =
11
+ "menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800";
12
+ export const ssoPopupName = "frak-sso";
13
+
14
+ /**
15
+ * Function used to open the SSO
16
+ * @param client - The current Frak Client
17
+ * @param args - The SSO parameters
18
+ *
19
+ * @description Two SSO flow modes:
20
+ *
21
+ * **Redirect Mode** (openInSameWindow: true):
22
+ * - Wallet generates URL and triggers redirect
23
+ * - Used when redirectUrl is provided
24
+ *
25
+ * **Popup Mode** (openInSameWindow: false/omitted):
26
+ * - SDK generates URL client-side (or uses provided ssoPopupUrl)
27
+ * - Opens popup synchronously (prevents popup blockers)
28
+ * - Waits for SSO completion via postMessage
29
+ *
30
+ * @example
31
+ * First we build the sso metadata
32
+ * ```ts
33
+ * // Build the metadata
34
+ * const metadata: SsoMetadata = {
35
+ * logoUrl: "https://my-app.com/logo.png",
36
+ * homepageLink: "https://my-app.com",
37
+ * };
38
+ * ```
39
+ *
40
+ * Then, either use it with direct exit (and so user is directly redirected to your website), or a custom redirect URL
41
+ * :::code-group
42
+ * ```ts [Popup (default)]
43
+ * // Opens in popup, SDK generates URL automatically
44
+ * await openSso(frakConfig, {
45
+ * directExit: true,
46
+ * metadata,
47
+ * });
48
+ * ```
49
+ * ```ts [Redirect]
50
+ * // Opens in same window with redirect
51
+ * await openSso(frakConfig, {
52
+ * redirectUrl: "https://my-app.com/frak-sso",
53
+ * metadata,
54
+ * openInSameWindow: true,
55
+ * });
56
+ * ```
57
+ * ```ts [Custom popup URL]
58
+ * // Advanced: provide custom SSO URL
59
+ * const { ssoUrl } = await prepareSso(frakConfig, { metadata });
60
+ * await openSso(frakConfig, {
61
+ * metadata,
62
+ * ssoPopupUrl: `${ssoUrl}&custom=param`,
63
+ * });
64
+ * ```
65
+ * :::
66
+ */
67
+ export async function openSso(
68
+ client: FrakClient,
69
+ args: OpenSsoParamsType
70
+ ): Promise<OpenSsoReturnType> {
71
+ const { metadata, customizations, walletUrl } = client.config;
72
+
73
+ // Check if redirect mode (default to true if redirectUrl present)
74
+ const isRedirectMode = args.openInSameWindow ?? !!args.redirectUrl;
75
+
76
+ if (isRedirectMode) {
77
+ // Redirect flow: Wallet generates URL and triggers redirect via lifecycle event
78
+ // This must happen on wallet side because only the iframe can trigger the redirect
79
+ return await client.request({
80
+ method: "frak_openSso",
81
+ params: [args, metadata.name, customizations?.css],
82
+ });
83
+ }
84
+
85
+ // Popup flow: Generate URL on SDK side and open synchronously
86
+ // This ensures window.open() is called in same tick as user gesture (no popup blocker)
87
+
88
+ // Step 1: Generate or use provided SSO URL
89
+ const ssoUrl =
90
+ args.ssoPopupUrl ??
91
+ generateSsoUrl(
92
+ walletUrl ?? "https://wallet.frak.id",
93
+ args,
94
+ computeProductId(),
95
+ metadata.name,
96
+ customizations?.css
97
+ );
98
+
99
+ // Step 2: Open popup synchronously (critical for popup blocker prevention)
100
+ const popup = window.open(ssoUrl, ssoPopupName, ssoPopupFeatures);
101
+ if (!popup) {
102
+ throw new Error(
103
+ "Popup was blocked. Please allow popups for this site."
104
+ );
105
+ }
106
+ popup.focus();
107
+
108
+ // Step 3: Wait for SSO completion via RPC
109
+ // The wallet iframe will resolve this when SSO page sends sso_complete message
110
+ const result = await client.request({
111
+ method: "frak_openSso",
112
+ params: [args, metadata.name, customizations?.css],
113
+ });
114
+
115
+ return result ?? {};
116
+ }
@@ -0,0 +1,48 @@
1
+ import type {
2
+ FrakClient,
3
+ PrepareSsoParamsType,
4
+ PrepareSsoReturnType,
5
+ } from "../types";
6
+
7
+ /**
8
+ * Generate SSO URL without opening popup
9
+ *
10
+ * This is a **synchronous**, client-side function that generates the SSO URL
11
+ * without any RPC calls to the wallet iframe. Use this when you need:
12
+ * - Custom URL modifications before opening popup
13
+ * - Pre-generation for advanced popup strategies
14
+ * - URL inspection/logging before SSO flow
15
+ *
16
+ * @param client - The current Frak Client
17
+ * @param args - The SSO parameters
18
+ * @returns Object containing the generated ssoUrl
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // Generate URL for inspection
23
+ * const { ssoUrl } = prepareSso(client, {
24
+ * metadata: { logoUrl: "..." },
25
+ * directExit: true
26
+ * });
27
+ * console.log("Opening SSO:", ssoUrl);
28
+ *
29
+ * // Add custom params
30
+ * const customUrl = `${ssoUrl}&tracking=abc123`;
31
+ * await openSso(client, { metadata, ssoPopupUrl: customUrl });
32
+ * ```
33
+ *
34
+ * @remarks
35
+ * For most use cases, just use `openSso()` which handles URL generation automatically.
36
+ * Only use `prepareSso()` when you need explicit control over the URL.
37
+ */
38
+ export async function prepareSso(
39
+ client: FrakClient,
40
+ args: PrepareSsoParamsType
41
+ ): Promise<PrepareSsoReturnType> {
42
+ const { metadata, customizations } = client.config;
43
+
44
+ return await client.request({
45
+ method: "frak_prepareSso",
46
+ params: [args, metadata.name, customizations?.css],
47
+ });
48
+ }