@squasher-ai/browser 0.1.0 → 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 (97) hide show
  1. package/LICENSE +21 -21
  2. package/dist/__tests__/client/harness.d.ts +18 -0
  3. package/dist/__tests__/client/harness.d.ts.map +1 -0
  4. package/dist/__tests__/client/harness.js +106 -0
  5. package/dist/__tests__/replay/coverage-harness.d.ts +29 -0
  6. package/dist/__tests__/replay/coverage-harness.d.ts.map +1 -0
  7. package/dist/__tests__/replay/coverage-harness.js +123 -0
  8. package/dist/__tests__/replay/test-helpers.d.ts +13 -0
  9. package/dist/__tests__/replay/test-helpers.d.ts.map +1 -0
  10. package/dist/__tests__/replay/test-helpers.js +9 -0
  11. package/dist/autocapture.d.ts +14 -18
  12. package/dist/autocapture.d.ts.map +1 -1
  13. package/dist/autocapture.js +77 -95
  14. package/dist/client.d.ts +12 -19
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +56 -72
  17. package/dist/config.d.ts +25 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +26 -0
  20. package/dist/console-capture.d.ts +8 -0
  21. package/dist/console-capture.d.ts.map +1 -0
  22. package/dist/console-capture.js +79 -0
  23. package/dist/errors.d.ts +2 -2
  24. package/dist/errors.d.ts.map +1 -1
  25. package/dist/errors.js +8 -5
  26. package/dist/index.d.ts +5 -38
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -82
  29. package/dist/next.d.ts +40 -0
  30. package/dist/next.d.ts.map +1 -0
  31. package/dist/next.js +67 -0
  32. package/dist/react.d.ts.map +1 -1
  33. package/dist/react.js +12 -10
  34. package/dist/replay-network.d.ts +21 -0
  35. package/dist/replay-network.d.ts.map +1 -0
  36. package/dist/replay-network.js +193 -0
  37. package/dist/replay-privacy.d.ts +2 -2
  38. package/dist/replay-privacy.d.ts.map +1 -1
  39. package/dist/replay-privacy.js +47 -17
  40. package/dist/replay-retry.d.ts +41 -0
  41. package/dist/replay-retry.d.ts.map +1 -0
  42. package/dist/replay-retry.js +193 -0
  43. package/dist/replay.d.ts +18 -5
  44. package/dist/replay.d.ts.map +1 -1
  45. package/dist/replay.js +131 -71
  46. package/dist/runtime/client-singleton.d.ts +25 -0
  47. package/dist/runtime/client-singleton.d.ts.map +1 -0
  48. package/dist/runtime/client-singleton.js +66 -0
  49. package/dist/runtime/client.d.ts +22 -0
  50. package/dist/runtime/client.d.ts.map +1 -0
  51. package/dist/runtime/client.js +12 -0
  52. package/dist/runtime/replay.d.ts +13 -0
  53. package/dist/runtime/replay.d.ts.map +1 -0
  54. package/dist/runtime/replay.js +15 -0
  55. package/dist/session.d.ts +0 -10
  56. package/dist/session.d.ts.map +1 -1
  57. package/dist/session.js +20 -21
  58. package/dist/telemetry.d.ts +1 -1
  59. package/dist/telemetry.d.ts.map +1 -1
  60. package/dist/telemetry.js +13 -10
  61. package/dist/trace-context.d.ts +9 -0
  62. package/dist/trace-context.d.ts.map +1 -0
  63. package/dist/trace-context.js +13 -0
  64. package/dist/transport.d.ts +11 -19
  65. package/dist/transport.d.ts.map +1 -1
  66. package/dist/transport.js +86 -66
  67. package/dist/types.d.ts +37 -135
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/types.js +0 -7
  70. package/dist/vitals.d.ts +55 -2
  71. package/dist/vitals.d.ts.map +1 -1
  72. package/dist/vitals.js +115 -16
  73. package/package.json +17 -3
  74. package/dist/__tests__/client.test.d.ts +0 -2
  75. package/dist/__tests__/client.test.d.ts.map +0 -1
  76. package/dist/__tests__/client.test.js +0 -103
  77. package/dist/__tests__/errors.test.d.ts +0 -2
  78. package/dist/__tests__/errors.test.d.ts.map +0 -1
  79. package/dist/__tests__/errors.test.js +0 -80
  80. package/dist/__tests__/replay-privacy.test.d.ts +0 -2
  81. package/dist/__tests__/replay-privacy.test.d.ts.map +0 -1
  82. package/dist/__tests__/replay-privacy.test.js +0 -124
  83. package/dist/__tests__/replay-startup.test.d.ts +0 -2
  84. package/dist/__tests__/replay-startup.test.d.ts.map +0 -1
  85. package/dist/__tests__/replay-startup.test.js +0 -99
  86. package/dist/__tests__/replay.test.d.ts +0 -2
  87. package/dist/__tests__/replay.test.d.ts.map +0 -1
  88. package/dist/__tests__/replay.test.js +0 -217
  89. package/dist/__tests__/session.test.d.ts +0 -2
  90. package/dist/__tests__/session.test.d.ts.map +0 -1
  91. package/dist/__tests__/session.test.js +0 -46
  92. package/dist/__tests__/transport.test.d.ts +0 -2
  93. package/dist/__tests__/transport.test.d.ts.map +0 -1
  94. package/dist/__tests__/transport.test.js +0 -101
  95. package/dist/__tests__/types.test.d.ts +0 -2
  96. package/dist/__tests__/types.test.d.ts.map +0 -1
  97. package/dist/__tests__/types.test.js +0 -139
@@ -1,46 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from "vitest";
2
- import { getSessionId, resetSessionId } from "../session";
3
- describe("session", () => {
4
- beforeEach(() => {
5
- resetSessionId();
6
- // Mock sessionStorage
7
- const store = {};
8
- vi.stubGlobal("sessionStorage", {
9
- getItem: (key) => store[key] ?? null,
10
- setItem: (key, val) => {
11
- store[key] = val;
12
- },
13
- removeItem: (key) => {
14
- delete store[key];
15
- },
16
- });
17
- });
18
- it("generates a UUID session ID", () => {
19
- const id = getSessionId();
20
- expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
21
- });
22
- it("returns the same ID on subsequent calls", () => {
23
- const id1 = getSessionId();
24
- const id2 = getSessionId();
25
- expect(id1).toBe(id2);
26
- });
27
- it("generates a new ID after reset", () => {
28
- const id1 = getSessionId();
29
- resetSessionId();
30
- const id2 = getSessionId();
31
- expect(id1).not.toBe(id2);
32
- });
33
- it("persists to sessionStorage", () => {
34
- const id = getSessionId();
35
- expect(sessionStorage.getItem("__sq_sid")).toBe(id);
36
- });
37
- it("restores from sessionStorage", () => {
38
- sessionStorage.setItem("__sq_sid", "existing-session-id");
39
- // Clear the in-memory cache
40
- resetSessionId();
41
- // Re-set the storage value after reset cleared it
42
- sessionStorage.setItem("__sq_sid", "existing-session-id");
43
- const id = getSessionId();
44
- expect(id).toBe("existing-session-id");
45
- });
46
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=transport.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"transport.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/transport.test.ts"],"names":[],"mappings":""}
@@ -1,101 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { Transport } from "../transport";
3
- describe("Transport", () => {
4
- let transport;
5
- beforeEach(() => {
6
- vi.useFakeTimers();
7
- // Mock sendBeacon
8
- vi.stubGlobal("navigator", {
9
- sendBeacon: vi.fn().mockReturnValue(true),
10
- });
11
- // Mock document visibility
12
- vi.stubGlobal("document", {
13
- addEventListener: vi.fn(),
14
- visibilityState: "visible",
15
- });
16
- // Mock window
17
- vi.stubGlobal("window", {
18
- addEventListener: vi.fn(),
19
- });
20
- transport = new Transport({
21
- endpoint: "https://ingest.squasher.ai",
22
- projectId: "test-project",
23
- apiKey: "sq_pk_test",
24
- debug: false,
25
- vitalsBufferSize: 3,
26
- vitalsFlushIntervalMs: 10_000,
27
- });
28
- });
29
- afterEach(() => {
30
- transport.dispose();
31
- vi.useRealTimers();
32
- vi.restoreAllMocks();
33
- });
34
- it("sends errors immediately via sendBeacon", () => {
35
- transport.sendError({
36
- message: "Test error",
37
- level: "error",
38
- sdk: { name: "@squasher-ai/browser", version: "0.1.0" },
39
- });
40
- expect(navigator.sendBeacon).toHaveBeenCalledOnce();
41
- const [url, blob] = navigator.sendBeacon.mock.calls[0];
42
- expect(url).toBe("https://ingest.squasher.ai/v1/ingest/test-project?key=sq_pk_test");
43
- expect(blob).toBeInstanceOf(Blob);
44
- });
45
- it("buffers vitals and flushes when buffer is full", () => {
46
- const vital = {
47
- type: "vital",
48
- name: "LCP",
49
- value: 2400,
50
- rating: "needs-improvement",
51
- delta: 2400,
52
- navigationType: "navigate",
53
- url: "https://example.com",
54
- session_id: "test-session",
55
- timestamp: Date.now(),
56
- };
57
- // Send 2 vitals (below buffer size of 3)
58
- transport.enqueueVital(vital);
59
- transport.enqueueVital(vital);
60
- expect(navigator.sendBeacon).not.toHaveBeenCalled();
61
- // Third vital triggers flush
62
- transport.enqueueVital(vital);
63
- expect(navigator.sendBeacon).toHaveBeenCalledOnce();
64
- const [url] = navigator.sendBeacon.mock.calls[0];
65
- expect(url).toBe("https://ingest.squasher.ai/v1/vitals/test-project");
66
- });
67
- it("flushes on dispose", () => {
68
- const vital = {
69
- type: "vital",
70
- name: "FCP",
71
- value: 1000,
72
- rating: "good",
73
- delta: 1000,
74
- navigationType: "navigate",
75
- url: "https://example.com",
76
- session_id: "s1",
77
- timestamp: Date.now(),
78
- };
79
- transport.enqueueVital(vital);
80
- expect(navigator.sendBeacon).not.toHaveBeenCalled();
81
- transport.dispose();
82
- expect(navigator.sendBeacon).toHaveBeenCalledOnce();
83
- });
84
- it("does not enqueue after dispose", () => {
85
- transport.dispose();
86
- vi.mocked(navigator.sendBeacon).mockClear();
87
- transport.enqueueVital({
88
- type: "vital",
89
- name: "CLS",
90
- value: 0.05,
91
- rating: "good",
92
- delta: 0.05,
93
- navigationType: "navigate",
94
- url: "https://example.com",
95
- session_id: "s1",
96
- timestamp: Date.now(),
97
- });
98
- // Should not send after dispose
99
- expect(navigator.sendBeacon).not.toHaveBeenCalled();
100
- });
101
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/types.test.ts"],"names":[],"mappings":""}
@@ -1,139 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- /**
3
- * Type-level parity tests.
4
- *
5
- * These tests verify that the browser SDK types match the ingestion wire
6
- * protocol and API spec shapes. They don't test runtime behavior — they
7
- * ensure type definitions are structurally compatible.
8
- */
9
- describe("type parity", () => {
10
- it("VitalEvent matches the ingestion payload schema", () => {
11
- const event = {
12
- type: "vital",
13
- name: "LCP",
14
- value: 2400,
15
- rating: "needs-improvement",
16
- delta: 2400,
17
- navigationType: "navigate",
18
- url: "https://example.com/checkout",
19
- route: "/checkout",
20
- session_id: "abc-123",
21
- timestamp: 1711234567890,
22
- environment: "production",
23
- release: "1.0.0",
24
- connection: "4g",
25
- device_type: "desktop",
26
- referrer: "https://google.com",
27
- };
28
- // Verify all fields are serializable to JSON
29
- const json = JSON.parse(JSON.stringify(event));
30
- expect(json.type).toBe("vital");
31
- expect(json.name).toBe("LCP");
32
- expect(json.value).toBe(2400);
33
- expect(json.rating).toBe("needs-improvement");
34
- expect(json.session_id).toBe("abc-123");
35
- });
36
- it("VitalsPayload wraps events array", () => {
37
- const payload = {
38
- events: [
39
- {
40
- type: "vital",
41
- name: "CLS",
42
- value: 0.05,
43
- rating: "good",
44
- delta: 0.01,
45
- navigationType: "navigate",
46
- url: "https://example.com",
47
- session_id: "s1",
48
- timestamp: 1000,
49
- },
50
- ],
51
- };
52
- const json = JSON.parse(JSON.stringify(payload));
53
- expect(json.events).toHaveLength(1);
54
- expect(json.events[0].name).toBe("CLS");
55
- });
56
- it("BrowserErrorEvent includes session_id", () => {
57
- const event = {
58
- message: "Test error",
59
- type: "TypeError",
60
- level: "error",
61
- session_id: "browser-session-123",
62
- sdk: { name: "@squasher-ai/browser", version: "0.1.0" },
63
- timestamp: new Date().toISOString(),
64
- };
65
- expect(event.session_id).toBe("browser-session-123");
66
- });
67
- it("BrowserErrorEvent supports analytics and agent telemetry fields", () => {
68
- const event = {
69
- message: "tool call completed",
70
- kind: "tool_call",
71
- event_name: "db.lookup",
72
- distinct_id: "agent-123",
73
- analytics: {
74
- event: "checkout_started",
75
- properties: {
76
- step: 1,
77
- },
78
- },
79
- trace: {
80
- trace_id: "trace-1",
81
- span_id: "span-1",
82
- },
83
- tool_call: {
84
- name: "db.lookup",
85
- duration_ms: 42,
86
- },
87
- llm: {
88
- provider: "anthropic",
89
- model: "claude-sonnet",
90
- },
91
- };
92
- expect(event.kind).toBe("tool_call");
93
- expect(event.analytics?.event).toBe("checkout_started");
94
- expect(event.trace?.trace_id).toBe("trace-1");
95
- expect(event.tool_call?.name).toBe("db.lookup");
96
- expect(event.llm?.model).toBe("claude-sonnet");
97
- });
98
- it("BrowserConfig has vitals-specific fields", () => {
99
- const config = {
100
- apiKey: "sq_pk_test",
101
- projectId: "test-project",
102
- enableVitals: true,
103
- vitalsSampleRate: 0.5,
104
- vitalsBufferSize: 10,
105
- vitalsFlushIntervalMs: 10_000,
106
- enableErrorCapture: true,
107
- enableAutoBreadcrumbs: true,
108
- };
109
- expect(config.enableVitals).toBe(true);
110
- expect(config.vitalsSampleRate).toBe(0.5);
111
- });
112
- it("BrowserConfig supports replay recording options", () => {
113
- const config = {
114
- apiKey: "sq_pk_test",
115
- projectId: "test-project",
116
- replay: {
117
- enabled: true,
118
- privacy: {
119
- blockSelector: ".pii-container",
120
- maskAllText: true,
121
- },
122
- sampleRate: 0.25,
123
- },
124
- };
125
- expect(config.replay?.enabled).toBe(true);
126
- expect(config.replay?.privacy?.blockSelector).toBe(".pii-container");
127
- expect(config.replay?.privacy?.maskAllText).toBe(true);
128
- expect(config.replay?.sampleRate).toBe(0.25);
129
- });
130
- it("VitalMetricName only allows valid metric names", () => {
131
- // TypeScript compile-time check — these are the only valid values
132
- const validNames = ["LCP", "CLS", "INP", "FCP", "TTFB"];
133
- expect(validNames).toHaveLength(5);
134
- });
135
- it("VitalRating only allows valid ratings", () => {
136
- const validRatings = ["good", "needs-improvement", "poor"];
137
- expect(validRatings).toHaveLength(3);
138
- });
139
- });