@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.
- package/README.md +58 -0
- package/cdn/bundle.js +3 -8
- package/dist/actions.cjs +1 -1
- package/dist/actions.d.cts +3 -1400
- package/dist/actions.d.ts +3 -1400
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -13
- package/dist/bundle.d.cts +4 -1927
- package/dist/bundle.d.ts +4 -1927
- package/dist/bundle.js +1 -13
- package/dist/computeLegacyProductId-BkyJ4rEY.d.ts +538 -0
- package/dist/computeLegacyProductId-Raks6FXg.d.cts +538 -0
- package/dist/index.cjs +1 -13
- package/dist/index.d.cts +3 -1269
- package/dist/index.d.ts +3 -1269
- package/dist/index.js +1 -13
- package/dist/openSso-BCJGchIb.d.cts +1022 -0
- package/dist/openSso-DG-_9CED.d.ts +1022 -0
- package/dist/setupClient-Cfwpu08d.js +13 -0
- package/dist/setupClient-Dh8ljuhV.cjs +13 -0
- package/dist/siweAuthenticate-BH7Dn7nZ.d.cts +536 -0
- package/dist/siweAuthenticate-BJHbtty4.js +1 -0
- package/dist/siweAuthenticate-Btem4QHs.d.ts +536 -0
- package/dist/siweAuthenticate-Cwj3HP0m.cjs +1 -0
- package/dist/trackEvent-M2RLTQ2p.js +1 -0
- package/dist/trackEvent-T_R9ER2S.cjs +1 -0
- package/package.json +25 -31
- package/src/actions/displayEmbeddedWallet.test.ts +194 -0
- package/src/actions/displayEmbeddedWallet.ts +21 -0
- package/src/actions/displayModal.test.ts +388 -0
- package/src/actions/displayModal.ts +120 -0
- package/src/actions/ensureIdentity.ts +68 -0
- package/src/actions/getMerchantInformation.test.ts +116 -0
- package/src/actions/getMerchantInformation.ts +16 -0
- package/src/actions/index.ts +30 -0
- package/src/actions/openSso.ts +118 -0
- package/src/actions/prepareSso.test.ts +223 -0
- package/src/actions/prepareSso.ts +48 -0
- package/src/actions/referral/processReferral.test.ts +248 -0
- package/src/actions/referral/processReferral.ts +232 -0
- package/src/actions/referral/referralInteraction.test.ts +147 -0
- package/src/actions/referral/referralInteraction.ts +52 -0
- package/src/actions/sendInteraction.ts +56 -0
- package/src/actions/trackPurchaseStatus.test.ts +500 -0
- package/src/actions/trackPurchaseStatus.ts +90 -0
- package/src/actions/watchWalletStatus.test.ts +372 -0
- package/src/actions/watchWalletStatus.ts +93 -0
- package/src/actions/wrapper/modalBuilder.test.ts +239 -0
- package/src/actions/wrapper/modalBuilder.ts +203 -0
- package/src/actions/wrapper/sendTransaction.test.ts +164 -0
- package/src/actions/wrapper/sendTransaction.ts +62 -0
- package/src/actions/wrapper/siweAuthenticate.test.ts +290 -0
- package/src/actions/wrapper/siweAuthenticate.ts +94 -0
- package/src/bundle.ts +2 -0
- package/src/clients/DebugInfo.test.ts +418 -0
- package/src/clients/DebugInfo.ts +182 -0
- package/src/clients/createIFrameFrakClient.ts +292 -0
- package/src/clients/index.ts +3 -0
- package/src/clients/setupClient.test.ts +343 -0
- package/src/clients/setupClient.ts +73 -0
- package/src/clients/transports/iframeLifecycleManager.test.ts +558 -0
- package/src/clients/transports/iframeLifecycleManager.ts +229 -0
- package/src/constants/interactionTypes.ts +15 -0
- package/src/constants/locales.ts +14 -0
- package/src/index.ts +109 -0
- package/src/types/client.ts +14 -0
- package/src/types/compression.ts +22 -0
- package/src/types/config.ts +117 -0
- package/src/types/context.ts +13 -0
- package/src/types/index.ts +74 -0
- package/src/types/lifecycle/client.ts +69 -0
- package/src/types/lifecycle/iframe.ts +41 -0
- package/src/types/lifecycle/index.ts +2 -0
- package/src/types/rpc/displayModal.ts +82 -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 +30 -0
- package/src/types/rpc/merchantInformation.ts +77 -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 +16 -0
- package/src/types/rpc/modal/login.ts +36 -0
- package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
- package/src/types/rpc/modal/transaction.ts +33 -0
- package/src/types/rpc/sso.ts +80 -0
- package/src/types/rpc/walletStatus.ts +29 -0
- package/src/types/rpc.ts +150 -0
- package/src/types/tracking.ts +60 -0
- package/src/types/transport.ts +34 -0
- package/src/utils/FrakContext.test.ts +407 -0
- package/src/utils/FrakContext.ts +158 -0
- package/src/utils/backendUrl.test.ts +83 -0
- package/src/utils/backendUrl.ts +62 -0
- package/src/utils/clientId.test.ts +41 -0
- package/src/utils/clientId.ts +43 -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 +149 -0
- package/src/utils/compression/decompress.ts +11 -0
- package/src/utils/compression/index.ts +3 -0
- package/src/utils/computeLegacyProductId.ts +11 -0
- package/src/utils/constants.test.ts +23 -0
- package/src/utils/constants.ts +9 -0
- package/src/utils/deepLinkWithFallback.test.ts +243 -0
- package/src/utils/deepLinkWithFallback.ts +103 -0
- package/src/utils/formatAmount.test.ts +113 -0
- package/src/utils/formatAmount.ts +24 -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 +463 -0
- package/src/utils/iframeHelper.ts +150 -0
- package/src/utils/index.ts +36 -0
- package/src/utils/merchantId.test.ts +653 -0
- package/src/utils/merchantId.ts +143 -0
- package/src/utils/sso.ts +126 -0
- package/src/utils/ssoUrlListener.test.ts +252 -0
- package/src/utils/ssoUrlListener.ts +60 -0
- package/src/utils/trackEvent.test.ts +180 -0
- package/src/utils/trackEvent.ts +41 -0
- package/cdn/bundle.js.LICENSE.txt +0 -10
- 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,73 @@
|
|
|
1
|
+
import { createIFrameFrakClient } from "../clients";
|
|
2
|
+
import type { FrakClient, FrakWalletSdkConfig } from "../types";
|
|
3
|
+
import { createIframe, getSupportedCurrency } from "../utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Directly setup the Frak client with an iframe
|
|
7
|
+
* Return when the FrakClient is ready (setup and communication estbalished with the wallet)
|
|
8
|
+
*
|
|
9
|
+
* @param config - The configuration to use for the Frak Wallet SDK
|
|
10
|
+
* @returns a Promise with the Frak Client
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const frakConfig: FrakWalletSdkConfig = {
|
|
14
|
+
* metadata: {
|
|
15
|
+
* name: "My app title",
|
|
16
|
+
* },
|
|
17
|
+
* }
|
|
18
|
+
* const client = await setupClient({ config: frakConfig });
|
|
19
|
+
*/
|
|
20
|
+
export async function setupClient({
|
|
21
|
+
config,
|
|
22
|
+
}: {
|
|
23
|
+
config: FrakWalletSdkConfig;
|
|
24
|
+
}): Promise<FrakClient | undefined> {
|
|
25
|
+
// Prepare the config
|
|
26
|
+
const preparedConfig = prepareConfig(config);
|
|
27
|
+
|
|
28
|
+
// Create our iframe
|
|
29
|
+
const iframe = await createIframe({
|
|
30
|
+
config: preparedConfig,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!iframe) {
|
|
34
|
+
console.error("Failed to create iframe");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create our client
|
|
39
|
+
const client = createIFrameFrakClient({
|
|
40
|
+
config: preparedConfig,
|
|
41
|
+
iframe,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Wait for the client to be all setup
|
|
45
|
+
await client.waitForSetup;
|
|
46
|
+
|
|
47
|
+
// Wait for the connection to be established
|
|
48
|
+
const waitForConnection = await client.waitForConnection;
|
|
49
|
+
if (!waitForConnection) {
|
|
50
|
+
console.error("Failed to connect to client");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return client;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Prepare the config for the Frak Client
|
|
59
|
+
* @param config - The configuration to use for the Frak Wallet SDK
|
|
60
|
+
* @returns The prepared configuration with the supported currency
|
|
61
|
+
*/
|
|
62
|
+
function prepareConfig(config: FrakWalletSdkConfig): FrakWalletSdkConfig {
|
|
63
|
+
// Get the supported currency (e.g. "eur")
|
|
64
|
+
const supportedCurrency = getSupportedCurrency(config.metadata?.currency);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
...config,
|
|
68
|
+
metadata: {
|
|
69
|
+
...config.metadata,
|
|
70
|
+
currency: supportedCurrency,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Mock dependencies
|
|
4
|
+
vi.mock("@frak-labs/frame-connector", () => ({
|
|
5
|
+
Deferred: class {
|
|
6
|
+
promise: Promise<any>;
|
|
7
|
+
resolve!: (value: any) => void;
|
|
8
|
+
reject!: (error: any) => void;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.promise = new Promise((resolve, reject) => {
|
|
12
|
+
this.resolve = resolve;
|
|
13
|
+
this.reject = reject;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock("../../utils/constants", () => ({
|
|
20
|
+
BACKUP_KEY: "frak-backup-key",
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock("../../utils/iframeHelper", () => ({
|
|
24
|
+
changeIframeVisibility: vi.fn(),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock("../../utils/clientId", () => ({
|
|
28
|
+
getClientId: vi.fn(() => "mock-client-id"),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock("../../utils/deepLinkWithFallback", () => ({
|
|
32
|
+
isFrakDeepLink: vi.fn((url: string) => url.startsWith("frakwallet://")),
|
|
33
|
+
triggerDeepLinkWithFallback: vi.fn(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const WALLET_ORIGIN = "https://wallet.frak.id";
|
|
37
|
+
|
|
38
|
+
describe("createIFrameLifecycleManager", () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
// Reset localStorage
|
|
42
|
+
localStorage.clear();
|
|
43
|
+
// Mock window.location
|
|
44
|
+
Object.defineProperty(window, "location", {
|
|
45
|
+
value: { href: "https://test.com" },
|
|
46
|
+
writable: true,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("manager initialization", () => {
|
|
51
|
+
test("should create manager with correct properties", async () => {
|
|
52
|
+
const { createIFrameLifecycleManager } = await import(
|
|
53
|
+
"./iframeLifecycleManager"
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const mockIframe = document.createElement("iframe");
|
|
57
|
+
const manager = createIFrameLifecycleManager({
|
|
58
|
+
iframe: mockIframe,
|
|
59
|
+
targetOrigin: WALLET_ORIGIN,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(manager).toBeDefined();
|
|
63
|
+
expect(manager.isConnected).toBeInstanceOf(Promise);
|
|
64
|
+
expect(manager.handleEvent).toBeInstanceOf(Function);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("should start with unresolved isConnected promise", async () => {
|
|
68
|
+
const { createIFrameLifecycleManager } = await import(
|
|
69
|
+
"./iframeLifecycleManager"
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const mockIframe = document.createElement("iframe");
|
|
73
|
+
const manager = createIFrameLifecycleManager({
|
|
74
|
+
iframe: mockIframe,
|
|
75
|
+
targetOrigin: WALLET_ORIGIN,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let resolved = false;
|
|
79
|
+
manager.isConnected.then(() => {
|
|
80
|
+
resolved = true;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Wait a tick
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
85
|
+
expect(resolved).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("connected event", () => {
|
|
90
|
+
test("should resolve isConnected on connected event", async () => {
|
|
91
|
+
const { createIFrameLifecycleManager } = await import(
|
|
92
|
+
"./iframeLifecycleManager"
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const mockIframe = document.createElement("iframe");
|
|
96
|
+
const manager = createIFrameLifecycleManager({
|
|
97
|
+
iframe: mockIframe,
|
|
98
|
+
targetOrigin: WALLET_ORIGIN,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const event = {
|
|
102
|
+
iframeLifecycle: "connected" as const,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
await manager.handleEvent(event);
|
|
106
|
+
|
|
107
|
+
await expect(manager.isConnected).resolves.toBe(true);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("backup events", () => {
|
|
112
|
+
test("should save backup to localStorage on do-backup", async () => {
|
|
113
|
+
const { createIFrameLifecycleManager } = await import(
|
|
114
|
+
"./iframeLifecycleManager"
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const mockIframe = document.createElement("iframe");
|
|
118
|
+
const manager = createIFrameLifecycleManager({
|
|
119
|
+
iframe: mockIframe,
|
|
120
|
+
targetOrigin: WALLET_ORIGIN,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const backup = "encrypted-backup-data";
|
|
124
|
+
const event = {
|
|
125
|
+
iframeLifecycle: "do-backup" as const,
|
|
126
|
+
data: { backup },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
await manager.handleEvent(event);
|
|
130
|
+
|
|
131
|
+
expect(localStorage.getItem("frak-backup-key")).toBe(backup);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("should remove backup when data.backup is undefined", async () => {
|
|
135
|
+
const { createIFrameLifecycleManager } = await import(
|
|
136
|
+
"./iframeLifecycleManager"
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const mockIframe = document.createElement("iframe");
|
|
140
|
+
const manager = createIFrameLifecycleManager({
|
|
141
|
+
iframe: mockIframe,
|
|
142
|
+
targetOrigin: WALLET_ORIGIN,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// First set a backup
|
|
146
|
+
localStorage.setItem("frak-backup-key", "old-backup");
|
|
147
|
+
|
|
148
|
+
const event = {
|
|
149
|
+
iframeLifecycle: "do-backup" as const,
|
|
150
|
+
data: {},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
await manager.handleEvent(event);
|
|
154
|
+
|
|
155
|
+
expect(localStorage.getItem("frak-backup-key")).toBeNull();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("should remove backup on remove-backup event", async () => {
|
|
159
|
+
const { createIFrameLifecycleManager } = await import(
|
|
160
|
+
"./iframeLifecycleManager"
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const mockIframe = document.createElement("iframe");
|
|
164
|
+
const manager = createIFrameLifecycleManager({
|
|
165
|
+
iframe: mockIframe,
|
|
166
|
+
targetOrigin: WALLET_ORIGIN,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// First set a backup
|
|
170
|
+
localStorage.setItem("frak-backup-key", "backup-to-remove");
|
|
171
|
+
|
|
172
|
+
const event = {
|
|
173
|
+
iframeLifecycle: "remove-backup" as const,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
await manager.handleEvent(event);
|
|
177
|
+
|
|
178
|
+
expect(localStorage.getItem("frak-backup-key")).toBeNull();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("visibility events", () => {
|
|
183
|
+
test("should show iframe on show event", async () => {
|
|
184
|
+
const { createIFrameLifecycleManager } = await import(
|
|
185
|
+
"./iframeLifecycleManager"
|
|
186
|
+
);
|
|
187
|
+
const { changeIframeVisibility } = await import(
|
|
188
|
+
"../../utils/iframeHelper"
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const mockIframe = document.createElement("iframe");
|
|
192
|
+
const manager = createIFrameLifecycleManager({
|
|
193
|
+
iframe: mockIframe,
|
|
194
|
+
targetOrigin: WALLET_ORIGIN,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const event = {
|
|
198
|
+
iframeLifecycle: "show" as const,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
await manager.handleEvent(event);
|
|
202
|
+
|
|
203
|
+
expect(changeIframeVisibility).toHaveBeenCalledWith({
|
|
204
|
+
iframe: mockIframe,
|
|
205
|
+
isVisible: true,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("should hide iframe on hide event", async () => {
|
|
210
|
+
const { createIFrameLifecycleManager } = await import(
|
|
211
|
+
"./iframeLifecycleManager"
|
|
212
|
+
);
|
|
213
|
+
const { changeIframeVisibility } = await import(
|
|
214
|
+
"../../utils/iframeHelper"
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const mockIframe = document.createElement("iframe");
|
|
218
|
+
const manager = createIFrameLifecycleManager({
|
|
219
|
+
iframe: mockIframe,
|
|
220
|
+
targetOrigin: WALLET_ORIGIN,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const event = {
|
|
224
|
+
iframeLifecycle: "hide" as const,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
await manager.handleEvent(event);
|
|
228
|
+
|
|
229
|
+
expect(changeIframeVisibility).toHaveBeenCalledWith({
|
|
230
|
+
iframe: mockIframe,
|
|
231
|
+
isVisible: false,
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("handshake event", () => {
|
|
237
|
+
test("should post handshake-response with token to iframe origin", async () => {
|
|
238
|
+
const { createIFrameLifecycleManager } = await import(
|
|
239
|
+
"./iframeLifecycleManager"
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const mockPostMessage = vi.fn();
|
|
243
|
+
const mockIframe = {
|
|
244
|
+
src: "https://wallet.frak.id/listener",
|
|
245
|
+
contentWindow: {
|
|
246
|
+
postMessage: mockPostMessage,
|
|
247
|
+
},
|
|
248
|
+
} as any;
|
|
249
|
+
|
|
250
|
+
const manager = createIFrameLifecycleManager({
|
|
251
|
+
iframe: mockIframe,
|
|
252
|
+
targetOrigin: WALLET_ORIGIN,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const event = {
|
|
256
|
+
iframeLifecycle: "handshake" as const,
|
|
257
|
+
data: { token: "handshake-token-123" },
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
await manager.handleEvent(event);
|
|
261
|
+
|
|
262
|
+
expect(mockPostMessage).toHaveBeenCalledWith(
|
|
263
|
+
{
|
|
264
|
+
clientLifecycle: "handshake-response",
|
|
265
|
+
data: {
|
|
266
|
+
token: "handshake-token-123",
|
|
267
|
+
currentUrl: "https://test.com",
|
|
268
|
+
clientId: "mock-client-id",
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
"https://wallet.frak.id"
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("should include current URL in handshake response", async () => {
|
|
276
|
+
const { createIFrameLifecycleManager } = await import(
|
|
277
|
+
"./iframeLifecycleManager"
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
Object.defineProperty(window, "location", {
|
|
281
|
+
value: { href: "https://example.com/page?param=value" },
|
|
282
|
+
writable: true,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const mockPostMessage = vi.fn();
|
|
286
|
+
const mockIframe = {
|
|
287
|
+
src: "https://wallet.frak.id/listener",
|
|
288
|
+
contentWindow: {
|
|
289
|
+
postMessage: mockPostMessage,
|
|
290
|
+
},
|
|
291
|
+
} as any;
|
|
292
|
+
|
|
293
|
+
const manager = createIFrameLifecycleManager({
|
|
294
|
+
iframe: mockIframe,
|
|
295
|
+
targetOrigin: WALLET_ORIGIN,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const event = {
|
|
299
|
+
iframeLifecycle: "handshake" as const,
|
|
300
|
+
data: { token: "token" },
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
await manager.handleEvent(event);
|
|
304
|
+
|
|
305
|
+
expect(mockPostMessage).toHaveBeenCalledWith(
|
|
306
|
+
expect.objectContaining({
|
|
307
|
+
data: expect.objectContaining({
|
|
308
|
+
currentUrl: "https://example.com/page?param=value",
|
|
309
|
+
}),
|
|
310
|
+
}),
|
|
311
|
+
"https://wallet.frak.id"
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("redirect event", () => {
|
|
317
|
+
test("should redirect with appended current URL for HTTP URLs", async () => {
|
|
318
|
+
const { createIFrameLifecycleManager } = await import(
|
|
319
|
+
"./iframeLifecycleManager"
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
Object.defineProperty(window, "location", {
|
|
323
|
+
value: {
|
|
324
|
+
href: "https://original.com",
|
|
325
|
+
},
|
|
326
|
+
writable: true,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const mockIframe = document.createElement("iframe");
|
|
330
|
+
const manager = createIFrameLifecycleManager({
|
|
331
|
+
iframe: mockIframe,
|
|
332
|
+
targetOrigin: WALLET_ORIGIN,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const event = {
|
|
336
|
+
iframeLifecycle: "redirect" as const,
|
|
337
|
+
data: {
|
|
338
|
+
baseRedirectUrl: "https://redirect.com?u=placeholder",
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
await manager.handleEvent(event);
|
|
343
|
+
|
|
344
|
+
expect(window.location.href).toBe(
|
|
345
|
+
"https://redirect.com/?u=https%3A%2F%2Foriginal.com"
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("should redirect without modification if no u parameter", async () => {
|
|
350
|
+
const { createIFrameLifecycleManager } = await import(
|
|
351
|
+
"./iframeLifecycleManager"
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
Object.defineProperty(window, "location", {
|
|
355
|
+
value: {
|
|
356
|
+
href: "https://original.com",
|
|
357
|
+
},
|
|
358
|
+
writable: true,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const mockIframe = document.createElement("iframe");
|
|
362
|
+
const manager = createIFrameLifecycleManager({
|
|
363
|
+
iframe: mockIframe,
|
|
364
|
+
targetOrigin: WALLET_ORIGIN,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const event = {
|
|
368
|
+
iframeLifecycle: "redirect" as const,
|
|
369
|
+
data: {
|
|
370
|
+
baseRedirectUrl: "https://redirect.com/path",
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
await manager.handleEvent(event);
|
|
375
|
+
|
|
376
|
+
expect(window.location.href).toBe("https://redirect.com/path");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("should use fallback detection for frakwallet:// deep links", async () => {
|
|
380
|
+
const { createIFrameLifecycleManager } = await import(
|
|
381
|
+
"./iframeLifecycleManager"
|
|
382
|
+
);
|
|
383
|
+
const { triggerDeepLinkWithFallback } = await import(
|
|
384
|
+
"../../utils/deepLinkWithFallback"
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
Object.defineProperty(window, "location", {
|
|
388
|
+
value: {
|
|
389
|
+
href: "https://original.com",
|
|
390
|
+
},
|
|
391
|
+
writable: true,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const mockIframe = document.createElement("iframe");
|
|
395
|
+
mockIframe.src = "https://wallet.frak.id";
|
|
396
|
+
const manager = createIFrameLifecycleManager({
|
|
397
|
+
iframe: mockIframe,
|
|
398
|
+
targetOrigin: WALLET_ORIGIN,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const event = {
|
|
402
|
+
iframeLifecycle: "redirect" as const,
|
|
403
|
+
data: {
|
|
404
|
+
baseRedirectUrl: "frakwallet://wallet",
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
await manager.handleEvent(event);
|
|
409
|
+
|
|
410
|
+
expect(triggerDeepLinkWithFallback).toHaveBeenCalledWith(
|
|
411
|
+
"frakwallet://wallet",
|
|
412
|
+
expect.objectContaining({
|
|
413
|
+
onFallback: expect.any(Function),
|
|
414
|
+
})
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("should post deep-link-failed message when fallback is triggered", async () => {
|
|
419
|
+
const { createIFrameLifecycleManager } = await import(
|
|
420
|
+
"./iframeLifecycleManager"
|
|
421
|
+
);
|
|
422
|
+
const { triggerDeepLinkWithFallback } = await import(
|
|
423
|
+
"../../utils/deepLinkWithFallback"
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
Object.defineProperty(window, "location", {
|
|
427
|
+
value: {
|
|
428
|
+
href: "https://original.com",
|
|
429
|
+
},
|
|
430
|
+
writable: true,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const mockPostMessage = vi.fn();
|
|
434
|
+
const mockIframe = {
|
|
435
|
+
src: "https://wallet.frak.id",
|
|
436
|
+
contentWindow: {
|
|
437
|
+
postMessage: mockPostMessage,
|
|
438
|
+
},
|
|
439
|
+
} as any;
|
|
440
|
+
|
|
441
|
+
const manager = createIFrameLifecycleManager({
|
|
442
|
+
iframe: mockIframe,
|
|
443
|
+
targetOrigin: WALLET_ORIGIN,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const event = {
|
|
447
|
+
iframeLifecycle: "redirect" as const,
|
|
448
|
+
data: {
|
|
449
|
+
baseRedirectUrl: "frakwallet://wallet",
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
await manager.handleEvent(event);
|
|
454
|
+
|
|
455
|
+
// Extract the onFallback callback from the mock call
|
|
456
|
+
const callArgs = (triggerDeepLinkWithFallback as any).mock.calls[0];
|
|
457
|
+
const options = callArgs[1];
|
|
458
|
+
expect(options).toBeDefined();
|
|
459
|
+
expect(options.onFallback).toBeInstanceOf(Function);
|
|
460
|
+
|
|
461
|
+
// Trigger the fallback callback
|
|
462
|
+
options.onFallback();
|
|
463
|
+
|
|
464
|
+
// Verify postMessage was called with deep-link-failed event
|
|
465
|
+
expect(mockPostMessage).toHaveBeenCalledWith(
|
|
466
|
+
{
|
|
467
|
+
clientLifecycle: "deep-link-failed",
|
|
468
|
+
data: { originalUrl: "frakwallet://wallet" },
|
|
469
|
+
},
|
|
470
|
+
"https://wallet.frak.id"
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("should NOT use fallback detection for HTTP URLs", async () => {
|
|
475
|
+
const { createIFrameLifecycleManager } = await import(
|
|
476
|
+
"./iframeLifecycleManager"
|
|
477
|
+
);
|
|
478
|
+
const { triggerDeepLinkWithFallback } = await import(
|
|
479
|
+
"../../utils/deepLinkWithFallback"
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
Object.defineProperty(window, "location", {
|
|
483
|
+
value: {
|
|
484
|
+
href: "https://original.com",
|
|
485
|
+
},
|
|
486
|
+
writable: true,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const mockIframe = document.createElement("iframe");
|
|
490
|
+
const manager = createIFrameLifecycleManager({
|
|
491
|
+
iframe: mockIframe,
|
|
492
|
+
targetOrigin: WALLET_ORIGIN,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const event = {
|
|
496
|
+
iframeLifecycle: "redirect" as const,
|
|
497
|
+
data: {
|
|
498
|
+
baseRedirectUrl: "https://wallet.frak.id/login",
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
await manager.handleEvent(event);
|
|
503
|
+
|
|
504
|
+
// Should NOT call fallback detection
|
|
505
|
+
expect(triggerDeepLinkWithFallback).not.toHaveBeenCalled();
|
|
506
|
+
// Should directly redirect
|
|
507
|
+
expect(window.location.href).toBe("https://wallet.frak.id/login");
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe("event filtering", () => {
|
|
512
|
+
test("should ignore events without iframeLifecycle property", async () => {
|
|
513
|
+
const { createIFrameLifecycleManager } = await import(
|
|
514
|
+
"./iframeLifecycleManager"
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
const mockIframe = document.createElement("iframe");
|
|
518
|
+
const manager = createIFrameLifecycleManager({
|
|
519
|
+
iframe: mockIframe,
|
|
520
|
+
targetOrigin: WALLET_ORIGIN,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const event = {
|
|
524
|
+
someOtherEvent: "value",
|
|
525
|
+
} as any;
|
|
526
|
+
|
|
527
|
+
// Should not throw
|
|
528
|
+
await expect(manager.handleEvent(event)).resolves.toBeUndefined();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("should only process events with iframeLifecycle", async () => {
|
|
532
|
+
const { createIFrameLifecycleManager } = await import(
|
|
533
|
+
"./iframeLifecycleManager"
|
|
534
|
+
);
|
|
535
|
+
const { changeIframeVisibility } = await import(
|
|
536
|
+
"../../utils/iframeHelper"
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
const mockIframe = document.createElement("iframe");
|
|
540
|
+
const manager = createIFrameLifecycleManager({
|
|
541
|
+
iframe: mockIframe,
|
|
542
|
+
targetOrigin: WALLET_ORIGIN,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Event without iframeLifecycle
|
|
546
|
+
await manager.handleEvent({ randomEvent: "show" } as any);
|
|
547
|
+
|
|
548
|
+
// changeIframeVisibility should not be called
|
|
549
|
+
expect(changeIframeVisibility).not.toHaveBeenCalled();
|
|
550
|
+
|
|
551
|
+
// Event with iframeLifecycle
|
|
552
|
+
await manager.handleEvent({ iframeLifecycle: "show" as const });
|
|
553
|
+
|
|
554
|
+
// Now it should be called
|
|
555
|
+
expect(changeIframeVisibility).toHaveBeenCalled();
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
});
|