browser-debug-mcp-bridge 1.6.0 → 1.10.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/README.md +25 -0
- package/apps/mcp-server/dist/db/automation-repository.js +199 -0
- package/apps/mcp-server/dist/db/automation-repository.js.map +1 -0
- package/apps/mcp-server/dist/db/connection.js +1 -5
- package/apps/mcp-server/dist/db/connection.js.map +1 -1
- package/apps/mcp-server/dist/db/events-repository.js +263 -14
- package/apps/mcp-server/dist/db/events-repository.js.map +1 -1
- package/apps/mcp-server/dist/db/index.js +2 -0
- package/apps/mcp-server/dist/db/index.js.map +1 -1
- package/apps/mcp-server/dist/db/migrations.js +180 -0
- package/apps/mcp-server/dist/db/migrations.js.map +1 -1
- package/apps/mcp-server/dist/db/schema.js +93 -1
- package/apps/mcp-server/dist/db/schema.js.map +1 -1
- package/apps/mcp-server/dist/main.js +54 -4
- package/apps/mcp-server/dist/main.js.map +1 -1
- package/apps/mcp-server/dist/mcp/server.js +2860 -86
- package/apps/mcp-server/dist/mcp/server.js.map +1 -1
- package/apps/mcp-server/dist/mcp-bridge.js +46 -3
- package/apps/mcp-server/dist/mcp-bridge.js.map +1 -1
- package/apps/mcp-server/dist/retention.js +67 -4
- package/apps/mcp-server/dist/retention.js.map +1 -1
- package/apps/mcp-server/dist/runtime-paths.js +33 -0
- package/apps/mcp-server/dist/runtime-paths.js.map +1 -0
- package/apps/mcp-server/dist/websocket/messages.js +30 -0
- package/apps/mcp-server/dist/websocket/messages.js.map +1 -1
- package/apps/mcp-server/dist/websocket/websocket-server.js +18 -0
- package/apps/mcp-server/dist/websocket/websocket-server.js.map +1 -1
- package/apps/mcp-server/package.json +2 -2
- package/package.json +17 -6
- package/scripts/mcp-start.cjs +201 -11
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ It captures telemetry from an actual browser session (console, network, navigati
|
|
|
10
10
|
- Query recent errors, failed requests, and event timelines
|
|
11
11
|
- Run targeted live capture (DOM subtree/document, styles, layout)
|
|
12
12
|
- Pull live in-memory console logs with server-side filters (`url`, `tabId`, `levels`, `contains`)
|
|
13
|
+
- Query targeted API calls with optional sanitized request/response bodies
|
|
14
|
+
- Wait deterministically for the next matching API request during repro flows
|
|
13
15
|
- Correlate user actions with network/runtime failures
|
|
14
16
|
- Keep privacy controls enabled (safe mode, allowlist, redaction)
|
|
15
17
|
|
|
@@ -183,6 +185,7 @@ Supported tools:
|
|
|
183
185
|
- `get_navigation_history`
|
|
184
186
|
- `get_console_events`
|
|
185
187
|
- `get_network_failures`
|
|
188
|
+
- `get_network_calls`
|
|
186
189
|
|
|
187
190
|
Example: URL-only query
|
|
188
191
|
|
|
@@ -245,6 +248,17 @@ Default port is `8065`.
|
|
|
245
248
|
- If a stale process still remains, stop it explicitly with `node scripts/mcp-start.cjs --stop`.
|
|
246
249
|
- Optional: set `MCP_STARTUP_TIMEOUT_MS` (default `15000`) for slower machines.
|
|
247
250
|
|
|
251
|
+
## Runtime Storage
|
|
252
|
+
|
|
253
|
+
By default, the launcher and server store local runtime state in a user-local app-data directory, not in the repo or package root.
|
|
254
|
+
|
|
255
|
+
- Windows: `%LOCALAPPDATA%\\browser-debug-mcp-bridge`
|
|
256
|
+
- macOS: `~/Library/Application Support/browser-debug-mcp-bridge`
|
|
257
|
+
- Linux: `$XDG_STATE_HOME/browser-debug-mcp-bridge` or `$XDG_DATA_HOME/browser-debug-mcp-bridge`
|
|
258
|
+
- Fallback: `~/.local/share/browser-debug-mcp-bridge`
|
|
259
|
+
|
|
260
|
+
This keeps SQLite data, snapshot assets, exports, and launcher lock files out of host app roots. To override it explicitly, set `DATA_DIR`.
|
|
261
|
+
|
|
248
262
|
Useful Windows command:
|
|
249
263
|
|
|
250
264
|
```powershell
|
|
@@ -268,12 +282,23 @@ node scripts/mcp-start.cjs --stop
|
|
|
268
282
|
pnpm typecheck
|
|
269
283
|
pnpm lint
|
|
270
284
|
pnpm test
|
|
285
|
+
pnpm test:e2e
|
|
286
|
+
pnpm test:e2e:head
|
|
287
|
+
pnpm test:e2e:smoke
|
|
288
|
+
pnpm test:e2e:full
|
|
271
289
|
pnpm build
|
|
272
290
|
pnpm docs:ci
|
|
273
291
|
pnpm verify
|
|
274
292
|
node scripts/mcp-start.cjs --stop
|
|
275
293
|
```
|
|
276
294
|
|
|
295
|
+
E2E commands run headless by default. Use `pnpm test:e2e:head` only for local headed debugging.
|
|
296
|
+
|
|
297
|
+
CI lanes:
|
|
298
|
+
|
|
299
|
+
- Pull requests and pushes to `main`: `verify` + Playwright smoke + Playwright full.
|
|
300
|
+
- Nightly: `verify` + Playwright full + runtime `/health` smoke check.
|
|
301
|
+
|
|
277
302
|
Optional one-shot local setup:
|
|
278
303
|
|
|
279
304
|
```powershell
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const AUTOMATION_EVENT_TYPES = new Set([
|
|
2
|
+
'automation_requested',
|
|
3
|
+
'automation_started',
|
|
4
|
+
'automation_succeeded',
|
|
5
|
+
'automation_failed',
|
|
6
|
+
'automation_stopped',
|
|
7
|
+
]);
|
|
8
|
+
export function isAutomationLifecycleEventType(eventType) {
|
|
9
|
+
return AUTOMATION_EVENT_TYPES.has(eventType);
|
|
10
|
+
}
|
|
11
|
+
export class AutomationRepository {
|
|
12
|
+
db;
|
|
13
|
+
constructor(db) {
|
|
14
|
+
this.db = db;
|
|
15
|
+
}
|
|
16
|
+
upsertLifecycleEvent(input) {
|
|
17
|
+
if (!isAutomationLifecycleEventType(input.eventType)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const normalized = normalizeLifecycleEvent(input);
|
|
21
|
+
const upsertRun = this.db.prepare(`
|
|
22
|
+
INSERT INTO automation_runs (
|
|
23
|
+
run_id,
|
|
24
|
+
session_id,
|
|
25
|
+
trace_id,
|
|
26
|
+
action,
|
|
27
|
+
tab_id,
|
|
28
|
+
selector,
|
|
29
|
+
status,
|
|
30
|
+
started_at,
|
|
31
|
+
completed_at,
|
|
32
|
+
stop_reason,
|
|
33
|
+
target_summary_json,
|
|
34
|
+
failure_json,
|
|
35
|
+
redaction_json,
|
|
36
|
+
created_at,
|
|
37
|
+
updated_at
|
|
38
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
39
|
+
ON CONFLICT(run_id) DO UPDATE SET
|
|
40
|
+
trace_id = COALESCE(excluded.trace_id, automation_runs.trace_id),
|
|
41
|
+
action = COALESCE(excluded.action, automation_runs.action),
|
|
42
|
+
tab_id = COALESCE(excluded.tab_id, automation_runs.tab_id),
|
|
43
|
+
selector = COALESCE(excluded.selector, automation_runs.selector),
|
|
44
|
+
status = excluded.status,
|
|
45
|
+
started_at = MIN(automation_runs.started_at, excluded.started_at),
|
|
46
|
+
completed_at = COALESCE(excluded.completed_at, automation_runs.completed_at),
|
|
47
|
+
stop_reason = COALESCE(excluded.stop_reason, automation_runs.stop_reason),
|
|
48
|
+
target_summary_json = COALESCE(excluded.target_summary_json, automation_runs.target_summary_json),
|
|
49
|
+
failure_json = COALESCE(excluded.failure_json, automation_runs.failure_json),
|
|
50
|
+
redaction_json = COALESCE(excluded.redaction_json, automation_runs.redaction_json),
|
|
51
|
+
updated_at = excluded.updated_at
|
|
52
|
+
`);
|
|
53
|
+
const upsertStep = this.db.prepare(`
|
|
54
|
+
INSERT INTO automation_steps (
|
|
55
|
+
step_id,
|
|
56
|
+
run_id,
|
|
57
|
+
session_id,
|
|
58
|
+
step_order,
|
|
59
|
+
trace_id,
|
|
60
|
+
action,
|
|
61
|
+
selector,
|
|
62
|
+
status,
|
|
63
|
+
started_at,
|
|
64
|
+
finished_at,
|
|
65
|
+
duration_ms,
|
|
66
|
+
tab_id,
|
|
67
|
+
target_summary_json,
|
|
68
|
+
redaction_json,
|
|
69
|
+
failure_json,
|
|
70
|
+
input_metadata_json,
|
|
71
|
+
event_type,
|
|
72
|
+
event_id,
|
|
73
|
+
created_at,
|
|
74
|
+
updated_at
|
|
75
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
76
|
+
ON CONFLICT(run_id, step_order) DO UPDATE SET
|
|
77
|
+
trace_id = COALESCE(excluded.trace_id, automation_steps.trace_id),
|
|
78
|
+
action = COALESCE(excluded.action, automation_steps.action),
|
|
79
|
+
selector = COALESCE(excluded.selector, automation_steps.selector),
|
|
80
|
+
status = excluded.status,
|
|
81
|
+
started_at = COALESCE(automation_steps.started_at, excluded.started_at),
|
|
82
|
+
finished_at = COALESCE(excluded.finished_at, automation_steps.finished_at),
|
|
83
|
+
duration_ms = COALESCE(excluded.duration_ms, automation_steps.duration_ms),
|
|
84
|
+
tab_id = COALESCE(excluded.tab_id, automation_steps.tab_id),
|
|
85
|
+
target_summary_json = COALESCE(excluded.target_summary_json, automation_steps.target_summary_json),
|
|
86
|
+
redaction_json = COALESCE(excluded.redaction_json, automation_steps.redaction_json),
|
|
87
|
+
failure_json = COALESCE(excluded.failure_json, automation_steps.failure_json),
|
|
88
|
+
input_metadata_json = COALESCE(excluded.input_metadata_json, automation_steps.input_metadata_json),
|
|
89
|
+
event_type = excluded.event_type,
|
|
90
|
+
event_id = COALESCE(excluded.event_id, automation_steps.event_id),
|
|
91
|
+
updated_at = excluded.updated_at
|
|
92
|
+
`);
|
|
93
|
+
upsertRun.run(normalized.runId, input.sessionId, normalized.traceId, normalized.action, normalized.tabId, normalized.selector, normalized.status, normalized.startedAt, normalized.completedAt, normalized.stopReason, normalized.targetSummaryJson, normalized.failureJson, normalized.redactionJson, normalized.createdAt, normalized.updatedAt);
|
|
94
|
+
upsertStep.run(normalized.stepId, normalized.runId, input.sessionId, normalized.stepOrder, normalized.traceId, normalized.action ?? 'unknown', normalized.selector, normalized.status, normalized.startedAt, normalized.finishedAt, normalized.durationMs, normalized.tabId, normalized.targetSummaryJson, normalized.redactionJson, normalized.failureJson, normalized.inputMetadataJson, input.eventType, input.eventId ?? null, normalized.createdAt, normalized.updatedAt);
|
|
95
|
+
}
|
|
96
|
+
listRuns(sessionId) {
|
|
97
|
+
return this.db.prepare(`
|
|
98
|
+
SELECT *
|
|
99
|
+
FROM automation_runs
|
|
100
|
+
WHERE session_id = ?
|
|
101
|
+
ORDER BY started_at ASC, run_id ASC
|
|
102
|
+
`).all(sessionId);
|
|
103
|
+
}
|
|
104
|
+
listSteps(runId) {
|
|
105
|
+
return this.db.prepare(`
|
|
106
|
+
SELECT *
|
|
107
|
+
FROM automation_steps
|
|
108
|
+
WHERE run_id = ?
|
|
109
|
+
ORDER BY step_order ASC, created_at ASC
|
|
110
|
+
`).all(runId);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function normalizeLifecycleEvent(input) {
|
|
114
|
+
const traceId = asNonEmptyString(input.payload.traceId);
|
|
115
|
+
const runId = asNonEmptyString(input.payload.runId)
|
|
116
|
+
?? (traceId ? `${input.sessionId}:${traceId}` : `${input.sessionId}:event:${input.eventId ?? input.timestamp}`);
|
|
117
|
+
const stepOrder = asInteger(input.payload.stepOrder) ?? 1;
|
|
118
|
+
const action = asNonEmptyString(input.payload.action);
|
|
119
|
+
const startedAt = asInteger(input.payload.startedAt) ?? input.timestamp;
|
|
120
|
+
const finishedAt = asInteger(input.payload.finishedAt);
|
|
121
|
+
const durationMs = asInteger(input.payload.durationMs)
|
|
122
|
+
?? (finishedAt !== null ? Math.max(0, finishedAt - startedAt) : null);
|
|
123
|
+
const target = asRecord(input.payload.target);
|
|
124
|
+
const selector = asNonEmptyString(input.payload.selector)
|
|
125
|
+
?? asNonEmptyString(target?.resolvedSelector)
|
|
126
|
+
?? asNonEmptyString(target?.selector);
|
|
127
|
+
const tabId = asInteger(target?.tabId) ?? input.tabId ?? null;
|
|
128
|
+
const stopReason = asNonEmptyString(input.payload.stopReason)
|
|
129
|
+
?? asNonEmptyString(asRecord(input.payload.failureReason)?.message);
|
|
130
|
+
const status = resolveStatus(input.eventType, input.payload);
|
|
131
|
+
const completedAt = isTerminalStatus(status) ? (finishedAt ?? input.timestamp) : null;
|
|
132
|
+
return {
|
|
133
|
+
runId,
|
|
134
|
+
stepId: `${runId}:${stepOrder}`,
|
|
135
|
+
stepOrder,
|
|
136
|
+
traceId,
|
|
137
|
+
action,
|
|
138
|
+
tabId,
|
|
139
|
+
selector,
|
|
140
|
+
status,
|
|
141
|
+
startedAt,
|
|
142
|
+
finishedAt: completedAt,
|
|
143
|
+
completedAt,
|
|
144
|
+
durationMs,
|
|
145
|
+
stopReason,
|
|
146
|
+
targetSummaryJson: stringifyJson(target),
|
|
147
|
+
failureJson: stringifyJson(asRecord(input.payload.failureReason)),
|
|
148
|
+
redactionJson: stringifyJson(asRecord(input.payload.redaction)),
|
|
149
|
+
inputMetadataJson: stringifyJson(asRecord(input.payload.input)),
|
|
150
|
+
createdAt: input.timestamp,
|
|
151
|
+
updatedAt: input.timestamp,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function resolveStatus(eventType, payload) {
|
|
155
|
+
const payloadStatus = asNonEmptyString(payload.status);
|
|
156
|
+
if (payloadStatus) {
|
|
157
|
+
return payloadStatus;
|
|
158
|
+
}
|
|
159
|
+
switch (eventType) {
|
|
160
|
+
case 'automation_requested':
|
|
161
|
+
return 'requested';
|
|
162
|
+
case 'automation_started':
|
|
163
|
+
return 'started';
|
|
164
|
+
case 'automation_succeeded':
|
|
165
|
+
return 'succeeded';
|
|
166
|
+
case 'automation_failed':
|
|
167
|
+
return 'failed';
|
|
168
|
+
case 'automation_stopped':
|
|
169
|
+
return 'stopped';
|
|
170
|
+
default:
|
|
171
|
+
return 'unknown';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function isTerminalStatus(status) {
|
|
175
|
+
return status === 'succeeded' || status === 'failed' || status === 'rejected' || status === 'stopped';
|
|
176
|
+
}
|
|
177
|
+
function asRecord(value) {
|
|
178
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
function asNonEmptyString(value) {
|
|
184
|
+
if (typeof value !== 'string') {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const trimmed = value.trim();
|
|
188
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
189
|
+
}
|
|
190
|
+
function asInteger(value) {
|
|
191
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
return Math.floor(value);
|
|
195
|
+
}
|
|
196
|
+
function stringifyJson(value) {
|
|
197
|
+
return value ? JSON.stringify(value) : null;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=automation-repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automation-repository.js","sourceRoot":"","sources":["../../src/db/automation-repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import { join } from 'path';
|
|
3
2
|
import { dirname } from 'path';
|
|
4
3
|
import { mkdirSync } from 'fs';
|
|
4
|
+
import { getDatabasePath } from '../runtime-paths.js';
|
|
5
5
|
let connection = null;
|
|
6
|
-
export function getDatabasePath() {
|
|
7
|
-
const dataDir = process.env.DATA_DIR || join(process.cwd(), 'data');
|
|
8
|
-
return join(dataDir, 'browser-debug.db');
|
|
9
|
-
}
|
|
10
6
|
export function createConnection(dbPath) {
|
|
11
7
|
const path = dbPath || getDatabasePath();
|
|
12
8
|
if (path !== ':memory:') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAOtD,IAAI,UAAU,GAA8B,IAAI,CAAC;AAEjD,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,MAAM,IAAI,GAAG,MAAM,IAAI,eAAe,EAAE,CAAC;IACzC,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE9B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE/B,OAAO;QACL,EAAE;QACF,WAAW,EAAE,IAAI;KAClB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,UAAU,GAAG,gBAAgB,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,UAAU,EAAE,CAAC;QACf,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QACtB,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,WAAW,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,eAAe,EAAE,CAAC;IAClB,UAAU,GAAG,IAAI,CAAC;AACpB,CAAC"}
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { resolveErrorFingerprint } from './error-fingerprints.js';
|
|
2
|
-
import { getDatabasePath } from '
|
|
2
|
+
import { getDatabasePath } from '../runtime-paths.js';
|
|
3
3
|
import { writeSnapshot } from '../retention.js';
|
|
4
|
+
import { AutomationRepository, isAutomationLifecycleEventType } from './automation-repository.js';
|
|
5
|
+
const INLINE_BODY_BYTES_THRESHOLD = 16 * 1024;
|
|
6
|
+
const BODY_KIND_REQUEST = 'request';
|
|
7
|
+
const BODY_KIND_RESPONSE = 'response';
|
|
8
|
+
const SENSITIVE_FIELD_NAMES = new Set([
|
|
9
|
+
'authorization',
|
|
10
|
+
'proxy-authorization',
|
|
11
|
+
'cookie',
|
|
12
|
+
'set-cookie',
|
|
13
|
+
'x-api-key',
|
|
14
|
+
'api-key',
|
|
15
|
+
'apikey',
|
|
16
|
+
'x-auth-token',
|
|
17
|
+
'access-token',
|
|
18
|
+
'refresh-token',
|
|
19
|
+
'token',
|
|
20
|
+
'password',
|
|
21
|
+
'secret',
|
|
22
|
+
'client_secret',
|
|
23
|
+
]);
|
|
24
|
+
const REDACTION_PATTERNS = [
|
|
25
|
+
{ pattern: /(Authorization:\s*Bearer\s+)[\w\-\.=]+/gi, replacement: '$1[REDACTED]' },
|
|
26
|
+
{ pattern: /eyJ[\w-]*\.eyJ[\w-]*\.[\w-]*/g, replacement: '[JWT_TOKEN]' },
|
|
27
|
+
{ pattern: /((?:api[_-]?key|apikey)\s*[:=]\s*)[\w-]+/gi, replacement: '$1[API_KEY]' },
|
|
28
|
+
{ pattern: /((?:access[_-]?token|refresh[_-]?token|token)\s*[:=]\s*)[^\s,;]+/gi, replacement: '$1[TOKEN]' },
|
|
29
|
+
{ pattern: /((?:password|pwd)\s*[:=]\s*)\S+/gi, replacement: '$1[PASSWORD]' },
|
|
30
|
+
];
|
|
4
31
|
export class EventsRepository {
|
|
5
32
|
db;
|
|
6
33
|
constructor(db) {
|
|
@@ -16,8 +43,15 @@ export class EventsRepository {
|
|
|
16
43
|
`);
|
|
17
44
|
const insertNetwork = this.db.prepare(`
|
|
18
45
|
INSERT INTO network (
|
|
19
|
-
request_id, session_id, ts_start, duration_ms, method, url, origin, status, initiator, error_class, response_size_est
|
|
20
|
-
|
|
46
|
+
request_id, session_id, trace_id, tab_id, ts_start, duration_ms, method, url, origin, status, initiator, error_class, response_size_est,
|
|
47
|
+
request_content_type, request_body_text, request_body_json, request_body_bytes, request_body_truncated, request_body_chunk_ref,
|
|
48
|
+
response_content_type, response_body_text, response_body_json, response_body_bytes, response_body_truncated, response_body_chunk_ref
|
|
49
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
50
|
+
`);
|
|
51
|
+
const insertBodyChunk = this.db.prepare(`
|
|
52
|
+
INSERT INTO body_chunks (
|
|
53
|
+
chunk_ref, session_id, request_id, trace_id, body_kind, content_type, body_text, body_bytes, truncated, created_at
|
|
54
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21
55
|
`);
|
|
22
56
|
const upsertFingerprint = this.db.prepare(`
|
|
23
57
|
INSERT INTO error_fingerprints (
|
|
@@ -29,23 +63,37 @@ export class EventsRepository {
|
|
|
29
63
|
sample_message = COALESCE(error_fingerprints.sample_message, excluded.sample_message),
|
|
30
64
|
sample_stack = COALESCE(error_fingerprints.sample_stack, excluded.sample_stack)
|
|
31
65
|
`);
|
|
66
|
+
const automationRepository = new AutomationRepository(this.db);
|
|
32
67
|
const runBatch = this.db.transaction((batch) => {
|
|
33
68
|
for (const message of batch) {
|
|
34
69
|
const eventId = `${message.sessionId}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
35
70
|
const dbEventType = this.mapEventType(message.eventType);
|
|
71
|
+
const sanitizedData = message.eventType === 'network'
|
|
72
|
+
? sanitizeRecord(message.data)
|
|
73
|
+
: message.data;
|
|
36
74
|
const eventTabId = typeof message.tabId === 'number' && Number.isFinite(message.tabId)
|
|
37
75
|
? Math.floor(message.tabId)
|
|
38
|
-
: this.resolveTabIdFromPayload(
|
|
39
|
-
const eventOrigin = this.resolveEventOrigin(
|
|
40
|
-
insert.run(eventId, message.sessionId, message.timestamp ?? Date.now(), dbEventType, JSON.stringify(
|
|
76
|
+
: this.resolveTabIdFromPayload(sanitizedData);
|
|
77
|
+
const eventOrigin = this.resolveEventOrigin(sanitizedData, message.origin);
|
|
78
|
+
insert.run(eventId, message.sessionId, message.timestamp ?? Date.now(), dbEventType, JSON.stringify(sanitizedData), eventTabId, eventOrigin);
|
|
41
79
|
if (message.eventType === 'error') {
|
|
42
|
-
this.upsertErrorFingerprintPrepared(upsertFingerprint, message.sessionId,
|
|
80
|
+
this.upsertErrorFingerprintPrepared(upsertFingerprint, message.sessionId, sanitizedData);
|
|
43
81
|
}
|
|
44
82
|
if (message.eventType === 'network') {
|
|
45
|
-
this.insertNetworkEventPrepared(insertNetwork, message.sessionId,
|
|
83
|
+
this.insertNetworkEventPrepared(insertNetwork, insertBodyChunk, message.sessionId, sanitizedData, eventOrigin, eventTabId);
|
|
46
84
|
}
|
|
47
85
|
if (message.eventType === 'ui_snapshot') {
|
|
48
|
-
this.insertSnapshotPrepared(message.sessionId, eventId,
|
|
86
|
+
this.insertSnapshotPrepared(message.sessionId, eventId, sanitizedData);
|
|
87
|
+
}
|
|
88
|
+
if (isAutomationLifecycleEventType(message.eventType)) {
|
|
89
|
+
automationRepository.upsertLifecycleEvent({
|
|
90
|
+
eventId,
|
|
91
|
+
eventType: message.eventType,
|
|
92
|
+
sessionId: message.sessionId,
|
|
93
|
+
timestamp: message.timestamp ?? Date.now(),
|
|
94
|
+
tabId: eventTabId,
|
|
95
|
+
payload: sanitizedData,
|
|
96
|
+
});
|
|
49
97
|
}
|
|
50
98
|
}
|
|
51
99
|
});
|
|
@@ -64,11 +112,43 @@ export class EventsRepository {
|
|
|
64
112
|
endSession(message) {
|
|
65
113
|
const update = this.db.prepare(`
|
|
66
114
|
UPDATE sessions
|
|
67
|
-
SET ended_at =
|
|
115
|
+
SET ended_at = ?, paused_at = NULL
|
|
68
116
|
WHERE session_id = ?
|
|
69
117
|
`);
|
|
70
118
|
update.run(Date.now(), message.sessionId);
|
|
71
119
|
}
|
|
120
|
+
pauseSession(message) {
|
|
121
|
+
const update = this.db.prepare(`
|
|
122
|
+
UPDATE sessions
|
|
123
|
+
SET paused_at = COALESCE(paused_at, ?), ended_at = NULL
|
|
124
|
+
WHERE session_id = ? AND ended_at IS NULL
|
|
125
|
+
`);
|
|
126
|
+
const result = update.run(Date.now(), message.sessionId);
|
|
127
|
+
if (result.changes === 0) {
|
|
128
|
+
throw new Error(`Session not found or already ended: ${message.sessionId}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
resumeSession(message) {
|
|
132
|
+
const update = this.db.prepare(`
|
|
133
|
+
UPDATE sessions
|
|
134
|
+
SET
|
|
135
|
+
paused_at = NULL,
|
|
136
|
+
ended_at = NULL,
|
|
137
|
+
url_last = COALESCE(?, url_last),
|
|
138
|
+
tab_id = COALESCE(?, tab_id),
|
|
139
|
+
window_id = COALESCE(?, window_id),
|
|
140
|
+
user_agent = COALESCE(?, user_agent),
|
|
141
|
+
viewport_w = COALESCE(?, viewport_w),
|
|
142
|
+
viewport_h = COALESCE(?, viewport_h),
|
|
143
|
+
dpr = COALESCE(?, dpr),
|
|
144
|
+
safe_mode = COALESCE(?, safe_mode)
|
|
145
|
+
WHERE session_id = ? AND ended_at IS NULL
|
|
146
|
+
`);
|
|
147
|
+
const result = update.run(message.url ?? null, message.tabId ?? null, message.windowId ?? null, message.userAgent ?? null, message.viewport?.width ?? null, message.viewport?.height ?? null, message.dpr ?? null, message.safeMode === undefined ? null : (message.safeMode ? 1 : 0), message.sessionId);
|
|
148
|
+
if (result.changes === 0) {
|
|
149
|
+
throw new Error(`Session not found or already ended: ${message.sessionId}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
72
152
|
insertEvent(message) {
|
|
73
153
|
this.insertEventsBatch([message]);
|
|
74
154
|
}
|
|
@@ -87,6 +167,11 @@ export class EventsRepository {
|
|
|
87
167
|
blur: 'ui',
|
|
88
168
|
keydown: 'ui',
|
|
89
169
|
ui_snapshot: 'ui',
|
|
170
|
+
automation_requested: 'ui',
|
|
171
|
+
automation_started: 'ui',
|
|
172
|
+
automation_succeeded: 'ui',
|
|
173
|
+
automation_failed: 'ui',
|
|
174
|
+
automation_stopped: 'ui',
|
|
90
175
|
custom: 'ui',
|
|
91
176
|
};
|
|
92
177
|
return mapping[eventType] || 'ui';
|
|
@@ -98,11 +183,94 @@ export class EventsRepository {
|
|
|
98
183
|
const now = Date.now();
|
|
99
184
|
statement.run(fingerprint, sessionId, data.message ?? 'Unknown error', data.stack ?? null, now, now);
|
|
100
185
|
}
|
|
101
|
-
insertNetworkEventPrepared(
|
|
102
|
-
const
|
|
103
|
-
|
|
186
|
+
insertNetworkEventPrepared(networkStatement, bodyChunkStatement, sessionId, data, eventOrigin, eventTabId) {
|
|
187
|
+
const normalized = this.normalizeNetworkInsertInput(sessionId, data, eventOrigin, eventTabId, bodyChunkStatement);
|
|
188
|
+
networkStatement.run(normalized.requestId, sessionId, normalized.traceId, normalized.tabId, normalized.tsStart, normalized.durationMs, normalized.method, normalized.url, normalized.origin, normalized.status, normalized.initiator, normalized.errorClass, normalized.responseSizeEst, normalized.request.contentType, normalized.request.bodyText, normalized.request.bodyJson, normalized.request.bodyBytes, normalized.request.truncated ? 1 : 0, normalized.request.chunkRef, normalized.response.contentType, normalized.response.bodyText, normalized.response.bodyJson, normalized.response.bodyBytes, normalized.response.truncated ? 1 : 0, normalized.response.chunkRef);
|
|
189
|
+
}
|
|
190
|
+
normalizeNetworkInsertInput(sessionId, data, eventOrigin, eventTabId, bodyChunkStatement) {
|
|
191
|
+
const requestId = toNonEmptyString(data.requestId)
|
|
192
|
+
?? `${sessionId}-net-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
193
|
+
const traceId = toNonEmptyString(data.traceId) ?? null;
|
|
194
|
+
const url = toNonEmptyString(data.url) ?? '';
|
|
104
195
|
const origin = eventOrigin ?? normalizeOriginCandidate(url);
|
|
105
|
-
|
|
196
|
+
const request = this.processNetworkBody({
|
|
197
|
+
sessionId,
|
|
198
|
+
requestId,
|
|
199
|
+
traceId,
|
|
200
|
+
bodyKind: BODY_KIND_REQUEST,
|
|
201
|
+
contentType: toNullableContentType(data.requestContentType),
|
|
202
|
+
bodyText: toNonEmptyString(data.requestBodyText),
|
|
203
|
+
bodyJson: toRecordLike(data.requestBodyJson),
|
|
204
|
+
bodyBytes: toNullableInteger(data.requestBodyBytes),
|
|
205
|
+
truncated: data.requestBodyTruncated === true,
|
|
206
|
+
bodyChunkStatement,
|
|
207
|
+
});
|
|
208
|
+
const response = this.processNetworkBody({
|
|
209
|
+
sessionId,
|
|
210
|
+
requestId,
|
|
211
|
+
traceId,
|
|
212
|
+
bodyKind: BODY_KIND_RESPONSE,
|
|
213
|
+
contentType: toNullableContentType(data.responseContentType),
|
|
214
|
+
bodyText: toNonEmptyString(data.responseBodyText),
|
|
215
|
+
bodyJson: toRecordLike(data.responseBodyJson),
|
|
216
|
+
bodyBytes: toNullableInteger(data.responseBodyBytes),
|
|
217
|
+
truncated: data.responseBodyTruncated === true,
|
|
218
|
+
bodyChunkStatement,
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
requestId,
|
|
222
|
+
traceId,
|
|
223
|
+
tabId: eventTabId,
|
|
224
|
+
tsStart: toNullableInteger(data.timestamp) ?? Date.now(),
|
|
225
|
+
durationMs: toNullableInteger(data.duration),
|
|
226
|
+
method: normalizeMethod(toNonEmptyString(data.method)),
|
|
227
|
+
url,
|
|
228
|
+
origin,
|
|
229
|
+
status: toNullableInteger(data.status),
|
|
230
|
+
initiator: normalizeInitiator(toNonEmptyString(data.initiator)),
|
|
231
|
+
errorClass: normalizeErrorClass(toNonEmptyString(data.errorType)),
|
|
232
|
+
responseSizeEst: toNullableInteger(data.responseSize),
|
|
233
|
+
request,
|
|
234
|
+
response,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
processNetworkBody(params) {
|
|
238
|
+
const redactedJson = params.bodyJson ? sanitizeRecord(params.bodyJson) : null;
|
|
239
|
+
const redactedText = params.bodyText ? redactString(params.bodyText) : null;
|
|
240
|
+
const resolvedText = redactedText
|
|
241
|
+
?? (redactedJson ? JSON.stringify(redactedJson) : null);
|
|
242
|
+
const resolvedBytes = resolvedText ? utf8Bytes(resolvedText) : params.bodyBytes;
|
|
243
|
+
const resolvedJsonText = redactedJson ? JSON.stringify(redactedJson) : null;
|
|
244
|
+
if (!resolvedText && !resolvedJsonText && resolvedBytes === null) {
|
|
245
|
+
return {
|
|
246
|
+
contentType: params.contentType,
|
|
247
|
+
bodyText: null,
|
|
248
|
+
bodyJson: null,
|
|
249
|
+
bodyBytes: null,
|
|
250
|
+
truncated: params.truncated,
|
|
251
|
+
chunkRef: null,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (resolvedText && resolvedBytes !== null && resolvedBytes > INLINE_BODY_BYTES_THRESHOLD) {
|
|
255
|
+
const chunkRef = `${params.requestId}:${params.bodyKind}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
256
|
+
params.bodyChunkStatement.run(chunkRef, params.sessionId, params.requestId, params.traceId, params.bodyKind, params.contentType, resolvedText, resolvedBytes, params.truncated ? 1 : 0, Date.now());
|
|
257
|
+
return {
|
|
258
|
+
contentType: params.contentType,
|
|
259
|
+
bodyText: null,
|
|
260
|
+
bodyJson: null,
|
|
261
|
+
bodyBytes: resolvedBytes,
|
|
262
|
+
truncated: params.truncated,
|
|
263
|
+
chunkRef,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
contentType: params.contentType,
|
|
268
|
+
bodyText: resolvedText,
|
|
269
|
+
bodyJson: resolvedJsonText,
|
|
270
|
+
bodyBytes: resolvedBytes,
|
|
271
|
+
truncated: params.truncated,
|
|
272
|
+
chunkRef: null,
|
|
273
|
+
};
|
|
106
274
|
}
|
|
107
275
|
insertSnapshotPrepared(sessionId, triggerEventId, data) {
|
|
108
276
|
writeSnapshot(this.db, getDatabasePath(), sessionId, data, triggerEventId);
|
|
@@ -144,4 +312,85 @@ function normalizeOriginCandidate(value) {
|
|
|
144
312
|
return null;
|
|
145
313
|
}
|
|
146
314
|
}
|
|
315
|
+
function toNonEmptyString(value) {
|
|
316
|
+
if (typeof value !== 'string') {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const trimmed = value.trim();
|
|
320
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
321
|
+
}
|
|
322
|
+
function toNullableInteger(value) {
|
|
323
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return Math.floor(value);
|
|
327
|
+
}
|
|
328
|
+
function toNullableContentType(value) {
|
|
329
|
+
const normalized = toNonEmptyString(value);
|
|
330
|
+
if (!normalized) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
return normalized.toLowerCase();
|
|
334
|
+
}
|
|
335
|
+
function toRecordLike(value) {
|
|
336
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
337
|
+
return value;
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
function utf8Bytes(value) {
|
|
342
|
+
return Buffer.byteLength(value, 'utf-8');
|
|
343
|
+
}
|
|
344
|
+
function normalizeMethod(value) {
|
|
345
|
+
return value ? value.toUpperCase() : 'GET';
|
|
346
|
+
}
|
|
347
|
+
function normalizeInitiator(value) {
|
|
348
|
+
if (!value) {
|
|
349
|
+
return 'other';
|
|
350
|
+
}
|
|
351
|
+
const normalized = value.toLowerCase();
|
|
352
|
+
if (normalized === 'fetch' || normalized === 'xhr' || normalized === 'img' || normalized === 'script' || normalized === 'other') {
|
|
353
|
+
return normalized;
|
|
354
|
+
}
|
|
355
|
+
return 'other';
|
|
356
|
+
}
|
|
357
|
+
function normalizeErrorClass(value) {
|
|
358
|
+
if (!value) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const normalized = value.toLowerCase();
|
|
362
|
+
if (normalized === 'timeout' || normalized === 'cors' || normalized === 'dns' || normalized === 'blocked' || normalized === 'http_error' || normalized === 'unknown') {
|
|
363
|
+
return normalized;
|
|
364
|
+
}
|
|
365
|
+
return 'unknown';
|
|
366
|
+
}
|
|
367
|
+
function sanitizeRecord(value) {
|
|
368
|
+
return sanitizeValue(value, 'root');
|
|
369
|
+
}
|
|
370
|
+
function sanitizeValue(value, key) {
|
|
371
|
+
if (typeof value === 'string') {
|
|
372
|
+
if (SENSITIVE_FIELD_NAMES.has(key.toLowerCase())) {
|
|
373
|
+
return '[REDACTED]';
|
|
374
|
+
}
|
|
375
|
+
return redactString(value);
|
|
376
|
+
}
|
|
377
|
+
if (Array.isArray(value)) {
|
|
378
|
+
return value.map((entry) => sanitizeValue(entry, key));
|
|
379
|
+
}
|
|
380
|
+
if (value && typeof value === 'object') {
|
|
381
|
+
const result = {};
|
|
382
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
383
|
+
result[entryKey] = sanitizeValue(entryValue, entryKey);
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
return value;
|
|
388
|
+
}
|
|
389
|
+
function redactString(value) {
|
|
390
|
+
let result = value;
|
|
391
|
+
for (const rule of REDACTION_PATTERNS) {
|
|
392
|
+
result = result.replace(rule.pattern, rule.replacement);
|
|
393
|
+
}
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
147
396
|
//# sourceMappingURL=events-repository.js.map
|