@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.
- package/LICENSE +21 -21
- package/dist/__tests__/client/harness.d.ts +18 -0
- package/dist/__tests__/client/harness.d.ts.map +1 -0
- package/dist/__tests__/client/harness.js +106 -0
- package/dist/__tests__/replay/coverage-harness.d.ts +29 -0
- package/dist/__tests__/replay/coverage-harness.d.ts.map +1 -0
- package/dist/__tests__/replay/coverage-harness.js +123 -0
- package/dist/__tests__/replay/test-helpers.d.ts +13 -0
- package/dist/__tests__/replay/test-helpers.d.ts.map +1 -0
- package/dist/__tests__/replay/test-helpers.js +9 -0
- package/dist/autocapture.d.ts +14 -18
- package/dist/autocapture.d.ts.map +1 -1
- package/dist/autocapture.js +77 -95
- package/dist/client.d.ts +12 -19
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +56 -72
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +26 -0
- package/dist/console-capture.d.ts +8 -0
- package/dist/console-capture.d.ts.map +1 -0
- package/dist/console-capture.js +79 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +8 -5
- package/dist/index.d.ts +5 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -82
- package/dist/next.d.ts +40 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +67 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +12 -10
- package/dist/replay-network.d.ts +21 -0
- package/dist/replay-network.d.ts.map +1 -0
- package/dist/replay-network.js +193 -0
- package/dist/replay-privacy.d.ts +2 -2
- package/dist/replay-privacy.d.ts.map +1 -1
- package/dist/replay-privacy.js +47 -17
- package/dist/replay-retry.d.ts +41 -0
- package/dist/replay-retry.d.ts.map +1 -0
- package/dist/replay-retry.js +193 -0
- package/dist/replay.d.ts +18 -5
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +131 -71
- package/dist/runtime/client-singleton.d.ts +25 -0
- package/dist/runtime/client-singleton.d.ts.map +1 -0
- package/dist/runtime/client-singleton.js +66 -0
- package/dist/runtime/client.d.ts +22 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +12 -0
- package/dist/runtime/replay.d.ts +13 -0
- package/dist/runtime/replay.d.ts.map +1 -0
- package/dist/runtime/replay.js +15 -0
- package/dist/session.d.ts +0 -10
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +20 -21
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +13 -10
- package/dist/trace-context.d.ts +9 -0
- package/dist/trace-context.d.ts.map +1 -0
- package/dist/trace-context.js +13 -0
- package/dist/transport.d.ts +11 -19
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +86 -66
- package/dist/types.d.ts +37 -135
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -7
- package/dist/vitals.d.ts +55 -2
- package/dist/vitals.d.ts.map +1 -1
- package/dist/vitals.js +115 -16
- package/package.json +17 -3
- package/dist/__tests__/client.test.d.ts +0 -2
- package/dist/__tests__/client.test.d.ts.map +0 -1
- package/dist/__tests__/client.test.js +0 -103
- package/dist/__tests__/errors.test.d.ts +0 -2
- package/dist/__tests__/errors.test.d.ts.map +0 -1
- package/dist/__tests__/errors.test.js +0 -80
- package/dist/__tests__/replay-privacy.test.d.ts +0 -2
- package/dist/__tests__/replay-privacy.test.d.ts.map +0 -1
- package/dist/__tests__/replay-privacy.test.js +0 -124
- package/dist/__tests__/replay-startup.test.d.ts +0 -2
- package/dist/__tests__/replay-startup.test.d.ts.map +0 -1
- package/dist/__tests__/replay-startup.test.js +0 -99
- package/dist/__tests__/replay.test.d.ts +0 -2
- package/dist/__tests__/replay.test.d.ts.map +0 -1
- package/dist/__tests__/replay.test.js +0 -217
- package/dist/__tests__/session.test.d.ts +0 -2
- package/dist/__tests__/session.test.d.ts.map +0 -1
- package/dist/__tests__/session.test.js +0 -46
- package/dist/__tests__/transport.test.d.ts +0 -2
- package/dist/__tests__/transport.test.d.ts.map +0 -1
- package/dist/__tests__/transport.test.js +0 -101
- package/dist/__tests__/types.test.d.ts +0 -2
- package/dist/__tests__/types.test.d.ts.map +0 -1
- 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 +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 +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
|
-
});
|