@frak-labs/core-sdk 0.1.1-beta.d96bc28d → 0.1.1-beta.df5fa6b3
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 -3
- package/dist/actions.cjs +1 -1
- package/dist/actions.d.cts +1 -1
- package/dist/actions.d.ts +1 -1
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -1
- package/dist/bundle.d.cts +3 -3
- package/dist/bundle.d.ts +3 -3
- package/dist/bundle.js +1 -1
- package/dist/{computeLegacyProductId-WbD1gXV9.d.ts → computeLegacyProductId-CSSiQO4V.d.ts} +28 -15
- package/dist/{computeLegacyProductId-CscYhyUi.d.cts → computeLegacyProductId-fl_Sx8v5.d.cts} +28 -15
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/setupClient--zfx2pFM.cjs +13 -0
- package/dist/setupClient-CFQDaGKh.js +13 -0
- package/dist/siweAuthenticate-BKSiU7Ej.js +1 -0
- package/dist/{siweAuthenticate-udoruuy9.d.ts → siweAuthenticate-BOqz9Uww.d.ts} +46 -2
- package/dist/siweAuthenticate-CVMVEe3M.cjs +1 -0
- package/dist/{siweAuthenticate-CR4Dpji6.d.cts → siweAuthenticate-DfiQdngJ.d.cts} +46 -2
- package/dist/{trackEvent-YfUh4jrx.js → trackEvent-B0S5E6_h.js} +1 -1
- package/dist/{trackEvent-CGIryq5h.cjs → trackEvent-BbX2d3g_.cjs} +1 -1
- package/package.json +2 -2
- package/src/actions/displayEmbeddedWallet.ts +1 -0
- package/src/actions/getMerchantInformation.ts +7 -0
- package/src/actions/sendInteraction.ts +31 -0
- package/src/actions/trackPurchaseStatus.test.ts +354 -141
- package/src/actions/trackPurchaseStatus.ts +45 -11
- package/src/clients/createIFrameFrakClient.ts +2 -1
- package/src/clients/transports/iframeLifecycleManager.ts +58 -3
- package/src/index.ts +0 -1
- package/src/types/index.ts +0 -1
- package/src/utils/constants.ts +0 -5
- package/src/utils/deepLinkWithFallback.test.ts +2 -2
- package/src/utils/deepLinkWithFallback.ts +9 -3
- package/src/utils/formatAmount.ts +6 -0
- package/src/utils/merchantId.test.ts +90 -1
- package/src/utils/merchantId.ts +22 -1
- package/src/utils/sso.ts +2 -1
- package/src/utils/trackEvent.ts +10 -0
- package/dist/setupClient-BjIbK6XJ.cjs +0 -13
- package/dist/setupClient-D_HId3e2.js +0 -13
- package/dist/siweAuthenticate-B_Z2OZmj.cjs +0 -1
- package/dist/siweAuthenticate-CQ4OfPuA.js +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getBackendUrl } from "../utils/backendUrl";
|
|
2
|
+
import { getClientId } from "../utils/clientId";
|
|
3
|
+
import { fetchMerchantId } from "../utils/merchantId";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Function used to track the status of a purchase
|
|
@@ -7,6 +9,7 @@ import { getBackendUrl } from "../utils/backendUrl";
|
|
|
7
9
|
* @param args.customerId - The customer id that made the purchase (on your side)
|
|
8
10
|
* @param args.orderId - The order id of the purchase (on your side)
|
|
9
11
|
* @param args.token - The token of the purchase
|
|
12
|
+
* @param args.merchantId - Optional explicit merchant id to use for the tracking request
|
|
10
13
|
*
|
|
11
14
|
* @description This function will send a request to the backend to listen for the purchase status.
|
|
12
15
|
*
|
|
@@ -16,41 +19,72 @@ import { getBackendUrl } from "../utils/backendUrl";
|
|
|
16
19
|
* customerId: checkout.order.customer.id,
|
|
17
20
|
* orderId: checkout.order.id,
|
|
18
21
|
* token: checkout.token,
|
|
22
|
+
* merchantId: "your-merchant-id",
|
|
19
23
|
* };
|
|
20
24
|
*
|
|
21
25
|
* await trackPurchaseStatus(payload);
|
|
22
26
|
* }
|
|
23
27
|
*
|
|
24
28
|
* @remarks
|
|
25
|
-
* -
|
|
26
|
-
* - This function
|
|
29
|
+
* - Merchant id is resolved in this order: explicit `args.merchantId`, `frak-merchant-id` from session storage, then `fetchMerchantId()`.
|
|
30
|
+
* - This function supports anonymous users and will use the `x-frak-client-id` header when available.
|
|
31
|
+
* - At least one identity source must exist (`frak-wallet-interaction-token` or `x-frak-client-id`), otherwise the tracking request is skipped.
|
|
32
|
+
* - This function will print a warning if used in a non-browser environment or if no identity / merchant id can be resolved.
|
|
27
33
|
*/
|
|
28
34
|
export async function trackPurchaseStatus(args: {
|
|
29
35
|
customerId: string | number;
|
|
30
36
|
orderId: string | number;
|
|
31
37
|
token: string;
|
|
38
|
+
merchantId?: string;
|
|
32
39
|
}) {
|
|
33
40
|
if (typeof window === "undefined") {
|
|
34
41
|
console.warn("[Frak] No window found, can't track purchase");
|
|
35
42
|
return;
|
|
36
43
|
}
|
|
44
|
+
|
|
37
45
|
const interactionToken = window.sessionStorage.getItem(
|
|
38
46
|
"frak-wallet-interaction-token"
|
|
39
47
|
);
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
|
|
49
|
+
const clientId = getClientId();
|
|
50
|
+
if (!interactionToken && !clientId) {
|
|
51
|
+
console.warn("[Frak] No identity found, skipping purchase check");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const merchantIdFromStorage =
|
|
56
|
+
window.sessionStorage.getItem("frak-merchant-id");
|
|
57
|
+
const merchantId =
|
|
58
|
+
args.merchantId ?? merchantIdFromStorage ?? (await fetchMerchantId());
|
|
59
|
+
|
|
60
|
+
if (!merchantId) {
|
|
61
|
+
console.warn("[Frak] No merchant id found, skipping purchase check");
|
|
42
62
|
return;
|
|
43
63
|
}
|
|
44
64
|
|
|
65
|
+
const headers: Record<string, string> = {
|
|
66
|
+
Accept: "application/json",
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (interactionToken) {
|
|
71
|
+
headers["x-wallet-sdk-auth"] = interactionToken;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (clientId) {
|
|
75
|
+
headers["x-frak-client-id"] = clientId;
|
|
76
|
+
}
|
|
77
|
+
|
|
45
78
|
// Submit the listening request
|
|
46
79
|
const backendUrl = getBackendUrl();
|
|
47
|
-
await fetch(`${backendUrl}/
|
|
80
|
+
await fetch(`${backendUrl}/user/track/purchase`, {
|
|
48
81
|
method: "POST",
|
|
49
|
-
headers
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
82
|
+
headers,
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
customerId: args.customerId,
|
|
85
|
+
orderId: args.orderId,
|
|
86
|
+
token: args.token,
|
|
87
|
+
merchantId,
|
|
88
|
+
}),
|
|
55
89
|
});
|
|
56
90
|
}
|
|
@@ -23,7 +23,8 @@ type SdkRpcClient = RpcClient<IFrameRpcSchema, FrakLifecycleEvent>;
|
|
|
23
23
|
/**
|
|
24
24
|
* Create a new iframe Frak client
|
|
25
25
|
* @param args
|
|
26
|
-
* @param args.config - The configuration to use for the Frak Wallet SDK
|
|
26
|
+
* @param args.config - The configuration to use for the Frak Wallet SDK.
|
|
27
|
+
* When `config.domain` is set, it is forwarded to the iframe handshake so the listener resolves the correct merchant in tunneled/proxied environments (e.g. Shopify dev with Cloudflare tunnel).
|
|
27
28
|
* @param args.iframe - The iframe to use for the communication
|
|
28
29
|
* @returns The created Frak Client
|
|
29
30
|
*
|
|
@@ -8,6 +8,28 @@ import {
|
|
|
8
8
|
} from "../../utils/deepLinkWithFallback";
|
|
9
9
|
import { changeIframeVisibility } from "../../utils/iframeHelper";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Detect iOS in-app browsers (Instagram, Facebook) where server-side
|
|
13
|
+
* 302 redirects to custom URL schemes (x-safari-https://) are silently
|
|
14
|
+
* swallowed by WKWebView. Direct window.location.href assignment works.
|
|
15
|
+
*/
|
|
16
|
+
const isIOSInAppBrowser = (() => {
|
|
17
|
+
if (typeof navigator === "undefined") return false;
|
|
18
|
+
const ua = navigator.userAgent;
|
|
19
|
+
// Standard iOS or iPadOS 13+ (reports as Macintosh with touch)
|
|
20
|
+
const isIOS =
|
|
21
|
+
/iPhone|iPad|iPod/i.test(ua) ||
|
|
22
|
+
(/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1);
|
|
23
|
+
if (!isIOS) return false;
|
|
24
|
+
const lower = ua.toLowerCase();
|
|
25
|
+
return (
|
|
26
|
+
lower.includes("instagram") ||
|
|
27
|
+
lower.includes("fban") ||
|
|
28
|
+
lower.includes("fbav") ||
|
|
29
|
+
lower.includes("facebook")
|
|
30
|
+
);
|
|
31
|
+
})();
|
|
32
|
+
|
|
11
33
|
/** @ignore */
|
|
12
34
|
export type IframeLifecycleManager = {
|
|
13
35
|
isConnected: Promise<boolean>;
|
|
@@ -26,7 +48,11 @@ function handleBackup(backup: string | undefined): void {
|
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
/**
|
|
29
|
-
* Handle handshake with iframe
|
|
51
|
+
* Handle handshake with iframe — sends client metadata so the listener can resolve the correct merchant
|
|
52
|
+
* @param iframe - The iframe element to post the handshake response to
|
|
53
|
+
* @param token - The handshake token received from the iframe
|
|
54
|
+
* @param targetOrigin - The target origin for postMessage security
|
|
55
|
+
* @param configDomain - Optional override domain for merchant resolution in tunneled/proxied environments
|
|
30
56
|
*/
|
|
31
57
|
function handleHandshake(
|
|
32
58
|
iframe: HTMLIFrameElement,
|
|
@@ -83,6 +109,27 @@ function computeRedirectUrl(
|
|
|
83
109
|
}
|
|
84
110
|
}
|
|
85
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Redirect current page to Safari via x-safari-https:// scheme.
|
|
114
|
+
* Used on iOS in-app browsers where backend 302 → custom scheme fails.
|
|
115
|
+
*/
|
|
116
|
+
function redirectToSafari(mergeToken?: string) {
|
|
117
|
+
const url = new URL(window.location.href);
|
|
118
|
+
if (mergeToken) {
|
|
119
|
+
url.searchParams.set("fmt", mergeToken);
|
|
120
|
+
}
|
|
121
|
+
const scheme =
|
|
122
|
+
url.protocol === "http:" ? "x-safari-http" : "x-safari-https";
|
|
123
|
+
window.location.href = `${scheme}://${url.host}${url.pathname}${url.search}${url.hash}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if this is a social/in-app-browser escape redirect (contains /common/social)
|
|
128
|
+
*/
|
|
129
|
+
function isSocialRedirect(url: string): boolean {
|
|
130
|
+
return url.includes("/common/social");
|
|
131
|
+
}
|
|
132
|
+
|
|
86
133
|
/**
|
|
87
134
|
* Handle redirect with deep link fallback
|
|
88
135
|
*/
|
|
@@ -92,9 +139,8 @@ function handleRedirect(
|
|
|
92
139
|
targetOrigin: string,
|
|
93
140
|
mergeToken?: string
|
|
94
141
|
): void {
|
|
95
|
-
const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
|
|
96
|
-
|
|
97
142
|
if (isFrakDeepLink(baseRedirectUrl)) {
|
|
143
|
+
const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
|
|
98
144
|
triggerDeepLinkWithFallback(finalUrl, {
|
|
99
145
|
onFallback: () => {
|
|
100
146
|
iframe.contentWindow?.postMessage(
|
|
@@ -106,13 +152,22 @@ function handleRedirect(
|
|
|
106
152
|
);
|
|
107
153
|
},
|
|
108
154
|
});
|
|
155
|
+
} else if (isIOSInAppBrowser && isSocialRedirect(baseRedirectUrl)) {
|
|
156
|
+
// iOS WKWebView silently swallows 302 redirects to custom URL
|
|
157
|
+
// schemes — bypass the server redirect entirely
|
|
158
|
+
redirectToSafari(mergeToken);
|
|
109
159
|
} else {
|
|
160
|
+
const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
|
|
110
161
|
window.location.href = finalUrl;
|
|
111
162
|
}
|
|
112
163
|
}
|
|
113
164
|
|
|
114
165
|
/**
|
|
115
166
|
* Create a new iframe lifecycle handler
|
|
167
|
+
* @param args
|
|
168
|
+
* @param args.iframe - The iframe element used for wallet communication
|
|
169
|
+
* @param args.targetOrigin - The wallet URL origin for postMessage security
|
|
170
|
+
* @param args.configDomain - Optional domain override forwarded during handshake for tunneled/proxied environments
|
|
116
171
|
* @ignore
|
|
117
172
|
*/
|
|
118
173
|
export function createIFrameLifecycleManager({
|
package/src/index.ts
CHANGED
package/src/types/index.ts
CHANGED
package/src/utils/constants.ts
CHANGED
|
@@ -7,8 +7,3 @@ export const BACKUP_KEY = "nexus-wallet-backup";
|
|
|
7
7
|
* Deep link scheme for Frak Wallet mobile app
|
|
8
8
|
*/
|
|
9
9
|
export const DEEP_LINK_SCHEME = "frakwallet://";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Android package name for Frak Wallet (used in intent:// URLs)
|
|
13
|
-
*/
|
|
14
|
-
export const ANDROID_PACKAGE = "id.frak.wallet";
|
|
@@ -182,7 +182,7 @@ describe("deepLinkWithFallback", () => {
|
|
|
182
182
|
triggerDeepLinkWithFallback("frakwallet://wallet");
|
|
183
183
|
|
|
184
184
|
expect(window.location.href).toBe(
|
|
185
|
-
"intent://wallet#Intent;scheme=frakwallet;
|
|
185
|
+
"intent://wallet#Intent;scheme=frakwallet;end"
|
|
186
186
|
);
|
|
187
187
|
});
|
|
188
188
|
|
|
@@ -194,7 +194,7 @@ describe("deepLinkWithFallback", () => {
|
|
|
194
194
|
);
|
|
195
195
|
|
|
196
196
|
expect(window.location.href).toBe(
|
|
197
|
-
"intent://pair?id=abc-123&mode=embedded#Intent;scheme=frakwallet;
|
|
197
|
+
"intent://pair?id=abc-123&mode=embedded#Intent;scheme=frakwallet;end"
|
|
198
198
|
);
|
|
199
199
|
});
|
|
200
200
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEEP_LINK_SCHEME } from "./constants";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Options for deep link with fallback
|
|
@@ -29,12 +29,18 @@ export function isChromiumAndroid(): boolean {
|
|
|
29
29
|
* Intent URLs let Chromium browsers open the app directly without
|
|
30
30
|
* showing the "Continue to app?" confirmation bar.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
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
|
|
33
39
|
*/
|
|
34
40
|
export function toAndroidIntentUrl(deepLink: string): string {
|
|
35
41
|
// Extract everything after "frakwallet://"
|
|
36
42
|
const path = deepLink.slice(DEEP_LINK_SCHEME.length);
|
|
37
|
-
return `intent://${path}#Intent;scheme=frakwallet;
|
|
43
|
+
return `intent://${path}#Intent;scheme=frakwallet;end`;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
/**
|
|
@@ -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);
|
|
@@ -17,8 +17,9 @@ vi.mock("./backendUrl", () => ({
|
|
|
17
17
|
|
|
18
18
|
describe("merchantId", () => {
|
|
19
19
|
beforeEach(() => {
|
|
20
|
-
// Clear cache before each test
|
|
20
|
+
// Clear cache before each test (also clears sessionStorage)
|
|
21
21
|
clearMerchantIdCache();
|
|
22
|
+
window.sessionStorage.clear();
|
|
22
23
|
// Clear all mocks
|
|
23
24
|
vi.clearAllMocks();
|
|
24
25
|
// Mock console methods to avoid noise in test output
|
|
@@ -263,6 +264,70 @@ describe("merchantId", () => {
|
|
|
263
264
|
expect(result2).toBe("merchant-cached");
|
|
264
265
|
expect(global.fetch).not.toHaveBeenCalled();
|
|
265
266
|
});
|
|
267
|
+
|
|
268
|
+
it("should persist merchantId to sessionStorage after fetch", async () => {
|
|
269
|
+
const mockResponse = {
|
|
270
|
+
merchantId: "merchant-persisted",
|
|
271
|
+
name: "Test Merchant",
|
|
272
|
+
domain: "shop.example.com",
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
276
|
+
ok: true,
|
|
277
|
+
json: async () => mockResponse,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await fetchMerchantId("shop.example.com");
|
|
281
|
+
|
|
282
|
+
expect(window.sessionStorage.getItem("frak-merchant-id")).toBe(
|
|
283
|
+
"merchant-persisted"
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should restore merchantId from sessionStorage when in-memory cache is cleared", async () => {
|
|
288
|
+
const mockResponse = {
|
|
289
|
+
merchantId: "merchant-storage",
|
|
290
|
+
name: "Test Merchant",
|
|
291
|
+
domain: "shop.example.com",
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
295
|
+
ok: true,
|
|
296
|
+
json: async () => mockResponse,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// First call populates both caches
|
|
300
|
+
const result1 = await fetchMerchantId("shop.example.com");
|
|
301
|
+
expect(result1).toBe("merchant-storage");
|
|
302
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
303
|
+
|
|
304
|
+
// Clear only in-memory cache (not sessionStorage)
|
|
305
|
+
vi.clearAllMocks();
|
|
306
|
+
global.fetch = vi.fn();
|
|
307
|
+
|
|
308
|
+
// Manually clear in-memory but keep sessionStorage
|
|
309
|
+
// We do this by calling the internal clear then restoring sessionStorage
|
|
310
|
+
const stored = window.sessionStorage.getItem("frak-merchant-id");
|
|
311
|
+
clearMerchantIdCache();
|
|
312
|
+
window.sessionStorage.setItem("frak-merchant-id", stored!);
|
|
313
|
+
|
|
314
|
+
// Second call should restore from sessionStorage without fetch
|
|
315
|
+
const result2 = await fetchMerchantId("shop.example.com");
|
|
316
|
+
expect(result2).toBe("merchant-storage");
|
|
317
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should not write to sessionStorage when fetch fails", async () => {
|
|
321
|
+
global.fetch = vi
|
|
322
|
+
.fn()
|
|
323
|
+
.mockRejectedValueOnce(new Error("Network error"));
|
|
324
|
+
|
|
325
|
+
await fetchMerchantId("shop.example.com");
|
|
326
|
+
|
|
327
|
+
expect(
|
|
328
|
+
window.sessionStorage.getItem("frak-merchant-id")
|
|
329
|
+
).toBeNull();
|
|
330
|
+
});
|
|
266
331
|
});
|
|
267
332
|
|
|
268
333
|
describe("clearMerchantIdCache", () => {
|
|
@@ -324,6 +389,30 @@ describe("merchantId", () => {
|
|
|
324
389
|
const result2 = await fetchMerchantId("shop2.example.com");
|
|
325
390
|
expect(result2).toBe("merchant-second");
|
|
326
391
|
});
|
|
392
|
+
|
|
393
|
+
it("should clear sessionStorage when clearing cache", async () => {
|
|
394
|
+
const mockResponse = {
|
|
395
|
+
merchantId: "merchant-session-clear",
|
|
396
|
+
name: "Test Merchant",
|
|
397
|
+
domain: "shop.example.com",
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
401
|
+
ok: true,
|
|
402
|
+
json: async () => mockResponse,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
await fetchMerchantId("shop.example.com");
|
|
406
|
+
expect(window.sessionStorage.getItem("frak-merchant-id")).toBe(
|
|
407
|
+
"merchant-session-clear"
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
clearMerchantIdCache();
|
|
411
|
+
|
|
412
|
+
expect(
|
|
413
|
+
window.sessionStorage.getItem("frak-merchant-id")
|
|
414
|
+
).toBeNull();
|
|
415
|
+
});
|
|
327
416
|
});
|
|
328
417
|
|
|
329
418
|
describe("resolveMerchantId", () => {
|
package/src/utils/merchantId.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { getBackendUrl } from "./backendUrl";
|
|
6
6
|
|
|
7
|
+
const MERCHANT_ID_STORAGE_KEY = "frak-merchant-id";
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Response from the merchant lookup endpoint
|
|
9
11
|
*/
|
|
@@ -39,11 +41,20 @@ export async function fetchMerchantId(
|
|
|
39
41
|
domain?: string,
|
|
40
42
|
walletUrl?: string
|
|
41
43
|
): Promise<string | undefined> {
|
|
42
|
-
// Use
|
|
44
|
+
// Use in-memory cache if available
|
|
43
45
|
if (cachedMerchantId) {
|
|
44
46
|
return cachedMerchantId;
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
// Check sessionStorage (survives page navigations)
|
|
50
|
+
if (typeof window !== "undefined") {
|
|
51
|
+
const stored = window.sessionStorage.getItem(MERCHANT_ID_STORAGE_KEY);
|
|
52
|
+
if (stored) {
|
|
53
|
+
cachedMerchantId = stored;
|
|
54
|
+
return stored;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
// If a fetch is already in progress, wait for it
|
|
48
59
|
if (cachePromise) {
|
|
49
60
|
return cachePromise;
|
|
@@ -85,6 +96,13 @@ async function fetchMerchantIdInternal(
|
|
|
85
96
|
|
|
86
97
|
const data = (await response.json()) as MerchantLookupResponse;
|
|
87
98
|
cachedMerchantId = data.merchantId;
|
|
99
|
+
// Persist to sessionStorage so it survives page navigations
|
|
100
|
+
if (typeof window !== "undefined") {
|
|
101
|
+
window.sessionStorage.setItem(
|
|
102
|
+
MERCHANT_ID_STORAGE_KEY,
|
|
103
|
+
data.merchantId
|
|
104
|
+
);
|
|
105
|
+
}
|
|
88
106
|
return cachedMerchantId;
|
|
89
107
|
} catch (error) {
|
|
90
108
|
console.warn("[Frak SDK] Failed to fetch merchantId:", error);
|
|
@@ -99,6 +117,9 @@ async function fetchMerchantIdInternal(
|
|
|
99
117
|
export function clearMerchantIdCache(): void {
|
|
100
118
|
cachedMerchantId = undefined;
|
|
101
119
|
cachePromise = undefined;
|
|
120
|
+
if (typeof window !== "undefined") {
|
|
121
|
+
window.sessionStorage.removeItem(MERCHANT_ID_STORAGE_KEY);
|
|
122
|
+
}
|
|
102
123
|
}
|
|
103
124
|
|
|
104
125
|
/**
|
package/src/utils/sso.ts
CHANGED
|
@@ -23,9 +23,10 @@ export type FullSsoParams = Omit<PrepareSsoParamsType, "metadata"> & {
|
|
|
23
23
|
*
|
|
24
24
|
* @param walletUrl - Base wallet URL (e.g., "https://wallet.frak.id")
|
|
25
25
|
* @param params - SSO parameters
|
|
26
|
-
* @param
|
|
26
|
+
* @param merchantId - Merchant identifier
|
|
27
27
|
* @param name - Application name
|
|
28
28
|
* @param css - Optional custom CSS
|
|
29
|
+
* @param clientId - Optional client identifier (auto-generated if omitted)
|
|
29
30
|
* @returns Complete SSO URL ready to open in popup or redirect
|
|
30
31
|
*
|
|
31
32
|
* @example
|
package/src/utils/trackEvent.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { FrakClient } from "../types";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Analytics event names emitted by the SDK
|
|
5
|
+
*/
|
|
3
6
|
export type FrakEvent =
|
|
4
7
|
| "share_button_clicked"
|
|
5
8
|
| "wallet_button_clicked"
|
|
@@ -13,6 +16,13 @@ export type FrakEvent =
|
|
|
13
16
|
|
|
14
17
|
type EventProps = Record<string, unknown>;
|
|
15
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Track an analytics event via OpenPanel.
|
|
21
|
+
* Fire-and-forget: silently catches errors.
|
|
22
|
+
* @param client - The Frak client instance (no-op if undefined)
|
|
23
|
+
* @param event - The event name to track
|
|
24
|
+
* @param props - Optional event properties
|
|
25
|
+
*/
|
|
16
26
|
export function trackEvent(
|
|
17
27
|
client: FrakClient | undefined,
|
|
18
28
|
event: FrakEvent,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const e=require(`./trackEvent-CGIryq5h.cjs`);let t=require(`@frak-labs/frame-connector`),n=require(`@openpanel/web`);const r=`nexus-wallet-backup`,i=`frakwallet://`;function a(e,t){if(typeof window>`u`)return;let n=new URL(window.location.href),r=n.searchParams.get(`sso`);r&&(t.then(()=>{e.sendLifecycle({clientLifecycle:`sso-redirect-complete`,data:{compressed:r}}),console.log(`[SSO URL Listener] Forwarded compressed SSO data to iframe`)}).catch(e=>{console.error(`[SSO URL Listener] Failed to forward SSO data:`,e)}),n.searchParams.delete(`sso`),window.history.replaceState({},``,n.toString()),console.log(`[SSO URL Listener] SSO parameter detected and URL cleaned`))}var o=class e{config;iframe;isSetupDone=!1;lastResponse=null;lastRequest=null;constructor(e,t){this.config=e,this.iframe=t,this.lastRequest=null,this.lastResponse=null}setLastResponse(e,t){this.lastResponse={message:e,response:t,timestamp:Date.now()}}setLastRequest(e){this.lastRequest={event:e,timestamp:Date.now()}}updateSetupStatus(e){this.isSetupDone=e}base64Encode(e){try{return btoa(JSON.stringify(e))}catch(e){return console.warn(`Failed to encode debug data`,e),btoa(`Failed to encode data`)}}getIframeStatus(){return this.iframe?{loading:this.iframe.hasAttribute(`loading`),url:this.iframe.src,readyState:this.iframe.contentDocument?.readyState?this.iframe.contentDocument.readyState===`complete`?1:0:-1,contentWindow:!!this.iframe.contentWindow,isConnected:this.iframe.isConnected}:null}getNavigatorInfo(){return navigator?{userAgent:navigator.userAgent,language:navigator.language,onLine:navigator.onLine,screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio}:null}gatherDebugInfo(e){let n=this.getIframeStatus(),r=this.getNavigatorInfo(),i=`Unknown`;return e instanceof t.FrakRpcError?i=`FrakRpcError: ${e.code} '${e.message}'`:e instanceof Error?i=e.message:typeof e==`string`&&(i=e),{timestamp:new Date().toISOString(),encodedUrl:btoa(window.location.href),encodedConfig:this.config?this.base64Encode(this.config):`no-config`,navigatorInfo:r?this.base64Encode(r):`no-navigator`,iframeStatus:n?this.base64Encode(n):`not-iframe`,lastRequest:this.lastRequest?this.base64Encode(this.lastRequest):`No Frak request logged`,lastResponse:this.lastResponse?this.base64Encode(this.lastResponse):`No Frak response logged`,clientStatus:this.isSetupDone?`setup`:`not-setup`,error:i}}static empty(){return new e}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
|
|
2
|
-
Debug Information:
|
|
3
|
-
-----------------
|
|
4
|
-
Timestamp: ${t.timestamp}
|
|
5
|
-
URL: ${t.encodedUrl}
|
|
6
|
-
Config: ${t.encodedConfig}
|
|
7
|
-
Navigator Info: ${t.navigatorInfo}
|
|
8
|
-
IFrame Status: ${t.iframeStatus}
|
|
9
|
-
Last Request: ${t.lastRequest}
|
|
10
|
-
Last Response: ${t.lastResponse}
|
|
11
|
-
Client Status: ${t.clientStatus}
|
|
12
|
-
Error: ${t.error}
|
|
13
|
-
`.trim()}};function s(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function c(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;package=id.frak.wallet;end`}function l(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=s()&&u(e)?c(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function u(e){return e.startsWith(i)}const d={id:`frak-wallet`,name:`frak-wallet`,title:`Frak Wallet`,allow:`publickey-credentials-get *; clipboard-write; web-share *`,style:{width:`0`,height:`0`,border:`0`,position:`absolute`,zIndex:2000001,top:`-1000px`,left:`-1000px`,colorScheme:`auto`}};function f({walletBaseUrl:e,config:t}){let n=document.querySelector(`#frak-wallet`);n&&n.remove();let r=document.createElement(`iframe`);return r.id=d.id,r.name=d.name,r.allow=d.allow,r.style.zIndex=d.style.zIndex.toString(),p({iframe:r,isVisible:!1}),r.src=`${t?.walletUrl??e??`https://wallet.frak.id`}/listener`,new Promise(e=>{r.addEventListener(`load`,()=>e(r)),document.body.appendChild(r)})}function p({iframe:e,isVisible:t}){if(!t){e.style.width=`0`,e.style.height=`0`,e.style.border=`0`,e.style.position=`fixed`,e.style.top=`-1000px`,e.style.left=`-1000px`;return}e.style.position=`fixed`,e.style.top=`0`,e.style.left=`0`,e.style.width=`100%`,e.style.height=`100%`,e.style.pointerEvents=`auto`}function m(e=`/listener`){if(!window.opener)return null;let t=t=>{try{return t.location.origin===window.location.origin&&t.location.pathname===e}catch{return!1}};if(t(window.opener))return window.opener;try{let e=window.opener.frames;for(let n=0;n<e.length;n++)if(t(e[n]))return e[n];return null}catch(t){return console.error(`[findIframeInOpener] Error finding iframe with pathname ${e}:`,t),null}}function h(e){e?localStorage.setItem(r,e):localStorage.removeItem(r)}function g(t,n,r,i){let a=new URL(window.location.href),o=a.searchParams.get(`fmt`)??void 0;t.contentWindow?.postMessage({clientLifecycle:`handshake-response`,data:{token:n,currentUrl:window.location.href,clientId:e.d(),pendingMergeToken:o,configDomain:i}},r),o&&(a.searchParams.delete(`fmt`),window.history.replaceState({},``,a.toString()))}function _(e,t){try{let n=new URL(e);return n.searchParams.has(`u`)?(n.searchParams.delete(`u`),n.searchParams.append(`u`,window.location.href),t&&n.searchParams.append(`fmt`,t),n.toString()):e}catch{return e}}function v(e,t,n,r){let i=_(t,r);u(t)?l(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}}):window.location.href=i}function y({iframe:e,targetOrigin:n,configDomain:i}){let a=new t.Deferred;return{handleEvent:async t=>{if(!(`iframeLifecycle`in t))return;let{iframeLifecycle:o,data:s}=t;switch(o){case`connected`:a.resolve(!0);break;case`do-backup`:h(s.backup);break;case`remove-backup`:localStorage.removeItem(r);break;case`show`:case`hide`:p({iframe:e,isVisible:o===`show`});break;case`handshake`:g(e,s.token,n,i);break;case`redirect`:v(e,s.baseRedirectUrl,n,s.mergeToken);break}},isConnected:a.promise}}function b({config:e,iframe:r}){let i=e?.walletUrl??`https://wallet.frak.id`,a=y({iframe:r,targetOrigin:i,configDomain:e.domain}),s=new o(e,r);if(!r.contentWindow)throw new t.FrakRpcError(t.RpcErrorCodes.configError,`The iframe does not have a content window`);let c=(0,t.createRpcClient)({emittingTransport:r.contentWindow,listeningTransport:window,targetOrigin:i,middleware:[{async onRequest(e,n){if(!await a.isConnected)throw new t.FrakRpcError(t.RpcErrorCodes.clientNotConnected,`The iframe provider isn't connected yet`);return n}},{onRequest(e,t){return s.setLastRequest(e),t},onResponse(e,t){return s.setLastResponse(e,t),t}}],lifecycleHandlers:{iframeLifecycle:async(e,t)=>{await a.handleEvent(e)}}}),l=x(c,a),u=async()=>{l(),c.cleanup(),r.remove()},d;console.log(`[Frak SDK] Initializing OpenPanel`),d=new n.OpenPanel({apiUrl:`https://op-api.gcp.frak.id`,clientId:`6eacc8d7-49ac-4936-95e9-81ef29449570`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:e,payload:t})=>(e!==`track`||!t?.properties||`sdkVersion`in t.properties||(t.properties={...t.properties,sdkVersion:`0.1.1`}),!0)}),d.setGlobalProperties({sdkVersion:`0.1.1`}),d.init();let f=S({config:e,rpcClient:c,lifecycleManager:a}).then(()=>s.updateSetupStatus(!0));return{config:e,debugInfo:s,waitForConnection:a.isConnected,waitForSetup:f,request:c.request,listenerRequest:c.listen,destroy:u,openPanel:d}}function x(e,t){let n,r,i=()=>e.sendLifecycle({clientLifecycle:`heartbeat`});async function a(){i(),n=setInterval(i,1e3),r=setTimeout(()=>{o(),console.log(`Heartbeat timeout: connection failed`)},3e4),await t.isConnected,o()}function o(){n&&clearInterval(n),r&&clearTimeout(r)}return a(),o}async function S({config:e,rpcClient:t,lifecycleManager:n}){await n.isConnected,a(t,n.isConnected);async function i(){let n=e.customizations?.css;if(!n)return;let r={clientLifecycle:`modal-css`,data:{cssLink:n}};t.sendLifecycle(r)}async function o(){let n=e.customizations?.i18n;if(!n)return;let r={clientLifecycle:`modal-i18n`,data:{i18n:n}};t.sendLifecycle(r)}async function s(){if(typeof window>`u`)return;let e=window.localStorage.getItem(r);if(!e)return;let n={clientLifecycle:`restore-backup`,data:{backup:e}};t.sendLifecycle(n)}await Promise.allSettled([i(),o(),s()])}function C(n){return(0,t.jsonDecode)(e.l(n))}const w={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function T(e){return e&&e in w?e:`eur`}function E(e){return e?w[e]??w.eur:w.eur}function D(e,t){let n=E(t),r=T(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function O(e){return e?`${e}Amount`:`eurAmount`}let k,A;async function j(e,t){if(k)return k;if(A)return A;A=M(e,t);let n=await A;return A=void 0,n}async function M(t,n){let r=t??(typeof window<`u`?window.location.hostname:``);if(r)try{let t=e.r(n),i=await fetch(`${t}/user/merchant/resolve?domain=${encodeURIComponent(r)}`);if(!i.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${r}: ${i.status}`);return}return k=(await i.json()).merchantId,k}catch(e){console.warn(`[Frak SDK] Failed to fetch merchantId:`,e);return}}function N(){k=void 0,A=void 0}async function P(e,t){return e.metadata?.merchantId?e.metadata.merchantId:j(void 0,t)}async function F({config:e}){let t=I(e),n=await f({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=b({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function I(e){let t=T(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return P}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return o}});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import{d as e,l as t,r as n}from"./trackEvent-YfUh4jrx.js";import{Deferred as r,FrakRpcError as i,RpcErrorCodes as a,createRpcClient as o,jsonDecode as s}from"@frak-labs/frame-connector";import{OpenPanel as c}from"@openpanel/web";const l=`nexus-wallet-backup`,u=`frakwallet://`;function d(e,t){if(typeof window>`u`)return;let n=new URL(window.location.href),r=n.searchParams.get(`sso`);r&&(t.then(()=>{e.sendLifecycle({clientLifecycle:`sso-redirect-complete`,data:{compressed:r}}),console.log(`[SSO URL Listener] Forwarded compressed SSO data to iframe`)}).catch(e=>{console.error(`[SSO URL Listener] Failed to forward SSO data:`,e)}),n.searchParams.delete(`sso`),window.history.replaceState({},``,n.toString()),console.log(`[SSO URL Listener] SSO parameter detected and URL cleaned`))}var f=class e{config;iframe;isSetupDone=!1;lastResponse=null;lastRequest=null;constructor(e,t){this.config=e,this.iframe=t,this.lastRequest=null,this.lastResponse=null}setLastResponse(e,t){this.lastResponse={message:e,response:t,timestamp:Date.now()}}setLastRequest(e){this.lastRequest={event:e,timestamp:Date.now()}}updateSetupStatus(e){this.isSetupDone=e}base64Encode(e){try{return btoa(JSON.stringify(e))}catch(e){return console.warn(`Failed to encode debug data`,e),btoa(`Failed to encode data`)}}getIframeStatus(){return this.iframe?{loading:this.iframe.hasAttribute(`loading`),url:this.iframe.src,readyState:this.iframe.contentDocument?.readyState?this.iframe.contentDocument.readyState===`complete`?1:0:-1,contentWindow:!!this.iframe.contentWindow,isConnected:this.iframe.isConnected}:null}getNavigatorInfo(){return navigator?{userAgent:navigator.userAgent,language:navigator.language,onLine:navigator.onLine,screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio}:null}gatherDebugInfo(e){let t=this.getIframeStatus(),n=this.getNavigatorInfo(),r=`Unknown`;return e instanceof i?r=`FrakRpcError: ${e.code} '${e.message}'`:e instanceof Error?r=e.message:typeof e==`string`&&(r=e),{timestamp:new Date().toISOString(),encodedUrl:btoa(window.location.href),encodedConfig:this.config?this.base64Encode(this.config):`no-config`,navigatorInfo:n?this.base64Encode(n):`no-navigator`,iframeStatus:t?this.base64Encode(t):`not-iframe`,lastRequest:this.lastRequest?this.base64Encode(this.lastRequest):`No Frak request logged`,lastResponse:this.lastResponse?this.base64Encode(this.lastResponse):`No Frak response logged`,clientStatus:this.isSetupDone?`setup`:`not-setup`,error:r}}static empty(){return new e}formatDebugInfo(e){let t=this.gatherDebugInfo(e);return`
|
|
2
|
-
Debug Information:
|
|
3
|
-
-----------------
|
|
4
|
-
Timestamp: ${t.timestamp}
|
|
5
|
-
URL: ${t.encodedUrl}
|
|
6
|
-
Config: ${t.encodedConfig}
|
|
7
|
-
Navigator Info: ${t.navigatorInfo}
|
|
8
|
-
IFrame Status: ${t.iframeStatus}
|
|
9
|
-
Last Request: ${t.lastRequest}
|
|
10
|
-
Last Response: ${t.lastResponse}
|
|
11
|
-
Client Status: ${t.clientStatus}
|
|
12
|
-
Error: ${t.error}
|
|
13
|
-
`.trim()}};function p(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function m(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;package=id.frak.wallet;end`}function h(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let a=p()&&g(e)?m(e):e;window.location.href=a,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function g(e){return e.startsWith(u)}const _={id:`frak-wallet`,name:`frak-wallet`,title:`Frak Wallet`,allow:`publickey-credentials-get *; clipboard-write; web-share *`,style:{width:`0`,height:`0`,border:`0`,position:`absolute`,zIndex:2000001,top:`-1000px`,left:`-1000px`,colorScheme:`auto`}};function v({walletBaseUrl:e,config:t}){let n=document.querySelector(`#frak-wallet`);n&&n.remove();let r=document.createElement(`iframe`);return r.id=_.id,r.name=_.name,r.allow=_.allow,r.style.zIndex=_.style.zIndex.toString(),y({iframe:r,isVisible:!1}),r.src=`${t?.walletUrl??e??`https://wallet.frak.id`}/listener`,new Promise(e=>{r.addEventListener(`load`,()=>e(r)),document.body.appendChild(r)})}function y({iframe:e,isVisible:t}){if(!t){e.style.width=`0`,e.style.height=`0`,e.style.border=`0`,e.style.position=`fixed`,e.style.top=`-1000px`,e.style.left=`-1000px`;return}e.style.position=`fixed`,e.style.top=`0`,e.style.left=`0`,e.style.width=`100%`,e.style.height=`100%`,e.style.pointerEvents=`auto`}function b(e=`/listener`){if(!window.opener)return null;let t=t=>{try{return t.location.origin===window.location.origin&&t.location.pathname===e}catch{return!1}};if(t(window.opener))return window.opener;try{let e=window.opener.frames;for(let n=0;n<e.length;n++)if(t(e[n]))return e[n];return null}catch(t){return console.error(`[findIframeInOpener] Error finding iframe with pathname ${e}:`,t),null}}function x(e){e?localStorage.setItem(l,e):localStorage.removeItem(l)}function S(t,n,r,i){let a=new URL(window.location.href),o=a.searchParams.get(`fmt`)??void 0;t.contentWindow?.postMessage({clientLifecycle:`handshake-response`,data:{token:n,currentUrl:window.location.href,clientId:e(),pendingMergeToken:o,configDomain:i}},r),o&&(a.searchParams.delete(`fmt`),window.history.replaceState({},``,a.toString()))}function C(e,t){try{let n=new URL(e);return n.searchParams.has(`u`)?(n.searchParams.delete(`u`),n.searchParams.append(`u`,window.location.href),t&&n.searchParams.append(`fmt`,t),n.toString()):e}catch{return e}}function w(e,t,n,r){let i=C(t,r);g(t)?h(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}}):window.location.href=i}function T({iframe:e,targetOrigin:t,configDomain:n}){let i=new r;return{handleEvent:async r=>{if(!(`iframeLifecycle`in r))return;let{iframeLifecycle:a,data:o}=r;switch(a){case`connected`:i.resolve(!0);break;case`do-backup`:x(o.backup);break;case`remove-backup`:localStorage.removeItem(l);break;case`show`:case`hide`:y({iframe:e,isVisible:a===`show`});break;case`handshake`:S(e,o.token,t,n);break;case`redirect`:w(e,o.baseRedirectUrl,t,o.mergeToken);break}},isConnected:i.promise}}function E({config:e,iframe:t}){let n=e?.walletUrl??`https://wallet.frak.id`,r=T({iframe:t,targetOrigin:n,configDomain:e.domain}),s=new f(e,t);if(!t.contentWindow)throw new i(a.configError,`The iframe does not have a content window`);let l=o({emittingTransport:t.contentWindow,listeningTransport:window,targetOrigin:n,middleware:[{async onRequest(e,t){if(!await r.isConnected)throw new i(a.clientNotConnected,`The iframe provider isn't connected yet`);return t}},{onRequest(e,t){return s.setLastRequest(e),t},onResponse(e,t){return s.setLastResponse(e,t),t}}],lifecycleHandlers:{iframeLifecycle:async(e,t)=>{await r.handleEvent(e)}}}),u=D(l,r),d=async()=>{u(),l.cleanup(),t.remove()},p;console.log(`[Frak SDK] Initializing OpenPanel`),p=new c({apiUrl:`https://op-api.gcp.frak.id`,clientId:`6eacc8d7-49ac-4936-95e9-81ef29449570`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:e,payload:t})=>(e!==`track`||!t?.properties||`sdkVersion`in t.properties||(t.properties={...t.properties,sdkVersion:`0.1.1`}),!0)}),p.setGlobalProperties({sdkVersion:`0.1.1`}),p.init();let m=O({config:e,rpcClient:l,lifecycleManager:r}).then(()=>s.updateSetupStatus(!0));return{config:e,debugInfo:s,waitForConnection:r.isConnected,waitForSetup:m,request:l.request,listenerRequest:l.listen,destroy:d,openPanel:p}}function D(e,t){let n,r,i=()=>e.sendLifecycle({clientLifecycle:`heartbeat`});async function a(){i(),n=setInterval(i,1e3),r=setTimeout(()=>{o(),console.log(`Heartbeat timeout: connection failed`)},3e4),await t.isConnected,o()}function o(){n&&clearInterval(n),r&&clearTimeout(r)}return a(),o}async function O({config:e,rpcClient:t,lifecycleManager:n}){await n.isConnected,d(t,n.isConnected);async function r(){let n=e.customizations?.css;if(!n)return;let r={clientLifecycle:`modal-css`,data:{cssLink:n}};t.sendLifecycle(r)}async function i(){let n=e.customizations?.i18n;if(!n)return;let r={clientLifecycle:`modal-i18n`,data:{i18n:n}};t.sendLifecycle(r)}async function a(){if(typeof window>`u`)return;let e=window.localStorage.getItem(l);if(!e)return;let n={clientLifecycle:`restore-backup`,data:{backup:e}};t.sendLifecycle(n)}await Promise.allSettled([r(),i(),a()])}function k(e){return s(t(e))}const A={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function j(e){return e&&e in A?e:`eur`}function M(e){return e?A[e]??A.eur:A.eur}function N(e,t){let n=M(t),r=j(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function P(e){return e?`${e}Amount`:`eurAmount`}let F,I;async function L(e,t){if(F)return F;if(I)return I;I=R(e,t);let n=await I;return I=void 0,n}async function R(e,t){let r=e??(typeof window<`u`?window.location.hostname:``);if(r)try{let e=n(t),i=await fetch(`${e}/user/merchant/resolve?domain=${encodeURIComponent(r)}`);if(!i.ok){console.warn(`[Frak SDK] Merchant lookup failed for domain ${r}: ${i.status}`);return}return F=(await i.json()).merchantId,F}catch(e){console.warn(`[Frak SDK] Failed to fetch merchantId:`,e);return}}function z(){F=void 0,I=void 0}async function B(e,t){return e.metadata?.merchantId?e.metadata.merchantId:L(void 0,t)}async function V({config:e}){let t=H(e),n=await v({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=E({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function H(e){let t=j(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}export{m as _,P as a,u as b,j as c,E as d,_ as f,g,p as h,B as i,A as l,b as m,z as n,N as o,v as p,L as r,M as s,V as t,k as u,h as v,f as y};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=require(`./trackEvent-CGIryq5h.cjs`);let t=require(`viem`),n=require(`@frak-labs/frame-connector`),r=require(`viem/siwe`);async function i(e,t){return await e.request({method:`frak_displayEmbeddedWallet`,params:[t,e.config.metadata]})}async function a(e,{steps:t,metadata:n}){return await e.request({method:`frak_displayModal`,params:[t,n,e.config.metadata]})}async function o(e){return await e.request({method:`frak_getMerchantInformation`})}async function s(e,t){let{metadata:n,customizations:r}=e.config;return await e.request({method:`frak_prepareSso`,params:[t,n.name,r?.css]})}async function c(e,t){try{await e.request({method:`frak_sendInteraction`,params:[t]})}catch{console.warn(`[Frak SDK] Failed to send interaction:`,t.type)}}async function l(t,{walletStatus:r,frakContext:i,modalConfig:a,options:o}){if(!i?.r)return`no-referrer`;e.t(t,`user_referred_started`,{properties:{referrer:i?.r,walletStatus:r?.key}}),c(t,{type:`arrival`,referrerWallet:i.r,landingUrl:typeof window<`u`?window.location.href:void 0});let s=!1;async function l(){if(!s)return s=!0,d(t,{modalConfig:{...a,loggedIn:{action:{key:`referred`}}},walletStatus:r})}try{let{status:n,currentWallet:a}=await u({initialWalletStatus:r,getFreshWalletStatus:l,frakContext:i});return e.n.replaceUrl({url:window.location?.href,context:o?.alwaysAppendUrl?{r:a}:null}),e.t(t,`user_referred_completed`,{properties:{status:n,referrer:i?.r,wallet:a}}),n}catch(a){return console.log(`Error processing referral`,{error:a}),e.t(t,`user_referred_error`,{properties:{referrer:i?.r,error:a instanceof n.FrakRpcError?`[${a.code}] ${a.name} - ${a.message}`:a instanceof Error?a.message:`undefined`}}),e.n.replaceUrl({url:window.location?.href,context:o?.alwaysAppendUrl?{r:r?.wallet}:null}),f(a)}}async function u({initialWalletStatus:e,getFreshWalletStatus:n,frakContext:r}){let i=e?.wallet;return i||=await n(),i&&(0,t.isAddressEqual)(r.r,i)?{status:`self-referral`,currentWallet:i}:{status:`success`,currentWallet:i}}async function d(e,{modalConfig:t,walletStatus:n}){return n?.key===`connected`?n.wallet??void 0:(await i(e,t??{}))?.wallet??void 0}function f(e){if(e instanceof n.FrakRpcError)switch(e.code){case n.RpcErrorCodes.walletNotConnected:return`no-wallet`;default:return`error`}return`error`}async function p(t,{modalConfig:n,options:r}={}){let i=e.n.parse({url:window.location.href}),a=await h(t);try{return await l(t,{walletStatus:a,frakContext:i,modalConfig:n,options:r})}catch(e){console.warn(`Error processing referral`,{error:e})}}async function m(t){if(typeof window>`u`){console.warn(`[Frak] No window found, can't track purchase`);return}let n=window.sessionStorage.getItem(`frak-wallet-interaction-token`);if(!n){console.warn(`[Frak] No frak session found, skipping purchase check`);return}let r=e.r();await fetch(`${r}/interactions/listenForPurchase`,{method:`POST`,headers:{Accept:`application/json`,"Content-Type":`application/json`,"x-wallet-sdk-auth":n},body:JSON.stringify(t)})}function h(e,t){if(!t)return e.request({method:`frak_listenToWalletStatus`}).then(t=>(g(e,t),t));let r=new n.Deferred,i=!1;return e.listenerRequest({method:`frak_listenToWalletStatus`},n=>{g(e,n),t(n),i||=(r.resolve(n),!0)}),r.promise}function g(e,t){typeof window>`u`||(e.openPanel?.setGlobalProperties({wallet:t.wallet??null}),t.interactionToken?window.sessionStorage.setItem(`frak-wallet-interaction-token`,t.interactionToken):window.sessionStorage.removeItem(`frak-wallet-interaction-token`))}function _(e,{metadata:t,login:n}){return v(e,{steps:{login:n??{}},metadata:t})}function v(e,t){function n(n){return v(e,{...t,steps:{...t.steps,sendTransaction:n}})}function r(n){return v(e,{...t,steps:{...t.steps,final:{...n,action:{key:`reward`}}}})}function i(n,r){return v(e,{...t,steps:{...t.steps,final:{...r,action:{key:`sharing`,options:n}}}})}async function o(n){return n&&(t.metadata=n(t.metadata??{})),await a(e,t)}return{params:t,sendTx:n,reward:r,sharing:i,display:o}}async function y(e,{tx:t,metadata:n}){return(await a(e,{metadata:n,steps:{login:{},sendTransaction:{tx:t}}})).sendTransaction}async function b(e,{siwe:t,metadata:n}){let i=e.config?.domain??window.location.host,o=t?.statement??`I confirm that I want to use my Frak wallet on: ${e.config.metadata.name}`;return(await a(e,{metadata:n,steps:{login:{},siweAuthenticate:{siwe:{...t,statement:o,nonce:t?.nonce??(0,r.generateSiweNonce)(),uri:t?.uri??`https://${i}`,version:t?.version??`1`,domain:i}}}})).siweAuthenticate}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return o}});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e,r as t,t as n}from"./trackEvent-YfUh4jrx.js";import{isAddressEqual as r}from"viem";import{Deferred as i,FrakRpcError as a,RpcErrorCodes as o}from"@frak-labs/frame-connector";import{generateSiweNonce as s}from"viem/siwe";async function c(e,t){return await e.request({method:`frak_displayEmbeddedWallet`,params:[t,e.config.metadata]})}async function l(e,{steps:t,metadata:n}){return await e.request({method:`frak_displayModal`,params:[t,n,e.config.metadata]})}async function u(e){return await e.request({method:`frak_getMerchantInformation`})}async function d(e,t){let{metadata:n,customizations:r}=e.config;return await e.request({method:`frak_prepareSso`,params:[t,n.name,r?.css]})}async function f(e,t){try{await e.request({method:`frak_sendInteraction`,params:[t]})}catch{console.warn(`[Frak SDK] Failed to send interaction:`,t.type)}}async function p(t,{walletStatus:r,frakContext:i,modalConfig:o,options:s}){if(!i?.r)return`no-referrer`;n(t,`user_referred_started`,{properties:{referrer:i?.r,walletStatus:r?.key}}),f(t,{type:`arrival`,referrerWallet:i.r,landingUrl:typeof window<`u`?window.location.href:void 0});let c=!1;async function l(){if(!c)return c=!0,h(t,{modalConfig:{...o,loggedIn:{action:{key:`referred`}}},walletStatus:r})}try{let{status:a,currentWallet:o}=await m({initialWalletStatus:r,getFreshWalletStatus:l,frakContext:i});return e.replaceUrl({url:window.location?.href,context:s?.alwaysAppendUrl?{r:o}:null}),n(t,`user_referred_completed`,{properties:{status:a,referrer:i?.r,wallet:o}}),a}catch(o){return console.log(`Error processing referral`,{error:o}),n(t,`user_referred_error`,{properties:{referrer:i?.r,error:o instanceof a?`[${o.code}] ${o.name} - ${o.message}`:o instanceof Error?o.message:`undefined`}}),e.replaceUrl({url:window.location?.href,context:s?.alwaysAppendUrl?{r:r?.wallet}:null}),g(o)}}async function m({initialWalletStatus:e,getFreshWalletStatus:t,frakContext:n}){let i=e?.wallet;return i||=await t(),i&&r(n.r,i)?{status:`self-referral`,currentWallet:i}:{status:`success`,currentWallet:i}}async function h(e,{modalConfig:t,walletStatus:n}){return n?.key===`connected`?n.wallet??void 0:(await c(e,t??{}))?.wallet??void 0}function g(e){if(e instanceof a)switch(e.code){case o.walletNotConnected:return`no-wallet`;default:return`error`}return`error`}async function _(t,{modalConfig:n,options:r}={}){let i=e.parse({url:window.location.href}),a=await y(t);try{return await p(t,{walletStatus:a,frakContext:i,modalConfig:n,options:r})}catch(e){console.warn(`Error processing referral`,{error:e})}}async function v(e){if(typeof window>`u`){console.warn(`[Frak] No window found, can't track purchase`);return}let n=window.sessionStorage.getItem(`frak-wallet-interaction-token`);if(!n){console.warn(`[Frak] No frak session found, skipping purchase check`);return}let r=t();await fetch(`${r}/interactions/listenForPurchase`,{method:`POST`,headers:{Accept:`application/json`,"Content-Type":`application/json`,"x-wallet-sdk-auth":n},body:JSON.stringify(e)})}function y(e,t){if(!t)return e.request({method:`frak_listenToWalletStatus`}).then(t=>(b(e,t),t));let n=new i,r=!1;return e.listenerRequest({method:`frak_listenToWalletStatus`},i=>{b(e,i),t(i),r||=(n.resolve(i),!0)}),n.promise}function b(e,t){typeof window>`u`||(e.openPanel?.setGlobalProperties({wallet:t.wallet??null}),t.interactionToken?window.sessionStorage.setItem(`frak-wallet-interaction-token`,t.interactionToken):window.sessionStorage.removeItem(`frak-wallet-interaction-token`))}function x(e,{metadata:t,login:n}){return S(e,{steps:{login:n??{}},metadata:t})}function S(e,t){function n(n){return S(e,{...t,steps:{...t.steps,sendTransaction:n}})}function r(n){return S(e,{...t,steps:{...t.steps,final:{...n,action:{key:`reward`}}}})}function i(n,r){return S(e,{...t,steps:{...t.steps,final:{...r,action:{key:`sharing`,options:n}}}})}async function a(n){return n&&(t.metadata=n(t.metadata??{})),await l(e,t)}return{params:t,sendTx:n,reward:r,sharing:i,display:a}}async function C(e,{tx:t,metadata:n}){return(await l(e,{metadata:n,steps:{login:{},sendTransaction:{tx:t}}})).sendTransaction}async function w(e,{siwe:t,metadata:n}){let r=e.config?.domain??window.location.host,i=t?.statement??`I confirm that I want to use my Frak wallet on: ${e.config.metadata.name}`;return(await l(e,{metadata:n,steps:{login:{},siweAuthenticate:{siwe:{...t,statement:i,nonce:t?.nonce??s(),uri:t?.uri??`https://${r}`,version:t?.version??`1`,domain:r}}}})).siweAuthenticate}export{v as a,f as c,l as d,c as f,y as i,d as l,C as n,_ as o,x as r,p as s,w as t,u};
|