@frak-labs/core-sdk 0.1.1-beta.f2691749 → 0.2.0-beta.514ef378

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 (50) hide show
  1. package/cdn/bundle.js +3 -3
  2. package/dist/actions.cjs +1 -1
  3. package/dist/actions.d.cts +3 -3
  4. package/dist/actions.d.ts +3 -3
  5. package/dist/actions.js +1 -1
  6. package/dist/bundle.cjs +1 -1
  7. package/dist/bundle.d.cts +4 -4
  8. package/dist/bundle.d.ts +4 -4
  9. package/dist/bundle.js +1 -1
  10. package/dist/{computeLegacyProductId-CSSiQO4V.d.ts → computeLegacyProductId-BkyJ4rEY.d.ts} +3 -3
  11. package/dist/{computeLegacyProductId-fl_Sx8v5.d.cts → computeLegacyProductId-Raks6FXg.d.cts} +3 -3
  12. package/dist/index.cjs +1 -1
  13. package/dist/index.d.cts +2 -2
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +1 -1
  16. package/dist/{openSso-CC1-loUk.d.cts → openSso-BCJGchIb.d.cts} +10 -7
  17. package/dist/{openSso-tkqaDQLV.d.ts → openSso-DG-_9CED.d.ts} +10 -7
  18. package/dist/setupClient-BEiAE56h.js +13 -0
  19. package/dist/setupClient-Ls3vKSlH.cjs +13 -0
  20. package/dist/{siweAuthenticate-CRMIfmna.d.ts → siweAuthenticate-BH7Dn7nZ.d.cts} +34 -4
  21. package/dist/siweAuthenticate-BJHbtty4.js +1 -0
  22. package/dist/{siweAuthenticate-BPqJTadR.d.cts → siweAuthenticate-Btem4QHs.d.ts} +34 -4
  23. package/dist/siweAuthenticate-Cwj3HP0m.cjs +1 -0
  24. package/dist/trackEvent-M2RLTQ2p.js +1 -0
  25. package/dist/trackEvent-T_R9ER2S.cjs +1 -0
  26. package/package.json +3 -3
  27. package/src/actions/ensureIdentity.ts +68 -0
  28. package/src/actions/index.ts +1 -0
  29. package/src/actions/openSso.ts +2 -0
  30. package/src/actions/sendInteraction.ts +2 -1
  31. package/src/actions/trackPurchaseStatus.test.ts +354 -141
  32. package/src/actions/trackPurchaseStatus.ts +45 -11
  33. package/src/actions/watchWalletStatus.ts +2 -2
  34. package/src/clients/createIFrameFrakClient.ts +3 -1
  35. package/src/clients/transports/iframeLifecycleManager.test.ts +2 -2
  36. package/src/clients/transports/iframeLifecycleManager.ts +50 -3
  37. package/src/types/lifecycle/client.ts +5 -5
  38. package/src/types/rpc.ts +6 -2
  39. package/src/utils/clientId.ts +3 -0
  40. package/src/utils/iframeHelper.test.ts +18 -5
  41. package/src/utils/iframeHelper.ts +4 -1
  42. package/src/utils/merchantId.test.ts +90 -1
  43. package/src/utils/merchantId.ts +22 -1
  44. package/src/utils/sso.ts +4 -5
  45. package/dist/setupClient-B-LPXmJS.js +0 -13
  46. package/dist/setupClient-CksNcHQl.cjs +0 -13
  47. package/dist/siweAuthenticate-B_Z2OZmj.cjs +0 -1
  48. package/dist/siweAuthenticate-CQ4OfPuA.js +0 -1
  49. package/dist/trackEvent-CGIryq5h.cjs +0 -1
  50. package/dist/trackEvent-YfUh4jrx.js +0 -1
@@ -1,4 +1,4 @@
1
- import { A as SiweAuthenticateReturnType, D as SendTransactionReturnType, E as SendTransactionModalStepType, F as PrepareSsoParamsType, I as PrepareSsoReturnType, M as LoginModalStepType, R as FinalActionType, S as ModalRpcMetadata, T as ModalStepTypes, a as FrakClient, d as GetMerchantInformationReturnType, g as DisplayEmbeddedWalletResultType, h as DisplayEmbeddedWalletParamsType, i as FrakContext, j as SiweAuthenticationParams, l as WalletStatusReturnType, m as SendInteractionParamsType, w as ModalRpcStepsResultType, x as DisplayModalParamsType, z as FinalModalStepType } from "./openSso-tkqaDQLV.js";
1
+ import { A as SiweAuthenticateReturnType, D as SendTransactionReturnType, E as SendTransactionModalStepType, F as PrepareSsoParamsType, I as PrepareSsoReturnType, M as LoginModalStepType, R as FinalActionType, S as ModalRpcMetadata, T as ModalStepTypes, a as FrakClient, d as GetMerchantInformationReturnType, g as DisplayEmbeddedWalletResultType, h as DisplayEmbeddedWalletParamsType, i as FrakContext, j as SiweAuthenticationParams, l as WalletStatusReturnType, m as SendInteractionParamsType, w as ModalRpcStepsResultType, x as DisplayModalParamsType, z as FinalModalStepType } from "./openSso-BCJGchIb.cjs";
2
2
 
3
3
  //#region src/actions/displayEmbeddedWallet.d.ts
4
4
  /**
@@ -117,6 +117,31 @@ declare function displayModal<T extends ModalStepTypes[] = ModalStepTypes[]>(cli
117
117
  metadata
118
118
  }: DisplayModalParamsType<T>): Promise<ModalRpcStepsResultType<T>>;
119
119
  //#endregion
120
+ //#region src/actions/ensureIdentity.d.ts
121
+ /**
122
+ * Ensure the current wallet ↔ clientId link exists on the backend.
123
+ *
124
+ * Called automatically by {@link watchWalletStatus} when a connected wallet
125
+ * status is received. Acts as a failsafe: if the primary merge (SSO, pairing,
126
+ * login/register) missed or silently failed, this ensures the link is
127
+ * eventually established.
128
+ *
129
+ * The call is:
130
+ * - **Idempotent** — if already linked, backend returns immediately
131
+ * - **Deduplicated** — only fires once per browser session per merchant
132
+ * - **Fire-and-forget** — errors are logged but never thrown
133
+ *
134
+ * @param interactionToken - The SDK JWT from wallet status (x-wallet-sdk-auth)
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * // Usually called automatically via watchWalletStatus side effect.
139
+ * // Can also be called manually if needed:
140
+ * await ensureIdentity("eyJhbGciOi...");
141
+ * ```
142
+ */
143
+ declare function ensureIdentity(interactionToken: string): Promise<void>;
144
+ //#endregion
120
145
  //#region src/actions/getMerchantInformation.d.ts
121
146
  /**
122
147
  * Fetch the current merchant information (name, rewards, tiers) from the wallet iframe
@@ -284,6 +309,7 @@ declare function sendInteraction(client: FrakClient, params: SendInteractionPara
284
309
  * @param args.customerId - The customer id that made the purchase (on your side)
285
310
  * @param args.orderId - The order id of the purchase (on your side)
286
311
  * @param args.token - The token of the purchase
312
+ * @param args.merchantId - Optional explicit merchant id to use for the tracking request
287
313
  *
288
314
  * @description This function will send a request to the backend to listen for the purchase status.
289
315
  *
@@ -293,19 +319,23 @@ declare function sendInteraction(client: FrakClient, params: SendInteractionPara
293
319
  * customerId: checkout.order.customer.id,
294
320
  * orderId: checkout.order.id,
295
321
  * token: checkout.token,
322
+ * merchantId: "your-merchant-id",
296
323
  * };
297
324
  *
298
325
  * await trackPurchaseStatus(payload);
299
326
  * }
300
327
  *
301
328
  * @remarks
302
- * - The `trackPurchaseStatus` function requires the `frak-wallet-interaction-token` stored in the session storage to authenticate the request.
303
- * - This function will print a warning if used in a non-browser environment or if the wallet interaction token is not available.
329
+ * - Merchant id is resolved in this order: explicit `args.merchantId`, `frak-merchant-id` from session storage, then `fetchMerchantId()`.
330
+ * - This function supports anonymous users and will use the `x-frak-client-id` header when available.
331
+ * - At least one identity source must exist (`frak-wallet-interaction-token` or `x-frak-client-id`), otherwise the tracking request is skipped.
332
+ * - This function will print a warning if used in a non-browser environment or if no identity / merchant id can be resolved.
304
333
  */
305
334
  declare function trackPurchaseStatus(args: {
306
335
  customerId: string | number;
307
336
  orderId: string | number;
308
337
  token: string;
338
+ merchantId?: string;
309
339
  }): Promise<void>;
310
340
  //#endregion
311
341
  //#region src/actions/watchWalletStatus.d.ts
@@ -503,4 +533,4 @@ declare function siweAuthenticate(client: FrakClient, {
503
533
  metadata
504
534
  }: SiweAuthenticateModalParams): Promise<SiweAuthenticateReturnType>;
505
535
  //#endregion
506
- export { displayEmbeddedWallet as _, ModalBuilder as a, watchWalletStatus as c, referralInteraction as d, ProcessReferralOptions as f, displayModal as g, getMerchantInformation as h, sendTransaction as i, trackPurchaseStatus as l, prepareSso as m, siweAuthenticate as n, ModalStepBuilder as o, processReferral as p, SendTransactionParams as r, modalBuilder as s, SiweAuthenticateModalParams as t, sendInteraction as u };
536
+ export { displayModal as _, ModalBuilder as a, watchWalletStatus as c, referralInteraction as d, ProcessReferralOptions as f, ensureIdentity as g, getMerchantInformation as h, sendTransaction as i, trackPurchaseStatus as l, prepareSso as m, siweAuthenticate as n, ModalStepBuilder as o, processReferral as p, SendTransactionParams as r, modalBuilder as s, SiweAuthenticateModalParams as t, sendInteraction as u, displayEmbeddedWallet as v };
@@ -0,0 +1 @@
1
+ import{a as e,h as t,o as n,r,t as i}from"./trackEvent-M2RLTQ2p.js";import{isAddressEqual as a}from"viem";import{Deferred as o,FrakRpcError as s,RpcErrorCodes as c}from"@frak-labs/frame-connector";import{generateSiweNonce as l}from"viem/siwe";async function u(e,t){return await e.request({method:`frak_displayEmbeddedWallet`,params:[t,e.config.metadata]})}async function d(e,{steps:t,metadata:n}){return await e.request({method:`frak_displayModal`,params:[t,n,e.config.metadata]})}async function f(e){if(typeof window>`u`)return;let i=t();if(!i)return;let a=await r();if(!a)return;let o=`frak-identity-ensured-${a}`;if(!window.sessionStorage.getItem(o))try{let t=n();(await fetch(`${t}/user/identity/ensure`,{method:`POST`,headers:{Accept:`application/json`,"Content-Type":`application/json`,"x-wallet-sdk-auth":e,"x-frak-client-id":i},body:JSON.stringify({merchantId:a})})).ok&&window.sessionStorage.setItem(o,`1`)}catch{}}async function p(e){return await e.request({method:`frak_getMerchantInformation`})}async function m(e,t){let{metadata:n,customizations:r}=e.config;return await e.request({method:`frak_prepareSso`,params:[t,n.name,r?.css]})}async function h(e,n){try{await e.request({method:`frak_sendInteraction`,params:[n,{clientId:t()}]})}catch{console.warn(`[Frak SDK] Failed to send interaction:`,n.type)}}async function g(t,{walletStatus:n,frakContext:r,modalConfig:a,options:o}){if(!r?.r)return`no-referrer`;i(t,`user_referred_started`,{properties:{referrer:r?.r,walletStatus:n?.key}}),h(t,{type:`arrival`,referrerWallet:r.r,landingUrl:typeof window<`u`?window.location.href:void 0});let c=!1;async function l(){if(!c)return c=!0,v(t,{modalConfig:{...a,loggedIn:{action:{key:`referred`}}},walletStatus:n})}try{let{status:a,currentWallet:s}=await _({initialWalletStatus:n,getFreshWalletStatus:l,frakContext:r});return e.replaceUrl({url:window.location?.href,context:o?.alwaysAppendUrl?{r:s}:null}),i(t,`user_referred_completed`,{properties:{status:a,referrer:r?.r,wallet:s}}),a}catch(a){return console.log(`Error processing referral`,{error:a}),i(t,`user_referred_error`,{properties:{referrer:r?.r,error:a instanceof s?`[${a.code}] ${a.name} - ${a.message}`:a instanceof Error?a.message:`undefined`}}),e.replaceUrl({url:window.location?.href,context:o?.alwaysAppendUrl?{r:n?.wallet}:null}),y(a)}}async function _({initialWalletStatus:e,getFreshWalletStatus:t,frakContext:n}){let r=e?.wallet;return r||=await t(),r&&a(n.r,r)?{status:`self-referral`,currentWallet:r}:{status:`success`,currentWallet:r}}async function v(e,{modalConfig:t,walletStatus:n}){return n?.key===`connected`?n.wallet??void 0:(await u(e,t??{}))?.wallet??void 0}function y(e){if(e instanceof s)switch(e.code){case c.walletNotConnected:return`no-wallet`;default:return`error`}return`error`}async function b(t,{modalConfig:n,options:r}={}){let i=e.parse({url:window.location.href}),a=await S(t);try{return await g(t,{walletStatus:a,frakContext:i,modalConfig:n,options:r})}catch(e){console.warn(`Error processing referral`,{error:e})}}async function x(e){if(typeof window>`u`){console.warn(`[Frak] No window found, can't track purchase`);return}let i=window.sessionStorage.getItem(`frak-wallet-interaction-token`),a=t();if(!i&&!a){console.warn(`[Frak] No identity found, skipping purchase check`);return}let o=window.sessionStorage.getItem(`frak-merchant-id`),s=e.merchantId??o??await r();if(!s){console.warn(`[Frak] No merchant id found, skipping purchase check`);return}let c={Accept:`application/json`,"Content-Type":`application/json`};i&&(c[`x-wallet-sdk-auth`]=i),a&&(c[`x-frak-client-id`]=a);let l=n();await fetch(`${l}/user/track/purchase`,{method:`POST`,headers:c,body:JSON.stringify({customerId:e.customerId,orderId:e.orderId,token:e.token,merchantId:s})})}function S(e,t){if(!t)return e.request({method:`frak_listenToWalletStatus`}).then(t=>(C(e,t),t));let n=new o,r=!1;return e.listenerRequest({method:`frak_listenToWalletStatus`},i=>{C(e,i),t(i),r||=(n.resolve(i),!0)}),n.promise}function C(e,t){typeof window>`u`||(e.openPanel?.setGlobalProperties({wallet:t.wallet??null}),t.interactionToken?(window.sessionStorage.setItem(`frak-wallet-interaction-token`,t.interactionToken),f(t.interactionToken)):window.sessionStorage.removeItem(`frak-wallet-interaction-token`))}function w(e,{metadata:t,login:n}){return T(e,{steps:{login:n??{}},metadata:t})}function T(e,t){function n(n){return T(e,{...t,steps:{...t.steps,sendTransaction:n}})}function r(n){return T(e,{...t,steps:{...t.steps,final:{...n,action:{key:`reward`}}}})}function i(n,r){return T(e,{...t,steps:{...t.steps,final:{...r,action:{key:`sharing`,options:n}}}})}async function a(n){return n&&(t.metadata=n(t.metadata??{})),await d(e,t)}return{params:t,sendTx:n,reward:r,sharing:i,display:a}}async function E(e,{tx:t,metadata:n}){return(await d(e,{metadata:n,steps:{login:{},sendTransaction:{tx:t}}})).sendTransaction}async function D(e,{siwe:t,metadata:n}){let r=e.config?.domain??window.location.host,i=t?.statement??`I confirm that I want to use my Frak wallet on: ${e.config.metadata.name}`;return(await d(e,{metadata:n,steps:{login:{},siweAuthenticate:{siwe:{...t,statement:i,nonce:t?.nonce??l(),uri:t?.uri??`https://${r}`,version:t?.version??`1`,domain:r}}}})).siweAuthenticate}export{x as a,h as c,f as d,d as f,S as i,m as l,E as n,b as o,u as p,w as r,g as s,D as t,p as u};
@@ -1,4 +1,4 @@
1
- import { A as SiweAuthenticateReturnType, D as SendTransactionReturnType, E as SendTransactionModalStepType, F as PrepareSsoParamsType, I as PrepareSsoReturnType, M as LoginModalStepType, R as FinalActionType, S as ModalRpcMetadata, T as ModalStepTypes, a as FrakClient, d as GetMerchantInformationReturnType, g as DisplayEmbeddedWalletResultType, h as DisplayEmbeddedWalletParamsType, i as FrakContext, j as SiweAuthenticationParams, l as WalletStatusReturnType, m as SendInteractionParamsType, w as ModalRpcStepsResultType, x as DisplayModalParamsType, z as FinalModalStepType } from "./openSso-CC1-loUk.cjs";
1
+ import { A as SiweAuthenticateReturnType, D as SendTransactionReturnType, E as SendTransactionModalStepType, F as PrepareSsoParamsType, I as PrepareSsoReturnType, M as LoginModalStepType, R as FinalActionType, S as ModalRpcMetadata, T as ModalStepTypes, a as FrakClient, d as GetMerchantInformationReturnType, g as DisplayEmbeddedWalletResultType, h as DisplayEmbeddedWalletParamsType, i as FrakContext, j as SiweAuthenticationParams, l as WalletStatusReturnType, m as SendInteractionParamsType, w as ModalRpcStepsResultType, x as DisplayModalParamsType, z as FinalModalStepType } from "./openSso-DG-_9CED.js";
2
2
 
3
3
  //#region src/actions/displayEmbeddedWallet.d.ts
4
4
  /**
@@ -117,6 +117,31 @@ declare function displayModal<T extends ModalStepTypes[] = ModalStepTypes[]>(cli
117
117
  metadata
118
118
  }: DisplayModalParamsType<T>): Promise<ModalRpcStepsResultType<T>>;
119
119
  //#endregion
120
+ //#region src/actions/ensureIdentity.d.ts
121
+ /**
122
+ * Ensure the current wallet ↔ clientId link exists on the backend.
123
+ *
124
+ * Called automatically by {@link watchWalletStatus} when a connected wallet
125
+ * status is received. Acts as a failsafe: if the primary merge (SSO, pairing,
126
+ * login/register) missed or silently failed, this ensures the link is
127
+ * eventually established.
128
+ *
129
+ * The call is:
130
+ * - **Idempotent** — if already linked, backend returns immediately
131
+ * - **Deduplicated** — only fires once per browser session per merchant
132
+ * - **Fire-and-forget** — errors are logged but never thrown
133
+ *
134
+ * @param interactionToken - The SDK JWT from wallet status (x-wallet-sdk-auth)
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * // Usually called automatically via watchWalletStatus side effect.
139
+ * // Can also be called manually if needed:
140
+ * await ensureIdentity("eyJhbGciOi...");
141
+ * ```
142
+ */
143
+ declare function ensureIdentity(interactionToken: string): Promise<void>;
144
+ //#endregion
120
145
  //#region src/actions/getMerchantInformation.d.ts
121
146
  /**
122
147
  * Fetch the current merchant information (name, rewards, tiers) from the wallet iframe
@@ -284,6 +309,7 @@ declare function sendInteraction(client: FrakClient, params: SendInteractionPara
284
309
  * @param args.customerId - The customer id that made the purchase (on your side)
285
310
  * @param args.orderId - The order id of the purchase (on your side)
286
311
  * @param args.token - The token of the purchase
312
+ * @param args.merchantId - Optional explicit merchant id to use for the tracking request
287
313
  *
288
314
  * @description This function will send a request to the backend to listen for the purchase status.
289
315
  *
@@ -293,19 +319,23 @@ declare function sendInteraction(client: FrakClient, params: SendInteractionPara
293
319
  * customerId: checkout.order.customer.id,
294
320
  * orderId: checkout.order.id,
295
321
  * token: checkout.token,
322
+ * merchantId: "your-merchant-id",
296
323
  * };
297
324
  *
298
325
  * await trackPurchaseStatus(payload);
299
326
  * }
300
327
  *
301
328
  * @remarks
302
- * - The `trackPurchaseStatus` function requires the `frak-wallet-interaction-token` stored in the session storage to authenticate the request.
303
- * - This function will print a warning if used in a non-browser environment or if the wallet interaction token is not available.
329
+ * - Merchant id is resolved in this order: explicit `args.merchantId`, `frak-merchant-id` from session storage, then `fetchMerchantId()`.
330
+ * - This function supports anonymous users and will use the `x-frak-client-id` header when available.
331
+ * - At least one identity source must exist (`frak-wallet-interaction-token` or `x-frak-client-id`), otherwise the tracking request is skipped.
332
+ * - This function will print a warning if used in a non-browser environment or if no identity / merchant id can be resolved.
304
333
  */
305
334
  declare function trackPurchaseStatus(args: {
306
335
  customerId: string | number;
307
336
  orderId: string | number;
308
337
  token: string;
338
+ merchantId?: string;
309
339
  }): Promise<void>;
310
340
  //#endregion
311
341
  //#region src/actions/watchWalletStatus.d.ts
@@ -503,4 +533,4 @@ declare function siweAuthenticate(client: FrakClient, {
503
533
  metadata
504
534
  }: SiweAuthenticateModalParams): Promise<SiweAuthenticateReturnType>;
505
535
  //#endregion
506
- export { displayEmbeddedWallet as _, ModalBuilder as a, watchWalletStatus as c, referralInteraction as d, ProcessReferralOptions as f, displayModal as g, getMerchantInformation as h, sendTransaction as i, trackPurchaseStatus as l, prepareSso as m, siweAuthenticate as n, ModalStepBuilder as o, processReferral as p, SendTransactionParams as r, modalBuilder as s, SiweAuthenticateModalParams as t, sendInteraction as u };
536
+ export { displayModal as _, ModalBuilder as a, watchWalletStatus as c, referralInteraction as d, ProcessReferralOptions as f, ensureIdentity as g, getMerchantInformation as h, sendTransaction as i, trackPurchaseStatus as l, prepareSso as m, siweAuthenticate as n, ModalStepBuilder as o, processReferral as p, SendTransactionParams as r, modalBuilder as s, SiweAuthenticateModalParams as t, sendInteraction as u, displayEmbeddedWallet as v };
@@ -0,0 +1 @@
1
+ const e=require(`./trackEvent-T_R9ER2S.cjs`);let t=require(`viem`),n=require(`@frak-labs/frame-connector`),r=require(`viem/siwe`);async function i(e,t){return await e.request({method:`frak_displayEmbeddedWallet`,params:[t,e.config.metadata]})}async function a(e,{steps:t,metadata:n}){return await e.request({method:`frak_displayModal`,params:[t,n,e.config.metadata]})}async function o(t){if(typeof window>`u`)return;let n=e.h();if(!n)return;let r=await e.r();if(!r)return;let i=`frak-identity-ensured-${r}`;if(!window.sessionStorage.getItem(i))try{let a=e.o();(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 s(e){return await e.request({method:`frak_getMerchantInformation`})}async function c(e,t){let{metadata:n,customizations:r}=e.config;return await e.request({method:`frak_prepareSso`,params:[t,n.name,r?.css]})}async function l(t,n){try{await t.request({method:`frak_sendInteraction`,params:[n,{clientId:e.h()}]})}catch{console.warn(`[Frak SDK] Failed to send interaction:`,n.type)}}async function u(t,{walletStatus:r,frakContext:i,modalConfig:a,options:o}){if(!i?.r)return`no-referrer`;e.t(t,`user_referred_started`,{properties:{referrer:i?.r,walletStatus:r?.key}}),l(t,{type:`arrival`,referrerWallet:i.r,landingUrl:typeof window<`u`?window.location.href:void 0});let s=!1;async function c(){if(!s)return s=!0,f(t,{modalConfig:{...a,loggedIn:{action:{key:`referred`}}},walletStatus:r})}try{let{status:n,currentWallet:a}=await d({initialWalletStatus:r,getFreshWalletStatus:c,frakContext:i});return e.a.replaceUrl({url:window.location?.href,context:o?.alwaysAppendUrl?{r:a}:null}),e.t(t,`user_referred_completed`,{properties:{status:n,referrer:i?.r,wallet:a}}),n}catch(a){return console.log(`Error processing referral`,{error:a}),e.t(t,`user_referred_error`,{properties:{referrer:i?.r,error:a instanceof n.FrakRpcError?`[${a.code}] ${a.name} - ${a.message}`:a instanceof Error?a.message:`undefined`}}),e.a.replaceUrl({url:window.location?.href,context:o?.alwaysAppendUrl?{r:r?.wallet}:null}),p(a)}}async function d({initialWalletStatus:e,getFreshWalletStatus:n,frakContext:r}){let i=e?.wallet;return i||=await n(),i&&(0,t.isAddressEqual)(r.r,i)?{status:`self-referral`,currentWallet:i}:{status:`success`,currentWallet:i}}async function f(e,{modalConfig:t,walletStatus:n}){return n?.key===`connected`?n.wallet??void 0:(await i(e,t??{}))?.wallet??void 0}function p(e){if(e instanceof n.FrakRpcError)switch(e.code){case n.RpcErrorCodes.walletNotConnected:return`no-wallet`;default:return`error`}return`error`}async function m(t,{modalConfig:n,options:r}={}){let i=e.a.parse({url:window.location.href}),a=await g(t);try{return await u(t,{walletStatus:a,frakContext:i,modalConfig:n,options:r})}catch(e){console.warn(`Error processing referral`,{error:e})}}async function h(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.h();if(!n&&!r){console.warn(`[Frak] No identity found, skipping purchase check`);return}let i=window.sessionStorage.getItem(`frak-merchant-id`),a=t.merchantId??i??await e.r();if(!a){console.warn(`[Frak] No merchant id found, skipping purchase check`);return}let o={Accept:`application/json`,"Content-Type":`application/json`};n&&(o[`x-wallet-sdk-auth`]=n),r&&(o[`x-frak-client-id`]=r);let s=e.o();await fetch(`${s}/user/track/purchase`,{method:`POST`,headers:o,body:JSON.stringify({customerId:t.customerId,orderId:t.orderId,token:t.token,merchantId:a})})}function g(e,t){if(!t)return e.request({method:`frak_listenToWalletStatus`}).then(t=>(_(e,t),t));let r=new n.Deferred,i=!1;return e.listenerRequest({method:`frak_listenToWalletStatus`},n=>{_(e,n),t(n),i||=(r.resolve(n),!0)}),r.promise}function _(e,t){typeof window>`u`||(e.openPanel?.setGlobalProperties({wallet:t.wallet??null}),t.interactionToken?(window.sessionStorage.setItem(`frak-wallet-interaction-token`,t.interactionToken),o(t.interactionToken)):window.sessionStorage.removeItem(`frak-wallet-interaction-token`))}function v(e,{metadata:t,login:n}){return y(e,{steps:{login:n??{}},metadata:t})}function y(e,t){function n(n){return y(e,{...t,steps:{...t.steps,sendTransaction:n}})}function r(n){return y(e,{...t,steps:{...t.steps,final:{...n,action:{key:`reward`}}}})}function i(n,r){return y(e,{...t,steps:{...t.steps,final:{...r,action:{key:`sharing`,options:n}}}})}async function o(n){return n&&(t.metadata=n(t.metadata??{})),await a(e,t)}return{params:t,sendTx:n,reward:r,sharing:i,display:o}}async function b(e,{tx:t,metadata:n}){return(await a(e,{metadata:n,steps:{login:{},sendTransaction:{tx:t}}})).sendTransaction}async function x(e,{siwe:t,metadata:n}){let i=e.config?.domain??window.location.host,o=t?.statement??`I confirm that I want to use my Frak wallet on: ${e.config.metadata.name}`;return(await a(e,{metadata:n,steps:{login:{},siweAuthenticate:{siwe:{...t,statement:o,nonce:t?.nonce??(0,r.generateSiweNonce)(),uri:t?.uri??`https://${i}`,version:t?.version??`1`,domain:i}}}})).siweAuthenticate}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return s}});
@@ -0,0 +1 @@
1
+ import{bytesToHex as e,hexToBytes as t,keccak256 as n,toHex as r}from"viem";import{jsonEncode as i}from"@frak-labs/frame-connector";const a=`frak-client-id`;function o(){return typeof crypto<`u`&&crypto.randomUUID?crypto.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)})}function s(){if(typeof window>`u`||!window.localStorage)return console.warn(`[Frak SDK] No Window / localStorage available to save the clientId`),o();let e=localStorage.getItem(a);return e||(e=o(),localStorage.setItem(a,e)),e}function c({domain:e}={}){return n(r((e??window.location.host).replace(`www.`,``)))}function l(e){return btoa(Array.from(e,e=>String.fromCharCode(e)).join(``)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function u(e){let t=e.length%4;return Uint8Array.from(atob(e.replace(/-/g,`+`).replace(/_/g,`/`).padEnd(e.length+(t===0?0:4-t),`=`)),e=>e.charCodeAt(0))}function d(e){return l(i(e))}function f(e,t,n,r,i,a){let o=d(p({redirectUrl:t.redirectUrl,directExit:t.directExit,lang:t.lang,merchantId:n,metadata:{name:r,css:a,logoUrl:t.metadata?.logoUrl,homepageLink:t.metadata?.homepageLink},clientId:i})),s=new URL(e);return s.pathname=`/sso`,s.searchParams.set(`p`,o),s.toString()}function p(e){return{r:e.redirectUrl,cId:e.clientId,d:e.directExit,l:e.lang,m:e.merchantId,md:{n:e.metadata?.name,css:e.metadata?.css,l:e.metadata?.logoUrl,h:e.metadata?.homepageLink}}}const m=`menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800`,h=`frak-sso`;async function g(e,t){let{metadata:n,customizations:r,walletUrl:i}=e.config;if(t.openInSameWindow??!!t.redirectUrl)return await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]});let a=t.ssoPopupUrl??f(i??`https://wallet.frak.id`,t,c(),n.name,s(),r?.css),o=window.open(a,h,m);if(!o)throw Error(`Popup was blocked. Please allow popups for this site.`);return o.focus(),await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]})??{}}const _=`https://backend.frak.id`;function v(e){return e.includes(`localhost:3000`)||e.includes(`localhost:3010`)}function y(e){return v(e)?`http://localhost:3030`:e.includes(`wallet-dev.frak.id`)||e.includes(`wallet.gcp-dev.frak.id`)?`https://backend.gcp-dev.frak.id`:_}function b(e){if(e)return y(e);if(typeof window<`u`){let e=window.FrakSetup?.client?.config?.walletUrl;if(e)return y(e)}return _}const x=`fCtx`;function S(e){if(e?.r)try{return l(t(e.r))}catch(t){console.error(`Error compressing Frak context`,{e:t,context:e})}}function C(t){if(!(!t||t.length===0))try{return{r:e(u(t),{size:20})}}catch(e){console.error(`Error decompressing Frak context`,{e,context:t})}}function w({url:e}){if(!e)return null;let t=new URL(e).searchParams.get(x);return t?C(t):null}function T({url:e,context:t}){if(!e)return null;let n=w({url:e}),r=n?{...n,...t}:t;if(!r.r)return null;let i=S(r);if(!i)return null;let a=new URL(e);return a.searchParams.set(x,i),a.toString()}function E(e){let t=new URL(e);return t.searchParams.delete(x),t.toString()}function D({url:e,context:t}){if(!window.location?.href||typeof window>`u`){console.error(`No window found, can't update context`);return}let n=e??window.location.href,r;r=t===null?E(n):T({url:n,context:t}),r&&window.history.replaceState(null,``,r.toString())}const O={compress:S,decompress:C,parse:w,update:T,remove:E,replaceUrl:D},k=`frak-merchant-id`;let A,j;async function M(e,t){if(A)return A;if(typeof window<`u`){let e=window.sessionStorage.getItem(k);if(e)return A=e,e}if(j)return j;j=N(e,t);let n=await j;return j=void 0,n}async function N(e,t){let n=e??(typeof window<`u`?window.location.hostname:``);if(n)try{let e=b(t),r=await fetch(`${e}/user/merchant/resolve?domain=${encodeURIComponent(n)}`);if(!r.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${n}: ${r.status}`);return}let i=await r.json();return A=i.merchantId,typeof window<`u`&&window.sessionStorage.setItem(k,i.merchantId),A}catch(e){console.warn(`[Frak SDK] Failed to fetch merchantId:`,e);return}}function P(){A=void 0,j=void 0,typeof window<`u`&&window.sessionStorage.removeItem(k)}async function F(e,t){return e.metadata?.merchantId?e.metadata.merchantId:M(void 0,t)}function I(e,t,n={}){if(!e){console.debug(`[Frak] No client provided, skipping event tracking`);return}try{e.openPanel?.track(t,n)}catch(e){console.debug(`[Frak] Failed to track event:`,t,e)}}export{O as a,m as c,d,u as f,s as h,F as i,h as l,c as m,P as n,b as o,l as p,M as r,g as s,I as t,f as u};
@@ -0,0 +1 @@
1
+ let e=require(`viem`),t=require(`@frak-labs/frame-connector`);const n=`frak-client-id`;function r(){return typeof crypto<`u`&&crypto.randomUUID?crypto.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)})}function i(){if(typeof window>`u`||!window.localStorage)return console.warn(`[Frak SDK] No Window / localStorage available to save the clientId`),r();let e=localStorage.getItem(n);return e||(e=r(),localStorage.setItem(n,e)),e}function a({domain:t}={}){return(0,e.keccak256)((0,e.toHex)((t??window.location.host).replace(`www.`,``)))}function o(e){return btoa(Array.from(e,e=>String.fromCharCode(e)).join(``)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function s(e){let t=e.length%4;return Uint8Array.from(atob(e.replace(/-/g,`+`).replace(/_/g,`/`).padEnd(e.length+(t===0?0:4-t),`=`)),e=>e.charCodeAt(0))}function c(e){return o((0,t.jsonEncode)(e))}function l(e,t,n,r,i,a){let o=c(u({redirectUrl:t.redirectUrl,directExit:t.directExit,lang:t.lang,merchantId:n,metadata:{name:r,css:a,logoUrl:t.metadata?.logoUrl,homepageLink:t.metadata?.homepageLink},clientId:i})),s=new URL(e);return s.pathname=`/sso`,s.searchParams.set(`p`,o),s.toString()}function u(e){return{r:e.redirectUrl,cId:e.clientId,d:e.directExit,l:e.lang,m:e.merchantId,md:{n:e.metadata?.name,css:e.metadata?.css,l:e.metadata?.logoUrl,h:e.metadata?.homepageLink}}}const d=`menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800`,f=`frak-sso`;async function p(e,t){let{metadata:n,customizations:r,walletUrl:o}=e.config;if(t.openInSameWindow??!!t.redirectUrl)return await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]});let s=t.ssoPopupUrl??l(o??`https://wallet.frak.id`,t,a(),n.name,i(),r?.css),c=window.open(s,f,d);if(!c)throw Error(`Popup was blocked. Please allow popups for this site.`);return c.focus(),await e.request({method:`frak_openSso`,params:[t,n.name,r?.css]})??{}}const m=`https://backend.frak.id`;function h(e){return e.includes(`localhost:3000`)||e.includes(`localhost:3010`)}function g(e){return h(e)?`http://localhost:3030`:e.includes(`wallet-dev.frak.id`)||e.includes(`wallet.gcp-dev.frak.id`)?`https://backend.gcp-dev.frak.id`:m}function _(e){if(e)return g(e);if(typeof window<`u`){let e=window.FrakSetup?.client?.config?.walletUrl;if(e)return g(e)}return m}const v=`fCtx`;function y(t){if(t?.r)try{return o((0,e.hexToBytes)(t.r))}catch(e){console.error(`Error compressing Frak context`,{e,context:t})}}function b(t){if(!(!t||t.length===0))try{return{r:(0,e.bytesToHex)(s(t),{size:20})}}catch(e){console.error(`Error decompressing Frak context`,{e,context:t})}}function x({url:e}){if(!e)return null;let t=new URL(e).searchParams.get(v);return t?b(t):null}function S({url:e,context:t}){if(!e)return null;let n=x({url:e}),r=n?{...n,...t}:t;if(!r.r)return null;let i=y(r);if(!i)return null;let a=new URL(e);return a.searchParams.set(v,i),a.toString()}function C(e){let t=new URL(e);return t.searchParams.delete(v),t.toString()}function w({url:e,context:t}){if(!window.location?.href||typeof window>`u`){console.error(`No window found, can't update context`);return}let n=e??window.location.href,r;r=t===null?C(n):S({url:n,context:t}),r&&window.history.replaceState(null,``,r.toString())}const T={compress:y,decompress:b,parse:x,update:S,remove:C,replaceUrl:w},E=`frak-merchant-id`;let D,O;async function k(e,t){if(D)return D;if(typeof window<`u`){let e=window.sessionStorage.getItem(E);if(e)return D=e,e}if(O)return O;O=A(e,t);let n=await O;return O=void 0,n}async function A(e,t){let n=e??(typeof window<`u`?window.location.hostname:``);if(n)try{let e=_(t),r=await fetch(`${e}/user/merchant/resolve?domain=${encodeURIComponent(n)}`);if(!r.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${n}: ${r.status}`);return}let i=await r.json();return D=i.merchantId,typeof window<`u`&&window.sessionStorage.setItem(E,i.merchantId),D}catch(e){console.warn(`[Frak SDK] Failed to fetch merchantId:`,e);return}}function j(){D=void 0,O=void 0,typeof window<`u`&&window.sessionStorage.removeItem(E)}async function M(e,t){return e.metadata?.merchantId?e.metadata.merchantId:k(void 0,t)}function N(e,t,n={}){if(!e){console.debug(`[Frak] No client provided, skipping event tracking`);return}try{e.openPanel?.track(t,n)}catch(e){console.debug(`[Frak] Failed to track event:`,t,e)}}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return l}});
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "0.1.1-beta.f2691749",
14
+ "version": "0.2.0-beta.514ef378",
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",
@@ -80,7 +80,7 @@
80
80
  "build": "tsdown",
81
81
  "build:watch": "tsdown --watch",
82
82
  "check-exports": "attw --pack --profile node16 .",
83
- "typecheck": "tsc --noEmit",
83
+ "typecheck": "tsgo --noEmit",
84
84
  "test": "vitest",
85
85
  "test:ui": "vitest --ui",
86
86
  "test:coverage": "vitest --coverage",
@@ -91,7 +91,7 @@
91
91
  "viem": "^2.x"
92
92
  },
93
93
  "dependencies": {
94
- "@frak-labs/frame-connector": "0.1.0-beta.f2691749",
94
+ "@frak-labs/frame-connector": "0.2.0-beta.514ef378",
95
95
  "@openpanel/web": "^1.0.7"
96
96
  },
97
97
  "devDependencies": {
@@ -0,0 +1,68 @@
1
+ import { getBackendUrl } from "../utils/backendUrl";
2
+ import { getClientId } from "../utils/clientId";
3
+ import { fetchMerchantId } from "../utils/merchantId";
4
+
5
+ const ENSURE_STORAGE_PREFIX = "frak-identity-ensured-";
6
+
7
+ /**
8
+ * Ensure the current wallet ↔ clientId link exists on the backend.
9
+ *
10
+ * Called automatically by {@link watchWalletStatus} when a connected wallet
11
+ * status is received. Acts as a failsafe: if the primary merge (SSO, pairing,
12
+ * login/register) missed or silently failed, this ensures the link is
13
+ * eventually established.
14
+ *
15
+ * The call is:
16
+ * - **Idempotent** — if already linked, backend returns immediately
17
+ * - **Deduplicated** — only fires once per browser session per merchant
18
+ * - **Fire-and-forget** — errors are logged but never thrown
19
+ *
20
+ * @param interactionToken - The SDK JWT from wallet status (x-wallet-sdk-auth)
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // Usually called automatically via watchWalletStatus side effect.
25
+ * // Can also be called manually if needed:
26
+ * await ensureIdentity("eyJhbGciOi...");
27
+ * ```
28
+ */
29
+ export async function ensureIdentity(interactionToken: string): Promise<void> {
30
+ if (typeof window === "undefined") {
31
+ return;
32
+ }
33
+
34
+ const clientId = getClientId();
35
+ if (!clientId) {
36
+ return;
37
+ }
38
+
39
+ const merchantId = await fetchMerchantId();
40
+ if (!merchantId) {
41
+ return;
42
+ }
43
+
44
+ const storageKey = `${ENSURE_STORAGE_PREFIX}${merchantId}`;
45
+ if (window.sessionStorage.getItem(storageKey)) {
46
+ return;
47
+ }
48
+
49
+ try {
50
+ const backendUrl = getBackendUrl();
51
+ const response = await fetch(`${backendUrl}/user/identity/ensure`, {
52
+ method: "POST",
53
+ headers: {
54
+ Accept: "application/json",
55
+ "Content-Type": "application/json",
56
+ "x-wallet-sdk-auth": interactionToken,
57
+ "x-frak-client-id": clientId,
58
+ },
59
+ body: JSON.stringify({ merchantId }),
60
+ });
61
+
62
+ if (response.ok) {
63
+ window.sessionStorage.setItem(storageKey, "1");
64
+ }
65
+ } catch {
66
+ // Fire-and-forget — retry on next session
67
+ }
68
+ }
@@ -1,5 +1,6 @@
1
1
  export { displayEmbeddedWallet } from "./displayEmbeddedWallet";
2
2
  export { displayModal } from "./displayModal";
3
+ export { ensureIdentity } from "./ensureIdentity";
3
4
  export { getMerchantInformation } from "./getMerchantInformation";
4
5
  export { openSso } from "./openSso";
5
6
  export { prepareSso } from "./prepareSso";
@@ -3,6 +3,7 @@ import type {
3
3
  OpenSsoParamsType,
4
4
  OpenSsoReturnType,
5
5
  } from "../types";
6
+ import { getClientId } from "../utils/clientId";
6
7
  import { computeLegacyProductId } from "../utils/computeLegacyProductId";
7
8
  import { generateSsoUrl } from "../utils/sso";
8
9
 
@@ -93,6 +94,7 @@ export async function openSso(
93
94
  args,
94
95
  computeLegacyProductId(),
95
96
  metadata.name,
97
+ getClientId(),
96
98
  customizations?.css
97
99
  );
98
100
 
@@ -1,5 +1,6 @@
1
1
  import type { FrakClient } from "../types";
2
2
  import type { SendInteractionParamsType } from "../types/rpc/interaction";
3
+ import { getClientId } from "../utils/clientId";
3
4
 
4
5
  /**
5
6
  * Send an interaction to the backend via the listener RPC.
@@ -46,7 +47,7 @@ export async function sendInteraction(
46
47
  try {
47
48
  await client.request({
48
49
  method: "frak_sendInteraction",
49
- params: [params],
50
+ params: [params, { clientId: getClientId() }],
50
51
  });
51
52
  } catch {
52
53
  // Silent failure - fire-and-forget