@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.
- package/cdn/bundle.js +1 -1
- package/dist/actions-D5GbNcvt.cjs +1 -0
- package/dist/actions-DE2dHqtp.js +1 -0
- package/dist/actions.cjs +1 -1
- package/dist/actions.d.cts +1 -1
- package/dist/actions.d.ts +1 -1
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -1
- package/dist/bundle.d.cts +2 -2
- package/dist/bundle.d.ts +2 -2
- package/dist/bundle.js +1 -1
- package/dist/frakContext-Bl17GGa3.cjs +1 -0
- package/dist/frakContext-DmI5CaSw.js +1 -0
- package/dist/{index-B5_aKV0q.d.ts → index-Cm1PNC9z.d.ts} +40 -5
- package/dist/{index-C135c5Ul.d.cts → index-Cumn_FNI.d.cts} +40 -5
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/src-5vpUuhfY.js +1 -0
- package/dist/src-BQsjMpvA.cjs +1 -0
- package/package.json +2 -4
- package/src/actions/referral/processReferral.test.ts +4 -4
- package/src/actions/referral/processReferral.ts +4 -4
- package/src/clients/createIFrameFrakClient.ts +7 -7
- package/src/context/frakContext.test.ts +55 -0
- package/src/context/frakContext.ts +13 -2
- package/src/index.ts +2 -0
- package/src/utils/analytics/events/lifecycle.ts +1 -1
- package/src/utils/analytics/events/referral.ts +3 -3
- package/src/utils/iframe/iframeHelper.test.ts +0 -39
- package/src/utils/iframe/iframeHelper.ts +2 -31
- package/src/utils/index.ts +5 -0
- package/src/utils/url/queryParams.test.ts +85 -0
- package/src/utils/url/queryParams.ts +57 -0
- package/dist/actions-BZhtIRpB.cjs +0 -1
- package/dist/actions-D5Lt3pG1.js +0 -1
- package/dist/frakContext-Cfp7sSid.cjs +0 -1
- package/dist/frakContext-UEOk9Bk4.js +0 -1
- package/dist/src-CB7gkP3D.js +0 -1
- 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
|
|
3
|
-
export {
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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 (!("
|
|
156
|
+
if (!("sdkVersion" in payload.properties)) {
|
|
157
157
|
payload.properties = {
|
|
158
158
|
...payload.properties,
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
169
|
-
|
|
168
|
+
sdkVersion: process.env.SDK_VERSION,
|
|
169
|
+
userAnonymousClientId: getClientId(),
|
|
170
170
|
});
|
|
171
171
|
openPanel.init();
|
|
172
172
|
openPanel.track("sdk_initialized", {
|
|
173
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export type SdkReferralEventMap = {
|
|
2
2
|
user_referred_started: {
|
|
3
3
|
referrer?: string;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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 =
|
|
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
|
package/src/utils/index.ts
CHANGED
|
@@ -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}});
|