@frak-labs/core-sdk 1.1.3-beta.20a3d2fa → 1.1.4

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 (41) hide show
  1. package/cdn/bundle.js +1 -1
  2. package/dist/actions-D5GbNcvt.cjs +1 -0
  3. package/dist/actions-DE2dHqtp.js +1 -0
  4. package/dist/actions.cjs +1 -1
  5. package/dist/actions.d.cts +1 -1
  6. package/dist/actions.d.ts +1 -1
  7. package/dist/actions.js +1 -1
  8. package/dist/bundle.cjs +1 -1
  9. package/dist/bundle.d.cts +2 -2
  10. package/dist/bundle.d.ts +2 -2
  11. package/dist/bundle.js +1 -1
  12. package/dist/frakContext-Bl17GGa3.cjs +1 -0
  13. package/dist/frakContext-DmI5CaSw.js +1 -0
  14. package/dist/{index-B5_aKV0q.d.ts → index-Cm1PNC9z.d.ts} +40 -5
  15. package/dist/{index-C135c5Ul.d.cts → index-Cumn_FNI.d.cts} +40 -5
  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/src-5vpUuhfY.js +1 -0
  21. package/dist/src-BQsjMpvA.cjs +1 -0
  22. package/package.json +2 -4
  23. package/src/actions/referral/processReferral.test.ts +4 -4
  24. package/src/actions/referral/processReferral.ts +4 -4
  25. package/src/clients/createIFrameFrakClient.ts +7 -7
  26. package/src/context/frakContext.test.ts +55 -0
  27. package/src/context/frakContext.ts +13 -2
  28. package/src/index.ts +2 -0
  29. package/src/utils/analytics/events/lifecycle.ts +1 -1
  30. package/src/utils/analytics/events/referral.ts +3 -3
  31. package/src/utils/iframe/iframeHelper.test.ts +0 -39
  32. package/src/utils/iframe/iframeHelper.ts +2 -31
  33. package/src/utils/index.ts +5 -0
  34. package/src/utils/url/queryParams.test.ts +85 -0
  35. package/src/utils/url/queryParams.ts +57 -0
  36. package/dist/actions-BZhtIRpB.cjs +0 -1
  37. package/dist/actions-D5Lt3pG1.js +0 -1
  38. package/dist/frakContext-Cfp7sSid.cjs +0 -1
  39. package/dist/frakContext-UEOk9Bk4.js +0 -1
  40. package/dist/src-CB7gkP3D.js +0 -1
  41. package/dist/src-CxIEmNS7.cjs +0 -1
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { $ as SdkResolvedConfig, A as ModalRpcStepsResultType, B as OpenSsoReturnType, C as LoggedInEmbeddedView, D as DisplayModalParamsType, E as SharingPageProduct, F as SiweAuthenticateModalStepType, G as FinalModalStepType, H as PrepareSsoReturnType, I as SiweAuthenticateReturnType, J as IFrameLifecycleEvent, K as ModalStepMetadata, L as SiweAuthenticationParams, M as SendTransactionModalStepType, N as SendTransactionReturnType, O as ModalRpcMetadata, P as SendTransactionTxType, Q as ResolvedSdkConfig, R as LoginModalStepType, S as EmbeddedViewActionSharing, T as DisplaySharingPageResultType, U as SsoMetadata, V as PrepareSsoParamsType, W as FinalActionType, X as MerchantConfigResponse, Y as ClientLifecycleEvent, Z as ResolvedPlacement, _ as SendInteractionParamsType, a as FrakContextV1, at as LocalizedI18nConfig, b as LoggedOutEmbeddedView, c as FrakLifecycleEvent, ct as TrackArrivalParams, d as WalletStatusReturnType, et as Currency, f as UserReferralStatusType, g as TokenAmountType, h as RewardTier, i as FrakContext, it as ListenerPreloadOption, j as ModalStepTypes, k as ModalRpcStepsInput, l as IFrameTransport, lt as TrackArrivalResult, m as GetMerchantInformationReturnType, n as ssoPopupFeatures, nt as I18nConfig, o as FrakContextV2, ot as AttributionDefaults, p as EstimatedReward, q as InteractionTypeKey, r as ssoPopupName, rt as Language, s as FrakClient, st as AttributionParams, tt as FrakWalletSdkConfig, u as IFrameRpcSchema, ut as UtmParams, v as DisplayEmbeddedWalletParamsType, w as DisplaySharingPageParamsType, x as EmbeddedViewActionReferred, y as DisplayEmbeddedWalletResultType, z as OpenSsoParamsType } from "./openSso-DRLH4D9a.js";
2
- import { A as getClientId, C as SdkEventMap, D as FrakContextManager, E as mergeAttribution, F as HashProtectedData, I as KeyProvider, M as setupClient, N as createIFrameFrakClient, O as DEEP_LINK_SCHEME, P as CompressedData, S as trackEvent, T as MergeAttributionInput, _ as isInAppBrowser, a as baseIframeProps, b as DeepLinkFallbackOptions, c as getCurrencyAmountKey, d as compressJsonToB64, f as base64urlDecode, g as isIOS, h as withCache, i as generateSsoUrl, j as getBackendUrl, k as sdkConfigStore, l as formatAmount, m as clearAllCache, n as CompressedSsoData, o as findIframeInOpener, p as base64urlEncode, r as FullSsoParams, s as getSupportedCurrency, t as AppSpecificSsoMetadata, u as decompressJsonFromB64, v as isMobile, w as SdkHandshakeFailureReason, x as triggerDeepLinkWithFallback, y as redirectToExternalBrowser } from "./index-B5_aKV0q.js";
3
- export { type AppSpecificSsoMetadata, type AttributionDefaults, type AttributionParams, type ClientLifecycleEvent, type CompressedData, type CompressedSsoData, type Currency, DEEP_LINK_SCHEME, type DeepLinkFallbackOptions, type DisplayEmbeddedWalletParamsType, type DisplayEmbeddedWalletResultType, type DisplayModalParamsType, type DisplaySharingPageParamsType, type DisplaySharingPageResultType, type EmbeddedViewActionReferred, type EmbeddedViewActionSharing, type EstimatedReward, type FinalActionType, type FinalModalStepType, type FrakClient, type FrakContext, FrakContextManager, type FrakContextV1, type FrakContextV2, type FrakLifecycleEvent, type FrakWalletSdkConfig, type FullSsoParams, type GetMerchantInformationReturnType, type HashProtectedData, type I18nConfig, type IFrameLifecycleEvent, type IFrameRpcSchema, type IFrameTransport, type InteractionTypeKey, type KeyProvider, type Language, type ListenerPreloadOption, type LocalizedI18nConfig, type LoggedInEmbeddedView, type LoggedOutEmbeddedView, type LoginModalStepType, type MerchantConfigResponse, type MergeAttributionInput, type ModalRpcMetadata, type ModalRpcStepsInput, type ModalRpcStepsResultType, type ModalStepMetadata, type ModalStepTypes, type OpenSsoParamsType, type OpenSsoReturnType, type PrepareSsoParamsType, type PrepareSsoReturnType, type ResolvedPlacement, type ResolvedSdkConfig, type RewardTier, type SdkEventMap, type SdkHandshakeFailureReason, type SdkResolvedConfig, type SendInteractionParamsType, type SendTransactionModalStepType, type SendTransactionReturnType, type SendTransactionTxType, type SharingPageProduct, type SiweAuthenticateModalStepType, type SiweAuthenticateReturnType, type SiweAuthenticationParams, type SsoMetadata, type TokenAmountType, type TrackArrivalParams, type TrackArrivalResult, type UserReferralStatusType, type UtmParams, type WalletStatusReturnType, base64urlDecode, base64urlEncode, baseIframeProps, clearAllCache, compressJsonToB64, createIFrameFrakClient, decompressJsonFromB64, findIframeInOpener, formatAmount, generateSsoUrl, getBackendUrl, getClientId, getCurrencyAmountKey, getSupportedCurrency, isIOS, isInAppBrowser, isMobile, mergeAttribution, redirectToExternalBrowser, sdkConfigStore, setupClient, ssoPopupFeatures, ssoPopupName, trackEvent, triggerDeepLinkWithFallback, withCache };
2
+ import { A as DEEP_LINK_SCHEME, C as triggerDeepLinkWithFallback, D as MergeAttributionInput, E as SdkHandshakeFailureReason, F as createIFrameFrakClient, I as CompressedData, L as HashProtectedData, M as getClientId, N as getBackendUrl, O as mergeAttribution, P as setupClient, R as KeyProvider, S as DeepLinkFallbackOptions, T as SdkEventMap, _ as withCache, a as FullSsoParams, b as isMobile, c as findIframeInOpener, d as formatAmount, f as decompressJsonFromB64, g as clearAllCache, h as base64urlEncode, i as CompressedSsoData, j as sdkConfigStore, k as FrakContextManager, l as getSupportedCurrency, m as base64urlDecode, n as getQueryParamCaseInsensitive, o as generateSsoUrl, p as compressJsonToB64, r as AppSpecificSsoMetadata, s as baseIframeProps, t as deleteQueryParamCaseInsensitive, u as getCurrencyAmountKey, v as isIOS, w as trackEvent, x as redirectToExternalBrowser, y as isInAppBrowser } from "./index-Cm1PNC9z.js";
3
+ export { AppSpecificSsoMetadata, AttributionDefaults, AttributionParams, ClientLifecycleEvent, CompressedData, CompressedSsoData, Currency, DEEP_LINK_SCHEME, DeepLinkFallbackOptions, DisplayEmbeddedWalletParamsType, DisplayEmbeddedWalletResultType, DisplayModalParamsType, DisplaySharingPageParamsType, DisplaySharingPageResultType, EmbeddedViewActionReferred, EmbeddedViewActionSharing, EstimatedReward, FinalActionType, FinalModalStepType, FrakClient, FrakContext, FrakContextManager, FrakContextV1, FrakContextV2, FrakLifecycleEvent, FrakWalletSdkConfig, FullSsoParams, GetMerchantInformationReturnType, HashProtectedData, I18nConfig, IFrameLifecycleEvent, IFrameRpcSchema, IFrameTransport, InteractionTypeKey, KeyProvider, Language, ListenerPreloadOption, LocalizedI18nConfig, LoggedInEmbeddedView, LoggedOutEmbeddedView, LoginModalStepType, MerchantConfigResponse, MergeAttributionInput, ModalRpcMetadata, ModalRpcStepsInput, ModalRpcStepsResultType, ModalStepMetadata, ModalStepTypes, OpenSsoParamsType, OpenSsoReturnType, PrepareSsoParamsType, PrepareSsoReturnType, ResolvedPlacement, ResolvedSdkConfig, RewardTier, SdkEventMap, SdkHandshakeFailureReason, SdkResolvedConfig, SendInteractionParamsType, SendTransactionModalStepType, SendTransactionReturnType, SendTransactionTxType, SharingPageProduct, SiweAuthenticateModalStepType, SiweAuthenticateReturnType, SiweAuthenticationParams, SsoMetadata, TokenAmountType, TrackArrivalParams, TrackArrivalResult, UserReferralStatusType, UtmParams, WalletStatusReturnType, base64urlDecode, base64urlEncode, baseIframeProps, clearAllCache, compressJsonToB64, createIFrameFrakClient, decompressJsonFromB64, deleteQueryParamCaseInsensitive, findIframeInOpener, formatAmount, generateSsoUrl, getBackendUrl, getClientId, getCurrencyAmountKey, getQueryParamCaseInsensitive, getSupportedCurrency, isIOS, isInAppBrowser, isMobile, mergeAttribution, redirectToExternalBrowser, sdkConfigStore, setupClient, ssoPopupFeatures, ssoPopupName, trackEvent, triggerDeepLinkWithFallback, withCache };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{_ as e,a as t,c as n,d as r,f as i,g as a,h as o,l as s,m as c,p as l,s as u,t as d,u as f}from"./frakContext-UEOk9Bk4.js";import{a as p,c as m,d as h,f as g,h as _,i as v,l as y,m as b,n as x,o as S,p as C,r as w,s as T,t as E,u as D}from"./src-CB7gkP3D.js";export{_ as DEEP_LINK_SCHEME,d as FrakContextManager,r as base64urlDecode,i as base64urlEncode,g as baseIframeProps,o as clearAllCache,f as compressJsonToB64,h as createIFrameFrakClient,S as decompressJsonFromB64,C as findIframeInOpener,v as formatAmount,s as generateSsoUrl,c as getBackendUrl,e as getClientId,w as getCurrencyAmountKey,p as getSupportedCurrency,T as isIOS,m as isInAppBrowser,y as isMobile,E as mergeAttribution,D as redirectToExternalBrowser,l as sdkConfigStore,x as setupClient,u as ssoPopupFeatures,n as ssoPopupName,t as trackEvent,b as triggerDeepLinkWithFallback,a as withCache};
1
+ import{_ as e,a as t,d as n,f as r,g as i,h as a,l as o,m as s,o as c,p as l,s as u,t as d,u as f,v as p,y as m}from"./frakContext-DmI5CaSw.js";import{a as h,c as g,d as _,f as v,h as y,i as b,l as x,m as S,n as C,o as w,p as T,r as E,s as D,t as O,u as k}from"./src-5vpUuhfY.js";export{y as DEEP_LINK_SCHEME,d as FrakContextManager,l as base64urlDecode,s as base64urlEncode,v as baseIframeProps,e as clearAllCache,r as compressJsonToB64,_ as createIFrameFrakClient,w as decompressJsonFromB64,t as deleteQueryParamCaseInsensitive,T as findIframeInOpener,b as formatAmount,n as generateSsoUrl,i as getBackendUrl,m as getClientId,E as getCurrencyAmountKey,c as getQueryParamCaseInsensitive,h as getSupportedCurrency,D as isIOS,g as isInAppBrowser,x as isMobile,O as mergeAttribution,k as redirectToExternalBrowser,a as sdkConfigStore,C as setupClient,o as ssoPopupFeatures,f as ssoPopupName,u as trackEvent,S as triggerDeepLinkWithFallback,p as withCache};
@@ -0,0 +1 @@
1
+ import{_ as e,g as t,h as n,p as r,y as i}from"./frakContext-DmI5CaSw.js";import{Deferred as a,FrakRpcError as o,RpcErrorCodes as s,createRpcClient as c,jsonDecode as l}from"@frak-labs/frame-connector";import{OpenPanel as u}from"@openpanel/web";const d=`nexus-wallet-backup`,f=`frakwallet://`;function p(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`))}function m(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}const h=f.replace(`://`,``);function g(e){return`intent://${e.slice(13)}#Intent;scheme=${h};end`}function _(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=m()&&v(e)?g(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function v(e){return e.startsWith(f)}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:n}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let a=document.createElement(`iframe`);a.id=y.id,a.name=y.name,a.allow=y.allow,a.style.zIndex=y.style.zIndex.toString(),x({iframe:a,isVisible:!1});let o=n?.walletUrl??e??`https://wallet.frak.id`,s=i();return C(o),C(t(o)),a.src=`${o}/listener?clientId=${encodeURIComponent(s)}`,new Promise(e=>{a.addEventListener(`load`,()=>e(a)),document.body.appendChild(a)})}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(e){if(!(typeof document>`u`))try{let t=new URL(e).origin,n=`link[rel="preconnect"][data-frak-preconnect="${t}"]`;if(document.head.querySelector(n))return;let r=document.createElement(`link`);r.rel=`preconnect`,r.href=t,r.crossOrigin=``,r.dataset.frakPreconnect=t,document.head.appendChild(r)}catch{}}const w=(()=>{if(typeof navigator>`u`)return!1;let e=navigator.userAgent;if(!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1))return!1;let t=e.toLowerCase();return t.includes(`instagram`)||t.includes(`fban`)||t.includes(`fbav`)||t.includes(`facebook`)})();function T(e){e?localStorage.setItem(d,e):localStorage.removeItem(d)}function E(e,t){try{let n=new URL(e);if(!n.searchParams.has(`u`))return e;let r=k(window.location.href,t);return n.searchParams.delete(`u`),n.searchParams.append(`u`,r),n.toString()}catch{return e}}function D(e){let t=new URL(window.location.href);e&&t.searchParams.set(`fmt`,e);let n=t.protocol===`http:`?`x-safari-http`:`x-safari-https`;window.location.href=`${n}://${t.host}${t.pathname}${t.search}${t.hash}`}function O(e){return e.includes(`/common/social`)}function k(e,t){if(!t)return e;try{let n=new URL(e);return n.searchParams.set(`fmt`,t),n.toString()}catch{return`${e}${e.includes(`?`)?`&`:`?`}fmt=${encodeURIComponent(t)}`}}function A(e,t,n,r,i){if(i){let e=E(t,r);window.open(e,`_blank`);return}if(v(t)){let i=E(t,r);_(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}})}else if(w&&O(t))D(r);else{let e=E(t,r);window.location.href=e}}function j({iframe:e,targetOrigin:t}){let n=new a;return{handleEvent:r=>{if(!(`iframeLifecycle`in r))return;let{iframeLifecycle:i,data:a}=r;switch(i){case`connected`:n.resolve(!0);break;case`do-backup`:T(a.backup);break;case`remove-backup`:localStorage.removeItem(d);break;case`show`:case`hide`:x({iframe:e,isVisible:i===`show`});break;case`redirect`:A(e,a.baseRedirectUrl,t,a.mergeToken,a.openInNewTab);break}},isConnected:n.promise}}function M({config:t,iframe:r}){let l=t?.walletUrl??`https://wallet.frak.id`,d=typeof navigator<`u`?navigator.language?.split(`-`)[0]:void 0,f=t.metadata.lang??(d===`en`||d===`fr`?d:void 0),p=t.domain??(typeof window<`u`?window.location.hostname:``);n.setCacheScope(p,f),n.reset();let m=n.isCacheFresh?void 0:n.resolve(t.domain,t.walletUrl,f),h=j({iframe:r,targetOrigin:l}),g=new a,_=Date.now();if(!r.contentWindow)throw new o(s.configError,`The iframe does not have a content window`);let v=c({emittingTransport:r.contentWindow,listeningTransport:window,targetOrigin:l,middleware:[{async onRequest(e,t){if(!await h.isConnected)throw new o(s.clientNotConnected,`The iframe provider isn't connected yet`);return await g.promise,t}}],lifecycleHandlers:{iframeLifecycle:(e,t)=>{h.handleEvent(e)}}}),y=N(v,h),b=async()=>{y(),v.cleanup(),r.remove(),e(),n.clearCache(),n.reset()},x;{console.log(`[Frak SDK] Initializing OpenPanel`),x=new u({apiUrl:`https://op-api.gcp.frak.id`,clientId:`f305d11d-b93b-487c-80d4-92deb7903e98`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:e,payload:t})=>(e!==`track`||!t?.properties||`sdkVersion`in t.properties||(t.properties={...t.properties,sdkVersion:`1.1.4`,userAnonymousClientId:i()}),!0)}),x.setGlobalProperties({sdkVersion:`1.1.4`,userAnonymousClientId:i()}),x.init(),x.track(`sdk_initialized`,{sdkVersion:`1.1.4`});let e=!1,t=setTimeout(()=>{e||(e=!0,x?.track(`sdk_iframe_handshake_failed`,{reason:`timeout`}))},3e4);h.isConnected.then(()=>{e||(e=!0,clearTimeout(t),x?.track(`sdk_iframe_connected`,{handshake_duration_ms:Date.now()-_}))}).catch(()=>{e||(e=!0,clearTimeout(t),x?.track(`sdk_iframe_handshake_failed`,{reason:`unknown`}))})}let S=P({config:t,rpcClient:v,lifecycleManager:h,configPromise:m,contextSent:g,openPanel:x}).then(()=>{}).catch(e=>{throw g.reject(e),e});return{config:t,waitForConnection:h.isConnected,waitForSetup:S,request:v.request,listenerRequest:v.listen,destroy:b,openPanel:x}}function N(e,t){let n,r,i=()=>e.sendLifecycle({clientLifecycle:`heartbeat`});async function a(){i(),n=setInterval(i,250),r=setTimeout(()=>{o(),console.log(`Heartbeat timeout: connection failed`)},3e4),await t.isConnected,o()}function o(){n&&clearInterval(n),r&&clearTimeout(r)}return a(),o}async function P({config:e,rpcClient:t,lifecycleManager:r,configPromise:a,contextSent:o,openPanel:s}){await r.isConnected,p(t,r.isConnected);let c=new URL(window.location.href),l=c.searchParams.get(`fmt`)??void 0;l&&(c.searchParams.delete(`fmt`),window.history.replaceState({},``,c.toString()));let u=t=>{let r=t?.merchantId??e.metadata.merchantId??``,i=t?.domain??``,a=t?.allowedDomains??[],o=t?.sdkConfig,s=o?.attribution||e.attribution?{...e.attribution,...o?.attribution}:void 0;n.setConfig(o?{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,hasRawSdkConfig:!0,name:o.name??e.metadata.name,logoUrl:o.logoUrl??e.metadata.logoUrl,homepageLink:o.homepageLink??e.metadata.homepageLink,lang:o.lang??e.metadata.lang,currency:o.currency??e.metadata.currency,hidden:o.hidden,css:o.css,translations:o.translations,placements:o.placements,components:o.components,attribution:s}:{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,name:e.metadata.name,logoUrl:e.metadata.logoUrl,homepageLink:e.metadata.homepageLink,lang:e.metadata.lang,currency:e.metadata.currency,attribution:s})},f=!1,m=e=>{let n=f?void 0:l;f=!0;let r=e.hasRawSdkConfig?{name:e.name,logoUrl:e.logoUrl,homepageLink:e.homepageLink,lang:e.lang,currency:e.currency,hidden:e.hidden,css:e.css,translations:e.translations,placements:e.placements,attribution:e.attribution}:e.attribution?{attribution:e.attribution}:void 0,a=i();if(s){let t=s.global??{};s.setGlobalProperties({...t,merchantId:e.merchantId,domain:e.domain??``})}t.sendLifecycle({clientLifecycle:`resolved-config`,data:{merchantId:e.merchantId,domain:e.domain??``,allowedDomains:e.allowedDomains??[],sourceUrl:window.location.href,...a&&{sdkAnonymousId:a},...n&&{pendingMergeToken:n},...r&&{sdkConfig:r}}})};n.isResolved&&(m(n.getConfig()),o.resolve()),a&&(u(await a),m(n.getConfig()),o.resolve());async function h(){let n=e.customizations?.css;n&&t.sendLifecycle({clientLifecycle:`modal-css`,data:{cssLink:n}})}async function g(){let n=e.customizations?.i18n;n&&t.sendLifecycle({clientLifecycle:`modal-i18n`,data:{i18n:n}})}async function _(){if(typeof window>`u`)return;let e=window.localStorage.getItem(d);e&&t.sendLifecycle({clientLifecycle:`restore-backup`,data:{backup:e}})}(await Promise.allSettled([h(),g(),_()])).some(e=>e.status===`rejected`)&&s?.track(`sdk_iframe_handshake_failed`,{reason:`asset_push`})}function F(){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 I=F();function L(){return typeof navigator>`u`?!1:I?!0:/Android|webOS|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}function R(){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 z=R();function B(e){I&&e.startsWith(`https://`)?window.location.href=`x-safari-https://${e.slice(8)}`:I&&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 V(e){return l(r(e))}const H={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function U(e){return e&&e in H?e:`eur`}function W(e){return e?H[e]??H.eur:H.eur}function G(e,t){let n=W(t),r=U(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function K(e){return e?`${e}Amount`:`eurAmount`}async function q({config:e}){let t=J(e),n=await b({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=M({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function J(e){let t=U(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}function Y({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}export{U as a,z as c,M as d,y as f,f as h,G as i,L as l,_ as m,q as n,V as o,S as p,K as r,I as s,Y as t,B as u};
@@ -0,0 +1 @@
1
+ const e=require(`./frakContext-Bl17GGa3.cjs`);let t=require(`@frak-labs/frame-connector`),n=require(`@openpanel/web`);const r=`nexus-wallet-backup`,i=`frakwallet://`;function a(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`))}function o(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}const s=i.replace(`://`,``);function c(e){return`intent://${e.slice(13)}#Intent;scheme=${s};end`}function l(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=o()&&u(e)?c(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function u(e){return e.startsWith(i)}const d={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 f({walletBaseUrl:t,config:n}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let i=document.createElement(`iframe`);i.id=d.id,i.name=d.name,i.allow=d.allow,i.style.zIndex=d.style.zIndex.toString(),p({iframe:i,isVisible:!1});let a=n?.walletUrl??t??`https://wallet.frak.id`,o=e.y();return h(a),h(e.g(a)),i.src=`${a}/listener?clientId=${encodeURIComponent(o)}`,new Promise(e=>{i.addEventListener(`load`,()=>e(i)),document.body.appendChild(i)})}function p({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 m(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(e){if(!(typeof document>`u`))try{let t=new URL(e).origin,n=`link[rel="preconnect"][data-frak-preconnect="${t}"]`;if(document.head.querySelector(n))return;let r=document.createElement(`link`);r.rel=`preconnect`,r.href=t,r.crossOrigin=``,r.dataset.frakPreconnect=t,document.head.appendChild(r)}catch{}}const g=(()=>{if(typeof navigator>`u`)return!1;let e=navigator.userAgent;if(!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1))return!1;let t=e.toLowerCase();return t.includes(`instagram`)||t.includes(`fban`)||t.includes(`fbav`)||t.includes(`facebook`)})();function _(e){e?localStorage.setItem(r,e):localStorage.removeItem(r)}function v(e,t){try{let n=new URL(e);if(!n.searchParams.has(`u`))return e;let r=x(window.location.href,t);return n.searchParams.delete(`u`),n.searchParams.append(`u`,r),n.toString()}catch{return e}}function y(e){let t=new URL(window.location.href);e&&t.searchParams.set(`fmt`,e);let n=t.protocol===`http:`?`x-safari-http`:`x-safari-https`;window.location.href=`${n}://${t.host}${t.pathname}${t.search}${t.hash}`}function b(e){return e.includes(`/common/social`)}function x(e,t){if(!t)return e;try{let n=new URL(e);return n.searchParams.set(`fmt`,t),n.toString()}catch{return`${e}${e.includes(`?`)?`&`:`?`}fmt=${encodeURIComponent(t)}`}}function S(e,t,n,r,i){if(i){let e=v(t,r);window.open(e,`_blank`);return}if(u(t)){let i=v(t,r);l(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}})}else if(g&&b(t))y(r);else{let e=v(t,r);window.location.href=e}}function C({iframe:e,targetOrigin:n}){let i=new t.Deferred;return{handleEvent:t=>{if(!(`iframeLifecycle`in t))return;let{iframeLifecycle:a,data:o}=t;switch(a){case`connected`:i.resolve(!0);break;case`do-backup`:_(o.backup);break;case`remove-backup`:localStorage.removeItem(r);break;case`show`:case`hide`:p({iframe:e,isVisible:a===`show`});break;case`redirect`:S(e,o.baseRedirectUrl,n,o.mergeToken,o.openInNewTab);break}},isConnected:i.promise}}function w({config:r,iframe:i}){let a=r?.walletUrl??`https://wallet.frak.id`,o=typeof navigator<`u`?navigator.language?.split(`-`)[0]:void 0,s=r.metadata.lang??(o===`en`||o===`fr`?o:void 0),c=r.domain??(typeof window<`u`?window.location.hostname:``);e.h.setCacheScope(c,s),e.h.reset();let l=e.h.isCacheFresh?void 0:e.h.resolve(r.domain,r.walletUrl,s),u=C({iframe:i,targetOrigin:a}),d=new t.Deferred,f=Date.now();if(!i.contentWindow)throw new t.FrakRpcError(t.RpcErrorCodes.configError,`The iframe does not have a content window`);let p=(0,t.createRpcClient)({emittingTransport:i.contentWindow,listeningTransport:window,targetOrigin:a,middleware:[{async onRequest(e,n){if(!await u.isConnected)throw new t.FrakRpcError(t.RpcErrorCodes.clientNotConnected,`The iframe provider isn't connected yet`);return await d.promise,n}}],lifecycleHandlers:{iframeLifecycle:(e,t)=>{u.handleEvent(e)}}}),m=T(p,u),h=async()=>{m(),p.cleanup(),i.remove(),e._(),e.h.clearCache(),e.h.reset()},g;{console.log(`[Frak SDK] Initializing OpenPanel`),g=new n.OpenPanel({apiUrl:`https://op-api.gcp.frak.id`,clientId:`f305d11d-b93b-487c-80d4-92deb7903e98`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:t,payload:n})=>(t!==`track`||!n?.properties||`sdkVersion`in n.properties||(n.properties={...n.properties,sdkVersion:`1.1.4`,userAnonymousClientId:e.y()}),!0)}),g.setGlobalProperties({sdkVersion:`1.1.4`,userAnonymousClientId:e.y()}),g.init(),g.track(`sdk_initialized`,{sdkVersion:`1.1.4`});let t=!1,r=setTimeout(()=>{t||(t=!0,g?.track(`sdk_iframe_handshake_failed`,{reason:`timeout`}))},3e4);u.isConnected.then(()=>{t||(t=!0,clearTimeout(r),g?.track(`sdk_iframe_connected`,{handshake_duration_ms:Date.now()-f}))}).catch(()=>{t||(t=!0,clearTimeout(r),g?.track(`sdk_iframe_handshake_failed`,{reason:`unknown`}))})}let _=E({config:r,rpcClient:p,lifecycleManager:u,configPromise:l,contextSent:d,openPanel:g}).then(()=>{}).catch(e=>{throw d.reject(e),e});return{config:r,waitForConnection:u.isConnected,waitForSetup:_,request:p.request,listenerRequest:p.listen,destroy:h,openPanel:g}}function T(e,t){let n,r,i=()=>e.sendLifecycle({clientLifecycle:`heartbeat`});async function a(){i(),n=setInterval(i,250),r=setTimeout(()=>{o(),console.log(`Heartbeat timeout: connection failed`)},3e4),await t.isConnected,o()}function o(){n&&clearInterval(n),r&&clearTimeout(r)}return a(),o}async function E({config:t,rpcClient:n,lifecycleManager:i,configPromise:o,contextSent:s,openPanel:c}){await i.isConnected,a(n,i.isConnected);let l=new URL(window.location.href),u=l.searchParams.get(`fmt`)??void 0;u&&(l.searchParams.delete(`fmt`),window.history.replaceState({},``,l.toString()));let d=n=>{let r=n?.merchantId??t.metadata.merchantId??``,i=n?.domain??``,a=n?.allowedDomains??[],o=n?.sdkConfig,s=o?.attribution||t.attribution?{...t.attribution,...o?.attribution}:void 0;e.h.setConfig(o?{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,hasRawSdkConfig:!0,name:o.name??t.metadata.name,logoUrl:o.logoUrl??t.metadata.logoUrl,homepageLink:o.homepageLink??t.metadata.homepageLink,lang:o.lang??t.metadata.lang,currency:o.currency??t.metadata.currency,hidden:o.hidden,css:o.css,translations:o.translations,placements:o.placements,components:o.components,attribution:s}:{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,name:t.metadata.name,logoUrl:t.metadata.logoUrl,homepageLink:t.metadata.homepageLink,lang:t.metadata.lang,currency:t.metadata.currency,attribution:s})},f=!1,p=t=>{let r=f?void 0:u;f=!0;let i=t.hasRawSdkConfig?{name:t.name,logoUrl:t.logoUrl,homepageLink:t.homepageLink,lang:t.lang,currency:t.currency,hidden:t.hidden,css:t.css,translations:t.translations,placements:t.placements,attribution:t.attribution}:t.attribution?{attribution:t.attribution}:void 0,a=e.y();if(c){let e=c.global??{};c.setGlobalProperties({...e,merchantId:t.merchantId,domain:t.domain??``})}n.sendLifecycle({clientLifecycle:`resolved-config`,data:{merchantId:t.merchantId,domain:t.domain??``,allowedDomains:t.allowedDomains??[],sourceUrl:window.location.href,...a&&{sdkAnonymousId:a},...r&&{pendingMergeToken:r},...i&&{sdkConfig:i}}})};e.h.isResolved&&(p(e.h.getConfig()),s.resolve()),o&&(d(await o),p(e.h.getConfig()),s.resolve());async function m(){let e=t.customizations?.css;e&&n.sendLifecycle({clientLifecycle:`modal-css`,data:{cssLink:e}})}async function h(){let e=t.customizations?.i18n;e&&n.sendLifecycle({clientLifecycle:`modal-i18n`,data:{i18n:e}})}async function g(){if(typeof window>`u`)return;let e=window.localStorage.getItem(r);e&&n.sendLifecycle({clientLifecycle:`restore-backup`,data:{backup:e}})}(await Promise.allSettled([m(),h(),g()])).some(e=>e.status===`rejected`)&&c?.track(`sdk_iframe_handshake_failed`,{reason:`asset_push`})}function D(){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 O=D();function k(){return typeof navigator>`u`?!1:O?!0:/Android|webOS|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}function A(){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 j=A();function M(e){O&&e.startsWith(`https://`)?window.location.href=`x-safari-https://${e.slice(8)}`:O&&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 N(n){return(0,t.jsonDecode)(e.p(n))}const P={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function F(e){return e&&e in P?e:`eur`}function I(e){return e?P[e]??P.eur:P.eur}function L(e,t){let n=I(t),r=F(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function R(e){return e?`${e}Amount`:`eurAmount`}async function z({config:e}){let t=B(e),n=await f({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=w({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function B(e){let t=F(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}function V({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}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return L}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return z}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return M}});
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "1.1.3-beta.20a3d2fa",
14
+ "version": "1.1.4",
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",
@@ -41,7 +41,6 @@
41
41
  "exports": {
42
42
  ".": {
43
43
  "development": "./src/index.ts",
44
- "production": "./src/index.ts",
45
44
  "import": {
46
45
  "types": "./dist/index.d.ts",
47
46
  "default": "./dist/index.js"
@@ -53,7 +52,6 @@
53
52
  },
54
53
  "./actions": {
55
54
  "development": "./src/actions/index.ts",
56
- "production": "./src/actions/index.ts",
57
55
  "import": {
58
56
  "types": "./dist/actions.d.ts",
59
57
  "default": "./dist/actions.js"
@@ -93,7 +91,7 @@
93
91
  "viem": "^2.x"
94
92
  },
95
93
  "dependencies": {
96
- "@frak-labs/frame-connector": "0.2.0-beta.20a3d2fa",
94
+ "@frak-labs/frame-connector": "0.2.0",
97
95
  "@openpanel/web": "^1.4.1"
98
96
  },
99
97
  "devDependencies": {
@@ -105,9 +105,9 @@ describe("processReferral", () => {
105
105
  mockClient,
106
106
  "user_referred_started",
107
107
  {
108
- referrer_client_id: "referrer-client-id",
109
- referrer_wallet: undefined,
110
- wallet_status: "connected",
108
+ referrerClientId: "referrer-client-id",
109
+ referrerWallet: undefined,
110
+ walletStatus: "connected",
111
111
  }
112
112
  );
113
113
 
@@ -235,7 +235,7 @@ describe("processReferral", () => {
235
235
  "user_referred_started",
236
236
  {
237
237
  referrer: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
238
- wallet_status: "connected",
238
+ walletStatus: "connected",
239
239
  }
240
240
  );
241
241
  });
@@ -53,9 +53,9 @@ function trackArrivalIfValid(
53
53
  ): boolean {
54
54
  if (isV2Context(frakContext)) {
55
55
  trackEvent(client, "user_referred_started", {
56
- referrer_client_id: frakContext.c,
57
- referrer_wallet: frakContext.w,
58
- wallet_status: walletStatus?.key,
56
+ referrerClientId: frakContext.c,
57
+ referrerWallet: frakContext.w,
58
+ walletStatus: walletStatus?.key,
59
59
  });
60
60
  sendInteraction(client, {
61
61
  type: "arrival",
@@ -70,7 +70,7 @@ function trackArrivalIfValid(
70
70
  if (isV1Context(frakContext)) {
71
71
  trackEvent(client, "user_referred_started", {
72
72
  referrer: frakContext.r,
73
- wallet_status: walletStatus?.key,
73
+ walletStatus: walletStatus?.key,
74
74
  });
75
75
  sendInteraction(client, {
76
76
  type: "arrival",
@@ -153,11 +153,11 @@ export function createIFrameFrakClient({
153
153
  if (!payload?.properties) return true;
154
154
 
155
155
  // Check if we we got the properties once initialized
156
- if (!("sdk_version" in payload.properties)) {
156
+ if (!("sdkVersion" in payload.properties)) {
157
157
  payload.properties = {
158
158
  ...payload.properties,
159
- sdk_version: process.env.SDK_VERSION,
160
- user_anonymous_client_id: getClientId(),
159
+ sdkVersion: process.env.SDK_VERSION,
160
+ userAnonymousClientId: getClientId(),
161
161
  };
162
162
  }
163
163
 
@@ -165,12 +165,12 @@ export function createIFrameFrakClient({
165
165
  },
166
166
  });
167
167
  openPanel.setGlobalProperties({
168
- sdk_version: process.env.SDK_VERSION,
169
- user_anonymous_client_id: getClientId(),
168
+ sdkVersion: process.env.SDK_VERSION,
169
+ userAnonymousClientId: getClientId(),
170
170
  });
171
171
  openPanel.init();
172
172
  openPanel.track("sdk_initialized", {
173
- sdk_version: process.env.SDK_VERSION,
173
+ sdkVersion: process.env.SDK_VERSION,
174
174
  });
175
175
 
176
176
  // Race the connection against the heartbeat timeout so we can
@@ -398,7 +398,7 @@ async function postConnectionSetup({
398
398
  const current = openPanel.global ?? {};
399
399
  openPanel.setGlobalProperties({
400
400
  ...current,
401
- merchant_id: resolved.merchantId,
401
+ merchantId: resolved.merchantId,
402
402
  domain: resolved.domain ?? "",
403
403
  });
404
404
  }
@@ -584,6 +584,61 @@ describe("FrakContextManager", () => {
584
584
  });
585
585
  });
586
586
 
587
+ describe("case-insensitive fCtx key", () => {
588
+ const context: FrakContextV2 = {
589
+ v: 2,
590
+ c: "550e8400-e29b-41d4-a716-446655440001",
591
+ m: "550e8400-e29b-41d4-a716-446655440000",
592
+ t: 1709654400,
593
+ };
594
+
595
+ it("should parse a lowercased fctx key with an intact value", () => {
596
+ const compressed = FrakContextManager.compress(context);
597
+ const url = `https://example.com?fctx=${compressed}`;
598
+
599
+ const result = FrakContextManager.parse({ url });
600
+
601
+ expect(result).toEqual(context);
602
+ });
603
+
604
+ it("should parse an uppercased FCTX key with an intact value", () => {
605
+ const compressed = FrakContextManager.compress(context);
606
+ const url = `https://example.com?FCTX=${compressed}`;
607
+
608
+ const result = FrakContextManager.parse({ url });
609
+
610
+ expect(result).toEqual(context);
611
+ });
612
+
613
+ it("should prefer the exact-case fCtx key when both variants exist", () => {
614
+ const canonical = FrakContextManager.compress(context);
615
+ const url = `https://example.com?fctx=stale&fCtx=${canonical}`;
616
+
617
+ const result = FrakContextManager.parse({ url });
618
+
619
+ expect(result).toEqual(context);
620
+ });
621
+
622
+ it("should remove a lowercased fctx key", () => {
623
+ const url = "https://example.com?fctx=abc&keep=me";
624
+
625
+ const result = FrakContextManager.remove(url);
626
+
627
+ expect(result).not.toContain("fctx");
628
+ expect(result).toContain("keep=me");
629
+ });
630
+
631
+ it("should not leave a stale lowercased fctx variant on update", () => {
632
+ const url = "https://example.com?fctx=stale";
633
+
634
+ const result = FrakContextManager.update({ url, context });
635
+
636
+ expect(result).toContain("fCtx=");
637
+ expect(result).not.toContain("fctx=stale");
638
+ expect(FrakContextManager.parse({ url: result! })).toEqual(context);
639
+ });
640
+ });
641
+
587
642
  describe("replaceUrl", () => {
588
643
  const mockAddress =
589
644
  "0x1234567890123456789012345678901234567890" as Address;
@@ -6,6 +6,10 @@ import type {
6
6
  } from "../types";
7
7
  import { isV2Context } from "../types";
8
8
  import { base64urlDecode, base64urlEncode } from "../utils/compression/b64";
9
+ import {
10
+ deleteQueryParamCaseInsensitive,
11
+ getQueryParamCaseInsensitive,
12
+ } from "../utils/url/queryParams";
9
13
  import { addressToBytes, bytesToAddress, isAddress } from "./address";
10
14
  import { decodeFrakContextV2, encodeFrakContextV2 } from "./frakContextV2Codec";
11
15
 
@@ -77,6 +81,9 @@ function decompress(context?: string): FrakContext | undefined {
77
81
  /**
78
82
  * Parse a URL to extract the Frak referral context from the `fCtx` query parameter.
79
83
  *
84
+ * The key is matched case-insensitively: some link channels (emails, messaging
85
+ * apps) lowercase query-param keys in transit, so `fCtx` can arrive as `fctx`.
86
+ *
80
87
  * @param args
81
88
  * @param args.url - The URL to parse
82
89
  * @returns The parsed FrakContext, or null if absent
@@ -85,7 +92,10 @@ function parse({ url }: { url: string }): FrakContext | null | undefined {
85
92
  if (!url) return null;
86
93
 
87
94
  const urlObj = new URL(url);
88
- const frakContext = urlObj.searchParams.get(contextKey);
95
+ const frakContext = getQueryParamCaseInsensitive(
96
+ urlObj.searchParams,
97
+ contextKey
98
+ );
89
99
  if (!frakContext) return null;
90
100
 
91
101
  return decompress(frakContext);
@@ -165,6 +175,7 @@ function update({
165
175
  if (!compressedContext) return null;
166
176
 
167
177
  const urlObj = new URL(url);
178
+ deleteQueryParamCaseInsensitive(urlObj.searchParams, contextKey);
168
179
  urlObj.searchParams.set(contextKey, compressedContext);
169
180
  applyAttributionParams(urlObj, attribution);
170
181
  return urlObj.toString();
@@ -178,7 +189,7 @@ function update({
178
189
  */
179
190
  function remove(url: string): string {
180
191
  const urlObj = new URL(url);
181
- urlObj.searchParams.delete(contextKey);
192
+ deleteQueryParamCaseInsensitive(urlObj.searchParams, contextKey);
182
193
  return urlObj.toString();
183
194
  }
184
195
 
package/src/index.ts CHANGED
@@ -107,11 +107,13 @@ export {
107
107
  compressJsonToB64,
108
108
  type DeepLinkFallbackOptions,
109
109
  decompressJsonFromB64,
110
+ deleteQueryParamCaseInsensitive,
110
111
  type FullSsoParams,
111
112
  findIframeInOpener,
112
113
  formatAmount,
113
114
  generateSsoUrl,
114
115
  getCurrencyAmountKey,
116
+ getQueryParamCaseInsensitive,
115
117
  getSupportedCurrency,
116
118
  isInAppBrowser,
117
119
  isIOS,
@@ -6,7 +6,7 @@ export type SdkHandshakeFailureReason =
6
6
 
7
7
  export type SdkLifecycleEventMap = {
8
8
  sdk_initialized: {
9
- sdk_version?: string;
9
+ sdkVersion?: string;
10
10
  };
11
11
  sdk_iframe_connected: {
12
12
  handshake_duration_ms: number;
@@ -1,9 +1,9 @@
1
1
  export type SdkReferralEventMap = {
2
2
  user_referred_started: {
3
3
  referrer?: string;
4
- referrer_client_id?: string;
5
- referrer_wallet?: string;
6
- wallet_status?: string;
4
+ referrerClientId?: string;
5
+ referrerWallet?: string;
6
+ walletStatus?: string;
7
7
  };
8
8
  user_referred_completed: {
9
9
  status: "success";
@@ -185,45 +185,6 @@ describe("iframeHelper", () => {
185
185
 
186
186
  expect(mockIframe.style.zIndex).toBe("2000001");
187
187
  });
188
-
189
- it("should append #preload=... when config.preload is non-empty", async () => {
190
- const config: FrakWalletSdkConfig = {
191
- metadata: { name: "Test" },
192
- preload: ["sharing"],
193
- };
194
-
195
- await createIframe({ config });
196
-
197
- expect(mockIframe.src).toBe(
198
- "https://wallet.frak.id/listener?clientId=mock-client-id-for-test#preload=sharing"
199
- );
200
- });
201
-
202
- it("should join multiple preload options with comma", async () => {
203
- const config: FrakWalletSdkConfig = {
204
- metadata: { name: "Test" },
205
- preload: ["sharing", "modal"],
206
- };
207
-
208
- await createIframe({ config });
209
-
210
- expect(mockIframe.src).toBe(
211
- "https://wallet.frak.id/listener?clientId=mock-client-id-for-test#preload=sharing,modal"
212
- );
213
- });
214
-
215
- it("should omit the preload hash when config.preload is an empty array", async () => {
216
- const config: FrakWalletSdkConfig = {
217
- metadata: { name: "Test" },
218
- preload: [],
219
- };
220
-
221
- await createIframe({ config });
222
-
223
- expect(mockIframe.src).toBe(
224
- "https://wallet.frak.id/listener?clientId=mock-client-id-for-test"
225
- );
226
- });
227
188
  });
228
189
 
229
190
  describe("changeIframeVisibility", () => {
@@ -1,6 +1,6 @@
1
1
  import { getBackendUrl } from "../../config/backendUrl";
2
2
  import { getClientId } from "../../config/clientId";
3
- import type { FrakWalletSdkConfig, ListenerPreloadOption } from "../../types";
3
+ import type { FrakWalletSdkConfig } from "../../types";
4
4
 
5
5
  /**
6
6
  * Base props for the iframe
@@ -64,11 +64,7 @@ export function createIframe({
64
64
  preconnect(walletUrl);
65
65
  preconnect(getBackendUrl(walletUrl));
66
66
 
67
- iframe.src = buildListenerUrl({
68
- walletUrl,
69
- clientId,
70
- preload: config?.preload,
71
- });
67
+ iframe.src = `${walletUrl}/listener?clientId=${encodeURIComponent(clientId)}`;
72
68
 
73
69
  return new Promise((resolve) => {
74
70
  iframe.addEventListener("load", () => resolve(iframe));
@@ -76,31 +72,6 @@ export function createIframe({
76
72
  });
77
73
  }
78
74
 
79
- /**
80
- * Build the listener iframe URL.
81
- *
82
- * Query params:
83
- * - `clientId` — anonymous SDK client identifier used for funnel joining.
84
- *
85
- * Hash params (consumed by `apps/listener/app/bootstrap.ts#setupPreloadHints`):
86
- * - `preload=modal,sharing` — idle-warms the matching Ring 1 + Ring 2 chunks.
87
- * Skipped entirely when no preload hints are provided so the listener
88
- * doesn't pay for warm-ups that nobody asked for.
89
- */
90
- function buildListenerUrl({
91
- walletUrl,
92
- clientId,
93
- preload,
94
- }: {
95
- walletUrl: string;
96
- clientId: string;
97
- preload?: ListenerPreloadOption[];
98
- }): string {
99
- const base = `${walletUrl}/listener?clientId=${encodeURIComponent(clientId)}`;
100
- if (!preload || preload.length === 0) return base;
101
- return `${base}#preload=${preload.join(",")}`;
102
- }
103
-
104
75
  /**
105
76
  * Change the visibility of the given iframe
106
77
  * @ignore
@@ -47,3 +47,8 @@ export {
47
47
  type FullSsoParams,
48
48
  generateSsoUrl,
49
49
  } from "./sso/sso";
50
+ // URL query params (case-insensitive key matching)
51
+ export {
52
+ deleteQueryParamCaseInsensitive,
53
+ getQueryParamCaseInsensitive,
54
+ } from "./url/queryParams";
@@ -0,0 +1,85 @@
1
+ import { describe, expect, it } from "../../../tests/vitest-fixtures";
2
+ import {
3
+ deleteQueryParamCaseInsensitive,
4
+ getQueryParamCaseInsensitive,
5
+ } from "./queryParams";
6
+
7
+ describe("getQueryParamCaseInsensitive", () => {
8
+ it("reads an exact-case key", () => {
9
+ const params = new URLSearchParams("frakAction=share");
10
+ expect(getQueryParamCaseInsensitive(params, "frakAction")).toBe(
11
+ "share"
12
+ );
13
+ });
14
+
15
+ it("reads a fully lowercased key (email/browser mangling)", () => {
16
+ const params = new URLSearchParams("frakaction=share");
17
+ expect(getQueryParamCaseInsensitive(params, "frakAction")).toBe(
18
+ "share"
19
+ );
20
+ });
21
+
22
+ it("reads an uppercased key", () => {
23
+ const params = new URLSearchParams("FRAKACTION=share");
24
+ expect(getQueryParamCaseInsensitive(params, "frakAction")).toBe(
25
+ "share"
26
+ );
27
+ });
28
+
29
+ it("reads a mixed-case key regardless of the lookup casing", () => {
30
+ const params = new URLSearchParams("FrAkAcTiOn=share");
31
+ expect(getQueryParamCaseInsensitive(params, "FRAKACTION")).toBe(
32
+ "share"
33
+ );
34
+ });
35
+
36
+ it("returns null when no key matches", () => {
37
+ const params = new URLSearchParams("other=1");
38
+ expect(getQueryParamCaseInsensitive(params, "frakAction")).toBeNull();
39
+ });
40
+
41
+ it("prefers the exact-case key over a case-folded duplicate", () => {
42
+ const params = new URLSearchParams("fctx=stale&fCtx=canonical");
43
+ expect(getQueryParamCaseInsensitive(params, "fCtx")).toBe("canonical");
44
+ });
45
+
46
+ it("falls back to the first case-folded variant when no exact match", () => {
47
+ const params = new URLSearchParams("fctx=first&FCTX=second");
48
+ expect(getQueryParamCaseInsensitive(params, "fCtx")).toBe("first");
49
+ });
50
+
51
+ it("preserves the value casing (only the key is normalized)", () => {
52
+ const params = new URLSearchParams("placement=Klaviyo-Post-Purchase");
53
+ expect(getQueryParamCaseInsensitive(params, "PLACEMENT")).toBe(
54
+ "Klaviyo-Post-Purchase"
55
+ );
56
+ });
57
+ });
58
+
59
+ describe("deleteQueryParamCaseInsensitive", () => {
60
+ it("deletes an exact-case key", () => {
61
+ const params = new URLSearchParams("frakAction=share&keep=me");
62
+ deleteQueryParamCaseInsensitive(params, "frakAction");
63
+ expect(params.toString()).toBe("keep=me");
64
+ });
65
+
66
+ it("deletes a lowercased key", () => {
67
+ const params = new URLSearchParams("frakaction=share&keep=me");
68
+ deleteQueryParamCaseInsensitive(params, "frakAction");
69
+ expect(params.toString()).toBe("keep=me");
70
+ });
71
+
72
+ it("deletes every case variant of the key", () => {
73
+ const params = new URLSearchParams(
74
+ "frakAction=a&frakaction=b&FRAKACTION=c&keep=me"
75
+ );
76
+ deleteQueryParamCaseInsensitive(params, "frakAction");
77
+ expect(params.toString()).toBe("keep=me");
78
+ });
79
+
80
+ it("is a no-op when the key is absent", () => {
81
+ const params = new URLSearchParams("keep=me");
82
+ deleteQueryParamCaseInsensitive(params, "frakAction");
83
+ expect(params.toString()).toBe("keep=me");
84
+ });
85
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Case-insensitive helpers for reading URL query parameters.
3
+ *
4
+ * Some email tools (Klaviyo, Omnisend, Customer.io …) and a few browsers
5
+ * lowercase the entire URL before the recipient opens it, so a mixed-case key
6
+ * authored as `frakAction` or `fCtx` can arrive as `frakaction` / `fctx`. A
7
+ * plain `searchParams.get("frakAction")` would miss it. Matching the key
8
+ * ignoring case keeps UI-triggering and referral params working regardless of
9
+ * how the link was mangled in transit.
10
+ *
11
+ * Note: only the key is normalised. An encoded value (base64url, tokens) is not
12
+ * recoverable if the same channel also lowercased the value itself.
13
+ */
14
+
15
+ /**
16
+ * Read a query parameter, matching its key case-insensitively.
17
+ *
18
+ * An exact-case match wins when present, so a canonical link is never shadowed
19
+ * by a mangled duplicate (`?fctx=stale&fCtx=real` resolves to `real`). Only when
20
+ * the exact key is absent do we scan for a case-folded variant.
21
+ *
22
+ * @returns the param value, or `null` when no key matches.
23
+ */
24
+ export function getQueryParamCaseInsensitive(
25
+ searchParams: URLSearchParams,
26
+ key: string
27
+ ): string | null {
28
+ const exact = searchParams.get(key);
29
+ if (exact !== null) return exact;
30
+
31
+ const target = key.toLowerCase();
32
+ for (const [paramKey, value] of searchParams) {
33
+ if (paramKey.toLowerCase() === target) {
34
+ return value;
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * Delete every query parameter whose key matches `key` case-insensitively.
42
+ *
43
+ * Keys are collected before deletion because mutating a `URLSearchParams`
44
+ * while iterating it skips entries.
45
+ */
46
+ export function deleteQueryParamCaseInsensitive(
47
+ searchParams: URLSearchParams,
48
+ key: string
49
+ ): void {
50
+ const target = key.toLowerCase();
51
+ const matchingKeys = [...searchParams.keys()].filter(
52
+ (paramKey) => paramKey.toLowerCase() === target
53
+ );
54
+ for (const paramKey of matchingKeys) {
55
+ searchParams.delete(paramKey);
56
+ }
57
+ }
@@ -1 +0,0 @@
1
- const e=require(`./frakContext-Cfp7sSid.cjs`);let t=require(`@frak-labs/frame-connector`);async function n(e,t,n){return await e.request({method:`frak_displayEmbeddedWallet`,params:n?[t,e.config.metadata,n]:[t,e.config.metadata]})}async function r(e,{steps:t,metadata:n},r){return await e.request({method:`frak_displayModal`,params:r?[t,n,e.config.metadata,r]:[t,n,e.config.metadata]})}async function i(e,t,n){return await e.request({method:`frak_displaySharingPage`,params:n?[t,e.config.metadata,n]:[t,e.config.metadata]})}async function a(t){if(typeof window>`u`)return;let n=e._();if(!n)return;let r=await e.p.resolveMerchantId();if(!r)return;let i=`frak-identity-ensured-${r}`;if(!window.sessionStorage.getItem(i))try{let a=e.m();(await fetch(`${a}/user/identity/ensure`,{method:`POST`,headers:{Accept:`application/json`,"Content-Type":`application/json`,"x-wallet-sdk-auth":t,"x-frak-client-id":n},body:JSON.stringify({merchantId:r})})).ok&&window.sessionStorage.setItem(i,`1`)}catch{}}async function o(t,n){return e.g(()=>t.request({method:`frak_getMerchantInformation`}),{cacheKey:`frak_getMerchantInformation`,cacheTime:n?.cacheTime})}async function s(t,n){return e.g(()=>t.request({method:`frak_getMergeToken`}),{cacheKey:`frak_getMergeToken`,cacheTime:n?.cacheTime})}async function c(t,n){return e.g(()=>t.request({method:`frak_getUserReferralStatus`}),{cacheKey:`frak_getUserReferralStatus`,cacheTime:n?.cacheTime})}async function l(e,t){let{metadata:n,customizations:r}=e.config;return await e.request({method:`frak_prepareSso`,params:[t,n.name,r?.css]})}async function u(t,n){try{await t.request({method:`frak_sendInteraction`,params:[n,{clientId:e._()}]})}catch{console.warn(`[Frak SDK] Failed to send interaction:`,n.type)}}function d(t,n,r){return e.r(n)?(e.a(t,`user_referred_started`,{referrer_client_id:n.c,referrer_wallet:n.w,wallet_status:r?.key}),u(t,{type:`arrival`,referrerClientId:n.c,referrerMerchantId:n.m,referrerWallet:n.w,referralTimestamp:n.t}),!0):e.n(n)?(e.a(t,`user_referred_started`,{referrer:n.r,wallet_status:r?.key}),u(t,{type:`arrival`,referrerWallet:n.r}),!0):!1}function f(t,n){let r=e._();return!r&&!n?null:{v:2,m:t,t:Math.floor(Date.now()/1e3),...r?{c:r}:{},...n?{w:n}:{}}}function p(t,n){return e.r(t)?t.w&&n?.wallet?e.i(t.w,n.wallet):t.c?e._()===t.c:!1:e.n(t)&&n?.wallet?e.i(t.r,n.wallet):!1}function m(t,{walletStatus:n,frakContext:r,options:i}){if(!r)return`no-referrer`;if(p(r,n))return`self-referral`;if(!d(t,r,n))return`no-referrer`;let a=e.r(r)?r.m:i?.merchantId,o=i?.alwaysAppendUrl&&a?f(a,n?.wallet):null;return e.t.replaceUrl({url:window.location?.href,context:o}),e.a(t,`user_referred_completed`,{status:`success`}),`success`}async function h(t,{options:n}={}){let r=e.t.parse({url:window.location.href}),i=await y(t);try{return m(t,{walletStatus:i,frakContext:r,options:n})}catch(e){console.warn(`Error processing referral`,{error:e})}}const g=`frak:referral-success`;async function _(e){try{await h(e)===`success`&&window.dispatchEvent(new Event(g))}catch(e){console.warn(`[Frak] Referral setup failed`,e)}}async function v(t){if(typeof window>`u`){console.warn(`[Frak] No window found, can't track purchase`);return}let n=window.sessionStorage.getItem(`frak-wallet-interaction-token`),r=e._();if(!n&&!r){console.warn(`[Frak] No identity found, skipping purchase check`);return}let i=t.merchantId??await e.p.resolveMerchantId();if(!i){console.warn(`[Frak] No merchant id found, skipping purchase check`);return}let a={Accept:`application/json`,"Content-Type":`application/json`};n&&(a[`x-wallet-sdk-auth`]=n),r&&(a[`x-frak-client-id`]=r);let o=e.m();await fetch(`${o}/user/track/purchase`,{method:`POST`,headers:a,body:JSON.stringify({customerId:t.customerId,orderId:t.orderId,token:t.token,merchantId:i})})}function y(e,n){if(!n)return e.request({method:`frak_listenToWalletStatus`}).then(t=>(b(e,t),t));let r=new t.Deferred,i=!1;return e.listenerRequest({method:`frak_listenToWalletStatus`},t=>{b(e,t),n(t),i||=(r.resolve(t),!0)}),r.promise}function b(e,t){typeof window>`u`||(e.openPanel?.setGlobalProperties({wallet:t.wallet??null}),t.interactionToken?(window.sessionStorage.setItem(`frak-wallet-interaction-token`,t.interactionToken),a(t.interactionToken)):window.sessionStorage.removeItem(`frak-wallet-interaction-token`))}function x(e,{metadata:t,login:n}){return S(e,{steps:{login:n??{}},metadata:t})}function S(e,t){function n(n){return S(e,{...t,steps:{...t.steps,sendTransaction:n}})}function i(n){return S(e,{...t,steps:{...t.steps,final:{...n,action:{key:`reward`}}}})}function a(n,r){return S(e,{...t,steps:{...t.steps,final:{...r,action:{key:`sharing`,options:n}}}})}async function o(n,i){return n&&(t.metadata=n(t.metadata??{})),await r(e,t,i)}return{params:t,sendTx:n,reward:i,sharing:a,display:o}}async function C(e,{tx:t,metadata:n}){return(await r(e,{metadata:n,steps:{login:{},sendTransaction:{tx:t}}})).sendTransaction}function w(e=96){let t=Math.ceil(e/2),n=``;if(typeof crypto<`u`&&crypto.getRandomValues){let e=new Uint8Array(t);crypto.getRandomValues(e);for(let t=0;t<e.length;t++)n+=e[t].toString(16).padStart(2,`0`)}else for(let e=0;e<t;e++)n+=(Math.random()*256|0).toString(16).padStart(2,`0`);return n.substring(0,e)}async function T(e,{siwe:t,metadata:n}){let i=e.config?.domain??window.location.host,a=t?.statement??`I confirm that I want to use my Frak wallet on: ${e.config.metadata.name}`;return(await r(e,{metadata:n,steps:{login:{},siweAuthenticate:{siwe:{...t,statement:a,nonce:t?.nonce??w(),uri:t?.uri??`https://${i}`,version:t?.version??`1`,domain:i}}}})).siweAuthenticate}Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return n}});