dev3000 0.0.174 → 0.0.176
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 +0 -4
- package/dist/cdp-monitor.d.ts +5 -1
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +273 -46
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +154 -217
- package/dist/cli.js.map +1 -1
- package/dist/commands/crawl.d.ts.map +1 -1
- package/dist/commands/crawl.js +4 -43
- package/dist/commands/crawl.js.map +1 -1
- package/dist/commands/errors.d.ts.map +1 -1
- package/dist/commands/errors.js +4 -53
- package/dist/commands/errors.js.map +1 -1
- package/dist/commands/fix.d.ts.map +1 -1
- package/dist/commands/fix.js +5 -74
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +4 -53
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/skill-runner.d.ts +15 -0
- package/dist/commands/skill-runner.d.ts.map +1 -0
- package/dist/commands/skill-runner.js +615 -0
- package/dist/commands/skill-runner.js.map +1 -0
- package/dist/dev-environment.d.ts +6 -3
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +123 -105
- package/dist/dev-environment.js.map +1 -1
- package/dist/skills/d3k/internal-skill.md +145 -0
- package/dist/skills/index.test.ts +28 -1
- package/dist/skills/index.ts +58 -7
- package/dist/utils/agent-browser.d.ts.map +1 -1
- package/dist/utils/agent-browser.js +6 -3
- package/dist/utils/agent-browser.js.map +1 -1
- package/dist/utils/agent-detection.d.ts +1 -0
- package/dist/utils/agent-detection.d.ts.map +1 -1
- package/dist/utils/agent-detection.js +11 -0
- package/dist/utils/agent-detection.js.map +1 -1
- package/dist/utils/agent-selection.js +3 -3
- package/dist/utils/agent-selection.js.map +1 -1
- package/dist/utils/browser-command-argv.d.ts +1 -1
- package/dist/utils/browser-command-argv.d.ts.map +1 -1
- package/dist/utils/browser-command-argv.js +1 -1
- package/dist/utils/browser-command-argv.js.map +1 -1
- package/dist/utils/cli-options.d.ts +5 -0
- package/dist/utils/cli-options.d.ts.map +1 -0
- package/dist/utils/cli-options.js +36 -0
- package/dist/utils/cli-options.js.map +1 -0
- package/dist/utils/project-name.d.ts +2 -0
- package/dist/utils/project-name.d.ts.map +1 -1
- package/dist/utils/project-name.js +6 -0
- package/dist/utils/project-name.js.map +1 -1
- package/dist/utils/session.d.ts +14 -0
- package/dist/utils/session.d.ts.map +1 -0
- package/dist/utils/session.js +65 -0
- package/dist/utils/session.js.map +1 -0
- package/dist/utils/version-check.js +2 -2
- package/dist/utils/version-check.js.map +1 -1
- package/package.json +10 -19
- package/dist/commands/cloud-check-pr.d.ts +0 -9
- package/dist/commands/cloud-check-pr.d.ts.map +0 -1
- package/dist/commands/cloud-check-pr.js +0 -243
- package/dist/commands/cloud-check-pr.js.map +0 -1
- package/dist/commands/cloud-fix.d.ts +0 -13
- package/dist/commands/cloud-fix.d.ts.map +0 -1
- package/dist/commands/cloud-fix.js +0 -79
- package/dist/commands/cloud-fix.js.map +0 -1
- package/dist/commands/find-component.d.ts +0 -8
- package/dist/commands/find-component.d.ts.map +0 -1
- package/dist/commands/find-component.js +0 -182
- package/dist/commands/find-component.js.map +0 -1
- package/dist/skills/d3k/SKILL.md +0 -126
- package/dist/skills/index.d.ts +0 -46
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -174
- package/dist/skills/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -62,9 +62,6 @@ d3k fix --time 30 # Analyze last 30 minutes (default: 10)
|
|
|
62
62
|
d3k crawl # Discover URLs by crawling the app
|
|
63
63
|
d3k crawl --depth all # Exhaustive crawl (default: 1 level)
|
|
64
64
|
|
|
65
|
-
d3k find-component "nav.header" # Find React component source
|
|
66
|
-
d3k find-component "[data-testid='button']"
|
|
67
|
-
|
|
68
65
|
d3k restart # Restart the development server (rarely needed)
|
|
69
66
|
```
|
|
70
67
|
|
|
@@ -74,7 +71,6 @@ d3k restart # Restart the development server (rarely needed)
|
|
|
74
71
|
d3k skill [name] # Get skill content or list available skills
|
|
75
72
|
d3k upgrade # Upgrade d3k to the latest version
|
|
76
73
|
d3k agent-browser # Run the bundled agent-browser CLI
|
|
77
|
-
d3k cloud # Cloud-based tools using Vercel Sandbox
|
|
78
74
|
```
|
|
79
75
|
|
|
80
76
|
## Options
|
package/dist/cdp-monitor.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface CDPTargetInfo {
|
|
|
17
17
|
url?: string;
|
|
18
18
|
webSocketDebuggerUrl?: string;
|
|
19
19
|
}
|
|
20
|
+
export declare const CHROME_CRASH_RESTORE_SUPPRESSION_FLAGS: string[];
|
|
21
|
+
export declare function resetChromeCrashRestoreState(profileDir: string): number;
|
|
20
22
|
export declare function getLoadingHtmlCandidates(currentDir: string, execPath?: string): string[];
|
|
21
23
|
export declare function selectCDPTarget(targets: CDPTargetInfo[], options?: {
|
|
22
24
|
appServerPort?: string;
|
|
@@ -44,13 +46,14 @@ export declare class CDPMonitor {
|
|
|
44
46
|
private onWindowClosedCallback;
|
|
45
47
|
private appServerPort?;
|
|
46
48
|
private initialAppUrl?;
|
|
49
|
+
private externalCdpBase?;
|
|
47
50
|
private headless;
|
|
48
51
|
private framework?;
|
|
49
52
|
private reactTrackingEnabled;
|
|
50
53
|
private lastReactSnapshotLogTime;
|
|
51
54
|
private pendingCommands;
|
|
52
55
|
private navigationTimeoutMs;
|
|
53
|
-
constructor(profileDir: string, screenshotDir: string, logger: (source: string, message: string) => void, debug?: boolean, browserPath?: string, pluginReactScan?: boolean, appServerPort?: string, initialAppUrl?: string, navigationTimeoutMs?: number, debugPort?: number, headless?: boolean, framework?: "nextjs" | "svelte" | "other");
|
|
56
|
+
constructor(profileDir: string, screenshotDir: string, logger: (source: string, message: string) => void, debug?: boolean, browserPath?: string, pluginReactScan?: boolean, appServerPort?: string, initialAppUrl?: string, navigationTimeoutMs?: number, debugPort?: number, headless?: boolean, framework?: "nextjs" | "svelte" | "other", externalCdpBase?: string);
|
|
54
57
|
private resolveReactDevToolsExtensionPath;
|
|
55
58
|
private debugLog;
|
|
56
59
|
private runCommand;
|
|
@@ -114,6 +117,7 @@ export declare class CDPMonitor {
|
|
|
114
117
|
*/
|
|
115
118
|
prepareShutdown(): void;
|
|
116
119
|
private waitForBrowserExit;
|
|
120
|
+
private sendBrowserCloseCommand;
|
|
117
121
|
shutdown(): Promise<void>;
|
|
118
122
|
private killInstanceChromeProcesses;
|
|
119
123
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdp-monitor.d.ts","sourceRoot":"","sources":["../src/cdp-monitor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AAE9B,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,SAAS,CAAA;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B;
|
|
1
|
+
{"version":3,"file":"cdp-monitor.d.ts","sourceRoot":"","sources":["../src/cdp-monitor.ts"],"names":[],"mappings":"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"}
|
package/dist/cdp-monitor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { existsSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
3
|
import { tmpdir } from "os";
|
|
4
4
|
import { dirname, join } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
@@ -45,6 +45,98 @@ const EMBEDDED_LOADING_HTML = `<!DOCTYPE html>
|
|
|
45
45
|
</html>`;
|
|
46
46
|
const DEFAULT_CDP_COMMAND_TIMEOUT_MS = 10000;
|
|
47
47
|
const DEFAULT_NAVIGATION_TIMEOUT_MS = 60000;
|
|
48
|
+
export const CHROME_CRASH_RESTORE_SUPPRESSION_FLAGS = [
|
|
49
|
+
"--disable-session-crashed-bubble",
|
|
50
|
+
"--disable-restore-session-state",
|
|
51
|
+
"--hide-crash-restore-bubble"
|
|
52
|
+
];
|
|
53
|
+
function isRecord(value) {
|
|
54
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
function patchChromePreferences(data) {
|
|
57
|
+
let changed = false;
|
|
58
|
+
const profile = isRecord(data.profile) ? data.profile : {};
|
|
59
|
+
if (data.profile !== profile) {
|
|
60
|
+
data.profile = profile;
|
|
61
|
+
changed = true;
|
|
62
|
+
}
|
|
63
|
+
if (profile.exit_type !== "Normal") {
|
|
64
|
+
profile.exit_type = "Normal";
|
|
65
|
+
changed = true;
|
|
66
|
+
}
|
|
67
|
+
if (profile.exited_cleanly !== true) {
|
|
68
|
+
profile.exited_cleanly = true;
|
|
69
|
+
changed = true;
|
|
70
|
+
}
|
|
71
|
+
return changed;
|
|
72
|
+
}
|
|
73
|
+
function patchChromeLocalState(data) {
|
|
74
|
+
let changed = false;
|
|
75
|
+
if (data.exit_type === "Crashed") {
|
|
76
|
+
data.exit_type = "Normal";
|
|
77
|
+
changed = true;
|
|
78
|
+
}
|
|
79
|
+
if (data.exited_cleanly === false) {
|
|
80
|
+
data.exited_cleanly = true;
|
|
81
|
+
changed = true;
|
|
82
|
+
}
|
|
83
|
+
if (isRecord(data.profile)) {
|
|
84
|
+
if (data.profile.exit_type === "Crashed") {
|
|
85
|
+
data.profile.exit_type = "Normal";
|
|
86
|
+
changed = true;
|
|
87
|
+
}
|
|
88
|
+
if (data.profile.exited_cleanly === false) {
|
|
89
|
+
data.profile.exited_cleanly = true;
|
|
90
|
+
changed = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return changed;
|
|
94
|
+
}
|
|
95
|
+
function patchJsonFile(filePath, patch) {
|
|
96
|
+
if (!existsSync(filePath)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
101
|
+
if (!isRecord(data) || !patch(data)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
writeFileSync(filePath, JSON.stringify(data));
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function getChromePreferencesFiles(profileDir) {
|
|
112
|
+
const files = new Set([join(profileDir, "Default", "Preferences")]);
|
|
113
|
+
if (!existsSync(profileDir)) {
|
|
114
|
+
return Array.from(files);
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
for (const entry of readdirSync(profileDir, { withFileTypes: true })) {
|
|
118
|
+
if (entry.isDirectory() && (entry.name === "Default" || entry.name.startsWith("Profile "))) {
|
|
119
|
+
files.add(join(profileDir, entry.name, "Preferences"));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Ignore unreadable profile directories.
|
|
125
|
+
}
|
|
126
|
+
return Array.from(files);
|
|
127
|
+
}
|
|
128
|
+
export function resetChromeCrashRestoreState(profileDir) {
|
|
129
|
+
let changedFiles = 0;
|
|
130
|
+
for (const preferencesFile of getChromePreferencesFiles(profileDir)) {
|
|
131
|
+
if (patchJsonFile(preferencesFile, patchChromePreferences)) {
|
|
132
|
+
changedFiles++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (patchJsonFile(join(profileDir, "Local State"), patchChromeLocalState)) {
|
|
136
|
+
changedFiles++;
|
|
137
|
+
}
|
|
138
|
+
return changedFiles;
|
|
139
|
+
}
|
|
48
140
|
export function getLoadingHtmlCandidates(currentDir, execPath = process.execPath) {
|
|
49
141
|
const candidates = [join(currentDir, "src/loading.html"), join(currentDir, "loading.html")];
|
|
50
142
|
const packageRoot = dirname(dirname(execPath));
|
|
@@ -157,13 +249,14 @@ export class CDPMonitor {
|
|
|
157
249
|
onWindowClosedCallback = null; // Callback for when window is manually closed
|
|
158
250
|
appServerPort; // Port of the user's app server to monitor
|
|
159
251
|
initialAppUrl; // App URL that the loading page should hand off to
|
|
252
|
+
externalCdpBase; // Existing CDP HTTP endpoint to connect to instead of launching Chrome
|
|
160
253
|
headless = false; // Run Chrome in headless mode
|
|
161
254
|
framework; // Framework hint from project detection
|
|
162
255
|
reactTrackingEnabled = false;
|
|
163
256
|
lastReactSnapshotLogTime = 0;
|
|
164
257
|
pendingCommands = new Map();
|
|
165
258
|
navigationTimeoutMs = DEFAULT_NAVIGATION_TIMEOUT_MS;
|
|
166
|
-
constructor(profileDir, screenshotDir, logger, debug = false, browserPath, pluginReactScan = false, appServerPort, initialAppUrl, navigationTimeoutMs = DEFAULT_NAVIGATION_TIMEOUT_MS, debugPort, headless = false, framework) {
|
|
259
|
+
constructor(profileDir, screenshotDir, logger, debug = false, browserPath, pluginReactScan = false, appServerPort, initialAppUrl, navigationTimeoutMs = DEFAULT_NAVIGATION_TIMEOUT_MS, debugPort, headless = false, framework, externalCdpBase) {
|
|
167
260
|
this.profileDir = profileDir;
|
|
168
261
|
this.screenshotDir = screenshotDir;
|
|
169
262
|
this.appServerPort = appServerPort;
|
|
@@ -175,6 +268,7 @@ export class CDPMonitor {
|
|
|
175
268
|
this.navigationTimeoutMs = navigationTimeoutMs;
|
|
176
269
|
this.headless = headless;
|
|
177
270
|
this.framework = framework;
|
|
271
|
+
this.externalCdpBase = externalCdpBase?.replace(/\/$/, "");
|
|
178
272
|
this.reactTrackingEnabled = framework === "nextjs";
|
|
179
273
|
// Use custom debug port if provided, otherwise use default 9222
|
|
180
274
|
if (debugPort) {
|
|
@@ -290,6 +384,13 @@ export class CDPMonitor {
|
|
|
290
384
|
const urlObj = new URL(url);
|
|
291
385
|
const hostname = urlObj.hostname;
|
|
292
386
|
const port = urlObj.port || (urlObj.protocol === "https:" ? "443" : "80");
|
|
387
|
+
if (this.initialAppUrl) {
|
|
388
|
+
const initial = new URL(this.initialAppUrl);
|
|
389
|
+
const initialPort = initial.port || (initial.protocol === "https:" ? "443" : "80");
|
|
390
|
+
if (urlObj.protocol === initial.protocol && hostname === initial.hostname && port === initialPort) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
293
394
|
// Only monitor localhost/127.0.0.1 (the user's local dev server)
|
|
294
395
|
const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0";
|
|
295
396
|
if (!isLocalhost) {
|
|
@@ -307,10 +408,15 @@ export class CDPMonitor {
|
|
|
307
408
|
}
|
|
308
409
|
}
|
|
309
410
|
async start() {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
411
|
+
if (this.externalCdpBase) {
|
|
412
|
+
this.debugLog(`Using external CDP endpoint: ${this.externalCdpBase}`);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
// Launch Chrome with CDP enabled
|
|
416
|
+
this.debugLog("Starting Chrome launch process");
|
|
417
|
+
await this.launchChrome();
|
|
418
|
+
this.debugLog("Chrome launch completed");
|
|
419
|
+
}
|
|
314
420
|
// Connect to Chrome DevTools Protocol
|
|
315
421
|
this.debugLog("Starting CDP connection");
|
|
316
422
|
await this.connectToCDP();
|
|
@@ -323,6 +429,10 @@ export class CDPMonitor {
|
|
|
323
429
|
this.debugLog("Setting up CDP event handlers");
|
|
324
430
|
this.setupEventHandlers();
|
|
325
431
|
this.debugLog("CDP event handlers setup completed");
|
|
432
|
+
if (this.externalCdpBase && this.initialAppUrl) {
|
|
433
|
+
this.debugLog(`Navigating external browser to ${this.initialAppUrl}`);
|
|
434
|
+
await this.navigateToUrl(this.initialAppUrl);
|
|
435
|
+
}
|
|
326
436
|
}
|
|
327
437
|
getCdpUrl() {
|
|
328
438
|
return this.cdpUrl;
|
|
@@ -438,10 +548,21 @@ export class CDPMonitor {
|
|
|
438
548
|
*/
|
|
439
549
|
async killExistingChromeWithProfile() {
|
|
440
550
|
try {
|
|
441
|
-
//
|
|
551
|
+
// Build a set of PIDs that must never be killed: this Node process and
|
|
552
|
+
// its parent. d3k's own argv contains the profile path (via --profile-dir),
|
|
553
|
+
// which previously caused a substring match here and made d3k SIGTERM itself.
|
|
554
|
+
const selfPids = new Set();
|
|
555
|
+
if (typeof process.pid === "number")
|
|
556
|
+
selfPids.add(process.pid);
|
|
557
|
+
if (typeof process.ppid === "number")
|
|
558
|
+
selfPids.add(process.ppid);
|
|
559
|
+
// Find Chrome processes using this profile directory. We only match the
|
|
560
|
+
// canonical `--user-data-dir=<profile>` form Chrome consumes; matching the
|
|
561
|
+
// bare path was too loose and caught unrelated processes (including d3k itself).
|
|
442
562
|
const processes = await this.listProcesses();
|
|
443
563
|
const pids = processes
|
|
444
|
-
.filter((proc) =>
|
|
564
|
+
.filter((proc) => !selfPids.has(proc.pid))
|
|
565
|
+
.filter((proc) => proc.command.includes(`--user-data-dir=${this.profileDir}`))
|
|
445
566
|
.map((proc) => proc.pid)
|
|
446
567
|
.filter((pid) => pid !== this.browser?.pid);
|
|
447
568
|
for (const pid of pids) {
|
|
@@ -465,6 +586,10 @@ export class CDPMonitor {
|
|
|
465
586
|
async launchChrome() {
|
|
466
587
|
// Kill any existing Chrome using this profile to prevent CDP conflicts
|
|
467
588
|
await this.killExistingChromeWithProfile();
|
|
589
|
+
const resetProfileFiles = resetChromeCrashRestoreState(this.profileDir);
|
|
590
|
+
if (resetProfileFiles > 0) {
|
|
591
|
+
this.debugLog(`Reset Chrome crash restore state in ${resetProfileFiles} profile file(s)`);
|
|
592
|
+
}
|
|
468
593
|
return new Promise((resolve, reject) => {
|
|
469
594
|
// Use custom browser path if provided, otherwise try different Chrome executables based on platform
|
|
470
595
|
const chromeCommands = this.browserPath
|
|
@@ -474,6 +599,8 @@ export class CDPMonitor {
|
|
|
474
599
|
"google-chrome",
|
|
475
600
|
"chrome",
|
|
476
601
|
"chromium",
|
|
602
|
+
"brave",
|
|
603
|
+
"brave-browser",
|
|
477
604
|
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
478
605
|
"/Applications/Comet.app/Contents/MacOS/Comet",
|
|
479
606
|
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
|
|
@@ -513,8 +640,7 @@ export class CDPMonitor {
|
|
|
513
640
|
"--disable-sync",
|
|
514
641
|
"--metrics-recording-only",
|
|
515
642
|
"--disable-default-apps",
|
|
516
|
-
|
|
517
|
-
"--disable-restore-session-state"
|
|
643
|
+
...CHROME_CRASH_RESTORE_SUPPRESSION_FLAGS
|
|
518
644
|
];
|
|
519
645
|
if (shouldEnableReactDevTools && reactDevToolsExtensionPath) {
|
|
520
646
|
chromeArgs.push(`--disable-extensions-except=${reactDevToolsExtensionPath}`);
|
|
@@ -625,19 +751,20 @@ export class CDPMonitor {
|
|
|
625
751
|
});
|
|
626
752
|
}
|
|
627
753
|
async connectToCDP() {
|
|
628
|
-
|
|
754
|
+
const cdpBase = this.externalCdpBase || `http://localhost:${this.debugPort}`;
|
|
755
|
+
this.debugLog(`Attempting to connect to CDP at ${cdpBase}`);
|
|
629
756
|
// Retry connection with exponential backoff
|
|
630
757
|
let retryCount = 0;
|
|
631
758
|
const maxRetries = 5;
|
|
632
759
|
while (retryCount < maxRetries) {
|
|
633
760
|
try {
|
|
634
761
|
// Get the WebSocket URL from Chrome's debug endpoint
|
|
635
|
-
const targetsResponse = await fetch(
|
|
762
|
+
const targetsResponse = await fetch(`${cdpBase}/json`);
|
|
636
763
|
let targets = (await targetsResponse.json());
|
|
637
764
|
this.debugLog(`Found ${targets.length} targets: ${JSON.stringify(targets.map((target) => ({ type: target.type ?? "unknown", url: target.url ?? "" })))}`);
|
|
638
765
|
if (targets.length === 0) {
|
|
639
766
|
this.debugLog("No debuggable targets found; creating a blank page target");
|
|
640
|
-
const newTargetResponse = await fetch(
|
|
767
|
+
const newTargetResponse = await fetch(`${cdpBase}/json/new?about:blank`, {
|
|
641
768
|
method: "PUT"
|
|
642
769
|
});
|
|
643
770
|
if (newTargetResponse.ok) {
|
|
@@ -704,6 +831,14 @@ export class CDPMonitor {
|
|
|
704
831
|
// Use a small delay to distinguish between temporary reconnects and permanent failures
|
|
705
832
|
setTimeout(() => {
|
|
706
833
|
if (!this.isShuttingDown && this.onWindowClosedCallback) {
|
|
834
|
+
if (this.externalCdpBase) {
|
|
835
|
+
this.debugLog("External CDP connection lost - attempting recovery");
|
|
836
|
+
this.logger("browser", "[CDP] Attempting to recover external CDP connection");
|
|
837
|
+
this.attemptReconnect().catch((err) => {
|
|
838
|
+
this.logger("browser", `[CDP] Reconnection failed: ${err.message}`);
|
|
839
|
+
});
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
707
842
|
// Check if Chrome process is still alive
|
|
708
843
|
if (!this.browser || this.browser.killed || !this.browser.pid) {
|
|
709
844
|
this.debugLog("Chrome process is dead and CDP connection lost, triggering d3k shutdown");
|
|
@@ -1774,49 +1909,141 @@ export class CDPMonitor {
|
|
|
1774
1909
|
browser.once("exit", onExit);
|
|
1775
1910
|
});
|
|
1776
1911
|
}
|
|
1912
|
+
async sendBrowserCloseCommand() {
|
|
1913
|
+
try {
|
|
1914
|
+
await this.sendCDPCommand("Browser.close", {}, 3000);
|
|
1915
|
+
this.debugLog("Sent Browser.close command");
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
catch (error) {
|
|
1919
|
+
this.debugLog(`Browser.close on page target failed: ${error}`);
|
|
1920
|
+
}
|
|
1921
|
+
const versionResponse = await fetch(`http://localhost:${this.debugPort}/json/version`, {
|
|
1922
|
+
signal: AbortSignal.timeout(1000)
|
|
1923
|
+
});
|
|
1924
|
+
if (!versionResponse.ok) {
|
|
1925
|
+
throw new Error(`Failed to get browser CDP endpoint: HTTP ${versionResponse.status}`);
|
|
1926
|
+
}
|
|
1927
|
+
const version = (await versionResponse.json());
|
|
1928
|
+
if (!version.webSocketDebuggerUrl) {
|
|
1929
|
+
throw new Error("Browser CDP endpoint did not include webSocketDebuggerUrl");
|
|
1930
|
+
}
|
|
1931
|
+
await new Promise((resolve, reject) => {
|
|
1932
|
+
const ws = new WebSocket(version.webSocketDebuggerUrl);
|
|
1933
|
+
let commandSent = false;
|
|
1934
|
+
let settled = false;
|
|
1935
|
+
const settle = (callback) => {
|
|
1936
|
+
if (settled)
|
|
1937
|
+
return;
|
|
1938
|
+
settled = true;
|
|
1939
|
+
clearTimeout(timeout);
|
|
1940
|
+
try {
|
|
1941
|
+
ws.close();
|
|
1942
|
+
}
|
|
1943
|
+
catch {
|
|
1944
|
+
// Ignore close errors.
|
|
1945
|
+
}
|
|
1946
|
+
callback();
|
|
1947
|
+
};
|
|
1948
|
+
const timeout = setTimeout(() => {
|
|
1949
|
+
settle(() => reject(new Error("Browser.close command timed out")));
|
|
1950
|
+
}, 3000);
|
|
1951
|
+
ws.on("open", () => {
|
|
1952
|
+
commandSent = true;
|
|
1953
|
+
ws.send(JSON.stringify({ id: 1, method: "Browser.close", params: {} }), (error) => {
|
|
1954
|
+
if (error) {
|
|
1955
|
+
settle(() => reject(error));
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
});
|
|
1959
|
+
ws.on("message", (data) => {
|
|
1960
|
+
try {
|
|
1961
|
+
const message = JSON.parse(data.toString());
|
|
1962
|
+
if (message.id !== 1) {
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
const responseError = message.error;
|
|
1966
|
+
if (responseError) {
|
|
1967
|
+
settle(() => reject(new Error(responseError.message || "Browser.close failed")));
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
settle(resolve);
|
|
1971
|
+
}
|
|
1972
|
+
catch (error) {
|
|
1973
|
+
settle(() => reject(error instanceof Error ? error : new Error(String(error))));
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
ws.on("error", (error) => {
|
|
1977
|
+
settle(() => reject(error));
|
|
1978
|
+
});
|
|
1979
|
+
ws.on("close", () => {
|
|
1980
|
+
if (commandSent) {
|
|
1981
|
+
settle(resolve);
|
|
1982
|
+
}
|
|
1983
|
+
else {
|
|
1984
|
+
settle(() => reject(new Error("Browser CDP websocket closed before Browser.close was sent")));
|
|
1985
|
+
}
|
|
1986
|
+
});
|
|
1987
|
+
});
|
|
1988
|
+
this.debugLog("Sent Browser.close command via browser CDP endpoint");
|
|
1989
|
+
}
|
|
1777
1990
|
async shutdown() {
|
|
1778
1991
|
this.isShuttingDown = true;
|
|
1779
1992
|
let browserClosedCleanly = false;
|
|
1993
|
+
if (this.externalCdpBase) {
|
|
1994
|
+
if (this.connection) {
|
|
1995
|
+
try {
|
|
1996
|
+
this.connection.ws.close();
|
|
1997
|
+
}
|
|
1998
|
+
catch (_e) {
|
|
1999
|
+
// Ignore close errors
|
|
2000
|
+
}
|
|
2001
|
+
this.connection = null;
|
|
2002
|
+
}
|
|
2003
|
+
this.chromePids.clear();
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
1780
2006
|
// Ask the browser process to exit first so Chrome doesn't think it crashed.
|
|
1781
|
-
if (this.connection
|
|
2007
|
+
if (this.connection) {
|
|
1782
2008
|
try {
|
|
1783
|
-
await this.
|
|
1784
|
-
this.
|
|
1785
|
-
browserClosedCleanly = await this.waitForBrowserExit(2000);
|
|
2009
|
+
await this.sendBrowserCloseCommand();
|
|
2010
|
+
browserClosedCleanly = await this.waitForBrowserExit(5000);
|
|
1786
2011
|
}
|
|
1787
2012
|
catch (_e) {
|
|
1788
2013
|
this.debugLog("Browser.close failed, trying page/tab close fallback");
|
|
1789
2014
|
}
|
|
1790
|
-
try {
|
|
1791
|
-
// Try to close the page
|
|
1792
|
-
await this.sendCDPCommand("Page.close");
|
|
1793
|
-
this.debugLog("Sent Page.close command");
|
|
1794
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1795
|
-
}
|
|
1796
|
-
catch (_e) {
|
|
1797
|
-
this.debugLog("Page.close failed, trying Target.closeTarget");
|
|
1798
|
-
}
|
|
1799
|
-
try {
|
|
1800
|
-
// Get the list of targets to find our specific tab
|
|
1801
|
-
const targets = (await this.sendCDPCommand("Target.getTargets"));
|
|
1802
|
-
this.debugLog(`Found ${targets.targetInfos?.length || 0} targets`);
|
|
1803
|
-
// Find our page target
|
|
1804
|
-
const pageTarget = targets.targetInfos?.find((t) => t.type === "page");
|
|
1805
|
-
if (pageTarget) {
|
|
1806
|
-
this.debugLog(`Closing page target: ${pageTarget.targetId}`);
|
|
1807
|
-
await this.sendCDPCommand("Target.closeTarget", {
|
|
1808
|
-
targetId: pageTarget.targetId
|
|
1809
|
-
});
|
|
1810
|
-
this.debugLog("Closed Chrome tab via CDP");
|
|
1811
|
-
}
|
|
1812
|
-
// Give it more time for the tab to close
|
|
1813
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1814
|
-
}
|
|
1815
|
-
catch (_e) {
|
|
1816
|
-
this.debugLog("Failed to close tab via CDP, will force close Chrome");
|
|
1817
|
-
}
|
|
1818
2015
|
if (!browserClosedCleanly) {
|
|
1819
|
-
|
|
2016
|
+
try {
|
|
2017
|
+
// Try to close the page
|
|
2018
|
+
await this.sendCDPCommand("Page.close");
|
|
2019
|
+
this.debugLog("Sent Page.close command");
|
|
2020
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2021
|
+
}
|
|
2022
|
+
catch (_e) {
|
|
2023
|
+
this.debugLog("Page.close failed, trying Target.closeTarget");
|
|
2024
|
+
}
|
|
2025
|
+
try {
|
|
2026
|
+
// Get the list of targets to find our specific tab
|
|
2027
|
+
const targets = (await this.sendCDPCommand("Target.getTargets"));
|
|
2028
|
+
this.debugLog(`Found ${targets.targetInfos?.length || 0} targets`);
|
|
2029
|
+
// Find our page target
|
|
2030
|
+
const pageTarget = targets.targetInfos?.find((t) => t.type === "page");
|
|
2031
|
+
if (pageTarget) {
|
|
2032
|
+
this.debugLog(`Closing page target: ${pageTarget.targetId}`);
|
|
2033
|
+
await this.sendCDPCommand("Target.closeTarget", {
|
|
2034
|
+
targetId: pageTarget.targetId
|
|
2035
|
+
});
|
|
2036
|
+
this.debugLog("Closed Chrome tab via CDP");
|
|
2037
|
+
}
|
|
2038
|
+
// Give it more time for the tab to close
|
|
2039
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2040
|
+
}
|
|
2041
|
+
catch (_e) {
|
|
2042
|
+
this.debugLog("Failed to close tab via CDP, will force close Chrome");
|
|
2043
|
+
}
|
|
2044
|
+
if (!browserClosedCleanly) {
|
|
2045
|
+
browserClosedCleanly = await this.waitForBrowserExit(1500);
|
|
2046
|
+
}
|
|
1820
2047
|
}
|
|
1821
2048
|
}
|
|
1822
2049
|
// Close CDP connection
|