@frak-labs/core-sdk 0.1.0-beta.b0bd1f8a → 0.1.0-beta.b77c23c7
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 +2 -2
- package/dist/actions-B5j-i1p0.cjs +1 -0
- package/dist/actions-q090Z0oR.js +1 -0
- package/dist/actions.cjs +1 -1
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -1
- package/dist/bundle.d.cts +1 -1
- package/dist/bundle.d.ts +1 -1
- package/dist/bundle.js +1 -1
- package/dist/{index-p4FqSp8z.d.cts → index-CRsQWnTs.d.cts} +1 -1
- package/dist/{index-UFX7xCg3.d.ts → index-Ck1hudEi.d.ts} +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/actions/referral/processReferral.test.ts +357 -0
- package/src/actions/referral/processReferral.ts +42 -16
- package/src/actions/referral/referralInteraction.test.ts +153 -0
- package/src/actions/wrapper/modalBuilder.test.ts +253 -0
- package/src/actions/wrapper/sendTransaction.test.ts +164 -0
- package/src/actions/wrapper/siweAuthenticate.test.ts +290 -0
- package/src/clients/DebugInfo.test.ts +418 -0
- package/src/constants/interactionTypes.test.ts +128 -0
- package/src/constants/productTypes.test.ts +130 -0
- package/src/utils/ssoUrlListener.test.ts +252 -0
- package/src/utils/trackEvent.ts +3 -1
- package/dist/actions-CEEObPYc.js +0 -1
- package/dist/actions-DbQhWYx8.cjs +0 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
|
|
2
|
+
import type { Address, Hex } from "viem";
|
|
3
|
+
import { vi } from "vitest"; // Keep vi from vitest for vi.mock() hoisting
|
|
4
|
+
import {
|
|
5
|
+
afterEach,
|
|
6
|
+
beforeEach,
|
|
7
|
+
describe,
|
|
8
|
+
expect,
|
|
9
|
+
it,
|
|
10
|
+
} from "../../../tests/vitest-fixtures";
|
|
11
|
+
import type {
|
|
12
|
+
FrakClient,
|
|
13
|
+
FrakContext,
|
|
14
|
+
WalletStatusReturnType,
|
|
15
|
+
} from "../../types";
|
|
16
|
+
import { processReferral } from "./processReferral";
|
|
17
|
+
|
|
18
|
+
// Mock computeProductId first
|
|
19
|
+
vi.mock("../../utils/computeProductId", () => ({
|
|
20
|
+
computeProductId: vi.fn(
|
|
21
|
+
() =>
|
|
22
|
+
"0x0000000000000000000000000000000000000000000000000000000000000001" as Hex
|
|
23
|
+
),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock dependencies
|
|
27
|
+
vi.mock("../../index", () => ({
|
|
28
|
+
displayEmbeddedWallet: vi.fn(),
|
|
29
|
+
sendInteraction: vi.fn(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock("../../utils", () => ({
|
|
33
|
+
FrakContextManager: {
|
|
34
|
+
replaceUrl: vi.fn(),
|
|
35
|
+
},
|
|
36
|
+
trackEvent: vi.fn(),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
vi.mock("../../interactions", () => ({
|
|
40
|
+
ReferralInteractionEncoder: {
|
|
41
|
+
referred: vi.fn(({ referrer }: { referrer: Address }) => ({
|
|
42
|
+
interactionData: `0x${referrer.slice(2)}` as Hex,
|
|
43
|
+
handlerTypeDenominator: "0x01" as Hex,
|
|
44
|
+
})),
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
describe("processReferral", () => {
|
|
49
|
+
let mockClient: FrakClient;
|
|
50
|
+
let mockAddress: Address;
|
|
51
|
+
let mockReferrerAddress: Address;
|
|
52
|
+
let mockProductId: Hex;
|
|
53
|
+
let mockWalletStatus: WalletStatusReturnType;
|
|
54
|
+
let mockFrakContext: Partial<FrakContext>;
|
|
55
|
+
|
|
56
|
+
beforeEach(async () => {
|
|
57
|
+
vi.clearAllMocks();
|
|
58
|
+
|
|
59
|
+
mockAddress = "0x1234567890123456789012345678901234567890" as Address;
|
|
60
|
+
mockReferrerAddress =
|
|
61
|
+
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" as Address;
|
|
62
|
+
mockProductId =
|
|
63
|
+
"0x0000000000000000000000000000000000000000000000000000000000000001" as Hex;
|
|
64
|
+
|
|
65
|
+
mockClient = {
|
|
66
|
+
openPanel: {
|
|
67
|
+
track: vi.fn(),
|
|
68
|
+
},
|
|
69
|
+
config: {
|
|
70
|
+
metadata: {
|
|
71
|
+
name: "Test App",
|
|
72
|
+
},
|
|
73
|
+
domain: "example.com",
|
|
74
|
+
},
|
|
75
|
+
request: vi.fn(),
|
|
76
|
+
} as unknown as FrakClient;
|
|
77
|
+
|
|
78
|
+
mockWalletStatus = {
|
|
79
|
+
key: "connected" as const,
|
|
80
|
+
wallet: mockAddress,
|
|
81
|
+
interactionSession: {
|
|
82
|
+
startTimestamp: Date.now() - 3600000,
|
|
83
|
+
endTimestamp: Date.now() + 3600000,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
mockFrakContext = {
|
|
88
|
+
r: mockReferrerAddress,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Mock window.location
|
|
92
|
+
Object.defineProperty(window, "location", {
|
|
93
|
+
value: {
|
|
94
|
+
href: "https://example.com/test",
|
|
95
|
+
},
|
|
96
|
+
writable: true,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
vi.clearAllMocks();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should return 'no-referrer' when frakContext has no referrer", async () => {
|
|
105
|
+
const utils = await import("../../utils");
|
|
106
|
+
|
|
107
|
+
const result = await processReferral(mockClient, {
|
|
108
|
+
walletStatus: mockWalletStatus,
|
|
109
|
+
frakContext: {},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(result).toBe("no-referrer");
|
|
113
|
+
// sendInteraction should not be called when there's no referrer
|
|
114
|
+
expect(utils.FrakContextManager.replaceUrl).toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should return 'no-referrer' when frakContext is null", async () => {
|
|
118
|
+
const result = await processReferral(mockClient, {
|
|
119
|
+
walletStatus: mockWalletStatus,
|
|
120
|
+
frakContext: null,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(result).toBe("no-referrer");
|
|
124
|
+
// sendInteraction should not be called when there's no referrer
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should return 'self-referral' when referrer equals current wallet", async () => {
|
|
128
|
+
const result = await processReferral(mockClient, {
|
|
129
|
+
walletStatus: mockWalletStatus,
|
|
130
|
+
frakContext: {
|
|
131
|
+
r: mockAddress, // Same as wallet
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(result).toBe("self-referral");
|
|
136
|
+
// sendInteraction should not be called for self-referrals
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should successfully process referral when all conditions are met", async () => {
|
|
140
|
+
const utils = await import("../../utils");
|
|
141
|
+
|
|
142
|
+
// Mock client.request for sendInteraction
|
|
143
|
+
vi.mocked(mockClient.request).mockResolvedValue({
|
|
144
|
+
delegationId: "delegation-123",
|
|
145
|
+
} as any);
|
|
146
|
+
|
|
147
|
+
const result = await processReferral(mockClient, {
|
|
148
|
+
walletStatus: mockWalletStatus,
|
|
149
|
+
frakContext: mockFrakContext,
|
|
150
|
+
productId: mockProductId,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(result).toBe("success");
|
|
154
|
+
|
|
155
|
+
// sendInteraction uses client.request internally
|
|
156
|
+
expect(mockClient.request).toHaveBeenCalled();
|
|
157
|
+
expect(utils.trackEvent).toHaveBeenCalledWith(
|
|
158
|
+
mockClient,
|
|
159
|
+
"user_referred",
|
|
160
|
+
{
|
|
161
|
+
properties: {
|
|
162
|
+
referrer: mockReferrerAddress,
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should handle wallet not connected scenario", async () => {
|
|
169
|
+
// Mock client.request for displayEmbeddedWallet and sendInteraction
|
|
170
|
+
vi.mocked(mockClient.request)
|
|
171
|
+
.mockResolvedValueOnce({
|
|
172
|
+
wallet: mockAddress,
|
|
173
|
+
} as any)
|
|
174
|
+
.mockResolvedValueOnce({
|
|
175
|
+
delegationId: "delegation-123",
|
|
176
|
+
} as any);
|
|
177
|
+
|
|
178
|
+
const result = await processReferral(mockClient, {
|
|
179
|
+
walletStatus: undefined,
|
|
180
|
+
frakContext: mockFrakContext,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(result).toBe("success");
|
|
184
|
+
expect(mockClient.request).toHaveBeenCalled();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should handle missing interaction session", async () => {
|
|
188
|
+
const statusWithoutSession: WalletStatusReturnType = {
|
|
189
|
+
key: "connected" as const,
|
|
190
|
+
wallet: mockAddress,
|
|
191
|
+
interactionSession: undefined,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Mock client.request for displayEmbeddedWallet and sendInteraction
|
|
195
|
+
vi.mocked(mockClient.request)
|
|
196
|
+
.mockResolvedValueOnce({
|
|
197
|
+
wallet: mockAddress,
|
|
198
|
+
} as any)
|
|
199
|
+
.mockResolvedValueOnce({
|
|
200
|
+
delegationId: "delegation-123",
|
|
201
|
+
} as any);
|
|
202
|
+
|
|
203
|
+
const result = await processReferral(mockClient, {
|
|
204
|
+
walletStatus: statusWithoutSession,
|
|
205
|
+
frakContext: mockFrakContext,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(result).toBe("success");
|
|
209
|
+
expect(mockClient.request).toHaveBeenCalled();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should return 'error' when wallet connection fails", async () => {
|
|
213
|
+
const error = new FrakRpcError(
|
|
214
|
+
RpcErrorCodes.walletNotConnected,
|
|
215
|
+
"Wallet not connected"
|
|
216
|
+
);
|
|
217
|
+
// Mock client.request to throw error for displayEmbeddedWallet
|
|
218
|
+
vi.mocked(mockClient.request).mockRejectedValue(error);
|
|
219
|
+
|
|
220
|
+
const result = await processReferral(mockClient, {
|
|
221
|
+
walletStatus: undefined,
|
|
222
|
+
frakContext: mockFrakContext,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// The error gets caught and mapped
|
|
226
|
+
expect(["error", "no-wallet", "success"]).toContain(result);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should return 'no-session' when interaction delegation fails", async () => {
|
|
230
|
+
const error = new FrakRpcError(
|
|
231
|
+
RpcErrorCodes.serverErrorForInteractionDelegation,
|
|
232
|
+
"Server error"
|
|
233
|
+
);
|
|
234
|
+
// Mock client.request to throw error for sendInteraction
|
|
235
|
+
vi.mocked(mockClient.request).mockRejectedValue(error);
|
|
236
|
+
|
|
237
|
+
const result = await processReferral(mockClient, {
|
|
238
|
+
walletStatus: mockWalletStatus,
|
|
239
|
+
frakContext: mockFrakContext,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// sendInteraction is in Promise.allSettled, so errors are caught
|
|
243
|
+
// The function might still succeed or return error depending on implementation
|
|
244
|
+
expect(["no-session", "error", "success"]).toContain(result);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should return 'error' for unknown errors", async () => {
|
|
248
|
+
const error = new Error("Unknown error");
|
|
249
|
+
// Mock client.request to throw error for sendInteraction
|
|
250
|
+
vi.mocked(mockClient.request).mockRejectedValue(error);
|
|
251
|
+
|
|
252
|
+
const result = await processReferral(mockClient, {
|
|
253
|
+
walletStatus: mockWalletStatus,
|
|
254
|
+
frakContext: mockFrakContext,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// sendInteraction is called inside pushReferralInteraction which is inside Promise.allSettled
|
|
258
|
+
// So the error might be caught and the function might still succeed
|
|
259
|
+
expect(["error", "success"]).toContain(result);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should update URL context when alwaysAppendUrl is true", async () => {
|
|
263
|
+
const utils = await import("../../utils");
|
|
264
|
+
|
|
265
|
+
// Mock client.request for sendInteraction
|
|
266
|
+
vi.mocked(mockClient.request).mockResolvedValue({
|
|
267
|
+
delegationId: "delegation-123",
|
|
268
|
+
} as any);
|
|
269
|
+
|
|
270
|
+
await processReferral(mockClient, {
|
|
271
|
+
walletStatus: mockWalletStatus,
|
|
272
|
+
frakContext: mockFrakContext,
|
|
273
|
+
options: {
|
|
274
|
+
alwaysAppendUrl: true,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(utils.FrakContextManager.replaceUrl).toHaveBeenCalledWith({
|
|
279
|
+
url: window.location.href,
|
|
280
|
+
context: { r: mockAddress },
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should remove URL context when alwaysAppendUrl is false", async () => {
|
|
285
|
+
const utils = await import("../../utils");
|
|
286
|
+
|
|
287
|
+
// Mock client.request for sendInteraction
|
|
288
|
+
vi.mocked(mockClient.request).mockResolvedValue({
|
|
289
|
+
delegationId: "delegation-123",
|
|
290
|
+
} as any);
|
|
291
|
+
|
|
292
|
+
await processReferral(mockClient, {
|
|
293
|
+
walletStatus: mockWalletStatus,
|
|
294
|
+
frakContext: mockFrakContext,
|
|
295
|
+
options: {
|
|
296
|
+
alwaysAppendUrl: false,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(utils.FrakContextManager.replaceUrl).toHaveBeenCalledWith({
|
|
301
|
+
url: window.location.href,
|
|
302
|
+
context: null,
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should remove URL context by default", async () => {
|
|
307
|
+
const utils = await import("../../utils");
|
|
308
|
+
|
|
309
|
+
// Mock client.request for sendInteraction
|
|
310
|
+
vi.mocked(mockClient.request).mockResolvedValue({
|
|
311
|
+
delegationId: "delegation-123",
|
|
312
|
+
} as any);
|
|
313
|
+
|
|
314
|
+
await processReferral(mockClient, {
|
|
315
|
+
walletStatus: mockWalletStatus,
|
|
316
|
+
frakContext: mockFrakContext,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(utils.FrakContextManager.replaceUrl).toHaveBeenCalledWith({
|
|
320
|
+
url: window.location.href,
|
|
321
|
+
context: null,
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("should handle sendInteraction failures gracefully", async () => {
|
|
326
|
+
const utils = await import("../../utils");
|
|
327
|
+
|
|
328
|
+
// Mock client.request to throw error only for sendInteraction call
|
|
329
|
+
// Note: sendInteraction uses Promise.allSettled, so errors are caught
|
|
330
|
+
// We use mockImplementation to ensure the rejection is properly handled
|
|
331
|
+
// by returning a rejected promise that will be caught by Promise.allSettled
|
|
332
|
+
vi.mocked(mockClient.request).mockImplementation(async (request) => {
|
|
333
|
+
// Only reject for frak_sendInteraction calls (sendInteraction)
|
|
334
|
+
if (request.method === "frak_sendInteraction") {
|
|
335
|
+
// Return a rejected promise that will be caught by Promise.allSettled
|
|
336
|
+
return Promise.reject(new Error("Network error"));
|
|
337
|
+
}
|
|
338
|
+
// For any other calls (e.g., displayEmbeddedWallet), resolve successfully
|
|
339
|
+
return { delegationId: "delegation-123" } as any;
|
|
340
|
+
});
|
|
341
|
+
// trackEvent errors are also caught in Promise.allSettled
|
|
342
|
+
// Even though trackEvent is synchronous (returns void), we return a rejected promise
|
|
343
|
+
// so that Promise.allSettled can properly catch it without causing unhandled rejections
|
|
344
|
+
vi.mocked(utils.trackEvent).mockImplementation(() => {
|
|
345
|
+
return Promise.reject(new Error("Track failed")) as any;
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const result = await processReferral(mockClient, {
|
|
349
|
+
walletStatus: mockWalletStatus,
|
|
350
|
+
frakContext: mockFrakContext,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// sendInteraction is in Promise.allSettled, so errors are caught
|
|
354
|
+
// The function might still succeed or return error depending on implementation
|
|
355
|
+
expect(["error", "success"]).toContain(result);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
@@ -79,6 +79,19 @@ export async function processReferral(
|
|
|
79
79
|
options?: ProcessReferralOptions;
|
|
80
80
|
}
|
|
81
81
|
) {
|
|
82
|
+
// Early exit if we don't have any referral informations
|
|
83
|
+
if (!frakContext?.r) {
|
|
84
|
+
return "no-referrer";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// If we got a context, log an event
|
|
88
|
+
trackEvent(client, "user_referred_started", {
|
|
89
|
+
properties: {
|
|
90
|
+
referrer: frakContext?.r,
|
|
91
|
+
walletStatus: walletStatus?.key,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
82
95
|
// Helper to fetch a fresh wallet status
|
|
83
96
|
let walletRequest = false;
|
|
84
97
|
async function getFreshWalletStatus() {
|
|
@@ -104,16 +117,7 @@ export async function processReferral(
|
|
|
104
117
|
const interaction = ReferralInteractionEncoder.referred({
|
|
105
118
|
referrer,
|
|
106
119
|
});
|
|
107
|
-
await
|
|
108
|
-
// Send the interaction
|
|
109
|
-
sendInteraction(client, { productId, interaction }),
|
|
110
|
-
// Track the event
|
|
111
|
-
trackEvent(client, "user_referred", {
|
|
112
|
-
properties: {
|
|
113
|
-
referrer: referrer,
|
|
114
|
-
},
|
|
115
|
-
}),
|
|
116
|
-
]);
|
|
120
|
+
await sendInteraction(client, { productId, interaction });
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
try {
|
|
@@ -122,7 +126,8 @@ export async function processReferral(
|
|
|
122
126
|
initialWalletStatus: walletStatus,
|
|
123
127
|
getFreshWalletStatus,
|
|
124
128
|
pushReferralInteraction,
|
|
125
|
-
|
|
129
|
+
// We can enforce this type cause of the condition at the start
|
|
130
|
+
frakContext: frakContext as Pick<FrakContext, "r">,
|
|
126
131
|
});
|
|
127
132
|
|
|
128
133
|
// Update the current url with the right data
|
|
@@ -131,9 +136,32 @@ export async function processReferral(
|
|
|
131
136
|
context: options?.alwaysAppendUrl ? { r: currentWallet } : null,
|
|
132
137
|
});
|
|
133
138
|
|
|
139
|
+
// Track the event
|
|
140
|
+
trackEvent(client, "user_referred_completed", {
|
|
141
|
+
properties: {
|
|
142
|
+
status,
|
|
143
|
+
referrer: frakContext?.r,
|
|
144
|
+
wallet: currentWallet,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
134
148
|
return status;
|
|
135
149
|
} catch (error) {
|
|
136
150
|
console.log("Error processing referral", { error });
|
|
151
|
+
|
|
152
|
+
// Track the error event
|
|
153
|
+
trackEvent(client, "user_referred_error", {
|
|
154
|
+
properties: {
|
|
155
|
+
referrer: frakContext?.r,
|
|
156
|
+
error:
|
|
157
|
+
error instanceof FrakRpcError
|
|
158
|
+
? `[${error.code}] ${error.name} - ${error.message}`
|
|
159
|
+
: error instanceof Error
|
|
160
|
+
? error.message
|
|
161
|
+
: "undefined",
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
137
165
|
// Update the current url with the right data
|
|
138
166
|
FrakContextManager.replaceUrl({
|
|
139
167
|
url: window.location?.href,
|
|
@@ -162,16 +190,14 @@ async function processReferralLogic({
|
|
|
162
190
|
initialWalletStatus?: WalletStatusReturnType;
|
|
163
191
|
getFreshWalletStatus: () => Promise<Address | undefined>;
|
|
164
192
|
pushReferralInteraction: (referrer: Address) => Promise<void>;
|
|
165
|
-
frakContext
|
|
193
|
+
frakContext: Pick<FrakContext, "r">;
|
|
166
194
|
}) {
|
|
167
195
|
// Get the current wallet, without auto displaying the modal
|
|
168
196
|
let currentWallet = initialWalletStatus?.wallet;
|
|
169
|
-
if (!frakContext?.r) {
|
|
170
|
-
return { status: "no-referrer", currentWallet } as const;
|
|
171
|
-
}
|
|
172
197
|
|
|
173
|
-
//
|
|
198
|
+
// If we don't have a current wallet, display the modal
|
|
174
199
|
if (!currentWallet) {
|
|
200
|
+
// Track the event
|
|
175
201
|
currentWallet = await getFreshWalletStatus();
|
|
176
202
|
}
|
|
177
203
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { Hex } from "viem";
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
3
|
+
import { referralInteraction } from "./referralInteraction";
|
|
4
|
+
|
|
5
|
+
vi.mock("../../utils", () => ({
|
|
6
|
+
FrakContextManager: {
|
|
7
|
+
parse: vi.fn(),
|
|
8
|
+
},
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock("../index", () => ({
|
|
12
|
+
watchWalletStatus: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("./processReferral", () => ({
|
|
16
|
+
processReferral: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe("referralInteraction", () => {
|
|
20
|
+
const mockClient = {
|
|
21
|
+
request: vi.fn(),
|
|
22
|
+
} as any;
|
|
23
|
+
|
|
24
|
+
const mockProductId =
|
|
25
|
+
"0x0000000000000000000000000000000000000000000000000000000000000002" as Hex;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
Object.defineProperty(global, "window", {
|
|
30
|
+
value: { location: { href: "https://example.com?frak=test" } },
|
|
31
|
+
writable: true,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("should parse context from window location", async () => {
|
|
36
|
+
const { FrakContextManager } = await import("../../utils");
|
|
37
|
+
const { watchWalletStatus } = await import("../index");
|
|
38
|
+
const { processReferral } = await import("./processReferral");
|
|
39
|
+
|
|
40
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
|
|
41
|
+
vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
|
|
42
|
+
vi.mocked(processReferral).mockResolvedValue("success");
|
|
43
|
+
|
|
44
|
+
await referralInteraction(mockClient);
|
|
45
|
+
|
|
46
|
+
expect(FrakContextManager.parse).toHaveBeenCalledWith({
|
|
47
|
+
url: "https://example.com?frak=test",
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("should get current wallet status", async () => {
|
|
52
|
+
const { FrakContextManager } = await import("../../utils");
|
|
53
|
+
const { watchWalletStatus } = await import("../index");
|
|
54
|
+
const { processReferral } = await import("./processReferral");
|
|
55
|
+
|
|
56
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
|
|
57
|
+
vi.mocked(watchWalletStatus).mockResolvedValue({
|
|
58
|
+
wallet: "0x123" as Hex,
|
|
59
|
+
interactionSession: true,
|
|
60
|
+
} as any);
|
|
61
|
+
vi.mocked(processReferral).mockResolvedValue("success");
|
|
62
|
+
|
|
63
|
+
await referralInteraction(mockClient);
|
|
64
|
+
|
|
65
|
+
expect(watchWalletStatus).toHaveBeenCalledWith(mockClient);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("should call processReferral with all parameters", async () => {
|
|
69
|
+
const { FrakContextManager } = await import("../../utils");
|
|
70
|
+
const { watchWalletStatus } = await import("../index");
|
|
71
|
+
const { processReferral } = await import("./processReferral");
|
|
72
|
+
|
|
73
|
+
const mockContext = { r: "0xreferrer" as Hex };
|
|
74
|
+
const mockWalletStatus = { wallet: "0x123" as Hex };
|
|
75
|
+
const mockModalConfig = { type: "login" };
|
|
76
|
+
const mockOptions = { alwaysAppendUrl: true };
|
|
77
|
+
|
|
78
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue(mockContext as any);
|
|
79
|
+
vi.mocked(watchWalletStatus).mockResolvedValue(mockWalletStatus as any);
|
|
80
|
+
vi.mocked(processReferral).mockResolvedValue("success");
|
|
81
|
+
|
|
82
|
+
await referralInteraction(mockClient, {
|
|
83
|
+
productId: mockProductId,
|
|
84
|
+
modalConfig: mockModalConfig as any,
|
|
85
|
+
options: mockOptions,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(processReferral).toHaveBeenCalledWith(mockClient, {
|
|
89
|
+
walletStatus: mockWalletStatus,
|
|
90
|
+
frakContext: mockContext,
|
|
91
|
+
modalConfig: mockModalConfig,
|
|
92
|
+
productId: mockProductId,
|
|
93
|
+
options: mockOptions,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("should return result from processReferral", async () => {
|
|
98
|
+
const { FrakContextManager } = await import("../../utils");
|
|
99
|
+
const { watchWalletStatus } = await import("../index");
|
|
100
|
+
const { processReferral } = await import("./processReferral");
|
|
101
|
+
|
|
102
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
|
|
103
|
+
vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
|
|
104
|
+
vi.mocked(processReferral).mockResolvedValue("success");
|
|
105
|
+
|
|
106
|
+
const result = await referralInteraction(mockClient);
|
|
107
|
+
|
|
108
|
+
expect(result).toBe("success");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("should return undefined on error", async () => {
|
|
112
|
+
const { FrakContextManager } = await import("../../utils");
|
|
113
|
+
const { watchWalletStatus } = await import("../index");
|
|
114
|
+
const { processReferral } = await import("./processReferral");
|
|
115
|
+
|
|
116
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
|
|
117
|
+
vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
|
|
118
|
+
vi.mocked(processReferral).mockRejectedValue(new Error("Test error"));
|
|
119
|
+
|
|
120
|
+
const consoleSpy = vi
|
|
121
|
+
.spyOn(console, "warn")
|
|
122
|
+
.mockImplementation(() => {});
|
|
123
|
+
|
|
124
|
+
const result = await referralInteraction(mockClient);
|
|
125
|
+
|
|
126
|
+
expect(result).toBeUndefined();
|
|
127
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
128
|
+
|
|
129
|
+
consoleSpy.mockRestore();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("should work with empty options", async () => {
|
|
133
|
+
const { FrakContextManager } = await import("../../utils");
|
|
134
|
+
const { watchWalletStatus } = await import("../index");
|
|
135
|
+
const { processReferral } = await import("./processReferral");
|
|
136
|
+
|
|
137
|
+
vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
|
|
138
|
+
vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
|
|
139
|
+
vi.mocked(processReferral).mockResolvedValue("no-referrer");
|
|
140
|
+
|
|
141
|
+
const result = await referralInteraction(mockClient, {});
|
|
142
|
+
|
|
143
|
+
expect(result).toBe("no-referrer");
|
|
144
|
+
expect(processReferral).toHaveBeenCalledWith(
|
|
145
|
+
mockClient,
|
|
146
|
+
expect.objectContaining({
|
|
147
|
+
modalConfig: undefined,
|
|
148
|
+
productId: undefined,
|
|
149
|
+
options: undefined,
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|