@frak-labs/core-sdk 0.0.19 → 0.1.0-beta.00226d62
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.iife.js +14 -0
- package/dist/actions-CEEObPYc.js +1 -0
- package/dist/actions-DbQhWYx8.cjs +1 -0
- package/dist/actions.cjs +1 -1
- package/dist/actions.d.cts +3 -1400
- package/dist/actions.d.ts +3 -1400
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -13
- package/dist/bundle.d.cts +6 -2022
- package/dist/bundle.d.ts +6 -2022
- package/dist/bundle.js +1 -13
- package/dist/index-7OZ39x1U.d.ts +195 -0
- package/dist/index-C6FxkWPC.d.cts +511 -0
- package/dist/index-UFX7xCg3.d.ts +351 -0
- package/dist/index-d8xS4ryI.d.ts +511 -0
- package/dist/index-p4FqSp8z.d.cts +351 -0
- package/dist/index-zDq-VlKx.d.cts +195 -0
- package/dist/index.cjs +1 -13
- package/dist/index.d.cts +4 -1373
- package/dist/index.d.ts +4 -1373
- package/dist/index.js +1 -13
- package/dist/interaction-DMJ3ZfaF.d.cts +45 -0
- package/dist/interaction-KX1h9a7V.d.ts +45 -0
- package/dist/interactions-DnfM3oe0.js +1 -0
- package/dist/interactions-EIXhNLf6.cjs +1 -0
- package/dist/interactions.cjs +1 -1
- package/dist/interactions.d.cts +2 -182
- package/dist/interactions.d.ts +2 -182
- package/dist/interactions.js +1 -1
- package/dist/openSso-D--Airj6.d.cts +1018 -0
- package/dist/openSso-DsKJ4y0j.d.ts +1018 -0
- package/dist/productTypes-BUkXJKZ7.cjs +1 -0
- package/dist/productTypes-CGb1MmBF.js +1 -0
- package/dist/src-B_xO0AR6.cjs +13 -0
- package/dist/src-D2d52OZa.js +13 -0
- package/dist/trackEvent-CHnYa85W.js +1 -0
- package/dist/trackEvent-GuQm_1Nm.cjs +1 -0
- package/package.json +27 -18
- package/src/actions/displayEmbeddedWallet.test.ts +194 -0
- package/src/actions/displayEmbeddedWallet.ts +20 -0
- package/src/actions/displayModal.test.ts +387 -0
- package/src/actions/displayModal.ts +131 -0
- package/src/actions/getProductInformation.test.ts +133 -0
- package/src/actions/getProductInformation.ts +14 -0
- package/src/actions/index.ts +29 -0
- package/src/actions/openSso.test.ts +407 -0
- package/src/actions/openSso.ts +116 -0
- package/src/actions/prepareSso.test.ts +223 -0
- package/src/actions/prepareSso.ts +48 -0
- package/src/actions/referral/processReferral.test.ts +357 -0
- package/src/actions/referral/processReferral.ts +230 -0
- package/src/actions/referral/referralInteraction.test.ts +153 -0
- package/src/actions/referral/referralInteraction.ts +57 -0
- package/src/actions/sendInteraction.test.ts +219 -0
- package/src/actions/sendInteraction.ts +32 -0
- package/src/actions/trackPurchaseStatus.test.ts +287 -0
- package/src/actions/trackPurchaseStatus.ts +53 -0
- package/src/actions/watchWalletStatus.test.ts +372 -0
- package/src/actions/watchWalletStatus.ts +94 -0
- package/src/actions/wrapper/modalBuilder.test.ts +253 -0
- package/src/actions/wrapper/modalBuilder.ts +212 -0
- package/src/actions/wrapper/sendTransaction.test.ts +164 -0
- package/src/actions/wrapper/sendTransaction.ts +62 -0
- package/src/actions/wrapper/siweAuthenticate.test.ts +290 -0
- package/src/actions/wrapper/siweAuthenticate.ts +94 -0
- package/src/bundle.ts +3 -0
- package/src/clients/DebugInfo.test.ts +418 -0
- package/src/clients/DebugInfo.ts +182 -0
- package/src/clients/createIFrameFrakClient.ts +287 -0
- package/src/clients/index.ts +3 -0
- package/src/clients/setupClient.test.ts +343 -0
- package/src/clients/setupClient.ts +73 -0
- package/src/clients/transports/iframeLifecycleManager.test.ts +399 -0
- package/src/clients/transports/iframeLifecycleManager.ts +90 -0
- package/src/constants/interactionTypes.test.ts +128 -0
- package/src/constants/interactionTypes.ts +44 -0
- package/src/constants/locales.ts +14 -0
- package/src/constants/productTypes.test.ts +130 -0
- package/src/constants/productTypes.ts +33 -0
- package/src/index.ts +101 -0
- package/src/interactions/index.ts +5 -0
- package/src/interactions/pressEncoder.test.ts +215 -0
- package/src/interactions/pressEncoder.ts +53 -0
- package/src/interactions/purchaseEncoder.test.ts +291 -0
- package/src/interactions/purchaseEncoder.ts +99 -0
- package/src/interactions/referralEncoder.test.ts +170 -0
- package/src/interactions/referralEncoder.ts +47 -0
- package/src/interactions/retailEncoder.test.ts +107 -0
- package/src/interactions/retailEncoder.ts +37 -0
- package/src/interactions/webshopEncoder.test.ts +56 -0
- package/src/interactions/webshopEncoder.ts +30 -0
- package/src/types/client.ts +14 -0
- package/src/types/compression.ts +22 -0
- package/src/types/config.ts +111 -0
- package/src/types/context.ts +13 -0
- package/src/types/index.ts +71 -0
- package/src/types/lifecycle/client.ts +46 -0
- package/src/types/lifecycle/iframe.ts +35 -0
- package/src/types/lifecycle/index.ts +2 -0
- package/src/types/rpc/displayModal.ts +84 -0
- package/src/types/rpc/embedded/index.ts +68 -0
- package/src/types/rpc/embedded/loggedIn.ts +55 -0
- package/src/types/rpc/embedded/loggedOut.ts +28 -0
- package/src/types/rpc/interaction.ts +43 -0
- package/src/types/rpc/modal/final.ts +46 -0
- package/src/types/rpc/modal/generic.ts +46 -0
- package/src/types/rpc/modal/index.ts +20 -0
- package/src/types/rpc/modal/login.ts +32 -0
- package/src/types/rpc/modal/openSession.ts +25 -0
- package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
- package/src/types/rpc/modal/transaction.ts +33 -0
- package/src/types/rpc/productInformation.ts +59 -0
- package/src/types/rpc/sso.ts +80 -0
- package/src/types/rpc/walletStatus.ts +35 -0
- package/src/types/rpc.ts +158 -0
- package/src/types/transport.ts +34 -0
- package/src/utils/FrakContext.test.ts +407 -0
- package/src/utils/FrakContext.ts +158 -0
- package/src/utils/compression/b64.test.ts +181 -0
- package/src/utils/compression/b64.ts +29 -0
- package/src/utils/compression/compress.test.ts +123 -0
- package/src/utils/compression/compress.ts +11 -0
- package/src/utils/compression/decompress.test.ts +145 -0
- package/src/utils/compression/decompress.ts +11 -0
- package/src/utils/compression/index.ts +3 -0
- package/src/utils/computeProductId.test.ts +80 -0
- package/src/utils/computeProductId.ts +11 -0
- package/src/utils/constants.test.ts +23 -0
- package/src/utils/constants.ts +4 -0
- package/src/utils/formatAmount.test.ts +113 -0
- package/src/utils/formatAmount.ts +18 -0
- package/src/utils/getCurrencyAmountKey.test.ts +44 -0
- package/src/utils/getCurrencyAmountKey.ts +15 -0
- package/src/utils/getSupportedCurrency.test.ts +51 -0
- package/src/utils/getSupportedCurrency.ts +14 -0
- package/src/utils/getSupportedLocale.test.ts +64 -0
- package/src/utils/getSupportedLocale.ts +16 -0
- package/src/utils/iframeHelper.test.ts +450 -0
- package/src/utils/iframeHelper.ts +143 -0
- package/src/utils/index.ts +21 -0
- package/src/utils/sso.test.ts +361 -0
- package/src/utils/sso.ts +119 -0
- package/src/utils/ssoUrlListener.test.ts +252 -0
- package/src/utils/ssoUrlListener.ts +60 -0
- package/src/utils/trackEvent.test.ts +162 -0
- package/src/utils/trackEvent.ts +26 -0
- package/cdn/bundle.js +0 -19
- package/cdn/bundle.js.LICENSE.txt +0 -10
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRpcClient,
|
|
3
|
+
FrakRpcError,
|
|
4
|
+
type RpcClient,
|
|
5
|
+
RpcErrorCodes,
|
|
6
|
+
} from "@frak-labs/frame-connector";
|
|
7
|
+
import { createClientCompressionMiddleware } from "@frak-labs/frame-connector/middleware";
|
|
8
|
+
import { OpenPanel } from "@openpanel/web";
|
|
9
|
+
import type { FrakLifecycleEvent } from "../types";
|
|
10
|
+
import type { FrakClient } from "../types/client";
|
|
11
|
+
import type { FrakWalletSdkConfig } from "../types/config";
|
|
12
|
+
import type { IFrameRpcSchema } from "../types/rpc";
|
|
13
|
+
import { BACKUP_KEY } from "../utils/constants";
|
|
14
|
+
import { setupSsoUrlListener } from "../utils/ssoUrlListener";
|
|
15
|
+
import { DebugInfoGatherer } from "./DebugInfo";
|
|
16
|
+
import {
|
|
17
|
+
createIFrameLifecycleManager,
|
|
18
|
+
type IframeLifecycleManager,
|
|
19
|
+
} from "./transports/iframeLifecycleManager";
|
|
20
|
+
|
|
21
|
+
type SdkRpcClient = RpcClient<IFrameRpcSchema, FrakLifecycleEvent>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a new iframe Frak client
|
|
25
|
+
* @param args
|
|
26
|
+
* @param args.config - The configuration to use for the Frak Wallet SDK
|
|
27
|
+
* @param args.iframe - The iframe to use for the communication
|
|
28
|
+
* @returns The created Frak Client
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const frakConfig: FrakWalletSdkConfig = {
|
|
32
|
+
* metadata: {
|
|
33
|
+
* name: "My app title",
|
|
34
|
+
* },
|
|
35
|
+
* }
|
|
36
|
+
* const iframe = await createIframe({ config: frakConfig });
|
|
37
|
+
* const client = createIFrameFrakClient({ config: frakConfig, iframe });
|
|
38
|
+
*/
|
|
39
|
+
export function createIFrameFrakClient({
|
|
40
|
+
config,
|
|
41
|
+
iframe,
|
|
42
|
+
}: {
|
|
43
|
+
config: FrakWalletSdkConfig;
|
|
44
|
+
iframe: HTMLIFrameElement;
|
|
45
|
+
}): FrakClient {
|
|
46
|
+
const frakWalletUrl = config?.walletUrl ?? "https://wallet.frak.id";
|
|
47
|
+
|
|
48
|
+
// Create lifecycle manager
|
|
49
|
+
const lifecycleManager = createIFrameLifecycleManager({ iframe });
|
|
50
|
+
|
|
51
|
+
// Create our debug info gatherer
|
|
52
|
+
const debugInfo = new DebugInfoGatherer(config, iframe);
|
|
53
|
+
|
|
54
|
+
// Validate iframe
|
|
55
|
+
if (!iframe.contentWindow) {
|
|
56
|
+
throw new FrakRpcError(
|
|
57
|
+
RpcErrorCodes.configError,
|
|
58
|
+
"The iframe does not have a content window"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create RPC client with middleware and lifecycle handlers
|
|
63
|
+
const rpcClient = createRpcClient<IFrameRpcSchema, FrakLifecycleEvent>({
|
|
64
|
+
emittingTransport: iframe.contentWindow,
|
|
65
|
+
listeningTransport: window,
|
|
66
|
+
targetOrigin: frakWalletUrl,
|
|
67
|
+
// Add compression middleware to handle request/response compression
|
|
68
|
+
middleware: [
|
|
69
|
+
// Ensure we are connected before sending request
|
|
70
|
+
{
|
|
71
|
+
async onRequest(_message, ctx) {
|
|
72
|
+
// Ensure the iframe is connected
|
|
73
|
+
const isConnected = await lifecycleManager.isConnected;
|
|
74
|
+
if (!isConnected) {
|
|
75
|
+
throw new FrakRpcError(
|
|
76
|
+
RpcErrorCodes.clientNotConnected,
|
|
77
|
+
"The iframe provider isn't connected yet"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return ctx;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
createClientCompressionMiddleware(),
|
|
84
|
+
// Save debug info
|
|
85
|
+
{
|
|
86
|
+
onRequest(message, ctx) {
|
|
87
|
+
debugInfo.setLastRequest(message);
|
|
88
|
+
return ctx;
|
|
89
|
+
},
|
|
90
|
+
onResponse(message, response) {
|
|
91
|
+
debugInfo.setLastResponse(message, response);
|
|
92
|
+
return response;
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
// Add lifecycle handlers to process iframe lifecycle events
|
|
97
|
+
lifecycleHandlers: {
|
|
98
|
+
iframeLifecycle: async (event, _context) => {
|
|
99
|
+
// Delegate to lifecycle manager (cast for type compatibility)
|
|
100
|
+
await lifecycleManager.handleEvent(event);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Setup heartbeat
|
|
106
|
+
const stopHeartbeat = setupHeartbeat(rpcClient, lifecycleManager);
|
|
107
|
+
|
|
108
|
+
// Build our destroy function
|
|
109
|
+
const destroy = async () => {
|
|
110
|
+
// Stop heartbeat
|
|
111
|
+
stopHeartbeat();
|
|
112
|
+
// Cleanup the RPC client
|
|
113
|
+
rpcClient.cleanup();
|
|
114
|
+
// Remove the iframe
|
|
115
|
+
iframe.remove();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Init open panel
|
|
119
|
+
let openPanel: OpenPanel | undefined;
|
|
120
|
+
if (
|
|
121
|
+
process.env.OPEN_PANEL_API_URL &&
|
|
122
|
+
process.env.OPEN_PANEL_SDK_CLIENT_ID
|
|
123
|
+
) {
|
|
124
|
+
console.log("[Frak SDK] Initializing OpenPanel");
|
|
125
|
+
openPanel = new OpenPanel({
|
|
126
|
+
apiUrl: process.env.OPEN_PANEL_API_URL,
|
|
127
|
+
clientId: process.env.OPEN_PANEL_SDK_CLIENT_ID,
|
|
128
|
+
trackScreenViews: true,
|
|
129
|
+
trackOutgoingLinks: true,
|
|
130
|
+
trackAttributes: false,
|
|
131
|
+
// We use a filter to ensure we got the open panel instance initialized
|
|
132
|
+
// A bit hacky, but this way we are sure that we got everything needed for the first event ever sent
|
|
133
|
+
filter: ({ type, payload }) => {
|
|
134
|
+
if (type !== "track") return true;
|
|
135
|
+
if (!payload?.properties) return true;
|
|
136
|
+
|
|
137
|
+
// Check if we we got the properties once initialized
|
|
138
|
+
if (!("sdkVersion" in payload.properties)) {
|
|
139
|
+
payload.properties = {
|
|
140
|
+
...payload.properties,
|
|
141
|
+
sdkVersion: process.env.SDK_VERSION,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
openPanel.setGlobalProperties({
|
|
149
|
+
sdkVersion: process.env.SDK_VERSION,
|
|
150
|
+
});
|
|
151
|
+
openPanel.init();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Perform the post connection setup
|
|
155
|
+
const waitForSetup = postConnectionSetup({
|
|
156
|
+
config,
|
|
157
|
+
rpcClient,
|
|
158
|
+
lifecycleManager,
|
|
159
|
+
}).then(() => debugInfo.updateSetupStatus(true));
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
config,
|
|
163
|
+
debugInfo,
|
|
164
|
+
waitForConnection: lifecycleManager.isConnected,
|
|
165
|
+
waitForSetup,
|
|
166
|
+
request: rpcClient.request,
|
|
167
|
+
listenerRequest: rpcClient.listen,
|
|
168
|
+
destroy,
|
|
169
|
+
openPanel,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Setup the heartbeat
|
|
175
|
+
* @param rpcClient - RPC client to send lifecycle events
|
|
176
|
+
* @param lifecycleManager - Lifecycle manager to track connection
|
|
177
|
+
*/
|
|
178
|
+
function setupHeartbeat(
|
|
179
|
+
rpcClient: SdkRpcClient,
|
|
180
|
+
lifecycleManager: IframeLifecycleManager
|
|
181
|
+
) {
|
|
182
|
+
const HEARTBEAT_INTERVAL = 1_000; // Send heartbeat every 100ms until we are connected
|
|
183
|
+
const HEARTBEAT_TIMEOUT = 30_000; // 30 seconds timeout
|
|
184
|
+
let heartbeatInterval: NodeJS.Timeout;
|
|
185
|
+
let timeoutId: NodeJS.Timeout;
|
|
186
|
+
|
|
187
|
+
const sendHeartbeat = () =>
|
|
188
|
+
rpcClient.sendLifecycle({
|
|
189
|
+
clientLifecycle: "heartbeat",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Start sending heartbeats
|
|
193
|
+
async function startHeartbeat() {
|
|
194
|
+
sendHeartbeat(); // Send initial heartbeat
|
|
195
|
+
heartbeatInterval = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
|
|
196
|
+
|
|
197
|
+
// Set up timeout
|
|
198
|
+
timeoutId = setTimeout(() => {
|
|
199
|
+
stopHeartbeat();
|
|
200
|
+
console.log("Heartbeat timeout: connection failed");
|
|
201
|
+
}, HEARTBEAT_TIMEOUT);
|
|
202
|
+
|
|
203
|
+
// Once connected, stop it
|
|
204
|
+
await lifecycleManager.isConnected;
|
|
205
|
+
|
|
206
|
+
// We are now connected, stop the heartbeat
|
|
207
|
+
stopHeartbeat();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Stop sending heartbeats
|
|
211
|
+
function stopHeartbeat() {
|
|
212
|
+
if (heartbeatInterval) {
|
|
213
|
+
clearInterval(heartbeatInterval);
|
|
214
|
+
}
|
|
215
|
+
if (timeoutId) {
|
|
216
|
+
clearTimeout(timeoutId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
startHeartbeat();
|
|
221
|
+
|
|
222
|
+
// Return cleanup function
|
|
223
|
+
return stopHeartbeat;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Perform the post connection setup
|
|
228
|
+
* @param config - SDK configuration
|
|
229
|
+
* @param rpcClient - RPC client to send lifecycle events
|
|
230
|
+
* @param lifecycleManager - Lifecycle manager to track connection
|
|
231
|
+
*/
|
|
232
|
+
async function postConnectionSetup({
|
|
233
|
+
config,
|
|
234
|
+
rpcClient,
|
|
235
|
+
lifecycleManager,
|
|
236
|
+
}: {
|
|
237
|
+
config: FrakWalletSdkConfig;
|
|
238
|
+
rpcClient: SdkRpcClient;
|
|
239
|
+
lifecycleManager: IframeLifecycleManager;
|
|
240
|
+
}): Promise<void> {
|
|
241
|
+
// Wait for the handler to be connected
|
|
242
|
+
await lifecycleManager.isConnected;
|
|
243
|
+
|
|
244
|
+
// Setup SSO URL listener to detect and forward SSO redirects
|
|
245
|
+
// This checks for ?sso= parameter and forwards compressed data to iframe
|
|
246
|
+
setupSsoUrlListener(rpcClient, lifecycleManager.isConnected);
|
|
247
|
+
|
|
248
|
+
// Push raw CSS if needed
|
|
249
|
+
async function pushCss() {
|
|
250
|
+
const cssLink = config.customizations?.css;
|
|
251
|
+
if (!cssLink) return;
|
|
252
|
+
|
|
253
|
+
const message = {
|
|
254
|
+
clientLifecycle: "modal-css" as const,
|
|
255
|
+
data: { cssLink },
|
|
256
|
+
};
|
|
257
|
+
rpcClient.sendLifecycle(message);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Push i18n if needed
|
|
261
|
+
async function pushI18n() {
|
|
262
|
+
const i18n = config.customizations?.i18n;
|
|
263
|
+
if (!i18n) return;
|
|
264
|
+
|
|
265
|
+
const message = {
|
|
266
|
+
clientLifecycle: "modal-i18n" as const,
|
|
267
|
+
data: { i18n },
|
|
268
|
+
};
|
|
269
|
+
rpcClient.sendLifecycle(message);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Push local backup if needed
|
|
273
|
+
async function pushBackup() {
|
|
274
|
+
if (typeof window === "undefined") return;
|
|
275
|
+
|
|
276
|
+
const backup = window.localStorage.getItem(BACKUP_KEY);
|
|
277
|
+
if (!backup) return;
|
|
278
|
+
|
|
279
|
+
const message = {
|
|
280
|
+
clientLifecycle: "restore-backup" as const,
|
|
281
|
+
data: { backup },
|
|
282
|
+
};
|
|
283
|
+
rpcClient.sendLifecycle(message);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
await Promise.allSettled([pushCss(), pushI18n(), pushBackup()]);
|
|
287
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Mock dependencies
|
|
4
|
+
vi.mock("../utils", () => ({
|
|
5
|
+
createIframe: vi.fn(),
|
|
6
|
+
getSupportedCurrency: vi.fn((currency) => currency || "eur"),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock("./createIFrameFrakClient", () => ({
|
|
10
|
+
createIFrameFrakClient: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe("setupClient", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("client setup", () => {
|
|
19
|
+
test("should create iframe with prepared config", async () => {
|
|
20
|
+
const { setupClient } = await import("./setupClient");
|
|
21
|
+
const { createIframe } = await import("../utils");
|
|
22
|
+
const { createIFrameFrakClient } = await import(
|
|
23
|
+
"./createIFrameFrakClient"
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const mockIframe = document.createElement("iframe");
|
|
27
|
+
const mockClient = {
|
|
28
|
+
waitForSetup: Promise.resolve(),
|
|
29
|
+
waitForConnection: Promise.resolve(true),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
33
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
34
|
+
mockClient as any
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const config = {
|
|
38
|
+
metadata: {
|
|
39
|
+
name: "Test App",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
await setupClient({ config });
|
|
44
|
+
|
|
45
|
+
expect(createIframe).toHaveBeenCalledWith({
|
|
46
|
+
config: expect.objectContaining({
|
|
47
|
+
metadata: expect.objectContaining({
|
|
48
|
+
name: "Test App",
|
|
49
|
+
currency: "eur",
|
|
50
|
+
}),
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("should preserve custom currency in config", async () => {
|
|
56
|
+
const { setupClient } = await import("./setupClient");
|
|
57
|
+
const { createIframe } = await import("../utils");
|
|
58
|
+
const { createIFrameFrakClient } = await import(
|
|
59
|
+
"./createIFrameFrakClient"
|
|
60
|
+
);
|
|
61
|
+
const { getSupportedCurrency } = await import("../utils");
|
|
62
|
+
|
|
63
|
+
const mockIframe = document.createElement("iframe");
|
|
64
|
+
const mockClient = {
|
|
65
|
+
waitForSetup: Promise.resolve(),
|
|
66
|
+
waitForConnection: Promise.resolve(true),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
vi.mocked(getSupportedCurrency).mockReturnValue("usd");
|
|
70
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
71
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
72
|
+
mockClient as any
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const config = {
|
|
76
|
+
metadata: {
|
|
77
|
+
name: "Test App",
|
|
78
|
+
currency: "usd" as const,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await setupClient({ config });
|
|
83
|
+
|
|
84
|
+
expect(getSupportedCurrency).toHaveBeenCalledWith("usd");
|
|
85
|
+
expect(createIframe).toHaveBeenCalledWith({
|
|
86
|
+
config: expect.objectContaining({
|
|
87
|
+
metadata: expect.objectContaining({
|
|
88
|
+
currency: "usd",
|
|
89
|
+
}),
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("should return undefined when iframe creation fails", async () => {
|
|
95
|
+
const { setupClient } = await import("./setupClient");
|
|
96
|
+
const { createIframe } = await import("../utils");
|
|
97
|
+
|
|
98
|
+
vi.mocked(createIframe).mockResolvedValue(undefined);
|
|
99
|
+
|
|
100
|
+
const config = {
|
|
101
|
+
metadata: {
|
|
102
|
+
name: "Test App",
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const result = await setupClient({ config });
|
|
107
|
+
|
|
108
|
+
expect(result).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("should create FrakClient with iframe", async () => {
|
|
112
|
+
const { setupClient } = await import("./setupClient");
|
|
113
|
+
const { createIframe } = await import("../utils");
|
|
114
|
+
const { createIFrameFrakClient } = await import(
|
|
115
|
+
"./createIFrameFrakClient"
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const mockIframe = document.createElement("iframe");
|
|
119
|
+
const mockClient = {
|
|
120
|
+
waitForSetup: Promise.resolve(),
|
|
121
|
+
waitForConnection: Promise.resolve(true),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
125
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
126
|
+
mockClient as any
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const config = {
|
|
130
|
+
metadata: {
|
|
131
|
+
name: "Test App",
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
await setupClient({ config });
|
|
136
|
+
|
|
137
|
+
expect(createIFrameFrakClient).toHaveBeenCalledWith({
|
|
138
|
+
config: expect.objectContaining({
|
|
139
|
+
metadata: expect.objectContaining({
|
|
140
|
+
name: "Test App",
|
|
141
|
+
}),
|
|
142
|
+
}),
|
|
143
|
+
iframe: mockIframe,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("should wait for client setup", async () => {
|
|
148
|
+
const { setupClient } = await import("./setupClient");
|
|
149
|
+
const { createIframe } = await import("../utils");
|
|
150
|
+
const { createIFrameFrakClient } = await import(
|
|
151
|
+
"./createIFrameFrakClient"
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const mockIframe = document.createElement("iframe");
|
|
155
|
+
const setupPromise = Promise.resolve();
|
|
156
|
+
const mockClient = {
|
|
157
|
+
waitForSetup: setupPromise,
|
|
158
|
+
waitForConnection: Promise.resolve(true),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
162
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
163
|
+
mockClient as any
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const config = {
|
|
167
|
+
metadata: {
|
|
168
|
+
name: "Test App",
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
await setupClient({ config });
|
|
173
|
+
|
|
174
|
+
// Verify setup was awaited
|
|
175
|
+
await expect(setupPromise).resolves.toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("should wait for connection", async () => {
|
|
179
|
+
const { setupClient } = await import("./setupClient");
|
|
180
|
+
const { createIframe } = await import("../utils");
|
|
181
|
+
const { createIFrameFrakClient } = await import(
|
|
182
|
+
"./createIFrameFrakClient"
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const mockIframe = document.createElement("iframe");
|
|
186
|
+
const connectionPromise = Promise.resolve(true);
|
|
187
|
+
const mockClient = {
|
|
188
|
+
waitForSetup: Promise.resolve(),
|
|
189
|
+
waitForConnection: connectionPromise,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
193
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
194
|
+
mockClient as any
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const config = {
|
|
198
|
+
metadata: {
|
|
199
|
+
name: "Test App",
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
await setupClient({ config });
|
|
204
|
+
|
|
205
|
+
// Verify connection was awaited
|
|
206
|
+
await expect(connectionPromise).resolves.toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("should return undefined when connection fails", async () => {
|
|
210
|
+
const { setupClient } = await import("./setupClient");
|
|
211
|
+
const { createIframe } = await import("../utils");
|
|
212
|
+
const { createIFrameFrakClient } = await import(
|
|
213
|
+
"./createIFrameFrakClient"
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const mockIframe = document.createElement("iframe");
|
|
217
|
+
const mockClient = {
|
|
218
|
+
waitForSetup: Promise.resolve(),
|
|
219
|
+
waitForConnection: Promise.resolve(false),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
223
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
224
|
+
mockClient as any
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const config = {
|
|
228
|
+
metadata: {
|
|
229
|
+
name: "Test App",
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const result = await setupClient({ config });
|
|
234
|
+
|
|
235
|
+
expect(result).toBeUndefined();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("should return client when setup successful", async () => {
|
|
239
|
+
const { setupClient } = await import("./setupClient");
|
|
240
|
+
const { createIframe } = await import("../utils");
|
|
241
|
+
const { createIFrameFrakClient } = await import(
|
|
242
|
+
"./createIFrameFrakClient"
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const mockIframe = document.createElement("iframe");
|
|
246
|
+
const mockClient = {
|
|
247
|
+
waitForSetup: Promise.resolve(),
|
|
248
|
+
waitForConnection: Promise.resolve(true),
|
|
249
|
+
request: vi.fn(),
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
253
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
254
|
+
mockClient as any
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const config = {
|
|
258
|
+
metadata: {
|
|
259
|
+
name: "Test App",
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const result = await setupClient({ config });
|
|
264
|
+
|
|
265
|
+
expect(result).toBe(mockClient);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe("config preparation", () => {
|
|
270
|
+
test("should use default currency when none provided", async () => {
|
|
271
|
+
const { setupClient } = await import("./setupClient");
|
|
272
|
+
const { createIframe } = await import("../utils");
|
|
273
|
+
const { createIFrameFrakClient } = await import(
|
|
274
|
+
"./createIFrameFrakClient"
|
|
275
|
+
);
|
|
276
|
+
const { getSupportedCurrency } = await import("../utils");
|
|
277
|
+
|
|
278
|
+
const mockIframe = document.createElement("iframe");
|
|
279
|
+
const mockClient = {
|
|
280
|
+
waitForSetup: Promise.resolve(),
|
|
281
|
+
waitForConnection: Promise.resolve(true),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
vi.mocked(getSupportedCurrency).mockReturnValue("eur");
|
|
285
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
286
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
287
|
+
mockClient as any
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const config = {
|
|
291
|
+
metadata: {
|
|
292
|
+
name: "Test App",
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
await setupClient({ config });
|
|
297
|
+
|
|
298
|
+
expect(getSupportedCurrency).toHaveBeenCalledWith(undefined);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("should merge metadata with prepared config", async () => {
|
|
302
|
+
const { setupClient } = await import("./setupClient");
|
|
303
|
+
const { createIframe } = await import("../utils");
|
|
304
|
+
const { createIFrameFrakClient } = await import(
|
|
305
|
+
"./createIFrameFrakClient"
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const mockIframe = document.createElement("iframe");
|
|
309
|
+
const mockClient = {
|
|
310
|
+
waitForSetup: Promise.resolve(),
|
|
311
|
+
waitForConnection: Promise.resolve(true),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
vi.mocked(createIframe).mockResolvedValue(mockIframe);
|
|
315
|
+
vi.mocked(createIFrameFrakClient).mockReturnValue(
|
|
316
|
+
mockClient as any
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const config = {
|
|
320
|
+
metadata: {
|
|
321
|
+
name: "Test App",
|
|
322
|
+
css: {
|
|
323
|
+
primaryColor: "#ff0000",
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
await setupClient({ config });
|
|
329
|
+
|
|
330
|
+
expect(createIframe).toHaveBeenCalledWith({
|
|
331
|
+
config: expect.objectContaining({
|
|
332
|
+
metadata: expect.objectContaining({
|
|
333
|
+
name: "Test App",
|
|
334
|
+
css: {
|
|
335
|
+
primaryColor: "#ff0000",
|
|
336
|
+
},
|
|
337
|
+
currency: "eur",
|
|
338
|
+
}),
|
|
339
|
+
}),
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createIFrameFrakClient } from "../clients";
|
|
2
|
+
import type { FrakClient, FrakWalletSdkConfig } from "../types";
|
|
3
|
+
import { createIframe, getSupportedCurrency } from "../utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Directly setup the Frak client with an iframe
|
|
7
|
+
* Return when the FrakClient is ready (setup and communication estbalished with the wallet)
|
|
8
|
+
*
|
|
9
|
+
* @param config - The configuration to use for the Frak Wallet SDK
|
|
10
|
+
* @returns a Promise with the Frak Client
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const frakConfig: FrakWalletSdkConfig = {
|
|
14
|
+
* metadata: {
|
|
15
|
+
* name: "My app title",
|
|
16
|
+
* },
|
|
17
|
+
* }
|
|
18
|
+
* const client = await setupClient({ config: frakConfig });
|
|
19
|
+
*/
|
|
20
|
+
export async function setupClient({
|
|
21
|
+
config,
|
|
22
|
+
}: {
|
|
23
|
+
config: FrakWalletSdkConfig;
|
|
24
|
+
}): Promise<FrakClient | undefined> {
|
|
25
|
+
// Prepare the config
|
|
26
|
+
const preparedConfig = prepareConfig(config);
|
|
27
|
+
|
|
28
|
+
// Create our iframe
|
|
29
|
+
const iframe = await createIframe({
|
|
30
|
+
config: preparedConfig,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!iframe) {
|
|
34
|
+
console.error("Failed to create iframe");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create our client
|
|
39
|
+
const client = createIFrameFrakClient({
|
|
40
|
+
config: preparedConfig,
|
|
41
|
+
iframe,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Wait for the client to be all setup
|
|
45
|
+
await client.waitForSetup;
|
|
46
|
+
|
|
47
|
+
// Wait for the connection to be established
|
|
48
|
+
const waitForConnection = await client.waitForConnection;
|
|
49
|
+
if (!waitForConnection) {
|
|
50
|
+
console.error("Failed to connect to client");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return client;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Prepare the config for the Frak Client
|
|
59
|
+
* @param config - The configuration to use for the Frak Wallet SDK
|
|
60
|
+
* @returns The prepared configuration with the supported currency
|
|
61
|
+
*/
|
|
62
|
+
function prepareConfig(config: FrakWalletSdkConfig): FrakWalletSdkConfig {
|
|
63
|
+
// Get the supported currency (e.g. "eur")
|
|
64
|
+
const supportedCurrency = getSupportedCurrency(config.metadata?.currency);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
...config,
|
|
68
|
+
metadata: {
|
|
69
|
+
...config.metadata,
|
|
70
|
+
currency: supportedCurrency,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|