@frak-labs/core-sdk 0.1.0 → 0.1.1-beta.1e44255d

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 (131) hide show
  1. package/README.md +58 -0
  2. package/cdn/bundle.js +3 -8
  3. package/dist/actions.cjs +1 -1
  4. package/dist/actions.d.cts +3 -1400
  5. package/dist/actions.d.ts +3 -1400
  6. package/dist/actions.js +1 -1
  7. package/dist/bundle.cjs +1 -13
  8. package/dist/bundle.d.cts +4 -1927
  9. package/dist/bundle.d.ts +4 -1927
  10. package/dist/bundle.js +1 -13
  11. package/dist/computeLegacyProductId-BkyJ4rEY.d.ts +538 -0
  12. package/dist/computeLegacyProductId-Raks6FXg.d.cts +538 -0
  13. package/dist/index.cjs +1 -13
  14. package/dist/index.d.cts +3 -1269
  15. package/dist/index.d.ts +3 -1269
  16. package/dist/index.js +1 -13
  17. package/dist/openSso-BCJGchIb.d.cts +1022 -0
  18. package/dist/openSso-DG-_9CED.d.ts +1022 -0
  19. package/dist/setupClient-Cfwpu08d.js +13 -0
  20. package/dist/setupClient-Dh8ljuhV.cjs +13 -0
  21. package/dist/siweAuthenticate-BH7Dn7nZ.d.cts +536 -0
  22. package/dist/siweAuthenticate-BJHbtty4.js +1 -0
  23. package/dist/siweAuthenticate-Btem4QHs.d.ts +536 -0
  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 +25 -31
  28. package/src/actions/displayEmbeddedWallet.test.ts +194 -0
  29. package/src/actions/displayEmbeddedWallet.ts +21 -0
  30. package/src/actions/displayModal.test.ts +388 -0
  31. package/src/actions/displayModal.ts +120 -0
  32. package/src/actions/ensureIdentity.ts +68 -0
  33. package/src/actions/getMerchantInformation.test.ts +116 -0
  34. package/src/actions/getMerchantInformation.ts +16 -0
  35. package/src/actions/index.ts +30 -0
  36. package/src/actions/openSso.ts +118 -0
  37. package/src/actions/prepareSso.test.ts +223 -0
  38. package/src/actions/prepareSso.ts +48 -0
  39. package/src/actions/referral/processReferral.test.ts +248 -0
  40. package/src/actions/referral/processReferral.ts +232 -0
  41. package/src/actions/referral/referralInteraction.test.ts +147 -0
  42. package/src/actions/referral/referralInteraction.ts +52 -0
  43. package/src/actions/sendInteraction.ts +56 -0
  44. package/src/actions/trackPurchaseStatus.test.ts +500 -0
  45. package/src/actions/trackPurchaseStatus.ts +90 -0
  46. package/src/actions/watchWalletStatus.test.ts +372 -0
  47. package/src/actions/watchWalletStatus.ts +93 -0
  48. package/src/actions/wrapper/modalBuilder.test.ts +239 -0
  49. package/src/actions/wrapper/modalBuilder.ts +203 -0
  50. package/src/actions/wrapper/sendTransaction.test.ts +164 -0
  51. package/src/actions/wrapper/sendTransaction.ts +62 -0
  52. package/src/actions/wrapper/siweAuthenticate.test.ts +290 -0
  53. package/src/actions/wrapper/siweAuthenticate.ts +94 -0
  54. package/src/bundle.ts +2 -0
  55. package/src/clients/DebugInfo.test.ts +418 -0
  56. package/src/clients/DebugInfo.ts +182 -0
  57. package/src/clients/createIFrameFrakClient.ts +292 -0
  58. package/src/clients/index.ts +3 -0
  59. package/src/clients/setupClient.test.ts +343 -0
  60. package/src/clients/setupClient.ts +73 -0
  61. package/src/clients/transports/iframeLifecycleManager.test.ts +558 -0
  62. package/src/clients/transports/iframeLifecycleManager.ts +229 -0
  63. package/src/constants/interactionTypes.ts +15 -0
  64. package/src/constants/locales.ts +14 -0
  65. package/src/index.ts +109 -0
  66. package/src/types/client.ts +14 -0
  67. package/src/types/compression.ts +22 -0
  68. package/src/types/config.ts +117 -0
  69. package/src/types/context.ts +13 -0
  70. package/src/types/index.ts +74 -0
  71. package/src/types/lifecycle/client.ts +69 -0
  72. package/src/types/lifecycle/iframe.ts +41 -0
  73. package/src/types/lifecycle/index.ts +2 -0
  74. package/src/types/rpc/displayModal.ts +82 -0
  75. package/src/types/rpc/embedded/index.ts +68 -0
  76. package/src/types/rpc/embedded/loggedIn.ts +55 -0
  77. package/src/types/rpc/embedded/loggedOut.ts +28 -0
  78. package/src/types/rpc/interaction.ts +30 -0
  79. package/src/types/rpc/merchantInformation.ts +77 -0
  80. package/src/types/rpc/modal/final.ts +46 -0
  81. package/src/types/rpc/modal/generic.ts +46 -0
  82. package/src/types/rpc/modal/index.ts +16 -0
  83. package/src/types/rpc/modal/login.ts +36 -0
  84. package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
  85. package/src/types/rpc/modal/transaction.ts +33 -0
  86. package/src/types/rpc/sso.ts +80 -0
  87. package/src/types/rpc/walletStatus.ts +29 -0
  88. package/src/types/rpc.ts +150 -0
  89. package/src/types/tracking.ts +60 -0
  90. package/src/types/transport.ts +34 -0
  91. package/src/utils/FrakContext.test.ts +407 -0
  92. package/src/utils/FrakContext.ts +158 -0
  93. package/src/utils/backendUrl.test.ts +83 -0
  94. package/src/utils/backendUrl.ts +62 -0
  95. package/src/utils/clientId.test.ts +41 -0
  96. package/src/utils/clientId.ts +43 -0
  97. package/src/utils/compression/b64.test.ts +181 -0
  98. package/src/utils/compression/b64.ts +29 -0
  99. package/src/utils/compression/compress.test.ts +123 -0
  100. package/src/utils/compression/compress.ts +11 -0
  101. package/src/utils/compression/decompress.test.ts +149 -0
  102. package/src/utils/compression/decompress.ts +11 -0
  103. package/src/utils/compression/index.ts +3 -0
  104. package/src/utils/computeLegacyProductId.ts +11 -0
  105. package/src/utils/constants.test.ts +23 -0
  106. package/src/utils/constants.ts +9 -0
  107. package/src/utils/deepLinkWithFallback.test.ts +243 -0
  108. package/src/utils/deepLinkWithFallback.ts +103 -0
  109. package/src/utils/formatAmount.test.ts +113 -0
  110. package/src/utils/formatAmount.ts +24 -0
  111. package/src/utils/getCurrencyAmountKey.test.ts +44 -0
  112. package/src/utils/getCurrencyAmountKey.ts +15 -0
  113. package/src/utils/getSupportedCurrency.test.ts +51 -0
  114. package/src/utils/getSupportedCurrency.ts +14 -0
  115. package/src/utils/getSupportedLocale.test.ts +64 -0
  116. package/src/utils/getSupportedLocale.ts +16 -0
  117. package/src/utils/iframeHelper.test.ts +463 -0
  118. package/src/utils/iframeHelper.ts +150 -0
  119. package/src/utils/index.ts +36 -0
  120. package/src/utils/merchantId.test.ts +653 -0
  121. package/src/utils/merchantId.ts +143 -0
  122. package/src/utils/sso.ts +126 -0
  123. package/src/utils/ssoUrlListener.test.ts +252 -0
  124. package/src/utils/ssoUrlListener.ts +60 -0
  125. package/src/utils/trackEvent.test.ts +180 -0
  126. package/src/utils/trackEvent.ts +41 -0
  127. package/cdn/bundle.js.LICENSE.txt +0 -10
  128. package/dist/interactions.cjs +0 -1
  129. package/dist/interactions.d.cts +0 -182
  130. package/dist/interactions.d.ts +0 -182
  131. package/dist/interactions.js +0 -1
@@ -0,0 +1,463 @@
1
+ import { vi } from "vitest";
2
+
3
+ vi.mock("./clientId", () => ({
4
+ getClientId: vi.fn(() => "mock-client-id-for-test"),
5
+ }));
6
+
7
+ /**
8
+ * Tests for iframe helper utilities
9
+ * Tests iframe creation, visibility management, and finder functions
10
+ */
11
+
12
+ import {
13
+ afterEach,
14
+ beforeEach,
15
+ describe,
16
+ expect,
17
+ it,
18
+ } from "../../tests/vitest-fixtures";
19
+ import type { FrakWalletSdkConfig } from "../types";
20
+ import {
21
+ baseIframeProps,
22
+ changeIframeVisibility,
23
+ createIframe,
24
+ findIframeInOpener,
25
+ } from "./iframeHelper";
26
+
27
+ describe("iframeHelper", () => {
28
+ describe("baseIframeProps", () => {
29
+ it("should have correct id and name", () => {
30
+ expect(baseIframeProps.id).toBe("frak-wallet");
31
+ expect(baseIframeProps.name).toBe("frak-wallet");
32
+ });
33
+
34
+ it("should have correct title", () => {
35
+ expect(baseIframeProps.title).toBe("Frak Wallet");
36
+ });
37
+
38
+ it("should have correct allow attribute", () => {
39
+ expect(baseIframeProps.allow).toContain(
40
+ "publickey-credentials-get"
41
+ );
42
+ expect(baseIframeProps.allow).toContain("clipboard-write");
43
+ expect(baseIframeProps.allow).toContain("web-share");
44
+ });
45
+
46
+ it("should have correct initial style", () => {
47
+ expect(baseIframeProps.style.width).toBe("0");
48
+ expect(baseIframeProps.style.height).toBe("0");
49
+ expect(baseIframeProps.style.border).toBe("0");
50
+ expect(baseIframeProps.style.position).toBe("absolute");
51
+ expect(baseIframeProps.style.zIndex).toBe(2000001);
52
+ });
53
+ });
54
+
55
+ describe("createIframe", () => {
56
+ let mockIframe: HTMLIFrameElement;
57
+ let appendChildSpy: ReturnType<typeof vi.fn>;
58
+ let querySelectorSpy: ReturnType<typeof vi.fn>;
59
+ let createElementSpy: ReturnType<typeof vi.fn>;
60
+
61
+ beforeEach(() => {
62
+ // Create mock iframe
63
+ mockIframe = {
64
+ id: "",
65
+ name: "",
66
+ allow: "",
67
+ src: "",
68
+ style: {} as CSSStyleDeclaration,
69
+ addEventListener: vi.fn((event, handler) => {
70
+ if (event === "load") {
71
+ // Simulate immediate load
72
+ setTimeout(() => handler(), 0);
73
+ }
74
+ }),
75
+ remove: vi.fn(),
76
+ } as unknown as HTMLIFrameElement;
77
+
78
+ // Mock document methods
79
+ createElementSpy = vi
80
+ .spyOn(document, "createElement")
81
+ .mockReturnValue(mockIframe);
82
+ querySelectorSpy = vi
83
+ .spyOn(document, "querySelector")
84
+ .mockReturnValue(null);
85
+ appendChildSpy = vi
86
+ .spyOn(document.body, "appendChild")
87
+ .mockReturnValue(mockIframe);
88
+ });
89
+
90
+ afterEach(() => {
91
+ createElementSpy.mockRestore();
92
+ querySelectorSpy.mockRestore();
93
+ appendChildSpy.mockRestore();
94
+ });
95
+
96
+ it("should create iframe with correct properties", async () => {
97
+ await createIframe({});
98
+
99
+ expect(document.createElement).toHaveBeenCalledWith("iframe");
100
+ expect(mockIframe.id).toBe("frak-wallet");
101
+ expect(mockIframe.name).toBe("frak-wallet");
102
+ expect(mockIframe.allow).toContain("publickey-credentials-get");
103
+ });
104
+
105
+ it("should append iframe to document body", async () => {
106
+ await createIframe({});
107
+
108
+ expect(document.body.appendChild).toHaveBeenCalledWith(mockIframe);
109
+ });
110
+
111
+ it("should set iframe src to default wallet URL", async () => {
112
+ await createIframe({});
113
+
114
+ expect(mockIframe.src).toBe(
115
+ "https://wallet.frak.id/listener?clientId=mock-client-id-for-test"
116
+ );
117
+ });
118
+
119
+ it("should use config walletUrl when provided", async () => {
120
+ const config: FrakWalletSdkConfig = {
121
+ walletUrl: "https://custom-wallet.com",
122
+ metadata: { name: "Test" },
123
+ };
124
+
125
+ await createIframe({ config });
126
+
127
+ expect(mockIframe.src).toBe(
128
+ "https://custom-wallet.com/listener?clientId=mock-client-id-for-test"
129
+ );
130
+ });
131
+
132
+ it("should use deprecated walletBaseUrl when provided", async () => {
133
+ await createIframe({ walletBaseUrl: "https://legacy-wallet.com" });
134
+
135
+ expect(mockIframe.src).toBe(
136
+ "https://legacy-wallet.com/listener?clientId=mock-client-id-for-test"
137
+ );
138
+ });
139
+
140
+ it("should prefer config.walletUrl over walletBaseUrl", async () => {
141
+ const config: FrakWalletSdkConfig = {
142
+ walletUrl: "https://new-wallet.com",
143
+ metadata: { name: "Test" },
144
+ };
145
+
146
+ await createIframe({
147
+ walletBaseUrl: "https://old-wallet.com",
148
+ config,
149
+ });
150
+
151
+ expect(mockIframe.src).toBe(
152
+ "https://new-wallet.com/listener?clientId=mock-client-id-for-test"
153
+ );
154
+ });
155
+
156
+ it("should remove existing iframe before creating new one", async () => {
157
+ const existingIframe = {
158
+ remove: vi.fn(),
159
+ } as unknown as HTMLIFrameElement;
160
+
161
+ querySelectorSpy.mockReturnValue(existingIframe);
162
+
163
+ await createIframe({});
164
+
165
+ expect(document.querySelector).toHaveBeenCalledWith("#frak-wallet");
166
+ expect(existingIframe.remove).toHaveBeenCalled();
167
+ });
168
+
169
+ it("should resolve promise on iframe load", async () => {
170
+ const result = await createIframe({});
171
+
172
+ expect(result).toBe(mockIframe);
173
+ });
174
+
175
+ it("should set iframe as initially hidden", async () => {
176
+ await createIframe({});
177
+
178
+ // Check that hidden styles were applied
179
+ expect(mockIframe.style.width).toBe("0");
180
+ expect(mockIframe.style.height).toBe("0");
181
+ });
182
+
183
+ it("should set zIndex from baseIframeProps", async () => {
184
+ await createIframe({});
185
+
186
+ expect(mockIframe.style.zIndex).toBe("2000001");
187
+ });
188
+ });
189
+
190
+ describe("changeIframeVisibility", () => {
191
+ let mockIframe: HTMLIFrameElement;
192
+
193
+ beforeEach(() => {
194
+ mockIframe = {
195
+ style: {} as CSSStyleDeclaration,
196
+ } as HTMLIFrameElement;
197
+ });
198
+
199
+ describe("when hiding iframe (isVisible: false)", () => {
200
+ it("should set width and height to 0", () => {
201
+ changeIframeVisibility({
202
+ iframe: mockIframe,
203
+ isVisible: false,
204
+ });
205
+
206
+ expect(mockIframe.style.width).toBe("0");
207
+ expect(mockIframe.style.height).toBe("0");
208
+ });
209
+
210
+ it("should set border to 0", () => {
211
+ changeIframeVisibility({
212
+ iframe: mockIframe,
213
+ isVisible: false,
214
+ });
215
+
216
+ expect(mockIframe.style.border).toBe("0");
217
+ });
218
+
219
+ it("should set position to fixed", () => {
220
+ changeIframeVisibility({
221
+ iframe: mockIframe,
222
+ isVisible: false,
223
+ });
224
+
225
+ expect(mockIframe.style.position).toBe("fixed");
226
+ });
227
+
228
+ it("should move iframe off-screen", () => {
229
+ changeIframeVisibility({
230
+ iframe: mockIframe,
231
+ isVisible: false,
232
+ });
233
+
234
+ expect(mockIframe.style.top).toBe("-1000px");
235
+ expect(mockIframe.style.left).toBe("-1000px");
236
+ });
237
+ });
238
+
239
+ describe("when showing iframe (isVisible: true)", () => {
240
+ it("should set full screen dimensions", () => {
241
+ changeIframeVisibility({ iframe: mockIframe, isVisible: true });
242
+
243
+ expect(mockIframe.style.width).toBe("100%");
244
+ expect(mockIframe.style.height).toBe("100%");
245
+ });
246
+
247
+ it("should position at top-left", () => {
248
+ changeIframeVisibility({ iframe: mockIframe, isVisible: true });
249
+
250
+ expect(mockIframe.style.top).toBe("0");
251
+ expect(mockIframe.style.left).toBe("0");
252
+ });
253
+
254
+ it("should set position to fixed", () => {
255
+ changeIframeVisibility({ iframe: mockIframe, isVisible: true });
256
+
257
+ expect(mockIframe.style.position).toBe("fixed");
258
+ });
259
+
260
+ it("should enable pointer events", () => {
261
+ changeIframeVisibility({ iframe: mockIframe, isVisible: true });
262
+
263
+ expect(mockIframe.style.pointerEvents).toBe("auto");
264
+ });
265
+ });
266
+
267
+ describe("toggling visibility", () => {
268
+ it("should hide then show correctly", () => {
269
+ changeIframeVisibility({ iframe: mockIframe, isVisible: true });
270
+ expect(mockIframe.style.width).toBe("100%");
271
+
272
+ changeIframeVisibility({
273
+ iframe: mockIframe,
274
+ isVisible: false,
275
+ });
276
+ expect(mockIframe.style.width).toBe("0");
277
+ });
278
+
279
+ it("should show then hide correctly", () => {
280
+ changeIframeVisibility({
281
+ iframe: mockIframe,
282
+ isVisible: false,
283
+ });
284
+ expect(mockIframe.style.top).toBe("-1000px");
285
+
286
+ changeIframeVisibility({ iframe: mockIframe, isVisible: true });
287
+ expect(mockIframe.style.top).toBe("0");
288
+ });
289
+ });
290
+ });
291
+
292
+ describe("findIframeInOpener", () => {
293
+ let originalOpener: Window;
294
+ let consoleErrorSpy: any;
295
+
296
+ beforeEach(() => {
297
+ originalOpener = window.opener;
298
+ consoleErrorSpy = vi
299
+ .spyOn(console, "error")
300
+ .mockImplementation(() => {});
301
+ });
302
+
303
+ afterEach(() => {
304
+ window.opener = originalOpener;
305
+ consoleErrorSpy.mockRestore();
306
+ });
307
+
308
+ it("should return null when window.opener is not available", () => {
309
+ window.opener = null;
310
+
311
+ const result = findIframeInOpener();
312
+
313
+ expect(result).toBeNull();
314
+ });
315
+
316
+ it("should find iframe in window.opener with default pathname", () => {
317
+ const mockOpener = {
318
+ location: {
319
+ origin: window.location.origin,
320
+ pathname: "/listener",
321
+ },
322
+ frames: [],
323
+ } as unknown as Window;
324
+
325
+ window.opener = mockOpener;
326
+
327
+ const result = findIframeInOpener();
328
+
329
+ expect(result).toBe(mockOpener);
330
+ });
331
+
332
+ it("should find iframe with custom pathname", () => {
333
+ const mockOpener = {
334
+ location: {
335
+ origin: window.location.origin,
336
+ pathname: "/custom-iframe",
337
+ },
338
+ frames: [],
339
+ } as unknown as Window;
340
+
341
+ window.opener = mockOpener;
342
+
343
+ const result = findIframeInOpener("/custom-iframe");
344
+
345
+ expect(result).toBe(mockOpener);
346
+ });
347
+
348
+ it("should search through frames in window.opener", () => {
349
+ const matchingFrame = {
350
+ location: {
351
+ origin: window.location.origin,
352
+ pathname: "/listener",
353
+ },
354
+ } as unknown as Window;
355
+
356
+ const nonMatchingFrame = {
357
+ location: {
358
+ origin: window.location.origin,
359
+ pathname: "/other",
360
+ },
361
+ } as unknown as Window;
362
+
363
+ const mockOpener = {
364
+ location: {
365
+ origin: window.location.origin,
366
+ pathname: "/parent",
367
+ },
368
+ frames: [nonMatchingFrame, matchingFrame],
369
+ length: 2,
370
+ } as unknown as Window;
371
+
372
+ window.opener = mockOpener;
373
+
374
+ const result = findIframeInOpener();
375
+
376
+ expect(result).toBe(matchingFrame);
377
+ });
378
+
379
+ it("should return null when no matching frame is found", () => {
380
+ const mockOpener = {
381
+ location: {
382
+ origin: window.location.origin,
383
+ pathname: "/wrong",
384
+ },
385
+ frames: [],
386
+ } as unknown as Window;
387
+
388
+ window.opener = mockOpener;
389
+
390
+ const result = findIframeInOpener("/listener");
391
+
392
+ expect(result).toBeNull();
393
+ });
394
+
395
+ it("should handle cross-origin frames gracefully", () => {
396
+ const crossOriginFrame = {
397
+ get location() {
398
+ throw new Error(
399
+ "SecurityError: Blocked a frame with origin"
400
+ );
401
+ },
402
+ } as unknown as Window;
403
+
404
+ const mockOpener = {
405
+ location: {
406
+ origin: window.location.origin,
407
+ pathname: "/parent",
408
+ },
409
+ frames: [crossOriginFrame],
410
+ length: 1,
411
+ } as unknown as Window;
412
+
413
+ window.opener = mockOpener;
414
+
415
+ const result = findIframeInOpener();
416
+
417
+ expect(result).toBeNull();
418
+ });
419
+
420
+ it("should handle errors during frame search", () => {
421
+ const mockOpener = {
422
+ location: {
423
+ origin: window.location.origin,
424
+ pathname: "/parent",
425
+ },
426
+ get frames() {
427
+ throw new Error("Access denied");
428
+ },
429
+ } as unknown as Window;
430
+
431
+ window.opener = mockOpener;
432
+
433
+ const result = findIframeInOpener();
434
+
435
+ expect(result).toBeNull();
436
+ expect(consoleErrorSpy).toHaveBeenCalled();
437
+ });
438
+
439
+ it("should match origin correctly", () => {
440
+ const wrongOriginFrame = {
441
+ location: {
442
+ origin: "https://different-origin.com",
443
+ pathname: "/listener",
444
+ },
445
+ } as unknown as Window;
446
+
447
+ const mockOpener = {
448
+ location: {
449
+ origin: "https://different-origin.com",
450
+ pathname: "/parent",
451
+ },
452
+ frames: [wrongOriginFrame],
453
+ length: 1,
454
+ } as unknown as Window;
455
+
456
+ window.opener = mockOpener;
457
+
458
+ const result = findIframeInOpener();
459
+
460
+ expect(result).toBeNull();
461
+ });
462
+ });
463
+ });
@@ -0,0 +1,150 @@
1
+ import type { FrakWalletSdkConfig } from "../types";
2
+ import { getClientId } from "./clientId";
3
+
4
+ /**
5
+ * Base props for the iframe
6
+ * @ignore
7
+ */
8
+ export const baseIframeProps = {
9
+ id: "frak-wallet",
10
+ name: "frak-wallet",
11
+ title: "Frak Wallet",
12
+ allow: "publickey-credentials-get *; clipboard-write; web-share *",
13
+ style: {
14
+ width: "0",
15
+ height: "0",
16
+ border: "0",
17
+ position: "absolute",
18
+ zIndex: 2000001,
19
+ top: "-1000px",
20
+ left: "-1000px",
21
+ colorScheme: "auto",
22
+ },
23
+ };
24
+
25
+ /**
26
+ * Create the Frak iframe
27
+ * @param args
28
+ * @param args.walletBaseUrl - Use `config.walletUrl` instead. Will be removed in future versions.
29
+ * @param args.config - The configuration object containing iframe options, including the replacement for `walletBaseUrl`.
30
+ */
31
+ export function createIframe({
32
+ walletBaseUrl,
33
+ config,
34
+ }: {
35
+ walletBaseUrl?: string;
36
+ config?: FrakWalletSdkConfig;
37
+ }): Promise<HTMLIFrameElement | undefined> {
38
+ // Check if the iframe is already created
39
+ const alreadyCreatedIFrame = document.querySelector("#frak-wallet");
40
+
41
+ // If the iframe is already created, remove it
42
+ if (alreadyCreatedIFrame) {
43
+ alreadyCreatedIFrame.remove();
44
+ }
45
+
46
+ const iframe = document.createElement("iframe");
47
+
48
+ // Set the base iframe props
49
+ iframe.id = baseIframeProps.id;
50
+ iframe.name = baseIframeProps.name;
51
+ iframe.allow = baseIframeProps.allow;
52
+ iframe.style.zIndex = baseIframeProps.style.zIndex.toString();
53
+
54
+ changeIframeVisibility({ iframe, isVisible: false });
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)}`;
61
+
62
+ return new Promise((resolve) => {
63
+ iframe.addEventListener("load", () => resolve(iframe));
64
+ document.body.appendChild(iframe);
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Change the visibility of the given iframe
70
+ * @ignore
71
+ */
72
+ export function changeIframeVisibility({
73
+ iframe,
74
+ isVisible,
75
+ }: {
76
+ iframe: HTMLIFrameElement;
77
+ isVisible: boolean;
78
+ }) {
79
+ if (!isVisible) {
80
+ iframe.style.width = "0";
81
+ iframe.style.height = "0";
82
+ iframe.style.border = "0";
83
+ iframe.style.position = "fixed";
84
+ iframe.style.top = "-1000px";
85
+ iframe.style.left = "-1000px";
86
+ return;
87
+ }
88
+
89
+ iframe.style.position = "fixed";
90
+ iframe.style.top = "0";
91
+ iframe.style.left = "0";
92
+ iframe.style.width = "100%";
93
+ iframe.style.height = "100%";
94
+ iframe.style.pointerEvents = "auto";
95
+ }
96
+
97
+ /**
98
+ * Find an iframe within window.opener by pathname
99
+ *
100
+ * When a popup is opened via window.open from an iframe, window.opener points to
101
+ * the parent window, not the iframe itself. This utility searches through all frames
102
+ * in window.opener to find an iframe matching the specified pathname.
103
+ *
104
+ * @param pathname - The pathname to search for (default: "/listener")
105
+ * @returns The matching iframe window, or null if not found
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * // Find the default /listener iframe
110
+ * const listenerIframe = findIframeInOpener();
111
+ *
112
+ * // Find a custom iframe
113
+ * const customIframe = findIframeInOpener("/my-custom-iframe");
114
+ * ```
115
+ */
116
+ export function findIframeInOpener(pathname = "/listener"): Window | null {
117
+ if (!window.opener) return null;
118
+
119
+ const frameCheck = (frame: Window) => {
120
+ try {
121
+ return (
122
+ frame.location.origin === window.location.origin &&
123
+ frame.location.pathname === pathname
124
+ );
125
+ } catch {
126
+ // Cross-origin frame, skip
127
+ return false;
128
+ }
129
+ };
130
+
131
+ // Check if the openner window is the right one
132
+ if (frameCheck(window.opener)) return window.opener;
133
+
134
+ // Search through frames in window.opener
135
+ try {
136
+ const frames = window.opener.frames;
137
+ for (let i = 0; i < frames.length; i++) {
138
+ if (frameCheck(frames[i])) {
139
+ return frames[i];
140
+ }
141
+ }
142
+ return null;
143
+ } catch (error) {
144
+ console.error(
145
+ `[findIframeInOpener] Error finding iframe with pathname ${pathname}:`,
146
+ error
147
+ );
148
+ return null;
149
+ }
150
+ }
@@ -0,0 +1,36 @@
1
+ export { Deferred } from "@frak-labs/frame-connector";
2
+ export { getBackendUrl } from "./backendUrl";
3
+ export { getClientId } from "./clientId";
4
+ export { base64urlDecode, base64urlEncode } from "./compression/b64";
5
+ export { compressJsonToB64 } from "./compression/compress";
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";
15
+ export { FrakContextManager } from "./FrakContext";
16
+ export { formatAmount } from "./formatAmount";
17
+ export { getCurrencyAmountKey } from "./getCurrencyAmountKey";
18
+ export { getSupportedCurrency } from "./getSupportedCurrency";
19
+ export { getSupportedLocale } from "./getSupportedLocale";
20
+ export {
21
+ baseIframeProps,
22
+ createIframe,
23
+ findIframeInOpener,
24
+ } from "./iframeHelper";
25
+ export {
26
+ clearMerchantIdCache,
27
+ fetchMerchantId,
28
+ resolveMerchantId,
29
+ } from "./merchantId";
30
+ export {
31
+ type AppSpecificSsoMetadata,
32
+ type CompressedSsoData,
33
+ type FullSsoParams,
34
+ generateSsoUrl,
35
+ } from "./sso";
36
+ export { type FrakEvent, trackEvent } from "./trackEvent";