@frak-labs/core-sdk 0.2.1-beta.d2556d47 → 0.2.1-beta.eb3cff34
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/cdn/bundle.js +3 -55
- package/dist/actions-D4aBXbdp.cjs +1 -0
- package/dist/actions-Dq_uN-wn.js +1 -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 -4
- package/dist/bundle.d.ts +4 -4
- package/dist/bundle.js +1 -1
- package/dist/{computeLegacyProductId-BP-ciVsp.d.cts → index-BV5D9DsW.d.ts} +71 -3
- package/dist/{siweAuthenticate-yITE-iKh.d.cts → index-BphwTmKA.d.cts} +115 -4
- package/dist/{computeLegacyProductId-DiJd7RNo.d.ts → index-Dwmo109y.d.cts} +71 -3
- package/dist/{siweAuthenticate-CDCsp8EJ.d.ts → index-_f8EuN_1.d.ts} +115 -4
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/{openSso-B8v3Vtnh.d.ts → openSso-BwEK2M98.d.cts} +174 -7
- package/dist/{openSso-n_B4LSuW.d.cts → openSso-C1Wzl5-i.d.ts} +174 -7
- package/dist/src-B3Dusips.cjs +13 -0
- package/dist/src-CnnhYPyK.js +13 -0
- package/dist/trackEvent-BqJqRZ-u.cjs +1 -0
- package/dist/trackEvent-Bqq4jd6R.js +1 -0
- package/package.json +9 -10
- package/src/actions/displaySharingPage.ts +49 -0
- package/src/actions/getMerchantInformation.test.ts +13 -1
- package/src/actions/getMerchantInformation.ts +20 -5
- package/src/actions/getMergeToken.ts +33 -0
- package/src/actions/getUserReferralStatus.ts +42 -0
- package/src/actions/index.ts +8 -1
- package/src/actions/referral/setupReferral.test.ts +79 -0
- package/src/actions/referral/setupReferral.ts +32 -0
- package/src/clients/createIFrameFrakClient.ts +5 -2
- package/src/clients/transports/iframeLifecycleManager.test.ts +14 -14
- package/src/clients/transports/iframeLifecycleManager.ts +35 -9
- package/src/index.ts +12 -1
- package/src/stubs/rrweb.ts +9 -0
- package/src/types/index.ts +7 -0
- package/src/types/lifecycle/iframe.ts +7 -0
- package/src/types/resolvedConfig.ts +26 -2
- package/src/types/rpc/displaySharingPage.ts +82 -0
- package/src/types/rpc/embedded/index.ts +1 -1
- package/src/types/rpc/userReferralStatus.ts +20 -0
- package/src/types/rpc.ts +47 -0
- package/src/utils/cache/index.ts +7 -0
- package/src/utils/cache/lruMap.test.ts +55 -0
- package/src/utils/cache/lruMap.ts +38 -0
- package/src/utils/cache/withCache.test.ts +168 -0
- package/src/utils/cache/withCache.ts +124 -0
- package/src/utils/inAppBrowser.ts +60 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/sdkConfigStore.ts +21 -35
- package/dist/setupClient-Dr_UYfTD.cjs +0 -13
- package/dist/setupClient-TuhDjVJx.js +0 -13
- package/dist/siweAuthenticate-0UPcUqI1.js +0 -1
- package/dist/siweAuthenticate-CfQibjZR.cjs +0 -1
- package/dist/trackEvent-5j5kkOCj.js +0 -1
- package/dist/trackEvent-B2uom25e.cjs +0 -1
package/src/types/rpc.ts
CHANGED
|
@@ -4,6 +4,10 @@ import type {
|
|
|
4
4
|
ModalRpcStepsInput,
|
|
5
5
|
ModalRpcStepsResultType,
|
|
6
6
|
} from "./rpc/displayModal";
|
|
7
|
+
import type {
|
|
8
|
+
DisplaySharingPageParamsType,
|
|
9
|
+
DisplaySharingPageResultType,
|
|
10
|
+
} from "./rpc/displaySharingPage";
|
|
7
11
|
import type {
|
|
8
12
|
DisplayEmbeddedWalletParamsType,
|
|
9
13
|
DisplayEmbeddedWalletResultType,
|
|
@@ -16,6 +20,7 @@ import type {
|
|
|
16
20
|
PrepareSsoParamsType,
|
|
17
21
|
PrepareSsoReturnType,
|
|
18
22
|
} from "./rpc/sso";
|
|
23
|
+
import type { UserReferralStatusType } from "./rpc/userReferralStatus";
|
|
19
24
|
import type { WalletStatusReturnType } from "./rpc/walletStatus";
|
|
20
25
|
|
|
21
26
|
/**
|
|
@@ -56,6 +61,11 @@ import type { WalletStatusReturnType } from "./rpc/walletStatus";
|
|
|
56
61
|
* - Params: [request: {@link DisplayEmbeddedWalletParamsType}, metadata: {@link FrakWalletSdkConfig}["metadata"], placement?: string]
|
|
57
62
|
* - Returns: {@link DisplayEmbeddedWalletResultType}
|
|
58
63
|
* - Response Type: promise (one-shot)
|
|
64
|
+
*
|
|
65
|
+
* #### frak_displaySharingPage
|
|
66
|
+
* - Params: [request: {@link DisplaySharingPageParamsType}, configMetadata: {@link FrakWalletSdkConfig}["metadata"], placement?: string]
|
|
67
|
+
* - Returns: {@link DisplaySharingPageResultType}
|
|
68
|
+
* - Response Type: promise (one-shot)
|
|
59
69
|
*/
|
|
60
70
|
export type IFrameRpcSchema = [
|
|
61
71
|
/**
|
|
@@ -149,4 +159,41 @@ export type IFrameRpcSchema = [
|
|
|
149
159
|
];
|
|
150
160
|
ReturnType: undefined;
|
|
151
161
|
},
|
|
162
|
+
/**
|
|
163
|
+
* Method to get the current user's referral status on this merchant.
|
|
164
|
+
* Returns whether the user was referred (has a referral link as referee).
|
|
165
|
+
* Returns null when the user's identity cannot be resolved.
|
|
166
|
+
* This is a one-shot request.
|
|
167
|
+
*/
|
|
168
|
+
{
|
|
169
|
+
Method: "frak_getUserReferralStatus";
|
|
170
|
+
Parameters?: undefined;
|
|
171
|
+
ReturnType: UserReferralStatusType | null;
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* Method to display a sharing page with product info and sharing buttons
|
|
175
|
+
* Resolves on first user action (share/copy) but the page stays visible
|
|
176
|
+
* This is a one-shot request
|
|
177
|
+
*/
|
|
178
|
+
{
|
|
179
|
+
Method: "frak_displaySharingPage";
|
|
180
|
+
Parameters: [
|
|
181
|
+
request: DisplaySharingPageParamsType,
|
|
182
|
+
configMetadata: FrakWalletSdkConfig["metadata"],
|
|
183
|
+
placement?: string,
|
|
184
|
+
];
|
|
185
|
+
ReturnType: DisplaySharingPageResultType;
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* Method to get a merge token for the current anonymous identity.
|
|
189
|
+
* Used by in-app browser redirect flows to preserve identity
|
|
190
|
+
* when switching from a WebView to the system browser.
|
|
191
|
+
* Returns the merge token string, or null if unavailable.
|
|
192
|
+
* This is a one-shot request.
|
|
193
|
+
*/
|
|
194
|
+
{
|
|
195
|
+
Method: "frak_getMergeToken";
|
|
196
|
+
Parameters?: undefined;
|
|
197
|
+
ReturnType: string | null;
|
|
198
|
+
},
|
|
152
199
|
];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from "../../../tests/vitest-fixtures";
|
|
2
|
+
import { LruMap } from "./lruMap";
|
|
3
|
+
|
|
4
|
+
describe("LruMap", () => {
|
|
5
|
+
it("should store and retrieve values", () => {
|
|
6
|
+
const map = new LruMap<number>(3);
|
|
7
|
+
map.set("a", 1);
|
|
8
|
+
map.set("b", 2);
|
|
9
|
+
|
|
10
|
+
expect(map.get("a")).toBe(1);
|
|
11
|
+
expect(map.get("b")).toBe(2);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should evict least recently used when exceeding max size", () => {
|
|
15
|
+
const map = new LruMap<number>(2);
|
|
16
|
+
map.set("a", 1);
|
|
17
|
+
map.set("b", 2);
|
|
18
|
+
map.set("c", 3); // Should evict "a"
|
|
19
|
+
|
|
20
|
+
expect(map.get("a")).toBeUndefined();
|
|
21
|
+
expect(map.get("b")).toBe(2);
|
|
22
|
+
expect(map.get("c")).toBe(3);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should promote accessed keys to most recently used", () => {
|
|
26
|
+
const map = new LruMap<number>(2);
|
|
27
|
+
map.set("a", 1);
|
|
28
|
+
map.set("b", 2);
|
|
29
|
+
|
|
30
|
+
// Access "a" to promote it
|
|
31
|
+
map.get("a");
|
|
32
|
+
|
|
33
|
+
// "b" is now least recently used, should be evicted
|
|
34
|
+
map.set("c", 3);
|
|
35
|
+
|
|
36
|
+
expect(map.get("a")).toBe(1);
|
|
37
|
+
expect(map.get("b")).toBeUndefined();
|
|
38
|
+
expect(map.get("c")).toBe(3);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should overwrite existing keys without increasing size", () => {
|
|
42
|
+
const map = new LruMap<number>(2);
|
|
43
|
+
map.set("a", 1);
|
|
44
|
+
map.set("b", 2);
|
|
45
|
+
map.set("a", 10); // Overwrite, not a new entry
|
|
46
|
+
|
|
47
|
+
expect(map.size).toBe(2);
|
|
48
|
+
expect(map.get("a")).toBe(10);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should return undefined for missing keys", () => {
|
|
52
|
+
const map = new LruMap<number>(2);
|
|
53
|
+
expect(map.get("missing")).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map with a LRU (Least Recently Used) eviction policy.
|
|
3
|
+
*
|
|
4
|
+
* When the map exceeds `maxSize`, the least recently accessed entry is removed.
|
|
5
|
+
* Accessing a key via `get()` promotes it to "most recently used".
|
|
6
|
+
*
|
|
7
|
+
* Adapted from viem's LruMap utility.
|
|
8
|
+
* @link https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
|
|
9
|
+
*/
|
|
10
|
+
export class LruMap<TValue = unknown> extends Map<string, TValue> {
|
|
11
|
+
maxSize: number;
|
|
12
|
+
|
|
13
|
+
constructor(size: number) {
|
|
14
|
+
super();
|
|
15
|
+
this.maxSize = size;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override get(key: string) {
|
|
19
|
+
const value = super.get(key);
|
|
20
|
+
if (super.has(key)) {
|
|
21
|
+
// Move to end (most recently used)
|
|
22
|
+
super.delete(key);
|
|
23
|
+
super.set(key, value as TValue);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override set(key: string, value: TValue) {
|
|
29
|
+
if (super.has(key)) super.delete(key);
|
|
30
|
+
super.set(key, value);
|
|
31
|
+
// Evict least recently used if over capacity
|
|
32
|
+
if (this.maxSize && this.size > this.maxSize) {
|
|
33
|
+
const firstKey = super.keys().next().value;
|
|
34
|
+
if (firstKey !== undefined) super.delete(firstKey);
|
|
35
|
+
}
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
vi,
|
|
8
|
+
} from "../../../tests/vitest-fixtures";
|
|
9
|
+
import { clearAllCache, withCache } from "./withCache";
|
|
10
|
+
|
|
11
|
+
describe("withCache", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
clearAllCache();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("caching behavior", () => {
|
|
21
|
+
it("should call fn on first invocation", async () => {
|
|
22
|
+
const fn = vi.fn().mockResolvedValue("result");
|
|
23
|
+
|
|
24
|
+
const result = await withCache(fn, {
|
|
25
|
+
cacheKey: "test-key",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(fn).toHaveBeenCalledOnce();
|
|
29
|
+
expect(result).toBe("result");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should return cached result on subsequent calls within TTL", async () => {
|
|
33
|
+
const fn = vi.fn().mockResolvedValue("result");
|
|
34
|
+
|
|
35
|
+
await withCache(fn, {
|
|
36
|
+
cacheKey: "test-key",
|
|
37
|
+
cacheTime: 10_000,
|
|
38
|
+
});
|
|
39
|
+
const result = await withCache(fn, {
|
|
40
|
+
cacheKey: "test-key",
|
|
41
|
+
cacheTime: 10_000,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(fn).toHaveBeenCalledOnce();
|
|
45
|
+
expect(result).toBe("result");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should re-fetch after cache expires", async () => {
|
|
49
|
+
vi.useFakeTimers();
|
|
50
|
+
|
|
51
|
+
const fn = vi
|
|
52
|
+
.fn()
|
|
53
|
+
.mockResolvedValueOnce("first")
|
|
54
|
+
.mockResolvedValueOnce("second");
|
|
55
|
+
|
|
56
|
+
const first = await withCache(fn, {
|
|
57
|
+
cacheKey: "test-key",
|
|
58
|
+
cacheTime: 100,
|
|
59
|
+
});
|
|
60
|
+
expect(first).toBe("first");
|
|
61
|
+
|
|
62
|
+
// Advance past TTL
|
|
63
|
+
vi.advanceTimersByTime(200);
|
|
64
|
+
|
|
65
|
+
const second = await withCache(fn, {
|
|
66
|
+
cacheKey: "test-key",
|
|
67
|
+
cacheTime: 100,
|
|
68
|
+
});
|
|
69
|
+
expect(second).toBe("second");
|
|
70
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
71
|
+
|
|
72
|
+
vi.useRealTimers();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should not cache when cacheTime is 0", async () => {
|
|
76
|
+
const fn = vi.fn().mockResolvedValue("result");
|
|
77
|
+
|
|
78
|
+
await withCache(fn, { cacheKey: "test-key", cacheTime: 0 });
|
|
79
|
+
await withCache(fn, { cacheKey: "test-key", cacheTime: 0 });
|
|
80
|
+
|
|
81
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should use different caches for different keys", async () => {
|
|
85
|
+
const fnA = vi.fn().mockResolvedValue("a");
|
|
86
|
+
const fnB = vi.fn().mockResolvedValue("b");
|
|
87
|
+
|
|
88
|
+
const a = await withCache(fnA, { cacheKey: "key-a" });
|
|
89
|
+
const b = await withCache(fnB, { cacheKey: "key-b" });
|
|
90
|
+
|
|
91
|
+
expect(a).toBe("a");
|
|
92
|
+
expect(b).toBe("b");
|
|
93
|
+
expect(fnA).toHaveBeenCalledOnce();
|
|
94
|
+
expect(fnB).toHaveBeenCalledOnce();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("deduplication", () => {
|
|
99
|
+
it("should deduplicate concurrent calls with the same key", async () => {
|
|
100
|
+
let resolvePromise: (value: string) => void;
|
|
101
|
+
const fn = vi.fn().mockImplementation(
|
|
102
|
+
() =>
|
|
103
|
+
new Promise<string>((resolve) => {
|
|
104
|
+
resolvePromise = resolve;
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const promise1 = withCache(fn, { cacheKey: "dedup-key" });
|
|
109
|
+
const promise2 = withCache(fn, { cacheKey: "dedup-key" });
|
|
110
|
+
|
|
111
|
+
// fn should only be called once
|
|
112
|
+
expect(fn).toHaveBeenCalledOnce();
|
|
113
|
+
|
|
114
|
+
// Both should resolve to the same value
|
|
115
|
+
resolvePromise!("shared");
|
|
116
|
+
const [result1, result2] = await Promise.all([promise1, promise2]);
|
|
117
|
+
expect(result1).toBe("shared");
|
|
118
|
+
expect(result2).toBe("shared");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("error handling", () => {
|
|
123
|
+
it("should propagate errors from fn", async () => {
|
|
124
|
+
const fn = vi.fn().mockRejectedValue(new Error("fetch failed"));
|
|
125
|
+
|
|
126
|
+
await expect(
|
|
127
|
+
withCache(fn, { cacheKey: "error-key" })
|
|
128
|
+
).rejects.toThrow("fetch failed");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should not cache errors — subsequent call retries", async () => {
|
|
132
|
+
vi.useFakeTimers();
|
|
133
|
+
const fn = vi
|
|
134
|
+
.fn()
|
|
135
|
+
.mockRejectedValueOnce(new Error("fail"))
|
|
136
|
+
.mockResolvedValueOnce("recovered");
|
|
137
|
+
|
|
138
|
+
await expect(
|
|
139
|
+
withCache(fn, { cacheKey: "retry-key" })
|
|
140
|
+
).rejects.toThrow("fail");
|
|
141
|
+
|
|
142
|
+
// Advance past the negative cache backoff (1s)
|
|
143
|
+
vi.advanceTimersByTime(1_001);
|
|
144
|
+
|
|
145
|
+
const result = await withCache(fn, { cacheKey: "retry-key" });
|
|
146
|
+
expect(result).toBe("recovered");
|
|
147
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
148
|
+
|
|
149
|
+
vi.useRealTimers();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("clearAllCache", () => {
|
|
154
|
+
it("should clear all cached data", async () => {
|
|
155
|
+
const fn = vi
|
|
156
|
+
.fn()
|
|
157
|
+
.mockResolvedValueOnce("first")
|
|
158
|
+
.mockResolvedValueOnce("second");
|
|
159
|
+
|
|
160
|
+
await withCache(fn, { cacheKey: "clear-key" });
|
|
161
|
+
clearAllCache();
|
|
162
|
+
const result = await withCache(fn, { cacheKey: "clear-key" });
|
|
163
|
+
|
|
164
|
+
expect(result).toBe("second");
|
|
165
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { LruMap } from "./lruMap";
|
|
2
|
+
|
|
3
|
+
type CacheEntry<TData> = {
|
|
4
|
+
data: TData;
|
|
5
|
+
created: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/** Global cache for in-flight promises (dedup concurrent calls) */
|
|
9
|
+
const promiseCache = new LruMap<Promise<unknown>>(1024);
|
|
10
|
+
|
|
11
|
+
/** Global cache for resolved responses (TTL-based) */
|
|
12
|
+
const responseCache = new LruMap<CacheEntry<unknown>>(1024);
|
|
13
|
+
|
|
14
|
+
/** Default cache time: 30 seconds */
|
|
15
|
+
export const DEFAULT_CACHE_TIME = 30_000;
|
|
16
|
+
|
|
17
|
+
/** Short negative cache to avoid flooding on transient failures */
|
|
18
|
+
const NEGATIVE_CACHE_TIME = 1_000;
|
|
19
|
+
|
|
20
|
+
/** Tracks recently failed keys to avoid request floods */
|
|
21
|
+
const failureCache = new LruMap<number>(1024);
|
|
22
|
+
|
|
23
|
+
type WithCacheOptions = {
|
|
24
|
+
/** The key to cache the data against */
|
|
25
|
+
cacheKey: string;
|
|
26
|
+
/** Time in ms that cached data will remain valid. Default: 30_000 (30s). Set to 0 to disable caching. */
|
|
27
|
+
cacheTime?: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the result of a given promise, and caches the result for
|
|
32
|
+
* subsequent invocations against a provided cache key.
|
|
33
|
+
*
|
|
34
|
+
* Also deduplicates concurrent calls — if multiple callers request the same
|
|
35
|
+
* cache key while the promise is pending, they share the same promise.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* // First call fetches, subsequent calls return cached data for 30s
|
|
40
|
+
* const data = await withCache(
|
|
41
|
+
* () => client.request({ method: "frak_getMerchantInformation" }),
|
|
42
|
+
* { cacheKey: "merchantInfo", cacheTime: 30_000 }
|
|
43
|
+
* );
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export async function withCache<TData>(
|
|
47
|
+
fn: () => Promise<TData>,
|
|
48
|
+
{ cacheKey, cacheTime = DEFAULT_CACHE_TIME }: WithCacheOptions
|
|
49
|
+
): Promise<TData> {
|
|
50
|
+
// Check response cache — return immediately if fresh
|
|
51
|
+
if (cacheTime > 0) {
|
|
52
|
+
const cached = responseCache.get(cacheKey) as
|
|
53
|
+
| CacheEntry<TData>
|
|
54
|
+
| undefined;
|
|
55
|
+
if (cached) {
|
|
56
|
+
const age = Date.now() - cached.created;
|
|
57
|
+
if (age < cacheTime) return cached.data;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if this key recently failed — back off briefly
|
|
62
|
+
const lastFailure = failureCache.get(cacheKey);
|
|
63
|
+
if (lastFailure && Date.now() - lastFailure < NEGATIVE_CACHE_TIME) {
|
|
64
|
+
throw new Error(`Cache: ${cacheKey} recently failed, backing off`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check if there's already a pending promise (dedup concurrent calls)
|
|
68
|
+
let promise = promiseCache.get(cacheKey) as Promise<TData> | undefined;
|
|
69
|
+
if (!promise) {
|
|
70
|
+
promise = fn();
|
|
71
|
+
promiseCache.set(cacheKey, promise);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const data = await promise;
|
|
76
|
+
// Store the response with a timestamp
|
|
77
|
+
responseCache.set(cacheKey, { data, created: Date.now() });
|
|
78
|
+
// Clear any previous failure
|
|
79
|
+
failureCache.delete(cacheKey);
|
|
80
|
+
return data;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Record the failure timestamp
|
|
83
|
+
failureCache.set(cacheKey, Date.now());
|
|
84
|
+
throw error;
|
|
85
|
+
} finally {
|
|
86
|
+
// Clear the promise cache so subsequent calls can re-fetch after TTL
|
|
87
|
+
promiseCache.delete(cacheKey);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get a cache handle for a specific key, useful for manual invalidation.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* // Invalidate merchant info cache after a mutation
|
|
97
|
+
* getCache("frak_getMerchantInformation").clear();
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function getCache(cacheKey: string) {
|
|
101
|
+
return {
|
|
102
|
+
/** Clear both the pending promise and the cached response */
|
|
103
|
+
clear: () => {
|
|
104
|
+
promiseCache.delete(cacheKey);
|
|
105
|
+
responseCache.delete(cacheKey);
|
|
106
|
+
},
|
|
107
|
+
/** Check if a non-expired response exists */
|
|
108
|
+
has: (cacheTime: number = DEFAULT_CACHE_TIME) => {
|
|
109
|
+
const cached = responseCache.get(cacheKey);
|
|
110
|
+
if (!cached) return false;
|
|
111
|
+
return Date.now() - cached.created < cacheTime;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Clear all cached data (both pending promises and resolved responses).
|
|
118
|
+
* Called automatically when the client is destroyed.
|
|
119
|
+
*/
|
|
120
|
+
export function clearAllCache() {
|
|
121
|
+
promiseCache.clear();
|
|
122
|
+
responseCache.clear();
|
|
123
|
+
failureCache.clear();
|
|
124
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if the current device runs iOS (including iPadOS 13+).
|
|
3
|
+
*/
|
|
4
|
+
function checkIsIOS(): boolean {
|
|
5
|
+
if (typeof navigator === "undefined") return false;
|
|
6
|
+
const ua = navigator.userAgent;
|
|
7
|
+
// Standard iOS devices
|
|
8
|
+
if (/iPhone|iPad|iPod/i.test(ua)) return true;
|
|
9
|
+
// iPadOS 13+ reports as Macintosh — detect via touch support
|
|
10
|
+
if (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1) return true;
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether the current device runs iOS (including iPadOS 13+).
|
|
16
|
+
*/
|
|
17
|
+
export const isIOS: boolean = checkIsIOS();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if the current browser is a social media in-app browser
|
|
21
|
+
* (Instagram, Facebook WebView).
|
|
22
|
+
*/
|
|
23
|
+
function checkInAppBrowser(): boolean {
|
|
24
|
+
if (typeof navigator === "undefined") return false;
|
|
25
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
26
|
+
return (
|
|
27
|
+
ua.includes("instagram") ||
|
|
28
|
+
ua.includes("fban") ||
|
|
29
|
+
ua.includes("fbav") ||
|
|
30
|
+
ua.includes("facebook")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether the current browser is a social media in-app browser
|
|
36
|
+
* (Instagram, Facebook).
|
|
37
|
+
*/
|
|
38
|
+
export const isInAppBrowser: boolean = checkInAppBrowser();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Redirect to external browser from in-app WebView.
|
|
42
|
+
*
|
|
43
|
+
* - **iOS**: Uses `x-safari-https://` scheme — server-side 302 redirects
|
|
44
|
+
* to custom URL schemes are silently swallowed by WKWebView.
|
|
45
|
+
* Direct `window.location.href` assignment works (confirmed iOS 17+).
|
|
46
|
+
*
|
|
47
|
+
* - **Android**: Uses backend `/common/social` endpoint which returns a PDF
|
|
48
|
+
* Content-Type response, forcing the WebView to hand off to the default browser.
|
|
49
|
+
*
|
|
50
|
+
* @param targetUrl - The URL to open in the external browser
|
|
51
|
+
*/
|
|
52
|
+
export function redirectToExternalBrowser(targetUrl: string): void {
|
|
53
|
+
if (isIOS && targetUrl.startsWith("https://")) {
|
|
54
|
+
window.location.href = `x-safari-https://${targetUrl.slice(8)}`;
|
|
55
|
+
} else if (isIOS && targetUrl.startsWith("http://")) {
|
|
56
|
+
window.location.href = `x-safari-http://${targetUrl.slice(7)}`;
|
|
57
|
+
} else {
|
|
58
|
+
window.location.href = `${process.env.BACKEND_URL}/common/social?u=${encodeURIComponent(targetUrl)}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { Deferred } from "@frak-labs/frame-connector";
|
|
2
2
|
export { getBackendUrl } from "./backendUrl";
|
|
3
|
+
export { clearAllCache, getCache, withCache } from "./cache";
|
|
3
4
|
export { getClientId } from "./clientId";
|
|
4
5
|
export { base64urlDecode, base64urlEncode } from "./compression/b64";
|
|
5
6
|
export { compressJsonToB64 } from "./compression/compress";
|
|
@@ -22,6 +23,11 @@ export {
|
|
|
22
23
|
createIframe,
|
|
23
24
|
findIframeInOpener,
|
|
24
25
|
} from "./iframeHelper";
|
|
26
|
+
export {
|
|
27
|
+
isInAppBrowser,
|
|
28
|
+
isIOS,
|
|
29
|
+
redirectToExternalBrowser,
|
|
30
|
+
} from "./inAppBrowser";
|
|
25
31
|
export { sdkConfigStore } from "./sdkConfigStore";
|
|
26
32
|
export {
|
|
27
33
|
type AppSpecificSsoMetadata,
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Reactivity is handled via the `frak:config` CustomEvent on `window`.
|
|
6
6
|
* Resolved configs are cached in localStorage (30 s TTL, stale-while-revalidate).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Backend fetch responses are cached and deduplicated via `withCache`.
|
|
9
|
+
* Also owns the `frak-merchant-id` sessionStorage compatibility key.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Language } from "../types/config";
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
SdkResolvedConfig,
|
|
16
16
|
} from "../types/resolvedConfig";
|
|
17
17
|
import { getBackendUrl } from "./backendUrl";
|
|
18
|
+
import { clearAllCache, withCache } from "./cache";
|
|
18
19
|
|
|
19
20
|
const GLOBAL_KEY = "__frakSdkConfig";
|
|
20
21
|
const CACHE_TTL = 30_000; // 30 seconds
|
|
@@ -118,19 +119,9 @@ function getTargetDomain(domain?: string): string {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
// ---------------------------------------------------------------------------
|
|
121
|
-
// Merchant config fetching (resolve)
|
|
122
|
+
// Merchant config fetching (resolve)
|
|
122
123
|
// ---------------------------------------------------------------------------
|
|
123
124
|
|
|
124
|
-
const responseCache = new Map<string, MerchantConfigResponse>();
|
|
125
|
-
const promiseCache = new Map<
|
|
126
|
-
string,
|
|
127
|
-
Promise<MerchantConfigResponse | undefined>
|
|
128
|
-
>();
|
|
129
|
-
|
|
130
|
-
function resolveCacheKey(domain: string, lang?: string): string {
|
|
131
|
-
return `${domain}:${lang ?? ""}`;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
125
|
async function fetchFromBackend(
|
|
135
126
|
targetDomain: string,
|
|
136
127
|
walletUrl?: string,
|
|
@@ -151,8 +142,6 @@ async function fetchFromBackend(
|
|
|
151
142
|
}
|
|
152
143
|
|
|
153
144
|
const data = (await response.json()) as MerchantConfigResponse;
|
|
154
|
-
const key = resolveCacheKey(targetDomain, lang);
|
|
155
|
-
responseCache.set(key, data);
|
|
156
145
|
|
|
157
146
|
// Write compatibility sessionStorage key
|
|
158
147
|
if (isBrowser) {
|
|
@@ -210,8 +199,7 @@ export const sdkConfigStore = {
|
|
|
210
199
|
|
|
211
200
|
clearCache(): void {
|
|
212
201
|
removeCache();
|
|
213
|
-
|
|
214
|
-
promiseCache.clear();
|
|
202
|
+
clearAllCache();
|
|
215
203
|
if (isBrowser) {
|
|
216
204
|
try {
|
|
217
205
|
sessionStorage.removeItem(MERCHANT_ID_KEY);
|
|
@@ -229,25 +217,23 @@ export const sdkConfigStore = {
|
|
|
229
217
|
return Promise.resolve(undefined);
|
|
230
218
|
}
|
|
231
219
|
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
promiseCache.delete(key);
|
|
220
|
+
const cacheKey = `sdkConfig:${targetDomain}:${lang ?? ""}`;
|
|
221
|
+
|
|
222
|
+
return withCache(
|
|
223
|
+
async () => {
|
|
224
|
+
const result = await fetchFromBackend(
|
|
225
|
+
targetDomain,
|
|
226
|
+
walletUrl,
|
|
227
|
+
lang
|
|
228
|
+
);
|
|
229
|
+
// Throw on failure so withCache doesn't cache undefined
|
|
230
|
+
if (!result) {
|
|
231
|
+
throw new Error("Config resolution returned empty");
|
|
232
|
+
}
|
|
246
233
|
return result;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return promise;
|
|
234
|
+
},
|
|
235
|
+
{ cacheKey, cacheTime: Number.POSITIVE_INFINITY }
|
|
236
|
+
).catch(() => undefined);
|
|
251
237
|
},
|
|
252
238
|
|
|
253
239
|
getMerchantId(): string | undefined {
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const e=require(`./trackEvent-B2uom25e.cjs`);let t=require(`@frak-labs/frame-connector`),n=require(`@openpanel/web`);const r=`nexus-wallet-backup`,i=`frakwallet://`;function a(){let e=navigator.userAgent;return/Android/i.test(e)&&/Chrome\/\d+/i.test(e)}function o(e){return`intent://${e.slice(13)}#Intent;scheme=frakwallet;end`}function s(e,t){let n=t?.timeout??2500,r=!1,i=()=>{document.hidden&&(r=!0)};document.addEventListener(`visibilitychange`,i);let s=a()&&c(e)?o(e):e;window.location.href=s,setTimeout(()=>{document.removeEventListener(`visibilitychange`,i),r||t?.onFallback?.()},n)}function c(e){return e.startsWith(i)}const l={eur:`fr-FR`,usd:`en-US`,gbp:`en-GB`};function u(e){return e&&e in l?e:`eur`}function d(e){return e?l[e]??l.eur:l.eur}function f(e,t){let n=d(t),r=u(t);return e.toLocaleString(n,{style:`currency`,currency:r,minimumFractionDigits:0,maximumFractionDigits:2})}function p(e){return e?`${e}Amount`:`eurAmount`}const m={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 h({walletBaseUrl:t,config:n}){let r=document.querySelector(`#frak-wallet`);r&&r.remove();let i=document.createElement(`iframe`);i.id=m.id,i.name=m.name,i.allow=m.allow,i.style.zIndex=m.style.zIndex.toString(),g({iframe:i,isVisible:!1});let a=n?.walletUrl??t??`https://wallet.frak.id`,o=e.g();return i.src=`${a}/listener?clientId=${encodeURIComponent(o)}`,new Promise(e=>{i.addEventListener(`load`,()=>e(i)),document.body.appendChild(i)})}function g({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 _(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 v(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 y=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()}};const b=(()=>{if(typeof navigator>`u`)return!1;let e=navigator.userAgent;if(!(/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&navigator.maxTouchPoints>1))return!1;let t=e.toLowerCase();return t.includes(`instagram`)||t.includes(`fban`)||t.includes(`fbav`)||t.includes(`facebook`)})();function x(e){e?localStorage.setItem(r,e):localStorage.removeItem(r)}function S(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 C(e){let t=new URL(window.location.href);e&&t.searchParams.set(`fmt`,e);let n=t.protocol===`http:`?`x-safari-http`:`x-safari-https`;window.location.href=`${n}://${t.host}${t.pathname}${t.search}${t.hash}`}function w(e){return e.includes(`/common/social`)}function T(e,t,n,r){if(c(t)){let i=S(t,r);s(i,{onFallback:()=>{e.contentWindow?.postMessage({clientLifecycle:`deep-link-failed`,data:{originalUrl:i}},n)}})}else if(b&&w(t))C(r);else{let e=S(t,r);window.location.href=e}}function E({iframe:e,targetOrigin:n}){let i=new t.Deferred;return{handleEvent:async t=>{if(!(`iframeLifecycle`in t))return;let{iframeLifecycle:a,data:o}=t;switch(a){case`connected`:i.resolve(!0);break;case`do-backup`:x(o.backup);break;case`remove-backup`:localStorage.removeItem(r);break;case`show`:case`hide`:g({iframe:e,isVisible:a===`show`});break;case`redirect`:T(e,o.baseRedirectUrl,n,o.mergeToken);break}},isConnected:i.promise}}function D({config:r,iframe:i}){let a=r?.walletUrl??`https://wallet.frak.id`,o=typeof navigator<`u`?navigator.language?.split(`-`)[0]:void 0,s=r.metadata.lang??(o===`en`||o===`fr`?o:void 0),c=r.domain??(typeof window<`u`?window.location.hostname:``);e.n.setCacheScope(c,s),e.n.reset();let l=e.n.isCacheFresh?void 0:e.n.resolve(r.domain,r.walletUrl,s),u=E({iframe:i,targetOrigin:a}),d=new t.Deferred,f=new y(r,i);if(!i.contentWindow)throw new t.FrakRpcError(t.RpcErrorCodes.configError,`The iframe does not have a content window`);let p=(0,t.createRpcClient)({emittingTransport:i.contentWindow,listeningTransport:window,targetOrigin:a,middleware:[{async onRequest(e,n){if(!await u.isConnected)throw new t.FrakRpcError(t.RpcErrorCodes.clientNotConnected,`The iframe provider isn't connected yet`);return await d.promise,n}},{onRequest(e,t){return f.setLastRequest(e),t},onResponse(e,t){return f.setLastResponse(e,t),t}}],lifecycleHandlers:{iframeLifecycle:async(e,t)=>{await u.handleEvent(e)}}}),m=O(p,u),h=async()=>{m(),p.cleanup(),i.remove(),e.n.clearCache(),e.n.reset()},g;console.log(`[Frak SDK] Initializing OpenPanel`),g=new n.OpenPanel({apiUrl:`https://op-api.gcp.frak.id`,clientId:`6eacc8d7-49ac-4936-95e9-81ef29449570`,trackScreenViews:!0,trackOutgoingLinks:!0,trackAttributes:!1,filter:({type:t,payload:n})=>(t!==`track`||!n?.properties||`sdkVersion`in n.properties||(n.properties={...n.properties,sdkVersion:`0.2.1`,userAnonymousClientId:e.g()}),!0)}),g.setGlobalProperties({sdkVersion:`0.2.1`,userAnonymousClientId:e.g()}),g.init();let _=k({config:r,rpcClient:p,lifecycleManager:u,configPromise:l,contextSent:d}).then(()=>f.updateSetupStatus(!0)).catch(e=>{throw d.reject(e),e});return{config:r,debugInfo:f,waitForConnection:u.isConnected,waitForSetup:_,request:p.request,listenerRequest:p.listen,destroy:h,openPanel:g}}function O(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 k({config:t,rpcClient:n,lifecycleManager:i,configPromise:a,contextSent:o}){await i.isConnected,v(n,i.isConnected);let s=new URL(window.location.href),c=s.searchParams.get(`fmt`)??void 0;c&&(s.searchParams.delete(`fmt`),window.history.replaceState({},``,s.toString()));let l=n=>{let r=n?.merchantId??t.metadata.merchantId??``,i=n?.domain??``,a=n?.allowedDomains??[],o=n?.sdkConfig;e.n.setConfig(o?{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,hasRawSdkConfig:!0,name:o.name??t.metadata.name,logoUrl:o.logoUrl??t.metadata.logoUrl,homepageLink:o.homepageLink??t.metadata.homepageLink,lang:o.lang??t.metadata.lang,currency:o.currency??t.metadata.currency,hidden:o.hidden,css:o.css,translations:o.translations,placements:o.placements}:{isResolved:!0,merchantId:r,domain:i,allowedDomains:a,name:t.metadata.name,logoUrl:t.metadata.logoUrl,homepageLink:t.metadata.homepageLink,lang:t.metadata.lang,currency:t.metadata.currency})},u=!1,d=e=>{let t=u?void 0:c;u=!0;let r=e.hasRawSdkConfig?{name:e.name,logoUrl:e.logoUrl,homepageLink:e.homepageLink,lang:e.lang,currency:e.currency,hidden:e.hidden,css:e.css,translations:e.translations,placements:e.placements}:void 0;n.sendLifecycle({clientLifecycle:`resolved-config`,data:{merchantId:e.merchantId,domain:e.domain??``,allowedDomains:e.allowedDomains??[],sourceUrl:window.location.href,...t&&{pendingMergeToken:t},...r&&{sdkConfig:r}}})};e.n.isResolved&&(d(e.n.getConfig()),o.resolve()),a&&(l(await a),d(e.n.getConfig()),o.resolve());async function f(){let e=t.customizations?.css;e&&n.sendLifecycle({clientLifecycle:`modal-css`,data:{cssLink:e}})}async function p(){let e=t.customizations?.i18n;e&&n.sendLifecycle({clientLifecycle:`modal-i18n`,data:{i18n:e}})}async function m(){if(typeof window>`u`)return;let e=window.localStorage.getItem(r);e&&n.sendLifecycle({clientLifecycle:`restore-backup`,data:{backup:e}})}await Promise.allSettled([f(),p(),m()])}async function A({config:e}){let t=j(e),n=await h({config:t});if(!n){console.error(`Failed to create iframe`);return}let r=D({config:t,iframe:n});if(await r.waitForSetup,!await r.waitForConnection){console.error(`Failed to connect to client`);return}return r}function j(e){let t=u(e.metadata?.currency);return{...e,metadata:{...e.metadata,currency:t}}}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return u}});
|