@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
package/src/types/rpc.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { Hex } from "viem";
|
|
2
|
+
import type { FrakWalletSdkConfig } from "./config";
|
|
3
|
+
import type {
|
|
4
|
+
ModalRpcMetadata,
|
|
5
|
+
ModalRpcStepsInput,
|
|
6
|
+
ModalRpcStepsResultType,
|
|
7
|
+
} from "./rpc/displayModal";
|
|
8
|
+
import type {
|
|
9
|
+
DisplayEmbeddedWalletParamsType,
|
|
10
|
+
DisplayEmbeddedWalletResultType,
|
|
11
|
+
} from "./rpc/embedded";
|
|
12
|
+
import type {
|
|
13
|
+
PreparedInteraction,
|
|
14
|
+
SendInteractionReturnType,
|
|
15
|
+
} from "./rpc/interaction";
|
|
16
|
+
import type { GetProductInformationReturnType } from "./rpc/productInformation";
|
|
17
|
+
import type {
|
|
18
|
+
OpenSsoParamsType,
|
|
19
|
+
OpenSsoReturnType,
|
|
20
|
+
PrepareSsoParamsType,
|
|
21
|
+
PrepareSsoReturnType,
|
|
22
|
+
} from "./rpc/sso";
|
|
23
|
+
import type { WalletStatusReturnType } from "./rpc/walletStatus";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* RPC interface that's used for the iframe communication
|
|
27
|
+
*
|
|
28
|
+
* Define all the methods available within the iFrame RPC client with response type annotations
|
|
29
|
+
*
|
|
30
|
+
* @group RPC Schema
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Each method in the schema now includes a ResponseType field that indicates:
|
|
34
|
+
* - "promise": One-shot request that resolves once
|
|
35
|
+
* - "stream": Streaming request that can emit multiple values
|
|
36
|
+
*
|
|
37
|
+
* ### Methods:
|
|
38
|
+
*
|
|
39
|
+
* #### frak_listenToWalletStatus
|
|
40
|
+
* - Params: None
|
|
41
|
+
* - Returns: {@link WalletStatusReturnType}
|
|
42
|
+
* - Response Type: stream (emits updates when wallet status changes)
|
|
43
|
+
*
|
|
44
|
+
* #### frak_displayModal
|
|
45
|
+
* - Params: [requests: {@link ModalRpcStepsInput}, metadata?: {@link ModalRpcMetadata}, configMetadata: {@link FrakWalletSdkConfig}["metadata"]]
|
|
46
|
+
* - Returns: {@link ModalRpcStepsResultType}
|
|
47
|
+
* - Response Type: promise (one-shot)
|
|
48
|
+
*
|
|
49
|
+
* #### frak_sendInteraction
|
|
50
|
+
* - Params: [productId: Hex, interaction: {@link PreparedInteraction}, signature?: Hex]
|
|
51
|
+
* - Returns: {@link SendInteractionReturnType}
|
|
52
|
+
* - Response Type: promise (one-shot)
|
|
53
|
+
*
|
|
54
|
+
* #### frak_sso
|
|
55
|
+
* - Params: [params: {@link OpenSsoParamsType}, name: string, customCss?: string]
|
|
56
|
+
* - Returns: {@link OpenSsoReturnType}
|
|
57
|
+
* - Response Type: promise (one-shot)
|
|
58
|
+
*
|
|
59
|
+
* #### frak_getProductInformation
|
|
60
|
+
* - Params: None
|
|
61
|
+
* - Returns: {@link GetProductInformationReturnType}
|
|
62
|
+
* - Response Type: promise (one-shot)
|
|
63
|
+
*
|
|
64
|
+
* #### frak_displayEmbeddedWallet
|
|
65
|
+
* - Params: [request: {@link DisplayEmbeddedWalletParamsType}, metadata: {@link FrakWalletSdkConfig}["metadata"]]
|
|
66
|
+
* - Returns: {@link DisplayEmbeddedWalletResultType}
|
|
67
|
+
* - Response Type: promise (one-shot)
|
|
68
|
+
*/
|
|
69
|
+
export type IFrameRpcSchema = [
|
|
70
|
+
/**
|
|
71
|
+
* Method used to listen to the wallet status
|
|
72
|
+
* This is a streaming method that emits updates when wallet status changes
|
|
73
|
+
*/
|
|
74
|
+
{
|
|
75
|
+
Method: "frak_listenToWalletStatus";
|
|
76
|
+
Parameters?: undefined;
|
|
77
|
+
ReturnType: WalletStatusReturnType;
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Method to display a modal with the provided steps
|
|
81
|
+
* This is a one-shot request
|
|
82
|
+
*/
|
|
83
|
+
{
|
|
84
|
+
Method: "frak_displayModal";
|
|
85
|
+
Parameters: [
|
|
86
|
+
requests: ModalRpcStepsInput,
|
|
87
|
+
metadata: ModalRpcMetadata | undefined,
|
|
88
|
+
configMetadata: FrakWalletSdkConfig["metadata"],
|
|
89
|
+
];
|
|
90
|
+
ReturnType: ModalRpcStepsResultType;
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* Method to transmit a user interaction
|
|
94
|
+
* This is a one-shot request
|
|
95
|
+
*/
|
|
96
|
+
{
|
|
97
|
+
Method: "frak_sendInteraction";
|
|
98
|
+
Parameters: [
|
|
99
|
+
productId: Hex,
|
|
100
|
+
interaction: PreparedInteraction,
|
|
101
|
+
signature?: Hex,
|
|
102
|
+
];
|
|
103
|
+
ReturnType: SendInteractionReturnType;
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* Method to prepare SSO (generate URL for popup)
|
|
107
|
+
* Returns the SSO URL that should be opened in a popup
|
|
108
|
+
* Only used for popup flows (not redirect flows)
|
|
109
|
+
*/
|
|
110
|
+
{
|
|
111
|
+
Method: "frak_prepareSso";
|
|
112
|
+
Parameters: [
|
|
113
|
+
params: PrepareSsoParamsType,
|
|
114
|
+
name: string,
|
|
115
|
+
customCss?: string,
|
|
116
|
+
];
|
|
117
|
+
ReturnType: PrepareSsoReturnType;
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* Method to open/trigger SSO
|
|
121
|
+
* Either triggers redirect (if openInSameWindow/redirectUrl)
|
|
122
|
+
* Or waits for popup completion (if popup mode)
|
|
123
|
+
* This method handles BOTH redirect and popup flows
|
|
124
|
+
*/
|
|
125
|
+
{
|
|
126
|
+
Method: "frak_openSso";
|
|
127
|
+
Parameters: [
|
|
128
|
+
params: OpenSsoParamsType,
|
|
129
|
+
name: string,
|
|
130
|
+
customCss?: string,
|
|
131
|
+
];
|
|
132
|
+
ReturnType: OpenSsoReturnType;
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* Method to get current product information's
|
|
136
|
+
* - Is product minted?
|
|
137
|
+
* - Does it have running campaign?
|
|
138
|
+
* - Estimated reward on actions
|
|
139
|
+
* This is a one-shot request
|
|
140
|
+
*/
|
|
141
|
+
{
|
|
142
|
+
Method: "frak_getProductInformation";
|
|
143
|
+
Parameters?: undefined;
|
|
144
|
+
ReturnType: GetProductInformationReturnType;
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* Method to show the embedded wallet, with potential customization
|
|
148
|
+
* This is a one-shot request
|
|
149
|
+
*/
|
|
150
|
+
{
|
|
151
|
+
Method: "frak_displayEmbeddedWallet";
|
|
152
|
+
Parameters: [
|
|
153
|
+
request: DisplayEmbeddedWalletParamsType,
|
|
154
|
+
metadata: FrakWalletSdkConfig["metadata"],
|
|
155
|
+
];
|
|
156
|
+
ReturnType: DisplayEmbeddedWalletResultType;
|
|
157
|
+
},
|
|
158
|
+
];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LifecycleMessage, RpcClient } from "@frak-labs/frame-connector";
|
|
2
|
+
import type { ClientLifecycleEvent, IFrameLifecycleEvent } from "./lifecycle";
|
|
3
|
+
import type { IFrameRpcSchema } from "./rpc";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* IFrame transport interface
|
|
7
|
+
*/
|
|
8
|
+
export type IFrameTransport = {
|
|
9
|
+
/**
|
|
10
|
+
* Wait for the connection to be established
|
|
11
|
+
*/
|
|
12
|
+
waitForConnection: Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Wait for the setup to be done
|
|
15
|
+
*/
|
|
16
|
+
waitForSetup: Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Function used to perform a single request via the iframe transport
|
|
19
|
+
*/
|
|
20
|
+
request: RpcClient<IFrameRpcSchema, LifecycleMessage>["request"];
|
|
21
|
+
/**
|
|
22
|
+
* Function used to listen to a request response via the iframe transport
|
|
23
|
+
*/
|
|
24
|
+
listenerRequest: RpcClient<IFrameRpcSchema, LifecycleMessage>["listen"];
|
|
25
|
+
/**
|
|
26
|
+
* Function used to destroy the iframe transport
|
|
27
|
+
*/
|
|
28
|
+
destroy: () => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Represent an iframe event
|
|
33
|
+
*/
|
|
34
|
+
export type FrakLifecycleEvent = IFrameLifecycleEvent | ClientLifecycleEvent;
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for FrakContextManager utility
|
|
3
|
+
* Tests Frak context compression, URL parsing, and management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { mockWindowHistory } from "@frak-labs/test-foundation";
|
|
7
|
+
import type { Address } from "viem";
|
|
8
|
+
import {
|
|
9
|
+
afterEach,
|
|
10
|
+
beforeEach,
|
|
11
|
+
describe,
|
|
12
|
+
expect,
|
|
13
|
+
it,
|
|
14
|
+
vi,
|
|
15
|
+
} from "../../tests/vitest-fixtures";
|
|
16
|
+
import type { FrakContext } from "../types";
|
|
17
|
+
import { FrakContextManager } from "./FrakContext";
|
|
18
|
+
|
|
19
|
+
describe("FrakContextManager", () => {
|
|
20
|
+
let consoleErrorSpy: any;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
consoleErrorSpy = vi
|
|
24
|
+
.spyOn(console, "error")
|
|
25
|
+
.mockImplementation(() => {});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
consoleErrorSpy.mockRestore();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("compress", () => {
|
|
33
|
+
it("should compress context with referrer address", () => {
|
|
34
|
+
const context: Partial<FrakContext> = {
|
|
35
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const result = FrakContextManager.compress(context);
|
|
39
|
+
|
|
40
|
+
expect(result).toBeDefined();
|
|
41
|
+
expect(typeof result).toBe("string");
|
|
42
|
+
expect(result?.length).toBeGreaterThan(0);
|
|
43
|
+
// Base64url should not contain +, /, or =
|
|
44
|
+
expect(result).not.toMatch(/[+/=]/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should return undefined when context has no referrer", () => {
|
|
48
|
+
const context: Partial<FrakContext> = {};
|
|
49
|
+
|
|
50
|
+
const result = FrakContextManager.compress(context);
|
|
51
|
+
|
|
52
|
+
expect(result).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should return undefined when context is undefined", () => {
|
|
56
|
+
const result = FrakContextManager.compress(undefined);
|
|
57
|
+
|
|
58
|
+
expect(result).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should handle compression errors gracefully", () => {
|
|
62
|
+
const invalidContext = {
|
|
63
|
+
r: "invalid-address" as Address,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = FrakContextManager.compress(invalidContext);
|
|
67
|
+
|
|
68
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
69
|
+
expect(result).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("decompress", () => {
|
|
74
|
+
it("should decompress valid base64url context", () => {
|
|
75
|
+
// First compress a context
|
|
76
|
+
const originalContext: FrakContext = {
|
|
77
|
+
r: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" as Address,
|
|
78
|
+
};
|
|
79
|
+
const compressed = FrakContextManager.compress(originalContext);
|
|
80
|
+
|
|
81
|
+
// Then decompress it
|
|
82
|
+
const result = FrakContextManager.decompress(compressed);
|
|
83
|
+
|
|
84
|
+
expect(result).toBeDefined();
|
|
85
|
+
expect(result?.r).toBe(
|
|
86
|
+
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should return undefined for empty string", () => {
|
|
91
|
+
const result = FrakContextManager.decompress("");
|
|
92
|
+
|
|
93
|
+
expect(result).toBeUndefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should return undefined for undefined input", () => {
|
|
97
|
+
const result = FrakContextManager.decompress(undefined);
|
|
98
|
+
|
|
99
|
+
expect(result).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should handle decompression errors gracefully", () => {
|
|
103
|
+
const result = FrakContextManager.decompress(
|
|
104
|
+
"invalid-base64url!@#"
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
108
|
+
expect(result).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should round-trip compress and decompress", () => {
|
|
112
|
+
const original: FrakContext = {
|
|
113
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const compressed = FrakContextManager.compress(original);
|
|
117
|
+
const decompressed = FrakContextManager.decompress(compressed);
|
|
118
|
+
|
|
119
|
+
expect(decompressed).toEqual(original);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("parse", () => {
|
|
124
|
+
it("should parse URL with fCtx parameter", () => {
|
|
125
|
+
const context: FrakContext = {
|
|
126
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
127
|
+
};
|
|
128
|
+
const compressed = FrakContextManager.compress(context);
|
|
129
|
+
const url = `https://example.com?fCtx=${compressed}`;
|
|
130
|
+
|
|
131
|
+
const result = FrakContextManager.parse({ url });
|
|
132
|
+
|
|
133
|
+
expect(result).toBeDefined();
|
|
134
|
+
expect(result?.r).toBe(
|
|
135
|
+
"0x1234567890123456789012345678901234567890"
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should return null for URL without fCtx parameter", () => {
|
|
140
|
+
const url = "https://example.com?other=param";
|
|
141
|
+
|
|
142
|
+
const result = FrakContextManager.parse({ url });
|
|
143
|
+
|
|
144
|
+
expect(result).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should return null for empty URL", () => {
|
|
148
|
+
const result = FrakContextManager.parse({ url: "" });
|
|
149
|
+
|
|
150
|
+
expect(result).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should parse URL with multiple parameters", () => {
|
|
154
|
+
const context: FrakContext = {
|
|
155
|
+
r: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" as Address,
|
|
156
|
+
};
|
|
157
|
+
const compressed = FrakContextManager.compress(context);
|
|
158
|
+
const url = `https://example.com?foo=bar&fCtx=${compressed}&baz=qux`;
|
|
159
|
+
|
|
160
|
+
const result = FrakContextManager.parse({ url });
|
|
161
|
+
|
|
162
|
+
expect(result).toBeDefined();
|
|
163
|
+
expect(result?.r).toBe(
|
|
164
|
+
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should return undefined for malformed fCtx parameter", () => {
|
|
169
|
+
// Use a string that will fail base64url decoding
|
|
170
|
+
const url = "https://example.com?fCtx=!!!invalid!!!";
|
|
171
|
+
|
|
172
|
+
const result = FrakContextManager.parse({ url });
|
|
173
|
+
|
|
174
|
+
// Should handle the error and return undefined
|
|
175
|
+
expect(result).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("update", () => {
|
|
180
|
+
it("should add fCtx to URL without existing context", () => {
|
|
181
|
+
const url = "https://example.com";
|
|
182
|
+
const context: FrakContext = {
|
|
183
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const result = FrakContextManager.update({ url, context });
|
|
187
|
+
|
|
188
|
+
expect(result).toBeDefined();
|
|
189
|
+
expect(result).toContain("fCtx=");
|
|
190
|
+
expect(result).toContain("https://example.com");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should merge with existing context in URL", () => {
|
|
194
|
+
const existingContext: FrakContext = {
|
|
195
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
196
|
+
};
|
|
197
|
+
const compressed = FrakContextManager.compress(existingContext);
|
|
198
|
+
const url = `https://example.com?fCtx=${compressed}`;
|
|
199
|
+
|
|
200
|
+
const newContext: FrakContext = {
|
|
201
|
+
r: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" as Address,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = FrakContextManager.update({
|
|
205
|
+
url,
|
|
206
|
+
context: newContext,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
expect(result).toBeDefined();
|
|
210
|
+
expect(result).toContain("fCtx=");
|
|
211
|
+
|
|
212
|
+
// Parse the result and check it has the new referrer
|
|
213
|
+
const parsedResult = FrakContextManager.parse({ url: result! });
|
|
214
|
+
expect(parsedResult?.r).toBe(
|
|
215
|
+
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should return null when URL is undefined", () => {
|
|
220
|
+
const context: FrakContext = {
|
|
221
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const result = FrakContextManager.update({
|
|
225
|
+
url: undefined,
|
|
226
|
+
context,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(result).toBeNull();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should return null when context has no referrer", () => {
|
|
233
|
+
const url = "https://example.com";
|
|
234
|
+
const context: Partial<FrakContext> = {};
|
|
235
|
+
|
|
236
|
+
const result = FrakContextManager.update({ url, context });
|
|
237
|
+
|
|
238
|
+
expect(result).toBeNull();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should preserve other URL parameters", () => {
|
|
242
|
+
const url = "https://example.com?foo=bar&baz=qux";
|
|
243
|
+
const context: FrakContext = {
|
|
244
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const result = FrakContextManager.update({ url, context });
|
|
248
|
+
|
|
249
|
+
expect(result).toContain("foo=bar");
|
|
250
|
+
expect(result).toContain("baz=qux");
|
|
251
|
+
expect(result).toContain("fCtx=");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should preserve URL hash", () => {
|
|
255
|
+
const url = "https://example.com#section";
|
|
256
|
+
const context: FrakContext = {
|
|
257
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const result = FrakContextManager.update({ url, context });
|
|
261
|
+
|
|
262
|
+
expect(result).toContain("#section");
|
|
263
|
+
expect(result).toContain("fCtx=");
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("remove", () => {
|
|
268
|
+
it("should remove fCtx parameter from URL", () => {
|
|
269
|
+
const context: FrakContext = {
|
|
270
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
271
|
+
};
|
|
272
|
+
const compressed = FrakContextManager.compress(context);
|
|
273
|
+
const url = `https://example.com?fCtx=${compressed}`;
|
|
274
|
+
|
|
275
|
+
const result = FrakContextManager.remove(url);
|
|
276
|
+
|
|
277
|
+
expect(result).toBe("https://example.com/");
|
|
278
|
+
expect(result).not.toContain("fCtx");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("should preserve other parameters when removing fCtx", () => {
|
|
282
|
+
const context: FrakContext = {
|
|
283
|
+
r: "0x1234567890123456789012345678901234567890" as Address,
|
|
284
|
+
};
|
|
285
|
+
const compressed = FrakContextManager.compress(context);
|
|
286
|
+
const url = `https://example.com?foo=bar&fCtx=${compressed}&baz=qux`;
|
|
287
|
+
|
|
288
|
+
const result = FrakContextManager.remove(url);
|
|
289
|
+
|
|
290
|
+
expect(result).toContain("foo=bar");
|
|
291
|
+
expect(result).toContain("baz=qux");
|
|
292
|
+
expect(result).not.toContain("fCtx");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should handle URL without fCtx parameter", () => {
|
|
296
|
+
const url = "https://example.com?foo=bar";
|
|
297
|
+
|
|
298
|
+
const result = FrakContextManager.remove(url);
|
|
299
|
+
|
|
300
|
+
expect(result).toContain("foo=bar");
|
|
301
|
+
expect(result).not.toContain("fCtx");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should preserve URL hash", () => {
|
|
305
|
+
const url = "https://example.com?fCtx=test#section";
|
|
306
|
+
|
|
307
|
+
const result = FrakContextManager.remove(url);
|
|
308
|
+
|
|
309
|
+
expect(result).toContain("#section");
|
|
310
|
+
expect(result).not.toContain("fCtx");
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe("replaceUrl", () => {
|
|
315
|
+
const mockAddress =
|
|
316
|
+
"0x1234567890123456789012345678901234567890" as Address;
|
|
317
|
+
|
|
318
|
+
beforeEach(() => {
|
|
319
|
+
// Mock window.location.href
|
|
320
|
+
Object.defineProperty(window, "location", {
|
|
321
|
+
writable: true,
|
|
322
|
+
value: {
|
|
323
|
+
href: "https://example.com/page",
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Mock window.history using our test utility
|
|
328
|
+
mockWindowHistory(vi);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("should update window.location with context", () => {
|
|
332
|
+
const url = "https://example.com/test";
|
|
333
|
+
const context: FrakContext = { r: mockAddress };
|
|
334
|
+
|
|
335
|
+
FrakContextManager.replaceUrl({ url, context });
|
|
336
|
+
|
|
337
|
+
const historySpy = vi.mocked(window.history.replaceState);
|
|
338
|
+
expect(historySpy).toHaveBeenCalledTimes(1);
|
|
339
|
+
expect(historySpy).toHaveBeenCalledWith(
|
|
340
|
+
null,
|
|
341
|
+
"",
|
|
342
|
+
expect.stringContaining("fCtx=")
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const calledUrl = historySpy.mock.calls[0]?.[2] as string;
|
|
346
|
+
expect(calledUrl).toContain("https://example.com/test");
|
|
347
|
+
expect(calledUrl).toContain("fCtx=");
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("should use provided URL instead of window.location.href", () => {
|
|
351
|
+
const customUrl = "https://custom.com/path";
|
|
352
|
+
const context: FrakContext = { r: mockAddress };
|
|
353
|
+
|
|
354
|
+
FrakContextManager.replaceUrl({ url: customUrl, context });
|
|
355
|
+
|
|
356
|
+
const historySpy = vi.mocked(window.history.replaceState);
|
|
357
|
+
const calledUrl = historySpy.mock.calls[0]?.[2] as string;
|
|
358
|
+
|
|
359
|
+
expect(calledUrl).toContain("https://custom.com/path");
|
|
360
|
+
expect(calledUrl).not.toContain("https://example.com/page");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("should remove fCtx when context is null", () => {
|
|
364
|
+
const url = "https://example.com/test?fCtx=existing";
|
|
365
|
+
|
|
366
|
+
FrakContextManager.replaceUrl({ url, context: null });
|
|
367
|
+
|
|
368
|
+
const historySpy = vi.mocked(window.history.replaceState);
|
|
369
|
+
expect(historySpy).toHaveBeenCalledTimes(1);
|
|
370
|
+
|
|
371
|
+
const calledUrl = historySpy.mock.calls[0]?.[2] as string;
|
|
372
|
+
expect(calledUrl).not.toContain("fCtx=");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("should not call replaceState when context has no referrer", () => {
|
|
376
|
+
const url = "https://example.com/test";
|
|
377
|
+
const context: Partial<FrakContext> = {};
|
|
378
|
+
|
|
379
|
+
FrakContextManager.replaceUrl({ url, context });
|
|
380
|
+
|
|
381
|
+
const historySpy = vi.mocked(window.history.replaceState);
|
|
382
|
+
expect(historySpy).not.toHaveBeenCalled();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("should handle missing window gracefully", () => {
|
|
386
|
+
// Remove window.location to simulate missing window
|
|
387
|
+
Object.defineProperty(window, "location", {
|
|
388
|
+
writable: true,
|
|
389
|
+
value: undefined,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const url = "https://example.com/test";
|
|
393
|
+
const context: FrakContext = { r: mockAddress };
|
|
394
|
+
|
|
395
|
+
// Should not throw error
|
|
396
|
+
expect(() => {
|
|
397
|
+
FrakContextManager.replaceUrl({ url, context });
|
|
398
|
+
}).not.toThrow();
|
|
399
|
+
|
|
400
|
+
const historySpy = vi.mocked(window.history.replaceState);
|
|
401
|
+
expect(historySpy).not.toHaveBeenCalled();
|
|
402
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
403
|
+
"No window found, can't update context"
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
});
|