dev3000 0.0.173 → 0.0.174
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/dist/cdp-monitor.d.ts +18 -2
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +234 -79
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +24 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +75 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/dev-environment.d.ts +23 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +174 -23
- package/dist/dev-environment.js.map +1 -1
- package/dist/tui-interface-opentui.d.ts +2 -0
- package/dist/tui-interface-opentui.d.ts.map +1 -1
- package/dist/tui-interface-opentui.js +17 -3
- package/dist/tui-interface-opentui.js.map +1 -1
- package/dist/utils/agent-selection.js +1 -1
- package/dist/utils/agent-selection.js.map +1 -1
- package/dist/utils/project-metadata.d.ts +4 -0
- package/dist/utils/project-metadata.d.ts.map +1 -0
- package/dist/utils/project-metadata.js +48 -0
- package/dist/utils/project-metadata.js.map +1 -0
- package/package.json +8 -10
package/dist/cdp-monitor.d.ts
CHANGED
|
@@ -10,6 +10,18 @@ export interface CDPConnection {
|
|
|
10
10
|
sessionId: string | null;
|
|
11
11
|
nextId: number;
|
|
12
12
|
}
|
|
13
|
+
export interface CDPTargetInfo {
|
|
14
|
+
id?: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
webSocketDebuggerUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function getLoadingHtmlCandidates(currentDir: string, execPath?: string): string[];
|
|
21
|
+
export declare function selectCDPTarget(targets: CDPTargetInfo[], options?: {
|
|
22
|
+
appServerPort?: string;
|
|
23
|
+
initialAppUrl?: string;
|
|
24
|
+
}): CDPTargetInfo;
|
|
13
25
|
export declare class CDPMonitor {
|
|
14
26
|
private browser;
|
|
15
27
|
private connection;
|
|
@@ -36,7 +48,9 @@ export declare class CDPMonitor {
|
|
|
36
48
|
private framework?;
|
|
37
49
|
private reactTrackingEnabled;
|
|
38
50
|
private lastReactSnapshotLogTime;
|
|
39
|
-
|
|
51
|
+
private pendingCommands;
|
|
52
|
+
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");
|
|
40
54
|
private resolveReactDevToolsExtensionPath;
|
|
41
55
|
private debugLog;
|
|
42
56
|
private runCommand;
|
|
@@ -68,8 +82,9 @@ export declare class CDPMonitor {
|
|
|
68
82
|
private enableCDPDomains;
|
|
69
83
|
private setupEventHandlers;
|
|
70
84
|
private onCDPEvent;
|
|
85
|
+
private rejectPendingCDPCommands;
|
|
71
86
|
private handleCDPMessage;
|
|
72
|
-
navigateToUrl(url: string): Promise<void>;
|
|
87
|
+
navigateToUrl(url: string, timeoutMs?: number): Promise<void>;
|
|
73
88
|
navigateToApp(port: string, useHttps?: boolean): Promise<void>;
|
|
74
89
|
private setupInteractionTracking;
|
|
75
90
|
private startInteractionPolling;
|
|
@@ -98,6 +113,7 @@ export declare class CDPMonitor {
|
|
|
98
113
|
* Call this before killing the app server to prevent CDP reconnection loops.
|
|
99
114
|
*/
|
|
100
115
|
prepareShutdown(): void;
|
|
116
|
+
private waitForBrowserExit;
|
|
101
117
|
shutdown(): Promise<void>;
|
|
102
118
|
private killInstanceChromeProcesses;
|
|
103
119
|
}
|
|
@@ -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;
|
|
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
|
@@ -43,6 +43,98 @@ const EMBEDDED_LOADING_HTML = `<!DOCTYPE html>
|
|
|
43
43
|
</div>
|
|
44
44
|
</body>
|
|
45
45
|
</html>`;
|
|
46
|
+
const DEFAULT_CDP_COMMAND_TIMEOUT_MS = 10000;
|
|
47
|
+
const DEFAULT_NAVIGATION_TIMEOUT_MS = 60000;
|
|
48
|
+
export function getLoadingHtmlCandidates(currentDir, execPath = process.execPath) {
|
|
49
|
+
const candidates = [join(currentDir, "src/loading.html"), join(currentDir, "loading.html")];
|
|
50
|
+
const packageRoot = dirname(dirname(execPath));
|
|
51
|
+
candidates.push(join(packageRoot, "src/loading.html"));
|
|
52
|
+
candidates.push(join(packageRoot, "loading.html"));
|
|
53
|
+
candidates.push(join(process.cwd(), "src/loading.html"));
|
|
54
|
+
return candidates;
|
|
55
|
+
}
|
|
56
|
+
function isD3kLoadingPageUrl(url) {
|
|
57
|
+
if (!url?.startsWith("file://")) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const pathname = decodeURIComponent(new URL(url).pathname);
|
|
62
|
+
return pathname.includes("/dev3000-loading-") && pathname.endsWith("/loading.html");
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function matchesAppServerPort(url, appServerPort) {
|
|
69
|
+
if (!url || !appServerPort) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const parsed = new URL(url);
|
|
74
|
+
const hostname = parsed.hostname;
|
|
75
|
+
const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
|
76
|
+
return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0") && port === appServerPort;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function matchesInitialAppUrl(url, initialAppUrl) {
|
|
83
|
+
if (!url || !initialAppUrl) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const actual = new URL(url);
|
|
88
|
+
const expected = new URL(initialAppUrl);
|
|
89
|
+
const actualPort = actual.port || (actual.protocol === "https:" ? "443" : "80");
|
|
90
|
+
const expectedPort = expected.port || (expected.protocol === "https:" ? "443" : "80");
|
|
91
|
+
return (actual.protocol === expected.protocol &&
|
|
92
|
+
actual.hostname === expected.hostname &&
|
|
93
|
+
actualPort === expectedPort &&
|
|
94
|
+
actual.pathname === expected.pathname);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function formatTargetForLog(target) {
|
|
101
|
+
return `${target.type || "unknown"}:${target.url || "<no-url>"}`;
|
|
102
|
+
}
|
|
103
|
+
export function selectCDPTarget(targets, options = {}) {
|
|
104
|
+
const debuggableTargets = targets.filter((target) => target.webSocketDebuggerUrl);
|
|
105
|
+
if (debuggableTargets.length === 0) {
|
|
106
|
+
throw new Error(`No debuggable target found in Chrome (found ${targets.length} targets)`);
|
|
107
|
+
}
|
|
108
|
+
const contextualMatches = debuggableTargets
|
|
109
|
+
.map((target) => ({
|
|
110
|
+
target,
|
|
111
|
+
score: (matchesInitialAppUrl(target.url, options.initialAppUrl) ? 100 : 0) +
|
|
112
|
+
(matchesAppServerPort(target.url, options.appServerPort) ? 70 : 0) +
|
|
113
|
+
(isD3kLoadingPageUrl(target.url) ? 90 : 0) +
|
|
114
|
+
(target.type === "page" ? 10 : 0)
|
|
115
|
+
}))
|
|
116
|
+
.sort((left, right) => right.score - left.score);
|
|
117
|
+
const bestContextualScore = contextualMatches[0]?.score || 0;
|
|
118
|
+
const minimumContextualScore = 70;
|
|
119
|
+
if (bestContextualScore >= minimumContextualScore) {
|
|
120
|
+
return contextualMatches[0].target;
|
|
121
|
+
}
|
|
122
|
+
const pageTarget = debuggableTargets.find((target) => target.type === "page");
|
|
123
|
+
if (!options.appServerPort && !options.initialAppUrl) {
|
|
124
|
+
return pageTarget || debuggableTargets[0];
|
|
125
|
+
}
|
|
126
|
+
if (debuggableTargets.length === 1 && debuggableTargets[0]?.url === "about:blank") {
|
|
127
|
+
return debuggableTargets[0];
|
|
128
|
+
}
|
|
129
|
+
const expectedDetails = [
|
|
130
|
+
options.initialAppUrl ? `url ${options.initialAppUrl}` : null,
|
|
131
|
+
options.appServerPort ? `port ${options.appServerPort}` : null,
|
|
132
|
+
"d3k loading page"
|
|
133
|
+
]
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
.join(", ");
|
|
136
|
+
throw new Error(`CDP target mismatch on port ${options.appServerPort || "unknown"}; expected ${expectedDetails}, found ${debuggableTargets.map(formatTargetForLog).join(", ")}`);
|
|
137
|
+
}
|
|
46
138
|
export class CDPMonitor {
|
|
47
139
|
browser = null;
|
|
48
140
|
connection = null;
|
|
@@ -69,7 +161,9 @@ export class CDPMonitor {
|
|
|
69
161
|
framework; // Framework hint from project detection
|
|
70
162
|
reactTrackingEnabled = false;
|
|
71
163
|
lastReactSnapshotLogTime = 0;
|
|
72
|
-
|
|
164
|
+
pendingCommands = new Map();
|
|
165
|
+
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) {
|
|
73
167
|
this.profileDir = profileDir;
|
|
74
168
|
this.screenshotDir = screenshotDir;
|
|
75
169
|
this.appServerPort = appServerPort;
|
|
@@ -78,6 +172,7 @@ export class CDPMonitor {
|
|
|
78
172
|
this.browserPath = browserPath;
|
|
79
173
|
this.pluginReactScan = pluginReactScan;
|
|
80
174
|
this.initialAppUrl = initialAppUrl;
|
|
175
|
+
this.navigationTimeoutMs = navigationTimeoutMs;
|
|
81
176
|
this.headless = headless;
|
|
82
177
|
this.framework = framework;
|
|
83
178
|
this.reactTrackingEnabled = framework === "nextjs";
|
|
@@ -269,11 +364,7 @@ export class CDPMonitor {
|
|
|
269
364
|
const currentDir = dirname(currentFile);
|
|
270
365
|
let loadingHtml;
|
|
271
366
|
try {
|
|
272
|
-
const loadingHtmlCandidates =
|
|
273
|
-
join(currentDir, "src/loading.html"),
|
|
274
|
-
join(currentDir, "loading.html"),
|
|
275
|
-
join(process.cwd(), "src/loading.html")
|
|
276
|
-
];
|
|
367
|
+
const loadingHtmlCandidates = getLoadingHtmlCandidates(currentDir);
|
|
277
368
|
const loadingHtmlPath = loadingHtmlCandidates.find((path) => existsSync(path));
|
|
278
369
|
if (!loadingHtmlPath) {
|
|
279
370
|
throw new Error("No loading.html found in expected locations");
|
|
@@ -442,6 +533,10 @@ export class CDPMonitor {
|
|
|
442
533
|
chromeArgs.push("--disable-setuid-sandbox");
|
|
443
534
|
chromeArgs.push("--disable-gpu");
|
|
444
535
|
chromeArgs.push("--disable-dev-shm-usage");
|
|
536
|
+
chromeArgs.push("--disable-background-timer-throttling");
|
|
537
|
+
chromeArgs.push("--disable-backgrounding-occluded-windows");
|
|
538
|
+
chromeArgs.push("--disable-renderer-backgrounding");
|
|
539
|
+
chromeArgs.push("--window-size=1920,1080");
|
|
445
540
|
this.debugLog("Launching Chrome in headless mode");
|
|
446
541
|
}
|
|
447
542
|
else {
|
|
@@ -538,15 +633,15 @@ export class CDPMonitor {
|
|
|
538
633
|
try {
|
|
539
634
|
// Get the WebSocket URL from Chrome's debug endpoint
|
|
540
635
|
const targetsResponse = await fetch(`http://localhost:${this.debugPort}/json`);
|
|
541
|
-
let targets = await targetsResponse.json();
|
|
542
|
-
this.debugLog(`Found ${targets.length} targets: ${JSON.stringify(targets.map((
|
|
636
|
+
let targets = (await targetsResponse.json());
|
|
637
|
+
this.debugLog(`Found ${targets.length} targets: ${JSON.stringify(targets.map((target) => ({ type: target.type ?? "unknown", url: target.url ?? "" })))}`);
|
|
543
638
|
if (targets.length === 0) {
|
|
544
639
|
this.debugLog("No debuggable targets found; creating a blank page target");
|
|
545
640
|
const newTargetResponse = await fetch(`http://localhost:${this.debugPort}/json/new?about:blank`, {
|
|
546
641
|
method: "PUT"
|
|
547
642
|
});
|
|
548
643
|
if (newTargetResponse.ok) {
|
|
549
|
-
const createdTarget = await newTargetResponse.json();
|
|
644
|
+
const createdTarget = (await newTargetResponse.json());
|
|
550
645
|
targets = [createdTarget];
|
|
551
646
|
this.debugLog(`Created page target: ${createdTarget.id || "unknown"} - ${createdTarget.url || ""}`);
|
|
552
647
|
}
|
|
@@ -554,19 +649,14 @@ export class CDPMonitor {
|
|
|
554
649
|
this.debugLog(`Failed to create page target: HTTP ${newTargetResponse.status}`);
|
|
555
650
|
}
|
|
556
651
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
pageTarget = targets.find((target) => target.webSocketDebuggerUrl);
|
|
562
|
-
if (pageTarget) {
|
|
563
|
-
this.debugLog(`No 'page' type target found, using target of type '${pageTarget.type}' instead`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (!pageTarget) {
|
|
567
|
-
throw new Error(`No debuggable target found in Chrome (found ${targets.length} targets)`);
|
|
568
|
-
}
|
|
652
|
+
const pageTarget = selectCDPTarget(targets, {
|
|
653
|
+
appServerPort: this.appServerPort,
|
|
654
|
+
initialAppUrl: this.initialAppUrl
|
|
655
|
+
});
|
|
569
656
|
const wsUrl = pageTarget.webSocketDebuggerUrl;
|
|
657
|
+
if (!wsUrl) {
|
|
658
|
+
throw new Error(`Selected CDP target does not have a websocket URL: ${formatTargetForLog(pageTarget)}`);
|
|
659
|
+
}
|
|
570
660
|
this.cdpUrl = wsUrl; // Store the CDP URL
|
|
571
661
|
this.debugLog(`Found page target: ${pageTarget.title || "Unknown"} - ${pageTarget.url}`);
|
|
572
662
|
this.debugLog(`Got CDP WebSocket URL: ${wsUrl}`);
|
|
@@ -599,6 +689,7 @@ export class CDPMonitor {
|
|
|
599
689
|
});
|
|
600
690
|
ws.on("close", (code, reason) => {
|
|
601
691
|
this.debugLog(`WebSocket closed with code ${code}, reason: ${reason}`);
|
|
692
|
+
this.rejectPendingCDPCommands(new Error(`CDP connection closed before response (code=${code})`));
|
|
602
693
|
if (!this.isShuttingDown) {
|
|
603
694
|
this.logger("browser", `[CDP] Connection lost unexpectedly (code: ${code}, reason: ${reason})`);
|
|
604
695
|
this.logger("browser", "[CDP] CDP connection lost - check for Chrome crash or server issues");
|
|
@@ -682,7 +773,7 @@ export class CDPMonitor {
|
|
|
682
773
|
throw err;
|
|
683
774
|
}
|
|
684
775
|
}
|
|
685
|
-
async sendCDPCommand(method, params = {}) {
|
|
776
|
+
async sendCDPCommand(method, params = {}, timeoutMs = DEFAULT_CDP_COMMAND_TIMEOUT_MS) {
|
|
686
777
|
if (!this.connection) {
|
|
687
778
|
throw new Error("No CDP connection available");
|
|
688
779
|
}
|
|
@@ -693,42 +784,32 @@ export class CDPMonitor {
|
|
|
693
784
|
method,
|
|
694
785
|
params
|
|
695
786
|
};
|
|
696
|
-
const messageHandler = (data) => {
|
|
697
|
-
try {
|
|
698
|
-
const message = JSON.parse(data.toString());
|
|
699
|
-
if (message.id === id) {
|
|
700
|
-
this.connection?.ws.removeListener("message", messageHandler);
|
|
701
|
-
if (message.error) {
|
|
702
|
-
reject(new Error(message.error.message));
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
resolve(message.result);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
catch (error) {
|
|
710
|
-
this.connection?.ws.removeListener("message", messageHandler);
|
|
711
|
-
reject(error);
|
|
712
|
-
}
|
|
713
|
-
};
|
|
714
|
-
this.connection?.ws.on("message", messageHandler);
|
|
715
|
-
// Command timeout
|
|
716
787
|
const timeout = setTimeout(() => {
|
|
717
|
-
this.
|
|
788
|
+
this.pendingCommands.delete(id);
|
|
789
|
+
this.debugLog(`CDP command #${id} timed out after ${timeoutMs}ms: ${method}`);
|
|
718
790
|
reject(new Error(`CDP command timeout: ${method}`));
|
|
719
|
-
},
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
791
|
+
}, timeoutMs);
|
|
792
|
+
this.pendingCommands.set(id, {
|
|
793
|
+
method,
|
|
794
|
+
startedAt: Date.now(),
|
|
795
|
+
timeout,
|
|
796
|
+
resolve,
|
|
797
|
+
reject: (error) => reject(error)
|
|
798
|
+
});
|
|
799
|
+
this.debugLog(`Sending CDP command #${id}: ${method} ${JSON.stringify(params)}`);
|
|
800
|
+
this.connection?.ws.send(JSON.stringify(command), (error) => {
|
|
801
|
+
if (!error) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const pending = this.pendingCommands.get(id);
|
|
805
|
+
if (!pending) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
clearTimeout(pending.timeout);
|
|
809
|
+
this.pendingCommands.delete(id);
|
|
810
|
+
this.debugLog(`Failed to send CDP command #${id}: ${method}: ${String(error)}`);
|
|
811
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
812
|
+
});
|
|
732
813
|
});
|
|
733
814
|
}
|
|
734
815
|
getBundleTypeLabel(bundleType) {
|
|
@@ -808,39 +889,39 @@ export class CDPMonitor {
|
|
|
808
889
|
}
|
|
809
890
|
async enableCDPDomains() {
|
|
810
891
|
const domains = [
|
|
811
|
-
"Runtime",
|
|
812
|
-
"Network",
|
|
813
|
-
"Page",
|
|
814
|
-
"DOM",
|
|
815
|
-
"Performance",
|
|
816
|
-
"Security",
|
|
817
|
-
"Log",
|
|
818
|
-
"Target"
|
|
892
|
+
{ name: "Runtime", required: true },
|
|
893
|
+
{ name: "Network", required: true },
|
|
894
|
+
{ name: "Page", required: true },
|
|
895
|
+
{ name: "DOM", required: true },
|
|
896
|
+
{ name: "Performance", required: false },
|
|
897
|
+
{ name: "Security", required: false },
|
|
898
|
+
{ name: "Log", required: true },
|
|
899
|
+
{ name: "Target", required: false }
|
|
819
900
|
// Note: Input domain is for dispatching events, not monitoring them - we use JS injection instead
|
|
820
901
|
];
|
|
821
902
|
for (const domain of domains) {
|
|
822
903
|
try {
|
|
823
|
-
this.debugLog(`Enabling CDP domain: ${domain}`);
|
|
824
|
-
await this.sendCDPCommand(`${domain}.enable
|
|
825
|
-
this.debugLog(`Successfully enabled CDP domain: ${domain}`);
|
|
904
|
+
this.debugLog(`Enabling CDP domain: ${domain.name}`);
|
|
905
|
+
await this.sendCDPCommand(`${domain.name}.enable`, {}, 3000);
|
|
906
|
+
this.debugLog(`Successfully enabled CDP domain: ${domain.name}`);
|
|
826
907
|
if (this.debug) {
|
|
827
|
-
this.logger("browser", `[CDP] Enabled ${domain} domain`);
|
|
908
|
+
this.logger("browser", `[CDP] Enabled ${domain.name} domain`);
|
|
828
909
|
}
|
|
829
910
|
}
|
|
830
911
|
catch (error) {
|
|
831
|
-
this.debugLog(`Failed to enable CDP domain ${domain}: ${error}`);
|
|
832
|
-
|
|
912
|
+
this.debugLog(`Failed to enable CDP domain ${domain.name}: ${error}`);
|
|
913
|
+
if (domain.required) {
|
|
914
|
+
throw new Error(`Failed to enable required CDP domain ${domain.name}: ${String(error)}`);
|
|
915
|
+
}
|
|
833
916
|
if (this.debug) {
|
|
834
|
-
this.logger("browser", `[CDP] Failed to enable ${domain}: ${error}`);
|
|
917
|
+
this.logger("browser", `[CDP] Failed to enable optional ${domain.name}: ${error}`);
|
|
835
918
|
}
|
|
836
|
-
// Continue with other domains instead of throwing
|
|
837
919
|
}
|
|
838
920
|
}
|
|
839
|
-
this.debugLog("
|
|
840
|
-
await this.sendCDPCommand("Runtime.enable");
|
|
921
|
+
this.debugLog("Setting async call stack depth for console and exception capture");
|
|
841
922
|
await this.sendCDPCommand("Runtime.setAsyncCallStackDepth", {
|
|
842
923
|
maxDepth: 32
|
|
843
|
-
});
|
|
924
|
+
}, 3000);
|
|
844
925
|
this.debugLog("CDP domains enabled successfully");
|
|
845
926
|
// Set viewport for headless mode to ensure consistent CLS measurements
|
|
846
927
|
// Without this, headless Chrome defaults to 800x600 which can cause
|
|
@@ -859,6 +940,18 @@ export class CDPMonitor {
|
|
|
859
940
|
catch (error) {
|
|
860
941
|
this.debugLog(`Failed to set viewport: ${error}`);
|
|
861
942
|
}
|
|
943
|
+
try {
|
|
944
|
+
await this.sendCDPCommand("Page.bringToFront", {});
|
|
945
|
+
}
|
|
946
|
+
catch (error) {
|
|
947
|
+
this.debugLog(`Failed to bring headless page to front: ${error}`);
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
await this.sendCDPCommand("Emulation.setFocusEmulationEnabled", { enabled: true });
|
|
951
|
+
}
|
|
952
|
+
catch (error) {
|
|
953
|
+
this.debugLog(`Failed to enable focus emulation: ${error}`);
|
|
954
|
+
}
|
|
862
955
|
}
|
|
863
956
|
}
|
|
864
957
|
setupEventHandlers() {
|
|
@@ -1089,8 +1182,34 @@ export class CDPMonitor {
|
|
|
1089
1182
|
onCDPEvent(method, handler) {
|
|
1090
1183
|
this.eventHandlers.set(method, handler);
|
|
1091
1184
|
}
|
|
1185
|
+
rejectPendingCDPCommands(error) {
|
|
1186
|
+
for (const [id, pending] of this.pendingCommands) {
|
|
1187
|
+
clearTimeout(pending.timeout);
|
|
1188
|
+
this.pendingCommands.delete(id);
|
|
1189
|
+
pending.reject(error);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1092
1192
|
handleCDPMessage(message) {
|
|
1193
|
+
if (typeof message.id === "number") {
|
|
1194
|
+
const pending = this.pendingCommands.get(message.id);
|
|
1195
|
+
if (!pending) {
|
|
1196
|
+
this.debugLog(`Received CDP response for unknown command #${message.id}`);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
clearTimeout(pending.timeout);
|
|
1200
|
+
this.pendingCommands.delete(message.id);
|
|
1201
|
+
const duration = Date.now() - pending.startedAt;
|
|
1202
|
+
if (message.error) {
|
|
1203
|
+
this.debugLog(`Received CDP error for #${message.id} (${pending.method}) after ${duration}ms: ${message.error.message}`);
|
|
1204
|
+
pending.reject(new Error(message.error.message || `CDP command failed: ${pending.method}`));
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
this.debugLog(`Received CDP response for #${message.id} (${pending.method}) after ${duration}ms`);
|
|
1208
|
+
pending.resolve(message.result || {});
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1093
1211
|
if (message.method) {
|
|
1212
|
+
this.debugLog(`Received CDP event: ${message.method}`);
|
|
1094
1213
|
const handler = this.eventHandlers.get(message.method);
|
|
1095
1214
|
if (handler) {
|
|
1096
1215
|
const event = {
|
|
@@ -1103,7 +1222,7 @@ export class CDPMonitor {
|
|
|
1103
1222
|
}
|
|
1104
1223
|
}
|
|
1105
1224
|
}
|
|
1106
|
-
async navigateToUrl(url) {
|
|
1225
|
+
async navigateToUrl(url, timeoutMs = this.navigationTimeoutMs) {
|
|
1107
1226
|
if (!this.connection) {
|
|
1108
1227
|
throw new Error("No CDP connection available");
|
|
1109
1228
|
}
|
|
@@ -1113,7 +1232,7 @@ export class CDPMonitor {
|
|
|
1113
1232
|
try {
|
|
1114
1233
|
const result = await this.sendCDPCommand("Page.navigate", {
|
|
1115
1234
|
url
|
|
1116
|
-
});
|
|
1235
|
+
}, timeoutMs);
|
|
1117
1236
|
const navigationTime = Date.now() - navigationStartTime;
|
|
1118
1237
|
this.debugLog(`Navigation command sent successfully (${navigationTime}ms)`);
|
|
1119
1238
|
this.debugLog(`Navigation result: ${JSON.stringify(result)}`);
|
|
@@ -1635,10 +1754,39 @@ export class CDPMonitor {
|
|
|
1635
1754
|
this.isShuttingDown = true;
|
|
1636
1755
|
this.debugLog("Shutdown signaled - reconnection attempts will be blocked");
|
|
1637
1756
|
}
|
|
1757
|
+
async waitForBrowserExit(timeoutMs) {
|
|
1758
|
+
const browser = this.browser;
|
|
1759
|
+
if (!browser) {
|
|
1760
|
+
return true;
|
|
1761
|
+
}
|
|
1762
|
+
if (browser.exitCode !== null || browser.killed) {
|
|
1763
|
+
return true;
|
|
1764
|
+
}
|
|
1765
|
+
return await new Promise((resolve) => {
|
|
1766
|
+
const onExit = () => {
|
|
1767
|
+
clearTimeout(timer);
|
|
1768
|
+
resolve(true);
|
|
1769
|
+
};
|
|
1770
|
+
const timer = setTimeout(() => {
|
|
1771
|
+
browser.removeListener("exit", onExit);
|
|
1772
|
+
resolve(false);
|
|
1773
|
+
}, timeoutMs);
|
|
1774
|
+
browser.once("exit", onExit);
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1638
1777
|
async shutdown() {
|
|
1639
1778
|
this.isShuttingDown = true;
|
|
1640
|
-
|
|
1779
|
+
let browserClosedCleanly = false;
|
|
1780
|
+
// Ask the browser process to exit first so Chrome doesn't think it crashed.
|
|
1641
1781
|
if (this.connection?.sessionId) {
|
|
1782
|
+
try {
|
|
1783
|
+
await this.sendCDPCommand("Browser.close");
|
|
1784
|
+
this.debugLog("Sent Browser.close command");
|
|
1785
|
+
browserClosedCleanly = await this.waitForBrowserExit(2000);
|
|
1786
|
+
}
|
|
1787
|
+
catch (_e) {
|
|
1788
|
+
this.debugLog("Browser.close failed, trying page/tab close fallback");
|
|
1789
|
+
}
|
|
1642
1790
|
try {
|
|
1643
1791
|
// Try to close the page
|
|
1644
1792
|
await this.sendCDPCommand("Page.close");
|
|
@@ -1667,6 +1815,9 @@ export class CDPMonitor {
|
|
|
1667
1815
|
catch (_e) {
|
|
1668
1816
|
this.debugLog("Failed to close tab via CDP, will force close Chrome");
|
|
1669
1817
|
}
|
|
1818
|
+
if (!browserClosedCleanly) {
|
|
1819
|
+
browserClosedCleanly = await this.waitForBrowserExit(1500);
|
|
1820
|
+
}
|
|
1670
1821
|
}
|
|
1671
1822
|
// Close CDP connection
|
|
1672
1823
|
if (this.connection) {
|
|
@@ -1678,7 +1829,11 @@ export class CDPMonitor {
|
|
|
1678
1829
|
}
|
|
1679
1830
|
this.connection = null;
|
|
1680
1831
|
}
|
|
1681
|
-
|
|
1832
|
+
if (browserClosedCleanly) {
|
|
1833
|
+
this.chromePids.clear();
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
// Kill only the Chrome processes for THIS instance as a fallback.
|
|
1682
1837
|
await this.killInstanceChromeProcesses();
|
|
1683
1838
|
}
|
|
1684
1839
|
async killInstanceChromeProcesses() {
|