@frak-labs/core-sdk 0.1.1 → 0.2.0

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 (125) hide show
  1. package/README.md +58 -0
  2. package/cdn/bundle.js +14 -0
  3. package/dist/actions.cjs +1 -1
  4. package/dist/actions.d.cts +3 -3
  5. package/dist/actions.d.ts +3 -3
  6. package/dist/actions.js +1 -1
  7. package/dist/bundle.cjs +1 -1
  8. package/dist/bundle.d.cts +4 -6
  9. package/dist/bundle.d.ts +4 -6
  10. package/dist/bundle.js +1 -1
  11. package/dist/{index-CRsQWnTs.d.cts → computeLegacyProductId-BkyJ4rEY.d.ts} +197 -10
  12. package/dist/{index-Ck1hudEi.d.ts → computeLegacyProductId-Raks6FXg.d.cts} +197 -10
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +3 -4
  15. package/dist/index.d.ts +3 -4
  16. package/dist/index.js +1 -1
  17. package/dist/{openSso-D--Airj6.d.cts → openSso-BCJGchIb.d.cts} +135 -131
  18. package/dist/{openSso-DsKJ4y0j.d.ts → openSso-DG-_9CED.d.ts} +135 -131
  19. package/dist/setupClient-CQrMDGyZ.js +13 -0
  20. package/dist/setupClient-Ccv3XxwL.cjs +13 -0
  21. package/dist/{index-d8xS4ryI.d.ts → siweAuthenticate-BH7Dn7nZ.d.cts} +90 -65
  22. package/dist/siweAuthenticate-BJHbtty4.js +1 -0
  23. package/dist/{index-C6FxkWPC.d.cts → siweAuthenticate-Btem4QHs.d.ts} +90 -65
  24. package/dist/siweAuthenticate-Cwj3HP0m.cjs +1 -0
  25. package/dist/trackEvent-M2RLTQ2p.js +1 -0
  26. package/dist/trackEvent-T_R9ER2S.cjs +1 -0
  27. package/package.json +11 -22
  28. package/src/actions/displayEmbeddedWallet.ts +1 -0
  29. package/src/actions/displayModal.test.ts +12 -11
  30. package/src/actions/displayModal.ts +7 -18
  31. package/src/actions/ensureIdentity.ts +68 -0
  32. package/src/actions/{getProductInformation.test.ts → getMerchantInformation.test.ts} +33 -50
  33. package/src/actions/getMerchantInformation.ts +16 -0
  34. package/src/actions/index.ts +3 -2
  35. package/src/actions/openSso.ts +4 -2
  36. package/src/actions/referral/processReferral.test.ts +42 -151
  37. package/src/actions/referral/processReferral.ts +18 -42
  38. package/src/actions/referral/referralInteraction.test.ts +1 -7
  39. package/src/actions/referral/referralInteraction.ts +1 -6
  40. package/src/actions/sendInteraction.ts +46 -22
  41. package/src/actions/trackPurchaseStatus.test.ts +354 -141
  42. package/src/actions/trackPurchaseStatus.ts +48 -11
  43. package/src/actions/watchWalletStatus.ts +2 -3
  44. package/src/actions/wrapper/modalBuilder.test.ts +0 -14
  45. package/src/actions/wrapper/modalBuilder.ts +3 -12
  46. package/src/bundle.ts +0 -1
  47. package/src/clients/createIFrameFrakClient.ts +10 -5
  48. package/src/clients/transports/iframeLifecycleManager.test.ts +163 -4
  49. package/src/clients/transports/iframeLifecycleManager.ts +172 -33
  50. package/src/constants/interactionTypes.ts +12 -41
  51. package/src/index.ts +24 -16
  52. package/src/types/config.ts +6 -0
  53. package/src/types/index.ts +13 -10
  54. package/src/types/lifecycle/client.ts +24 -1
  55. package/src/types/lifecycle/iframe.ts +6 -0
  56. package/src/types/rpc/displayModal.ts +2 -4
  57. package/src/types/rpc/embedded/index.ts +2 -2
  58. package/src/types/rpc/interaction.ts +26 -39
  59. package/src/types/rpc/merchantInformation.ts +77 -0
  60. package/src/types/rpc/modal/index.ts +0 -4
  61. package/src/types/rpc/modal/login.ts +5 -1
  62. package/src/types/rpc/walletStatus.ts +1 -7
  63. package/src/types/rpc.ts +22 -30
  64. package/src/types/tracking.ts +60 -0
  65. package/src/utils/backendUrl.test.ts +83 -0
  66. package/src/utils/backendUrl.ts +62 -0
  67. package/src/utils/clientId.test.ts +41 -0
  68. package/src/utils/clientId.ts +43 -0
  69. package/src/utils/compression/compress.test.ts +1 -1
  70. package/src/utils/compression/compress.ts +2 -2
  71. package/src/utils/compression/decompress.test.ts +8 -4
  72. package/src/utils/compression/decompress.ts +2 -2
  73. package/src/utils/{computeProductId.ts → computeLegacyProductId.ts} +2 -2
  74. package/src/utils/constants.ts +5 -0
  75. package/src/utils/deepLinkWithFallback.test.ts +243 -0
  76. package/src/utils/deepLinkWithFallback.ts +103 -0
  77. package/src/utils/formatAmount.ts +6 -0
  78. package/src/utils/iframeHelper.test.ts +18 -5
  79. package/src/utils/iframeHelper.ts +10 -3
  80. package/src/utils/index.ts +16 -1
  81. package/src/utils/merchantId.test.ts +653 -0
  82. package/src/utils/merchantId.ts +143 -0
  83. package/src/utils/sso.ts +18 -11
  84. package/src/utils/trackEvent.test.ts +23 -5
  85. package/src/utils/trackEvent.ts +13 -0
  86. package/cdn/bundle.iife.js +0 -14
  87. package/dist/actions-B5j-i1p0.cjs +0 -1
  88. package/dist/actions-q090Z0oR.js +0 -1
  89. package/dist/index-7OZ39x1U.d.ts +0 -195
  90. package/dist/index-zDq-VlKx.d.cts +0 -195
  91. package/dist/interaction-DMJ3ZfaF.d.cts +0 -45
  92. package/dist/interaction-KX1h9a7V.d.ts +0 -45
  93. package/dist/interactions-DnfM3oe0.js +0 -1
  94. package/dist/interactions-EIXhNLf6.cjs +0 -1
  95. package/dist/interactions.cjs +0 -1
  96. package/dist/interactions.d.cts +0 -2
  97. package/dist/interactions.d.ts +0 -2
  98. package/dist/interactions.js +0 -1
  99. package/dist/productTypes-BUkXJKZ7.cjs +0 -1
  100. package/dist/productTypes-CGb1MmBF.js +0 -1
  101. package/dist/src-1LQ4eLq5.js +0 -13
  102. package/dist/src-hW71KjPN.cjs +0 -13
  103. package/dist/trackEvent-CHnYa85W.js +0 -1
  104. package/dist/trackEvent-GuQm_1Nm.cjs +0 -1
  105. package/src/actions/getProductInformation.ts +0 -14
  106. package/src/actions/openSso.test.ts +0 -407
  107. package/src/actions/sendInteraction.test.ts +0 -219
  108. package/src/constants/interactionTypes.test.ts +0 -128
  109. package/src/constants/productTypes.test.ts +0 -130
  110. package/src/constants/productTypes.ts +0 -33
  111. package/src/interactions/index.ts +0 -5
  112. package/src/interactions/pressEncoder.test.ts +0 -215
  113. package/src/interactions/pressEncoder.ts +0 -53
  114. package/src/interactions/purchaseEncoder.test.ts +0 -291
  115. package/src/interactions/purchaseEncoder.ts +0 -99
  116. package/src/interactions/referralEncoder.test.ts +0 -170
  117. package/src/interactions/referralEncoder.ts +0 -47
  118. package/src/interactions/retailEncoder.test.ts +0 -107
  119. package/src/interactions/retailEncoder.ts +0 -37
  120. package/src/interactions/webshopEncoder.test.ts +0 -56
  121. package/src/interactions/webshopEncoder.ts +0 -30
  122. package/src/types/rpc/modal/openSession.ts +0 -25
  123. package/src/types/rpc/productInformation.ts +0 -59
  124. package/src/utils/computeProductId.test.ts +0 -80
  125. package/src/utils/sso.test.ts +0 -361
@@ -0,0 +1,243 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
+ import {
3
+ isFrakDeepLink,
4
+ triggerDeepLinkWithFallback,
5
+ } from "./deepLinkWithFallback";
6
+
7
+ /**
8
+ * Set navigator.userAgent for testing platform-specific behavior
9
+ */
10
+ function mockUserAgent(ua: string) {
11
+ Object.defineProperty(navigator, "userAgent", {
12
+ value: ua,
13
+ writable: true,
14
+ configurable: true,
15
+ });
16
+ }
17
+
18
+ const CHROME_ANDROID_UA =
19
+ "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36";
20
+ const FIREFOX_ANDROID_UA =
21
+ "Mozilla/5.0 (Android 14; Mobile; rv:121.0) Gecko/121.0 Firefox/121.0";
22
+ const DESKTOP_CHROME_UA =
23
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
24
+
25
+ describe("deepLinkWithFallback", () => {
26
+ let originalHidden: boolean;
27
+ let originalAddEventListener: typeof document.addEventListener;
28
+ let originalRemoveEventListener: typeof document.removeEventListener;
29
+ let originalUserAgent: string;
30
+ let visibilityChangeHandler: (() => void) | null = null;
31
+
32
+ beforeEach(() => {
33
+ vi.useFakeTimers();
34
+
35
+ // Store originals
36
+ originalHidden = document.hidden;
37
+ originalAddEventListener = document.addEventListener;
38
+ originalRemoveEventListener = document.removeEventListener;
39
+ originalUserAgent = navigator.userAgent;
40
+
41
+ // Default to desktop Chrome (non-Android)
42
+ mockUserAgent(DESKTOP_CHROME_UA);
43
+
44
+ // Mock document.hidden
45
+ Object.defineProperty(document, "hidden", {
46
+ value: false,
47
+ writable: true,
48
+ configurable: true,
49
+ });
50
+
51
+ // Mock window.location
52
+ Object.defineProperty(window, "location", {
53
+ value: { href: "https://test.com" },
54
+ writable: true,
55
+ configurable: true,
56
+ });
57
+
58
+ // Capture visibilitychange handler
59
+ visibilityChangeHandler = null;
60
+ document.addEventListener = vi.fn(
61
+ (event: string, handler: EventListener) => {
62
+ if (event === "visibilitychange") {
63
+ visibilityChangeHandler = handler as () => void;
64
+ }
65
+ }
66
+ );
67
+ document.removeEventListener = vi.fn();
68
+ });
69
+
70
+ afterEach(() => {
71
+ vi.useRealTimers();
72
+ vi.restoreAllMocks();
73
+
74
+ // Restore originals
75
+ Object.defineProperty(document, "hidden", {
76
+ value: originalHidden,
77
+ writable: true,
78
+ configurable: true,
79
+ });
80
+ document.addEventListener = originalAddEventListener;
81
+ document.removeEventListener = originalRemoveEventListener;
82
+ mockUserAgent(originalUserAgent);
83
+ });
84
+
85
+ describe("triggerDeepLinkWithFallback", () => {
86
+ test("should trigger deep link immediately", () => {
87
+ triggerDeepLinkWithFallback("frakwallet://wallet");
88
+
89
+ expect(window.location.href).toBe("frakwallet://wallet");
90
+ });
91
+
92
+ test("should add visibilitychange listener", () => {
93
+ triggerDeepLinkWithFallback("frakwallet://wallet");
94
+
95
+ expect(document.addEventListener).toHaveBeenCalledWith(
96
+ "visibilitychange",
97
+ expect.any(Function)
98
+ );
99
+ });
100
+
101
+ test("should trigger fallback callback when page stays visible after timeout", () => {
102
+ const onFallback = vi.fn();
103
+
104
+ triggerDeepLinkWithFallback("frakwallet://wallet", { onFallback });
105
+
106
+ // Document stays visible (app not installed)
107
+ Object.defineProperty(document, "hidden", {
108
+ value: false,
109
+ configurable: true,
110
+ });
111
+
112
+ // Advance past timeout
113
+ vi.advanceTimersByTime(2500);
114
+
115
+ expect(onFallback).toHaveBeenCalledTimes(1);
116
+ });
117
+
118
+ test("should NOT trigger fallback when page goes hidden (app opened)", () => {
119
+ const onFallback = vi.fn();
120
+
121
+ triggerDeepLinkWithFallback("frakwallet://wallet", { onFallback });
122
+
123
+ // Simulate app opening (page goes to background)
124
+ Object.defineProperty(document, "hidden", {
125
+ value: true,
126
+ configurable: true,
127
+ });
128
+ visibilityChangeHandler?.();
129
+
130
+ // Advance past timeout
131
+ vi.advanceTimersByTime(2500);
132
+
133
+ // Fallback should NOT be triggered
134
+ expect(onFallback).not.toHaveBeenCalled();
135
+ // Location should still be the deep link
136
+ expect(window.location.href).toBe("frakwallet://wallet");
137
+ });
138
+
139
+ test("should respect custom timeout", () => {
140
+ const onFallback = vi.fn();
141
+
142
+ triggerDeepLinkWithFallback("frakwallet://wallet", {
143
+ timeout: 1000,
144
+ onFallback,
145
+ });
146
+
147
+ // Advance to just before custom timeout
148
+ vi.advanceTimersByTime(999);
149
+ expect(onFallback).not.toHaveBeenCalled();
150
+
151
+ // Advance past custom timeout
152
+ vi.advanceTimersByTime(1);
153
+ expect(onFallback).toHaveBeenCalledTimes(1);
154
+ });
155
+
156
+ test("should remove event listener after timeout", () => {
157
+ triggerDeepLinkWithFallback("frakwallet://wallet");
158
+
159
+ // Advance past timeout
160
+ vi.advanceTimersByTime(2500);
161
+
162
+ expect(document.removeEventListener).toHaveBeenCalledWith(
163
+ "visibilitychange",
164
+ expect.any(Function)
165
+ );
166
+ });
167
+
168
+ test("should work without onFallback callback", () => {
169
+ // Should not throw
170
+ triggerDeepLinkWithFallback("frakwallet://wallet");
171
+
172
+ // Advance past timeout
173
+ vi.advanceTimersByTime(2500);
174
+
175
+ // No error should occur, callback is optional
176
+ });
177
+
178
+ describe("Android Intent URL conversion", () => {
179
+ test("should use intent:// URL on Chromium Android", () => {
180
+ mockUserAgent(CHROME_ANDROID_UA);
181
+
182
+ triggerDeepLinkWithFallback("frakwallet://wallet");
183
+
184
+ expect(window.location.href).toBe(
185
+ "intent://wallet#Intent;scheme=frakwallet;end"
186
+ );
187
+ });
188
+
189
+ test("should preserve path and query params in intent URL", () => {
190
+ mockUserAgent(CHROME_ANDROID_UA);
191
+
192
+ triggerDeepLinkWithFallback(
193
+ "frakwallet://pair?id=abc-123&mode=embedded"
194
+ );
195
+
196
+ expect(window.location.href).toBe(
197
+ "intent://pair?id=abc-123&mode=embedded#Intent;scheme=frakwallet;end"
198
+ );
199
+ });
200
+
201
+ test("should use custom scheme on Firefox Android", () => {
202
+ mockUserAgent(FIREFOX_ANDROID_UA);
203
+
204
+ triggerDeepLinkWithFallback("frakwallet://wallet");
205
+
206
+ expect(window.location.href).toBe("frakwallet://wallet");
207
+ });
208
+
209
+ test("should use custom scheme on desktop Chrome", () => {
210
+ mockUserAgent(DESKTOP_CHROME_UA);
211
+
212
+ triggerDeepLinkWithFallback("frakwallet://wallet");
213
+
214
+ expect(window.location.href).toBe("frakwallet://wallet");
215
+ });
216
+
217
+ test("should not convert non-frak deep links to intent URL", () => {
218
+ mockUserAgent(CHROME_ANDROID_UA);
219
+
220
+ triggerDeepLinkWithFallback("https://wallet.frak.id/pair");
221
+
222
+ expect(window.location.href).toBe(
223
+ "https://wallet.frak.id/pair"
224
+ );
225
+ });
226
+ });
227
+ });
228
+
229
+ describe("isFrakDeepLink", () => {
230
+ test("should return true for frakwallet:// URLs", () => {
231
+ expect(isFrakDeepLink("frakwallet://wallet")).toBe(true);
232
+ expect(isFrakDeepLink("frakwallet://pair?id=123")).toBe(true);
233
+ expect(isFrakDeepLink("frakwallet://")).toBe(true);
234
+ });
235
+
236
+ test("should return false for non-frakwallet URLs", () => {
237
+ expect(isFrakDeepLink("https://wallet.frak.id")).toBe(false);
238
+ expect(isFrakDeepLink("http://example.com")).toBe(false);
239
+ expect(isFrakDeepLink("myapp://something")).toBe(false);
240
+ expect(isFrakDeepLink("")).toBe(false);
241
+ });
242
+ });
243
+ });
@@ -0,0 +1,103 @@
1
+ import { DEEP_LINK_SCHEME } from "./constants";
2
+
3
+ /**
4
+ * Options for deep link with fallback
5
+ */
6
+ export type DeepLinkFallbackOptions = {
7
+ /** Timeout in ms before triggering fallback (default: 2500ms) */
8
+ timeout?: number;
9
+ /** Callback invoked when fallback is triggered (app not installed) */
10
+ onFallback?: () => void;
11
+ };
12
+
13
+ /**
14
+ * Check if running on a Chromium-based Android browser.
15
+ *
16
+ * On Chrome Android, custom scheme deep links (e.g. frakwallet://) trigger
17
+ * a confirmation bar ("Continue to Frak Wallet?"). Using intent:// URLs
18
+ * instead bypasses this for Chromium browsers while keeping custom scheme
19
+ * fallback for non-Chromium browsers (e.g. Firefox) where it works fine.
20
+ */
21
+ export function isChromiumAndroid(): boolean {
22
+ const ua = navigator.userAgent;
23
+ return /Android/i.test(ua) && /Chrome\/\d+/i.test(ua);
24
+ }
25
+
26
+ /**
27
+ * Convert a frakwallet:// deep link to an Android intent:// URL.
28
+ *
29
+ * Intent URLs let Chromium browsers open the app directly without
30
+ * showing the "Continue to app?" confirmation bar.
31
+ *
32
+ * Note: We intentionally omit the `package` parameter. Including it
33
+ * causes Chrome to redirect to the Play Store when the app is not
34
+ * installed, which breaks the visibility-based fallback detection.
35
+ * Without `package`, Chrome simply does nothing when the app is
36
+ * missing, allowing the fallback mechanism to fire correctly.
37
+ *
38
+ * Format: intent://path#Intent;scheme=frakwallet;end
39
+ */
40
+ export function toAndroidIntentUrl(deepLink: string): string {
41
+ // Extract everything after "frakwallet://"
42
+ const path = deepLink.slice(DEEP_LINK_SCHEME.length);
43
+ return `intent://${path}#Intent;scheme=frakwallet;end`;
44
+ }
45
+
46
+ /**
47
+ * Trigger a deep link with visibility-based fallback detection.
48
+ *
49
+ * Uses the Page Visibility API to detect if the app opened (page goes hidden).
50
+ * If the page remains visible after the timeout, assumes app is not installed
51
+ * and invokes the onFallback callback.
52
+ *
53
+ * On Chromium Android, converts custom scheme to intent:// URL to avoid
54
+ * the "Continue to app?" confirmation bar.
55
+ *
56
+ * @param deepLink - The deep link URL to trigger (e.g., "frakwallet://wallet")
57
+ * @param options - Optional configuration (timeout, onFallback callback)
58
+ */
59
+ export function triggerDeepLinkWithFallback(
60
+ deepLink: string,
61
+ options?: DeepLinkFallbackOptions
62
+ ): void {
63
+ const timeout = options?.timeout ?? 2500;
64
+
65
+ // Track if the app opened (page went to background)
66
+ let appOpened = false;
67
+
68
+ const onVisibilityChange = () => {
69
+ if (document.hidden) {
70
+ appOpened = true;
71
+ }
72
+ };
73
+
74
+ // Start listening for visibility changes
75
+ document.addEventListener("visibilitychange", onVisibilityChange);
76
+
77
+ // On Chromium Android, use intent:// to avoid confirmation bar
78
+ const url =
79
+ isChromiumAndroid() && isFrakDeepLink(deepLink)
80
+ ? toAndroidIntentUrl(deepLink)
81
+ : deepLink;
82
+
83
+ // Trigger the deep link
84
+ window.location.href = url;
85
+
86
+ // Check after timeout if app opened
87
+ setTimeout(() => {
88
+ // Clean up listener
89
+ document.removeEventListener("visibilitychange", onVisibilityChange);
90
+
91
+ if (!appOpened) {
92
+ // App didn't open - trigger fallback callback
93
+ options?.onFallback?.();
94
+ }
95
+ }, timeout);
96
+ }
97
+
98
+ /**
99
+ * Check if a URL is a Frak deep link
100
+ */
101
+ export function isFrakDeepLink(url: string): boolean {
102
+ return url.startsWith(DEEP_LINK_SCHEME);
103
+ }
@@ -2,6 +2,12 @@ import type { Currency } from "../types";
2
2
  import { getSupportedCurrency } from "./getSupportedCurrency";
3
3
  import { getSupportedLocale } from "./getSupportedLocale";
4
4
 
5
+ /**
6
+ * Format a numeric amount as a localized currency string
7
+ * @param amount - The raw numeric amount to format
8
+ * @param currency - Optional currency config; defaults to EUR/fr-FR when omitted
9
+ * @returns Localized currency string (e.g. "1 500 €", "$1,500")
10
+ */
5
11
  export function formatAmount(amount: number, currency?: Currency) {
6
12
  // Get the supported locale (e.g. "fr-FR")
7
13
  const supportedLocale = getSupportedLocale(currency);
@@ -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
@@ -51,13 +52,19 @@ export function createIframe({
51
52
  iframe.style.zIndex = baseIframeProps.style.zIndex.toString();
52
53
 
53
54
  changeIframeVisibility({ iframe, isVisible: false });
54
- document.body.appendChild(iframe);
55
+
56
+ // Set src BEFORE appending to DOM to avoid about:blank load event
57
+ const walletUrl =
58
+ config?.walletUrl ?? walletBaseUrl ?? "https://wallet.frak.id";
59
+ const clientId = getClientId();
60
+ iframe.src = `${walletUrl}/listener?clientId=${encodeURIComponent(clientId)}`;
55
61
 
56
62
  return new Promise((resolve) => {
57
- iframe?.addEventListener("load", () => resolve(iframe));
58
- iframe.src = `${config?.walletUrl ?? walletBaseUrl ?? "https://wallet.frak.id"}/listener`;
63
+ iframe.addEventListener("load", () => resolve(iframe));
64
+ document.body.appendChild(iframe);
59
65
  });
60
66
  }
67
+
61
68
  /**
62
69
  * Change the visibility of the given iframe
63
70
  * @ignore
@@ -1,7 +1,17 @@
1
1
  export { Deferred } from "@frak-labs/frame-connector";
2
+ export { getBackendUrl } from "./backendUrl";
3
+ export { getClientId } from "./clientId";
2
4
  export { base64urlDecode, base64urlEncode } from "./compression/b64";
3
5
  export { compressJsonToB64 } from "./compression/compress";
4
6
  export { decompressJsonFromB64 } from "./compression/decompress";
7
+ export { DEEP_LINK_SCHEME } from "./constants";
8
+ export {
9
+ type DeepLinkFallbackOptions,
10
+ isChromiumAndroid,
11
+ isFrakDeepLink,
12
+ toAndroidIntentUrl,
13
+ triggerDeepLinkWithFallback,
14
+ } from "./deepLinkWithFallback";
5
15
  export { FrakContextManager } from "./FrakContext";
6
16
  export { formatAmount } from "./formatAmount";
7
17
  export { getCurrencyAmountKey } from "./getCurrencyAmountKey";
@@ -12,10 +22,15 @@ export {
12
22
  createIframe,
13
23
  findIframeInOpener,
14
24
  } from "./iframeHelper";
25
+ export {
26
+ clearMerchantIdCache,
27
+ fetchMerchantId,
28
+ resolveMerchantId,
29
+ } from "./merchantId";
15
30
  export {
16
31
  type AppSpecificSsoMetadata,
17
32
  type CompressedSsoData,
18
33
  type FullSsoParams,
19
34
  generateSsoUrl,
20
35
  } from "./sso";
21
- export { trackEvent } from "./trackEvent";
36
+ export { type FrakEvent, trackEvent } from "./trackEvent";