@frak-labs/core-sdk 1.0.0-beta.61e6fb99 → 1.0.0-beta.a7645eaa

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 (38) hide show
  1. package/cdn/bundle.js +3 -3
  2. package/dist/actions-3g2MTs6f.cjs +1 -0
  3. package/dist/actions-C-vPLo1H.js +1 -0
  4. package/dist/actions.cjs +1 -1
  5. package/dist/actions.d.cts +2 -2
  6. package/dist/actions.d.ts +2 -2
  7. package/dist/actions.js +1 -1
  8. package/dist/bundle.cjs +1 -1
  9. package/dist/bundle.d.cts +3 -3
  10. package/dist/bundle.d.ts +3 -3
  11. package/dist/bundle.js +1 -1
  12. package/dist/{index-CGyEOo9J.d.cts → index-BWic1g0J.d.cts} +1 -1
  13. package/dist/{index-B_Uj-puh.d.ts → index-CNukNJzV.d.ts} +2 -1
  14. package/dist/{index-Cdf5j2_W.d.ts → index-Du4nB3qO.d.ts} +1 -1
  15. package/dist/{index-ByVpu25D.d.cts → index-tYNUZNRd.d.cts} +2 -1
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.d.cts +2 -2
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +1 -1
  20. package/dist/{openSso-B6pD2oA6.d.ts → openSso-3YqtmSkM.d.ts} +18 -7
  21. package/dist/{openSso-qjaccFd0.d.cts → openSso-SP6T9cHA.d.cts} +18 -7
  22. package/dist/sdkConfigStore-thuozJuB.cjs +1 -0
  23. package/dist/sdkConfigStore-ylgC-Tp0.js +1 -0
  24. package/dist/{src-BxRYON49.js → src-BhrneHv1.js} +1 -1
  25. package/dist/{src-BqpqVHCq.cjs → src-DkkrSfA4.cjs} +1 -1
  26. package/package.json +2 -2
  27. package/src/actions/referral/processReferral.test.ts +125 -0
  28. package/src/actions/referral/processReferral.ts +22 -6
  29. package/src/types/context.ts +16 -4
  30. package/src/types/rpc/interaction.ts +1 -1
  31. package/src/types/tracking.ts +1 -1
  32. package/src/utils/FrakContext.test.ts +84 -1
  33. package/src/utils/FrakContext.ts +23 -10
  34. package/src/utils/analytics/events/referral.ts +1 -0
  35. package/dist/actions-Di4welXI.cjs +0 -1
  36. package/dist/actions-DyMkUe65.js +0 -1
  37. package/dist/sdkConfigStore-DvwFc6Ym.cjs +0 -1
  38. package/dist/sdkConfigStore-M37skmM8.js +0 -1
@@ -46,7 +46,7 @@ type AttributionParams = {
46
46
  */
47
47
  type AttributionDefaults = Omit<AttributionParams, "utmContent">;
48
48
  type TrackArrivalParams = {
49
- /** @deprecated V1 legacy use referrerClientId for v2 contexts */referrerWallet?: Address;
49
+ /** Sharer wallet address. Accepted in both V1 (legacy) and V2 (authenticated sharer) contexts. */referrerWallet?: Address;
50
50
  referrerClientId?: string;
51
51
  referrerMerchantId?: string; /** Epoch seconds timestamp from the referral link creation */
52
52
  referralTimestamp?: number;
@@ -951,7 +951,7 @@ type DisplayEmbeddedWalletResultType = {
951
951
  * @group RPC Schema
952
952
  */
953
953
  type SendInteractionParamsType = {
954
- type: "arrival"; /** @deprecated V1 legacy use referrerClientId for v2 */
954
+ type: "arrival"; /** Sharer wallet address. Accepted in both V1 (legacy, wallet-only) and V2 (authenticated sharer) contexts. */
955
955
  referrerWallet?: Address;
956
956
  referrerClientId?: string;
957
957
  referrerMerchantId?: string; /** Epoch seconds timestamp from the referral link creation */
@@ -1296,15 +1296,26 @@ type FrakContextV1 = {
1296
1296
  /** Referrer wallet address */r: Address;
1297
1297
  };
1298
1298
  /**
1299
- * V2 Frak Context — anonymous-first referral context.
1300
- * Contains the sharer's clientId, merchantId, and link creation timestamp.
1299
+ * V2 Frak Context — anonymous-first referral context with optional wallet.
1300
+ *
1301
+ * Carries merchant context (`m`) and creation timestamp (`t`) unconditionally.
1302
+ * Identifies the sharer via either the anonymous clientId (`c`) or, when the
1303
+ * sharer is authenticated, the stronger wallet identifier (`w`). A valid V2
1304
+ * context MUST contain at least one of `c` or `w`; both may be present when
1305
+ * a logged-in user shares a link (best attribution signal).
1306
+ *
1307
+ * `w` takes precedence as the source of truth because the wallet is bound to
1308
+ * the user's WebAuthn credential, survives localStorage clears, and is global
1309
+ * across merchants — unlike `c`, which is a per-browser UUID.
1310
+ *
1301
1311
  * @ignore
1302
1312
  */
1303
1313
  type FrakContextV2 = {
1304
- /** Version discriminator */v: 2; /** Sharer's anonymous clientId (UUID from localStorage) */
1305
- c: string; /** Merchant ID (UUID) */
1314
+ /** Version discriminator */v: 2; /** Merchant ID (UUID) */
1306
1315
  m: string; /** Link creation timestamp (epoch seconds) */
1307
- t: number;
1316
+ t: number; /** Sharer's anonymous clientId (UUID from localStorage). Optional when `w` is provided. */
1317
+ c?: string; /** Sharer's wallet address. Preferred source of truth when the sharer is authenticated. Optional when `c` is provided. */
1318
+ w?: Address;
1308
1319
  };
1309
1320
  /**
1310
1321
  * The current Frak Context — union of all versions.
@@ -0,0 +1 @@
1
+ let e=require(`viem`),t=require(`@frak-labs/frame-connector`);const n=`frak-client-id`;function r(){return typeof crypto<`u`&&crypto.randomUUID?crypto.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)})}function i(){if(typeof window>`u`||!window.localStorage)return console.warn(`[Frak SDK] No Window / localStorage available to save the clientId`),r();let e=localStorage.getItem(n);return e||(e=r(),localStorage.setItem(n,e)),e}function a({domain:t}={}){return(0,e.keccak256)((0,e.toHex)((t??window.location.host).replace(`www.`,``)))}function o(e){return btoa(Array.from(e,e=>String.fromCharCode(e)).join(``)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function s(e){let t=e.length%4;return Uint8Array.from(atob(e.replace(/-/g,`+`).replace(/_/g,`/`).padEnd(e.length+(t===0?0:4-t),`=`)),e=>e.charCodeAt(0))}function c(e){return o((0,t.jsonEncode)(e))}function l(e,t,n,r,i,a){let o=c(u({redirectUrl:t.redirectUrl,directExit:t.directExit,lang:t.lang,merchantId:n,metadata:{name:r,css:a,logoUrl:t.metadata?.logoUrl,homepageLink:t.metadata?.homepageLink},clientId:i})),s=new URL(e);return s.pathname=`/sso`,s.searchParams.set(`p`,o),s.toString()}function u(e){return{r:e.redirectUrl,cId:e.clientId,d:e.directExit,l:e.lang,m:e.merchantId,md:{n:e.metadata?.name,css:e.metadata?.css,l:e.metadata?.logoUrl,h:e.metadata?.homepageLink}}}const d=`menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800`,f=`frak-sso`;async function p(e,t){let{metadata:n,customizations:r,walletUrl:o}=e.config;if(t.openInSameWindow??!!t.redirectUrl)return await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]});let s=t.ssoPopupUrl??l(o??`https://wallet.frak.id`,t,a(),n.name,i(),r?.css),c=window.open(s,f,d);if(!c)throw Error(`Popup was blocked. Please allow popups for this site.`);return c.focus(),await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]})??{}}function ee(e,t,n){if(!e){console.debug(`[Frak] No client provided, skipping event tracking`);return}try{e.openPanel?.track(t,n)}catch(e){console.debug(`[Frak] Failed to track event:`,t,e)}}const m=`https://backend.frak.id`;function te(e){return e.includes(`localhost:3000`)||e.includes(`localhost:3010`)}function h(e){return te(e)?`https://localhost:3030`:e.includes(`wallet-dev.frak.id`)||e.includes(`wallet.gcp-dev.frak.id`)?`https://backend.gcp-dev.frak.id`:m}function g(e){if(e)return h(e);if(typeof window<`u`){let e=window.FrakSetup?.client?.config?.walletUrl;if(e)return h(e)}return m}var _=class extends Map{maxSize;constructor(e){super(),this.maxSize=e}get(e){let t=super.get(e);return super.has(e)&&(super.delete(e),super.set(e,t)),t}set(e,t){if(super.has(e)&&super.delete(e),super.set(e,t),this.maxSize&&this.size>this.maxSize){let e=super.keys().next().value;e!==void 0&&super.delete(e)}return this}};const v=new _(1024),y=new _(1024),b=3e4,x=new _(1024);async function S(e,{cacheKey:t,cacheTime:n=b}){if(n>0){let e=y.get(t);if(e&&Date.now()-e.created<n)return e.data}let r=x.get(t);if(r&&Date.now()-r<1e3)throw Error(`Cache: ${t} recently failed, backing off`);let i=v.get(t);i||(i=e(),v.set(t,i));try{let e=await i;return y.set(t,{data:e,created:Date.now()}),x.delete(t),e}catch(e){throw x.set(t,Date.now()),e}finally{v.delete(t)}}function C(e){return{clear:()=>{v.delete(e),y.delete(e)},has:(t=b)=>{let n=y.get(e);return n?Date.now()-n.created<t:!1}}}function w(){v.clear(),y.clear(),x.clear()}function T(e){return(0,t.jsonDecode)(s(e))}function E(e){return`r`in e&&!(`v`in e)}function D(e){return`v`in e&&e.v===2}const O=`fCtx`;function k(t){if(t)try{if(D(t)){if(!t.m||!t.t)return;let n=t.w&&(0,e.isAddress)(t.w);return!t.c&&!n?void 0:c({v:2,m:t.m,t:t.t,...t.c?{c:t.c}:{},...n?{w:t.w}:{}})}return o((0,e.hexToBytes)(t.r))}catch(e){console.error(`Error compressing Frak context`,{e,context:t})}}function A(t){if(!(!t||t.length===0))try{let n=T(t);if(n&&typeof n==`object`&&n.v===2){if(!n.m||!n.t)return;let t=n.w&&(0,e.isAddress)(n.w);return!n.c&&!t?void 0:{v:2,m:n.m,t:n.t,...n.c?{c:n.c}:{},...t?{w:n.w}:{}}}let r=(0,e.bytesToHex)(s(t),{size:20});if((0,e.isAddress)(r))return{r}}catch(e){console.error(`Error decompressing Frak context`,{e,context:t})}}function ne({url:e}){if(!e)return null;let t=new URL(e).searchParams.get(O);return t?A(t):null}const j=`frak`;function M(e,t){let n=D(e);return{utm_source:t.utmSource??j,utm_medium:t.utmMedium??`referral`,utm_campaign:t.utmCampaign??(n?e.m:void 0),utm_content:t.utmContent,utm_term:t.utmTerm,via:t.via??j,ref:t.ref??(n?e.c:void 0)}}function N(e,t,n){let r=M(t,n??{});for(let[t,n]of Object.entries(r))n===void 0||n===``||e.searchParams.has(t)||e.searchParams.set(t,n)}function P({url:e,context:t,attribution:n}){if(!e)return null;let r=k(t);if(!r)return null;let i=new URL(e);return i.searchParams.set(O,r),N(i,t,n),i.toString()}function F(e){let t=new URL(e);return t.searchParams.delete(O),t.toString()}function I({url:e,context:t}){if(!window.location?.href||typeof window>`u`){console.error(`No window found, can't update context`);return}let n=e??window.location.href,r;r=t===null?F(n):P({url:n,context:t}),r&&window.history.replaceState(null,``,r.toString())}const L={compress:k,decompress:A,parse:ne,update:P,remove:F,replaceUrl:I},R=`__frakSdkConfig`,z=`frak-config-cache`,B=`frak-merchant-id`,V={key:z},H=typeof window<`u`;function U(){return{isResolved:!1,merchantId:``}}let W=null;function G(){if(!H)return null;try{let e=localStorage.getItem(V.key);if(!e)return null;let t=JSON.parse(e);return t.config?.isResolved?(W=t,t):null}catch{return null}}function K(){return(W??G())?.config}function q(){let e=W??G();return e?Date.now()-e.timestamp<3e4:!1}function J(e){if(!(!H||!e.isResolved))try{let t={config:e,timestamp:Date.now()};localStorage.setItem(V.key,JSON.stringify(t)),W=t}catch{}}function Y(){if(H){W=null;try{localStorage.removeItem(V.key)}catch{}}}function X(){H&&(window[R]||(window[R]=K()??U()))}X();function Z(){return H?window[R]??U():U()}function Q(e){H&&window.dispatchEvent(new CustomEvent(`frak:config`,{detail:e}))}function re(e){return e??(H?window.location.hostname:``)}async function ie(e,t,n){try{let r=g(t),i=n?`&lang=${encodeURIComponent(n)}`:``,a=await fetch(`${r}/user/merchant/resolve?domain=${encodeURIComponent(e)}${i}`);if(!a.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${e}: ${a.status}`);return}let o=await a.json();if(H)try{sessionStorage.setItem(B,o.merchantId)}catch{}return o}catch(e){console.warn(`[Frak SDK] Failed to fetch merchant config:`,e);return}}const $={getConfig:Z,get isResolved(){return Z().isResolved},get isCacheFresh(){return q()},setCacheScope(e,t){V.key=`${z}:${`${e}:${t??``}`}`,W=null},setConfig(e){if(H&&(window[R]=e),J(e),Q(e),H&&e.merchantId)try{sessionStorage.setItem(B,e.merchantId)}catch{}},reset(){let e=K()??U();H&&(window[R]=e),Q(e)},clearCache(){if(Y(),w(),H)try{sessionStorage.removeItem(B)}catch{}},resolve(e,t,n){let r=re(e);return r?S(async()=>{let e=await ie(r,t,n);if(!e)throw Error(`Config resolution returned empty`);return e},{cacheKey:`sdkConfig:${r}:${n??``}`,cacheTime:1/0}).catch(()=>void 0):Promise.resolve(void 0)},getMerchantId(){let e=Z();if(e.isResolved&&e.merchantId)return e.merchantId;if(H)try{return sessionStorage.getItem(B)??void 0}catch{}},async resolveMerchantId(e,t){return $.getMerchantId()||(await $.resolve(e,t))?.merchantId}};Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return L}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return $}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return ee}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return i}});
@@ -0,0 +1 @@
1
+ import{bytesToHex as e,hexToBytes as t,isAddress as n,keccak256 as r,toHex as i}from"viem";import{jsonDecode as a,jsonEncode as o}from"@frak-labs/frame-connector";const s=`frak-client-id`;function c(){return typeof crypto<`u`&&crypto.randomUUID?crypto.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)})}function l(){if(typeof window>`u`||!window.localStorage)return console.warn(`[Frak SDK] No Window / localStorage available to save the clientId`),c();let e=localStorage.getItem(s);return e||(e=c(),localStorage.setItem(s,e)),e}function u({domain:e}={}){return r(i((e??window.location.host).replace(`www.`,``)))}function d(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+(t===0?0:4-t),`=`)),e=>e.charCodeAt(0))}function p(e){return d(o(e))}function m(e,t,n,r,i,a){let o=p(ee({redirectUrl:t.redirectUrl,directExit:t.directExit,lang:t.lang,merchantId:n,metadata:{name:r,css:a,logoUrl:t.metadata?.logoUrl,homepageLink:t.metadata?.homepageLink},clientId:i})),s=new URL(e);return s.pathname=`/sso`,s.searchParams.set(`p`,o),s.toString()}function ee(e){return{r:e.redirectUrl,cId:e.clientId,d:e.directExit,l:e.lang,m:e.merchantId,md:{n:e.metadata?.name,css:e.metadata?.css,l:e.metadata?.logoUrl,h:e.metadata?.homepageLink}}}const h=`menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800`,g=`frak-sso`;async function _(e,t){let{metadata:n,customizations:r,walletUrl:i}=e.config;if(t.openInSameWindow??!!t.redirectUrl)return await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]});let a=t.ssoPopupUrl??m(i??`https://wallet.frak.id`,t,u(),n.name,l(),r?.css),o=window.open(a,g,h);if(!o)throw Error(`Popup was blocked. Please allow popups for this site.`);return o.focus(),await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]})??{}}function v(e,t,n){if(!e){console.debug(`[Frak] No client provided, skipping event tracking`);return}try{e.openPanel?.track(t,n)}catch(e){console.debug(`[Frak] Failed to track event:`,t,e)}}const y=`https://backend.frak.id`;function b(e){return e.includes(`localhost:3000`)||e.includes(`localhost:3010`)}function x(e){return b(e)?`https://localhost:3030`:e.includes(`wallet-dev.frak.id`)||e.includes(`wallet.gcp-dev.frak.id`)?`https://backend.gcp-dev.frak.id`:y}function S(e){if(e)return x(e);if(typeof window<`u`){let e=window.FrakSetup?.client?.config?.walletUrl;if(e)return x(e)}return y}var C=class extends Map{maxSize;constructor(e){super(),this.maxSize=e}get(e){let t=super.get(e);return super.has(e)&&(super.delete(e),super.set(e,t)),t}set(e,t){if(super.has(e)&&super.delete(e),super.set(e,t),this.maxSize&&this.size>this.maxSize){let e=super.keys().next().value;e!==void 0&&super.delete(e)}return this}};const w=new C(1024),T=new C(1024),E=3e4,D=new C(1024);async function O(e,{cacheKey:t,cacheTime:n=E}){if(n>0){let e=T.get(t);if(e&&Date.now()-e.created<n)return e.data}let r=D.get(t);if(r&&Date.now()-r<1e3)throw Error(`Cache: ${t} recently failed, backing off`);let i=w.get(t);i||(i=e(),w.set(t,i));try{let e=await i;return T.set(t,{data:e,created:Date.now()}),D.delete(t),e}catch(e){throw D.set(t,Date.now()),e}finally{w.delete(t)}}function k(e){return{clear:()=>{w.delete(e),T.delete(e)},has:(t=E)=>{let n=T.get(e);return n?Date.now()-n.created<t:!1}}}function A(){w.clear(),T.clear(),D.clear()}function j(e){return a(f(e))}function M(e){return`r`in e&&!(`v`in e)}function N(e){return`v`in e&&e.v===2}const P=`fCtx`;function F(e){if(e)try{if(N(e)){if(!e.m||!e.t)return;let t=e.w&&n(e.w);return!e.c&&!t?void 0:p({v:2,m:e.m,t:e.t,...e.c?{c:e.c}:{},...t?{w:e.w}:{}})}return d(t(e.r))}catch(t){console.error(`Error compressing Frak context`,{e:t,context:e})}}function I(t){if(!(!t||t.length===0))try{let r=j(t);if(r&&typeof r==`object`&&r.v===2){if(!r.m||!r.t)return;let e=r.w&&n(r.w);return!r.c&&!e?void 0:{v:2,m:r.m,t:r.t,...r.c?{c:r.c}:{},...e?{w:r.w}:{}}}let i=e(f(t),{size:20});if(n(i))return{r:i}}catch(e){console.error(`Error decompressing Frak context`,{e,context:t})}}function te({url:e}){if(!e)return null;let t=new URL(e).searchParams.get(P);return t?I(t):null}const L=`frak`;function R(e,t){let n=N(e);return{utm_source:t.utmSource??L,utm_medium:t.utmMedium??`referral`,utm_campaign:t.utmCampaign??(n?e.m:void 0),utm_content:t.utmContent,utm_term:t.utmTerm,via:t.via??L,ref:t.ref??(n?e.c:void 0)}}function z(e,t,n){let r=R(t,n??{});for(let[t,n]of Object.entries(r))n===void 0||n===``||e.searchParams.has(t)||e.searchParams.set(t,n)}function B({url:e,context:t,attribution:n}){if(!e)return null;let r=F(t);if(!r)return null;let i=new URL(e);return i.searchParams.set(P,r),z(i,t,n),i.toString()}function V(e){let t=new URL(e);return t.searchParams.delete(P),t.toString()}function ne({url:e,context:t}){if(!window.location?.href||typeof window>`u`){console.error(`No window found, can't update context`);return}let n=e??window.location.href,r;r=t===null?V(n):B({url:n,context:t}),r&&window.history.replaceState(null,``,r.toString())}const re={compress:F,decompress:I,parse:te,update:B,remove:V,replaceUrl:ne},H=`__frakSdkConfig`,U=`frak-config-cache`,W=`frak-merchant-id`,G={key:U},K=typeof window<`u`;function q(){return{isResolved:!1,merchantId:``}}let J=null;function Y(){if(!K)return null;try{let e=localStorage.getItem(G.key);if(!e)return null;let t=JSON.parse(e);return t.config?.isResolved?(J=t,t):null}catch{return null}}function X(){return(J??Y())?.config}function ie(){let e=J??Y();return e?Date.now()-e.timestamp<3e4:!1}function ae(e){if(!(!K||!e.isResolved))try{let t={config:e,timestamp:Date.now()};localStorage.setItem(G.key,JSON.stringify(t)),J=t}catch{}}function oe(){if(K){J=null;try{localStorage.removeItem(G.key)}catch{}}}function se(){K&&(window[H]||(window[H]=X()??q()))}se();function Z(){return K?window[H]??q():q()}function Q(e){K&&window.dispatchEvent(new CustomEvent(`frak:config`,{detail:e}))}function ce(e){return e??(K?window.location.hostname:``)}async function le(e,t,n){try{let r=S(t),i=n?`&lang=${encodeURIComponent(n)}`:``,a=await fetch(`${r}/user/merchant/resolve?domain=${encodeURIComponent(e)}${i}`);if(!a.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${e}: ${a.status}`);return}let o=await a.json();if(K)try{sessionStorage.setItem(W,o.merchantId)}catch{}return o}catch(e){console.warn(`[Frak SDK] Failed to fetch merchant config:`,e);return}}const $={getConfig:Z,get isResolved(){return Z().isResolved},get isCacheFresh(){return ie()},setCacheScope(e,t){G.key=`${U}:${`${e}:${t??``}`}`,J=null},setConfig(e){if(K&&(window[H]=e),ae(e),Q(e),K&&e.merchantId)try{sessionStorage.setItem(W,e.merchantId)}catch{}},reset(){let e=X()??q();K&&(window[H]=e),Q(e)},clearCache(){if(oe(),A(),K)try{sessionStorage.removeItem(W)}catch{}},resolve(e,t,n){let r=ce(e);return r?O(async()=>{let e=await le(r,t,n);if(!e)throw Error(`Config resolution returned empty`);return e},{cacheKey:`sdkConfig:${r}:${n??``}`,cacheTime:1/0}).catch(()=>void 0):Promise.resolve(void 0)},getMerchantId(){let e=Z();if(e.isResolved&&e.merchantId)return e.merchantId;if(K)try{return sessionStorage.getItem(W)??void 0}catch{}},async resolveMerchantId(e,t){return $.getMerchantId()||(await $.resolve(e,t))?.merchantId}};export{d as _,j as a,O as c,_ as d,h as f,f as g,p as h,N as i,S as l,m,re as n,A as o,g as p,M as r,k as s,$ as t,v as u,u as v,l as y};
@@ -1,4 +1,4 @@
1
- import{o as e,t,y as n}from"./sdkConfigStore-M37skmM8.js";import{Deferred as r,FrakRpcError as i,RpcErrorCodes as a,createRpcClient as o}from"@frak-labs/frame-connector";import{OpenPanel as s}from"@openpanel/web";const c=`nexus-wallet-backup`,l=`frakwallet://`;function u(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function d(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;end`}function f(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=u()&&p(e)?d(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function p(e){return e.startsWith(l)}const m={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function h(e){return e&&e in m?e:`eur`}function g(e){return e?m[e]??m.eur:m.eur}function _(e,t){let n=g(t),r=h(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function v(e){return e?`${e}Amount`:`eurAmount`}const y={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 b({walletBaseUrl:e,config:t}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let i=document.createElement(`iframe`);i.id=y.id,i.name=y.name,i.allow=y.allow,i.style.zIndex=y.style.zIndex.toString(),x({iframe:i,isVisible:!1});let a=t?.walletUrl??e??`https://wallet.frak.id`,o=n();return i.src=`${a}/listener?clientId=${encodeURIComponent(o)}`,new Promise(e=>{i.addEventListener(`load`,()=>e(i)),document.body.appendChild(i)})}function x({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 S(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 C(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent;return!!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1)}const w=C();function T(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent.toLowerCase();return e.includes(`instagram`)||e.includes(`fban`)||e.includes(`fbav`)||e.includes(`facebook`)}const E=T();function D(e){w&&e.startsWith(`https://`)?window.location.href=`x-safari-https://${e.slice(8)}`:w&&e.startsWith(`http://`)?window.location.href=`x-safari-http://${e.slice(7)}`:window.location.href=`https://backend.frak.id/common/social?u=${encodeURIComponent(e)}`}function O({perCall:e,defaults:t,productUtmContent:n}){if(e===null)return;let r=e!==void 0,i=t!==void 0&&Object.keys(t).length>0;if(!r&&!i&&!(n!==void 0&&n!==``))return;let a={...t,...e??{}},o=n??e?.utmContent;return o!==void 0&&o!==``?a.utmContent=o:delete a.utmContent,a}function k(e,t){if(typeof window>`u`)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`))}var A=class e{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?+(this.iframe.contentDocument.readyState===`complete`):-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:typeof e==`string`&&(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 e}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
1
+ import{o as e,t,y as n}from"./sdkConfigStore-ylgC-Tp0.js";import{Deferred as r,FrakRpcError as i,RpcErrorCodes as a,createRpcClient as o}from"@frak-labs/frame-connector";import{OpenPanel as s}from"@openpanel/web";const c=`nexus-wallet-backup`,l=`frakwallet://`;function u(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function d(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;end`}function f(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=u()&&p(e)?d(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function p(e){return e.startsWith(l)}const m={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function h(e){return e&&e in m?e:`eur`}function g(e){return e?m[e]??m.eur:m.eur}function _(e,t){let n=g(t),r=h(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function v(e){return e?`${e}Amount`:`eurAmount`}const y={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 b({walletBaseUrl:e,config:t}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let i=document.createElement(`iframe`);i.id=y.id,i.name=y.name,i.allow=y.allow,i.style.zIndex=y.style.zIndex.toString(),x({iframe:i,isVisible:!1});let a=t?.walletUrl??e??`https://wallet.frak.id`,o=n();return i.src=`${a}/listener?clientId=${encodeURIComponent(o)}`,new Promise(e=>{i.addEventListener(`load`,()=>e(i)),document.body.appendChild(i)})}function x({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 S(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 C(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent;return!!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1)}const w=C();function T(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent.toLowerCase();return e.includes(`instagram`)||e.includes(`fban`)||e.includes(`fbav`)||e.includes(`facebook`)}const E=T();function D(e){w&&e.startsWith(`https://`)?window.location.href=`x-safari-https://${e.slice(8)}`:w&&e.startsWith(`http://`)?window.location.href=`x-safari-http://${e.slice(7)}`:window.location.href=`https://backend.frak.id/common/social?u=${encodeURIComponent(e)}`}function O({perCall:e,defaults:t,productUtmContent:n}){if(e===null)return;let r=e!==void 0,i=t!==void 0&&Object.keys(t).length>0;if(!r&&!i&&!(n!==void 0&&n!==``))return;let a={...t,...e??{}},o=n??e?.utmContent;return o!==void 0&&o!==``?a.utmContent=o:delete a.utmContent,a}function k(e,t){if(typeof window>`u`)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`))}var A=class e{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?+(this.iframe.contentDocument.readyState===`complete`):-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:typeof e==`string`&&(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 e}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
2
2
  Debug Information:
3
3
  -----------------
4
4
  Timestamp: ${t.timestamp}
@@ -1,4 +1,4 @@
1
- const e=require(`./sdkConfigStore-DvwFc6Ym.cjs`);let t=require(`@frak-labs/frame-connector`),n=require(`@openpanel/web`);const r=`nexus-wallet-backup`,i=`frakwallet://`;function a(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function o(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;end`}function s(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let s=a()&&c(e)?o(e):e;window.location.href=s,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function c(e){return e.startsWith(i)}const l={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function u(e){return e&&e in l?e:`eur`}function d(e){return e?l[e]??l.eur:l.eur}function f(e,t){let n=d(t),r=u(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function p(e){return e?`${e}Amount`:`eurAmount`}const m={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 h({walletBaseUrl:t,config:n}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let i=document.createElement(`iframe`);i.id=m.id,i.name=m.name,i.allow=m.allow,i.style.zIndex=m.style.zIndex.toString(),g({iframe:i,isVisible:!1});let a=n?.walletUrl??t??`https://wallet.frak.id`,o=e.y();return i.src=`${a}/listener?clientId=${encodeURIComponent(o)}`,new Promise(e=>{i.addEventListener(`load`,()=>e(i)),document.body.appendChild(i)})}function g({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 _(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 v(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent;return!!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1)}const y=v();function b(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent.toLowerCase();return e.includes(`instagram`)||e.includes(`fban`)||e.includes(`fbav`)||e.includes(`facebook`)}const x=b();function S(e){y&&e.startsWith(`https://`)?window.location.href=`x-safari-https://${e.slice(8)}`:y&&e.startsWith(`http://`)?window.location.href=`x-safari-http://${e.slice(7)}`:window.location.href=`https://backend.frak.id/common/social?u=${encodeURIComponent(e)}`}function C({perCall:e,defaults:t,productUtmContent:n}){if(e===null)return;let r=e!==void 0,i=t!==void 0&&Object.keys(t).length>0;if(!r&&!i&&!(n!==void 0&&n!==``))return;let a={...t,...e??{}},o=n??e?.utmContent;return o!==void 0&&o!==``?a.utmContent=o:delete a.utmContent,a}function w(e,t){if(typeof window>`u`)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`))}var T=class e{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?+(this.iframe.contentDocument.readyState===`complete`):-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(),i=`Unknown`;return e instanceof t.FrakRpcError?i=`FrakRpcError: ${e.code} '${e.message}'`:e instanceof Error?i=e.message:typeof e==`string`&&(i=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:i}}static empty(){return new e}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
1
+ const e=require(`./sdkConfigStore-thuozJuB.cjs`);let t=require(`@frak-labs/frame-connector`),n=require(`@openpanel/web`);const r=`nexus-wallet-backup`,i=`frakwallet://`;function a(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function o(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;end`}function s(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let s=a()&&c(e)?o(e):e;window.location.href=s,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function c(e){return e.startsWith(i)}const l={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function u(e){return e&&e in l?e:`eur`}function d(e){return e?l[e]??l.eur:l.eur}function f(e,t){let n=d(t),r=u(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function p(e){return e?`${e}Amount`:`eurAmount`}const m={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 h({walletBaseUrl:t,config:n}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let i=document.createElement(`iframe`);i.id=m.id,i.name=m.name,i.allow=m.allow,i.style.zIndex=m.style.zIndex.toString(),g({iframe:i,isVisible:!1});let a=n?.walletUrl??t??`https://wallet.frak.id`,o=e.y();return i.src=`${a}/listener?clientId=${encodeURIComponent(o)}`,new Promise(e=>{i.addEventListener(`load`,()=>e(i)),document.body.appendChild(i)})}function g({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 _(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 v(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent;return!!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1)}const y=v();function b(){if(typeof navigator>`u`)return!1;let e=navigator.userAgent.toLowerCase();return e.includes(`instagram`)||e.includes(`fban`)||e.includes(`fbav`)||e.includes(`facebook`)}const x=b();function S(e){y&&e.startsWith(`https://`)?window.location.href=`x-safari-https://${e.slice(8)}`:y&&e.startsWith(`http://`)?window.location.href=`x-safari-http://${e.slice(7)}`:window.location.href=`https://backend.frak.id/common/social?u=${encodeURIComponent(e)}`}function C({perCall:e,defaults:t,productUtmContent:n}){if(e===null)return;let r=e!==void 0,i=t!==void 0&&Object.keys(t).length>0;if(!r&&!i&&!(n!==void 0&&n!==``))return;let a={...t,...e??{}},o=n??e?.utmContent;return o!==void 0&&o!==``?a.utmContent=o:delete a.utmContent,a}function w(e,t){if(typeof window>`u`)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`))}var T=class e{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?+(this.iframe.contentDocument.readyState===`complete`):-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(),i=`Unknown`;return e instanceof t.FrakRpcError?i=`FrakRpcError: ${e.code} '${e.message}'`:e instanceof Error?i=e.message:typeof e==`string`&&(i=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:i}}static empty(){return new e}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
2
2
  Debug Information:
3
3
  -----------------
4
4
  Timestamp: ${t.timestamp}
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "1.0.0-beta.61e6fb99",
14
+ "version": "1.0.0-beta.a7645eaa",
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",
@@ -91,7 +91,7 @@
91
91
  "viem": "^2.x"
92
92
  },
93
93
  "dependencies": {
94
- "@frak-labs/frame-connector": "0.2.0-beta.61e6fb99",
94
+ "@frak-labs/frame-connector": "0.2.0-beta.a7645eaa",
95
95
  "@openpanel/web": "^1.2.0"
96
96
  },
97
97
  "devDependencies": {
@@ -101,6 +101,7 @@ describe("processReferral", () => {
101
101
  "user_referred_started",
102
102
  {
103
103
  referrerClientId: "referrer-client-id",
104
+ referrerWallet: undefined,
104
105
  walletStatus: "connected",
105
106
  }
106
107
  );
@@ -109,6 +110,7 @@ describe("processReferral", () => {
109
110
  type: "arrival",
110
111
  referrerClientId: "referrer-client-id",
111
112
  referrerMerchantId: "merchant-uuid",
113
+ referrerWallet: undefined,
112
114
  referralTimestamp: 1709654400,
113
115
  landingUrl: "https://example.com/test",
114
116
  });
@@ -133,6 +135,73 @@ describe("processReferral", () => {
133
135
  expect(result).toBe("self-referral");
134
136
  vi.mocked(utils.getClientId).mockReturnValue("test-client-id");
135
137
  });
138
+
139
+ it("should successfully process v2 referral with wallet only (no clientId)", async () => {
140
+ await import("../../utils");
141
+ const { sendInteraction } = await import("../sendInteraction");
142
+
143
+ const referrerWallet =
144
+ "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" as Address;
145
+ const v2WithWalletOnly: FrakContextV2 = {
146
+ v: 2,
147
+ m: "merchant-uuid",
148
+ t: 1709654400,
149
+ w: referrerWallet,
150
+ };
151
+
152
+ const result = await processReferral(mockClient, {
153
+ walletStatus: mockWalletStatus,
154
+ frakContext: v2WithWalletOnly,
155
+ });
156
+
157
+ expect(result).toBe("success");
158
+ expect(sendInteraction).toHaveBeenCalledWith(mockClient, {
159
+ type: "arrival",
160
+ referrerClientId: undefined,
161
+ referrerMerchantId: "merchant-uuid",
162
+ referrerWallet,
163
+ referralTimestamp: 1709654400,
164
+ landingUrl: "https://example.com/test",
165
+ });
166
+ });
167
+
168
+ it("should return 'self-referral' when v2 wallet matches current wallet", async () => {
169
+ const v2SelfReferralByWallet: FrakContextV2 = {
170
+ v: 2,
171
+ m: "merchant-uuid",
172
+ t: 1709654400,
173
+ w: mockAddress,
174
+ };
175
+
176
+ const result = await processReferral(mockClient, {
177
+ walletStatus: mockWalletStatus,
178
+ frakContext: v2SelfReferralByWallet,
179
+ });
180
+
181
+ expect(result).toBe("self-referral");
182
+ });
183
+
184
+ it("should prefer wallet over clientId for self-referral when both are present", async () => {
185
+ const utils = await import("../../utils");
186
+ // clientId does NOT match current user, but wallet does → still self-referral
187
+ vi.mocked(utils.getClientId).mockReturnValue("some-other-client");
188
+
189
+ const v2Hybrid: FrakContextV2 = {
190
+ v: 2,
191
+ c: "referrer-client-id",
192
+ m: "merchant-uuid",
193
+ t: 1709654400,
194
+ w: mockAddress,
195
+ };
196
+
197
+ const result = await processReferral(mockClient, {
198
+ walletStatus: mockWalletStatus,
199
+ frakContext: v2Hybrid,
200
+ });
201
+
202
+ expect(result).toBe("self-referral");
203
+ vi.mocked(utils.getClientId).mockReturnValue("test-client-id");
204
+ });
136
205
  });
137
206
 
138
207
  describe("V1 context (backward compat)", () => {
@@ -198,6 +267,7 @@ describe("processReferral", () => {
198
267
  v: 2,
199
268
  c: "test-client-id",
200
269
  m: "merchant-uuid",
270
+ w: mockAddress,
201
271
  }),
202
272
  });
203
273
  });
@@ -225,4 +295,59 @@ describe("processReferral", () => {
225
295
  context: null,
226
296
  });
227
297
  });
298
+
299
+ it("should emit wallet in replacement context when alwaysAppendUrl is true and user is connected", async () => {
300
+ const utils = await import("../../utils");
301
+ vi.mocked(utils.getClientId).mockReturnValue(null as never);
302
+
303
+ const v2Context: FrakContextV2 = {
304
+ v: 2,
305
+ c: "referrer-client-id",
306
+ m: "merchant-uuid",
307
+ t: 1709654400,
308
+ };
309
+
310
+ await processReferral(mockClient, {
311
+ walletStatus: mockWalletStatus,
312
+ frakContext: v2Context,
313
+ options: { alwaysAppendUrl: true },
314
+ });
315
+
316
+ // clientId is null, but wallet is available — should still emit {w, m}
317
+ expect(utils.FrakContextManager.replaceUrl).toHaveBeenCalledWith({
318
+ url: window.location.href,
319
+ context: expect.objectContaining({
320
+ v: 2,
321
+ m: "merchant-uuid",
322
+ w: mockAddress,
323
+ }),
324
+ });
325
+
326
+ vi.mocked(utils.getClientId).mockReturnValue("test-client-id");
327
+ });
328
+
329
+ it("should return null replacement context when both clientId and wallet are missing", async () => {
330
+ const utils = await import("../../utils");
331
+ vi.mocked(utils.getClientId).mockReturnValue(null as never);
332
+
333
+ const v2Context: FrakContextV2 = {
334
+ v: 2,
335
+ c: "referrer-client-id",
336
+ m: "merchant-uuid",
337
+ t: 1709654400,
338
+ };
339
+
340
+ await processReferral(mockClient, {
341
+ walletStatus: { key: "not-connected" as const },
342
+ frakContext: v2Context,
343
+ options: { alwaysAppendUrl: true },
344
+ });
345
+
346
+ expect(utils.FrakContextManager.replaceUrl).toHaveBeenCalledWith({
347
+ url: window.location.href,
348
+ context: null,
349
+ });
350
+
351
+ vi.mocked(utils.getClientId).mockReturnValue("test-client-id");
352
+ });
228
353
  });
@@ -55,12 +55,14 @@ function trackArrivalIfValid(
55
55
  if (isV2Context(frakContext)) {
56
56
  trackEvent(client, "user_referred_started", {
57
57
  referrerClientId: frakContext.c,
58
+ referrerWallet: frakContext.w,
58
59
  walletStatus: walletStatus?.key,
59
60
  });
60
61
  sendInteraction(client, {
61
62
  type: "arrival",
62
63
  referrerClientId: frakContext.c,
63
64
  referrerMerchantId: frakContext.m,
65
+ referrerWallet: frakContext.w,
64
66
  referralTimestamp: frakContext.t,
65
67
  landingUrl,
66
68
  });
@@ -85,16 +87,23 @@ function trackArrivalIfValid(
85
87
 
86
88
  /**
87
89
  * Build a V2 context representing the current user for URL replacement.
88
- * @returns A V2 context, or null if clientId or merchantId is unavailable
90
+ *
91
+ * Emits both `c` (anonymous clientId) and `w` (wallet) when available — wallet
92
+ * is the preferred identity signal and takes attribution precedence downstream.
93
+ * Returns null when neither identifier is available.
89
94
  */
90
- function buildCurrentUserContext(merchantId: string): FrakContextV2 | null {
95
+ function buildCurrentUserContext(
96
+ merchantId: string,
97
+ wallet?: WalletStatusReturnType["wallet"]
98
+ ): FrakContextV2 | null {
91
99
  const clientId = getClientId();
92
- if (!clientId) return null;
100
+ if (!clientId && !wallet) return null;
93
101
  return {
94
102
  v: 2,
95
- c: clientId,
96
103
  m: merchantId,
97
104
  t: Math.floor(Date.now() / 1000),
105
+ ...(clientId ? { c: clientId } : {}),
106
+ ...(wallet ? { w: wallet } : {}),
98
107
  };
99
108
  }
100
109
 
@@ -107,7 +116,14 @@ function isSelfReferral(
107
116
  walletStatus?: WalletStatusReturnType
108
117
  ): boolean {
109
118
  if (isV2Context(frakContext)) {
110
- return getClientId() === frakContext.c;
119
+ // Wallet match takes precedence — it's the strongest signal we have.
120
+ if (frakContext.w && walletStatus?.wallet) {
121
+ return isAddressEqual(frakContext.w, walletStatus.wallet);
122
+ }
123
+ if (frakContext.c) {
124
+ return getClientId() === frakContext.c;
125
+ }
126
+ return false;
111
127
  }
112
128
  if (isV1Context(frakContext) && walletStatus?.wallet) {
113
129
  return isAddressEqual(frakContext.r, walletStatus.wallet);
@@ -164,7 +180,7 @@ export function processReferral(
164
180
 
165
181
  const replaceContext =
166
182
  options?.alwaysAppendUrl && contextMerchantId
167
- ? buildCurrentUserContext(contextMerchantId)
183
+ ? buildCurrentUserContext(contextMerchantId, walletStatus?.wallet)
168
184
  : null;
169
185
 
170
186
  FrakContextManager.replaceUrl({
@@ -11,19 +11,31 @@ export type FrakContextV1 = {
11
11
  };
12
12
 
13
13
  /**
14
- * V2 Frak Context — anonymous-first referral context.
15
- * Contains the sharer's clientId, merchantId, and link creation timestamp.
14
+ * V2 Frak Context — anonymous-first referral context with optional wallet.
15
+ *
16
+ * Carries merchant context (`m`) and creation timestamp (`t`) unconditionally.
17
+ * Identifies the sharer via either the anonymous clientId (`c`) or, when the
18
+ * sharer is authenticated, the stronger wallet identifier (`w`). A valid V2
19
+ * context MUST contain at least one of `c` or `w`; both may be present when
20
+ * a logged-in user shares a link (best attribution signal).
21
+ *
22
+ * `w` takes precedence as the source of truth because the wallet is bound to
23
+ * the user's WebAuthn credential, survives localStorage clears, and is global
24
+ * across merchants — unlike `c`, which is a per-browser UUID.
25
+ *
16
26
  * @ignore
17
27
  */
18
28
  export type FrakContextV2 = {
19
29
  /** Version discriminator */
20
30
  v: 2;
21
- /** Sharer's anonymous clientId (UUID from localStorage) */
22
- c: string;
23
31
  /** Merchant ID (UUID) */
24
32
  m: string;
25
33
  /** Link creation timestamp (epoch seconds) */
26
34
  t: number;
35
+ /** Sharer's anonymous clientId (UUID from localStorage). Optional when `w` is provided. */
36
+ c?: string;
37
+ /** Sharer's wallet address. Preferred source of truth when the sharer is authenticated. Optional when `c` is provided. */
38
+ w?: Address;
27
39
  };
28
40
 
29
41
  /**
@@ -11,7 +11,7 @@ import type { Address } from "viem";
11
11
  export type SendInteractionParamsType =
12
12
  | {
13
13
  type: "arrival";
14
- /** @deprecated V1 legacy use referrerClientId for v2 */
14
+ /** Sharer wallet address. Accepted in both V1 (legacy, wallet-only) and V2 (authenticated sharer) contexts. */
15
15
  referrerWallet?: Address;
16
16
  referrerClientId?: string;
17
17
  referrerMerchantId?: string;
@@ -45,7 +45,7 @@ export type AttributionParams = {
45
45
  export type AttributionDefaults = Omit<AttributionParams, "utmContent">;
46
46
 
47
47
  export type TrackArrivalParams = {
48
- /** @deprecated V1 legacy use referrerClientId for v2 contexts */
48
+ /** Sharer wallet address. Accepted in both V1 (legacy) and V2 (authenticated sharer) contexts. */
49
49
  referrerWallet?: Address;
50
50
  referrerClientId?: string;
51
51
  referrerMerchantId?: string;
@@ -42,7 +42,7 @@ describe("FrakContextManager", () => {
42
42
  expect(result).not.toMatch(/[+/=]/);
43
43
  });
44
44
 
45
- it("should return undefined when v2 context is missing clientId", () => {
45
+ it("should return undefined when v2 context has neither clientId nor wallet", () => {
46
46
  const partial = { v: 2 as const, m: "m", t: 123 };
47
47
  const result = FrakContextManager.compress(
48
48
  partial as FrakContextV2
@@ -50,6 +50,33 @@ describe("FrakContextManager", () => {
50
50
  expect(result).toBeUndefined();
51
51
  });
52
52
 
53
+ it("should compress v2 context with wallet only (no clientId)", () => {
54
+ const v2WithWalletOnly: FrakContextV2 = {
55
+ v: 2,
56
+ m: "merchant-uuid-1234",
57
+ t: 1709654400,
58
+ w: "0x1234567890123456789012345678901234567890" as Address,
59
+ };
60
+ const result = FrakContextManager.compress(v2WithWalletOnly);
61
+ expect(result).toBeDefined();
62
+ const decompressed = FrakContextManager.decompress(result);
63
+ expect(decompressed).toEqual(v2WithWalletOnly);
64
+ });
65
+
66
+ it("should compress v2 context with both clientId and wallet", () => {
67
+ const v2Hybrid: FrakContextV2 = {
68
+ v: 2,
69
+ c: "test-client-id-uuid",
70
+ m: "merchant-uuid-1234",
71
+ t: 1709654400,
72
+ w: "0x1234567890123456789012345678901234567890" as Address,
73
+ };
74
+ const result = FrakContextManager.compress(v2Hybrid);
75
+ expect(result).toBeDefined();
76
+ const decompressed = FrakContextManager.decompress(result);
77
+ expect(decompressed).toEqual(v2Hybrid);
78
+ });
79
+
53
80
  it("should return undefined when v2 context is missing merchantId", () => {
54
81
  const partial = { v: 2 as const, c: "c", t: 123 };
55
82
  const result = FrakContextManager.compress(
@@ -65,6 +92,40 @@ describe("FrakContextManager", () => {
65
92
  );
66
93
  expect(result).toBeUndefined();
67
94
  });
95
+
96
+ it("should reject v2 context with a malformed wallet address", () => {
97
+ const partial = {
98
+ v: 2 as const,
99
+ m: "merchant-uuid",
100
+ t: 1709654400,
101
+ w: "0xnot-a-valid-address" as Address,
102
+ };
103
+ const result = FrakContextManager.compress(
104
+ partial as FrakContextV2
105
+ );
106
+ // Invalid wallet → falls back to clientId requirement; absent here → undefined
107
+ expect(result).toBeUndefined();
108
+ });
109
+
110
+ it("should drop a malformed wallet but keep a valid clientId", () => {
111
+ const hybrid = {
112
+ v: 2 as const,
113
+ c: "valid-client-id",
114
+ m: "merchant-uuid",
115
+ t: 1709654400,
116
+ w: "0xnot-a-valid-address" as Address,
117
+ };
118
+ const compressed = FrakContextManager.compress(
119
+ hybrid as FrakContextV2
120
+ );
121
+ const decompressed = FrakContextManager.decompress(compressed);
122
+ expect(decompressed).toEqual({
123
+ v: 2,
124
+ c: "valid-client-id",
125
+ m: "merchant-uuid",
126
+ t: 1709654400,
127
+ });
128
+ });
68
129
  });
69
130
 
70
131
  describe("decompress", () => {
@@ -74,6 +135,28 @@ describe("FrakContextManager", () => {
74
135
 
75
136
  expect(decompressed).toEqual(v2Context);
76
137
  });
138
+
139
+ it("should drop malformed `w` field on decompress (defense against crafted URLs)", async () => {
140
+ // Manually compress a payload bypassing client-side validation
141
+ // by reaching into the underlying compressJsonToB64.
142
+ const { compressJsonToB64 } = await import(
143
+ "./compression/compress"
144
+ );
145
+ const malformed = compressJsonToB64({
146
+ v: 2,
147
+ c: "valid-client-id",
148
+ m: "merchant-uuid",
149
+ t: 1709654400,
150
+ w: "0xnot-a-real-address",
151
+ });
152
+ const result = FrakContextManager.decompress(malformed);
153
+ expect(result).toEqual({
154
+ v: 2,
155
+ c: "valid-client-id",
156
+ m: "merchant-uuid",
157
+ t: 1709654400,
158
+ });
159
+ });
77
160
  });
78
161
 
79
162
  describe("parse", () => {
@@ -28,13 +28,17 @@ function compress(context?: FrakContextV1 | FrakContextV2): string | undefined {
28
28
  if (!context) return;
29
29
  try {
30
30
  if (isV2Context(context)) {
31
- // Runtime validation: all V2 fields must be present and truthy
32
- if (!context.c || !context.m || !context.t) return undefined;
31
+ // Runtime validation: m + t are always required, and at least one of
32
+ // c (anonymous fingerprint) or w (valid wallet) must be present.
33
+ if (!context.m || !context.t) return undefined;
34
+ const hasValidWallet = context.w && isAddress(context.w);
35
+ if (!context.c && !hasValidWallet) return undefined;
33
36
  return compressJsonToB64({
34
37
  v: 2,
35
- c: context.c,
36
38
  m: context.m,
37
39
  t: context.t,
40
+ ...(context.c ? { c: context.c } : {}),
41
+ ...(hasValidWallet ? { w: context.w } : {}),
38
42
  });
39
43
  }
40
44
 
@@ -61,10 +65,18 @@ function decompress(context?: string): FrakContext | undefined {
61
65
  // Try V2 JSON first — V2 payloads are longer than V1's 20-byte address
62
66
  const json = decompressJsonFromB64<FrakContextV2>(context);
63
67
  if (json && typeof json === "object" && json.v === 2) {
64
- if (json.c && json.m && json.t) {
65
- return { v: 2, c: json.c, m: json.m, t: json.t };
66
- }
67
- return undefined;
68
+ if (!json.m || !json.t) return undefined;
69
+ // Validate `w` with isAddress() protects self-referral checks and
70
+ // URL replacement from crafted payloads carrying a malformed wallet.
71
+ const hasValidWallet = json.w && isAddress(json.w);
72
+ if (!json.c && !hasValidWallet) return undefined;
73
+ return {
74
+ v: 2,
75
+ m: json.m,
76
+ t: json.t,
77
+ ...(json.c ? { c: json.c } : {}),
78
+ ...(hasValidWallet ? { w: json.w } : {}),
79
+ };
68
80
  }
69
81
 
70
82
  // Fall back to V1: raw 20-byte address
@@ -109,9 +121,10 @@ const DEFAULT_ATTRIBUTION_SOURCE = "frak";
109
121
  /**
110
122
  * Resolve attribution defaults from the provided context.
111
123
  *
112
- * V2 contexts expose the merchantId (`m`) and clientId (`c`), which feed
113
- * `utm_campaign` and `ref` respectively. V1 contexts have no equivalent, so
114
- * only the static defaults (`utm_source`, `utm_medium`, `via`) apply.
124
+ * V2 contexts expose the merchantId (`m`) and, when anonymous, the clientId
125
+ * (`c`), which feed `utm_campaign` and `ref` respectively. When V2 only carries
126
+ * a wallet (`w`), `ref` is intentionally left unset — we don't want wallet
127
+ * addresses leaking into UTM params. V1 contexts have no equivalent.
115
128
  */
116
129
  function resolveAttributionValues(
117
130
  context: FrakContextV1 | FrakContextV2,
@@ -2,6 +2,7 @@ export type SdkReferralEventMap = {
2
2
  user_referred_started: {
3
3
  referrer?: string;
4
4
  referrerClientId?: string;
5
+ referrerWallet?: string;
5
6
  walletStatus?: string;
6
7
  };
7
8
  user_referred_completed: {