@frak-labs/core-sdk 0.1.0-beta.8d103039 → 0.1.0-beta.b0bd1f8a

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 (90) hide show
  1. package/cdn/bundle.iife.js +14 -0
  2. package/dist/actions-CEEObPYc.js +1 -0
  3. package/dist/actions-DbQhWYx8.cjs +1 -0
  4. package/dist/actions.cjs +1 -1
  5. package/dist/actions.d.cts +3 -1481
  6. package/dist/actions.d.ts +3 -1481
  7. package/dist/actions.js +1 -1
  8. package/dist/bundle.cjs +1 -13
  9. package/dist/bundle.d.cts +6 -2087
  10. package/dist/bundle.d.ts +6 -2087
  11. package/dist/bundle.js +1 -13
  12. package/dist/index-7OZ39x1U.d.ts +195 -0
  13. package/dist/index-C6FxkWPC.d.cts +511 -0
  14. package/dist/index-UFX7xCg3.d.ts +351 -0
  15. package/dist/index-d8xS4ryI.d.ts +511 -0
  16. package/dist/index-p4FqSp8z.d.cts +351 -0
  17. package/dist/index-zDq-VlKx.d.cts +195 -0
  18. package/dist/index.cjs +1 -13
  19. package/dist/index.d.cts +4 -1387
  20. package/dist/index.d.ts +4 -1387
  21. package/dist/index.js +1 -13
  22. package/dist/interaction-DMJ3ZfaF.d.cts +45 -0
  23. package/dist/interaction-KX1h9a7V.d.ts +45 -0
  24. package/dist/interactions-DnfM3oe0.js +1 -0
  25. package/dist/interactions-EIXhNLf6.cjs +1 -0
  26. package/dist/interactions.cjs +1 -1
  27. package/dist/interactions.d.cts +2 -182
  28. package/dist/interactions.d.ts +2 -182
  29. package/dist/interactions.js +1 -1
  30. package/dist/openSso-D--Airj6.d.cts +1018 -0
  31. package/dist/openSso-DsKJ4y0j.d.ts +1018 -0
  32. package/dist/productTypes-BUkXJKZ7.cjs +1 -0
  33. package/dist/productTypes-CGb1MmBF.js +1 -0
  34. package/dist/src-B_xO0AR6.cjs +13 -0
  35. package/dist/src-D2d52OZa.js +13 -0
  36. package/dist/trackEvent-CHnYa85W.js +1 -0
  37. package/dist/trackEvent-GuQm_1Nm.cjs +1 -0
  38. package/package.json +21 -17
  39. package/src/actions/displayEmbeddedWallet.test.ts +194 -0
  40. package/src/actions/displayModal.test.ts +387 -0
  41. package/src/actions/getProductInformation.test.ts +133 -0
  42. package/src/actions/index.ts +19 -19
  43. package/src/actions/openSso.test.ts +407 -0
  44. package/src/actions/prepareSso.test.ts +223 -0
  45. package/src/actions/referral/processReferral.ts +1 -1
  46. package/src/actions/referral/referralInteraction.ts +1 -1
  47. package/src/actions/sendInteraction.test.ts +219 -0
  48. package/src/actions/trackPurchaseStatus.test.ts +287 -0
  49. package/src/actions/watchWalletStatus.test.ts +372 -0
  50. package/src/bundle.ts +1 -1
  51. package/src/clients/createIFrameFrakClient.ts +2 -2
  52. package/src/clients/index.ts +1 -1
  53. package/src/clients/setupClient.test.ts +343 -0
  54. package/src/clients/setupClient.ts +3 -1
  55. package/src/clients/transports/iframeLifecycleManager.test.ts +399 -0
  56. package/src/clients/transports/iframeLifecycleManager.ts +3 -1
  57. package/src/index.ts +72 -74
  58. package/src/interactions/index.ts +2 -2
  59. package/src/interactions/pressEncoder.test.ts +215 -0
  60. package/src/interactions/pressEncoder.ts +1 -1
  61. package/src/interactions/purchaseEncoder.test.ts +291 -0
  62. package/src/interactions/purchaseEncoder.ts +8 -3
  63. package/src/interactions/referralEncoder.test.ts +170 -0
  64. package/src/interactions/retailEncoder.test.ts +107 -0
  65. package/src/interactions/retailEncoder.ts +1 -1
  66. package/src/interactions/webshopEncoder.test.ts +56 -0
  67. package/src/types/index.ts +51 -50
  68. package/src/types/lifecycle/index.ts +1 -1
  69. package/src/types/rpc/embedded/loggedIn.ts +1 -1
  70. package/src/types/rpc/embedded/loggedOut.ts +1 -1
  71. package/src/types/rpc/modal/index.ts +11 -11
  72. package/src/utils/FrakContext.test.ts +407 -0
  73. package/src/utils/FrakContext.ts +8 -2
  74. package/src/utils/compression/b64.test.ts +181 -0
  75. package/src/utils/compression/compress.test.ts +123 -0
  76. package/src/utils/compression/decompress.test.ts +145 -0
  77. package/src/utils/compression/index.ts +1 -1
  78. package/src/utils/computeProductId.test.ts +80 -0
  79. package/src/utils/constants.test.ts +23 -0
  80. package/src/utils/formatAmount.test.ts +113 -0
  81. package/src/utils/getCurrencyAmountKey.test.ts +44 -0
  82. package/src/utils/getSupportedCurrency.test.ts +51 -0
  83. package/src/utils/getSupportedLocale.test.ts +64 -0
  84. package/src/utils/iframeHelper.test.ts +450 -0
  85. package/src/utils/iframeHelper.ts +4 -3
  86. package/src/utils/index.ts +12 -12
  87. package/src/utils/sso.test.ts +361 -0
  88. package/src/utils/trackEvent.test.ts +162 -0
  89. package/cdn/bundle.js +0 -19
  90. package/cdn/bundle.js.LICENSE.txt +0 -10
@@ -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
+ });
@@ -67,7 +67,10 @@ function parse({ url }: { url: string }) {
67
67
  function update({
68
68
  url,
69
69
  context,
70
- }: { url?: string; context: Partial<FrakContext> }) {
70
+ }: {
71
+ url?: string;
72
+ context: Partial<FrakContext>;
73
+ }) {
71
74
  if (!url) return null;
72
75
 
73
76
  // Parse the current context
@@ -111,7 +114,10 @@ function remove(url: string) {
111
114
  function replaceUrl({
112
115
  url: baseUrl,
113
116
  context,
114
- }: { url?: string; context: Partial<FrakContext> | null }) {
117
+ }: {
118
+ url?: string;
119
+ context: Partial<FrakContext> | null;
120
+ }) {
115
121
  // If no window here early exit
116
122
  if (!window.location?.href || typeof window === "undefined") {
117
123
  console.error("No window found, can't update context");
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Tests for base64url encoding and decoding utilities
3
+ * Tests encoding, decoding, and round-trip operations
4
+ */
5
+
6
+ import { describe, expect, it, test } from "../../../tests/vitest-fixtures";
7
+ import { base64urlDecode, base64urlEncode } from "./b64";
8
+
9
+ describe("base64urlEncode", () => {
10
+ test("should encode empty Uint8Array", ({ mockUint8Arrays }) => {
11
+ const result = base64urlEncode(mockUint8Arrays.empty);
12
+
13
+ expect(result).toBe("");
14
+ });
15
+
16
+ test("should encode simple Uint8Array", ({ mockUint8Arrays }) => {
17
+ const result = base64urlEncode(mockUint8Arrays.simple);
18
+
19
+ // "Hello" should encode to "SGVsbG8"
20
+ expect(result).toBe("SGVsbG8");
21
+ });
22
+
23
+ test("should replace + with - for URL safety", () => {
24
+ // Create data that would produce + in standard base64
25
+ const data = new Uint8Array([0xfb, 0xff]);
26
+ const result = base64urlEncode(data);
27
+
28
+ // Should not contain +
29
+ expect(result).not.toContain("+");
30
+ expect(result).toContain("-");
31
+ });
32
+
33
+ test("should replace / with _ for URL safety", () => {
34
+ // Create data that would produce / in standard base64
35
+ const data = new Uint8Array([0xff, 0xff]);
36
+ const result = base64urlEncode(data);
37
+
38
+ // Should not contain /
39
+ expect(result).not.toContain("/");
40
+ expect(result).toContain("_");
41
+ });
42
+
43
+ test("should remove padding =", () => {
44
+ // Create data that would have padding
45
+ const data = new Uint8Array([72]); // "H" -> "SA==" in standard base64
46
+ const result = base64urlEncode(data);
47
+
48
+ // Should not contain =
49
+ expect(result).not.toContain("=");
50
+ expect(result).toBe("SA");
51
+ });
52
+
53
+ test("should handle various byte lengths", () => {
54
+ const lengths = [1, 2, 3, 4, 5, 10, 20];
55
+
56
+ for (const length of lengths) {
57
+ const data = new Uint8Array(length).fill(65); // Fill with 'A' character code
58
+ const result = base64urlEncode(data);
59
+
60
+ // Should produce a string
61
+ expect(typeof result).toBe("string");
62
+ expect(result.length).toBeGreaterThan(0);
63
+ }
64
+ });
65
+
66
+ test("should handle complex byte array", ({ mockUint8Arrays }) => {
67
+ const result = base64urlEncode(mockUint8Arrays.complex);
68
+
69
+ // Should produce valid base64url string
70
+ expect(result).toMatch(/^[A-Za-z0-9_-]*$/);
71
+ });
72
+ });
73
+
74
+ describe("base64urlDecode", () => {
75
+ test("should decode empty string", ({ mockBase64Strings }) => {
76
+ const result = base64urlDecode(mockBase64Strings.empty);
77
+
78
+ expect(result).toEqual(new Uint8Array([]));
79
+ expect(result.length).toBe(0);
80
+ });
81
+
82
+ test("should decode simple base64url string", ({ mockBase64Strings }) => {
83
+ const result = base64urlDecode(mockBase64Strings.simple);
84
+
85
+ // "SGVsbG8" should decode to "Hello"
86
+ const expected = new Uint8Array([72, 101, 108, 108, 111]);
87
+ expect(result).toEqual(expected);
88
+ });
89
+
90
+ test("should handle strings without padding", () => {
91
+ // Base64url strings don't have padding
92
+ const encoded = "SGVsbG8"; // "Hello" without padding
93
+ const result = base64urlDecode(encoded);
94
+
95
+ const expected = new Uint8Array([72, 101, 108, 108, 111]);
96
+ expect(result).toEqual(expected);
97
+ });
98
+
99
+ test("should reverse URL-safe character replacements", ({
100
+ mockBase64Strings,
101
+ }) => {
102
+ const result = base64urlDecode(mockBase64Strings.withSpecialChars);
103
+
104
+ // Should handle - and _ characters
105
+ expect(result).toBeInstanceOf(Uint8Array);
106
+ });
107
+
108
+ test("should handle various valid base64url string lengths", () => {
109
+ // Use valid base64url strings
110
+ const testStrings = ["QQ", "QUI", "QUJD", "QUJDRA"]; // "A", "AB", "ABC", "ABCD" encoded
111
+
112
+ for (const str of testStrings) {
113
+ const result = base64urlDecode(str);
114
+
115
+ // Should produce Uint8Array
116
+ expect(result).toBeInstanceOf(Uint8Array);
117
+ }
118
+ });
119
+ });
120
+
121
+ describe("base64url round-trip", () => {
122
+ test("should successfully round-trip empty data", ({ mockUint8Arrays }) => {
123
+ const encoded = base64urlEncode(mockUint8Arrays.empty);
124
+ const decoded = base64urlDecode(encoded);
125
+
126
+ expect(decoded).toEqual(mockUint8Arrays.empty);
127
+ });
128
+
129
+ test("should successfully round-trip simple data", ({
130
+ mockUint8Arrays,
131
+ }) => {
132
+ const encoded = base64urlEncode(mockUint8Arrays.simple);
133
+ const decoded = base64urlDecode(encoded);
134
+
135
+ expect(decoded).toEqual(mockUint8Arrays.simple);
136
+ });
137
+
138
+ test("should successfully round-trip complex data", ({
139
+ mockUint8Arrays,
140
+ }) => {
141
+ const encoded = base64urlEncode(mockUint8Arrays.complex);
142
+ const decoded = base64urlDecode(encoded);
143
+
144
+ expect(decoded).toEqual(mockUint8Arrays.complex);
145
+ });
146
+
147
+ it("should handle all byte values (0-255)", () => {
148
+ // Test with all possible byte values
149
+ const allBytes = new Uint8Array(256);
150
+ for (let i = 0; i < 256; i++) {
151
+ allBytes[i] = i;
152
+ }
153
+
154
+ const encoded = base64urlEncode(allBytes);
155
+ const decoded = base64urlDecode(encoded);
156
+
157
+ expect(decoded).toEqual(allBytes);
158
+ });
159
+
160
+ it("should preserve binary data integrity", () => {
161
+ const binaryData = new Uint8Array([0, 1, 127, 128, 255, 254, 100, 200]);
162
+
163
+ const encoded = base64urlEncode(binaryData);
164
+ const decoded = base64urlDecode(encoded);
165
+
166
+ expect(decoded).toEqual(binaryData);
167
+ });
168
+
169
+ it("should handle random data correctly", () => {
170
+ // Generate some pseudo-random data
171
+ const randomData = new Uint8Array(32);
172
+ for (let i = 0; i < 32; i++) {
173
+ randomData[i] = Math.floor(Math.random() * 256);
174
+ }
175
+
176
+ const encoded = base64urlEncode(randomData);
177
+ const decoded = base64urlDecode(encoded);
178
+
179
+ expect(decoded).toEqual(randomData);
180
+ });
181
+ });