browser-debug-mcp-bridge 1.1.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 +15 -0
- package/README.md +101 -0
- package/apps/mcp-server/dist/db/connection.js +42 -0
- package/apps/mcp-server/dist/db/connection.js.map +1 -0
- package/apps/mcp-server/dist/db/error-fingerprints.js +21 -0
- package/apps/mcp-server/dist/db/error-fingerprints.js.map +1 -0
- package/apps/mcp-server/dist/db/events-repository.js +109 -0
- package/apps/mcp-server/dist/db/events-repository.js.map +1 -0
- package/apps/mcp-server/dist/db/index.js +4 -0
- package/apps/mcp-server/dist/db/index.js.map +1 -0
- package/apps/mcp-server/dist/db/migrations.js +101 -0
- package/apps/mcp-server/dist/db/migrations.js.map +1 -0
- package/apps/mcp-server/dist/db/schema.js +157 -0
- package/apps/mcp-server/dist/db/schema.js.map +1 -0
- package/apps/mcp-server/dist/main.js +384 -0
- package/apps/mcp-server/dist/main.js.map +1 -0
- package/apps/mcp-server/dist/mcp/server.js +1619 -0
- package/apps/mcp-server/dist/mcp/server.js.map +1 -0
- package/apps/mcp-server/dist/mcp-bridge.js +55 -0
- package/apps/mcp-server/dist/mcp-bridge.js.map +1 -0
- package/apps/mcp-server/dist/retention.js +841 -0
- package/apps/mcp-server/dist/retention.js.map +1 -0
- package/apps/mcp-server/dist/websocket/index.js +3 -0
- package/apps/mcp-server/dist/websocket/index.js.map +1 -0
- package/apps/mcp-server/dist/websocket/messages.js +150 -0
- package/apps/mcp-server/dist/websocket/messages.js.map +1 -0
- package/apps/mcp-server/dist/websocket/websocket-server.js +302 -0
- package/apps/mcp-server/dist/websocket/websocket-server.js.map +1 -0
- package/apps/mcp-server/package.json +28 -0
- package/package.json +88 -0
- package/scripts/mcp-start.cjs +229 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
11
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Browser Debug MCP Bridge
|
|
2
|
+
|
|
3
|
+
Chrome Extension + local Node.js MCP server that captures browser debugging telemetry from a real user session and exposes it through MCP tools.
|
|
4
|
+
|
|
5
|
+
## Why this project exists
|
|
6
|
+
|
|
7
|
+
- Debug with real browser context (logged-in sessions, feature flags, extensions)
|
|
8
|
+
- Store lightweight telemetry continuously, request heavy DOM data on demand
|
|
9
|
+
- Keep privacy-first defaults with safe mode, allowlists, and redaction
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- Node.js 20+
|
|
14
|
+
- pnpm 9+
|
|
15
|
+
- Chrome (for the extension)
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm install
|
|
21
|
+
pnpm nx serve mcp-server
|
|
22
|
+
pnpm nx build chrome-extension --watch
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run unified ingest + MCP stdio runtime (for external MCP clients):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm install
|
|
29
|
+
node scripts/mcp-start.cjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Quick npm MCP launch (marketplace-style, after publish):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx -y browser-debug-mcp-bridge
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
GitHub fallback launch (if npm package is not available yet):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx -y --package=github:RobertoM80/browser-debug-mcp-bridge browser-debug-mcp-bridge
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Optional one-step setup scripts:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Windows (PowerShell)
|
|
48
|
+
./install.ps1
|
|
49
|
+
|
|
50
|
+
# macOS/Linux
|
|
51
|
+
bash ./install.sh
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Useful workspace commands:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm typecheck
|
|
58
|
+
pnpm test
|
|
59
|
+
pnpm nx run-many -t lint
|
|
60
|
+
pnpm nx run-many -t build
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Enable local pre-commit checks (typecheck + lint + test before each commit):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm hooks:install
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Load the extension
|
|
70
|
+
|
|
71
|
+
1. Build the extension: `pnpm nx build chrome-extension`
|
|
72
|
+
2. Open Chrome -> `chrome://extensions`
|
|
73
|
+
3. Enable Developer mode
|
|
74
|
+
4. Click **Load unpacked**
|
|
75
|
+
5. Select `dist/apps/chrome-extension`
|
|
76
|
+
|
|
77
|
+
## Main docs
|
|
78
|
+
|
|
79
|
+
- Project spec: `PROJECT_INFOS.md`
|
|
80
|
+
- Full beginner setup guide: `HOW_TO_USE_BROWSER_DEBUG_MCP_BRIDGE.md`
|
|
81
|
+
- MCP tools reference: `docs/MCP_TOOLS.md`
|
|
82
|
+
- MCP client setup (Codex/Claude/Cursor/Windsurf): `docs/MCP_CLIENT_SETUP.md`
|
|
83
|
+
- GitHub Actions explained: `docs/GITHUB_ACTIONS.md`
|
|
84
|
+
- Security and privacy controls: `SECURITY.md`
|
|
85
|
+
- Troubleshooting guide: `docs/TROUBLESHOOTING.md`
|
|
86
|
+
- Architecture overview: `docs/ARCHITECTURE.md`
|
|
87
|
+
- Architecture decisions: `docs/ARCHITECTURE_DECISIONS.md`
|
|
88
|
+
|
|
89
|
+
## Repository layout
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
apps/
|
|
93
|
+
mcp-server/ Fastify + WebSocket ingest + MCP server
|
|
94
|
+
chrome-extension/ MV3 extension (background/content/injected)
|
|
95
|
+
viewer/ Optional UI
|
|
96
|
+
libs/
|
|
97
|
+
shared/ Shared schemas/types/utils
|
|
98
|
+
redaction/ Privacy redaction engine
|
|
99
|
+
selectors/ Robust selector generation
|
|
100
|
+
mcp-contracts/ MCP tool contracts and schemas
|
|
101
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { dirname } from 'path';
|
|
4
|
+
import { mkdirSync } from 'fs';
|
|
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
|
+
export function createConnection(dbPath) {
|
|
11
|
+
const path = dbPath || getDatabasePath();
|
|
12
|
+
if (path !== ':memory:') {
|
|
13
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
const db = new Database(path);
|
|
16
|
+
db.pragma('journal_mode = WAL');
|
|
17
|
+
db.pragma('foreign_keys = ON');
|
|
18
|
+
return {
|
|
19
|
+
db,
|
|
20
|
+
isConnected: true
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function getConnection() {
|
|
24
|
+
if (!connection) {
|
|
25
|
+
connection = createConnection();
|
|
26
|
+
}
|
|
27
|
+
return connection;
|
|
28
|
+
}
|
|
29
|
+
export function closeConnection() {
|
|
30
|
+
if (connection) {
|
|
31
|
+
connection.db.close();
|
|
32
|
+
connection = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function isConnected() {
|
|
36
|
+
return connection !== null && connection.isConnected;
|
|
37
|
+
}
|
|
38
|
+
export function resetConnection() {
|
|
39
|
+
closeConnection();
|
|
40
|
+
connection = null;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAO/B,IAAI,UAAU,GAA8B,IAAI,CAAC;AAEjD,MAAM,UAAU,eAAe;IAC7B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;IACpE,OAAO,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;AAC3C,CAAC;AAED,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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
function normalizeText(value) {
|
|
3
|
+
return value.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
4
|
+
}
|
|
5
|
+
export function createErrorFingerprint(message, stack) {
|
|
6
|
+
const normalizedMessage = normalizeText(message || 'unknown error');
|
|
7
|
+
const normalizedStack = normalizeText(stack || '');
|
|
8
|
+
const source = `${normalizedMessage}\n${normalizedStack}`;
|
|
9
|
+
const digest = createHash('sha256').update(source).digest('hex').slice(0, 16);
|
|
10
|
+
return `fp-${digest}`;
|
|
11
|
+
}
|
|
12
|
+
export function resolveErrorFingerprint(data) {
|
|
13
|
+
const provided = typeof data.fingerprint === 'string' ? data.fingerprint.trim() : '';
|
|
14
|
+
if (provided) {
|
|
15
|
+
return provided;
|
|
16
|
+
}
|
|
17
|
+
const message = typeof data.message === 'string' ? data.message : 'Unknown error';
|
|
18
|
+
const stack = typeof data.stack === 'string' ? data.stack : undefined;
|
|
19
|
+
return createErrorFingerprint(message, stack);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=error-fingerprints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-fingerprints.js","sourceRoot":"","sources":["../../src/db/error-fingerprints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAe,EAAE,KAAc;IACpE,MAAM,iBAAiB,GAAG,aAAa,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC;IACpE,MAAM,eAAe,GAAG,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,GAAG,iBAAiB,KAAK,eAAe,EAAE,CAAC;IAC1D,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9E,OAAO,MAAM,MAAM,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAA6B;IACnE,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACrF,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;IAClF,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAEtE,OAAO,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { resolveErrorFingerprint } from './error-fingerprints';
|
|
2
|
+
import { getDatabasePath } from './connection';
|
|
3
|
+
import { writeSnapshot } from '../retention';
|
|
4
|
+
export class EventsRepository {
|
|
5
|
+
db;
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
}
|
|
9
|
+
insertEventsBatch(messages) {
|
|
10
|
+
if (messages.length === 0) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const insert = this.db.prepare(`
|
|
14
|
+
INSERT INTO events (event_id, session_id, ts, type, payload_json)
|
|
15
|
+
VALUES (?, ?, ?, ?, ?)
|
|
16
|
+
`);
|
|
17
|
+
const insertNetwork = this.db.prepare(`
|
|
18
|
+
INSERT INTO network (
|
|
19
|
+
request_id, session_id, ts_start, duration_ms, method, url, status, initiator, error_class, response_size_est
|
|
20
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21
|
+
`);
|
|
22
|
+
const upsertFingerprint = this.db.prepare(`
|
|
23
|
+
INSERT INTO error_fingerprints (
|
|
24
|
+
fingerprint, session_id, count, sample_message, sample_stack, first_seen_at, last_seen_at
|
|
25
|
+
) VALUES (?, ?, 1, ?, ?, ?, ?)
|
|
26
|
+
ON CONFLICT(fingerprint) DO UPDATE SET
|
|
27
|
+
count = count + 1,
|
|
28
|
+
last_seen_at = excluded.last_seen_at,
|
|
29
|
+
sample_message = COALESCE(error_fingerprints.sample_message, excluded.sample_message),
|
|
30
|
+
sample_stack = COALESCE(error_fingerprints.sample_stack, excluded.sample_stack)
|
|
31
|
+
`);
|
|
32
|
+
const runBatch = this.db.transaction((batch) => {
|
|
33
|
+
for (const message of batch) {
|
|
34
|
+
const eventId = `${message.sessionId}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
35
|
+
const dbEventType = this.mapEventType(message.eventType);
|
|
36
|
+
insert.run(eventId, message.sessionId, message.timestamp ?? Date.now(), dbEventType, JSON.stringify(message.data));
|
|
37
|
+
if (message.eventType === 'error') {
|
|
38
|
+
this.upsertErrorFingerprintPrepared(upsertFingerprint, message.sessionId, message.data);
|
|
39
|
+
}
|
|
40
|
+
if (message.eventType === 'network') {
|
|
41
|
+
this.insertNetworkEventPrepared(insertNetwork, message.sessionId, message.data);
|
|
42
|
+
}
|
|
43
|
+
if (message.eventType === 'ui_snapshot') {
|
|
44
|
+
this.insertSnapshotPrepared(message.sessionId, eventId, message.data);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
runBatch(messages);
|
|
49
|
+
}
|
|
50
|
+
createSession(message) {
|
|
51
|
+
const insert = this.db.prepare(`
|
|
52
|
+
INSERT INTO sessions (
|
|
53
|
+
session_id, created_at, tab_id, window_id, url_start, url_last,
|
|
54
|
+
user_agent, viewport_w, viewport_h, dpr, safe_mode, allowlist_hash
|
|
55
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
56
|
+
`);
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
insert.run(message.sessionId, now, message.tabId ?? null, message.windowId ?? null, message.url, message.url, message.userAgent ?? null, message.viewport?.width ?? null, message.viewport?.height ?? null, message.dpr ?? null, message.safeMode ? 1 : 0, null);
|
|
59
|
+
}
|
|
60
|
+
endSession(message) {
|
|
61
|
+
const update = this.db.prepare(`
|
|
62
|
+
UPDATE sessions
|
|
63
|
+
SET ended_at = ?
|
|
64
|
+
WHERE session_id = ?
|
|
65
|
+
`);
|
|
66
|
+
update.run(Date.now(), message.sessionId);
|
|
67
|
+
}
|
|
68
|
+
insertEvent(message) {
|
|
69
|
+
this.insertEventsBatch([message]);
|
|
70
|
+
}
|
|
71
|
+
mapEventType(eventType) {
|
|
72
|
+
const mapping = {
|
|
73
|
+
'navigation': 'nav',
|
|
74
|
+
'console': 'console',
|
|
75
|
+
'error': 'error',
|
|
76
|
+
'network': 'network',
|
|
77
|
+
'click': 'ui',
|
|
78
|
+
'scroll': 'ui',
|
|
79
|
+
'input': 'ui',
|
|
80
|
+
'change': 'ui',
|
|
81
|
+
'submit': 'ui',
|
|
82
|
+
'focus': 'ui',
|
|
83
|
+
'blur': 'ui',
|
|
84
|
+
'keydown': 'ui',
|
|
85
|
+
'ui_snapshot': 'ui',
|
|
86
|
+
'custom': 'ui',
|
|
87
|
+
};
|
|
88
|
+
return mapping[eventType] || 'ui';
|
|
89
|
+
}
|
|
90
|
+
upsertErrorFingerprintPrepared(statement, sessionId, data) {
|
|
91
|
+
const fingerprint = resolveErrorFingerprint(data);
|
|
92
|
+
if (!fingerprint)
|
|
93
|
+
return;
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
statement.run(fingerprint, sessionId, data.message ?? 'Unknown error', data.stack ?? null, now, now);
|
|
96
|
+
}
|
|
97
|
+
insertNetworkEventPrepared(statement, sessionId, data) {
|
|
98
|
+
const requestId = `${sessionId}-net-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
99
|
+
statement.run(requestId, sessionId, data.timestamp ?? Date.now(), data.duration ?? null, data.method ?? 'GET', data.url ?? '', data.status ?? null, data.initiator ?? 'other', data.errorType ?? null, data.responseSize ?? null);
|
|
100
|
+
}
|
|
101
|
+
insertSnapshotPrepared(sessionId, triggerEventId, data) {
|
|
102
|
+
writeSnapshot(this.db, getDatabasePath(), sessionId, data, triggerEventId);
|
|
103
|
+
}
|
|
104
|
+
sessionExists(sessionId) {
|
|
105
|
+
const result = this.db.prepare('SELECT 1 FROM sessions WHERE session_id = ?').get(sessionId);
|
|
106
|
+
return !!result;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=events-repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-repository.js","sourceRoot":"","sources":["../../src/db/events-repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { initializeSchema, getSchemaVersion, clearDatabase, SCHEMA_VERSION } from './schema';
|
|
2
|
+
const migrations = [
|
|
3
|
+
{
|
|
4
|
+
version: 1,
|
|
5
|
+
name: 'initial_schema',
|
|
6
|
+
up: initializeSchema
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
version: 2,
|
|
10
|
+
name: 'retention_and_pinning',
|
|
11
|
+
up: (db) => {
|
|
12
|
+
const hasPinnedColumn = db.prepare("PRAGMA table_info('sessions')").all().some((column) => column.name === 'pinned');
|
|
13
|
+
if (!hasPinnedColumn) {
|
|
14
|
+
db.exec(`
|
|
15
|
+
ALTER TABLE sessions ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;
|
|
16
|
+
`);
|
|
17
|
+
}
|
|
18
|
+
db.exec(`
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_pinned_created_at ON sessions(pinned, created_at);
|
|
20
|
+
`);
|
|
21
|
+
db.exec(`
|
|
22
|
+
CREATE TABLE IF NOT EXISTS server_settings (
|
|
23
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
24
|
+
retention_days INTEGER NOT NULL DEFAULT 30,
|
|
25
|
+
max_db_mb INTEGER NOT NULL DEFAULT 1024,
|
|
26
|
+
max_sessions INTEGER NOT NULL DEFAULT 10000,
|
|
27
|
+
cleanup_interval_minutes INTEGER NOT NULL DEFAULT 60,
|
|
28
|
+
last_cleanup_at INTEGER,
|
|
29
|
+
export_path_override TEXT
|
|
30
|
+
);
|
|
31
|
+
`);
|
|
32
|
+
db.exec(`
|
|
33
|
+
INSERT OR IGNORE INTO server_settings (
|
|
34
|
+
id,
|
|
35
|
+
retention_days,
|
|
36
|
+
max_db_mb,
|
|
37
|
+
max_sessions,
|
|
38
|
+
cleanup_interval_minutes,
|
|
39
|
+
last_cleanup_at,
|
|
40
|
+
export_path_override
|
|
41
|
+
) VALUES (1, 30, 1024, 10000, 60, NULL, NULL);
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
version: 3,
|
|
47
|
+
name: 'snapshots_storage',
|
|
48
|
+
up: (db) => {
|
|
49
|
+
db.exec(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
51
|
+
snapshot_id TEXT PRIMARY KEY,
|
|
52
|
+
session_id TEXT NOT NULL,
|
|
53
|
+
trigger_event_id TEXT,
|
|
54
|
+
ts INTEGER NOT NULL,
|
|
55
|
+
trigger TEXT NOT NULL,
|
|
56
|
+
selector TEXT,
|
|
57
|
+
url TEXT,
|
|
58
|
+
mode TEXT NOT NULL,
|
|
59
|
+
style_mode TEXT,
|
|
60
|
+
dom_json TEXT,
|
|
61
|
+
styles_json TEXT,
|
|
62
|
+
png_path TEXT,
|
|
63
|
+
png_mime TEXT,
|
|
64
|
+
png_bytes INTEGER,
|
|
65
|
+
dom_truncated INTEGER NOT NULL DEFAULT 0,
|
|
66
|
+
styles_truncated INTEGER NOT NULL DEFAULT 0,
|
|
67
|
+
png_truncated INTEGER NOT NULL DEFAULT 0,
|
|
68
|
+
created_at INTEGER NOT NULL,
|
|
69
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE,
|
|
70
|
+
FOREIGN KEY (trigger_event_id) REFERENCES events(event_id) ON DELETE SET NULL
|
|
71
|
+
);
|
|
72
|
+
`);
|
|
73
|
+
db.exec(`
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_session_ts ON snapshots(session_id, ts);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_session_trigger_ts ON snapshots(session_id, trigger, ts);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_png_path ON snapshots(png_path);
|
|
77
|
+
`);
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
export function runMigrations(db) {
|
|
82
|
+
const currentVersion = getSchemaVersion(db) || 0;
|
|
83
|
+
const pendingMigrations = migrations.filter(m => m.version > currentVersion);
|
|
84
|
+
for (const migration of pendingMigrations) {
|
|
85
|
+
migration.up(db);
|
|
86
|
+
const insertVersion = db.prepare(`
|
|
87
|
+
INSERT INTO schema_version (version, applied_at)
|
|
88
|
+
VALUES (?, ?)
|
|
89
|
+
`);
|
|
90
|
+
insertVersion.run(migration.version, Date.now());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function initializeDatabase(db) {
|
|
94
|
+
runMigrations(db);
|
|
95
|
+
}
|
|
96
|
+
export function resetDatabase(db) {
|
|
97
|
+
clearDatabase(db);
|
|
98
|
+
initializeDatabase(db);
|
|
99
|
+
}
|
|
100
|
+
export { getSchemaVersion, clearDatabase, SCHEMA_VERSION };
|
|
101
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAQ7F,MAAM,UAAU,GAAgB;IAC9B;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,gBAAgB;QACtB,EAAE,EAAE,gBAAgB;KACrB;IACD;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,uBAAuB;QAC7B,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;YACT,MAAM,eAAe,GAAI,EAAE,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,GAAG,EAA8B,CAAC,IAAI,CACzG,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,CACrC,CAAC;YACF,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,EAAE,CAAC,IAAI,CAAC;;SAEP,CAAC,CAAC;YACL,CAAC;YACD,EAAE,CAAC,IAAI,CAAC;;OAEP,CAAC,CAAC;YACH,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;OAUP,CAAC,CAAC;YACH,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;OAUP,CAAC,CAAC;QACL,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,mBAAmB;QACzB,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;YACT,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;OAuBP,CAAC,CAAC;YACH,EAAE,CAAC,IAAI,CAAC;;;;OAIP,CAAC,CAAC;QACL,CAAC;KACF;CACF,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,EAAY;IACxC,MAAM,cAAc,GAAG,gBAAgB,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAEjD,MAAM,iBAAiB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,cAAc,CAAC,CAAC;IAE7E,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE,CAAC;QAC1C,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAEjB,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC;;;KAGhC,CAAC,CAAC;QACH,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,EAAY;IAC7C,aAAa,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAY;IACxC,aAAa,CAAC,EAAE,CAAC,CAAC;IAClB,kBAAkB,CAAC,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export const SCHEMA_VERSION = 3;
|
|
2
|
+
export const CREATE_TABLES_SQL = `
|
|
3
|
+
-- Sessions table
|
|
4
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
5
|
+
session_id TEXT PRIMARY KEY,
|
|
6
|
+
created_at INTEGER NOT NULL,
|
|
7
|
+
ended_at INTEGER,
|
|
8
|
+
tab_id INTEGER,
|
|
9
|
+
window_id INTEGER,
|
|
10
|
+
url_start TEXT,
|
|
11
|
+
url_last TEXT,
|
|
12
|
+
user_agent TEXT,
|
|
13
|
+
viewport_w INTEGER,
|
|
14
|
+
viewport_h INTEGER,
|
|
15
|
+
dpr REAL,
|
|
16
|
+
safe_mode INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
allowlist_hash TEXT,
|
|
18
|
+
pinned INTEGER NOT NULL DEFAULT 0
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_created_at ON sessions(created_at);
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_ended_at ON sessions(ended_at);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_pinned_created_at ON sessions(pinned, created_at);
|
|
24
|
+
|
|
25
|
+
-- Server settings table
|
|
26
|
+
CREATE TABLE IF NOT EXISTS server_settings (
|
|
27
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
28
|
+
retention_days INTEGER NOT NULL DEFAULT 30,
|
|
29
|
+
max_db_mb INTEGER NOT NULL DEFAULT 1024,
|
|
30
|
+
max_sessions INTEGER NOT NULL DEFAULT 10000,
|
|
31
|
+
cleanup_interval_minutes INTEGER NOT NULL DEFAULT 60,
|
|
32
|
+
last_cleanup_at INTEGER,
|
|
33
|
+
export_path_override TEXT
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
INSERT OR IGNORE INTO server_settings (
|
|
37
|
+
id,
|
|
38
|
+
retention_days,
|
|
39
|
+
max_db_mb,
|
|
40
|
+
max_sessions,
|
|
41
|
+
cleanup_interval_minutes,
|
|
42
|
+
last_cleanup_at,
|
|
43
|
+
export_path_override
|
|
44
|
+
) VALUES (1, 30, 1024, 10000, 60, NULL, NULL);
|
|
45
|
+
|
|
46
|
+
-- Events table
|
|
47
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
48
|
+
event_id TEXT PRIMARY KEY,
|
|
49
|
+
session_id TEXT NOT NULL,
|
|
50
|
+
ts INTEGER NOT NULL,
|
|
51
|
+
type TEXT NOT NULL CHECK(type IN ('console', 'error', 'network', 'nav', 'ui', 'element_ref')),
|
|
52
|
+
payload_json TEXT NOT NULL,
|
|
53
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_events_session_id ON events(session_id);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_events_type ON events(type);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_events_session_type ON events(session_id, type);
|
|
60
|
+
|
|
61
|
+
-- Network table
|
|
62
|
+
CREATE TABLE IF NOT EXISTS network (
|
|
63
|
+
request_id TEXT PRIMARY KEY,
|
|
64
|
+
session_id TEXT NOT NULL,
|
|
65
|
+
ts_start INTEGER NOT NULL,
|
|
66
|
+
duration_ms INTEGER,
|
|
67
|
+
method TEXT NOT NULL,
|
|
68
|
+
url TEXT NOT NULL,
|
|
69
|
+
status INTEGER,
|
|
70
|
+
initiator TEXT CHECK(initiator IN ('fetch', 'xhr', 'img', 'script', 'other')),
|
|
71
|
+
error_class TEXT CHECK(error_class IN ('timeout', 'cors', 'dns', 'blocked', 'http_error', 'unknown')),
|
|
72
|
+
response_size_est INTEGER,
|
|
73
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_network_session_id ON network(session_id);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_network_url ON network(url);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_network_ts_start ON network(ts_start);
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_network_error_class ON network(error_class);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_network_session_error ON network(session_id, error_class);
|
|
81
|
+
|
|
82
|
+
-- Error fingerprints table
|
|
83
|
+
CREATE TABLE IF NOT EXISTS error_fingerprints (
|
|
84
|
+
fingerprint TEXT PRIMARY KEY,
|
|
85
|
+
session_id TEXT NOT NULL,
|
|
86
|
+
count INTEGER NOT NULL DEFAULT 1,
|
|
87
|
+
sample_message TEXT NOT NULL,
|
|
88
|
+
sample_stack TEXT,
|
|
89
|
+
first_seen_at INTEGER NOT NULL,
|
|
90
|
+
last_seen_at INTEGER NOT NULL,
|
|
91
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_error_fingerprints_session_id ON error_fingerprints(session_id);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_error_fingerprints_count ON error_fingerprints(count);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_error_fingerprints_last_seen ON error_fingerprints(last_seen_at);
|
|
97
|
+
|
|
98
|
+
-- UI snapshots table
|
|
99
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
100
|
+
snapshot_id TEXT PRIMARY KEY,
|
|
101
|
+
session_id TEXT NOT NULL,
|
|
102
|
+
trigger_event_id TEXT,
|
|
103
|
+
ts INTEGER NOT NULL,
|
|
104
|
+
trigger TEXT NOT NULL,
|
|
105
|
+
selector TEXT,
|
|
106
|
+
url TEXT,
|
|
107
|
+
mode TEXT NOT NULL,
|
|
108
|
+
style_mode TEXT,
|
|
109
|
+
dom_json TEXT,
|
|
110
|
+
styles_json TEXT,
|
|
111
|
+
png_path TEXT,
|
|
112
|
+
png_mime TEXT,
|
|
113
|
+
png_bytes INTEGER,
|
|
114
|
+
dom_truncated INTEGER NOT NULL DEFAULT 0,
|
|
115
|
+
styles_truncated INTEGER NOT NULL DEFAULT 0,
|
|
116
|
+
png_truncated INTEGER NOT NULL DEFAULT 0,
|
|
117
|
+
created_at INTEGER NOT NULL,
|
|
118
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE,
|
|
119
|
+
FOREIGN KEY (trigger_event_id) REFERENCES events(event_id) ON DELETE SET NULL
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_session_ts ON snapshots(session_id, ts);
|
|
123
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_session_trigger_ts ON snapshots(session_id, trigger, ts);
|
|
124
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_png_path ON snapshots(png_path);
|
|
125
|
+
|
|
126
|
+
-- Schema version tracking
|
|
127
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
128
|
+
version INTEGER PRIMARY KEY,
|
|
129
|
+
applied_at INTEGER NOT NULL
|
|
130
|
+
);
|
|
131
|
+
`;
|
|
132
|
+
export function initializeSchema(db) {
|
|
133
|
+
db.exec(CREATE_TABLES_SQL);
|
|
134
|
+
}
|
|
135
|
+
export function getSchemaVersion(db) {
|
|
136
|
+
const tableExists = db.prepare(`
|
|
137
|
+
SELECT name FROM sqlite_master
|
|
138
|
+
WHERE type='table' AND name='schema_version'
|
|
139
|
+
`).get();
|
|
140
|
+
if (!tableExists) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const result = db.prepare('SELECT version FROM schema_version ORDER BY version DESC LIMIT 1').get();
|
|
144
|
+
return result?.version ?? null;
|
|
145
|
+
}
|
|
146
|
+
export function clearDatabase(db) {
|
|
147
|
+
db.exec(`
|
|
148
|
+
DELETE FROM error_fingerprints;
|
|
149
|
+
DELETE FROM network;
|
|
150
|
+
DELETE FROM snapshots;
|
|
151
|
+
DELETE FROM events;
|
|
152
|
+
DELETE FROM sessions;
|
|
153
|
+
DELETE FROM server_settings;
|
|
154
|
+
DELETE FROM schema_version;
|
|
155
|
+
`);
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiIhC,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,EAAY;IAC3C,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAY;IAC3C,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC;;;GAG9B,CAAC,CAAC,GAAG,EAAE,CAAC;IAET,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,kEAAkE,CAAC,CAAC,GAAG,EAAqC,CAAC;IACvI,OAAO,MAAM,EAAE,OAAO,IAAI,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAY;IACxC,EAAE,CAAC,IAAI,CAAC;;;;;;;;GAQP,CAAC,CAAC;AACL,CAAC"}
|