@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.
Files changed (148) 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 -1400
  6. package/dist/actions.d.ts +3 -1400
  7. package/dist/actions.js +1 -1
  8. package/dist/bundle.cjs +1 -13
  9. package/dist/bundle.d.cts +6 -2022
  10. package/dist/bundle.d.ts +6 -2022
  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 -1373
  20. package/dist/index.d.ts +4 -1373
  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 +27 -18
  39. package/src/actions/displayEmbeddedWallet.test.ts +194 -0
  40. package/src/actions/displayEmbeddedWallet.ts +20 -0
  41. package/src/actions/displayModal.test.ts +387 -0
  42. package/src/actions/displayModal.ts +131 -0
  43. package/src/actions/getProductInformation.test.ts +133 -0
  44. package/src/actions/getProductInformation.ts +14 -0
  45. package/src/actions/index.ts +29 -0
  46. package/src/actions/openSso.test.ts +407 -0
  47. package/src/actions/openSso.ts +116 -0
  48. package/src/actions/prepareSso.test.ts +223 -0
  49. package/src/actions/prepareSso.ts +48 -0
  50. package/src/actions/referral/processReferral.test.ts +357 -0
  51. package/src/actions/referral/processReferral.ts +230 -0
  52. package/src/actions/referral/referralInteraction.test.ts +153 -0
  53. package/src/actions/referral/referralInteraction.ts +57 -0
  54. package/src/actions/sendInteraction.test.ts +219 -0
  55. package/src/actions/sendInteraction.ts +32 -0
  56. package/src/actions/trackPurchaseStatus.test.ts +287 -0
  57. package/src/actions/trackPurchaseStatus.ts +53 -0
  58. package/src/actions/watchWalletStatus.test.ts +372 -0
  59. package/src/actions/watchWalletStatus.ts +94 -0
  60. package/src/actions/wrapper/modalBuilder.test.ts +253 -0
  61. package/src/actions/wrapper/modalBuilder.ts +212 -0
  62. package/src/actions/wrapper/sendTransaction.test.ts +164 -0
  63. package/src/actions/wrapper/sendTransaction.ts +62 -0
  64. package/src/actions/wrapper/siweAuthenticate.test.ts +290 -0
  65. package/src/actions/wrapper/siweAuthenticate.ts +94 -0
  66. package/src/bundle.ts +3 -0
  67. package/src/clients/DebugInfo.test.ts +418 -0
  68. package/src/clients/DebugInfo.ts +182 -0
  69. package/src/clients/createIFrameFrakClient.ts +287 -0
  70. package/src/clients/index.ts +3 -0
  71. package/src/clients/setupClient.test.ts +343 -0
  72. package/src/clients/setupClient.ts +73 -0
  73. package/src/clients/transports/iframeLifecycleManager.test.ts +399 -0
  74. package/src/clients/transports/iframeLifecycleManager.ts +90 -0
  75. package/src/constants/interactionTypes.test.ts +128 -0
  76. package/src/constants/interactionTypes.ts +44 -0
  77. package/src/constants/locales.ts +14 -0
  78. package/src/constants/productTypes.test.ts +130 -0
  79. package/src/constants/productTypes.ts +33 -0
  80. package/src/index.ts +101 -0
  81. package/src/interactions/index.ts +5 -0
  82. package/src/interactions/pressEncoder.test.ts +215 -0
  83. package/src/interactions/pressEncoder.ts +53 -0
  84. package/src/interactions/purchaseEncoder.test.ts +291 -0
  85. package/src/interactions/purchaseEncoder.ts +99 -0
  86. package/src/interactions/referralEncoder.test.ts +170 -0
  87. package/src/interactions/referralEncoder.ts +47 -0
  88. package/src/interactions/retailEncoder.test.ts +107 -0
  89. package/src/interactions/retailEncoder.ts +37 -0
  90. package/src/interactions/webshopEncoder.test.ts +56 -0
  91. package/src/interactions/webshopEncoder.ts +30 -0
  92. package/src/types/client.ts +14 -0
  93. package/src/types/compression.ts +22 -0
  94. package/src/types/config.ts +111 -0
  95. package/src/types/context.ts +13 -0
  96. package/src/types/index.ts +71 -0
  97. package/src/types/lifecycle/client.ts +46 -0
  98. package/src/types/lifecycle/iframe.ts +35 -0
  99. package/src/types/lifecycle/index.ts +2 -0
  100. package/src/types/rpc/displayModal.ts +84 -0
  101. package/src/types/rpc/embedded/index.ts +68 -0
  102. package/src/types/rpc/embedded/loggedIn.ts +55 -0
  103. package/src/types/rpc/embedded/loggedOut.ts +28 -0
  104. package/src/types/rpc/interaction.ts +43 -0
  105. package/src/types/rpc/modal/final.ts +46 -0
  106. package/src/types/rpc/modal/generic.ts +46 -0
  107. package/src/types/rpc/modal/index.ts +20 -0
  108. package/src/types/rpc/modal/login.ts +32 -0
  109. package/src/types/rpc/modal/openSession.ts +25 -0
  110. package/src/types/rpc/modal/siweAuthenticate.ts +37 -0
  111. package/src/types/rpc/modal/transaction.ts +33 -0
  112. package/src/types/rpc/productInformation.ts +59 -0
  113. package/src/types/rpc/sso.ts +80 -0
  114. package/src/types/rpc/walletStatus.ts +35 -0
  115. package/src/types/rpc.ts +158 -0
  116. package/src/types/transport.ts +34 -0
  117. package/src/utils/FrakContext.test.ts +407 -0
  118. package/src/utils/FrakContext.ts +158 -0
  119. package/src/utils/compression/b64.test.ts +181 -0
  120. package/src/utils/compression/b64.ts +29 -0
  121. package/src/utils/compression/compress.test.ts +123 -0
  122. package/src/utils/compression/compress.ts +11 -0
  123. package/src/utils/compression/decompress.test.ts +145 -0
  124. package/src/utils/compression/decompress.ts +11 -0
  125. package/src/utils/compression/index.ts +3 -0
  126. package/src/utils/computeProductId.test.ts +80 -0
  127. package/src/utils/computeProductId.ts +11 -0
  128. package/src/utils/constants.test.ts +23 -0
  129. package/src/utils/constants.ts +4 -0
  130. package/src/utils/formatAmount.test.ts +113 -0
  131. package/src/utils/formatAmount.ts +18 -0
  132. package/src/utils/getCurrencyAmountKey.test.ts +44 -0
  133. package/src/utils/getCurrencyAmountKey.ts +15 -0
  134. package/src/utils/getSupportedCurrency.test.ts +51 -0
  135. package/src/utils/getSupportedCurrency.ts +14 -0
  136. package/src/utils/getSupportedLocale.test.ts +64 -0
  137. package/src/utils/getSupportedLocale.ts +16 -0
  138. package/src/utils/iframeHelper.test.ts +450 -0
  139. package/src/utils/iframeHelper.ts +143 -0
  140. package/src/utils/index.ts +21 -0
  141. package/src/utils/sso.test.ts +361 -0
  142. package/src/utils/sso.ts +119 -0
  143. package/src/utils/ssoUrlListener.test.ts +252 -0
  144. package/src/utils/ssoUrlListener.ts +60 -0
  145. package/src/utils/trackEvent.test.ts +162 -0
  146. package/src/utils/trackEvent.ts +26 -0
  147. package/cdn/bundle.js +0 -19
  148. package/cdn/bundle.js.LICENSE.txt +0 -10
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Tests for DebugInfoGatherer class
3
+ * Tests debug information gathering and formatting for error reporting
4
+ */
5
+
6
+ import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
7
+ import {
8
+ afterEach,
9
+ beforeEach,
10
+ describe,
11
+ expect,
12
+ it,
13
+ vi,
14
+ } from "../../tests/vitest-fixtures";
15
+ import type { FrakWalletSdkConfig } from "../types";
16
+ import { DebugInfoGatherer } from "./DebugInfo";
17
+
18
+ describe("DebugInfoGatherer", () => {
19
+ let mockConfig: FrakWalletSdkConfig;
20
+ let mockIframe: HTMLIFrameElement;
21
+ let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
22
+
23
+ beforeEach(() => {
24
+ // Create mock config
25
+ mockConfig = {
26
+ metadata: {
27
+ name: "Test App",
28
+ },
29
+ };
30
+
31
+ // Create mock iframe
32
+ mockIframe = document.createElement("iframe");
33
+ mockIframe.src = "https://wallet.frak.id";
34
+ mockIframe.setAttribute("loading", "lazy");
35
+ document.body.appendChild(mockIframe);
36
+
37
+ // Mock iframe contentDocument
38
+ Object.defineProperty(mockIframe, "contentDocument", {
39
+ value: {
40
+ readyState: "complete",
41
+ },
42
+ writable: true,
43
+ });
44
+
45
+ // Mock iframe contentWindow
46
+ Object.defineProperty(mockIframe, "contentWindow", {
47
+ value: {},
48
+ writable: true,
49
+ });
50
+
51
+ // Spy on console.warn
52
+ consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
53
+ });
54
+
55
+ afterEach(() => {
56
+ consoleWarnSpy.mockRestore();
57
+ document.body.removeChild(mockIframe);
58
+ vi.clearAllMocks();
59
+ });
60
+
61
+ describe("constructor", () => {
62
+ it("should create instance with config and iframe", () => {
63
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
64
+
65
+ expect(gatherer).toBeInstanceOf(DebugInfoGatherer);
66
+ });
67
+
68
+ it("should create instance without config and iframe", () => {
69
+ const gatherer = new DebugInfoGatherer();
70
+
71
+ expect(gatherer).toBeInstanceOf(DebugInfoGatherer);
72
+ });
73
+
74
+ it("should initialize with null lastRequest and lastResponse", () => {
75
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
76
+
77
+ // Access private properties through formatDebugInfo output
78
+ const debugInfo = gatherer.formatDebugInfo("test error");
79
+ expect(debugInfo).toContain("No Frak request logged");
80
+ expect(debugInfo).toContain("No Frak response logged");
81
+ });
82
+ });
83
+
84
+ describe("static empty()", () => {
85
+ it("should create empty instance", () => {
86
+ const gatherer = DebugInfoGatherer.empty();
87
+
88
+ expect(gatherer).toBeInstanceOf(DebugInfoGatherer);
89
+ });
90
+
91
+ it("should create instance without config or iframe", () => {
92
+ const gatherer = DebugInfoGatherer.empty();
93
+ const debugInfo = gatherer.formatDebugInfo("test error");
94
+
95
+ expect(debugInfo).toContain("no-config");
96
+ expect(debugInfo).toContain("not-iframe");
97
+ });
98
+ });
99
+
100
+ describe("setLastRequest", () => {
101
+ it("should set last request with timestamp", () => {
102
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
103
+ const mockMessage = {
104
+ id: 1,
105
+ method: "test_method",
106
+ params: [],
107
+ } as any;
108
+
109
+ gatherer.setLastRequest(mockMessage);
110
+
111
+ const debugInfo = gatherer.formatDebugInfo("test error");
112
+ expect(debugInfo).toContain("Last Request:");
113
+ expect(debugInfo).not.toContain("No Frak request logged");
114
+ });
115
+ });
116
+
117
+ describe("setLastResponse", () => {
118
+ it("should set last response with timestamp", () => {
119
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
120
+ const mockMessage = {
121
+ id: 1,
122
+ method: "test_method",
123
+ } as any;
124
+ const mockResponse = {
125
+ id: 1,
126
+ result: "success",
127
+ } as any;
128
+
129
+ gatherer.setLastResponse(mockMessage, mockResponse);
130
+
131
+ const debugInfo = gatherer.formatDebugInfo("test error");
132
+ expect(debugInfo).toContain("Last Response:");
133
+ expect(debugInfo).not.toContain("No Frak response logged");
134
+ });
135
+ });
136
+
137
+ describe("updateSetupStatus", () => {
138
+ it("should update setup status to true", () => {
139
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
140
+
141
+ gatherer.updateSetupStatus(true);
142
+
143
+ const debugInfo = gatherer.formatDebugInfo("test error");
144
+ expect(debugInfo).toContain("Client Status: setup");
145
+ });
146
+
147
+ it("should update setup status to false", () => {
148
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
149
+
150
+ gatherer.updateSetupStatus(false);
151
+
152
+ const debugInfo = gatherer.formatDebugInfo("test error");
153
+ expect(debugInfo).toContain("Client Status: not-setup");
154
+ });
155
+ });
156
+
157
+ describe("formatDebugInfo", () => {
158
+ it("should format debug info with all fields", () => {
159
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
160
+
161
+ const debugInfo = gatherer.formatDebugInfo("test error");
162
+
163
+ expect(debugInfo).toContain("Debug Information:");
164
+ expect(debugInfo).toContain("Timestamp:");
165
+ expect(debugInfo).toContain("URL:");
166
+ expect(debugInfo).toContain("Config:");
167
+ expect(debugInfo).toContain("Navigator Info:");
168
+ expect(debugInfo).toContain("IFrame Status:");
169
+ expect(debugInfo).toContain("Last Request:");
170
+ expect(debugInfo).toContain("Last Response:");
171
+ expect(debugInfo).toContain("Client Status:");
172
+ expect(debugInfo).toContain("Error:");
173
+ });
174
+
175
+ it("should format FrakRpcError correctly", () => {
176
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
177
+ const error = new FrakRpcError(
178
+ RpcErrorCodes.walletNotConnected,
179
+ "Wallet not connected"
180
+ );
181
+
182
+ const debugInfo = gatherer.formatDebugInfo(error);
183
+
184
+ expect(debugInfo).toContain(
185
+ `FrakRpcError: ${RpcErrorCodes.walletNotConnected} 'Wallet not connected'`
186
+ );
187
+ });
188
+
189
+ it("should format regular Error correctly", () => {
190
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
191
+ const error = new Error("Network timeout");
192
+
193
+ const debugInfo = gatherer.formatDebugInfo(error);
194
+
195
+ expect(debugInfo).toContain("Network timeout");
196
+ });
197
+
198
+ it("should format string error correctly", () => {
199
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
200
+
201
+ const debugInfo = gatherer.formatDebugInfo("String error message");
202
+
203
+ expect(debugInfo).toContain("String error message");
204
+ });
205
+
206
+ it("should format unknown error as 'Unknown'", () => {
207
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
208
+
209
+ const debugInfo = gatherer.formatDebugInfo(null);
210
+
211
+ expect(debugInfo).toContain("Error: Unknown");
212
+ });
213
+
214
+ it("should include encoded URL", () => {
215
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
216
+
217
+ const debugInfo = gatherer.formatDebugInfo("test error");
218
+
219
+ // URL should be base64 encoded
220
+ expect(debugInfo).toContain("URL:");
221
+ const urlMatch = debugInfo.match(/URL: (.+)/);
222
+ expect(urlMatch).toBeTruthy();
223
+ if (urlMatch) {
224
+ // Should be base64 encoded (alphanumeric + =)
225
+ expect(urlMatch[1]).toMatch(/^[A-Za-z0-9+/=]+$/);
226
+ }
227
+ });
228
+
229
+ it("should include encoded config when config is provided", () => {
230
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
231
+
232
+ const debugInfo = gatherer.formatDebugInfo("test error");
233
+
234
+ expect(debugInfo).toContain("Config:");
235
+ expect(debugInfo).not.toContain("no-config");
236
+ });
237
+
238
+ it("should show 'no-config' when config is not provided", () => {
239
+ const gatherer = new DebugInfoGatherer(undefined, mockIframe);
240
+
241
+ const debugInfo = gatherer.formatDebugInfo("test error");
242
+
243
+ expect(debugInfo).toContain("Config: no-config");
244
+ });
245
+
246
+ it("should include iframe status when iframe is provided", () => {
247
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
248
+
249
+ const debugInfo = gatherer.formatDebugInfo("test error");
250
+
251
+ expect(debugInfo).toContain("IFrame Status:");
252
+ expect(debugInfo).not.toContain("not-iframe");
253
+ });
254
+
255
+ it("should show 'not-iframe' when iframe is not provided", () => {
256
+ const gatherer = new DebugInfoGatherer(mockConfig, undefined);
257
+
258
+ const debugInfo = gatherer.formatDebugInfo("test error");
259
+
260
+ expect(debugInfo).toContain("IFrame Status: not-iframe");
261
+ });
262
+
263
+ it("should include navigator info", () => {
264
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
265
+
266
+ const debugInfo = gatherer.formatDebugInfo("test error");
267
+
268
+ expect(debugInfo).toContain("Navigator Info:");
269
+ expect(debugInfo).not.toContain("no-navigator");
270
+ });
271
+
272
+ it("should handle iframe without contentDocument", () => {
273
+ const iframeWithoutDoc = document.createElement("iframe");
274
+ Object.defineProperty(iframeWithoutDoc, "contentDocument", {
275
+ value: null,
276
+ writable: true,
277
+ });
278
+ Object.defineProperty(iframeWithoutDoc, "contentWindow", {
279
+ value: {},
280
+ writable: true,
281
+ });
282
+
283
+ const gatherer = new DebugInfoGatherer(
284
+ mockConfig,
285
+ iframeWithoutDoc
286
+ );
287
+
288
+ const debugInfo = gatherer.formatDebugInfo("test error");
289
+
290
+ expect(debugInfo).toContain("IFrame Status:");
291
+ });
292
+
293
+ it("should handle iframe with incomplete readyState", () => {
294
+ const iframeIncomplete = document.createElement("iframe");
295
+ Object.defineProperty(iframeIncomplete, "contentDocument", {
296
+ value: {
297
+ readyState: "loading",
298
+ },
299
+ writable: true,
300
+ });
301
+ Object.defineProperty(iframeIncomplete, "contentWindow", {
302
+ value: {},
303
+ writable: true,
304
+ });
305
+
306
+ const gatherer = new DebugInfoGatherer(
307
+ mockConfig,
308
+ iframeIncomplete
309
+ );
310
+
311
+ const debugInfo = gatherer.formatDebugInfo("test error");
312
+
313
+ expect(debugInfo).toContain("IFrame Status:");
314
+ });
315
+
316
+ it("should include last request when set", () => {
317
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
318
+ const mockMessage = {
319
+ id: 1,
320
+ method: "test_method",
321
+ params: [],
322
+ } as any;
323
+
324
+ gatherer.setLastRequest(mockMessage);
325
+ const debugInfo = gatherer.formatDebugInfo("test error");
326
+
327
+ expect(debugInfo).toContain("Last Request:");
328
+ expect(debugInfo).not.toContain("No Frak request logged");
329
+ });
330
+
331
+ it("should include last response when set", () => {
332
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
333
+ const mockMessage = {
334
+ id: 1,
335
+ method: "test_method",
336
+ } as any;
337
+ const mockResponse = {
338
+ id: 1,
339
+ result: "success",
340
+ } as any;
341
+
342
+ gatherer.setLastResponse(mockMessage, mockResponse);
343
+ const debugInfo = gatherer.formatDebugInfo("test error");
344
+
345
+ expect(debugInfo).toContain("Last Response:");
346
+ expect(debugInfo).not.toContain("No Frak response logged");
347
+ });
348
+
349
+ it("should show 'No Frak request logged' when no request is set", () => {
350
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
351
+
352
+ const debugInfo = gatherer.formatDebugInfo("test error");
353
+
354
+ expect(debugInfo).toContain("No Frak request logged");
355
+ });
356
+
357
+ it("should show 'No Frak response logged' when no response is set", () => {
358
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
359
+
360
+ const debugInfo = gatherer.formatDebugInfo("test error");
361
+
362
+ expect(debugInfo).toContain("No Frak response logged");
363
+ });
364
+ });
365
+
366
+ describe("base64 encoding error handling", () => {
367
+ it("should handle encoding errors gracefully", () => {
368
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
369
+
370
+ // Create a circular reference that can't be JSON stringified
371
+ const circularObj: any = { prop: "value" };
372
+ circularObj.self = circularObj;
373
+
374
+ // Mock base64Encode to throw an error
375
+ // We can't directly test the private method, but we can test
376
+ // that formatDebugInfo doesn't throw when encoding fails
377
+ // by checking that it still produces output
378
+
379
+ const debugInfo = gatherer.formatDebugInfo("test error");
380
+
381
+ // Should still produce debug info even if encoding fails
382
+ expect(debugInfo).toContain("Debug Information:");
383
+ });
384
+ });
385
+
386
+ describe("integration", () => {
387
+ it("should format complete debug info with all data set", () => {
388
+ const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
389
+ const mockRequest = {
390
+ id: 1,
391
+ method: "frak_sendInteraction",
392
+ params: [],
393
+ } as any;
394
+ const mockResponse = {
395
+ id: 1,
396
+ result: { delegationId: "123" },
397
+ } as any;
398
+ const error = new FrakRpcError(
399
+ RpcErrorCodes.serverError,
400
+ "Server error"
401
+ );
402
+
403
+ gatherer.setLastRequest(mockRequest);
404
+ gatherer.setLastResponse(mockRequest, mockResponse);
405
+ gatherer.updateSetupStatus(true);
406
+
407
+ const debugInfo = gatherer.formatDebugInfo(error);
408
+
409
+ expect(debugInfo).toContain("Debug Information:");
410
+ expect(debugInfo).toContain("Client Status: setup");
411
+ expect(debugInfo).toContain(
412
+ `FrakRpcError: ${RpcErrorCodes.serverError} 'Server error'`
413
+ );
414
+ expect(debugInfo).not.toContain("No Frak request logged");
415
+ expect(debugInfo).not.toContain("No Frak response logged");
416
+ });
417
+ });
418
+ });
@@ -0,0 +1,182 @@
1
+ import {
2
+ FrakRpcError,
3
+ type RpcMessage,
4
+ type RpcResponse,
5
+ } from "@frak-labs/frame-connector";
6
+ import type { FrakWalletSdkConfig } from "../types";
7
+
8
+ type IframeStatus = {
9
+ loading: boolean;
10
+ url: string | null;
11
+ readyState: number;
12
+ contentWindow: boolean;
13
+ isConnected: boolean;
14
+ };
15
+
16
+ type DebugInfo = {
17
+ timestamp: string;
18
+ encodedUrl: string;
19
+ navigatorInfo: string;
20
+ encodedConfig: string;
21
+ iframeStatus: string;
22
+ lastRequest: string;
23
+ lastResponse: string;
24
+ clientStatus: string;
25
+ error: string;
26
+ };
27
+
28
+ type NavigatorInfo = {
29
+ userAgent: string;
30
+ language: string;
31
+ onLine: boolean;
32
+ screenWidth: number;
33
+ screenHeight: number;
34
+ pixelRatio: number;
35
+ };
36
+
37
+ /** @ignore */
38
+ export class DebugInfoGatherer {
39
+ private config?: FrakWalletSdkConfig;
40
+ private iframe?: HTMLIFrameElement;
41
+ private isSetupDone = false;
42
+ private lastResponse: null | {
43
+ message: RpcMessage;
44
+ response: RpcResponse;
45
+ timestamp: number;
46
+ } = null;
47
+ private lastRequest: null | {
48
+ event: RpcMessage;
49
+ timestamp: number;
50
+ } = null;
51
+
52
+ constructor(config?: FrakWalletSdkConfig, iframe?: HTMLIFrameElement) {
53
+ this.config = config;
54
+ this.iframe = iframe;
55
+ this.lastRequest = null;
56
+ this.lastResponse = null;
57
+ }
58
+
59
+ // Update communication logs
60
+ public setLastResponse(message: RpcMessage, response: RpcResponse) {
61
+ this.lastResponse = {
62
+ message,
63
+ response,
64
+ timestamp: Date.now(),
65
+ };
66
+ }
67
+ public setLastRequest(event: RpcMessage) {
68
+ this.lastRequest = { event, timestamp: Date.now() };
69
+ }
70
+
71
+ // Update connection status
72
+ public updateSetupStatus(status: boolean) {
73
+ this.isSetupDone = status;
74
+ }
75
+
76
+ private base64Encode(data: object): string {
77
+ try {
78
+ return btoa(JSON.stringify(data));
79
+ } catch (err) {
80
+ console.warn("Failed to encode debug data", err);
81
+ return btoa("Failed to encode data");
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Extract information from the iframe status
87
+ */
88
+ private getIframeStatus(): IframeStatus | null {
89
+ if (!this.iframe) {
90
+ return null;
91
+ }
92
+ return {
93
+ loading: this.iframe.hasAttribute("loading"),
94
+ url: this.iframe.src,
95
+ readyState: this.iframe.contentDocument?.readyState
96
+ ? this.iframe.contentDocument.readyState === "complete"
97
+ ? 1
98
+ : 0
99
+ : -1,
100
+ contentWindow: !!this.iframe.contentWindow,
101
+ isConnected: this.iframe.isConnected,
102
+ };
103
+ }
104
+
105
+ private getNavigatorInfo(): NavigatorInfo | null {
106
+ if (!navigator) {
107
+ return null;
108
+ }
109
+ return {
110
+ userAgent: navigator.userAgent,
111
+ language: navigator.language,
112
+ onLine: navigator.onLine,
113
+ screenWidth: window.screen.width,
114
+ screenHeight: window.screen.height,
115
+ pixelRatio: window.devicePixelRatio,
116
+ };
117
+ }
118
+
119
+ private gatherDebugInfo(error: Error | unknown): DebugInfo {
120
+ const iframeStatus = this.getIframeStatus();
121
+ const navigatorInfo = this.getNavigatorInfo();
122
+
123
+ // Format the error in a readable format
124
+ let formattedError = "Unknown";
125
+ if (error instanceof FrakRpcError) {
126
+ formattedError = `FrakRpcError: ${error.code} '${error.message}'`;
127
+ } else if (error instanceof Error) {
128
+ formattedError = error.message;
129
+ } else if (typeof error === "string") {
130
+ formattedError = error;
131
+ }
132
+
133
+ // Craft the debug info
134
+ const debugInfo: DebugInfo = {
135
+ timestamp: new Date().toISOString(),
136
+ encodedUrl: btoa(window.location.href),
137
+ encodedConfig: this.config
138
+ ? this.base64Encode(this.config)
139
+ : "no-config",
140
+ navigatorInfo: navigatorInfo
141
+ ? this.base64Encode(navigatorInfo)
142
+ : "no-navigator",
143
+ iframeStatus: iframeStatus
144
+ ? this.base64Encode(iframeStatus)
145
+ : "not-iframe",
146
+ lastRequest: this.lastRequest
147
+ ? this.base64Encode(this.lastRequest)
148
+ : "No Frak request logged",
149
+ lastResponse: this.lastResponse
150
+ ? this.base64Encode(this.lastResponse)
151
+ : "No Frak response logged",
152
+ clientStatus: this.isSetupDone ? "setup" : "not-setup",
153
+ error: formattedError,
154
+ };
155
+
156
+ return debugInfo;
157
+ }
158
+
159
+ public static empty(): DebugInfoGatherer {
160
+ return new DebugInfoGatherer();
161
+ }
162
+
163
+ /**
164
+ * Format Frak debug information
165
+ */
166
+ public formatDebugInfo(error: Error | unknown | string): string {
167
+ const debugInfo = this.gatherDebugInfo(error);
168
+ return `
169
+ Debug Information:
170
+ -----------------
171
+ Timestamp: ${debugInfo.timestamp}
172
+ URL: ${debugInfo.encodedUrl}
173
+ Config: ${debugInfo.encodedConfig}
174
+ Navigator Info: ${debugInfo.navigatorInfo}
175
+ IFrame Status: ${debugInfo.iframeStatus}
176
+ Last Request: ${debugInfo.lastRequest}
177
+ Last Response: ${debugInfo.lastResponse}
178
+ Client Status: ${debugInfo.clientStatus}
179
+ Error: ${debugInfo.error}
180
+ `.trim();
181
+ }
182
+ }