@frak-labs/core-sdk 0.1.0-beta.afa252b0 → 0.1.0-beta.d9302e66
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.
- package/package.json +22 -17
- package/src/actions/displayEmbeddedWallet.test.ts +194 -0
- package/src/actions/displayEmbeddedWallet.ts +20 -0
- package/src/actions/displayModal.test.ts +387 -0
- package/src/actions/displayModal.ts +131 -0
- package/src/actions/getProductInformation.test.ts +133 -0
- package/src/actions/getProductInformation.ts +14 -0
- package/src/actions/index.ts +29 -0
- package/src/actions/openSso.test.ts +407 -0
- package/src/actions/openSso.ts +116 -0
- package/src/actions/prepareSso.test.ts +223 -0
- package/src/actions/prepareSso.ts +48 -0
- package/src/actions/referral/processReferral.ts +230 -0
- package/src/actions/referral/referralInteraction.ts +57 -0
- package/src/actions/sendInteraction.test.ts +219 -0
- package/src/actions/sendInteraction.ts +32 -0
- package/src/actions/trackPurchaseStatus.test.ts +287 -0
- package/src/actions/trackPurchaseStatus.ts +53 -0
- package/src/actions/watchWalletStatus.test.ts +372 -0
- package/src/actions/watchWalletStatus.ts +94 -0
- package/src/actions/wrapper/modalBuilder.ts +212 -0
- package/src/actions/wrapper/sendTransaction.ts +62 -0
- package/src/actions/wrapper/siweAuthenticate.ts +94 -0
- package/src/bundle.ts +3 -0
- package/src/clients/DebugInfo.ts +182 -0
- package/src/clients/createIFrameFrakClient.ts +287 -0
- package/src/clients/index.ts +3 -0
- package/src/clients/setupClient.ts +73 -0
- package/src/clients/transports/iframeLifecycleManager.ts +90 -0
- package/src/constants/interactionTypes.ts +44 -0
- package/src/constants/locales.ts +14 -0
- package/src/constants/productTypes.ts +33 -0
- package/src/index.ts +101 -0
- package/src/interactions/index.ts +5 -0
- package/src/interactions/pressEncoder.test.ts +215 -0
- package/src/interactions/pressEncoder.ts +53 -0
- package/src/interactions/purchaseEncoder.test.ts +291 -0
- package/src/interactions/purchaseEncoder.ts +99 -0
- package/src/interactions/referralEncoder.test.ts +170 -0
- package/src/interactions/referralEncoder.ts +47 -0
- package/src/interactions/retailEncoder.test.ts +107 -0
- package/src/interactions/retailEncoder.ts +37 -0
- package/src/interactions/webshopEncoder.test.ts +56 -0
- package/src/interactions/webshopEncoder.ts +30 -0
- package/src/types/client.ts +14 -0
- package/src/types/compression.ts +22 -0
- package/src/types/config.ts +111 -0
- package/src/types/context.ts +13 -0
- package/src/types/index.ts +71 -0
- package/src/types/lifecycle/client.ts +46 -0
- package/src/types/lifecycle/iframe.ts +35 -0
- package/src/types/lifecycle/index.ts +2 -0
- package/src/types/rpc/displayModal.ts +84 -0
- package/src/types/rpc/embedded/index.ts +68 -0
- package/src/types/rpc/embedded/loggedIn.ts +55 -0
- package/src/types/rpc/embedded/loggedOut.ts +28 -0
- package/src/types/rpc/interaction.ts +43 -0
- package/src/types/rpc/modal/final.ts +46 -0
- package/src/types/rpc/modal/generic.ts +46 -0
- package/src/types/rpc/modal/index.ts +20 -0
- package/src/types/rpc/modal/login.ts +32 -0
- package/src/types/rpc/modal/openSession.ts +25 -0
- package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
- package/src/types/rpc/modal/transaction.ts +33 -0
- package/src/types/rpc/productInformation.ts +59 -0
- package/src/types/rpc/sso.ts +80 -0
- package/src/types/rpc/walletStatus.ts +35 -0
- package/src/types/rpc.ts +158 -0
- package/src/types/transport.ts +34 -0
- package/src/utils/FrakContext.test.ts +338 -0
- package/src/utils/FrakContext.ts +158 -0
- package/src/utils/compression/b64.test.ts +181 -0
- package/src/utils/compression/b64.ts +29 -0
- package/src/utils/compression/compress.test.ts +123 -0
- package/src/utils/compression/compress.ts +11 -0
- package/src/utils/compression/decompress.test.ts +145 -0
- package/src/utils/compression/decompress.ts +11 -0
- package/src/utils/compression/index.ts +3 -0
- package/src/utils/computeProductId.test.ts +80 -0
- package/src/utils/computeProductId.ts +11 -0
- package/src/utils/constants.test.ts +23 -0
- package/src/utils/constants.ts +4 -0
- package/src/utils/formatAmount.test.ts +113 -0
- package/src/utils/formatAmount.ts +18 -0
- package/src/utils/getCurrencyAmountKey.test.ts +44 -0
- package/src/utils/getCurrencyAmountKey.ts +15 -0
- package/src/utils/getSupportedCurrency.test.ts +51 -0
- package/src/utils/getSupportedCurrency.ts +14 -0
- package/src/utils/getSupportedLocale.test.ts +64 -0
- package/src/utils/getSupportedLocale.ts +16 -0
- package/src/utils/iframeHelper.test.ts +450 -0
- package/src/utils/iframeHelper.ts +143 -0
- package/src/utils/index.ts +21 -0
- package/src/utils/sso.test.ts +361 -0
- package/src/utils/sso.ts +119 -0
- package/src/utils/ssoUrlListener.ts +60 -0
- package/src/utils/trackEvent.test.ts +162 -0
- package/src/utils/trackEvent.ts +26 -0
- package/cdn/bundle.js +0 -19
- package/cdn/bundle.js.LICENSE.txt +0 -10
- package/dist/actions.cjs +0 -1
- package/dist/actions.d.cts +0 -1481
- package/dist/actions.d.ts +0 -1481
- package/dist/actions.js +0 -1
- package/dist/bundle.cjs +0 -13
- package/dist/bundle.d.cts +0 -2087
- package/dist/bundle.d.ts +0 -2087
- package/dist/bundle.js +0 -13
- package/dist/index.cjs +0 -13
- package/dist/index.d.cts +0 -1387
- package/dist/index.d.ts +0 -1387
- package/dist/index.js +0 -13
- package/dist/interactions.cjs +0 -1
- package/dist/interactions.d.cts +0 -182
- package/dist/interactions.d.ts +0 -182
- 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,143 @@
|
|
|
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
|
+
document.body.appendChild(iframe);
|
|
55
|
+
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
iframe?.addEventListener("load", () => resolve(iframe));
|
|
58
|
+
iframe.src = `${config?.walletUrl ?? walletBaseUrl ?? "https://wallet.frak.id"}/listener`;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Change the visibility of the given iframe
|
|
63
|
+
* @ignore
|
|
64
|
+
*/
|
|
65
|
+
export function changeIframeVisibility({
|
|
66
|
+
iframe,
|
|
67
|
+
isVisible,
|
|
68
|
+
}: {
|
|
69
|
+
iframe: HTMLIFrameElement;
|
|
70
|
+
isVisible: boolean;
|
|
71
|
+
}) {
|
|
72
|
+
if (!isVisible) {
|
|
73
|
+
iframe.style.width = "0";
|
|
74
|
+
iframe.style.height = "0";
|
|
75
|
+
iframe.style.border = "0";
|
|
76
|
+
iframe.style.position = "fixed";
|
|
77
|
+
iframe.style.top = "-1000px";
|
|
78
|
+
iframe.style.left = "-1000px";
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
iframe.style.position = "fixed";
|
|
83
|
+
iframe.style.top = "0";
|
|
84
|
+
iframe.style.left = "0";
|
|
85
|
+
iframe.style.width = "100%";
|
|
86
|
+
iframe.style.height = "100%";
|
|
87
|
+
iframe.style.pointerEvents = "auto";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find an iframe within window.opener by pathname
|
|
92
|
+
*
|
|
93
|
+
* When a popup is opened via window.open from an iframe, window.opener points to
|
|
94
|
+
* the parent window, not the iframe itself. This utility searches through all frames
|
|
95
|
+
* in window.opener to find an iframe matching the specified pathname.
|
|
96
|
+
*
|
|
97
|
+
* @param pathname - The pathname to search for (default: "/listener")
|
|
98
|
+
* @returns The matching iframe window, or null if not found
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* // Find the default /listener iframe
|
|
103
|
+
* const listenerIframe = findIframeInOpener();
|
|
104
|
+
*
|
|
105
|
+
* // Find a custom iframe
|
|
106
|
+
* const customIframe = findIframeInOpener("/my-custom-iframe");
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function findIframeInOpener(pathname = "/listener"): Window | null {
|
|
110
|
+
if (!window.opener) return null;
|
|
111
|
+
|
|
112
|
+
const frameCheck = (frame: Window) => {
|
|
113
|
+
try {
|
|
114
|
+
return (
|
|
115
|
+
frame.location.origin === window.location.origin &&
|
|
116
|
+
frame.location.pathname === pathname
|
|
117
|
+
);
|
|
118
|
+
} catch {
|
|
119
|
+
// Cross-origin frame, skip
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Check if the openner window is the right one
|
|
125
|
+
if (frameCheck(window.opener)) return window.opener;
|
|
126
|
+
|
|
127
|
+
// Search through frames in window.opener
|
|
128
|
+
try {
|
|
129
|
+
const frames = window.opener.frames;
|
|
130
|
+
for (let i = 0; i < frames.length; i++) {
|
|
131
|
+
if (frameCheck(frames[i])) {
|
|
132
|
+
return frames[i];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(
|
|
138
|
+
`[findIframeInOpener] Error finding iframe with pathname ${pathname}:`,
|
|
139
|
+
error
|
|
140
|
+
);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { Deferred } from "@frak-labs/frame-connector";
|
|
2
|
+
export { base64urlDecode, base64urlEncode } from "./compression/b64";
|
|
3
|
+
export { compressJsonToB64 } from "./compression/compress";
|
|
4
|
+
export { decompressJsonFromB64 } from "./compression/decompress";
|
|
5
|
+
export { FrakContextManager } from "./FrakContext";
|
|
6
|
+
export { formatAmount } from "./formatAmount";
|
|
7
|
+
export { getCurrencyAmountKey } from "./getCurrencyAmountKey";
|
|
8
|
+
export { getSupportedCurrency } from "./getSupportedCurrency";
|
|
9
|
+
export { getSupportedLocale } from "./getSupportedLocale";
|
|
10
|
+
export {
|
|
11
|
+
baseIframeProps,
|
|
12
|
+
createIframe,
|
|
13
|
+
findIframeInOpener,
|
|
14
|
+
} from "./iframeHelper";
|
|
15
|
+
export {
|
|
16
|
+
type AppSpecificSsoMetadata,
|
|
17
|
+
type CompressedSsoData,
|
|
18
|
+
type FullSsoParams,
|
|
19
|
+
generateSsoUrl,
|
|
20
|
+
} from "./sso";
|
|
21
|
+
export { trackEvent } from "./trackEvent";
|