browsertrack 0.1.0 → 0.1.1

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 (55) hide show
  1. package/.github/workflows/docs.yml +54 -0
  2. package/AGENTS.md +133 -0
  3. package/README.md +1 -1
  4. package/dist/{chunk-6A7FFDIB.js → chunk-6VA7GBAO.js} +29 -4
  5. package/dist/chunk-6VA7GBAO.js.map +1 -0
  6. package/dist/{chunk-ANMR5WBH.js → chunk-G2Y3CXCY.js} +2 -2
  7. package/dist/{chunk-BSKNG4V7.js → chunk-ILRYKMME.js} +30 -1
  8. package/dist/chunk-ILRYKMME.js.map +1 -0
  9. package/dist/{chunk-X7C5CHBO.js → chunk-SKCMT2DE.js} +637 -76
  10. package/dist/chunk-SKCMT2DE.js.map +1 -0
  11. package/dist/{chunk-ZLNGOOH2.js → chunk-SPCIROIU.js} +65 -3
  12. package/dist/chunk-SPCIROIU.js.map +1 -0
  13. package/dist/cli/index.js +115 -2
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/client/index.cjs +637 -75
  16. package/dist/client/index.d.ts +16 -2
  17. package/dist/client/index.js +2 -2
  18. package/dist/client.iife.js +393 -72
  19. package/dist/core/index.d.ts +12 -4
  20. package/dist/core/index.js +9 -3
  21. package/dist/daemon/index.d.ts +1 -1
  22. package/dist/daemon/index.js +3 -3
  23. package/dist/{engine-DKloFjvs.d.ts → engine-CeT9URuN.d.ts} +4 -0
  24. package/dist/index.d.ts +4 -3
  25. package/dist/index.js +13 -7
  26. package/dist/mcp/index.d.ts +2 -2
  27. package/dist/mcp/index.js +2 -2
  28. package/dist/{server-CN-se8td.d.ts → server-Dd8NX2Mk.d.ts} +1 -1
  29. package/docboot.config.js +16 -0
  30. package/docs/cli.md +51 -0
  31. package/docs/closed-loop-verification.md +57 -0
  32. package/docs/getting-started.md +122 -0
  33. package/docs/incidents-diagnostics.md +61 -0
  34. package/docs/index.md +46 -0
  35. package/docs/mcp-reference.md +128 -0
  36. package/docs/security-privacy.md +32 -0
  37. package/docs/visual-notes.md +67 -0
  38. package/package.json +6 -2
  39. package/packages/client/package.json +2 -2
  40. package/packages/client/src/notes/inspector.ts +689 -79
  41. package/packages/client/src/transport/websocket.ts +15 -0
  42. package/packages/core/package.json +3 -0
  43. package/packages/core/src/redaction.ts +40 -5
  44. package/packages/daemon/src/server/ws.ts +72 -1
  45. package/packages/daemon/src/session/manager.ts +21 -0
  46. package/packages/daemon/src/storage/db.ts +10 -1
  47. package/test/client/interceptors.test.ts +61 -0
  48. package/test/core/redaction.test.ts +12 -0
  49. package/dist/chunk-6A7FFDIB.js.map +0 -1
  50. package/dist/chunk-BSKNG4V7.js.map +0 -1
  51. package/dist/chunk-X7C5CHBO.js.map +0 -1
  52. package/dist/chunk-ZLNGOOH2.js.map +0 -1
  53. package/packages/core/dist/index.d.ts +0 -317
  54. package/packages/core/dist/index.js +0 -221
  55. /package/dist/{chunk-ANMR5WBH.js.map → chunk-G2Y3CXCY.js.map} +0 -0
@@ -1,317 +0,0 @@
1
- type EventType = 'runtime_error' | 'unhandled_rejection' | 'console' | 'fetch' | 'xhr' | 'navigation' | 'interaction';
2
- type ConsoleLevel = 'error' | 'warn' | 'info' | 'log' | 'debug';
3
- interface ElementSummary {
4
- selector: string;
5
- tag: string;
6
- id?: string;
7
- classes?: string[];
8
- attributes?: Record<string, string>;
9
- outerHTML?: string;
10
- innerText?: string;
11
- boundingRect?: {
12
- x: number;
13
- y: number;
14
- width: number;
15
- height: number;
16
- top: number;
17
- left: number;
18
- bottom: number;
19
- right: number;
20
- };
21
- visible: boolean;
22
- }
23
- interface Breadcrumb {
24
- id?: string;
25
- type: 'navigation' | 'click' | 'submit' | 'fetch' | 'xhr' | 'console' | 'error';
26
- category?: string;
27
- message: string;
28
- timestamp: number;
29
- level?: 'info' | 'warn' | 'error';
30
- data?: Record<string, any>;
31
- element?: ElementSummary;
32
- }
33
- interface NetworkEvent {
34
- id?: string;
35
- url: string;
36
- method: string;
37
- status?: number;
38
- statusText?: string;
39
- durationMs: number;
40
- error?: string;
41
- aborted?: boolean;
42
- timestamp: number;
43
- requestHeaders?: Record<string, string>;
44
- }
45
- interface RuntimeErrorEvent {
46
- message: string;
47
- stack?: string;
48
- filename?: string;
49
- lineno?: number;
50
- colno?: number;
51
- errorType: string;
52
- timestamp: number;
53
- }
54
- interface ConsoleEvent {
55
- level: ConsoleLevel;
56
- message: string;
57
- args?: any[];
58
- stack?: string;
59
- timestamp: number;
60
- }
61
- interface NavigationEvent {
62
- from?: string;
63
- to: string;
64
- type: 'pushState' | 'replaceState' | 'popstate' | 'hashchange' | 'initial';
65
- timestamp: number;
66
- }
67
- interface HelloMessage {
68
- type: 'hello';
69
- origin: string;
70
- url: string;
71
- title: string;
72
- userAgent: string;
73
- timestamp: number;
74
- projectId?: string;
75
- }
76
- interface ClientEventMessage {
77
- type: 'event';
78
- sessionId: string;
79
- eventType: EventType;
80
- payload: RuntimeErrorEvent | ConsoleEvent | NetworkEvent | NavigationEvent | Breadcrumb;
81
- breadcrumbs?: Breadcrumb[];
82
- lastElement?: ElementSummary;
83
- route?: string;
84
- url: string;
85
- title?: string;
86
- timestamp: number;
87
- }
88
-
89
- type IncidentStatus = 'OPEN' | 'FIX_ATTEMPTED' | 'VERIFYING' | 'VERIFIED' | 'FAILED' | 'INCONCLUSIVE';
90
- type IncidentSeverity = 'error' | 'warn' | 'fatal';
91
- interface IncidentSource {
92
- file: string;
93
- line: number;
94
- column?: number;
95
- }
96
- interface Incident {
97
- id: string;
98
- projectId: string;
99
- sessionId: string;
100
- type: string;
101
- severity: IncidentSeverity;
102
- message: string;
103
- source: IncidentSource;
104
- fingerprint: string;
105
- route: string;
106
- firstSeen: string;
107
- lastSeen: string;
108
- occurrences: number;
109
- status: IncidentStatus;
110
- stack?: string;
111
- breadcrumbs: Breadcrumb[];
112
- networkFailures: NetworkEvent[];
113
- lastElement?: ElementSummary;
114
- screenshots?: {
115
- error?: string;
116
- before?: string;
117
- after?: string;
118
- };
119
- }
120
- interface IncidentOccurrence {
121
- id: string;
122
- incidentId: string;
123
- sessionId: string;
124
- timestamp: string;
125
- route: string;
126
- url: string;
127
- stack?: string;
128
- breadcrumbs: Breadcrumb[];
129
- lastElement?: ElementSummary;
130
- }
131
-
132
- type CommandType = 'reload' | 'navigate' | 'get_page_state' | 'query_element' | 'capture_element';
133
- interface ClientCommand<T = any> {
134
- id: string;
135
- type: CommandType;
136
- params?: T;
137
- }
138
- interface ReloadParams {
139
- force?: boolean;
140
- }
141
- interface NavigateParams {
142
- url: string;
143
- }
144
- interface QueryElementParams {
145
- selector: string;
146
- }
147
- interface CaptureElementParams {
148
- selector?: string;
149
- }
150
- interface QueryElementResult {
151
- exists: boolean;
152
- visible?: boolean;
153
- tag?: string;
154
- id?: string;
155
- classes?: string[];
156
- boundingRect?: {
157
- x: number;
158
- y: number;
159
- width: number;
160
- height: number;
161
- top: number;
162
- left: number;
163
- bottom: number;
164
- right: number;
165
- };
166
- innerText?: string;
167
- outerHTML?: string;
168
- }
169
- interface PageStateResult {
170
- url: string;
171
- route: string;
172
- title: string;
173
- readyState: string;
174
- activeElement?: ElementSummary;
175
- }
176
- interface CaptureElementResult {
177
- dataUrl?: string;
178
- format?: string;
179
- width?: number;
180
- height?: number;
181
- }
182
- interface CommandResponse<T = any> {
183
- id: string;
184
- ok: boolean;
185
- result?: T;
186
- error?: string;
187
- reason?: string;
188
- }
189
-
190
- type ProbeType = 'element_exists' | 'element_visible' | 'text_contains' | 'route_is' | 'network_request_succeeded' | 'no_incident';
191
- interface VerificationProbe {
192
- type: ProbeType;
193
- selector?: string;
194
- text?: string;
195
- route?: string;
196
- fingerprint?: string;
197
- urlPattern?: string;
198
- timeoutMs?: number;
199
- }
200
- interface VerificationRecipe {
201
- route?: string;
202
- targetSelector?: string;
203
- expect?: VerificationProbe[];
204
- observationWindowMs?: number;
205
- }
206
- interface ProbeResult {
207
- type: ProbeType;
208
- passed: boolean;
209
- details?: string;
210
- }
211
- interface VerificationResult {
212
- incidentId: string;
213
- status: IncidentStatus;
214
- checks: ProbeResult[];
215
- screenshots?: {
216
- before?: string;
217
- after?: string;
218
- };
219
- timestamp: string;
220
- message?: string;
221
- }
222
-
223
- interface Project {
224
- id: string;
225
- name: string;
226
- origin: string;
227
- path?: string;
228
- createdAt: string;
229
- updatedAt: string;
230
- }
231
- interface Session {
232
- id: string;
233
- projectId: string;
234
- origin: string;
235
- url: string;
236
- title: string;
237
- userAgent: string;
238
- connectedAt: string;
239
- lastSeenAt: string;
240
- active: boolean;
241
- }
242
-
243
- /**
244
- * Error fingerprinting and normalization utilities
245
- */
246
- interface FingerprintInput {
247
- type: string;
248
- message: string;
249
- sourceFile?: string;
250
- line?: number;
251
- column?: number;
252
- stack?: string;
253
- }
254
- /**
255
- * Normalizes error messages by stripping dynamic content like UUIDs, timestamps,
256
- * hex addresses, and file timestamps.
257
- */
258
- declare function normalizeErrorMessage(message: string): string;
259
- /**
260
- * Normalizes source filenames by removing origin and cache-busting query strings.
261
- */
262
- declare function normalizeSourceFile(filename?: string): string;
263
- /**
264
- * Extracts source location from stack if not explicitly provided
265
- */
266
- declare function extractSourceFromStack(stack?: string): {
267
- file: string;
268
- line: number;
269
- column?: number;
270
- } | null;
271
- /**
272
- * Computes a deterministic fingerprint for an error
273
- */
274
- declare function computeFingerprint(input: FingerprintInput): string;
275
-
276
- /**
277
- * Security and Redaction Utilities
278
- */
279
- declare const SENSITIVE_KEY_PATTERNS: RegExp[];
280
- declare const SENSITIVE_QUERY_PARAMS: string[];
281
- declare const REDACTED_PLACEHOLDER = "[REDACTED]";
282
- /**
283
- * Checks if a property name matches any sensitive pattern
284
- */
285
- declare function isSensitiveKey(key: string): boolean;
286
- /**
287
- * Redacts sensitive query parameters from a URL string
288
- */
289
- declare function redactUrl(rawUrl: string): string;
290
- /**
291
- * Redacts sensitive HTTP headers
292
- */
293
- declare function redactHeaders(headers: Record<string, string>): Record<string, string>;
294
- /**
295
- * Deeply redacts sensitive keys from an object or array
296
- */
297
- declare function redactSensitiveData<T = any>(data: T, maxDepth?: number, currentDepth?: number): T;
298
-
299
- /**
300
- * Semantic element selector generation and inspection
301
- */
302
- interface SelectorOptions {
303
- maxClasses?: number;
304
- maxOuterHTMLLength?: number;
305
- maxInnerTextLength?: number;
306
- }
307
- /**
308
- * Builds a clean, human-readable semantic selector for a DOM element.
309
- * Prioritizes data-testid/data-test, id, semantic tag + specific class, or concise parent path.
310
- */
311
- declare function getSemanticSelector(element: HTMLElement | Element, options?: SelectorOptions): string;
312
- /**
313
- * Truncates string to specified max length with ellipsis
314
- */
315
- declare function truncate(str: string | undefined | null, maxLength?: number): string;
316
-
317
- export { type Breadcrumb, type CaptureElementParams, type CaptureElementResult, type ClientCommand, type ClientEventMessage, type CommandResponse, type CommandType, type ConsoleEvent, type ConsoleLevel, type ElementSummary, type EventType, type FingerprintInput, type HelloMessage, type Incident, type IncidentOccurrence, type IncidentSeverity, type IncidentSource, type IncidentStatus, type NavigateParams, type NavigationEvent, type NetworkEvent, type PageStateResult, type ProbeResult, type ProbeType, type Project, type QueryElementParams, type QueryElementResult, REDACTED_PLACEHOLDER, type ReloadParams, type RuntimeErrorEvent, SENSITIVE_KEY_PATTERNS, SENSITIVE_QUERY_PARAMS, type SelectorOptions, type Session, type VerificationProbe, type VerificationRecipe, type VerificationResult, computeFingerprint, extractSourceFromStack, getSemanticSelector, isSensitiveKey, normalizeErrorMessage, normalizeSourceFile, redactHeaders, redactSensitiveData, redactUrl, truncate };
@@ -1,221 +0,0 @@
1
- // src/fingerprint.ts
2
- function normalizeErrorMessage(message) {
3
- if (!message) return "unknown_error";
4
- return message.trim().replace(/0x[0-9a-fA-F]+/g, "0x<HEX>").replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g, "<UUID>").replace(/\?[tv]=[\w.-]+/g, "").replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z?/g, "<TIMESTAMP>").replace(/#\d+/g, "#<ID>").replace(/\s+/g, " ");
5
- }
6
- function normalizeSourceFile(filename) {
7
- if (!filename) return "unknown_source";
8
- let cleaned = filename.trim();
9
- try {
10
- if (cleaned.startsWith("http://") || cleaned.startsWith("https://")) {
11
- const url = new URL(cleaned);
12
- cleaned = url.pathname;
13
- }
14
- } catch {
15
- cleaned = cleaned.replace(/^https?:\/\/[^/]+/, "");
16
- }
17
- cleaned = cleaned.split("?")[0].split("#")[0];
18
- cleaned = cleaned.replace(/\\/g, "/");
19
- return cleaned || "unknown_source";
20
- }
21
- function extractSourceFromStack(stack) {
22
- if (!stack) return null;
23
- const lines = stack.split("\n");
24
- for (const line of lines) {
25
- const match = line.match(/(?:at\s+(?:.*?\s+\()?)?(https?:\/\/[^\s)]+|file:\/\/[^\s)]+|\/[^\s)]+):(\d+):(\d+)\)?/);
26
- if (match) {
27
- return {
28
- file: normalizeSourceFile(match[1]),
29
- line: parseInt(match[2], 10),
30
- column: parseInt(match[3], 10)
31
- };
32
- }
33
- }
34
- return null;
35
- }
36
- function djb2Hash(str) {
37
- let hash = 5381;
38
- for (let i = 0; i < str.length; i++) {
39
- hash = hash * 33 ^ str.charCodeAt(i);
40
- }
41
- return (hash >>> 0).toString(16).padStart(8, "0");
42
- }
43
- function computeFingerprint(input) {
44
- const normType = (input.type || "Error").trim().toLowerCase();
45
- const normMsg = normalizeErrorMessage(input.message);
46
- let sourceFile = normalizeSourceFile(input.sourceFile);
47
- let line = input.line || 0;
48
- if ((sourceFile === "unknown_source" || line === 0) && input.stack) {
49
- const extracted = extractSourceFromStack(input.stack);
50
- if (extracted) {
51
- sourceFile = extracted.file;
52
- line = extracted.line;
53
- }
54
- }
55
- const rawKey = `${normType}::${normMsg}::${sourceFile}::${line}`;
56
- const hash = djb2Hash(rawKey);
57
- return `fp_${hash}`;
58
- }
59
-
60
- // src/redaction.ts
61
- var SENSITIVE_KEY_PATTERNS = [
62
- /^authorization$/i,
63
- /^cookie$/i,
64
- /^set-cookie$/i,
65
- /password/i,
66
- /token/i,
67
- /secret/i,
68
- /apikey/i,
69
- /api_key/i,
70
- /access_token/i,
71
- /refresh_token/i,
72
- /auth/i,
73
- /credentials/i,
74
- /private_key/i,
75
- /ssn/i,
76
- /credit_card/i,
77
- /cvv/i
78
- ];
79
- var SENSITIVE_QUERY_PARAMS = [
80
- "token",
81
- "auth",
82
- "key",
83
- "apikey",
84
- "api_key",
85
- "secret",
86
- "password",
87
- "access_token",
88
- "refresh_token",
89
- "code",
90
- "signature"
91
- ];
92
- var REDACTED_PLACEHOLDER = "[REDACTED]";
93
- function isSensitiveKey(key) {
94
- if (!key) return false;
95
- return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key));
96
- }
97
- function redactUrl(rawUrl) {
98
- if (!rawUrl) return rawUrl;
99
- try {
100
- const isRelative = !rawUrl.startsWith("http://") && !rawUrl.startsWith("https://") && !rawUrl.startsWith("ws://") && !rawUrl.startsWith("wss://");
101
- const base = "http://localhost";
102
- const parsed = new URL(rawUrl, base);
103
- let changed = false;
104
- for (const param of SENSITIVE_QUERY_PARAMS) {
105
- if (parsed.searchParams.has(param)) {
106
- parsed.searchParams.set(param, REDACTED_PLACEHOLDER);
107
- changed = true;
108
- }
109
- }
110
- for (const key of Array.from(parsed.searchParams.keys())) {
111
- if (isSensitiveKey(key)) {
112
- parsed.searchParams.set(key, REDACTED_PLACEHOLDER);
113
- changed = true;
114
- }
115
- }
116
- if (!changed) return rawUrl;
117
- if (isRelative) {
118
- return parsed.pathname + parsed.search + parsed.hash;
119
- }
120
- return parsed.toString();
121
- } catch {
122
- let safe = rawUrl;
123
- for (const param of SENSITIVE_QUERY_PARAMS) {
124
- const reg = new RegExp(`([?&]${param}=)[^&#]+`, "gi");
125
- safe = safe.replace(reg, `$1${REDACTED_PLACEHOLDER}`);
126
- }
127
- return safe;
128
- }
129
- }
130
- function redactHeaders(headers) {
131
- if (!headers || typeof headers !== "object") return {};
132
- const safe = {};
133
- for (const [key, value] of Object.entries(headers)) {
134
- if (isSensitiveKey(key)) {
135
- safe[key] = REDACTED_PLACEHOLDER;
136
- } else {
137
- safe[key] = value;
138
- }
139
- }
140
- return safe;
141
- }
142
- function redactSensitiveData(data, maxDepth = 4, currentDepth = 0) {
143
- if (data === null || data === void 0) return data;
144
- if (typeof data !== "object") return data;
145
- if (currentDepth > maxDepth) return "[DEPTH_EXCEEDED]";
146
- if (Array.isArray(data)) {
147
- return data.map((item) => redactSensitiveData(item, maxDepth, currentDepth + 1));
148
- }
149
- const result = {};
150
- for (const [key, value] of Object.entries(data)) {
151
- if (isSensitiveKey(key)) {
152
- result[key] = REDACTED_PLACEHOLDER;
153
- } else if (typeof value === "string") {
154
- if (value.startsWith("http://") || value.startsWith("https://") || value.includes("?")) {
155
- result[key] = redactUrl(value);
156
- } else {
157
- result[key] = value;
158
- }
159
- } else if (typeof value === "object" && value !== null) {
160
- result[key] = redactSensitiveData(value, maxDepth, currentDepth + 1);
161
- } else {
162
- result[key] = value;
163
- }
164
- }
165
- return result;
166
- }
167
-
168
- // src/selector.ts
169
- function getSemanticSelector(element, options = {}) {
170
- if (!element || !element.tagName) return "unknown";
171
- const maxClasses = options.maxClasses ?? 2;
172
- const testId = element.getAttribute("data-testid") || element.getAttribute("data-test") || element.getAttribute("data-cy") || element.getAttribute("data-qa");
173
- if (testId) {
174
- return `[data-testid="${testId}"]`;
175
- }
176
- const id = element.id;
177
- if (id && !/^[0-9]+$/.test(id) && !/[0-9a-f]{8}-[0-9a-f]{4}/i.test(id)) {
178
- return `#${id}`;
179
- }
180
- const ariaLabel = element.getAttribute("aria-label");
181
- if (ariaLabel && ariaLabel.length < 30) {
182
- return `${element.tagName.toLowerCase()}[aria-label="${ariaLabel}"]`;
183
- }
184
- const nameAttr = element.getAttribute("name");
185
- if (nameAttr) {
186
- return `${element.tagName.toLowerCase()}[name="${nameAttr}"]`;
187
- }
188
- const tag = element.tagName.toLowerCase();
189
- const classList = Array.from(element.classList || []).filter((c) => !c.startsWith("css-") && !c.startsWith("_") && !/^[a-z0-9]{5,}$/i.test(c)).slice(0, maxClasses);
190
- if (classList.length > 0) {
191
- const classStr = classList.map((c) => `.${c}`).join("");
192
- return `${tag}${classStr}`;
193
- }
194
- if (element.parentElement && element.parentElement !== document.body && element.parentElement !== document.documentElement) {
195
- const parentSelector = getSemanticSelector(element.parentElement, { maxClasses: 1 });
196
- if (parentSelector && parentSelector !== "unknown" && !parentSelector.includes(">")) {
197
- return `${parentSelector} > ${tag}`;
198
- }
199
- }
200
- return tag;
201
- }
202
- function truncate(str, maxLength = 200) {
203
- if (!str) return "";
204
- if (str.length <= maxLength) return str;
205
- return str.slice(0, maxLength) + "...";
206
- }
207
- export {
208
- REDACTED_PLACEHOLDER,
209
- SENSITIVE_KEY_PATTERNS,
210
- SENSITIVE_QUERY_PARAMS,
211
- computeFingerprint,
212
- extractSourceFromStack,
213
- getSemanticSelector,
214
- isSensitiveKey,
215
- normalizeErrorMessage,
216
- normalizeSourceFile,
217
- redactHeaders,
218
- redactSensitiveData,
219
- redactUrl,
220
- truncate
221
- };