@frak-labs/core-sdk 0.1.1 → 0.2.0-beta.7898df5b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/cdn/bundle.js +14 -0
- package/dist/actions.cjs +1 -1
- package/dist/actions.d.cts +3 -3
- package/dist/actions.d.ts +3 -3
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -1
- package/dist/bundle.d.cts +4 -6
- package/dist/bundle.d.ts +4 -6
- package/dist/bundle.js +1 -1
- package/dist/computeLegacyProductId-CCAZvLa5.d.cts +537 -0
- package/dist/computeLegacyProductId-b5cUWdAm.d.ts +537 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -1
- package/dist/{openSso-D--Airj6.d.cts → openSso-B0g7-807.d.cts} +173 -136
- package/dist/{openSso-DsKJ4y0j.d.ts → openSso-CMzwvaCa.d.ts} +173 -136
- package/dist/setupClient-BICl5fdX.js +13 -0
- package/dist/setupClient-nl8Dhh4V.cjs +13 -0
- package/dist/siweAuthenticate-BWmI2_TN.cjs +1 -0
- package/dist/{index-d8xS4ryI.d.ts → siweAuthenticate-CVigMOxz.d.cts} +113 -92
- package/dist/{index-C6FxkWPC.d.cts → siweAuthenticate-CnCZ7mok.d.ts} +113 -92
- package/dist/siweAuthenticate-zczqxm0a.js +1 -0
- package/dist/trackEvent-CeLFVzZn.js +1 -0
- package/dist/trackEvent-Ew5r5zfI.cjs +1 -0
- package/package.json +11 -22
- package/src/actions/displayEmbeddedWallet.ts +1 -0
- package/src/actions/displayModal.test.ts +12 -11
- package/src/actions/displayModal.ts +7 -18
- package/src/actions/ensureIdentity.ts +68 -0
- package/src/actions/{getProductInformation.test.ts → getMerchantInformation.test.ts} +33 -50
- package/src/actions/getMerchantInformation.ts +16 -0
- package/src/actions/index.ts +3 -2
- package/src/actions/openSso.ts +4 -2
- package/src/actions/referral/processReferral.test.ts +117 -242
- package/src/actions/referral/processReferral.ts +134 -204
- package/src/actions/referral/referralInteraction.test.ts +4 -12
- package/src/actions/referral/referralInteraction.ts +3 -13
- package/src/actions/sendInteraction.ts +46 -22
- package/src/actions/trackPurchaseStatus.test.ts +354 -141
- package/src/actions/trackPurchaseStatus.ts +48 -11
- package/src/actions/watchWalletStatus.ts +2 -3
- package/src/actions/wrapper/modalBuilder.test.ts +0 -14
- package/src/actions/wrapper/modalBuilder.ts +3 -12
- package/src/bundle.ts +0 -1
- package/src/clients/createIFrameFrakClient.ts +10 -5
- package/src/clients/transports/iframeLifecycleManager.test.ts +163 -4
- package/src/clients/transports/iframeLifecycleManager.ts +172 -33
- package/src/constants/interactionTypes.ts +12 -41
- package/src/index.ts +27 -16
- package/src/types/config.ts +6 -0
- package/src/types/context.ts +48 -6
- package/src/types/index.ts +15 -11
- package/src/types/lifecycle/client.ts +24 -1
- package/src/types/lifecycle/iframe.ts +6 -0
- package/src/types/rpc/displayModal.ts +2 -4
- package/src/types/rpc/embedded/index.ts +2 -2
- package/src/types/rpc/interaction.ts +31 -39
- package/src/types/rpc/merchantInformation.ts +77 -0
- package/src/types/rpc/modal/index.ts +0 -4
- package/src/types/rpc/modal/login.ts +5 -1
- package/src/types/rpc/walletStatus.ts +1 -7
- package/src/types/rpc.ts +22 -30
- package/src/types/tracking.ts +31 -0
- package/src/utils/FrakContext.test.ts +270 -186
- package/src/utils/FrakContext.ts +78 -56
- package/src/utils/backendUrl.test.ts +83 -0
- package/src/utils/backendUrl.ts +62 -0
- package/src/utils/clientId.test.ts +41 -0
- package/src/utils/clientId.ts +43 -0
- package/src/utils/compression/compress.test.ts +1 -1
- package/src/utils/compression/compress.ts +2 -2
- package/src/utils/compression/decompress.test.ts +8 -4
- package/src/utils/compression/decompress.ts +2 -2
- package/src/utils/{computeProductId.ts → computeLegacyProductId.ts} +2 -2
- package/src/utils/constants.ts +5 -0
- package/src/utils/deepLinkWithFallback.test.ts +243 -0
- package/src/utils/deepLinkWithFallback.ts +103 -0
- package/src/utils/formatAmount.ts +6 -0
- package/src/utils/iframeHelper.test.ts +18 -5
- package/src/utils/iframeHelper.ts +10 -3
- package/src/utils/index.ts +16 -1
- package/src/utils/merchantId.test.ts +653 -0
- package/src/utils/merchantId.ts +143 -0
- package/src/utils/sso.ts +18 -11
- package/src/utils/trackEvent.test.ts +23 -5
- package/src/utils/trackEvent.ts +13 -0
- package/cdn/bundle.iife.js +0 -14
- package/dist/actions-B5j-i1p0.cjs +0 -1
- package/dist/actions-q090Z0oR.js +0 -1
- package/dist/index-7OZ39x1U.d.ts +0 -195
- package/dist/index-CRsQWnTs.d.cts +0 -351
- package/dist/index-Ck1hudEi.d.ts +0 -351
- package/dist/index-zDq-VlKx.d.cts +0 -195
- package/dist/interaction-DMJ3ZfaF.d.cts +0 -45
- package/dist/interaction-KX1h9a7V.d.ts +0 -45
- package/dist/interactions-DnfM3oe0.js +0 -1
- package/dist/interactions-EIXhNLf6.cjs +0 -1
- package/dist/interactions.cjs +0 -1
- package/dist/interactions.d.cts +0 -2
- package/dist/interactions.d.ts +0 -2
- package/dist/interactions.js +0 -1
- package/dist/productTypes-BUkXJKZ7.cjs +0 -1
- package/dist/productTypes-CGb1MmBF.js +0 -1
- package/dist/src-1LQ4eLq5.js +0 -13
- package/dist/src-hW71KjPN.cjs +0 -13
- package/dist/trackEvent-CHnYa85W.js +0 -1
- package/dist/trackEvent-GuQm_1Nm.cjs +0 -1
- package/src/actions/getProductInformation.ts +0 -14
- package/src/actions/openSso.test.ts +0 -407
- package/src/actions/sendInteraction.test.ts +0 -219
- package/src/constants/interactionTypes.test.ts +0 -128
- package/src/constants/productTypes.test.ts +0 -130
- package/src/constants/productTypes.ts +0 -33
- package/src/interactions/index.ts +0 -5
- package/src/interactions/pressEncoder.test.ts +0 -215
- package/src/interactions/pressEncoder.ts +0 -53
- package/src/interactions/purchaseEncoder.test.ts +0 -291
- package/src/interactions/purchaseEncoder.ts +0 -99
- package/src/interactions/referralEncoder.test.ts +0 -170
- package/src/interactions/referralEncoder.ts +0 -47
- package/src/interactions/retailEncoder.test.ts +0 -107
- package/src/interactions/retailEncoder.ts +0 -37
- package/src/interactions/webshopEncoder.test.ts +0 -56
- package/src/interactions/webshopEncoder.ts +0 -30
- package/src/types/rpc/modal/openSession.ts +0 -25
- package/src/types/rpc/productInformation.ts +0 -59
- package/src/utils/computeProductId.test.ts +0 -80
- package/src/utils/sso.test.ts +0 -361
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { getBackendUrl } from "../utils/backendUrl";
|
|
2
|
+
import { getClientId } from "../utils/clientId";
|
|
3
|
+
import { fetchMerchantId } from "../utils/merchantId";
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Function used to track the status of a purchase
|
|
3
7
|
* when a purchase is tracked, the `purchaseCompleted` interactions will be automatically send for the user when we receive the purchase confirmation via webhook.
|
|
@@ -5,6 +9,7 @@
|
|
|
5
9
|
* @param args.customerId - The customer id that made the purchase (on your side)
|
|
6
10
|
* @param args.orderId - The order id of the purchase (on your side)
|
|
7
11
|
* @param args.token - The token of the purchase
|
|
12
|
+
* @param args.merchantId - Optional explicit merchant id to use for the tracking request
|
|
8
13
|
*
|
|
9
14
|
* @description This function will send a request to the backend to listen for the purchase status.
|
|
10
15
|
*
|
|
@@ -14,40 +19,72 @@
|
|
|
14
19
|
* customerId: checkout.order.customer.id,
|
|
15
20
|
* orderId: checkout.order.id,
|
|
16
21
|
* token: checkout.token,
|
|
22
|
+
* merchantId: "your-merchant-id",
|
|
17
23
|
* };
|
|
18
24
|
*
|
|
19
25
|
* await trackPurchaseStatus(payload);
|
|
20
26
|
* }
|
|
21
27
|
*
|
|
22
28
|
* @remarks
|
|
23
|
-
* -
|
|
24
|
-
* - 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.
|
|
25
33
|
*/
|
|
26
34
|
export async function trackPurchaseStatus(args: {
|
|
27
35
|
customerId: string | number;
|
|
28
36
|
orderId: string | number;
|
|
29
37
|
token: string;
|
|
38
|
+
merchantId?: string;
|
|
30
39
|
}) {
|
|
31
40
|
if (typeof window === "undefined") {
|
|
32
41
|
console.warn("[Frak] No window found, can't track purchase");
|
|
33
42
|
return;
|
|
34
43
|
}
|
|
44
|
+
|
|
35
45
|
const interactionToken = window.sessionStorage.getItem(
|
|
36
46
|
"frak-wallet-interaction-token"
|
|
37
47
|
);
|
|
38
|
-
|
|
39
|
-
|
|
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");
|
|
40
62
|
return;
|
|
41
63
|
}
|
|
42
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
|
+
|
|
43
78
|
// Submit the listening request
|
|
44
|
-
|
|
79
|
+
const backendUrl = getBackendUrl();
|
|
80
|
+
await fetch(`${backendUrl}/user/track/purchase`, {
|
|
45
81
|
method: "POST",
|
|
46
|
-
headers
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
82
|
+
headers,
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
customerId: args.customerId,
|
|
85
|
+
orderId: args.orderId,
|
|
86
|
+
token: args.token,
|
|
87
|
+
merchantId,
|
|
88
|
+
}),
|
|
52
89
|
});
|
|
53
90
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Deferred } from "@frak-labs/frame-connector";
|
|
2
2
|
import type { FrakClient } from "../types/client";
|
|
3
3
|
import type { WalletStatusReturnType } from "../types/rpc/walletStatus";
|
|
4
|
+
import { ensureIdentity } from "./ensureIdentity";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Function used to watch the current frak wallet status
|
|
@@ -14,7 +15,6 @@ import type { WalletStatusReturnType } from "../types/rpc/walletStatus";
|
|
|
14
15
|
* await watchWalletStatus(frakConfig, (status: WalletStatusReturnType) => {
|
|
15
16
|
* if (status.key === "connected") {
|
|
16
17
|
* console.log("Wallet connected:", status.wallet);
|
|
17
|
-
* console.log("Current interaction session:", status.interactionSession);
|
|
18
18
|
* } else {
|
|
19
19
|
* console.log("Wallet not connected");
|
|
20
20
|
* }
|
|
@@ -82,13 +82,12 @@ function walletStatusSideEffect(
|
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
if (status.interactionToken) {
|
|
85
|
-
// If we got an interaction token, save it
|
|
86
85
|
window.sessionStorage.setItem(
|
|
87
86
|
"frak-wallet-interaction-token",
|
|
88
87
|
status.interactionToken
|
|
89
88
|
);
|
|
89
|
+
ensureIdentity(status.interactionToken);
|
|
90
90
|
} else {
|
|
91
|
-
// Otherwise, remove it
|
|
92
91
|
window.sessionStorage.removeItem("frak-wallet-interaction-token");
|
|
93
92
|
}
|
|
94
93
|
}
|
|
@@ -25,7 +25,6 @@ describe("modalBuilder", () => {
|
|
|
25
25
|
|
|
26
26
|
expect(builder.params).toBeDefined();
|
|
27
27
|
expect(builder.params.steps.login).toEqual({});
|
|
28
|
-
expect(builder.params.steps.openSession).toEqual({});
|
|
29
28
|
});
|
|
30
29
|
|
|
31
30
|
it("should create builder with custom login params", () => {
|
|
@@ -36,14 +35,6 @@ describe("modalBuilder", () => {
|
|
|
36
35
|
expect(builder.params.steps.login).toEqual({ allowSso: true });
|
|
37
36
|
});
|
|
38
37
|
|
|
39
|
-
it("should create builder with custom openSession params", () => {
|
|
40
|
-
const builder = modalBuilder(mockClient, {
|
|
41
|
-
openSession: {},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
expect(builder.params.steps.openSession).toEqual({});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
38
|
it("should create builder with metadata", () => {
|
|
48
39
|
const builder = modalBuilder(mockClient, {
|
|
49
40
|
metadata: {
|
|
@@ -197,10 +188,6 @@ describe("modalBuilder", () => {
|
|
|
197
188
|
login: {
|
|
198
189
|
wallet: "0x1234567890123456789012345678901234567890" as Address,
|
|
199
190
|
},
|
|
200
|
-
openSession: {
|
|
201
|
-
startTimestamp: 1234567890,
|
|
202
|
-
endTimestamp: 1234567900,
|
|
203
|
-
},
|
|
204
191
|
};
|
|
205
192
|
vi.mocked(displayModal).mockResolvedValue(mockResponse as any);
|
|
206
193
|
|
|
@@ -240,7 +227,6 @@ describe("modalBuilder", () => {
|
|
|
240
227
|
login: {
|
|
241
228
|
wallet: "0x1234567890123456789012345678901234567890" as Address,
|
|
242
229
|
},
|
|
243
|
-
openSession: {},
|
|
244
230
|
};
|
|
245
231
|
vi.mocked(displayModal).mockResolvedValue(mockResponse as any);
|
|
246
232
|
|
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
ModalRpcMetadata,
|
|
8
8
|
ModalRpcStepsResultType,
|
|
9
9
|
ModalStepTypes,
|
|
10
|
-
OpenInteractionSessionModalStepType,
|
|
11
10
|
SendTransactionModalStepType,
|
|
12
11
|
} from "../../types";
|
|
13
12
|
import { displayModal } from "../displayModal";
|
|
@@ -58,9 +57,7 @@ export type ModalStepBuilder<
|
|
|
58
57
|
/**
|
|
59
58
|
* Represent the output type of the modal builder
|
|
60
59
|
*/
|
|
61
|
-
export type ModalBuilder = ModalStepBuilder<
|
|
62
|
-
[LoginModalStepType, OpenInteractionSessionModalStepType]
|
|
63
|
-
>;
|
|
60
|
+
export type ModalBuilder = ModalStepBuilder<[LoginModalStepType]>;
|
|
64
61
|
|
|
65
62
|
/**
|
|
66
63
|
* Helper to craft Frak modal, and share a base initial config
|
|
@@ -68,9 +65,8 @@ export type ModalBuilder = ModalStepBuilder<
|
|
|
68
65
|
* @param args
|
|
69
66
|
* @param args.metadata - Common modal metadata (customisation, language etc)
|
|
70
67
|
* @param args.login - Login step parameters
|
|
71
|
-
* @param args.openSession - Open session step parameters
|
|
72
68
|
*
|
|
73
|
-
* @description This function will create a modal builder with the provided metadata
|
|
69
|
+
* @description This function will create a modal builder with the provided metadata and login parameters.
|
|
74
70
|
*
|
|
75
71
|
* @example
|
|
76
72
|
* Here is an example of how to use the `modalBuilder` to create and display a sharing modal:
|
|
@@ -102,20 +98,15 @@ export function modalBuilder(
|
|
|
102
98
|
{
|
|
103
99
|
metadata,
|
|
104
100
|
login,
|
|
105
|
-
openSession,
|
|
106
101
|
}: {
|
|
107
102
|
metadata?: ModalRpcMetadata;
|
|
108
103
|
login?: LoginModalStepType["params"];
|
|
109
|
-
openSession?: OpenInteractionSessionModalStepType["params"];
|
|
110
104
|
}
|
|
111
105
|
): ModalBuilder {
|
|
112
106
|
// Build the initial modal params
|
|
113
|
-
const baseParams: DisplayModalParamsType<
|
|
114
|
-
[LoginModalStepType, OpenInteractionSessionModalStepType]
|
|
115
|
-
> = {
|
|
107
|
+
const baseParams: DisplayModalParamsType<[LoginModalStepType]> = {
|
|
116
108
|
steps: {
|
|
117
109
|
login: login ?? {},
|
|
118
|
-
openSession: openSession ?? {},
|
|
119
110
|
},
|
|
120
111
|
metadata,
|
|
121
112
|
};
|
package/src/bundle.ts
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
type RpcClient,
|
|
5
5
|
RpcErrorCodes,
|
|
6
6
|
} from "@frak-labs/frame-connector";
|
|
7
|
-
import { createClientCompressionMiddleware } from "@frak-labs/frame-connector/middleware";
|
|
8
7
|
import { OpenPanel } from "@openpanel/web";
|
|
9
8
|
import type { FrakLifecycleEvent } from "../types";
|
|
10
9
|
import type { FrakClient } from "../types/client";
|
|
11
10
|
import type { FrakWalletSdkConfig } from "../types/config";
|
|
12
11
|
import type { IFrameRpcSchema } from "../types/rpc";
|
|
12
|
+
import { getClientId } from "../utils";
|
|
13
13
|
import { BACKUP_KEY } from "../utils/constants";
|
|
14
14
|
import { setupSsoUrlListener } from "../utils/ssoUrlListener";
|
|
15
15
|
import { DebugInfoGatherer } from "./DebugInfo";
|
|
@@ -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
|
*
|
|
@@ -46,7 +47,11 @@ export function createIFrameFrakClient({
|
|
|
46
47
|
const frakWalletUrl = config?.walletUrl ?? "https://wallet.frak.id";
|
|
47
48
|
|
|
48
49
|
// Create lifecycle manager
|
|
49
|
-
const lifecycleManager = createIFrameLifecycleManager({
|
|
50
|
+
const lifecycleManager = createIFrameLifecycleManager({
|
|
51
|
+
iframe,
|
|
52
|
+
targetOrigin: frakWalletUrl,
|
|
53
|
+
configDomain: config.domain,
|
|
54
|
+
});
|
|
50
55
|
|
|
51
56
|
// Create our debug info gatherer
|
|
52
57
|
const debugInfo = new DebugInfoGatherer(config, iframe);
|
|
@@ -64,7 +69,6 @@ export function createIFrameFrakClient({
|
|
|
64
69
|
emittingTransport: iframe.contentWindow,
|
|
65
70
|
listeningTransport: window,
|
|
66
71
|
targetOrigin: frakWalletUrl,
|
|
67
|
-
// Add compression middleware to handle request/response compression
|
|
68
72
|
middleware: [
|
|
69
73
|
// Ensure we are connected before sending request
|
|
70
74
|
{
|
|
@@ -80,7 +84,6 @@ export function createIFrameFrakClient({
|
|
|
80
84
|
return ctx;
|
|
81
85
|
},
|
|
82
86
|
},
|
|
83
|
-
createClientCompressionMiddleware(),
|
|
84
87
|
// Save debug info
|
|
85
88
|
{
|
|
86
89
|
onRequest(message, ctx) {
|
|
@@ -139,6 +142,7 @@ export function createIFrameFrakClient({
|
|
|
139
142
|
payload.properties = {
|
|
140
143
|
...payload.properties,
|
|
141
144
|
sdkVersion: process.env.SDK_VERSION,
|
|
145
|
+
userAnonymousClientId: getClientId(),
|
|
142
146
|
};
|
|
143
147
|
}
|
|
144
148
|
|
|
@@ -147,6 +151,7 @@ export function createIFrameFrakClient({
|
|
|
147
151
|
});
|
|
148
152
|
openPanel.setGlobalProperties({
|
|
149
153
|
sdkVersion: process.env.SDK_VERSION,
|
|
154
|
+
userAnonymousClientId: getClientId(),
|
|
150
155
|
});
|
|
151
156
|
openPanel.init();
|
|
152
157
|
}
|
|
@@ -24,6 +24,17 @@ vi.mock("../../utils/iframeHelper", () => ({
|
|
|
24
24
|
changeIframeVisibility: vi.fn(),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
+
vi.mock("../../utils/clientId", () => ({
|
|
28
|
+
getClientId: vi.fn(() => "mock-client-id"),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock("../../utils/deepLinkWithFallback", () => ({
|
|
32
|
+
isFrakDeepLink: vi.fn((url: string) => url.startsWith("frakwallet://")),
|
|
33
|
+
triggerDeepLinkWithFallback: vi.fn(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const WALLET_ORIGIN = "https://wallet.frak.id";
|
|
37
|
+
|
|
27
38
|
describe("createIFrameLifecycleManager", () => {
|
|
28
39
|
beforeEach(() => {
|
|
29
40
|
vi.clearAllMocks();
|
|
@@ -45,6 +56,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
45
56
|
const mockIframe = document.createElement("iframe");
|
|
46
57
|
const manager = createIFrameLifecycleManager({
|
|
47
58
|
iframe: mockIframe,
|
|
59
|
+
targetOrigin: WALLET_ORIGIN,
|
|
48
60
|
});
|
|
49
61
|
|
|
50
62
|
expect(manager).toBeDefined();
|
|
@@ -60,6 +72,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
60
72
|
const mockIframe = document.createElement("iframe");
|
|
61
73
|
const manager = createIFrameLifecycleManager({
|
|
62
74
|
iframe: mockIframe,
|
|
75
|
+
targetOrigin: WALLET_ORIGIN,
|
|
63
76
|
});
|
|
64
77
|
|
|
65
78
|
let resolved = false;
|
|
@@ -82,6 +95,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
82
95
|
const mockIframe = document.createElement("iframe");
|
|
83
96
|
const manager = createIFrameLifecycleManager({
|
|
84
97
|
iframe: mockIframe,
|
|
98
|
+
targetOrigin: WALLET_ORIGIN,
|
|
85
99
|
});
|
|
86
100
|
|
|
87
101
|
const event = {
|
|
@@ -103,6 +117,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
103
117
|
const mockIframe = document.createElement("iframe");
|
|
104
118
|
const manager = createIFrameLifecycleManager({
|
|
105
119
|
iframe: mockIframe,
|
|
120
|
+
targetOrigin: WALLET_ORIGIN,
|
|
106
121
|
});
|
|
107
122
|
|
|
108
123
|
const backup = "encrypted-backup-data";
|
|
@@ -124,6 +139,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
124
139
|
const mockIframe = document.createElement("iframe");
|
|
125
140
|
const manager = createIFrameLifecycleManager({
|
|
126
141
|
iframe: mockIframe,
|
|
142
|
+
targetOrigin: WALLET_ORIGIN,
|
|
127
143
|
});
|
|
128
144
|
|
|
129
145
|
// First set a backup
|
|
@@ -147,6 +163,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
147
163
|
const mockIframe = document.createElement("iframe");
|
|
148
164
|
const manager = createIFrameLifecycleManager({
|
|
149
165
|
iframe: mockIframe,
|
|
166
|
+
targetOrigin: WALLET_ORIGIN,
|
|
150
167
|
});
|
|
151
168
|
|
|
152
169
|
// First set a backup
|
|
@@ -174,6 +191,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
174
191
|
const mockIframe = document.createElement("iframe");
|
|
175
192
|
const manager = createIFrameLifecycleManager({
|
|
176
193
|
iframe: mockIframe,
|
|
194
|
+
targetOrigin: WALLET_ORIGIN,
|
|
177
195
|
});
|
|
178
196
|
|
|
179
197
|
const event = {
|
|
@@ -199,6 +217,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
199
217
|
const mockIframe = document.createElement("iframe");
|
|
200
218
|
const manager = createIFrameLifecycleManager({
|
|
201
219
|
iframe: mockIframe,
|
|
220
|
+
targetOrigin: WALLET_ORIGIN,
|
|
202
221
|
});
|
|
203
222
|
|
|
204
223
|
const event = {
|
|
@@ -215,13 +234,14 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
215
234
|
});
|
|
216
235
|
|
|
217
236
|
describe("handshake event", () => {
|
|
218
|
-
test("should post handshake-response with token", async () => {
|
|
237
|
+
test("should post handshake-response with token to iframe origin", async () => {
|
|
219
238
|
const { createIFrameLifecycleManager } = await import(
|
|
220
239
|
"./iframeLifecycleManager"
|
|
221
240
|
);
|
|
222
241
|
|
|
223
242
|
const mockPostMessage = vi.fn();
|
|
224
243
|
const mockIframe = {
|
|
244
|
+
src: "https://wallet.frak.id/listener",
|
|
225
245
|
contentWindow: {
|
|
226
246
|
postMessage: mockPostMessage,
|
|
227
247
|
},
|
|
@@ -229,6 +249,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
229
249
|
|
|
230
250
|
const manager = createIFrameLifecycleManager({
|
|
231
251
|
iframe: mockIframe,
|
|
252
|
+
targetOrigin: WALLET_ORIGIN,
|
|
232
253
|
});
|
|
233
254
|
|
|
234
255
|
const event = {
|
|
@@ -244,9 +265,10 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
244
265
|
data: {
|
|
245
266
|
token: "handshake-token-123",
|
|
246
267
|
currentUrl: "https://test.com",
|
|
268
|
+
clientId: "mock-client-id",
|
|
247
269
|
},
|
|
248
270
|
},
|
|
249
|
-
"
|
|
271
|
+
"https://wallet.frak.id"
|
|
250
272
|
);
|
|
251
273
|
});
|
|
252
274
|
|
|
@@ -262,6 +284,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
262
284
|
|
|
263
285
|
const mockPostMessage = vi.fn();
|
|
264
286
|
const mockIframe = {
|
|
287
|
+
src: "https://wallet.frak.id/listener",
|
|
265
288
|
contentWindow: {
|
|
266
289
|
postMessage: mockPostMessage,
|
|
267
290
|
},
|
|
@@ -269,6 +292,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
269
292
|
|
|
270
293
|
const manager = createIFrameLifecycleManager({
|
|
271
294
|
iframe: mockIframe,
|
|
295
|
+
targetOrigin: WALLET_ORIGIN,
|
|
272
296
|
});
|
|
273
297
|
|
|
274
298
|
const event = {
|
|
@@ -284,13 +308,13 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
284
308
|
currentUrl: "https://example.com/page?param=value",
|
|
285
309
|
}),
|
|
286
310
|
}),
|
|
287
|
-
"
|
|
311
|
+
"https://wallet.frak.id"
|
|
288
312
|
);
|
|
289
313
|
});
|
|
290
314
|
});
|
|
291
315
|
|
|
292
316
|
describe("redirect event", () => {
|
|
293
|
-
test("should redirect with appended current URL", async () => {
|
|
317
|
+
test("should redirect with appended current URL for HTTP URLs", async () => {
|
|
294
318
|
const { createIFrameLifecycleManager } = await import(
|
|
295
319
|
"./iframeLifecycleManager"
|
|
296
320
|
);
|
|
@@ -305,6 +329,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
305
329
|
const mockIframe = document.createElement("iframe");
|
|
306
330
|
const manager = createIFrameLifecycleManager({
|
|
307
331
|
iframe: mockIframe,
|
|
332
|
+
targetOrigin: WALLET_ORIGIN,
|
|
308
333
|
});
|
|
309
334
|
|
|
310
335
|
const event = {
|
|
@@ -336,6 +361,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
336
361
|
const mockIframe = document.createElement("iframe");
|
|
337
362
|
const manager = createIFrameLifecycleManager({
|
|
338
363
|
iframe: mockIframe,
|
|
364
|
+
targetOrigin: WALLET_ORIGIN,
|
|
339
365
|
});
|
|
340
366
|
|
|
341
367
|
const event = {
|
|
@@ -349,6 +375,137 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
349
375
|
|
|
350
376
|
expect(window.location.href).toBe("https://redirect.com/path");
|
|
351
377
|
});
|
|
378
|
+
|
|
379
|
+
test("should use fallback detection for frakwallet:// deep links", async () => {
|
|
380
|
+
const { createIFrameLifecycleManager } = await import(
|
|
381
|
+
"./iframeLifecycleManager"
|
|
382
|
+
);
|
|
383
|
+
const { triggerDeepLinkWithFallback } = await import(
|
|
384
|
+
"../../utils/deepLinkWithFallback"
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
Object.defineProperty(window, "location", {
|
|
388
|
+
value: {
|
|
389
|
+
href: "https://original.com",
|
|
390
|
+
},
|
|
391
|
+
writable: true,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const mockIframe = document.createElement("iframe");
|
|
395
|
+
mockIframe.src = "https://wallet.frak.id";
|
|
396
|
+
const manager = createIFrameLifecycleManager({
|
|
397
|
+
iframe: mockIframe,
|
|
398
|
+
targetOrigin: WALLET_ORIGIN,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const event = {
|
|
402
|
+
iframeLifecycle: "redirect" as const,
|
|
403
|
+
data: {
|
|
404
|
+
baseRedirectUrl: "frakwallet://wallet",
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
await manager.handleEvent(event);
|
|
409
|
+
|
|
410
|
+
expect(triggerDeepLinkWithFallback).toHaveBeenCalledWith(
|
|
411
|
+
"frakwallet://wallet",
|
|
412
|
+
expect.objectContaining({
|
|
413
|
+
onFallback: expect.any(Function),
|
|
414
|
+
})
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("should post deep-link-failed message when fallback is triggered", async () => {
|
|
419
|
+
const { createIFrameLifecycleManager } = await import(
|
|
420
|
+
"./iframeLifecycleManager"
|
|
421
|
+
);
|
|
422
|
+
const { triggerDeepLinkWithFallback } = await import(
|
|
423
|
+
"../../utils/deepLinkWithFallback"
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
Object.defineProperty(window, "location", {
|
|
427
|
+
value: {
|
|
428
|
+
href: "https://original.com",
|
|
429
|
+
},
|
|
430
|
+
writable: true,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const mockPostMessage = vi.fn();
|
|
434
|
+
const mockIframe = {
|
|
435
|
+
src: "https://wallet.frak.id",
|
|
436
|
+
contentWindow: {
|
|
437
|
+
postMessage: mockPostMessage,
|
|
438
|
+
},
|
|
439
|
+
} as any;
|
|
440
|
+
|
|
441
|
+
const manager = createIFrameLifecycleManager({
|
|
442
|
+
iframe: mockIframe,
|
|
443
|
+
targetOrigin: WALLET_ORIGIN,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const event = {
|
|
447
|
+
iframeLifecycle: "redirect" as const,
|
|
448
|
+
data: {
|
|
449
|
+
baseRedirectUrl: "frakwallet://wallet",
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
await manager.handleEvent(event);
|
|
454
|
+
|
|
455
|
+
// Extract the onFallback callback from the mock call
|
|
456
|
+
const callArgs = (triggerDeepLinkWithFallback as any).mock.calls[0];
|
|
457
|
+
const options = callArgs[1];
|
|
458
|
+
expect(options).toBeDefined();
|
|
459
|
+
expect(options.onFallback).toBeInstanceOf(Function);
|
|
460
|
+
|
|
461
|
+
// Trigger the fallback callback
|
|
462
|
+
options.onFallback();
|
|
463
|
+
|
|
464
|
+
// Verify postMessage was called with deep-link-failed event
|
|
465
|
+
expect(mockPostMessage).toHaveBeenCalledWith(
|
|
466
|
+
{
|
|
467
|
+
clientLifecycle: "deep-link-failed",
|
|
468
|
+
data: { originalUrl: "frakwallet://wallet" },
|
|
469
|
+
},
|
|
470
|
+
"https://wallet.frak.id"
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("should NOT use fallback detection for HTTP URLs", async () => {
|
|
475
|
+
const { createIFrameLifecycleManager } = await import(
|
|
476
|
+
"./iframeLifecycleManager"
|
|
477
|
+
);
|
|
478
|
+
const { triggerDeepLinkWithFallback } = await import(
|
|
479
|
+
"../../utils/deepLinkWithFallback"
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
Object.defineProperty(window, "location", {
|
|
483
|
+
value: {
|
|
484
|
+
href: "https://original.com",
|
|
485
|
+
},
|
|
486
|
+
writable: true,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const mockIframe = document.createElement("iframe");
|
|
490
|
+
const manager = createIFrameLifecycleManager({
|
|
491
|
+
iframe: mockIframe,
|
|
492
|
+
targetOrigin: WALLET_ORIGIN,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const event = {
|
|
496
|
+
iframeLifecycle: "redirect" as const,
|
|
497
|
+
data: {
|
|
498
|
+
baseRedirectUrl: "https://wallet.frak.id/login",
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
await manager.handleEvent(event);
|
|
503
|
+
|
|
504
|
+
// Should NOT call fallback detection
|
|
505
|
+
expect(triggerDeepLinkWithFallback).not.toHaveBeenCalled();
|
|
506
|
+
// Should directly redirect
|
|
507
|
+
expect(window.location.href).toBe("https://wallet.frak.id/login");
|
|
508
|
+
});
|
|
352
509
|
});
|
|
353
510
|
|
|
354
511
|
describe("event filtering", () => {
|
|
@@ -360,6 +517,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
360
517
|
const mockIframe = document.createElement("iframe");
|
|
361
518
|
const manager = createIFrameLifecycleManager({
|
|
362
519
|
iframe: mockIframe,
|
|
520
|
+
targetOrigin: WALLET_ORIGIN,
|
|
363
521
|
});
|
|
364
522
|
|
|
365
523
|
const event = {
|
|
@@ -381,6 +539,7 @@ describe("createIFrameLifecycleManager", () => {
|
|
|
381
539
|
const mockIframe = document.createElement("iframe");
|
|
382
540
|
const manager = createIFrameLifecycleManager({
|
|
383
541
|
iframe: mockIframe,
|
|
542
|
+
targetOrigin: WALLET_ORIGIN,
|
|
384
543
|
});
|
|
385
544
|
|
|
386
545
|
// Event without iframeLifecycle
|