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