@frak-labs/core-sdk 0.1.0-beta.267778ad → 0.1.0-beta.278e9605

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.
Files changed (63) hide show
  1. package/package.json +20 -17
  2. package/src/actions/displayEmbeddedWallet.test.ts +194 -0
  3. package/src/actions/displayModal.test.ts +387 -0
  4. package/src/actions/getProductInformation.test.ts +133 -0
  5. package/src/actions/index.ts +19 -19
  6. package/src/actions/openSso.test.ts +407 -0
  7. package/src/actions/prepareSso.test.ts +223 -0
  8. package/src/actions/sendInteraction.test.ts +219 -0
  9. package/src/actions/trackPurchaseStatus.test.ts +287 -0
  10. package/src/actions/watchWalletStatus.test.ts +372 -0
  11. package/src/bundle.ts +1 -1
  12. package/src/clients/createIFrameFrakClient.ts +2 -2
  13. package/src/clients/index.ts +1 -1
  14. package/src/clients/setupClient.ts +3 -1
  15. package/src/clients/transports/iframeLifecycleManager.ts +3 -1
  16. package/src/index.ts +72 -74
  17. package/src/interactions/index.ts +2 -2
  18. package/src/interactions/pressEncoder.test.ts +215 -0
  19. package/src/interactions/pressEncoder.ts +1 -1
  20. package/src/interactions/purchaseEncoder.test.ts +291 -0
  21. package/src/interactions/purchaseEncoder.ts +8 -3
  22. package/src/interactions/referralEncoder.test.ts +170 -0
  23. package/src/interactions/retailEncoder.test.ts +107 -0
  24. package/src/interactions/retailEncoder.ts +1 -1
  25. package/src/interactions/webshopEncoder.test.ts +56 -0
  26. package/src/types/index.ts +51 -50
  27. package/src/types/lifecycle/index.ts +1 -1
  28. package/src/types/rpc/modal/index.ts +11 -11
  29. package/src/utils/FrakContext.test.ts +338 -0
  30. package/src/utils/FrakContext.ts +8 -2
  31. package/src/utils/compression/b64.test.ts +181 -0
  32. package/src/utils/compression/compress.test.ts +123 -0
  33. package/src/utils/compression/decompress.test.ts +145 -0
  34. package/src/utils/compression/index.ts +1 -1
  35. package/src/utils/computeProductId.test.ts +80 -0
  36. package/src/utils/constants.test.ts +23 -0
  37. package/src/utils/formatAmount.test.ts +113 -0
  38. package/src/utils/getCurrencyAmountKey.test.ts +44 -0
  39. package/src/utils/getSupportedCurrency.test.ts +51 -0
  40. package/src/utils/getSupportedLocale.test.ts +64 -0
  41. package/src/utils/iframeHelper.test.ts +450 -0
  42. package/src/utils/iframeHelper.ts +4 -3
  43. package/src/utils/index.ts +12 -12
  44. package/src/utils/sso.test.ts +361 -0
  45. package/src/utils/trackEvent.test.ts +162 -0
  46. package/cdn/bundle.js +0 -19
  47. package/cdn/bundle.js.LICENSE.txt +0 -10
  48. package/dist/actions.cjs +0 -1
  49. package/dist/actions.d.cts +0 -1481
  50. package/dist/actions.d.ts +0 -1481
  51. package/dist/actions.js +0 -1
  52. package/dist/bundle.cjs +0 -13
  53. package/dist/bundle.d.cts +0 -2087
  54. package/dist/bundle.d.ts +0 -2087
  55. package/dist/bundle.js +0 -13
  56. package/dist/index.cjs +0 -13
  57. package/dist/index.d.cts +0 -1387
  58. package/dist/index.d.ts +0 -1387
  59. package/dist/index.js +0 -13
  60. package/dist/interactions.cjs +0 -1
  61. package/dist/interactions.d.cts +0 -182
  62. package/dist/interactions.d.ts +0 -182
  63. package/dist/interactions.js +0 -1
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Tests for getProductInformation action
3
+ * Tests fetching product information via RPC
4
+ */
5
+
6
+ import type { Address, Hex } from "viem";
7
+ import { describe, expect, it, vi } from "../../tests/vitest-fixtures";
8
+ import type { FrakClient, GetProductInformationReturnType } from "../types";
9
+ import { getProductInformation } from "./getProductInformation";
10
+
11
+ describe("getProductInformation", () => {
12
+ describe("success cases", () => {
13
+ it("should call client.request with correct method", async () => {
14
+ const mockResponse: GetProductInformationReturnType = {
15
+ id: "0x1234567890123456789012345678901234567890123456789012345678901234" as Hex,
16
+ onChainMetadata: {
17
+ name: "Test Product",
18
+ domain: "example.com",
19
+ productTypes: ["press"],
20
+ },
21
+ rewards: [],
22
+ };
23
+
24
+ const mockClient = {
25
+ request: vi.fn().mockResolvedValue(mockResponse),
26
+ } as unknown as FrakClient;
27
+
28
+ await getProductInformation(mockClient);
29
+
30
+ expect(mockClient.request).toHaveBeenCalledWith({
31
+ method: "frak_getProductInformation",
32
+ });
33
+ });
34
+
35
+ it("should return product information", async () => {
36
+ const mockResponse: GetProductInformationReturnType = {
37
+ id: "0x1234567890123456789012345678901234567890123456789012345678901234" as Hex,
38
+ onChainMetadata: {
39
+ name: "Test Product",
40
+ domain: "example.com",
41
+ productTypes: ["press"],
42
+ },
43
+ rewards: [],
44
+ };
45
+
46
+ const mockClient = {
47
+ request: vi.fn().mockResolvedValue(mockResponse),
48
+ } as unknown as FrakClient;
49
+
50
+ const result = await getProductInformation(mockClient);
51
+
52
+ expect(result).toEqual(mockResponse);
53
+ });
54
+
55
+ it("should return product information with rewards", async () => {
56
+ const mockResponse: GetProductInformationReturnType = {
57
+ id: "0x1234567890123456789012345678901234567890123456789012345678901234" as Hex,
58
+ onChainMetadata: {
59
+ name: "Test Product",
60
+ domain: "example.com",
61
+ productTypes: ["press", "purchase"],
62
+ },
63
+ maxReferrer: {
64
+ amount: 100,
65
+ eurAmount: 10,
66
+ usdAmount: 12,
67
+ gbpAmount: 9,
68
+ },
69
+ maxReferee: {
70
+ amount: 50,
71
+ eurAmount: 5,
72
+ usdAmount: 6,
73
+ gbpAmount: 4.5,
74
+ },
75
+ rewards: [
76
+ {
77
+ token: "0x1234567890123456789012345678901234567890" as Address,
78
+ campaign:
79
+ "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" as Address,
80
+ interactionTypeKey: "press.readArticle",
81
+ referrer: {
82
+ amount: 10,
83
+ eurAmount: 1,
84
+ usdAmount: 1.2,
85
+ gbpAmount: 0.9,
86
+ },
87
+ referee: {
88
+ amount: 5,
89
+ eurAmount: 0.5,
90
+ usdAmount: 0.6,
91
+ gbpAmount: 0.45,
92
+ },
93
+ },
94
+ ],
95
+ };
96
+
97
+ const mockClient = {
98
+ request: vi.fn().mockResolvedValue(mockResponse),
99
+ } as unknown as FrakClient;
100
+
101
+ const result = await getProductInformation(mockClient);
102
+
103
+ expect(result).toEqual(mockResponse);
104
+ expect(result.rewards).toHaveLength(1);
105
+ expect(result.maxReferrer).toBeDefined();
106
+ expect(result.maxReferee).toBeDefined();
107
+ });
108
+ });
109
+
110
+ describe("error handling", () => {
111
+ it("should propagate errors from client.request", async () => {
112
+ const error = new Error("RPC request failed");
113
+ const mockClient = {
114
+ request: vi.fn().mockRejectedValue(error),
115
+ } as unknown as FrakClient;
116
+
117
+ await expect(getProductInformation(mockClient)).rejects.toThrow(
118
+ "RPC request failed"
119
+ );
120
+ });
121
+
122
+ it("should handle network timeout errors", async () => {
123
+ const error = new Error("Request timeout");
124
+ const mockClient = {
125
+ request: vi.fn().mockRejectedValue(error),
126
+ } as unknown as FrakClient;
127
+
128
+ await expect(getProductInformation(mockClient)).rejects.toThrow(
129
+ "Request timeout"
130
+ );
131
+ });
132
+ });
133
+ });
@@ -1,29 +1,29 @@
1
- export { watchWalletStatus } from "./watchWalletStatus";
2
- export { sendInteraction } from "./sendInteraction";
3
- export { displayModal } from "./displayModal";
4
1
  export { displayEmbeddedWallet } from "./displayEmbeddedWallet";
2
+ export { displayModal } from "./displayModal";
3
+ export { getProductInformation } from "./getProductInformation";
5
4
  export { openSso } from "./openSso";
6
5
  export { prepareSso } from "./prepareSso";
7
- export { getProductInformation } from "./getProductInformation";
6
+ export {
7
+ type ProcessReferralOptions,
8
+ processReferral,
9
+ } from "./referral/processReferral";
10
+ // Referral interaction
11
+ export { referralInteraction } from "./referral/referralInteraction";
12
+ export { sendInteraction } from "./sendInteraction";
8
13
  // Helper to track the purchase status
9
14
  export { trackPurchaseStatus } from "./trackPurchaseStatus";
10
- // Modal wrappers
15
+ export { watchWalletStatus } from "./watchWalletStatus";
11
16
  export {
12
- siweAuthenticate,
13
- type SiweAuthenticateModalParams,
14
- } from "./wrapper/siweAuthenticate";
17
+ type ModalBuilder,
18
+ type ModalStepBuilder,
19
+ modalBuilder,
20
+ } from "./wrapper/modalBuilder";
15
21
  export {
16
- sendTransaction,
17
22
  type SendTransactionParams,
23
+ sendTransaction,
18
24
  } from "./wrapper/sendTransaction";
25
+ // Modal wrappers
19
26
  export {
20
- modalBuilder,
21
- type ModalStepBuilder,
22
- type ModalBuilder,
23
- } from "./wrapper/modalBuilder";
24
- // Referral interaction
25
- export { referralInteraction } from "./referral/referralInteraction";
26
- export {
27
- processReferral,
28
- type ProcessReferralOptions,
29
- } from "./referral/processReferral";
27
+ type SiweAuthenticateModalParams,
28
+ siweAuthenticate,
29
+ } from "./wrapper/siweAuthenticate";
@@ -0,0 +1,407 @@
1
+ /**
2
+ * Tests for openSso action
3
+ * Tests SSO flows in both redirect and popup modes
4
+ */
5
+
6
+ import { vi } from "vitest";
7
+
8
+ // Mock utilities before imports
9
+ vi.mock("../utils/sso", () => ({
10
+ generateSsoUrl: vi.fn((walletUrl, _args, productId, name, _css) => {
11
+ return `${walletUrl}/sso?name=${name}&productId=${productId}`;
12
+ }),
13
+ }));
14
+
15
+ vi.mock("../utils/computeProductId", () => ({
16
+ computeProductId: vi.fn(
17
+ () =>
18
+ "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
19
+ ),
20
+ }));
21
+
22
+ import {
23
+ afterEach,
24
+ beforeEach,
25
+ describe,
26
+ expect,
27
+ it,
28
+ } from "../../tests/vitest-fixtures";
29
+ import type {
30
+ FrakClient,
31
+ OpenSsoParamsType,
32
+ OpenSsoReturnType,
33
+ } from "../types";
34
+ import { openSso, ssoPopupFeatures, ssoPopupName } from "./openSso";
35
+
36
+ describe("openSso", () => {
37
+ describe("constants", () => {
38
+ it("should have correct popup features", () => {
39
+ expect(ssoPopupFeatures).toBe(
40
+ "menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800"
41
+ );
42
+ });
43
+
44
+ it("should have correct popup name", () => {
45
+ expect(ssoPopupName).toBe("frak-sso");
46
+ });
47
+ });
48
+
49
+ describe("redirect mode", () => {
50
+ it("should use redirect mode when openInSameWindow is true", async () => {
51
+ const mockResponse: OpenSsoReturnType = {
52
+ wallet: "0x1234567890123456789012345678901234567890",
53
+ };
54
+
55
+ const mockClient = {
56
+ config: {
57
+ metadata: { name: "Test App" },
58
+ },
59
+ request: vi.fn().mockResolvedValue(mockResponse),
60
+ } as unknown as FrakClient;
61
+
62
+ const params: OpenSsoParamsType = {
63
+ openInSameWindow: true,
64
+ metadata: {
65
+ logoUrl: "https://example.com/logo.png",
66
+ },
67
+ };
68
+
69
+ const result = await openSso(mockClient, params);
70
+
71
+ expect(mockClient.request).toHaveBeenCalledWith({
72
+ method: "frak_openSso",
73
+ params: [params, "Test App", undefined],
74
+ });
75
+ expect(result).toEqual(mockResponse);
76
+ });
77
+
78
+ it("should use redirect mode when redirectUrl is provided", async () => {
79
+ const mockResponse: OpenSsoReturnType = {
80
+ wallet: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
81
+ };
82
+
83
+ const mockClient = {
84
+ config: {
85
+ metadata: { name: "Test App" },
86
+ },
87
+ request: vi.fn().mockResolvedValue(mockResponse),
88
+ } as unknown as FrakClient;
89
+
90
+ const params: OpenSsoParamsType = {
91
+ redirectUrl: "https://example.com/callback",
92
+ metadata: {
93
+ logoUrl: "https://example.com/logo.png",
94
+ },
95
+ };
96
+
97
+ const result = await openSso(mockClient, params);
98
+
99
+ expect(mockClient.request).toHaveBeenCalledWith({
100
+ method: "frak_openSso",
101
+ params: [params, "Test App", undefined],
102
+ });
103
+ expect(result).toEqual(mockResponse);
104
+ });
105
+
106
+ it("should pass custom CSS in redirect mode", async () => {
107
+ const mockResponse: OpenSsoReturnType = {};
108
+
109
+ const mockClient = {
110
+ config: {
111
+ metadata: { name: "Test App" },
112
+ customizations: {
113
+ css: ":root { --primary: blue; }",
114
+ },
115
+ },
116
+ request: vi.fn().mockResolvedValue(mockResponse),
117
+ } as unknown as FrakClient;
118
+
119
+ const params: OpenSsoParamsType = {
120
+ openInSameWindow: true,
121
+ metadata: {
122
+ logoUrl: "https://example.com/logo.png",
123
+ },
124
+ };
125
+
126
+ await openSso(mockClient, params);
127
+
128
+ expect(mockClient.request).toHaveBeenCalledWith({
129
+ method: "frak_openSso",
130
+ params: [params, "Test App", ":root { --primary: blue; }"],
131
+ });
132
+ });
133
+ });
134
+
135
+ describe("popup mode", () => {
136
+ let windowOpenSpy: any;
137
+ let mockPopup: {
138
+ focus: ReturnType<typeof vi.fn>;
139
+ };
140
+
141
+ beforeEach(() => {
142
+ mockPopup = {
143
+ focus: vi.fn(),
144
+ };
145
+ windowOpenSpy = vi
146
+ .spyOn(window, "open")
147
+ .mockReturnValue(mockPopup as unknown as Window);
148
+ });
149
+
150
+ afterEach(() => {
151
+ windowOpenSpy.mockRestore();
152
+ });
153
+
154
+ it("should open popup with generated URL", async () => {
155
+ const mockResponse: OpenSsoReturnType = {
156
+ wallet: "0x1234567890123456789012345678901234567890",
157
+ };
158
+
159
+ const mockClient = {
160
+ config: {
161
+ metadata: { name: "Test App" },
162
+ walletUrl: "https://wallet.frak.id",
163
+ },
164
+ request: vi.fn().mockResolvedValue(mockResponse),
165
+ } as unknown as FrakClient;
166
+
167
+ const params: OpenSsoParamsType = {
168
+ metadata: {
169
+ logoUrl: "https://example.com/logo.png",
170
+ },
171
+ };
172
+
173
+ await openSso(mockClient, params);
174
+
175
+ expect(window.open).toHaveBeenCalledWith(
176
+ expect.stringContaining("https://wallet.frak.id/sso"),
177
+ "frak-sso",
178
+ "menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800"
179
+ );
180
+ });
181
+
182
+ it("should use custom ssoPopupUrl when provided", async () => {
183
+ const mockResponse: OpenSsoReturnType = {};
184
+
185
+ const mockClient = {
186
+ config: {
187
+ metadata: { name: "Test App" },
188
+ },
189
+ request: vi.fn().mockResolvedValue(mockResponse),
190
+ } as unknown as FrakClient;
191
+
192
+ const params: OpenSsoParamsType = {
193
+ ssoPopupUrl: "https://custom-wallet.com/sso?custom=param",
194
+ metadata: {
195
+ logoUrl: "https://example.com/logo.png",
196
+ },
197
+ };
198
+
199
+ await openSso(mockClient, params);
200
+
201
+ expect(window.open).toHaveBeenCalledWith(
202
+ "https://custom-wallet.com/sso?custom=param",
203
+ "frak-sso",
204
+ "menubar=no,status=no,scrollbars=no,fullscreen=no,width=500, height=800"
205
+ );
206
+ });
207
+
208
+ it("should focus popup after opening", async () => {
209
+ const mockResponse: OpenSsoReturnType = {};
210
+
211
+ const mockClient = {
212
+ config: {
213
+ metadata: { name: "Test App" },
214
+ },
215
+ request: vi.fn().mockResolvedValue(mockResponse),
216
+ } as unknown as FrakClient;
217
+
218
+ const params: OpenSsoParamsType = {
219
+ metadata: {
220
+ logoUrl: "https://example.com/logo.png",
221
+ },
222
+ };
223
+
224
+ await openSso(mockClient, params);
225
+
226
+ expect(mockPopup.focus).toHaveBeenCalled();
227
+ });
228
+
229
+ it("should wait for SSO completion via client.request", async () => {
230
+ const mockResponse: OpenSsoReturnType = {
231
+ wallet: "0x1234567890123456789012345678901234567890",
232
+ };
233
+
234
+ const mockClient = {
235
+ config: {
236
+ metadata: { name: "Test App" },
237
+ },
238
+ request: vi.fn().mockResolvedValue(mockResponse),
239
+ } as unknown as FrakClient;
240
+
241
+ const params: OpenSsoParamsType = {
242
+ metadata: {
243
+ logoUrl: "https://example.com/logo.png",
244
+ },
245
+ };
246
+
247
+ const result = await openSso(mockClient, params);
248
+
249
+ expect(mockClient.request).toHaveBeenCalledWith({
250
+ method: "frak_openSso",
251
+ params: [params, "Test App", undefined],
252
+ });
253
+ expect(result).toEqual(mockResponse);
254
+ });
255
+
256
+ it("should return empty object when result is null", async () => {
257
+ const mockClient = {
258
+ config: {
259
+ metadata: { name: "Test App" },
260
+ },
261
+ request: vi.fn().mockResolvedValue(null),
262
+ } as unknown as FrakClient;
263
+
264
+ const params: OpenSsoParamsType = {
265
+ metadata: {
266
+ logoUrl: "https://example.com/logo.png",
267
+ },
268
+ };
269
+
270
+ const result = await openSso(mockClient, params);
271
+
272
+ expect(result).toEqual({});
273
+ });
274
+
275
+ it("should use default wallet URL when not configured", async () => {
276
+ const mockResponse: OpenSsoReturnType = {};
277
+
278
+ const mockClient = {
279
+ config: {
280
+ metadata: { name: "Test App" },
281
+ },
282
+ request: vi.fn().mockResolvedValue(mockResponse),
283
+ } as unknown as FrakClient;
284
+
285
+ const params: OpenSsoParamsType = {
286
+ metadata: {
287
+ logoUrl: "https://example.com/logo.png",
288
+ },
289
+ };
290
+
291
+ await openSso(mockClient, params);
292
+
293
+ expect(window.open).toHaveBeenCalledWith(
294
+ expect.stringContaining("https://wallet.frak.id/sso"),
295
+ expect.any(String),
296
+ expect.any(String)
297
+ );
298
+ });
299
+ });
300
+
301
+ describe("popup blocker", () => {
302
+ it("should throw error when popup is blocked", async () => {
303
+ vi.spyOn(window, "open").mockReturnValue(null);
304
+
305
+ const mockClient = {
306
+ config: {
307
+ metadata: { name: "Test App" },
308
+ },
309
+ request: vi.fn(),
310
+ } as unknown as FrakClient;
311
+
312
+ const params: OpenSsoParamsType = {
313
+ metadata: {
314
+ logoUrl: "https://example.com/logo.png",
315
+ },
316
+ };
317
+
318
+ await expect(openSso(mockClient, params)).rejects.toThrow(
319
+ "Popup was blocked. Please allow popups for this site."
320
+ );
321
+ });
322
+
323
+ it("should not call client.request when popup is blocked", async () => {
324
+ vi.spyOn(window, "open").mockReturnValue(null);
325
+
326
+ const mockClient = {
327
+ config: {
328
+ metadata: { name: "Test App" },
329
+ },
330
+ request: vi.fn(),
331
+ } as unknown as FrakClient;
332
+
333
+ const params: OpenSsoParamsType = {
334
+ metadata: {
335
+ logoUrl: "https://example.com/logo.png",
336
+ },
337
+ };
338
+
339
+ try {
340
+ await openSso(mockClient, params);
341
+ } catch {
342
+ // Expected error
343
+ }
344
+
345
+ expect(mockClient.request).not.toHaveBeenCalled();
346
+ });
347
+ });
348
+
349
+ describe("mode detection", () => {
350
+ it("should prefer openInSameWindow over redirectUrl", async () => {
351
+ const mockResponse: OpenSsoReturnType = {};
352
+
353
+ const mockClient = {
354
+ config: {
355
+ metadata: { name: "Test App" },
356
+ },
357
+ request: vi.fn().mockResolvedValue(mockResponse),
358
+ } as unknown as FrakClient;
359
+
360
+ const params: OpenSsoParamsType = {
361
+ openInSameWindow: false,
362
+ redirectUrl: "https://example.com/callback",
363
+ metadata: {
364
+ logoUrl: "https://example.com/logo.png",
365
+ },
366
+ };
367
+
368
+ const windowOpenSpy = vi.spyOn(window, "open").mockReturnValue({
369
+ focus: vi.fn(),
370
+ } as unknown as Window);
371
+
372
+ await openSso(mockClient, params);
373
+
374
+ // Should use popup mode because openInSameWindow=false
375
+ expect(window.open).toHaveBeenCalled();
376
+
377
+ windowOpenSpy.mockRestore();
378
+ });
379
+
380
+ it("should use popup mode when neither flag is set", async () => {
381
+ const mockResponse: OpenSsoReturnType = {};
382
+
383
+ const mockClient = {
384
+ config: {
385
+ metadata: { name: "Test App" },
386
+ },
387
+ request: vi.fn().mockResolvedValue(mockResponse),
388
+ } as unknown as FrakClient;
389
+
390
+ const params: OpenSsoParamsType = {
391
+ metadata: {
392
+ logoUrl: "https://example.com/logo.png",
393
+ },
394
+ };
395
+
396
+ const windowOpenSpy = vi.spyOn(window, "open").mockReturnValue({
397
+ focus: vi.fn(),
398
+ } as unknown as Window);
399
+
400
+ await openSso(mockClient, params);
401
+
402
+ expect(window.open).toHaveBeenCalled();
403
+
404
+ windowOpenSpy.mockRestore();
405
+ });
406
+ });
407
+ });