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.
- package/README.md +85 -0
- package/dist/collectors/console.d.ts +17 -0
- package/dist/collectors/console.d.ts.map +1 -0
- package/dist/collectors/console.js +65 -0
- package/dist/collectors/console.js.map +1 -0
- package/dist/collectors/dom.d.ts +21 -0
- package/dist/collectors/dom.d.ts.map +1 -0
- package/dist/collectors/dom.js +88 -0
- package/dist/collectors/dom.js.map +1 -0
- package/dist/collectors/network.d.ts +19 -0
- package/dist/collectors/network.d.ts.map +1 -0
- package/dist/collectors/network.js +114 -0
- package/dist/collectors/network.js.map +1 -0
- package/dist/connection/cdp.d.ts +99 -0
- package/dist/connection/cdp.d.ts.map +1 -0
- package/dist/connection/cdp.js +317 -0
- package/dist/connection/cdp.js.map +1 -0
- package/dist/connection/finder.d.ts +11 -0
- package/dist/connection/finder.d.ts.map +1 -0
- package/dist/connection/finder.js +23 -0
- package/dist/connection/finder.js.map +1 -0
- package/dist/connection/launcher.d.ts +30 -0
- package/dist/connection/launcher.d.ts.map +1 -0
- package/dist/connection/launcher.js +134 -0
- package/dist/connection/launcher.js.map +1 -0
- package/dist/connection/tabs.d.ts +49 -0
- package/dist/connection/tabs.d.ts.map +1 -0
- package/dist/connection/tabs.js +216 -0
- package/dist/connection/tabs.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +431 -0
- package/dist/index.js.map +1 -0
- package/dist/session/BdgSession.d.ts +62 -0
- package/dist/session/BdgSession.d.ts.map +1 -0
- package/dist/session/BdgSession.js +172 -0
- package/dist/session/BdgSession.js.map +1 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/pageLoad.d.ts +21 -0
- package/dist/utils/pageLoad.d.ts.map +1 -0
- package/dist/utils/pageLoad.js +85 -0
- package/dist/utils/pageLoad.js.map +1 -0
- package/dist/utils/session.d.ts +104 -0
- package/dist/utils/session.d.ts.map +1 -0
- package/dist/utils/session.js +244 -0
- package/dist/utils/session.js.map +1 -0
- package/dist/utils/url.d.ts +6 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/url.js +11 -0
- package/dist/utils/url.js.map +1 -0
- package/dist/utils/validation.d.ts +12 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +20 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { normalizeUrl } from '../utils/url.js';
|
|
2
|
+
/**
|
|
3
|
+
* Score how well a tab matches the search URL.
|
|
4
|
+
* Higher score = better match.
|
|
5
|
+
*
|
|
6
|
+
* @param tab - Target to score
|
|
7
|
+
* @param searchUrl - Normalized URL to match against
|
|
8
|
+
* @returns Match score (0-100)
|
|
9
|
+
*/
|
|
10
|
+
function scoreTabMatch(tab, searchUrl) {
|
|
11
|
+
// Exact match = best score
|
|
12
|
+
if (tab.url === searchUrl) {
|
|
13
|
+
return 100;
|
|
14
|
+
}
|
|
15
|
+
// Try URL-based matching
|
|
16
|
+
try {
|
|
17
|
+
const tabUrlObj = new URL(tab.url);
|
|
18
|
+
const searchUrlObj = new URL(searchUrl);
|
|
19
|
+
// Same host + path = excellent score
|
|
20
|
+
if (tabUrlObj.host === searchUrlObj.host &&
|
|
21
|
+
tabUrlObj.pathname === searchUrlObj.pathname) {
|
|
22
|
+
return 90;
|
|
23
|
+
}
|
|
24
|
+
// Same host + path prefix = good score
|
|
25
|
+
if (tabUrlObj.host === searchUrlObj.host &&
|
|
26
|
+
tabUrlObj.pathname.startsWith(searchUrlObj.pathname)) {
|
|
27
|
+
return 70;
|
|
28
|
+
}
|
|
29
|
+
// Same host = decent score
|
|
30
|
+
if (tabUrlObj.host === searchUrlObj.host) {
|
|
31
|
+
return 50;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// URL parsing failed, fall back to substring matching
|
|
36
|
+
}
|
|
37
|
+
// Substring match = weak score
|
|
38
|
+
if (tab.url.includes(searchUrl)) {
|
|
39
|
+
return 30;
|
|
40
|
+
}
|
|
41
|
+
return 0; // No match
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Find the best matching target from a list of tabs.
|
|
45
|
+
*
|
|
46
|
+
* @param url - Target URL to find (will be normalized)
|
|
47
|
+
* @param targets - List of available targets
|
|
48
|
+
* @returns Best matching target or null if no match found
|
|
49
|
+
*/
|
|
50
|
+
export function findBestTarget(url, targets) {
|
|
51
|
+
const searchUrl = normalizeUrl(url);
|
|
52
|
+
// Filter for page targets only
|
|
53
|
+
const pageTargets = targets.filter((t) => t.type === 'page');
|
|
54
|
+
if (pageTargets.length === 0) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
// Score all targets
|
|
58
|
+
const scored = pageTargets
|
|
59
|
+
.map((t) => ({ target: t, score: scoreTabMatch(t, searchUrl) }))
|
|
60
|
+
.filter(({ score }) => score > 0)
|
|
61
|
+
.sort((a, b) => b.score - a.score);
|
|
62
|
+
if (scored.length === 0) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// Warn if multiple tabs have same score
|
|
66
|
+
if (scored.length > 1 &&
|
|
67
|
+
scored[0].score === scored[1].score &&
|
|
68
|
+
scored[0].score < 100) {
|
|
69
|
+
console.error(`Warning: Multiple tabs match equally (score ${scored[0].score}), using: ${scored[0].target.url}`);
|
|
70
|
+
}
|
|
71
|
+
return scored[0].target;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a new tab with the specified URL using CDP.
|
|
75
|
+
*
|
|
76
|
+
* @param url - URL to open in the new tab
|
|
77
|
+
* @param cdp - CDP connection instance
|
|
78
|
+
* @returns Target information for the new tab
|
|
79
|
+
*/
|
|
80
|
+
export async function createNewTab(url, cdp) {
|
|
81
|
+
const normalizedUrl = normalizeUrl(url);
|
|
82
|
+
const port = cdp.getPort();
|
|
83
|
+
// Try CDP method first
|
|
84
|
+
try {
|
|
85
|
+
const response = await cdp.send('Target.createTarget', {
|
|
86
|
+
url: normalizedUrl,
|
|
87
|
+
newWindow: false, // Open as tab, not window
|
|
88
|
+
});
|
|
89
|
+
// Fetch target info from Chrome
|
|
90
|
+
const listResponse = await fetch(`http://127.0.0.1:${port}/json/list`);
|
|
91
|
+
const targets = await listResponse.json();
|
|
92
|
+
// Find the target we just created
|
|
93
|
+
const target = targets.find((t) => t.id === response.targetId);
|
|
94
|
+
if (target) {
|
|
95
|
+
return target;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (cdpError) {
|
|
99
|
+
console.error(`CDP Target.createTarget failed, trying HTTP endpoint...`);
|
|
100
|
+
}
|
|
101
|
+
// Fallback to HTTP endpoint method
|
|
102
|
+
try {
|
|
103
|
+
const createResponse = await fetch(`http://127.0.0.1:${port}/json/new?${encodeURIComponent(normalizedUrl)}`);
|
|
104
|
+
if (!createResponse.ok) {
|
|
105
|
+
throw new Error(`HTTP /json/new failed: ${createResponse.statusText}`);
|
|
106
|
+
}
|
|
107
|
+
const target = await createResponse.json();
|
|
108
|
+
return target;
|
|
109
|
+
}
|
|
110
|
+
catch (httpError) {
|
|
111
|
+
throw new Error(`Failed to create new tab: ${httpError instanceof Error ? httpError.message : String(httpError)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Navigate an existing tab to a new URL using CDP.
|
|
116
|
+
*
|
|
117
|
+
* @param targetId - CDP target ID to navigate
|
|
118
|
+
* @param url - URL to navigate to
|
|
119
|
+
* @param cdp - CDP connection instance
|
|
120
|
+
*/
|
|
121
|
+
export async function navigateToUrl(targetId, url, cdp) {
|
|
122
|
+
const normalizedUrl = normalizeUrl(url);
|
|
123
|
+
// Attach to target
|
|
124
|
+
const sessionResponse = await cdp.send('Target.attachToTarget', {
|
|
125
|
+
targetId,
|
|
126
|
+
flatten: true,
|
|
127
|
+
});
|
|
128
|
+
// Navigate
|
|
129
|
+
const navResponse = await cdp.send('Page.navigate', { url: normalizedUrl }, sessionResponse.sessionId);
|
|
130
|
+
if (navResponse.errorText) {
|
|
131
|
+
throw new Error(`Navigation failed: ${navResponse.errorText}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Wait for a target to be ready (URL matches expected).
|
|
136
|
+
*
|
|
137
|
+
* @param targetId - CDP target ID to wait for
|
|
138
|
+
* @param expectedUrl - Expected URL (normalized)
|
|
139
|
+
* @param cdp - CDP connection instance
|
|
140
|
+
* @param maxWaitMs - Maximum time to wait in milliseconds
|
|
141
|
+
*/
|
|
142
|
+
export async function waitForTargetReady(targetId, expectedUrl, cdp, maxWaitMs = 15000) {
|
|
143
|
+
const startTime = Date.now();
|
|
144
|
+
const normalizedUrl = normalizeUrl(expectedUrl);
|
|
145
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
146
|
+
try {
|
|
147
|
+
// Fetch current targets
|
|
148
|
+
const response = await fetch(`http://127.0.0.1:${cdp.getPort()}/json/list`);
|
|
149
|
+
const targets = await response.json();
|
|
150
|
+
const target = targets.find((t) => t.id === targetId);
|
|
151
|
+
if (target) {
|
|
152
|
+
// Check if URL matches or is loading
|
|
153
|
+
if (target.url === normalizedUrl ||
|
|
154
|
+
target.url.startsWith(normalizedUrl) ||
|
|
155
|
+
target.url === 'about:blank' // Still loading
|
|
156
|
+
) {
|
|
157
|
+
// Wait a bit more if still on about:blank
|
|
158
|
+
if (target.url === 'about:blank') {
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
return; // Target is ready!
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Target doesn't exist yet or fetch failed, keep waiting
|
|
168
|
+
}
|
|
169
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
170
|
+
}
|
|
171
|
+
throw new Error(`Target did not become ready within ${maxWaitMs}ms`);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create a new tab or find existing tab with the target URL.
|
|
175
|
+
*
|
|
176
|
+
* This is the main entry point for tab management.
|
|
177
|
+
* It tries to find an existing tab first, and if not found,
|
|
178
|
+
* creates a new one.
|
|
179
|
+
*
|
|
180
|
+
* @param url - Target URL
|
|
181
|
+
* @param cdp - CDP connection instance
|
|
182
|
+
* @param reuseTab - If true, navigate existing tab instead of creating new one
|
|
183
|
+
* @returns Target information
|
|
184
|
+
*/
|
|
185
|
+
export async function createOrFindTarget(url, cdp, reuseTab = false) {
|
|
186
|
+
// Only look for existing tabs if reuseTab is true
|
|
187
|
+
if (reuseTab) {
|
|
188
|
+
// Try to find existing target via CDP
|
|
189
|
+
const targetsResponse = await cdp.send('Target.getTargets');
|
|
190
|
+
const targets = targetsResponse.targetInfos.map((info) => ({
|
|
191
|
+
id: info.targetId,
|
|
192
|
+
type: info.type,
|
|
193
|
+
url: info.url,
|
|
194
|
+
title: info.title,
|
|
195
|
+
webSocketDebuggerUrl: '', // Not needed for this operation
|
|
196
|
+
}));
|
|
197
|
+
const existingTarget = findBestTarget(url, targets);
|
|
198
|
+
if (existingTarget) {
|
|
199
|
+
console.error(`Found existing tab: ${existingTarget.url}`);
|
|
200
|
+
// If URLs don't match exactly, navigate
|
|
201
|
+
if (existingTarget.url !== normalizeUrl(url)) {
|
|
202
|
+
console.error(`Navigating tab to: ${url}`);
|
|
203
|
+
await navigateToUrl(existingTarget.id, url, cdp);
|
|
204
|
+
await waitForTargetReady(existingTarget.id, url, cdp);
|
|
205
|
+
}
|
|
206
|
+
return existingTarget;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// No matching tab found (or reuseTab=false), create new one
|
|
210
|
+
console.error(`Creating new tab for: ${url}`);
|
|
211
|
+
const newTarget = await createNewTab(url, cdp);
|
|
212
|
+
// Wait for tab to start loading
|
|
213
|
+
await waitForTargetReady(newTarget.id, url, cdp);
|
|
214
|
+
return newTarget;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../src/connection/tabs.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { launchChrome, isChromeRunning } from './connection/launcher.js';
|
|
4
|
+
import { BdgSession } from './session/BdgSession.js';
|
|
5
|
+
import { normalizeUrl } from './utils/url.js';
|
|
6
|
+
import { validateCollectorTypes } from './utils/validation.js';
|
|
7
|
+
import { createOrFindTarget } from './connection/tabs.js';
|
|
8
|
+
import { writePid, readPid, isProcessAlive, writeSessionOutput, acquireSessionLock, writeSessionMetadata, cleanupSession } from './utils/session.js';
|
|
9
|
+
const program = new Command();
|
|
10
|
+
// Global state for signal handling
|
|
11
|
+
let session = null;
|
|
12
|
+
let launchedChrome = null;
|
|
13
|
+
let isShuttingDown = false;
|
|
14
|
+
let shutdownKeepalive = null;
|
|
15
|
+
async function handleStop() {
|
|
16
|
+
if (isShuttingDown || !session) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
isShuttingDown = true;
|
|
20
|
+
// Keep event loop alive during shutdown to prevent premature exit
|
|
21
|
+
shutdownKeepalive = setInterval(() => { }, 1000);
|
|
22
|
+
try {
|
|
23
|
+
console.error('Stopping session...');
|
|
24
|
+
const output = await session.stop();
|
|
25
|
+
// Write to file for 'bdg stop' to read (synchronous to ensure it completes)
|
|
26
|
+
try {
|
|
27
|
+
console.error('Writing session output...');
|
|
28
|
+
writeSessionOutput(output);
|
|
29
|
+
console.error('Session output written successfully');
|
|
30
|
+
}
|
|
31
|
+
catch (writeError) {
|
|
32
|
+
console.error('Failed to write session output:', writeError);
|
|
33
|
+
console.error('Write error details:', writeError);
|
|
34
|
+
}
|
|
35
|
+
// Also output to stdout (for foreground use)
|
|
36
|
+
console.log(JSON.stringify(output, null, 2));
|
|
37
|
+
// Leave Chrome running for future sessions (persistent profile benefit)
|
|
38
|
+
if (launchedChrome) {
|
|
39
|
+
console.error('Leaving Chrome running for future sessions (use persistent profile)');
|
|
40
|
+
console.error(`Chrome PID: ${launchedChrome.pid}, port: ${launchedChrome.port}`);
|
|
41
|
+
}
|
|
42
|
+
// Cleanup session files
|
|
43
|
+
try {
|
|
44
|
+
cleanupSession();
|
|
45
|
+
}
|
|
46
|
+
catch (cleanupError) {
|
|
47
|
+
console.error('Error cleaning up session:', cleanupError);
|
|
48
|
+
}
|
|
49
|
+
// Clear keepalive and exit
|
|
50
|
+
if (shutdownKeepalive) {
|
|
51
|
+
clearInterval(shutdownKeepalive);
|
|
52
|
+
}
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error('Error during shutdown:', error);
|
|
57
|
+
const errorOutput = {
|
|
58
|
+
success: false,
|
|
59
|
+
timestamp: new Date().toISOString(),
|
|
60
|
+
duration: 0,
|
|
61
|
+
target: { url: '', title: '' },
|
|
62
|
+
data: {},
|
|
63
|
+
error: error instanceof Error ? error.message : String(error)
|
|
64
|
+
};
|
|
65
|
+
// Write error output to file (synchronous to ensure it completes)
|
|
66
|
+
try {
|
|
67
|
+
console.error('Writing error output...');
|
|
68
|
+
writeSessionOutput(errorOutput);
|
|
69
|
+
console.error('Error output written successfully');
|
|
70
|
+
}
|
|
71
|
+
catch (writeError) {
|
|
72
|
+
console.error('Failed to write error output:', writeError);
|
|
73
|
+
console.error('Write error details:', writeError);
|
|
74
|
+
}
|
|
75
|
+
console.log(JSON.stringify(errorOutput, null, 2));
|
|
76
|
+
// Leave Chrome running even on error
|
|
77
|
+
if (launchedChrome) {
|
|
78
|
+
console.error('Leaving Chrome running (use persistent profile)');
|
|
79
|
+
console.error(`Chrome PID: ${launchedChrome.pid}, port: ${launchedChrome.port}`);
|
|
80
|
+
}
|
|
81
|
+
// Cleanup session files
|
|
82
|
+
try {
|
|
83
|
+
cleanupSession();
|
|
84
|
+
}
|
|
85
|
+
catch (cleanupError) {
|
|
86
|
+
console.error('Error cleaning up session:', cleanupError);
|
|
87
|
+
}
|
|
88
|
+
// Clear keepalive and exit
|
|
89
|
+
if (shutdownKeepalive) {
|
|
90
|
+
clearInterval(shutdownKeepalive);
|
|
91
|
+
}
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Register signal handlers
|
|
96
|
+
// Wrap in async IIFE to ensure handler completes before exit
|
|
97
|
+
process.on('SIGINT', () => {
|
|
98
|
+
// Prevent multiple signals from being processed
|
|
99
|
+
if (!isShuttingDown) {
|
|
100
|
+
handleStop().catch((error) => {
|
|
101
|
+
console.error('Fatal error during shutdown:', error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
process.on('SIGTERM', () => {
|
|
107
|
+
if (!isShuttingDown) {
|
|
108
|
+
handleStop().catch((error) => {
|
|
109
|
+
console.error('Fatal error during shutdown:', error);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
async function run(url, options, collectors) {
|
|
115
|
+
const startTime = Date.now();
|
|
116
|
+
try {
|
|
117
|
+
// 1. Acquire session lock
|
|
118
|
+
if (!acquireSessionLock()) {
|
|
119
|
+
const existingPid = readPid();
|
|
120
|
+
throw new Error(`Session already running (PID ${existingPid}). Stop it with: bdg stop`);
|
|
121
|
+
}
|
|
122
|
+
// Validate collector types
|
|
123
|
+
validateCollectorTypes(collectors);
|
|
124
|
+
// Normalize URL - add http:// if no protocol specified
|
|
125
|
+
const targetUrl = normalizeUrl(url);
|
|
126
|
+
// 2. Launch or connect to Chrome
|
|
127
|
+
const chromeRunning = await isChromeRunning(options.port);
|
|
128
|
+
if (!chromeRunning) {
|
|
129
|
+
// Launch Chrome with target URL
|
|
130
|
+
launchedChrome = await launchChrome({
|
|
131
|
+
port: options.port,
|
|
132
|
+
headless: false,
|
|
133
|
+
url: targetUrl,
|
|
134
|
+
userDataDir: options.userDataDir,
|
|
135
|
+
});
|
|
136
|
+
console.error(`Chrome launched (PID: ${launchedChrome.pid})`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.error(`Chrome already running on port ${options.port}`);
|
|
140
|
+
}
|
|
141
|
+
// 3. Create session (connects to CDP)
|
|
142
|
+
// We need a temporary target just to connect to CDP
|
|
143
|
+
// Then we'll use createOrFindTarget to get the right tab
|
|
144
|
+
const tempResponse = await fetch(`http://127.0.0.1:${options.port}/json/list`);
|
|
145
|
+
const tempTargets = await tempResponse.json();
|
|
146
|
+
if (tempTargets.length === 0) {
|
|
147
|
+
throw new Error('No targets available in Chrome');
|
|
148
|
+
}
|
|
149
|
+
// Use first available target to establish CDP connection
|
|
150
|
+
session = new BdgSession(tempTargets[0], options.port);
|
|
151
|
+
await session.connect();
|
|
152
|
+
if (!session.isConnected()) {
|
|
153
|
+
throw new Error('Failed to establish CDP connection');
|
|
154
|
+
}
|
|
155
|
+
// 4. Create or find target tab using TabManager
|
|
156
|
+
console.error(`Finding or creating tab for: ${targetUrl}`);
|
|
157
|
+
const target = await createOrFindTarget(url, session.getCDP(), options.reuseTab ?? false);
|
|
158
|
+
console.error(`Using tab: ${target.url}`);
|
|
159
|
+
// Fetch full target info with webSocketDebuggerUrl
|
|
160
|
+
const fullTargetResponse = await fetch(`http://127.0.0.1:${options.port}/json/list`);
|
|
161
|
+
const fullTargets = await fullTargetResponse.json();
|
|
162
|
+
const fullTarget = fullTargets.find((t) => t.id === target.id);
|
|
163
|
+
if (!fullTarget || !fullTarget.webSocketDebuggerUrl) {
|
|
164
|
+
throw new Error(`Could not find webSocketDebuggerUrl for target ${target.id}`);
|
|
165
|
+
}
|
|
166
|
+
// Update session with the correct target
|
|
167
|
+
// Close current session and reconnect to the correct target
|
|
168
|
+
await session.getCDP().close();
|
|
169
|
+
session = new BdgSession(fullTarget, options.port);
|
|
170
|
+
await session.connect();
|
|
171
|
+
// 5. Start collectors
|
|
172
|
+
for (const collector of collectors) {
|
|
173
|
+
await session.startCollector(collector);
|
|
174
|
+
}
|
|
175
|
+
// 6. Write session metadata
|
|
176
|
+
writePid(process.pid);
|
|
177
|
+
writeSessionMetadata({
|
|
178
|
+
bdgPid: process.pid,
|
|
179
|
+
chromePid: launchedChrome?.pid,
|
|
180
|
+
startTime,
|
|
181
|
+
port: options.port,
|
|
182
|
+
targetId: fullTarget.id,
|
|
183
|
+
webSocketDebuggerUrl: fullTarget.webSocketDebuggerUrl,
|
|
184
|
+
});
|
|
185
|
+
const collectorNames = collectors.length === 3
|
|
186
|
+
? 'network, console, and DOM'
|
|
187
|
+
: collectors.join(', ');
|
|
188
|
+
if (options.timeout) {
|
|
189
|
+
console.error(`Collecting ${collectorNames}... (Ctrl+C to stop and output, or wait ${options.timeout}s for timeout)`);
|
|
190
|
+
// Set timeout if explicitly provided
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
console.error(`\nTimeout reached (${options.timeout}s)`);
|
|
193
|
+
handleStop();
|
|
194
|
+
}, options.timeout * 1000);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.error(`Collecting ${collectorNames}... (Ctrl+C to stop and output, or use 'bdg stop')`);
|
|
198
|
+
}
|
|
199
|
+
// 7. Keep alive until signal or error
|
|
200
|
+
await new Promise((_, reject) => {
|
|
201
|
+
if (!session) {
|
|
202
|
+
reject(new Error('Session not initialized'));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Listen for WebSocket connection loss
|
|
206
|
+
const connectionCheckInterval = setInterval(() => {
|
|
207
|
+
if (!session) {
|
|
208
|
+
clearInterval(connectionCheckInterval);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (!session.isConnected()) {
|
|
212
|
+
clearInterval(connectionCheckInterval);
|
|
213
|
+
reject(new Error('WebSocket connection lost'));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}, 2000); // Check every 2 seconds
|
|
217
|
+
// Listen for target destruction (tab closed/navigated)
|
|
218
|
+
session.getCDP().on('Target.targetDestroyed', (params) => {
|
|
219
|
+
if (params.targetId === target.id) {
|
|
220
|
+
clearInterval(connectionCheckInterval);
|
|
221
|
+
reject(new Error('Browser tab was closed'));
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
const errorOutput = {
|
|
228
|
+
success: false,
|
|
229
|
+
timestamp: new Date().toISOString(),
|
|
230
|
+
duration: Date.now() - startTime,
|
|
231
|
+
target: { url: '', title: '' },
|
|
232
|
+
data: {},
|
|
233
|
+
error: error instanceof Error ? error.message : String(error)
|
|
234
|
+
};
|
|
235
|
+
console.log(JSON.stringify(errorOutput, null, 2));
|
|
236
|
+
// Cleanup on error
|
|
237
|
+
if (launchedChrome) {
|
|
238
|
+
try {
|
|
239
|
+
await launchedChrome.kill();
|
|
240
|
+
}
|
|
241
|
+
catch { }
|
|
242
|
+
}
|
|
243
|
+
cleanupSession();
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
program
|
|
248
|
+
.name('bdg')
|
|
249
|
+
.description('Browser telemetry via Chrome DevTools Protocol')
|
|
250
|
+
.version('0.1.0');
|
|
251
|
+
program
|
|
252
|
+
.argument('<url>', 'Target URL (example.com or localhost:3000)')
|
|
253
|
+
.option('-p, --port <number>', 'Chrome debugging port', '9222')
|
|
254
|
+
.option('-t, --timeout <seconds>', 'Auto-stop after timeout (optional)')
|
|
255
|
+
.option('-r, --reuse-tab', 'Navigate existing tab instead of creating new one')
|
|
256
|
+
.option('-u, --user-data-dir <path>', 'Chrome user data directory (default: ~/.bdg/chrome-profile)')
|
|
257
|
+
.action(async (url, options) => {
|
|
258
|
+
const port = parseInt(options.port);
|
|
259
|
+
const timeout = options.timeout ? parseInt(options.timeout) : undefined;
|
|
260
|
+
const reuseTab = options.reuseTab ?? false;
|
|
261
|
+
const userDataDir = options.userDataDir;
|
|
262
|
+
await run(url, { port, timeout, reuseTab, userDataDir }, ['dom', 'network', 'console']);
|
|
263
|
+
});
|
|
264
|
+
program
|
|
265
|
+
.command('dom')
|
|
266
|
+
.description('Collect DOM only')
|
|
267
|
+
.argument('<url>', 'Target URL')
|
|
268
|
+
.option('-p, --port <number>', 'Chrome debugging port', '9222')
|
|
269
|
+
.option('-t, --timeout <seconds>', 'Auto-stop after timeout (optional)')
|
|
270
|
+
.option('-r, --reuse-tab', 'Navigate existing tab instead of creating new one')
|
|
271
|
+
.option('-u, --user-data-dir <path>', 'Chrome user data directory (default: ~/.bdg/chrome-profile)')
|
|
272
|
+
.action(async (url, options) => {
|
|
273
|
+
const port = parseInt(options.port);
|
|
274
|
+
const timeout = options.timeout ? parseInt(options.timeout) : undefined;
|
|
275
|
+
const reuseTab = options.reuseTab ?? false;
|
|
276
|
+
const userDataDir = options.userDataDir;
|
|
277
|
+
await run(url, { port, timeout, reuseTab, userDataDir }, ['dom']);
|
|
278
|
+
});
|
|
279
|
+
program
|
|
280
|
+
.command('network')
|
|
281
|
+
.description('Collect network requests only')
|
|
282
|
+
.argument('<url>', 'Target URL')
|
|
283
|
+
.option('-p, --port <number>', 'Chrome debugging port', '9222')
|
|
284
|
+
.option('-t, --timeout <seconds>', 'Auto-stop after timeout (optional)')
|
|
285
|
+
.option('-r, --reuse-tab', 'Navigate existing tab instead of creating new one')
|
|
286
|
+
.option('-u, --user-data-dir <path>', 'Chrome user data directory (default: ~/.bdg/chrome-profile)')
|
|
287
|
+
.action(async (url, options) => {
|
|
288
|
+
const port = parseInt(options.port);
|
|
289
|
+
const timeout = options.timeout ? parseInt(options.timeout) : undefined;
|
|
290
|
+
const reuseTab = options.reuseTab ?? false;
|
|
291
|
+
const userDataDir = options.userDataDir;
|
|
292
|
+
await run(url, { port, timeout, reuseTab, userDataDir }, ['network']);
|
|
293
|
+
});
|
|
294
|
+
program
|
|
295
|
+
.command('console')
|
|
296
|
+
.description('Collect console logs only')
|
|
297
|
+
.argument('<url>', 'Target URL')
|
|
298
|
+
.option('-p, --port <number>', 'Chrome debugging port', '9222')
|
|
299
|
+
.option('-t, --timeout <seconds>', 'Auto-stop after timeout (optional)')
|
|
300
|
+
.option('-r, --reuse-tab', 'Navigate existing tab instead of creating new one')
|
|
301
|
+
.option('-u, --user-data-dir <path>', 'Chrome user data directory (default: ~/.bdg/chrome-profile)')
|
|
302
|
+
.action(async (url, options) => {
|
|
303
|
+
const port = parseInt(options.port);
|
|
304
|
+
const timeout = options.timeout ? parseInt(options.timeout) : undefined;
|
|
305
|
+
const reuseTab = options.reuseTab ?? false;
|
|
306
|
+
const userDataDir = options.userDataDir;
|
|
307
|
+
await run(url, { port, timeout, reuseTab, userDataDir }, ['console']);
|
|
308
|
+
});
|
|
309
|
+
program
|
|
310
|
+
.command('query')
|
|
311
|
+
.description('Execute JavaScript in the active session for live debugging')
|
|
312
|
+
.argument('<script>', 'JavaScript to execute (e.g., "document.querySelector(\'input[type=email]\').value")')
|
|
313
|
+
.option('-p, --port <number>', 'Chrome debugging port', '9222')
|
|
314
|
+
.action(async (script, options) => {
|
|
315
|
+
try {
|
|
316
|
+
const port = parseInt(options.port);
|
|
317
|
+
// Check if session is running
|
|
318
|
+
const pid = readPid();
|
|
319
|
+
if (!pid || !isProcessAlive(pid)) {
|
|
320
|
+
console.error('Error: No active session running');
|
|
321
|
+
console.error('Start a session with: bdg <url>');
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
// Read session metadata to get the target ID
|
|
325
|
+
const { readSessionMetadata } = await import('./utils/session.js');
|
|
326
|
+
const metadata = readSessionMetadata();
|
|
327
|
+
if (!metadata || !metadata.targetId || !metadata.webSocketDebuggerUrl) {
|
|
328
|
+
console.error('Error: No target information in session metadata');
|
|
329
|
+
console.error('Session may have been started with an older version');
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
// Verify the target still exists
|
|
333
|
+
const response = await fetch(`http://127.0.0.1:${port}/json/list`);
|
|
334
|
+
const targets = await response.json();
|
|
335
|
+
const target = targets.find((t) => t.id === metadata.targetId);
|
|
336
|
+
if (!target) {
|
|
337
|
+
console.error('Error: Session target not found (tab may have been closed)');
|
|
338
|
+
console.error('Start a new session with: bdg <url>');
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
// Create temporary CDP connection using stored webSocketDebuggerUrl
|
|
342
|
+
const { CDPConnection } = await import('./connection/cdp.js');
|
|
343
|
+
const cdp = new CDPConnection();
|
|
344
|
+
await cdp.connect(metadata.webSocketDebuggerUrl);
|
|
345
|
+
// Execute JavaScript
|
|
346
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
347
|
+
expression: script,
|
|
348
|
+
returnByValue: true,
|
|
349
|
+
awaitPromise: true
|
|
350
|
+
});
|
|
351
|
+
await cdp.close();
|
|
352
|
+
// Output result
|
|
353
|
+
if (result.exceptionDetails) {
|
|
354
|
+
console.error('Error executing script:');
|
|
355
|
+
console.error(result.exceptionDetails.exception.description);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
console.log(JSON.stringify(result.result.value, null, 2));
|
|
359
|
+
process.exit(0);
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
program
|
|
367
|
+
.command('stop')
|
|
368
|
+
.description('Stop all active sessions and free ports (does not capture output)')
|
|
369
|
+
.option('--kill-chrome', 'Also kill Chrome browser')
|
|
370
|
+
.action(async (options) => {
|
|
371
|
+
try {
|
|
372
|
+
const { readSessionMetadata } = await import('./utils/session.js');
|
|
373
|
+
// Read PID
|
|
374
|
+
const pid = readPid();
|
|
375
|
+
if (!pid) {
|
|
376
|
+
console.error('No active session found');
|
|
377
|
+
console.error('All ports should be free');
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
console.error(`Stopping session (PID ${pid})...`);
|
|
381
|
+
// Read metadata BEFORE killing the process (so we can get Chrome PID)
|
|
382
|
+
const metadata = readSessionMetadata();
|
|
383
|
+
// Kill the bdg process (use SIGKILL for immediate termination)
|
|
384
|
+
if (isProcessAlive(pid)) {
|
|
385
|
+
try {
|
|
386
|
+
process.kill(pid, 'SIGKILL');
|
|
387
|
+
console.error(`✓ Killed bdg session (PID ${pid})`);
|
|
388
|
+
}
|
|
389
|
+
catch (killError) {
|
|
390
|
+
console.error(`Warning: Could not kill process ${pid}:`, killError);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
console.error(`Process ${pid} already stopped`);
|
|
395
|
+
}
|
|
396
|
+
// Kill Chrome if requested
|
|
397
|
+
if (options.killChrome) {
|
|
398
|
+
if (metadata?.chromePid) {
|
|
399
|
+
try {
|
|
400
|
+
if (isProcessAlive(metadata.chromePid)) {
|
|
401
|
+
process.kill(metadata.chromePid, 'SIGTERM');
|
|
402
|
+
console.error(`✓ Killed Chrome (PID ${metadata.chromePid})`);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
console.error(`Chrome process (PID ${metadata.chromePid}) already stopped`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (chromeError) {
|
|
409
|
+
console.error(`Warning: Could not kill Chrome:`, chromeError);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
console.error('Warning: Chrome PID not found in session metadata');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
console.error('Leaving Chrome running (use --kill-chrome to close it)');
|
|
418
|
+
}
|
|
419
|
+
// Clean up session files
|
|
420
|
+
cleanupSession();
|
|
421
|
+
console.error('✓ Cleaned up session files');
|
|
422
|
+
console.error('\nAll sessions stopped and ports freed');
|
|
423
|
+
process.exit(0);
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
console.error(`Error stopping session: ${error instanceof Error ? error.message : String(error)}`);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
program.parse();
|
|
431
|
+
//# sourceMappingURL=index.js.map
|