@frak-labs/core-sdk 0.1.0 → 0.1.1-beta.4dfea079

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