@squasher-ai/browser 0.1.1 → 0.2.0

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 (98) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -41
  3. package/dist/__tests__/client/harness.d.ts +18 -0
  4. package/dist/__tests__/client/harness.d.ts.map +1 -0
  5. package/dist/__tests__/client/harness.js +106 -0
  6. package/dist/__tests__/replay/coverage-harness.d.ts +29 -0
  7. package/dist/__tests__/replay/coverage-harness.d.ts.map +1 -0
  8. package/dist/__tests__/replay/coverage-harness.js +123 -0
  9. package/dist/__tests__/replay/test-helpers.d.ts +13 -0
  10. package/dist/__tests__/replay/test-helpers.d.ts.map +1 -0
  11. package/dist/__tests__/replay/test-helpers.js +9 -0
  12. package/dist/autocapture.d.ts +14 -18
  13. package/dist/autocapture.d.ts.map +1 -1
  14. package/dist/autocapture.js +77 -95
  15. package/dist/client.d.ts +12 -19
  16. package/dist/client.d.ts.map +1 -1
  17. package/dist/client.js +56 -72
  18. package/dist/config.d.ts +25 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +26 -0
  21. package/dist/console-capture.d.ts +8 -0
  22. package/dist/console-capture.d.ts.map +1 -0
  23. package/dist/console-capture.js +79 -0
  24. package/dist/errors.d.ts +2 -2
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +8 -5
  27. package/dist/index.d.ts +5 -38
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4 -82
  30. package/dist/next.d.ts +40 -0
  31. package/dist/next.d.ts.map +1 -0
  32. package/dist/next.js +67 -0
  33. package/dist/react.d.ts.map +1 -1
  34. package/dist/react.js +12 -10
  35. package/dist/replay-network.d.ts +21 -0
  36. package/dist/replay-network.d.ts.map +1 -0
  37. package/dist/replay-network.js +193 -0
  38. package/dist/replay-privacy.d.ts +2 -2
  39. package/dist/replay-privacy.d.ts.map +1 -1
  40. package/dist/replay-privacy.js +47 -17
  41. package/dist/replay-retry.d.ts +41 -0
  42. package/dist/replay-retry.d.ts.map +1 -0
  43. package/dist/replay-retry.js +193 -0
  44. package/dist/replay.d.ts +18 -5
  45. package/dist/replay.d.ts.map +1 -1
  46. package/dist/replay.js +131 -71
  47. package/dist/runtime/client-singleton.d.ts +25 -0
  48. package/dist/runtime/client-singleton.d.ts.map +1 -0
  49. package/dist/runtime/client-singleton.js +66 -0
  50. package/dist/runtime/client.d.ts +22 -0
  51. package/dist/runtime/client.d.ts.map +1 -0
  52. package/dist/runtime/client.js +12 -0
  53. package/dist/runtime/replay.d.ts +13 -0
  54. package/dist/runtime/replay.d.ts.map +1 -0
  55. package/dist/runtime/replay.js +15 -0
  56. package/dist/session.d.ts +0 -10
  57. package/dist/session.d.ts.map +1 -1
  58. package/dist/session.js +20 -21
  59. package/dist/telemetry.d.ts +1 -1
  60. package/dist/telemetry.d.ts.map +1 -1
  61. package/dist/telemetry.js +13 -10
  62. package/dist/trace-context.d.ts +9 -0
  63. package/dist/trace-context.d.ts.map +1 -0
  64. package/dist/trace-context.js +13 -0
  65. package/dist/transport.d.ts +11 -19
  66. package/dist/transport.d.ts.map +1 -1
  67. package/dist/transport.js +86 -66
  68. package/dist/types.d.ts +37 -135
  69. package/dist/types.d.ts.map +1 -1
  70. package/dist/types.js +0 -7
  71. package/dist/vitals.d.ts +55 -2
  72. package/dist/vitals.d.ts.map +1 -1
  73. package/dist/vitals.js +115 -16
  74. package/package.json +25 -3
  75. package/dist/__tests__/client.test.d.ts +0 -2
  76. package/dist/__tests__/client.test.d.ts.map +0 -1
  77. package/dist/__tests__/client.test.js +0 -103
  78. package/dist/__tests__/errors.test.d.ts +0 -2
  79. package/dist/__tests__/errors.test.d.ts.map +0 -1
  80. package/dist/__tests__/errors.test.js +0 -80
  81. package/dist/__tests__/replay-privacy.test.d.ts +0 -2
  82. package/dist/__tests__/replay-privacy.test.d.ts.map +0 -1
  83. package/dist/__tests__/replay-privacy.test.js +0 -124
  84. package/dist/__tests__/replay-startup.test.d.ts +0 -2
  85. package/dist/__tests__/replay-startup.test.d.ts.map +0 -1
  86. package/dist/__tests__/replay-startup.test.js +0 -99
  87. package/dist/__tests__/replay.test.d.ts +0 -2
  88. package/dist/__tests__/replay.test.d.ts.map +0 -1
  89. package/dist/__tests__/replay.test.js +0 -217
  90. package/dist/__tests__/session.test.d.ts +0 -2
  91. package/dist/__tests__/session.test.d.ts.map +0 -1
  92. package/dist/__tests__/session.test.js +0 -46
  93. package/dist/__tests__/transport.test.d.ts +0 -2
  94. package/dist/__tests__/transport.test.d.ts.map +0 -1
  95. package/dist/__tests__/transport.test.js +0 -101
  96. package/dist/__tests__/types.test.d.ts +0 -2
  97. package/dist/__tests__/types.test.d.ts.map +0 -1
  98. package/dist/__tests__/types.test.js +0 -139
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Squasher
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Squasher
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # @squasher-ai/browser
2
2
 
3
- Browser SDK for Squasher.
4
-
5
- Capture frontend errors, Core Web Vitals, automatic breadcrumbs, analytics-style events, and optional session replay from a browser app.
3
+ Official @squasher-ai/browser package for Squasher.
6
4
 
7
5
  ## Install
8
6
 
@@ -10,42 +8,5 @@ Capture frontend errors, Core Web Vitals, automatic breadcrumbs, analytics-style
10
8
  npm install @squasher-ai/browser
11
9
  ```
12
10
 
13
- ## Setup
14
-
15
- ```ts
16
- import { init } from "@squasher-ai/browser";
17
-
18
- init({
19
- apiKey: process.env.NEXT_PUBLIC_SQUASHER_API_KEY!,
20
- projectId: process.env.NEXT_PUBLIC_SQUASHER_PROJECT_ID!,
21
- environment: process.env.NODE_ENV,
22
- replay: {
23
- enabled: true,
24
- sampleRate: 0.1,
25
- },
26
- });
27
- ```
28
-
29
- ## React Error Boundary
30
-
31
- ```tsx
32
- import { SquasherErrorBoundary } from "@squasher-ai/browser/react";
33
-
34
- <SquasherErrorBoundary fallback={<div>Something went wrong.</div>}>
35
- <App />
36
- </SquasherErrorBoundary>;
37
- ```
38
-
39
- ## Manual Capture
40
-
41
- ```ts
42
- import { captureError, captureMessage, page, track } from "@squasher-ai/browser";
43
-
44
- track("checkout_started", { step: 1 });
45
- page("Checkout");
46
- captureMessage("Frontend boot complete", "info");
47
- captureError(new Error("Checkout failed"), { cartId: "cart_123" });
48
- ```
49
-
50
11
  Docs: https://docs.squasher.ai
51
- Homepage: https://squasher.ai
12
+ Homepage: https://squasher.ai
@@ -0,0 +1,18 @@
1
+ import { BrowserClient } from "../../client.js";
2
+ import type { ReplayRecorderConfig } from "../../replay.js";
3
+ import type { BrowserClientRuntime, BrowserTransport } from "../../runtime/client.js";
4
+ import type { BrowserConfig, BrowserErrorEvent } from "../../types.js";
5
+ import type { VitalsCollectorConfig } from "../../vitals.js";
6
+ export declare const installAutocaptureMock: import("vitest").Mock<(onBreadcrumb: import("../../console-capture.js").BreadcrumbCallback, options: import("../../autocapture.js").AutocaptureOptions) => () => void>;
7
+ export declare const installGlobalErrorHandlersMock: import("vitest").Mock<(onError: (event: BrowserErrorEvent) => void) => () => void>;
8
+ export declare const replayCtorMock: import("vitest").Mock<(config: ReplayRecorderConfig) => void>;
9
+ export declare const replayDisposeMock: import("vitest").Mock<() => void>;
10
+ export declare const startVitalsCollectionMock: import("vitest").Mock<(transport: BrowserTransport, config: VitalsCollectorConfig) => void>;
11
+ export declare const vitalsSuppressMock: import("vitest").Mock<() => void>;
12
+ export declare const browserClientRuntime: BrowserClientRuntime;
13
+ export declare function buildConfig(overrides?: Partial<BrowserConfig>): BrowserConfig;
14
+ export declare function createClient(overrides?: Partial<BrowserConfig>): BrowserClient;
15
+ export declare function readBeaconEvent(callIndex: number): Promise<BrowserErrorEvent>;
16
+ export declare function setupClientTest(): void;
17
+ export declare function teardownClientTest(): void;
18
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../../src/__tests__/client/harness.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGtF,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,eAAO,MAAM,sBAAsB,wKAAsD,CAAC;AAC1F,eAAO,MAAM,8BAA8B,oFACkB,CAAC;AAC9D,eAAO,MAAM,cAAc,iCAAkB,oBAAoB,KAAK,IAAI,CAAG,CAAC;AAC9E,eAAO,MAAM,iBAAiB,8BAAe,IAAI,CAAG,CAAC;AACrD,eAAO,MAAM,yBAAyB,oCAClB,gBAAgB,UAAU,qBAAqB,KAAK,IAAI,CAAG,CAAC;AAChF,eAAO,MAAM,kBAAkB,8BAAe,IAAI,CAAG,CAAC;AAEtD,eAAO,MAAM,oBAAoB,EAAE,oBAYlC,CAAC;AAuBF,wBAAgB,WAAW,CAAC,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,aAAa,CASjF;AAED,wBAAgB,YAAY,CAAC,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,aAAa,CAElF;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAQnF;AAED,wBAAgB,eAAe,IAAI,IAAI,CAwCtC;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC"}
@@ -0,0 +1,106 @@
1
+ import { vi } from "vitest";
2
+ import { BrowserClient } from "../../client.js";
3
+ import { resetSessionId } from "../../session.js";
4
+ import { Transport } from "../../transport.js";
5
+ export const installAutocaptureMock = vi.fn();
6
+ export const installGlobalErrorHandlersMock = vi.fn();
7
+ export const replayCtorMock = vi.fn();
8
+ export const replayDisposeMock = vi.fn();
9
+ export const startVitalsCollectionMock = vi.fn();
10
+ export const vitalsSuppressMock = vi.fn();
11
+ export const browserClientRuntime = {
12
+ createReplayRecorder: (config) => {
13
+ replayCtorMock(config);
14
+ return { dispose: replayDisposeMock };
15
+ },
16
+ createTransport: (config) => new Transport(config),
17
+ installAutocapture: (onBreadcrumb, options) => installAutocaptureMock(onBreadcrumb, options),
18
+ installGlobalErrorHandlers: (onError) => installGlobalErrorHandlersMock(onError),
19
+ startVitalsCollection: (transport, config) => {
20
+ startVitalsCollectionMock(transport, config);
21
+ return { suppress: vitalsSuppressMock };
22
+ },
23
+ };
24
+ function createStorage() {
25
+ const storage = {};
26
+ return {
27
+ clear: () => {
28
+ for (const key of Object.keys(storage))
29
+ delete storage[key];
30
+ },
31
+ getItem: (key) => storage[key] ?? null,
32
+ key: (index) => Object.keys(storage)[index] ?? null,
33
+ get length() {
34
+ return Object.keys(storage).length;
35
+ },
36
+ removeItem: (key) => {
37
+ delete storage[key];
38
+ },
39
+ setItem: (key, value) => {
40
+ storage[key] = value;
41
+ },
42
+ };
43
+ }
44
+ export function buildConfig(overrides = {}) {
45
+ return {
46
+ apiKey: "sq_pk_test",
47
+ projectId: "project-123",
48
+ enableVitals: false,
49
+ enableErrorCapture: false,
50
+ enableAutoBreadcrumbs: false,
51
+ ...overrides,
52
+ };
53
+ }
54
+ export function createClient(overrides = {}) {
55
+ return new BrowserClient(buildConfig(overrides), browserClientRuntime);
56
+ }
57
+ export async function readBeaconEvent(callIndex) {
58
+ const beaconCall = vi.mocked(navigator.sendBeacon).mock.calls[callIndex];
59
+ const payloadBlob = beaconCall?.[1];
60
+ if (!(payloadBlob instanceof Blob)) {
61
+ throw new Error("expected sendBeacon payload to be a Blob");
62
+ }
63
+ return JSON.parse(await payloadBlob.text());
64
+ }
65
+ export function setupClientTest() {
66
+ resetSessionId();
67
+ vi.useFakeTimers();
68
+ vi.restoreAllMocks();
69
+ const consoleLog = vi.spyOn(console, "log").mockImplementation(() => undefined);
70
+ const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
71
+ installAutocaptureMock.mockReset();
72
+ installGlobalErrorHandlersMock.mockReset();
73
+ replayCtorMock.mockReset();
74
+ replayDisposeMock.mockReset();
75
+ startVitalsCollectionMock.mockReset();
76
+ vitalsSuppressMock.mockReset();
77
+ vi.stubGlobal("console", { ...console, log: consoleLog, warn: consoleWarn });
78
+ vi.stubGlobal("crypto", {
79
+ randomUUID: vi.fn().mockReturnValue("aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"),
80
+ });
81
+ vi.stubGlobal("sessionStorage", createStorage());
82
+ vi.stubGlobal("navigator", {
83
+ sendBeacon: vi.fn().mockReturnValue(true),
84
+ });
85
+ vi.stubGlobal("fetch", vi.fn());
86
+ vi.stubGlobal("document", {
87
+ addEventListener: vi.fn(),
88
+ referrer: "https://referrer.example/from",
89
+ removeEventListener: vi.fn(),
90
+ title: "Settings",
91
+ visibilityState: "visible",
92
+ });
93
+ vi.stubGlobal("window", {
94
+ addEventListener: vi.fn(),
95
+ removeEventListener: vi.fn(),
96
+ });
97
+ vi.stubGlobal("location", {
98
+ href: "https://example.com/settings?tab=profile",
99
+ pathname: "/settings",
100
+ search: "?tab=profile",
101
+ });
102
+ }
103
+ export function teardownClientTest() {
104
+ vi.useRealTimers();
105
+ vi.unstubAllGlobals();
106
+ }
@@ -0,0 +1,29 @@
1
+ import type { ReplayNetworkCaptureOptions } from "../../replay-network.js";
2
+ import type { ReplayPrivacyRecordOptions } from "../../replay-privacy.js";
3
+ import { ReplayRecorder, type ReplayRecorderConfig } from "../../replay.js";
4
+ import type { MockReplayEvent } from "./test-helpers.js";
5
+ interface ReplayPayload {
6
+ session_id: string;
7
+ events: MockReplayEvent[];
8
+ page_url: string;
9
+ started_at: string;
10
+ visitor_id?: string;
11
+ }
12
+ export declare const replayUrl = "https://ingest.squasher.ai/v1/projects/project-123/replays";
13
+ export declare const sessionId = "11111111-2222-4333-8444-555555555555";
14
+ export declare const installReplayNetworkCaptureMock: import("vitest").Mock<(options: ReplayNetworkCaptureOptions) => () => void>;
15
+ export declare const rrwebRecordMock: import("vitest").Mock<(options: ReplayPrivacyRecordOptions) => (() => void) | undefined>;
16
+ export declare const rrwebStopMock: import("vitest").Mock<() => void>;
17
+ export declare const rrwebAddCustomEventMock: import("vitest").Mock<(tag: string, payload: import("../../replay-network.js").ReplayNetworkEventPayload) => void>;
18
+ export declare function setupReplayCoverageTest(): void;
19
+ export declare function teardownReplayCoverageTest(): void;
20
+ export declare function createRecorder(overrides?: Partial<ReplayRecorderConfig>): ReplayRecorder;
21
+ export declare function readyRecorder(overrides?: Partial<ReplayRecorderConfig>): Promise<ReplayRecorder>;
22
+ export declare function emitFromRecorder(event: MockReplayEvent, overrides?: Partial<ReplayRecorderConfig>): Promise<ReplayRecorder>;
23
+ export declare function emitRecordedEvent(event: MockReplayEvent): void;
24
+ export declare function flushFinal(eventName: "beforeunload" | "pagehide"): Promise<void>;
25
+ export declare function fireLifecycleEvent(eventName: "beforeunload" | "pagehide"): void;
26
+ export declare function getPayload(callIndex?: number): ReplayPayload;
27
+ export declare function drainMicrotasks(turns?: number): Promise<void>;
28
+ export {};
29
+ //# sourceMappingURL=coverage-harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-harness.d.ts","sourceRoot":"","sources":["../../../src/__tests__/replay/coverage-harness.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAG5E,OAAO,KAAK,EAAE,eAAe,EAAc,MAAM,mBAAmB,CAAC;AAErE,UAAU,aAAa;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,+DAA+D,CAAC;AACtF,eAAO,MAAM,SAAS,yCAAyC,CAAC;AAChE,eAAO,MAAM,+BAA+B,kCAC1B,2BAA2B,KAAK,MAAM,IAAI,CAAG,CAAC;AAChE,eAAO,MAAM,eAAe,kCACV,0BAA0B,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAG,CAAC;AAC7E,eAAO,MAAM,aAAa,8BAAe,IAAI,CAAG,CAAC;AACjD,eAAO,MAAM,uBAAuB,oHACkC,CAAC;AAkCvE,wBAAgB,uBAAuB,IAAI,IAAI,CAkC9C;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAKjD;AAED,wBAAgB,cAAc,CAAC,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAAG,cAAc,CAa5F;AAED,wBAAsB,aAAa,CACjC,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,OAAO,CAAC,cAAc,CAAC,CAIzB;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,eAAe,EACtB,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,OAAO,CAAC,cAAc,CAAC,CAIzB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAE9D;AAED,wBAAsB,UAAU,CAAC,SAAS,EAAE,cAAc,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtF;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,cAAc,GAAG,UAAU,GAAG,IAAI,CAE/E;AAED,wBAAgB,UAAU,CAAC,SAAS,SAAI,GAAG,aAAa,CAOvD;AAED,wBAAsB,eAAe,CAAC,KAAK,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/D"}
@@ -0,0 +1,123 @@
1
+ import { expect, vi } from "vitest";
2
+ import { ReplayRecorder } from "../../replay.js";
3
+ import { resetSessionId } from "../../session.js";
4
+ export const replayUrl = "https://ingest.squasher.ai/v1/projects/project-123/replays";
5
+ export const sessionId = "11111111-2222-4333-8444-555555555555";
6
+ export const installReplayNetworkCaptureMock = vi.fn();
7
+ export const rrwebRecordMock = vi.fn();
8
+ export const rrwebStopMock = vi.fn();
9
+ export const rrwebAddCustomEventMock = vi.fn();
10
+ const replayRuntime = {
11
+ installNetworkCapture: (options) => installReplayNetworkCaptureMock(options),
12
+ loadRecordingAdapter: async () => ({
13
+ emitCustomEvent: rrwebAddCustomEventMock,
14
+ start: rrwebRecordMock,
15
+ }),
16
+ };
17
+ let activeRecorder;
18
+ let emitReplayEvent;
19
+ let listeners = {};
20
+ function createStorage() {
21
+ const storage = {};
22
+ return {
23
+ clear: () => {
24
+ for (const key of Object.keys(storage))
25
+ delete storage[key];
26
+ },
27
+ getItem: (key) => storage[key] ?? null,
28
+ key: (index) => Object.keys(storage)[index] ?? null,
29
+ get length() {
30
+ return Object.keys(storage).length;
31
+ },
32
+ removeItem: (key) => {
33
+ delete storage[key];
34
+ },
35
+ setItem: (key, value) => {
36
+ storage[key] = value;
37
+ },
38
+ };
39
+ }
40
+ export function setupReplayCoverageTest() {
41
+ activeRecorder = undefined;
42
+ emitReplayEvent = undefined;
43
+ listeners = {};
44
+ resetSessionId();
45
+ vi.useFakeTimers();
46
+ vi.restoreAllMocks();
47
+ vi.stubGlobal("crypto", { randomUUID: vi.fn().mockReturnValue(sessionId) });
48
+ vi.stubGlobal("sessionStorage", createStorage());
49
+ vi.stubGlobal("location", { href: "https://example.com/dashboard" });
50
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response()));
51
+ vi.stubGlobal("navigator", {
52
+ sendBeacon: vi.fn().mockReturnValue(true),
53
+ });
54
+ vi.stubGlobal("document", {});
55
+ vi.stubGlobal("window", {
56
+ addEventListener: vi.fn((event, listener) => {
57
+ listeners[event] = [...(listeners[event] ?? []), listener];
58
+ }),
59
+ removeEventListener: vi.fn((event, listener) => {
60
+ listeners[event] = (listeners[event] ?? []).filter((value) => value !== listener);
61
+ }),
62
+ });
63
+ installReplayNetworkCaptureMock.mockReset();
64
+ rrwebAddCustomEventMock.mockReset();
65
+ rrwebStopMock.mockReset();
66
+ rrwebRecordMock.mockReset();
67
+ installReplayNetworkCaptureMock.mockImplementation(() => () => undefined);
68
+ rrwebRecordMock.mockImplementation((options) => {
69
+ emitReplayEvent = options.emit;
70
+ return rrwebStopMock;
71
+ });
72
+ }
73
+ export function teardownReplayCoverageTest() {
74
+ activeRecorder?.dispose();
75
+ activeRecorder = undefined;
76
+ vi.useRealTimers();
77
+ vi.unstubAllGlobals();
78
+ }
79
+ export function createRecorder(overrides = {}) {
80
+ activeRecorder = new ReplayRecorder({
81
+ endpoint: "https://ingest.squasher.ai",
82
+ projectId: "project-123",
83
+ apiKey: "sq_pk_test",
84
+ debug: false,
85
+ sampleRate: 1,
86
+ ...overrides,
87
+ }, replayRuntime);
88
+ return activeRecorder;
89
+ }
90
+ export async function readyRecorder(overrides = {}) {
91
+ const recorder = createRecorder(overrides);
92
+ await recorder.ready;
93
+ return recorder;
94
+ }
95
+ export async function emitFromRecorder(event, overrides = {}) {
96
+ const recorder = await readyRecorder(overrides);
97
+ emitReplayEvent?.(event);
98
+ return recorder;
99
+ }
100
+ export function emitRecordedEvent(event) {
101
+ emitReplayEvent?.(event);
102
+ }
103
+ export async function flushFinal(eventName) {
104
+ listeners[eventName]?.[0]?.();
105
+ await Promise.resolve();
106
+ }
107
+ export function fireLifecycleEvent(eventName) {
108
+ listeners[eventName]?.[0]?.();
109
+ }
110
+ export function getPayload(callIndex = 0) {
111
+ const body = vi.mocked(fetch).mock.calls[callIndex]?.[1]?.body;
112
+ expect(Object.prototype.toString.call(body)).toBe("[object String]");
113
+ if (Object.prototype.toString.call(body) !== "[object String]") {
114
+ throw new Error("expected replay request body to be a string");
115
+ }
116
+ return JSON.parse(String(body));
117
+ }
118
+ export async function drainMicrotasks(turns = 10) {
119
+ if (turns <= 0)
120
+ return;
121
+ await Promise.resolve();
122
+ await drainMicrotasks(turns - 1);
123
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReplayPrivacyRecordOptions } from "../../replay-privacy.js";
2
+ export type MockReplayValue = boolean | null | number | string | MockReplayObject | MockReplayValue[];
3
+ export interface MockReplayObject {
4
+ [key: string]: MockReplayValue;
5
+ }
6
+ export type MockReplayEvent = {
7
+ type: number;
8
+ timestamp: number;
9
+ data: MockReplayObject;
10
+ };
11
+ export type ReplayEmit = NonNullable<ReplayPrivacyRecordOptions["emit"]>;
12
+ export declare function createReplayEvent(type: number, timestamp: number): MockReplayEvent;
13
+ //# sourceMappingURL=test-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../../src/__tests__/replay/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,MAAM,MAAM,eAAe,GACvB,OAAO,GACP,IAAI,GACJ,MAAM,GACN,MAAM,GACN,gBAAgB,GAChB,eAAe,EAAE,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;CAChC;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAC;AAEzE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAQlF"}
@@ -0,0 +1,9 @@
1
+ export function createReplayEvent(type, timestamp) {
2
+ return {
3
+ type,
4
+ timestamp,
5
+ data: {
6
+ source: type,
7
+ },
8
+ };
9
+ }
@@ -1,21 +1,17 @@
1
1
  /**
2
- * Auto-capture breadcrumbs for navigation, clicks, and fetch errors.
3
- *
4
- * Breadcrumbs are lightweight events that provide context about what the
5
- * user was doing before an error occurred. They are attached to error
6
- * events to help with debugging and AI triage.
7
- *
8
- * Categories:
9
- * - "navigation" — route changes (pushState, replaceState, popstate)
10
- * - "ui.click" — user clicks on elements
11
- * - "fetch" — failed fetch requests (4xx/5xx)
12
- * - "console" — console.error / console.warn calls
2
+ * Auto-capture breadcrumbs. Categories emitted:
3
+ * - "navigation" — pushState/replaceState/popstate
4
+ * - "ui.click" — clicks on elements
5
+ * - "fetch" — 4xx/5xx fetch responses
6
+ * - "console" — console.error / console.warn calls
13
7
  */
14
- import type { Breadcrumb } from "./types";
15
- export type BreadcrumbCallback = (crumb: Omit<Breadcrumb, "timestamp">) => void;
16
- /**
17
- * Install all auto-capture hooks.
18
- * Returns a cleanup function to remove all hooks and restore originals.
19
- */
20
- export declare function installAutocapture(onBreadcrumb: BreadcrumbCallback): () => void;
8
+ import { type BreadcrumbCallback } from "./console-capture.js";
9
+ import type { BrowserErrorEvent } from "./types.js";
10
+ export type { BreadcrumbCallback } from "./console-capture.js";
11
+ export interface AutocaptureOptions {
12
+ captureBreadcrumbs?: boolean;
13
+ onConsoleError?: (event: BrowserErrorEvent) => void;
14
+ }
15
+ /** Installs all hooks. Returned function removes them and restores originals. */
16
+ export declare function installAutocapture(onBreadcrumb: BreadcrumbCallback, options?: AutocaptureOptions): () => void;
21
17
  //# sourceMappingURL=autocapture.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"autocapture.d.ts","sourceRoot":"","sources":["../src/autocapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,KAAK,IAAI,CAAC;AAEhF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAa/E"}
1
+ {"version":3,"file":"autocapture.d.ts","sourceRoot":"","sources":["../src/autocapture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAc,MAAM,YAAY,CAAC;AAEhE,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACrD;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,kBAAkB,EAChC,OAAO,GAAE,kBAAuB,GAC/B,MAAM,IAAI,CAwBZ"}