browser-debugger-cli 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +85 -0
  2. package/dist/collectors/console.d.ts +17 -0
  3. package/dist/collectors/console.d.ts.map +1 -0
  4. package/dist/collectors/console.js +65 -0
  5. package/dist/collectors/console.js.map +1 -0
  6. package/dist/collectors/dom.d.ts +21 -0
  7. package/dist/collectors/dom.d.ts.map +1 -0
  8. package/dist/collectors/dom.js +88 -0
  9. package/dist/collectors/dom.js.map +1 -0
  10. package/dist/collectors/network.d.ts +19 -0
  11. package/dist/collectors/network.d.ts.map +1 -0
  12. package/dist/collectors/network.js +114 -0
  13. package/dist/collectors/network.js.map +1 -0
  14. package/dist/connection/cdp.d.ts +99 -0
  15. package/dist/connection/cdp.d.ts.map +1 -0
  16. package/dist/connection/cdp.js +317 -0
  17. package/dist/connection/cdp.js.map +1 -0
  18. package/dist/connection/finder.d.ts +11 -0
  19. package/dist/connection/finder.d.ts.map +1 -0
  20. package/dist/connection/finder.js +23 -0
  21. package/dist/connection/finder.js.map +1 -0
  22. package/dist/connection/launcher.d.ts +30 -0
  23. package/dist/connection/launcher.d.ts.map +1 -0
  24. package/dist/connection/launcher.js +134 -0
  25. package/dist/connection/launcher.js.map +1 -0
  26. package/dist/connection/tabs.d.ts +49 -0
  27. package/dist/connection/tabs.d.ts.map +1 -0
  28. package/dist/connection/tabs.js +216 -0
  29. package/dist/connection/tabs.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +431 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/session/BdgSession.d.ts +62 -0
  35. package/dist/session/BdgSession.d.ts.map +1 -0
  36. package/dist/session/BdgSession.js +172 -0
  37. package/dist/session/BdgSession.js.map +1 -0
  38. package/dist/types.d.ts +159 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/utils/pageLoad.d.ts +21 -0
  43. package/dist/utils/pageLoad.d.ts.map +1 -0
  44. package/dist/utils/pageLoad.js +85 -0
  45. package/dist/utils/pageLoad.js.map +1 -0
  46. package/dist/utils/session.d.ts +104 -0
  47. package/dist/utils/session.d.ts.map +1 -0
  48. package/dist/utils/session.js +244 -0
  49. package/dist/utils/session.js.map +1 -0
  50. package/dist/utils/url.d.ts +6 -0
  51. package/dist/utils/url.d.ts.map +1 -0
  52. package/dist/utils/url.js +11 -0
  53. package/dist/utils/url.js.map +1 -0
  54. package/dist/utils/validation.d.ts +12 -0
  55. package/dist/utils/validation.d.ts.map +1 -0
  56. package/dist/utils/validation.js +20 -0
  57. package/dist/utils/validation.js.map +1 -0
  58. package/package.json +56 -0
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # browser-debugger-cli
2
+
3
+ CLI tool for collecting browser telemetry (DOM, network, console) via Chrome DevTools Protocol.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g browser-debugger-cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Just run bdg - it will auto-launch Chrome if needed
15
+ bdg localhost:3000
16
+
17
+ # Interact with the page, then press Ctrl+C
18
+ ```
19
+
20
+ JSON output with collected network requests, console logs, and DOM will be written to stdout.
21
+
22
+ ### How It Works
23
+
24
+ `bdg` automatically launches Chrome with debugging enabled if it's not already running. You can also manually start Chrome first if you prefer:
25
+
26
+ ```bash
27
+ # macOS
28
+ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
29
+ --remote-debugging-port=9222 \
30
+ --user-data-dir=/tmp/chrome-bdg
31
+
32
+ # Linux
33
+ google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-bdg
34
+
35
+ # Windows
36
+ "C:\Program Files\Google\Chrome\Application\chrome.exe" ^
37
+ --remote-debugging-port=9222 ^
38
+ --user-data-dir=C:\temp\chrome-bdg
39
+ ```
40
+
41
+ If Chrome is already running with debugging enabled, `bdg` will detect and use the existing instance.
42
+
43
+ ## Usage
44
+
45
+ ```bash
46
+ # Collect all telemetry (default)
47
+ bdg localhost:3000
48
+
49
+ # Collect specific data
50
+ bdg dom localhost:3000
51
+ bdg network localhost:3000
52
+ bdg console localhost:3000
53
+
54
+ # Options
55
+ bdg localhost:3000 --port 9223
56
+ bdg localhost:3000 --timeout 30
57
+ ```
58
+
59
+ ## Output
60
+
61
+ ```json
62
+ {
63
+ "success": true,
64
+ "duration": 45230,
65
+ "target": { "url": "...", "title": "..." },
66
+ "data": {
67
+ "network": [...],
68
+ "console": [...],
69
+ "dom": {...}
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## Documentation
75
+
76
+ See [CHROME_SETUP.md](./CHROME_SETUP.md) for Chrome configuration details.
77
+
78
+ ## Requirements
79
+
80
+ - Node.js >= 18.0.0
81
+ - Chrome/Chromium with remote debugging
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,17 @@
1
+ import { CDPConnection } from '../connection/cdp.js';
2
+ import { ConsoleMessage, CleanupFunction } from '../types.js';
3
+ /**
4
+ * Start collecting console messages and exceptions via CDP Runtime domain.
5
+ *
6
+ * Captures console.log, console.error, etc. and JavaScript exceptions thrown in the page.
7
+ *
8
+ * @param cdp - CDP connection instance
9
+ * @param messages - Array to populate with console messages
10
+ * @returns Cleanup function to remove event handlers
11
+ *
12
+ * @remarks
13
+ * - Message limit of 10,000 prevents memory issues in long-running sessions
14
+ * - After limit is reached, new messages are silently dropped (warning logged once)
15
+ */
16
+ export declare function startConsoleCollection(cdp: CDPConnection, messages: ConsoleMessage[]): Promise<CleanupFunction>;
17
+ //# sourceMappingURL=console.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/collectors/console.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EACL,cAAc,EACd,eAAe,EAGhB,MAAM,aAAa,CAAC;AAIrB;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,cAAc,EAAE,GACzB,OAAO,CAAC,eAAe,CAAC,CAoD1B"}
@@ -0,0 +1,65 @@
1
+ const MAX_MESSAGES = 10000; // Prevent memory issues
2
+ /**
3
+ * Start collecting console messages and exceptions via CDP Runtime domain.
4
+ *
5
+ * Captures console.log, console.error, etc. and JavaScript exceptions thrown in the page.
6
+ *
7
+ * @param cdp - CDP connection instance
8
+ * @param messages - Array to populate with console messages
9
+ * @returns Cleanup function to remove event handlers
10
+ *
11
+ * @remarks
12
+ * - Message limit of 10,000 prevents memory issues in long-running sessions
13
+ * - After limit is reached, new messages are silently dropped (warning logged once)
14
+ */
15
+ export async function startConsoleCollection(cdp, messages) {
16
+ const handlers = [];
17
+ // Enable runtime
18
+ await cdp.send('Runtime.enable');
19
+ await cdp.send('Log.enable');
20
+ // Listen for console API calls
21
+ const consoleAPIId = cdp.on('Runtime.consoleAPICalled', (params) => {
22
+ const message = {
23
+ type: params.type,
24
+ text: params.args
25
+ .map((arg) => {
26
+ if (arg.value !== undefined) {
27
+ return String(arg.value);
28
+ }
29
+ if (arg.description !== undefined) {
30
+ return arg.description;
31
+ }
32
+ return '';
33
+ })
34
+ .join(' '),
35
+ timestamp: params.timestamp,
36
+ args: params.args
37
+ };
38
+ if (messages.length < MAX_MESSAGES) {
39
+ messages.push(message);
40
+ }
41
+ else if (messages.length === MAX_MESSAGES) {
42
+ console.error(`Warning: Console message limit reached (${MAX_MESSAGES})`);
43
+ }
44
+ });
45
+ handlers.push({ event: 'Runtime.consoleAPICalled', id: consoleAPIId });
46
+ // Listen for exceptions
47
+ const exceptionId = cdp.on('Runtime.exceptionThrown', (params) => {
48
+ const exception = params.exceptionDetails;
49
+ const message = {
50
+ type: 'error',
51
+ text: exception.text || exception.exception?.description || 'Unknown error',
52
+ timestamp: exception.timestamp || Date.now()
53
+ };
54
+ if (messages.length < MAX_MESSAGES) {
55
+ messages.push(message);
56
+ }
57
+ });
58
+ handlers.push({ event: 'Runtime.exceptionThrown', id: exceptionId });
59
+ // Return cleanup function
60
+ return () => {
61
+ // Remove event handlers
62
+ handlers.forEach(({ event, id }) => cdp.off(event, id));
63
+ };
64
+ }
65
+ //# sourceMappingURL=console.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.js","sourceRoot":"","sources":["../../src/collectors/console.ts"],"names":[],"mappings":"AAQA,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,wBAAwB;AAEpD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAkB,EAClB,QAA0B;IAE1B,MAAM,QAAQ,GAAyC,EAAE,CAAC;IAE1D,iBAAiB;IACjB,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACjC,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAE7B,+BAA+B;IAC/B,MAAM,YAAY,GAAG,GAAG,CAAC,EAAE,CAAC,0BAA0B,EAAE,CAAC,MAAiC,EAAE,EAAE;QAC5F,MAAM,OAAO,GAAmB;YAC9B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;iBACd,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBACX,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;oBAC5B,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC3B,CAAC;gBACD,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;oBAClC,OAAO,GAAG,CAAC,WAAW,CAAC;gBACzB,CAAC;gBACD,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC;iBACD,IAAI,CAAC,GAAG,CAAC;YACZ,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;YACnC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;YAC5C,OAAO,CAAC,KAAK,CAAC,2CAA2C,YAAY,GAAG,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;IAEvE,wBAAwB;IACxB,MAAM,WAAW,GAAG,GAAG,CAAC,EAAE,CAAC,yBAAyB,EAAE,CAAC,MAAgC,EAAE,EAAE;QACzF,MAAM,SAAS,GAAG,MAAM,CAAC,gBAAgB,CAAC;QAC1C,MAAM,OAAO,GAAmB;YAC9B,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,SAAS,CAAC,SAAS,EAAE,WAAW,IAAI,eAAe;YAC3E,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE;SAC7C,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;YACnC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;IAErE,0BAA0B;IAC1B,OAAO,GAAG,EAAE;QACV,wBAAwB;QACxB,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { CDPConnection } from '../connection/cdp.js';
2
+ import { DOMData, CleanupFunction } from '../types.js';
3
+ /**
4
+ * Prepare CDP domains for DOM collection.
5
+ *
6
+ * Enables Page, DOM, and Runtime domains required for capturing DOM snapshots.
7
+ *
8
+ * @param cdp - CDP connection instance
9
+ * @returns Cleanup function that disables Runtime domain
10
+ */
11
+ export declare function prepareDOMCollection(cdp: CDPConnection): Promise<CleanupFunction>;
12
+ /**
13
+ * Capture a complete DOM snapshot of the current page.
14
+ *
15
+ * Called during session shutdown to get the final state of the page.
16
+ *
17
+ * @param cdp - CDP connection instance
18
+ * @returns DOM data including URL, title, and full HTML
19
+ */
20
+ export declare function collectDOM(cdp: CDPConnection): Promise<DOMData>;
21
+ //# sourceMappingURL=dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/collectors/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAsBvF;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAoErE"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Prepare CDP domains for DOM collection.
3
+ *
4
+ * Enables Page, DOM, and Runtime domains required for capturing DOM snapshots.
5
+ *
6
+ * @param cdp - CDP connection instance
7
+ * @returns Cleanup function that disables Runtime domain
8
+ */
9
+ export async function prepareDOMCollection(cdp) {
10
+ // Enable Page domain for frame tree
11
+ await cdp.send('Page.enable');
12
+ // Enable DOM domain for document access
13
+ await cdp.send('DOM.enable');
14
+ // Enable Runtime domain for document.title evaluation
15
+ await cdp.send('Runtime.enable');
16
+ // Return cleanup function that disables Runtime domain
17
+ return () => {
18
+ // Disable Runtime domain to clean up resources
19
+ // Note: This is best-effort during shutdown; errors are ignored
20
+ try {
21
+ cdp.send('Runtime.disable').catch(() => {
22
+ // Ignore errors during cleanup (Chrome may be closing)
23
+ });
24
+ }
25
+ catch {
26
+ // Ignore synchronous errors
27
+ }
28
+ };
29
+ }
30
+ /**
31
+ * Capture a complete DOM snapshot of the current page.
32
+ *
33
+ * Called during session shutdown to get the final state of the page.
34
+ *
35
+ * @param cdp - CDP connection instance
36
+ * @returns DOM data including URL, title, and full HTML
37
+ */
38
+ export async function collectDOM(cdp) {
39
+ try {
40
+ // Add a timeout wrapper to prevent hanging
41
+ const timeout = 5000; // 5 seconds should be plenty for DOM capture
42
+ const captureWithTimeout = async (promise, label) => {
43
+ return Promise.race([
44
+ promise,
45
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${timeout}ms`)), timeout))
46
+ ]);
47
+ };
48
+ // Get document
49
+ console.error('Getting document...');
50
+ const { root } = await captureWithTimeout(cdp.send('DOM.getDocument', { depth: -1 }), 'DOM.getDocument');
51
+ console.error(`Got document root (nodeId: ${root.nodeId})`);
52
+ // Get outer HTML
53
+ console.error('Getting outer HTML...');
54
+ const { outerHTML } = await captureWithTimeout(cdp.send('DOM.getOuterHTML', { nodeId: root.nodeId }), 'DOM.getOuterHTML');
55
+ console.error(`Got outer HTML (${outerHTML.length} chars)`);
56
+ // Get page info
57
+ console.error('Getting page info...');
58
+ const frameTree = await captureWithTimeout(cdp.send('Page.getFrameTree'), 'Page.getFrameTree');
59
+ const frame = frameTree.frameTree.frame;
60
+ console.error(`Got page info (url: ${frame.url})`);
61
+ // Get real document title using Runtime.evaluate
62
+ console.error('Getting document title...');
63
+ let title = 'Untitled';
64
+ try {
65
+ const titleResult = await captureWithTimeout(cdp.send('Runtime.evaluate', {
66
+ expression: 'document.title',
67
+ returnByValue: true
68
+ }), 'Runtime.evaluate (document.title)');
69
+ if (titleResult.result && titleResult.result.value) {
70
+ title = titleResult.result.value;
71
+ }
72
+ }
73
+ catch (titleError) {
74
+ console.error('Failed to get document title, using fallback:', titleError);
75
+ }
76
+ console.error(`Got document title: ${title}`);
77
+ return {
78
+ url: frame.url,
79
+ title,
80
+ outerHTML
81
+ };
82
+ }
83
+ catch (error) {
84
+ console.error('Error in collectDOM:', error);
85
+ throw error;
86
+ }
87
+ }
88
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/collectors/dom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { CDPConnection } from '../connection/cdp.js';
2
+ import { NetworkRequest, CleanupFunction } from '../types.js';
3
+ /**
4
+ * Start collecting network requests via CDP Network domain.
5
+ *
6
+ * Tracks all HTTP requests and responses, including headers and bodies (for JSON/text responses).
7
+ * Implements automatic cleanup of stale requests to prevent memory leaks during long sessions.
8
+ *
9
+ * @param cdp - CDP connection instance
10
+ * @param requests - Array to populate with completed network requests
11
+ * @returns Cleanup function to remove event handlers and clear state
12
+ *
13
+ * @remarks
14
+ * - Stale requests (incomplete after 60s) are removed from tracking but NOT added to output
15
+ * - Request limit of 10,000 prevents memory issues in long-running sessions
16
+ * - Response bodies are only fetched for JSON/JavaScript/text MIME types
17
+ */
18
+ export declare function startNetworkCollection(cdp: CDPConnection, requests: NetworkRequest[]): Promise<CleanupFunction>;
19
+ //# sourceMappingURL=network.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/collectors/network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EACL,cAAc,EACd,eAAe,EAKhB,MAAM,aAAa,CAAC;AAKrB;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,cAAc,EAAE,GACzB,OAAO,CAAC,eAAe,CAAC,CAwG1B"}
@@ -0,0 +1,114 @@
1
+ const MAX_REQUESTS = 10000; // Prevent memory issues
2
+ const STALE_REQUEST_TIMEOUT = 60000; // 60 seconds
3
+ /**
4
+ * Start collecting network requests via CDP Network domain.
5
+ *
6
+ * Tracks all HTTP requests and responses, including headers and bodies (for JSON/text responses).
7
+ * Implements automatic cleanup of stale requests to prevent memory leaks during long sessions.
8
+ *
9
+ * @param cdp - CDP connection instance
10
+ * @param requests - Array to populate with completed network requests
11
+ * @returns Cleanup function to remove event handlers and clear state
12
+ *
13
+ * @remarks
14
+ * - Stale requests (incomplete after 60s) are removed from tracking but NOT added to output
15
+ * - Request limit of 10,000 prevents memory issues in long-running sessions
16
+ * - Response bodies are only fetched for JSON/JavaScript/text MIME types
17
+ */
18
+ export async function startNetworkCollection(cdp, requests) {
19
+ const requestMap = new Map();
20
+ const handlers = [];
21
+ // Enable network tracking
22
+ await cdp.send('Network.enable');
23
+ // Periodic cleanup of stale requests (see JSDoc @remarks for behavior)
24
+ const cleanupInterval = setInterval(() => {
25
+ const now = Date.now();
26
+ const staleRequests = [];
27
+ requestMap.forEach((value, requestId) => {
28
+ if (now - value.timestamp > STALE_REQUEST_TIMEOUT) {
29
+ staleRequests.push(requestId);
30
+ }
31
+ });
32
+ if (staleRequests.length > 0) {
33
+ console.error(`Cleaning up ${staleRequests.length} stale network requests`);
34
+ staleRequests.forEach(requestId => requestMap.delete(requestId));
35
+ }
36
+ }, 30000); // Check every 30 seconds
37
+ // Listen for requests
38
+ const requestWillBeSentId = cdp.on('Network.requestWillBeSent', (params) => {
39
+ if (requestMap.size >= MAX_REQUESTS) {
40
+ console.error(`Warning: Network request limit reached (${MAX_REQUESTS}), dropping new requests`);
41
+ return;
42
+ }
43
+ const request = {
44
+ requestId: params.requestId,
45
+ url: params.request.url,
46
+ method: params.request.method,
47
+ timestamp: params.timestamp,
48
+ requestHeaders: params.request.headers,
49
+ requestBody: params.request.postData
50
+ };
51
+ requestMap.set(params.requestId, {
52
+ request,
53
+ timestamp: Date.now()
54
+ });
55
+ });
56
+ handlers.push({ event: 'Network.requestWillBeSent', id: requestWillBeSentId });
57
+ // Listen for responses
58
+ const responseReceivedId = cdp.on('Network.responseReceived', (params) => {
59
+ const entry = requestMap.get(params.requestId);
60
+ if (entry) {
61
+ entry.request.status = params.response.status;
62
+ entry.request.mimeType = params.response.mimeType;
63
+ entry.request.responseHeaders = params.response.headers;
64
+ }
65
+ });
66
+ handlers.push({ event: 'Network.responseReceived', id: responseReceivedId });
67
+ // Listen for finished requests
68
+ const loadingFinishedId = cdp.on('Network.loadingFinished', async (params) => {
69
+ const entry = requestMap.get(params.requestId);
70
+ if (entry && requests.length < MAX_REQUESTS) {
71
+ const request = entry.request;
72
+ // Try to get response body for API calls
73
+ if (request.mimeType?.includes('json') || request.mimeType?.includes('javascript') || request.mimeType?.includes('text')) {
74
+ try {
75
+ const { body } = await cdp.send('Network.getResponseBody', { requestId: params.requestId });
76
+ request.responseBody = body;
77
+ }
78
+ catch (error) {
79
+ // Response body not available (e.g., 204 No Content, redirects, etc.)
80
+ }
81
+ }
82
+ requests.push(request);
83
+ requestMap.delete(params.requestId);
84
+ }
85
+ else if (requests.length >= MAX_REQUESTS) {
86
+ console.error(`Warning: Network request limit reached (${MAX_REQUESTS})`);
87
+ requestMap.delete(params.requestId);
88
+ }
89
+ });
90
+ handlers.push({ event: 'Network.loadingFinished', id: loadingFinishedId });
91
+ // Listen for failed requests
92
+ const loadingFailedId = cdp.on('Network.loadingFailed', (params) => {
93
+ const entry = requestMap.get(params.requestId);
94
+ if (entry && requests.length < MAX_REQUESTS) {
95
+ entry.request.status = 0; // Indicate failure
96
+ requests.push(entry.request);
97
+ requestMap.delete(params.requestId);
98
+ }
99
+ else if (requests.length >= MAX_REQUESTS) {
100
+ requestMap.delete(params.requestId);
101
+ }
102
+ });
103
+ handlers.push({ event: 'Network.loadingFailed', id: loadingFailedId });
104
+ // Return cleanup function
105
+ return () => {
106
+ // Clear interval
107
+ clearInterval(cleanupInterval);
108
+ // Remove event handlers
109
+ handlers.forEach(({ event, id }) => cdp.off(event, id));
110
+ // Clear request map
111
+ requestMap.clear();
112
+ };
113
+ }
114
+ //# sourceMappingURL=network.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/collectors/network.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { ConnectionOptions } from '../types.js';
2
+ /**
3
+ * Chrome DevTools Protocol WebSocket connection manager.
4
+ *
5
+ * Handles bidirectional communication with Chrome via CDP:
6
+ * - Request/response correlation with message IDs
7
+ * - Event subscription and handling
8
+ * - Connection lifecycle (connect, reconnect, keepalive)
9
+ * - Graceful error handling and cleanup
10
+ */
11
+ export declare class CDPConnection {
12
+ private ws;
13
+ private messageId;
14
+ private pendingMessages;
15
+ private nextHandlerId;
16
+ private eventHandlers;
17
+ private pingInterval;
18
+ private pongTimeout;
19
+ private missedPongs;
20
+ private readonly MAX_MISSED_PONGS;
21
+ private wsUrl;
22
+ private autoReconnect;
23
+ private reconnectAttempts;
24
+ private readonly MAX_RECONNECT_ATTEMPTS;
25
+ private isIntentionallyClosed;
26
+ private onReconnect?;
27
+ /**
28
+ * Connect to Chrome via WebSocket.
29
+ *
30
+ * @param wsUrl - WebSocket debugger URL from CDP target
31
+ * @param options - Connection configuration options
32
+ * @throws Error if connection fails after all retries
33
+ */
34
+ connect(wsUrl: string, options?: ConnectionOptions): Promise<void>;
35
+ private attemptConnection;
36
+ private attemptReconnection;
37
+ private startKeepalive;
38
+ private stopKeepalive;
39
+ /**
40
+ * Get the CDP port from the WebSocket URL.
41
+ *
42
+ * @returns CDP port number
43
+ * @throws Error if not connected or URL is invalid
44
+ */
45
+ getPort(): number;
46
+ /**
47
+ * Send a CDP command and wait for the response.
48
+ *
49
+ * @param method - CDP method name (e.g., 'Page.navigate', 'DOM.getDocument')
50
+ * @param params - Method parameters
51
+ * @param sessionId - Optional session ID for commands sent to specific targets
52
+ * @returns Promise resolving to the command result
53
+ * @throws Error if not connected or command times out (30s)
54
+ *
55
+ * @remarks
56
+ * Return type is `any` because CDP response structures vary by method.
57
+ * Callers should type-assert the result based on the specific method called.
58
+ */
59
+ send(method: string, params?: Record<string, any>, sessionId?: string): Promise<any>;
60
+ /**
61
+ * Register an event handler for CDP events.
62
+ *
63
+ * @param event - CDP event name (e.g., 'Network.requestWillBeSent')
64
+ * @param handler - Callback function to handle the event
65
+ * @returns Handler ID for later removal with off()
66
+ *
67
+ * @remarks
68
+ * Handler parameter type is `any` for flexibility. Callers should use typed
69
+ * event parameter interfaces (e.g., `CDPNetworkRequestParams`) at call site.
70
+ */
71
+ on(event: string, handler: (params: any) => void): number;
72
+ /**
73
+ * Remove a specific event handler.
74
+ *
75
+ * @param event - CDP event name
76
+ * @param handlerId - Handler ID returned from on()
77
+ */
78
+ off(event: string, handlerId: number): void;
79
+ /**
80
+ * Remove all event handlers for a specific event or all events.
81
+ *
82
+ * @param event - Optional event name. If omitted, removes all handlers for all events.
83
+ */
84
+ removeAllListeners(event?: string): void;
85
+ /**
86
+ * Close the WebSocket connection and clean up resources.
87
+ *
88
+ * @param code - WebSocket close code (default: 1000 for normal closure)
89
+ * @param reason - Human-readable close reason
90
+ */
91
+ close(code?: number, reason?: string): void;
92
+ /**
93
+ * Check if the WebSocket connection is open and ready.
94
+ *
95
+ * @returns True if connected and ready to send/receive
96
+ */
97
+ isConnected(): boolean;
98
+ }
99
+ //# sourceMappingURL=cdp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdp.d.ts","sourceRoot":"","sources":["../../src/connection/cdp.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,eAAe,CAIlB;IACL,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAAyD;IAG9E,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAK;IAGtC,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAK;IAC5C,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,WAAW,CAAC,CAAsB;IAE1C;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B5E,OAAO,CAAC,iBAAiB;YAoFX,mBAAmB;IAqBjC,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,aAAa;IAWrB;;;;;OAKG;IACH,OAAO,IAAI,MAAM;IAajB;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAsC9F;;;;;;;;;;OAUG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM;IASzD;;;;;OAKG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAU3C;;;;OAIG;IACH,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAQxC;;;;;OAKG;IACH,KAAK,CAAC,IAAI,SAAO,EAAE,MAAM,SAAmB,GAAG,IAAI;IAoBnD;;;;OAIG;IACH,WAAW,IAAI,OAAO;CAGvB"}