@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,6 @@
1
1
  import { getBackendUrl } from "../utils/backendUrl";
2
+ import { getClientId } from "../utils/clientId";
3
+ import { fetchMerchantId } from "../utils/merchantId";
2
4
 
3
5
  /**
4
6
  * Function used to track the status of a purchase
@@ -7,6 +9,7 @@ import { getBackendUrl } from "../utils/backendUrl";
7
9
  * @param args.customerId - The customer id that made the purchase (on your side)
8
10
  * @param args.orderId - The order id of the purchase (on your side)
9
11
  * @param args.token - The token of the purchase
12
+ * @param args.merchantId - Optional explicit merchant id to use for the tracking request
10
13
  *
11
14
  * @description This function will send a request to the backend to listen for the purchase status.
12
15
  *
@@ -16,41 +19,72 @@ import { getBackendUrl } from "../utils/backendUrl";
16
19
  * customerId: checkout.order.customer.id,
17
20
  * orderId: checkout.order.id,
18
21
  * token: checkout.token,
22
+ * merchantId: "your-merchant-id",
19
23
  * };
20
24
  *
21
25
  * await trackPurchaseStatus(payload);
22
26
  * }
23
27
  *
24
28
  * @remarks
25
- * - The `trackPurchaseStatus` function requires the `frak-wallet-interaction-token` stored in the session storage to authenticate the request.
26
- * - This function will print a warning if used in a non-browser environment or if the wallet interaction token is not available.
29
+ * - Merchant id is resolved in this order: explicit `args.merchantId`, `frak-merchant-id` from session storage, then `fetchMerchantId()`.
30
+ * - This function supports anonymous users and will use the `x-frak-client-id` header when available.
31
+ * - At least one identity source must exist (`frak-wallet-interaction-token` or `x-frak-client-id`), otherwise the tracking request is skipped.
32
+ * - This function will print a warning if used in a non-browser environment or if no identity / merchant id can be resolved.
27
33
  */
28
34
  export async function trackPurchaseStatus(args: {
29
35
  customerId: string | number;
30
36
  orderId: string | number;
31
37
  token: string;
38
+ merchantId?: string;
32
39
  }) {
33
40
  if (typeof window === "undefined") {
34
41
  console.warn("[Frak] No window found, can't track purchase");
35
42
  return;
36
43
  }
44
+
37
45
  const interactionToken = window.sessionStorage.getItem(
38
46
  "frak-wallet-interaction-token"
39
47
  );
40
- if (!interactionToken) {
41
- console.warn("[Frak] No frak session found, skipping purchase check");
48
+
49
+ const clientId = getClientId();
50
+ if (!interactionToken && !clientId) {
51
+ console.warn("[Frak] No identity found, skipping purchase check");
52
+ return;
53
+ }
54
+
55
+ const merchantIdFromStorage =
56
+ window.sessionStorage.getItem("frak-merchant-id");
57
+ const merchantId =
58
+ args.merchantId ?? merchantIdFromStorage ?? (await fetchMerchantId());
59
+
60
+ if (!merchantId) {
61
+ console.warn("[Frak] No merchant id found, skipping purchase check");
42
62
  return;
43
63
  }
44
64
 
65
+ const headers: Record<string, string> = {
66
+ Accept: "application/json",
67
+ "Content-Type": "application/json",
68
+ };
69
+
70
+ if (interactionToken) {
71
+ headers["x-wallet-sdk-auth"] = interactionToken;
72
+ }
73
+
74
+ if (clientId) {
75
+ headers["x-frak-client-id"] = clientId;
76
+ }
77
+
45
78
  // Submit the listening request
46
79
  const backendUrl = getBackendUrl();
47
- await fetch(`${backendUrl}/interactions/listenForPurchase`, {
80
+ await fetch(`${backendUrl}/user/track/purchase`, {
48
81
  method: "POST",
49
- headers: {
50
- Accept: "application/json",
51
- "Content-Type": "application/json",
52
- "x-wallet-sdk-auth": interactionToken,
53
- },
54
- body: JSON.stringify(args),
82
+ headers,
83
+ body: JSON.stringify({
84
+ customerId: args.customerId,
85
+ orderId: args.orderId,
86
+ token: args.token,
87
+ merchantId,
88
+ }),
55
89
  });
56
90
  }
@@ -1,6 +1,7 @@
1
1
  import { Deferred } from "@frak-labs/frame-connector";
2
2
  import type { FrakClient } from "../types/client";
3
3
  import type { WalletStatusReturnType } from "../types/rpc/walletStatus";
4
+ import { ensureIdentity } from "./ensureIdentity";
4
5
 
5
6
  /**
6
7
  * Function used to watch the current frak wallet status
@@ -81,13 +82,12 @@ function walletStatusSideEffect(
81
82
  });
82
83
 
83
84
  if (status.interactionToken) {
84
- // If we got an interaction token, save it
85
85
  window.sessionStorage.setItem(
86
86
  "frak-wallet-interaction-token",
87
87
  status.interactionToken
88
88
  );
89
+ ensureIdentity(status.interactionToken);
89
90
  } else {
90
- // Otherwise, remove it
91
91
  window.sessionStorage.removeItem("frak-wallet-interaction-token");
92
92
  }
93
93
  }
@@ -9,9 +9,9 @@ import type { FrakLifecycleEvent } from "../types";
9
9
  import type { FrakClient } from "../types/client";
10
10
  import type { FrakWalletSdkConfig } from "../types/config";
11
11
  import type { IFrameRpcSchema } from "../types/rpc";
12
+ import { getClientId } from "../utils";
12
13
  import { BACKUP_KEY } from "../utils/constants";
13
14
  import { setupSsoUrlListener } from "../utils/ssoUrlListener";
14
-
15
15
  import { DebugInfoGatherer } from "./DebugInfo";
16
16
  import {
17
17
  createIFrameLifecycleManager,
@@ -142,6 +142,7 @@ export function createIFrameFrakClient({
142
142
  payload.properties = {
143
143
  ...payload.properties,
144
144
  sdkVersion: process.env.SDK_VERSION,
145
+ userAnonymousClientId: getClientId(),
145
146
  };
146
147
  }
147
148
 
@@ -150,6 +151,7 @@ export function createIFrameFrakClient({
150
151
  });
151
152
  openPanel.setGlobalProperties({
152
153
  sdkVersion: process.env.SDK_VERSION,
154
+ userAnonymousClientId: getClientId(),
153
155
  });
154
156
  openPanel.init();
155
157
  }
@@ -25,7 +25,7 @@ vi.mock("../../utils/iframeHelper", () => ({
25
25
  }));
26
26
 
27
27
  vi.mock("../../utils/clientId", () => ({
28
- getClientId: vi.fn(() => "mock-client-id-12345"),
28
+ getClientId: vi.fn(() => "mock-client-id"),
29
29
  }));
30
30
 
31
31
  vi.mock("../../utils/deepLinkWithFallback", () => ({
@@ -265,7 +265,7 @@ describe("createIFrameLifecycleManager", () => {
265
265
  data: {
266
266
  token: "handshake-token-123",
267
267
  currentUrl: "https://test.com",
268
- clientId: "mock-client-id-12345",
268
+ clientId: "mock-client-id",
269
269
  },
270
270
  },
271
271
  "https://wallet.frak.id"
@@ -8,6 +8,28 @@ import {
8
8
  } from "../../utils/deepLinkWithFallback";
9
9
  import { changeIframeVisibility } from "../../utils/iframeHelper";
10
10
 
11
+ /**
12
+ * Detect iOS in-app browsers (Instagram, Facebook) where server-side
13
+ * 302 redirects to custom URL schemes (x-safari-https://) are silently
14
+ * swallowed by WKWebView. Direct window.location.href assignment works.
15
+ */
16
+ const isIOSInAppBrowser = (() => {
17
+ if (typeof navigator === "undefined") return false;
18
+ const ua = navigator.userAgent;
19
+ // Standard iOS or iPadOS 13+ (reports as Macintosh with touch)
20
+ const isIOS =
21
+ /iPhone|iPad|iPod/i.test(ua) ||
22
+ (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1);
23
+ if (!isIOS) return false;
24
+ const lower = ua.toLowerCase();
25
+ return (
26
+ lower.includes("instagram") ||
27
+ lower.includes("fban") ||
28
+ lower.includes("fbav") ||
29
+ lower.includes("facebook")
30
+ );
31
+ })();
32
+
11
33
  /** @ignore */
12
34
  export type IframeLifecycleManager = {
13
35
  isConnected: Promise<boolean>;
@@ -47,9 +69,9 @@ function handleHandshake(
47
69
  data: {
48
70
  token,
49
71
  currentUrl: window.location.href,
50
- clientId: getClientId(),
51
72
  pendingMergeToken,
52
73
  configDomain,
74
+ clientId: getClientId(),
53
75
  },
54
76
  },
55
77
  targetOrigin
@@ -87,6 +109,27 @@ function computeRedirectUrl(
87
109
  }
88
110
  }
89
111
 
112
+ /**
113
+ * Redirect current page to Safari via x-safari-https:// scheme.
114
+ * Used on iOS in-app browsers where backend 302 → custom scheme fails.
115
+ */
116
+ function redirectToSafari(mergeToken?: string) {
117
+ const url = new URL(window.location.href);
118
+ if (mergeToken) {
119
+ url.searchParams.set("fmt", mergeToken);
120
+ }
121
+ const scheme =
122
+ url.protocol === "http:" ? "x-safari-http" : "x-safari-https";
123
+ window.location.href = `${scheme}://${url.host}${url.pathname}${url.search}${url.hash}`;
124
+ }
125
+
126
+ /**
127
+ * Check if this is a social/in-app-browser escape redirect (contains /common/social)
128
+ */
129
+ function isSocialRedirect(url: string): boolean {
130
+ return url.includes("/common/social");
131
+ }
132
+
90
133
  /**
91
134
  * Handle redirect with deep link fallback
92
135
  */
@@ -96,9 +139,8 @@ function handleRedirect(
96
139
  targetOrigin: string,
97
140
  mergeToken?: string
98
141
  ): void {
99
- const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
100
-
101
142
  if (isFrakDeepLink(baseRedirectUrl)) {
143
+ const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
102
144
  triggerDeepLinkWithFallback(finalUrl, {
103
145
  onFallback: () => {
104
146
  iframe.contentWindow?.postMessage(
@@ -110,7 +152,12 @@ function handleRedirect(
110
152
  );
111
153
  },
112
154
  });
155
+ } else if (isIOSInAppBrowser && isSocialRedirect(baseRedirectUrl)) {
156
+ // iOS WKWebView silently swallows 302 redirects to custom URL
157
+ // schemes — bypass the server redirect entirely
158
+ redirectToSafari(mergeToken);
113
159
  } else {
160
+ const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
114
161
  window.location.href = finalUrl;
115
162
  }
116
163
  }
@@ -38,17 +38,17 @@ type HandshakeResponse = {
38
38
  data: {
39
39
  token: string;
40
40
  currentUrl: string;
41
- /**
42
- * The anonymous client ID for identity tracking
43
- * Generated by SDK, stored in partner site's localStorage
44
- */
45
- clientId: string;
46
41
  /**
47
42
  * Pending merge token extracted from URL (?fmt= parameter)
48
43
  * When present, listener should execute identity merge in background
49
44
  * URL is cleaned after handshake response is sent
50
45
  */
51
46
  pendingMergeToken?: string;
47
+ /**
48
+ * Client ID for identity tracking (belt & suspenders fallback)
49
+ * Primary delivery is via iframe URL query param; handshake is backup for SSR
50
+ */
51
+ clientId?: string;
52
52
  /**
53
53
  * Explicit domain from SDK config (FrakWalletSdkConfig.domain)
54
54
  * When present, listener should prefer this over URL-derived domain
package/src/types/rpc.ts CHANGED
@@ -136,11 +136,15 @@ export type IFrameRpcSchema = [
136
136
  /**
137
137
  * Method to send interactions (arrival, sharing, custom events)
138
138
  * Fire-and-forget method - no return value expected
139
- * merchantId and clientId are resolved from context
139
+ * merchantId is resolved from context
140
+ * clientId is passed via metadata as safeguard against handshake race condition
140
141
  */
141
142
  {
142
143
  Method: "frak_sendInteraction";
143
- Parameters: [interaction: SendInteractionParamsType];
144
+ Parameters: [
145
+ interaction: SendInteractionParamsType,
146
+ metadata?: { clientId?: string },
147
+ ];
144
148
  ReturnType: undefined;
145
149
  },
146
150
  ];
@@ -28,6 +28,9 @@ function generateUUID(): string {
28
28
  export function getClientId(): string {
29
29
  if (typeof window === "undefined" || !window.localStorage) {
30
30
  // SSR or no localStorage - generate ephemeral ID
31
+ console.warn(
32
+ "[Frak SDK] No Window / localStorage available to save the clientId"
33
+ );
31
34
  return generateUUID();
32
35
  }
33
36
 
@@ -1,3 +1,9 @@
1
+ import { vi } from "vitest";
2
+
3
+ vi.mock("./clientId", () => ({
4
+ getClientId: vi.fn(() => "mock-client-id-for-test"),
5
+ }));
6
+
1
7
  /**
2
8
  * Tests for iframe helper utilities
3
9
  * Tests iframe creation, visibility management, and finder functions
@@ -9,7 +15,6 @@ import {
9
15
  describe,
10
16
  expect,
11
17
  it,
12
- vi,
13
18
  } from "../../tests/vitest-fixtures";
14
19
  import type { FrakWalletSdkConfig } from "../types";
15
20
  import {
@@ -106,7 +111,9 @@ describe("iframeHelper", () => {
106
111
  it("should set iframe src to default wallet URL", async () => {
107
112
  await createIframe({});
108
113
 
109
- expect(mockIframe.src).toBe("https://wallet.frak.id/listener");
114
+ expect(mockIframe.src).toBe(
115
+ "https://wallet.frak.id/listener?clientId=mock-client-id-for-test"
116
+ );
110
117
  });
111
118
 
112
119
  it("should use config walletUrl when provided", async () => {
@@ -117,13 +124,17 @@ describe("iframeHelper", () => {
117
124
 
118
125
  await createIframe({ config });
119
126
 
120
- expect(mockIframe.src).toBe("https://custom-wallet.com/listener");
127
+ expect(mockIframe.src).toBe(
128
+ "https://custom-wallet.com/listener?clientId=mock-client-id-for-test"
129
+ );
121
130
  });
122
131
 
123
132
  it("should use deprecated walletBaseUrl when provided", async () => {
124
133
  await createIframe({ walletBaseUrl: "https://legacy-wallet.com" });
125
134
 
126
- expect(mockIframe.src).toBe("https://legacy-wallet.com/listener");
135
+ expect(mockIframe.src).toBe(
136
+ "https://legacy-wallet.com/listener?clientId=mock-client-id-for-test"
137
+ );
127
138
  });
128
139
 
129
140
  it("should prefer config.walletUrl over walletBaseUrl", async () => {
@@ -137,7 +148,9 @@ describe("iframeHelper", () => {
137
148
  config,
138
149
  });
139
150
 
140
- expect(mockIframe.src).toBe("https://new-wallet.com/listener");
151
+ expect(mockIframe.src).toBe(
152
+ "https://new-wallet.com/listener?clientId=mock-client-id-for-test"
153
+ );
141
154
  });
142
155
 
143
156
  it("should remove existing iframe before creating new one", async () => {
@@ -1,4 +1,5 @@
1
1
  import type { FrakWalletSdkConfig } from "../types";
2
+ import { getClientId } from "./clientId";
2
3
 
3
4
  /**
4
5
  * Base props for the iframe
@@ -55,13 +56,15 @@ export function createIframe({
55
56
  // Set src BEFORE appending to DOM to avoid about:blank load event
56
57
  const walletUrl =
57
58
  config?.walletUrl ?? walletBaseUrl ?? "https://wallet.frak.id";
58
- iframe.src = `${walletUrl}/listener`;
59
+ const clientId = getClientId();
60
+ iframe.src = `${walletUrl}/listener?clientId=${encodeURIComponent(clientId)}`;
59
61
 
60
62
  return new Promise((resolve) => {
61
63
  iframe.addEventListener("load", () => resolve(iframe));
62
64
  document.body.appendChild(iframe);
63
65
  });
64
66
  }
67
+
65
68
  /**
66
69
  * Change the visibility of the given iframe
67
70
  * @ignore
@@ -17,8 +17,9 @@ vi.mock("./backendUrl", () => ({
17
17
 
18
18
  describe("merchantId", () => {
19
19
  beforeEach(() => {
20
- // Clear cache before each test
20
+ // Clear cache before each test (also clears sessionStorage)
21
21
  clearMerchantIdCache();
22
+ window.sessionStorage.clear();
22
23
  // Clear all mocks
23
24
  vi.clearAllMocks();
24
25
  // Mock console methods to avoid noise in test output
@@ -263,6 +264,70 @@ describe("merchantId", () => {
263
264
  expect(result2).toBe("merchant-cached");
264
265
  expect(global.fetch).not.toHaveBeenCalled();
265
266
  });
267
+
268
+ it("should persist merchantId to sessionStorage after fetch", async () => {
269
+ const mockResponse = {
270
+ merchantId: "merchant-persisted",
271
+ name: "Test Merchant",
272
+ domain: "shop.example.com",
273
+ };
274
+
275
+ global.fetch = vi.fn().mockResolvedValueOnce({
276
+ ok: true,
277
+ json: async () => mockResponse,
278
+ });
279
+
280
+ await fetchMerchantId("shop.example.com");
281
+
282
+ expect(window.sessionStorage.getItem("frak-merchant-id")).toBe(
283
+ "merchant-persisted"
284
+ );
285
+ });
286
+
287
+ it("should restore merchantId from sessionStorage when in-memory cache is cleared", async () => {
288
+ const mockResponse = {
289
+ merchantId: "merchant-storage",
290
+ name: "Test Merchant",
291
+ domain: "shop.example.com",
292
+ };
293
+
294
+ global.fetch = vi.fn().mockResolvedValueOnce({
295
+ ok: true,
296
+ json: async () => mockResponse,
297
+ });
298
+
299
+ // First call populates both caches
300
+ const result1 = await fetchMerchantId("shop.example.com");
301
+ expect(result1).toBe("merchant-storage");
302
+ expect(global.fetch).toHaveBeenCalledTimes(1);
303
+
304
+ // Clear only in-memory cache (not sessionStorage)
305
+ vi.clearAllMocks();
306
+ global.fetch = vi.fn();
307
+
308
+ // Manually clear in-memory but keep sessionStorage
309
+ // We do this by calling the internal clear then restoring sessionStorage
310
+ const stored = window.sessionStorage.getItem("frak-merchant-id");
311
+ clearMerchantIdCache();
312
+ window.sessionStorage.setItem("frak-merchant-id", stored!);
313
+
314
+ // Second call should restore from sessionStorage without fetch
315
+ const result2 = await fetchMerchantId("shop.example.com");
316
+ expect(result2).toBe("merchant-storage");
317
+ expect(global.fetch).not.toHaveBeenCalled();
318
+ });
319
+
320
+ it("should not write to sessionStorage when fetch fails", async () => {
321
+ global.fetch = vi
322
+ .fn()
323
+ .mockRejectedValueOnce(new Error("Network error"));
324
+
325
+ await fetchMerchantId("shop.example.com");
326
+
327
+ expect(
328
+ window.sessionStorage.getItem("frak-merchant-id")
329
+ ).toBeNull();
330
+ });
266
331
  });
267
332
 
268
333
  describe("clearMerchantIdCache", () => {
@@ -324,6 +389,30 @@ describe("merchantId", () => {
324
389
  const result2 = await fetchMerchantId("shop2.example.com");
325
390
  expect(result2).toBe("merchant-second");
326
391
  });
392
+
393
+ it("should clear sessionStorage when clearing cache", async () => {
394
+ const mockResponse = {
395
+ merchantId: "merchant-session-clear",
396
+ name: "Test Merchant",
397
+ domain: "shop.example.com",
398
+ };
399
+
400
+ global.fetch = vi.fn().mockResolvedValueOnce({
401
+ ok: true,
402
+ json: async () => mockResponse,
403
+ });
404
+
405
+ await fetchMerchantId("shop.example.com");
406
+ expect(window.sessionStorage.getItem("frak-merchant-id")).toBe(
407
+ "merchant-session-clear"
408
+ );
409
+
410
+ clearMerchantIdCache();
411
+
412
+ expect(
413
+ window.sessionStorage.getItem("frak-merchant-id")
414
+ ).toBeNull();
415
+ });
327
416
  });
328
417
 
329
418
  describe("resolveMerchantId", () => {
@@ -4,6 +4,8 @@
4
4
 
5
5
  import { getBackendUrl } from "./backendUrl";
6
6
 
7
+ const MERCHANT_ID_STORAGE_KEY = "frak-merchant-id";
8
+
7
9
  /**
8
10
  * Response from the merchant lookup endpoint
9
11
  */
@@ -39,11 +41,20 @@ export async function fetchMerchantId(
39
41
  domain?: string,
40
42
  walletUrl?: string
41
43
  ): Promise<string | undefined> {
42
- // Use cached value if available
44
+ // Use in-memory cache if available
43
45
  if (cachedMerchantId) {
44
46
  return cachedMerchantId;
45
47
  }
46
48
 
49
+ // Check sessionStorage (survives page navigations)
50
+ if (typeof window !== "undefined") {
51
+ const stored = window.sessionStorage.getItem(MERCHANT_ID_STORAGE_KEY);
52
+ if (stored) {
53
+ cachedMerchantId = stored;
54
+ return stored;
55
+ }
56
+ }
57
+
47
58
  // If a fetch is already in progress, wait for it
48
59
  if (cachePromise) {
49
60
  return cachePromise;
@@ -85,6 +96,13 @@ async function fetchMerchantIdInternal(
85
96
 
86
97
  const data = (await response.json()) as MerchantLookupResponse;
87
98
  cachedMerchantId = data.merchantId;
99
+ // Persist to sessionStorage so it survives page navigations
100
+ if (typeof window !== "undefined") {
101
+ window.sessionStorage.setItem(
102
+ MERCHANT_ID_STORAGE_KEY,
103
+ data.merchantId
104
+ );
105
+ }
88
106
  return cachedMerchantId;
89
107
  } catch (error) {
90
108
  console.warn("[Frak SDK] Failed to fetch merchantId:", error);
@@ -99,6 +117,9 @@ async function fetchMerchantIdInternal(
99
117
  export function clearMerchantIdCache(): void {
100
118
  cachedMerchantId = undefined;
101
119
  cachePromise = undefined;
120
+ if (typeof window !== "undefined") {
121
+ window.sessionStorage.removeItem(MERCHANT_ID_STORAGE_KEY);
122
+ }
102
123
  }
103
124
 
104
125
  /**
package/src/utils/sso.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { Hex } from "viem";
2
2
  import type { PrepareSsoParamsType, SsoMetadata } from "../types";
3
- import { getClientId } from "./clientId";
4
3
  import { compressJsonToB64 } from "./compression/compress";
5
4
 
6
5
  export type AppSpecificSsoMetadata = SsoMetadata & {
@@ -25,8 +24,8 @@ export type FullSsoParams = Omit<PrepareSsoParamsType, "metadata"> & {
25
24
  * @param params - SSO parameters
26
25
  * @param merchantId - Merchant identifier
27
26
  * @param name - Application name
27
+ * @param clientId - Client identifier for identity tracking
28
28
  * @param css - Optional custom CSS
29
- * @param clientId - Optional client identifier (auto-generated if omitted)
30
29
  * @returns Complete SSO URL ready to open in popup or redirect
31
30
  *
32
31
  * @example
@@ -45,8 +44,8 @@ export function generateSsoUrl(
45
44
  params: PrepareSsoParamsType,
46
45
  merchantId: string,
47
46
  name: string,
48
- css?: string,
49
- clientId?: string
47
+ clientId: string,
48
+ css?: string
50
49
  ): string {
51
50
  // Build full params with app-specific metadata
52
51
  const fullParams: FullSsoParams = {
@@ -60,7 +59,7 @@ export function generateSsoUrl(
60
59
  logoUrl: params.metadata?.logoUrl,
61
60
  homepageLink: params.metadata?.homepageLink,
62
61
  },
63
- clientId: clientId ?? getClientId(),
62
+ clientId,
64
63
  };
65
64
 
66
65
  // Compress params to minimal format
@@ -1,13 +0,0 @@
1
- import{d as e,l as t,r as n}from"./trackEvent-YfUh4jrx.js";import{Deferred as r,FrakRpcError as i,RpcErrorCodes as a,createRpcClient as o,jsonDecode as s}from"@frak-labs/frame-connector";import{OpenPanel as c}from"@openpanel/web";const l=`nexus-wallet-backup`,u=`frakwallet://`;function d(e,t){if(typeof window>`u`)return;let n=new URL(window.location.href),r=n.searchParams.get(`sso`);r&&(t.then(()=>{e.sendLifecycle({clientLifecycle:`sso-redirect-complete`,data:{compressed:r}}),console.log(`[SSO URL Listener] Forwarded compressed SSO data to iframe`)}).catch(e=>{console.error(`[SSO URL Listener] Failed to forward SSO data:`,e)}),n.searchParams.delete(`sso`),window.history.replaceState({},``,n.toString()),console.log(`[SSO URL Listener] SSO parameter detected and URL cleaned`))}var f=class e{config;iframe;isSetupDone=!1;lastResponse=null;lastRequest=null;constructor(e,t){this.config=e,this.iframe=t,this.lastRequest=null,this.lastResponse=null}setLastResponse(e,t){this.lastResponse={message:e,response:t,timestamp:Date.now()}}setLastRequest(e){this.lastRequest={event:e,timestamp:Date.now()}}updateSetupStatus(e){this.isSetupDone=e}base64Encode(e){try{return btoa(JSON.stringify(e))}catch(e){return console.warn(`Failed to encode debug data`,e),btoa(`Failed to encode data`)}}getIframeStatus(){return this.iframe?{loading:this.iframe.hasAttribute(`loading`),url:this.iframe.src,readyState:this.iframe.contentDocument?.readyState?this.iframe.contentDocument.readyState===`complete`?1:0:-1,contentWindow:!!this.iframe.contentWindow,isConnected:this.iframe.isConnected}:null}getNavigatorInfo(){return navigator?{userAgent:navigator.userAgent,language:navigator.language,onLine:navigator.onLine,screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio}:null}gatherDebugInfo(e){let t=this.getIframeStatus(),n=this.getNavigatorInfo(),r=`Unknown`;return e instanceof i?r=`FrakRpcError: ${e.code} '${e.message}'`:e instanceof Error?r=e.message:typeof e==`string`&&(r=e),{timestamp:new Date().toISOString(),encodedUrl:btoa(window.location.href),encodedConfig:this.config?this.base64Encode(this.config):`no-config`,navigatorInfo:n?this.base64Encode(n):`no-navigator`,iframeStatus:t?this.base64Encode(t):`not-iframe`,lastRequest:this.lastRequest?this.base64Encode(this.lastRequest):`No Frak request logged`,lastResponse:this.lastResponse?this.base64Encode(this.lastResponse):`No Frak response logged`,clientStatus:this.isSetupDone?`setup`:`not-setup`,error:r}}static empty(){return new e}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
2
- Debug Information:
3
- -----------------
4
- Timestamp: ${t.timestamp}
5
- URL: ${t.encodedUrl}
6
- Config: ${t.encodedConfig}
7
- Navigator Info: ${t.navigatorInfo}
8
- IFrame Status: ${t.iframeStatus}
9
- Last Request: ${t.lastRequest}
10
- Last Response: ${t.lastResponse}
11
- Client Status: ${t.clientStatus}
12
- Error: ${t.error}
13
- `.trim()}};function p(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function m(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;end`}function h(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=p()&&g(e)?m(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function g(e){return e.startsWith(u)}const _={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 v({walletBaseUrl:e,config:t}){let n=document.querySelector(`#frak-wallet`);n&&n.remove();let r=document.createElement(`iframe`);return r.id=_.id,r.name=_.name,r.allow=_.allow,r.style.zIndex=_.style.zIndex.toString(),y({iframe:r,isVisible:!1}),r.src=`${t?.walletUrl??e??`https://wallet.frak.id`}/listener`,new Promise(e=>{r.addEventListener(`load`,()=>e(r)),document.body.appendChild(r)})}function y({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 b(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 x(e){e?localStorage.setItem(l,e):localStorage.removeItem(l)}function S(t,n,r,i){let a=new URL(window.location.href),o=a.searchParams.get(`fmt`)??void 0;t.contentWindow?.postMessage({clientLifecycle:`handshake-response`,data:{token:n,currentUrl:window.location.href,clientId:e(),pendingMergeToken:o,configDomain:i}},r),o&&(a.searchParams.delete(`fmt`),window.history.replaceState({},``,a.toString()))}function C(e,t){try{let n=new URL(e);return n.searchParams.has(`u`)?(n.searchParams.delete(`u`),n.searchParams.append(`u`,window.location.href),t&&n.searchParams.append(`fmt`,t),n.toString()):e}catch{return e}}function w(e,t,n,r){let i=C(t,r);g(t)?h(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}}):window.location.href=i}function T({iframe:e,targetOrigin:t,configDomain:n}){let i=new r;return{handleEvent:async r=>{if(!(`iframeLifecycle`in r))return;let{iframeLifecycle:a,data:o}=r;switch(a){case`connected`:i.resolve(!0);break;case`do-backup`:x(o.backup);break;case`remove-backup`:localStorage.removeItem(l);break;case`show`:case`hide`:y({iframe:e,isVisible:a===`show`});break;case`handshake`:S(e,o.token,t,n);break;case`redirect`:w(e,o.baseRedirectUrl,t,o.mergeToken);break}},isConnected:i.promise}}function E({config:e,iframe:t}){let n=e?.walletUrl??`https://wallet.frak.id`,r=T({iframe:t,targetOrigin:n,configDomain:e.domain}),s=new f(e,t);if(!t.contentWindow)throw new i(a.configError,`The iframe does not have a content window`);let l=o({emittingTransport:t.contentWindow,listeningTransport:window,targetOrigin:n,middleware:[{async onRequest(e,t){if(!await r.isConnected)throw new i(a.clientNotConnected,`The iframe provider isn't connected yet`);return t}},{onRequest(e,t){return s.setLastRequest(e),t},onResponse(e,t){return s.setLastResponse(e,t),t}}],lifecycleHandlers:{iframeLifecycle:async(e,t)=>{await r.handleEvent(e)}}}),u=D(l,r),d=async()=>{u(),l.cleanup(),t.remove()},p;console.log(`[Frak SDK] Initializing OpenPanel`),p=new c({apiUrl:`https://op-api.gcp.frak.id`,clientId:`6eacc8d7-49ac-4936-95e9-81ef29449570`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:e,payload:t})=>(e!==`track`||!t?.properties||`sdkVersion`in t.properties||(t.properties={...t.properties,sdkVersion:`0.1.1`}),!0)}),p.setGlobalProperties({sdkVersion:`0.1.1`}),p.init();let m=O({config:e,rpcClient:l,lifecycleManager:r}).then(()=>s.updateSetupStatus(!0));return{config:e,debugInfo:s,waitForConnection:r.isConnected,waitForSetup:m,request:l.request,listenerRequest:l.listen,destroy:d,openPanel:p}}function D(e,t){let n,r,i=()=>e.sendLifecycle({clientLifecycle:`heartbeat`});async function a(){i(),n=setInterval(i,1e3),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 O({config:e,rpcClient:t,lifecycleManager:n}){await n.isConnected,d(t,n.isConnected);async function r(){let n=e.customizations?.css;if(!n)return;let r={clientLifecycle:`modal-css`,data:{cssLink:n}};t.sendLifecycle(r)}async function i(){let n=e.customizations?.i18n;if(!n)return;let r={clientLifecycle:`modal-i18n`,data:{i18n:n}};t.sendLifecycle(r)}async function a(){if(typeof window>`u`)return;let e=window.localStorage.getItem(l);if(!e)return;let n={clientLifecycle:`restore-backup`,data:{backup:e}};t.sendLifecycle(n)}await Promise.allSettled([r(),i(),a()])}function k(e){return s(t(e))}const A={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function j(e){return e&&e in A?e:`eur`}function M(e){return e?A[e]??A.eur:A.eur}function N(e,t){let n=M(t),r=j(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function P(e){return e?`${e}Amount`:`eurAmount`}let F,I;async function L(e,t){if(F)return F;if(I)return I;I=R(e,t);let n=await I;return I=void 0,n}async function R(e,t){let r=e??(typeof window<`u`?window.location.hostname:``);if(r)try{let e=n(t),i=await fetch(`${e}/user/merchant/resolve?domain=${encodeURIComponent(r)}`);if(!i.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${r}: ${i.status}`);return}return F=(await i.json()).merchantId,F}catch(e){console.warn(`[Frak SDK] Failed to fetch merchantId:`,e);return}}function z(){F=void 0,I=void 0}async function B(e,t){return e.metadata?.merchantId?e.metadata.merchantId:L(void 0,t)}async function V({config:e}){let t=H(e),n=await v({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=E({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function H(e){let t=j(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}export{m as _,P as a,u as b,j as c,E as d,_ as f,g,p as h,B as i,A as l,b as m,z as n,N as o,v as p,L as r,M as s,V as t,k as u,h as v,f as y};
@@ -1,13 +0,0 @@
1
- const e=require(`./trackEvent-CGIryq5h.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`))}var o=class e{config;iframe;isSetupDone=!1;lastResponse=null;lastRequest=null;constructor(e,t){this.config=e,this.iframe=t,this.lastRequest=null,this.lastResponse=null}setLastResponse(e,t){this.lastResponse={message:e,response:t,timestamp:Date.now()}}setLastRequest(e){this.lastRequest={event:e,timestamp:Date.now()}}updateSetupStatus(e){this.isSetupDone=e}base64Encode(e){try{return btoa(JSON.stringify(e))}catch(e){return console.warn(`Failed to encode debug data`,e),btoa(`Failed to encode data`)}}getIframeStatus(){return this.iframe?{loading:this.iframe.hasAttribute(`loading`),url:this.iframe.src,readyState:this.iframe.contentDocument?.readyState?this.iframe.contentDocument.readyState===`complete`?1:0:-1,contentWindow:!!this.iframe.contentWindow,isConnected:this.iframe.isConnected}:null}getNavigatorInfo(){return navigator?{userAgent:navigator.userAgent,language:navigator.language,onLine:navigator.onLine,screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio}:null}gatherDebugInfo(e){let n=this.getIframeStatus(),r=this.getNavigatorInfo(),i=`Unknown`;return e instanceof t.FrakRpcError?i=`FrakRpcError: ${e.code} '${e.message}'`:e instanceof Error?i=e.message:typeof e==`string`&&(i=e),{timestamp:new Date().toISOString(),encodedUrl:btoa(window.location.href),encodedConfig:this.config?this.base64Encode(this.config):`no-config`,navigatorInfo:r?this.base64Encode(r):`no-navigator`,iframeStatus:n?this.base64Encode(n):`not-iframe`,lastRequest:this.lastRequest?this.base64Encode(this.lastRequest):`No Frak request logged`,lastResponse:this.lastResponse?this.base64Encode(this.lastResponse):`No Frak response logged`,clientStatus:this.isSetupDone?`setup`:`not-setup`,error:i}}static empty(){return new e}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
2
- Debug Information:
3
- -----------------
4
- Timestamp: ${t.timestamp}
5
- URL: ${t.encodedUrl}
6
- Config: ${t.encodedConfig}
7
- Navigator Info: ${t.navigatorInfo}
8
- IFrame Status: ${t.iframeStatus}
9
- Last Request: ${t.lastRequest}
10
- Last Response: ${t.lastResponse}
11
- Client Status: ${t.clientStatus}
12
- Error: ${t.error}
13
- `.trim()}};function s(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function c(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;end`}function l(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=s()&&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:e,config:t}){let n=document.querySelector(`#frak-wallet`);n&&n.remove();let r=document.createElement(`iframe`);return r.id=d.id,r.name=d.name,r.allow=d.allow,r.style.zIndex=d.style.zIndex.toString(),p({iframe:r,isVisible:!1}),r.src=`${t?.walletUrl??e??`https://wallet.frak.id`}/listener`,new Promise(e=>{r.addEventListener(`load`,()=>e(r)),document.body.appendChild(r)})}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){e?localStorage.setItem(r,e):localStorage.removeItem(r)}function g(t,n,r,i){let a=new URL(window.location.href),o=a.searchParams.get(`fmt`)??void 0;t.contentWindow?.postMessage({clientLifecycle:`handshake-response`,data:{token:n,currentUrl:window.location.href,clientId:e.d(),pendingMergeToken:o,configDomain:i}},r),o&&(a.searchParams.delete(`fmt`),window.history.replaceState({},``,a.toString()))}function _(e,t){try{let n=new URL(e);return n.searchParams.has(`u`)?(n.searchParams.delete(`u`),n.searchParams.append(`u`,window.location.href),t&&n.searchParams.append(`fmt`,t),n.toString()):e}catch{return e}}function v(e,t,n,r){let i=_(t,r);u(t)?l(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}}):window.location.href=i}function y({iframe:e,targetOrigin:n,configDomain:i}){let a=new t.Deferred;return{handleEvent:async t=>{if(!(`iframeLifecycle`in t))return;let{iframeLifecycle:o,data:s}=t;switch(o){case`connected`:a.resolve(!0);break;case`do-backup`:h(s.backup);break;case`remove-backup`:localStorage.removeItem(r);break;case`show`:case`hide`:p({iframe:e,isVisible:o===`show`});break;case`handshake`:g(e,s.token,n,i);break;case`redirect`:v(e,s.baseRedirectUrl,n,s.mergeToken);break}},isConnected:a.promise}}function b({config:e,iframe:r}){let i=e?.walletUrl??`https://wallet.frak.id`,a=y({iframe:r,targetOrigin:i,configDomain:e.domain}),s=new o(e,r);if(!r.contentWindow)throw new t.FrakRpcError(t.RpcErrorCodes.configError,`The iframe does not have a content window`);let c=(0,t.createRpcClient)({emittingTransport:r.contentWindow,listeningTransport:window,targetOrigin:i,middleware:[{async onRequest(e,n){if(!await a.isConnected)throw new t.FrakRpcError(t.RpcErrorCodes.clientNotConnected,`The iframe provider isn't connected yet`);return n}},{onRequest(e,t){return s.setLastRequest(e),t},onResponse(e,t){return s.setLastResponse(e,t),t}}],lifecycleHandlers:{iframeLifecycle:async(e,t)=>{await a.handleEvent(e)}}}),l=x(c,a),u=async()=>{l(),c.cleanup(),r.remove()},d;console.log(`[Frak SDK] Initializing OpenPanel`),d=new n.OpenPanel({apiUrl:`https://op-api.gcp.frak.id`,clientId:`6eacc8d7-49ac-4936-95e9-81ef29449570`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:e,payload:t})=>(e!==`track`||!t?.properties||`sdkVersion`in t.properties||(t.properties={...t.properties,sdkVersion:`0.1.1`}),!0)}),d.setGlobalProperties({sdkVersion:`0.1.1`}),d.init();let f=S({config:e,rpcClient:c,lifecycleManager:a}).then(()=>s.updateSetupStatus(!0));return{config:e,debugInfo:s,waitForConnection:a.isConnected,waitForSetup:f,request:c.request,listenerRequest:c.listen,destroy:u,openPanel:d}}function x(e,t){let n,r,i=()=>e.sendLifecycle({clientLifecycle:`heartbeat`});async function a(){i(),n=setInterval(i,1e3),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 S({config:e,rpcClient:t,lifecycleManager:n}){await n.isConnected,a(t,n.isConnected);async function i(){let n=e.customizations?.css;if(!n)return;let r={clientLifecycle:`modal-css`,data:{cssLink:n}};t.sendLifecycle(r)}async function o(){let n=e.customizations?.i18n;if(!n)return;let r={clientLifecycle:`modal-i18n`,data:{i18n:n}};t.sendLifecycle(r)}async function s(){if(typeof window>`u`)return;let e=window.localStorage.getItem(r);if(!e)return;let n={clientLifecycle:`restore-backup`,data:{backup:e}};t.sendLifecycle(n)}await Promise.allSettled([i(),o(),s()])}function C(n){return(0,t.jsonDecode)(e.l(n))}const w={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function T(e){return e&&e in w?e:`eur`}function E(e){return e?w[e]??w.eur:w.eur}function D(e,t){let n=E(t),r=T(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function O(e){return e?`${e}Amount`:`eurAmount`}let k,A;async function j(e,t){if(k)return k;if(A)return A;A=M(e,t);let n=await A;return A=void 0,n}async function M(t,n){let r=t??(typeof window<`u`?window.location.hostname:``);if(r)try{let t=e.r(n),i=await fetch(`${t}/user/merchant/resolve?domain=${encodeURIComponent(r)}`);if(!i.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${r}: ${i.status}`);return}return k=(await i.json()).merchantId,k}catch(e){console.warn(`[Frak SDK] Failed to fetch merchantId:`,e);return}}function N(){k=void 0,A=void 0}async function P(e,t){return e.metadata?.merchantId?e.metadata.merchantId:j(void 0,t)}async function F({config:e}){let t=I(e),n=await f({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=b({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function I(e){let t=T(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return P}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return o}});