dev3000 0.0.186 → 0.0.188
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 +101 -13
- package/bin/dev3000 +1 -2
- package/dist/cdp-monitor.d.ts +7 -12
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +86 -170
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.d.ts +1 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -1369
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/portless.d.ts +3 -0
- package/dist/portless.d.ts.map +1 -1
- package/dist/portless.js +10 -0
- package/dist/portless.js.map +1 -1
- package/dist/runtime/bridge.d.ts +26 -0
- package/dist/runtime/bridge.d.ts.map +1 -0
- package/dist/runtime/bridge.js +340 -0
- package/dist/runtime/bridge.js.map +1 -0
- package/dist/runtime/chrome-isolation.d.ts +16 -0
- package/dist/runtime/chrome-isolation.d.ts.map +1 -0
- package/dist/runtime/chrome-isolation.js +119 -0
- package/dist/runtime/chrome-isolation.js.map +1 -0
- package/dist/runtime/cli.js +116 -26
- package/dist/runtime/cli.js.map +1 -1
- package/dist/runtime/event-views.d.ts +21 -0
- package/dist/runtime/event-views.d.ts.map +1 -0
- package/dist/runtime/event-views.js +53 -0
- package/dist/runtime/event-views.js.map +1 -0
- package/dist/runtime/harness.d.ts +16 -0
- package/dist/runtime/harness.d.ts.map +1 -0
- package/dist/runtime/harness.js +68 -0
- package/dist/runtime/harness.js.map +1 -0
- package/dist/runtime/local-proxy.d.ts +12 -0
- package/dist/runtime/local-proxy.d.ts.map +1 -0
- package/dist/runtime/local-proxy.js +114 -0
- package/dist/runtime/local-proxy.js.map +1 -0
- package/dist/runtime/runtime.d.ts +14 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +80 -19
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/sandbox-agent.d.ts +6 -0
- package/dist/runtime/sandbox-agent.d.ts.map +1 -0
- package/dist/runtime/sandbox-agent.js +35 -0
- package/dist/runtime/sandbox-agent.js.map +1 -0
- package/dist/runtime/sandbox-command.d.ts +9 -0
- package/dist/runtime/sandbox-command.d.ts.map +1 -0
- package/dist/runtime/sandbox-command.js +101 -0
- package/dist/runtime/sandbox-command.js.map +1 -0
- package/dist/runtime/sandbox-proxy.d.ts +11 -0
- package/dist/runtime/sandbox-proxy.d.ts.map +1 -0
- package/dist/runtime/sandbox-proxy.js +123 -0
- package/dist/runtime/sandbox-proxy.js.map +1 -0
- package/dist/runtime/sandbox-sessions.d.ts +31 -0
- package/dist/runtime/sandbox-sessions.d.ts.map +1 -0
- package/dist/runtime/sandbox-sessions.js +102 -0
- package/dist/runtime/sandbox-sessions.js.map +1 -0
- package/dist/runtime/sandbox.d.ts +21 -0
- package/dist/runtime/sandbox.d.ts.map +1 -0
- package/dist/runtime/sandbox.js +481 -0
- package/dist/runtime/sandbox.js.map +1 -0
- package/dist/utils/agent-browser.d.ts +2 -120
- package/dist/utils/agent-browser.d.ts.map +1 -1
- package/dist/utils/agent-browser.js +13 -376
- package/dist/utils/agent-browser.js.map +1 -1
- package/dist/utils/project-name.d.ts +2 -2
- package/dist/utils/project-name.js +2 -2
- package/package.json +13 -22
- package/skills/d3k/SKILL.md +23 -1
- package/dist/commands/crawl.d.ts +0 -12
- package/dist/commands/crawl.d.ts.map +0 -1
- package/dist/commands/crawl.js +0 -101
- package/dist/commands/crawl.js.map +0 -1
- package/dist/commands/errors.d.ts +0 -14
- package/dist/commands/errors.d.ts.map +0 -1
- package/dist/commands/errors.js +0 -246
- package/dist/commands/errors.js.map +0 -1
- package/dist/commands/fix.d.ts +0 -13
- package/dist/commands/fix.d.ts.map +0 -1
- package/dist/commands/fix.js +0 -219
- package/dist/commands/fix.js.map +0 -1
- package/dist/commands/logs.d.ts +0 -13
- package/dist/commands/logs.d.ts.map +0 -1
- package/dist/commands/logs.js +0 -146
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/resume.d.ts +0 -11
- package/dist/commands/resume.d.ts.map +0 -1
- package/dist/commands/resume.js +0 -75
- package/dist/commands/resume.js.map +0 -1
- package/dist/commands/status.d.ts +0 -21
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -44
- package/dist/commands/status.js.map +0 -1
- package/dist/components/AgentSelector.d.ts +0 -12
- package/dist/components/AgentSelector.d.ts.map +0 -1
- package/dist/components/AgentSelector.js +0 -99
- package/dist/components/AgentSelector.js.map +0 -1
- package/dist/components/PackageSelector.d.ts +0 -13
- package/dist/components/PackageSelector.d.ts.map +0 -1
- package/dist/components/PackageSelector.js +0 -79
- package/dist/components/PackageSelector.js.map +0 -1
- package/dist/constants/log-colors.d.ts +0 -43
- package/dist/constants/log-colors.d.ts.map +0 -1
- package/dist/constants/log-colors.js +0 -59
- package/dist/constants/log-colors.js.map +0 -1
- package/dist/dev-environment.d.ts +0 -187
- package/dist/dev-environment.d.ts.map +0 -1
- package/dist/dev-environment.js +0 -2248
- package/dist/dev-environment.js.map +0 -1
- package/dist/screencast-manager.d.ts +0 -78
- package/dist/screencast-manager.d.ts.map +0 -1
- package/dist/screencast-manager.js +0 -529
- package/dist/screencast-manager.js.map +0 -1
- package/dist/services/parsers/error-detectors/base.d.ts +0 -24
- package/dist/services/parsers/error-detectors/base.d.ts.map +0 -1
- package/dist/services/parsers/error-detectors/base.js +0 -44
- package/dist/services/parsers/error-detectors/base.js.map +0 -1
- package/dist/services/parsers/error-detectors/index.d.ts +0 -6
- package/dist/services/parsers/error-detectors/index.d.ts.map +0 -1
- package/dist/services/parsers/error-detectors/index.js +0 -6
- package/dist/services/parsers/error-detectors/index.js.map +0 -1
- package/dist/services/parsers/error-detectors/nextjs.d.ts +0 -9
- package/dist/services/parsers/error-detectors/nextjs.d.ts.map +0 -1
- package/dist/services/parsers/error-detectors/nextjs.js +0 -52
- package/dist/services/parsers/error-detectors/nextjs.js.map +0 -1
- package/dist/services/parsers/index.d.ts +0 -9
- package/dist/services/parsers/index.d.ts.map +0 -1
- package/dist/services/parsers/index.js +0 -10
- package/dist/services/parsers/index.js.map +0 -1
- package/dist/services/parsers/log-parsers/base.d.ts +0 -26
- package/dist/services/parsers/log-parsers/base.d.ts.map +0 -1
- package/dist/services/parsers/log-parsers/base.js +0 -6
- package/dist/services/parsers/log-parsers/base.js.map +0 -1
- package/dist/services/parsers/log-parsers/index.d.ts +0 -6
- package/dist/services/parsers/log-parsers/index.d.ts.map +0 -1
- package/dist/services/parsers/log-parsers/index.js +0 -5
- package/dist/services/parsers/log-parsers/index.js.map +0 -1
- package/dist/services/parsers/log-parsers/standard.d.ts +0 -9
- package/dist/services/parsers/log-parsers/standard.d.ts.map +0 -1
- package/dist/services/parsers/log-parsers/standard.js +0 -17
- package/dist/services/parsers/log-parsers/standard.js.map +0 -1
- package/dist/services/parsers/output-processor.d.ts +0 -39
- package/dist/services/parsers/output-processor.d.ts.map +0 -1
- package/dist/services/parsers/output-processor.js +0 -54
- package/dist/services/parsers/output-processor.js.map +0 -1
- package/dist/skills/d3k/internal-skill.md +0 -32
- package/dist/skills/index.d.ts +0 -54
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -214
- package/dist/skills/index.js.map +0 -1
- package/dist/src/tui-interface-impl.tsx +0 -644
- package/dist/tui-interface-impl.d.ts +0 -30
- package/dist/tui-interface-impl.d.ts.map +0 -1
- package/dist/tui-interface-impl.js +0 -403
- package/dist/tui-interface-impl.js.map +0 -1
- package/dist/tui-interface-opentui.d.ts +0 -33
- package/dist/tui-interface-opentui.d.ts.map +0 -1
- package/dist/tui-interface-opentui.js +0 -1128
- package/dist/tui-interface-opentui.js.map +0 -1
- package/dist/tui-interface.d.ts +0 -38
- package/dist/tui-interface.d.ts.map +0 -1
- package/dist/tui-interface.js +0 -84
- package/dist/tui-interface.js.map +0 -1
- package/dist/utils/agent-detection.d.ts +0 -17
- package/dist/utils/agent-detection.d.ts.map +0 -1
- package/dist/utils/agent-detection.js +0 -81
- package/dist/utils/agent-detection.js.map +0 -1
- package/dist/utils/agent-selection.d.ts +0 -25
- package/dist/utils/agent-selection.d.ts.map +0 -1
- package/dist/utils/agent-selection.js +0 -73
- package/dist/utils/agent-selection.js.map +0 -1
- package/dist/utils/browser-command-argv.d.ts +0 -19
- package/dist/utils/browser-command-argv.d.ts.map +0 -1
- package/dist/utils/browser-command-argv.js +0 -149
- package/dist/utils/browser-command-argv.js.map +0 -1
- package/dist/utils/build-version.d.ts +0 -2
- package/dist/utils/build-version.d.ts.map +0 -1
- package/dist/utils/build-version.js +0 -8
- package/dist/utils/build-version.js.map +0 -1
- package/dist/utils/cli-options.d.ts +0 -5
- package/dist/utils/cli-options.d.ts.map +0 -1
- package/dist/utils/cli-options.js +0 -36
- package/dist/utils/cli-options.js.map +0 -1
- package/dist/utils/d3k-dir.d.ts +0 -3
- package/dist/utils/d3k-dir.d.ts.map +0 -1
- package/dist/utils/d3k-dir.js +0 -12
- package/dist/utils/d3k-dir.js.map +0 -1
- package/dist/utils/d3k-skill-installer.d.ts +0 -4
- package/dist/utils/d3k-skill-installer.d.ts.map +0 -1
- package/dist/utils/d3k-skill-installer.js +0 -30
- package/dist/utils/d3k-skill-installer.js.map +0 -1
- package/dist/utils/log-filename.d.ts +0 -30
- package/dist/utils/log-filename.d.ts.map +0 -1
- package/dist/utils/log-filename.js +0 -60
- package/dist/utils/log-filename.js.map +0 -1
- package/dist/utils/project-detector.d.ts +0 -25
- package/dist/utils/project-detector.d.ts.map +0 -1
- package/dist/utils/project-detector.js +0 -153
- package/dist/utils/project-detector.js.map +0 -1
- package/dist/utils/project-metadata.d.ts +0 -4
- package/dist/utils/project-metadata.d.ts.map +0 -1
- package/dist/utils/project-metadata.js +0 -48
- package/dist/utils/project-metadata.js.map +0 -1
- package/dist/utils/repo-validate.d.ts +0 -2
- package/dist/utils/repo-validate.d.ts.map +0 -1
- package/dist/utils/repo-validate.js +0 -10
- package/dist/utils/repo-validate.js.map +0 -1
- package/dist/utils/session.d.ts +0 -19
- package/dist/utils/session.d.ts.map +0 -1
- package/dist/utils/session.js +0 -65
- package/dist/utils/session.js.map +0 -1
- package/dist/utils/skill-installer.d.ts +0 -65
- package/dist/utils/skill-installer.d.ts.map +0 -1
- package/dist/utils/skill-installer.js +0 -265
- package/dist/utils/skill-installer.js.map +0 -1
- package/dist/utils/timestamp.d.ts +0 -20
- package/dist/utils/timestamp.d.ts.map +0 -1
- package/dist/utils/timestamp.js +0 -57
- package/dist/utils/timestamp.js.map +0 -1
- package/dist/utils/tmux-helpers.d.ts +0 -36
- package/dist/utils/tmux-helpers.d.ts.map +0 -1
- package/dist/utils/tmux-helpers.js +0 -118
- package/dist/utils/tmux-helpers.js.map +0 -1
- package/dist/utils/user-config.d.ts +0 -12
- package/dist/utils/user-config.d.ts.map +0 -1
- package/dist/utils/user-config.js +0 -46
- package/dist/utils/user-config.js.map +0 -1
- package/dist/utils/version-check.d.ts +0 -52
- package/dist/utils/version-check.d.ts.map +0 -1
- package/dist/utils/version-check.js +0 -304
- package/dist/utils/version-check.js.map +0 -1
- package/src/tui-interface-impl.tsx +0 -644
package/dist/cdp-monitor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import { existsSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { existsSync, mkdirSync, 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";
|
|
6
6
|
import { WebSocket } from "ws";
|
|
7
|
+
import { isolatedChromeInvocation } from "./runtime/chrome-isolation.js";
|
|
7
8
|
/** Sandbox exceptions must be explicit; headless mode alone never disables it. */
|
|
8
9
|
export function getChromeSandboxFlags(env = process.env) {
|
|
9
10
|
return env.D3K_CHROME_NO_SANDBOX === "1" ? ["--no-sandbox", "--disable-setuid-sandbox"] : [];
|
|
@@ -233,49 +234,39 @@ export function selectCDPTarget(targets, options = {}) {
|
|
|
233
234
|
}
|
|
234
235
|
export class CDPMonitor {
|
|
235
236
|
eventObserver;
|
|
236
|
-
automaticScreenshots = true;
|
|
237
237
|
setEventObserver(observer) {
|
|
238
238
|
this.eventObserver = observer;
|
|
239
239
|
}
|
|
240
|
-
setAutomaticScreenshots(enabled) {
|
|
241
|
-
this.automaticScreenshots = enabled;
|
|
242
|
-
}
|
|
243
240
|
browser = null;
|
|
244
241
|
connection = null;
|
|
245
242
|
debugPort = 9222;
|
|
246
243
|
eventHandlers = new Map();
|
|
247
244
|
profileDir;
|
|
248
|
-
screenshotDir;
|
|
249
245
|
logger;
|
|
250
246
|
debug = false;
|
|
251
247
|
browserPath;
|
|
252
248
|
isShuttingDown = false;
|
|
253
|
-
|
|
254
|
-
networkIdleTimer = null;
|
|
255
|
-
pluginReactScan = false;
|
|
249
|
+
reconnecting = false;
|
|
256
250
|
cdpUrl = null;
|
|
257
|
-
lastScreenshotTime = 0;
|
|
258
|
-
minScreenshotInterval = 1000; // Minimum 1 second between screenshots
|
|
259
|
-
navigationInProgress = false; // Track if a navigation event recently occurred
|
|
260
251
|
chromePids = new Set(); // Track all Chrome PIDs for this instance
|
|
261
252
|
onWindowClosedCallback = null; // Callback for when window is manually closed
|
|
262
253
|
appServerPort; // Port of the user's app server to monitor
|
|
263
254
|
initialAppUrl; // App URL that the loading page should hand off to
|
|
264
255
|
externalCdpBase; // Existing CDP HTTP endpoint to connect to instead of launching Chrome
|
|
256
|
+
filesystemIsolation = false;
|
|
257
|
+
isolatedCertificatePin;
|
|
265
258
|
headless = false; // Run Chrome in headless mode
|
|
266
259
|
framework; // Framework hint from project detection
|
|
267
260
|
reactTrackingEnabled = false;
|
|
268
261
|
lastReactSnapshotLogTime = 0;
|
|
269
262
|
pendingCommands = new Map();
|
|
270
263
|
navigationTimeoutMs = DEFAULT_NAVIGATION_TIMEOUT_MS;
|
|
271
|
-
constructor(profileDir,
|
|
264
|
+
constructor(profileDir, logger, debug = false, browserPath, appServerPort, initialAppUrl, navigationTimeoutMs = DEFAULT_NAVIGATION_TIMEOUT_MS, debugPort, headless = false, framework, externalCdpBase) {
|
|
272
265
|
this.profileDir = profileDir;
|
|
273
|
-
this.screenshotDir = screenshotDir;
|
|
274
266
|
this.appServerPort = appServerPort;
|
|
275
267
|
this.logger = logger;
|
|
276
268
|
this.debug = debug;
|
|
277
269
|
this.browserPath = browserPath;
|
|
278
|
-
this.pluginReactScan = pluginReactScan;
|
|
279
270
|
this.initialAppUrl = initialAppUrl;
|
|
280
271
|
this.navigationTimeoutMs = navigationTimeoutMs;
|
|
281
272
|
this.headless = headless;
|
|
@@ -452,6 +443,10 @@ export class CDPMonitor {
|
|
|
452
443
|
getChromePids() {
|
|
453
444
|
return Array.from(this.chromePids);
|
|
454
445
|
}
|
|
446
|
+
requireFilesystemIsolation(certificatePin) {
|
|
447
|
+
this.isolatedCertificatePin = certificatePin;
|
|
448
|
+
this.filesystemIsolation = true;
|
|
449
|
+
}
|
|
455
450
|
setOnWindowClosedCallback(callback) {
|
|
456
451
|
this.onWindowClosedCallback = callback;
|
|
457
452
|
}
|
|
@@ -479,7 +474,9 @@ export class CDPMonitor {
|
|
|
479
474
|
}
|
|
480
475
|
createLoadingPage(targetUrl) {
|
|
481
476
|
// Use a unique temp directory per launch to avoid stale file:// cache artifacts.
|
|
482
|
-
|
|
477
|
+
if (this.filesystemIsolation)
|
|
478
|
+
mkdirSync(this.profileDir, { recursive: true, mode: 0o700 });
|
|
479
|
+
const loadingDir = mkdtempSync(join(this.filesystemIsolation ? this.profileDir : tmpdir(), "dev3000-loading-"));
|
|
483
480
|
const loadingPath = join(loadingDir, "loading.html");
|
|
484
481
|
// Read the loading HTML from a runtime-valid location.
|
|
485
482
|
const currentFile = fileURLToPath(import.meta.url);
|
|
@@ -538,11 +535,9 @@ export class CDPMonitor {
|
|
|
538
535
|
this.debugLog(`Chrome crashed: code=${code}, signal=${signal}`);
|
|
539
536
|
// Log context for crash correlation
|
|
540
537
|
this.logger("browser", "[CRASH] Chrome crashed - check recent server/browser logs for correlation");
|
|
541
|
-
// Take screenshot if still connected (for crash context)
|
|
542
|
-
if (this.connection && this.connection.ws.readyState === 1) {
|
|
543
|
-
this.takeScreenshot("crash");
|
|
544
|
-
}
|
|
545
538
|
}
|
|
539
|
+
this.prepareShutdown();
|
|
540
|
+
this.onWindowClosedCallback?.();
|
|
546
541
|
}
|
|
547
542
|
});
|
|
548
543
|
this.browser.on("error", (error) => {
|
|
@@ -629,7 +624,13 @@ export class CDPMonitor {
|
|
|
629
624
|
this.debugLog(`Custom browser path: ${this.browserPath}`);
|
|
630
625
|
}
|
|
631
626
|
let attemptIndex = 0;
|
|
627
|
+
let launchComplete = false;
|
|
632
628
|
const tryNextChrome = () => {
|
|
629
|
+
if (launchComplete || this.isShuttingDown) {
|
|
630
|
+
if (!launchComplete)
|
|
631
|
+
reject(new Error("Browser session is ending"));
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
633
634
|
if (attemptIndex >= chromeCommands.length) {
|
|
634
635
|
const errorMsg = `Failed to launch Chrome: all ${chromeCommands.length} browser paths exhausted`;
|
|
635
636
|
this.logger("browser", `[CDP] ${errorMsg}`);
|
|
@@ -684,7 +685,11 @@ export class CDPMonitor {
|
|
|
684
685
|
}
|
|
685
686
|
// Add initial page
|
|
686
687
|
chromeArgs.push(this.createLoadingPage(this.initialAppUrl));
|
|
687
|
-
|
|
688
|
+
const invocation = this.filesystemIsolation
|
|
689
|
+
? isolatedChromeInvocation(chromePath, chromeArgs, this.profileDir, this.isolatedCertificatePin)
|
|
690
|
+
: { binary: chromePath, args: chromeArgs, env: process.env };
|
|
691
|
+
this.browser = spawn(invocation.binary, invocation.args, {
|
|
692
|
+
env: invocation.env,
|
|
688
693
|
stdio: "pipe",
|
|
689
694
|
detached: true // Separate process group so Chrome doesn't receive SIGINT on Ctrl+C
|
|
690
695
|
});
|
|
@@ -721,6 +726,11 @@ export class CDPMonitor {
|
|
|
721
726
|
// Poll for Chrome readiness instead of fixed timeout
|
|
722
727
|
const checkChromeReady = async (attempts = 0) => {
|
|
723
728
|
const maxAttempts = 30; // 30 attempts = 15 seconds max
|
|
729
|
+
if (launchComplete || this.isShuttingDown) {
|
|
730
|
+
if (!launchComplete)
|
|
731
|
+
reject(new Error("Browser session is ending"));
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
724
734
|
if (processExited) {
|
|
725
735
|
return;
|
|
726
736
|
}
|
|
@@ -747,6 +757,11 @@ export class CDPMonitor {
|
|
|
747
757
|
this.debugLog(`Chrome successfully started with path: ${chromePath} (after ${attempts * 500}ms)`);
|
|
748
758
|
// Discover all Chrome PIDs for this instance
|
|
749
759
|
await this.discoverChromePids();
|
|
760
|
+
if (this.isShuttingDown) {
|
|
761
|
+
reject(new Error("Browser session is ending"));
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
launchComplete = true;
|
|
750
765
|
// Set up runtime crash monitoring after successful launch
|
|
751
766
|
this.setupRuntimeCrashMonitoring();
|
|
752
767
|
resolve();
|
|
@@ -771,24 +786,20 @@ export class CDPMonitor {
|
|
|
771
786
|
let retryCount = 0;
|
|
772
787
|
const maxRetries = 5;
|
|
773
788
|
while (retryCount < maxRetries) {
|
|
789
|
+
if (this.isShuttingDown)
|
|
790
|
+
throw new Error("Browser session is ending");
|
|
774
791
|
try {
|
|
775
792
|
// Get the WebSocket URL from Chrome's debug endpoint
|
|
776
793
|
const targetsResponse = await fetch(`${cdpBase}/json`);
|
|
777
|
-
|
|
794
|
+
const targets = (await targetsResponse.json());
|
|
778
795
|
this.debugLog(`Found ${targets.length} targets: ${JSON.stringify(targets.map((target) => ({ type: target.type ?? "unknown", url: target.url ?? "" })))}`);
|
|
779
|
-
if (
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
targets = [createdTarget];
|
|
787
|
-
this.debugLog(`Created page target: ${createdTarget.id || "unknown"} - ${createdTarget.url || ""}`);
|
|
788
|
-
}
|
|
789
|
-
else {
|
|
790
|
-
this.debugLog(`Failed to create page target: HTTP ${newTargetResponse.status}`);
|
|
791
|
-
}
|
|
796
|
+
if (this.isShuttingDown)
|
|
797
|
+
throw new Error("Browser session is ending");
|
|
798
|
+
if (!targets.some((target) => target.type === "page")) {
|
|
799
|
+
// A closed final window is a session end, never a request to create a replacement tab.
|
|
800
|
+
this.prepareShutdown();
|
|
801
|
+
this.onWindowClosedCallback?.();
|
|
802
|
+
throw new Error("Browser has no open targets; session ended");
|
|
792
803
|
}
|
|
793
804
|
const pageTarget = selectCDPTarget(targets, {
|
|
794
805
|
appServerPort: this.appServerPort,
|
|
@@ -808,6 +819,11 @@ export class CDPMonitor {
|
|
|
808
819
|
ws.setMaxListeners(20);
|
|
809
820
|
ws.on("open", () => {
|
|
810
821
|
this.debugLog("WebSocket connection opened successfully");
|
|
822
|
+
if (this.isShuttingDown) {
|
|
823
|
+
ws.close();
|
|
824
|
+
reject(new Error("Browser session is ending"));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
811
827
|
this.connection = {
|
|
812
828
|
ws,
|
|
813
829
|
sessionId: null,
|
|
@@ -884,6 +900,8 @@ export class CDPMonitor {
|
|
|
884
900
|
});
|
|
885
901
|
}
|
|
886
902
|
catch (error) {
|
|
903
|
+
if (this.isShuttingDown)
|
|
904
|
+
throw error;
|
|
887
905
|
retryCount++;
|
|
888
906
|
this.debugLog(`CDP connection attempt ${retryCount} failed: ${error}`);
|
|
889
907
|
if (retryCount >= maxRetries) {
|
|
@@ -896,30 +914,43 @@ export class CDPMonitor {
|
|
|
896
914
|
}
|
|
897
915
|
}
|
|
898
916
|
}
|
|
917
|
+
/** A returning companion can retry after the previous finite recovery attempt was exhausted. */
|
|
918
|
+
async recoverExternalConnection() {
|
|
919
|
+
if (this.externalCdpBase && !this.getCdpUrl())
|
|
920
|
+
await this.attemptReconnect();
|
|
921
|
+
}
|
|
899
922
|
async attemptReconnect() {
|
|
900
|
-
this.
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
923
|
+
if (this.isShuttingDown || this.reconnecting || this.getCdpUrl())
|
|
924
|
+
return;
|
|
925
|
+
this.reconnecting = true;
|
|
926
|
+
try {
|
|
927
|
+
this.debugLog("Starting CDP reconnection attempt");
|
|
928
|
+
this.logger("browser", "[CDP] Starting reconnection process...");
|
|
929
|
+
// Close existing connection if any
|
|
930
|
+
if (this.connection) {
|
|
931
|
+
try {
|
|
932
|
+
this.connection.ws.close();
|
|
933
|
+
}
|
|
934
|
+
catch (err) {
|
|
935
|
+
this.debugLog(`Error closing old connection: ${err}`);
|
|
936
|
+
}
|
|
937
|
+
this.connection = null;
|
|
938
|
+
}
|
|
939
|
+
// Attempt to reconnect
|
|
904
940
|
try {
|
|
905
|
-
this.
|
|
941
|
+
await this.connectToCDP();
|
|
942
|
+
await this.enableCDPDomains();
|
|
943
|
+
await this.setupInteractionTracking();
|
|
944
|
+
this.logger("browser", "[CDP] Reconnection successful! Monitoring restored.");
|
|
945
|
+
this.debugLog("CDP reconnection completed successfully and monitoring was restored");
|
|
906
946
|
}
|
|
907
947
|
catch (err) {
|
|
908
|
-
this.
|
|
948
|
+
this.logger("browser", `[CDP] Reconnection failed: ${err}`);
|
|
949
|
+
throw err;
|
|
909
950
|
}
|
|
910
|
-
this.connection = null;
|
|
911
951
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
await this.connectToCDP();
|
|
915
|
-
await this.enableCDPDomains();
|
|
916
|
-
await this.setupInteractionTracking();
|
|
917
|
-
this.logger("browser", "[CDP] Reconnection successful! Monitoring restored.");
|
|
918
|
-
this.debugLog("CDP reconnection completed successfully and monitoring was restored");
|
|
919
|
-
}
|
|
920
|
-
catch (err) {
|
|
921
|
-
this.logger("browser", `[CDP] Reconnection failed: ${err}`);
|
|
922
|
-
throw err;
|
|
952
|
+
finally {
|
|
953
|
+
this.reconnecting = false;
|
|
923
954
|
}
|
|
924
955
|
}
|
|
925
956
|
async sendCDPCommand(method, params = {}, timeoutMs = DEFAULT_CDP_COMMAND_TIMEOUT_MS) {
|
|
@@ -1179,8 +1210,6 @@ export class CDPMonitor {
|
|
|
1179
1210
|
}
|
|
1180
1211
|
this.logger("browser", errorMsg);
|
|
1181
1212
|
void this.logReactSnapshot("exception context", true);
|
|
1182
|
-
// Take screenshot immediately on errors (no delay needed)
|
|
1183
|
-
this.takeScreenshot("error");
|
|
1184
1213
|
});
|
|
1185
1214
|
// Browser console logs via Log domain (additional capture method)
|
|
1186
1215
|
this.onCDPEvent("Log.entryAdded", (event) => {
|
|
@@ -1259,40 +1288,20 @@ export class CDPMonitor {
|
|
|
1259
1288
|
return;
|
|
1260
1289
|
}
|
|
1261
1290
|
this.logger("browser", `[NAVIGATION] ${url}`);
|
|
1262
|
-
// Mark that we're in a navigation - we'll take a screenshot when it settles
|
|
1263
|
-
this.navigationInProgress = true;
|
|
1264
1291
|
});
|
|
1265
|
-
//
|
|
1292
|
+
// Reinject interaction tracking when documents load
|
|
1266
1293
|
this.onCDPEvent("Page.loadEventFired", async (_event) => {
|
|
1267
1294
|
this.logger("browser", "[DOM] Load event fired");
|
|
1268
|
-
this.takeScreenshot("page-loaded");
|
|
1269
1295
|
// Reinject interaction tracking on page load
|
|
1270
1296
|
await this.setupInteractionTracking();
|
|
1271
1297
|
void this.logReactSnapshot("post-load context");
|
|
1272
1298
|
});
|
|
1273
1299
|
this.onCDPEvent("Page.domContentEventFired", async (_event) => {
|
|
1274
1300
|
this.logger("browser", "[DOM] DOM content loaded");
|
|
1275
|
-
// Skip screenshot on DOM content loaded - we'll get one on page-loaded
|
|
1276
1301
|
// Reinject interaction tracking on DOM content loaded
|
|
1277
1302
|
await this.setupInteractionTracking();
|
|
1278
1303
|
void this.logReactSnapshot("domcontentloaded context");
|
|
1279
1304
|
});
|
|
1280
|
-
// Network activity tracking for better screenshot timing
|
|
1281
|
-
this.onCDPEvent("Network.requestWillBeSent", (_event) => {
|
|
1282
|
-
this.pendingRequests++;
|
|
1283
|
-
if (this.networkIdleTimer) {
|
|
1284
|
-
clearTimeout(this.networkIdleTimer);
|
|
1285
|
-
this.networkIdleTimer = null;
|
|
1286
|
-
}
|
|
1287
|
-
});
|
|
1288
|
-
this.onCDPEvent("Network.loadingFinished", (_event) => {
|
|
1289
|
-
this.pendingRequests--;
|
|
1290
|
-
this.scheduleNetworkIdleScreenshot();
|
|
1291
|
-
});
|
|
1292
|
-
this.onCDPEvent("Network.loadingFailed", (_event) => {
|
|
1293
|
-
this.pendingRequests--;
|
|
1294
|
-
this.scheduleNetworkIdleScreenshot();
|
|
1295
|
-
});
|
|
1296
1305
|
// DOM mutations for interaction context
|
|
1297
1306
|
this.onCDPEvent("DOM.documentUpdated", () => {
|
|
1298
1307
|
// Document structure changed - useful for SPA routing
|
|
@@ -1325,6 +1334,7 @@ export class CDPMonitor {
|
|
|
1325
1334
|
if (params.targetId === monitoredTarget && this.onWindowClosedCallback && !this.isShuttingDown) {
|
|
1326
1335
|
this.debugLog("Chrome window was manually closed, triggering d3k shutdown");
|
|
1327
1336
|
this.logger("browser", "[TARGET] Chrome window manually closed, shutting down d3k");
|
|
1337
|
+
this.prepareShutdown();
|
|
1328
1338
|
this.onWindowClosedCallback();
|
|
1329
1339
|
}
|
|
1330
1340
|
});
|
|
@@ -1409,10 +1419,6 @@ export class CDPMonitor {
|
|
|
1409
1419
|
this.logger("browser", `[CDP] Navigation failed: ${error}`);
|
|
1410
1420
|
throw error;
|
|
1411
1421
|
}
|
|
1412
|
-
// Take a delayed screenshot to catch dynamic content
|
|
1413
|
-
setTimeout(() => {
|
|
1414
|
-
this.takeScreenshot("navigation-delayed");
|
|
1415
|
-
}, 2000);
|
|
1416
1422
|
// Set up interaction tracking - but be more efficient about it
|
|
1417
1423
|
const trackingStartTime = Date.now();
|
|
1418
1424
|
this.debugLog("Setting up interaction tracking");
|
|
@@ -1427,7 +1433,6 @@ export class CDPMonitor {
|
|
|
1427
1433
|
this.debugLog(`Interaction tracking setup completed (${trackingTime}ms)`);
|
|
1428
1434
|
// Start polling for interactions from the injected script
|
|
1429
1435
|
this.startInteractionPolling();
|
|
1430
|
-
// Multiple screenshot triggers will ensure we catch the initial page load
|
|
1431
1436
|
}
|
|
1432
1437
|
async navigateToApp(port, useHttps = false) {
|
|
1433
1438
|
const protocol = useHttps ? "https" : "http";
|
|
@@ -1452,33 +1457,6 @@ export class CDPMonitor {
|
|
|
1452
1457
|
if (!window.__dev3000_cdp_tracking) {
|
|
1453
1458
|
window.__dev3000_cdp_tracking = true;
|
|
1454
1459
|
|
|
1455
|
-
${this.pluginReactScan
|
|
1456
|
-
? `
|
|
1457
|
-
// Inject react-scan for React performance monitoring
|
|
1458
|
-
if (!window.__REACT_SCAN_INJECTED__) {
|
|
1459
|
-
const script = document.createElement('script');
|
|
1460
|
-
script.src = 'https://unpkg.com/react-scan@latest/dist/auto.global.js';
|
|
1461
|
-
script.onload = () => {
|
|
1462
|
-
console.debug('[DEV3000] react-scan loaded successfully');
|
|
1463
|
-
window.__REACT_SCAN_INJECTED__ = true;
|
|
1464
|
-
|
|
1465
|
-
// Optional: Configure react-scan
|
|
1466
|
-
if (window.ReactScan && window.ReactScan.configure) {
|
|
1467
|
-
window.ReactScan.configure({
|
|
1468
|
-
// Add any configuration options here
|
|
1469
|
-
playSound: false,
|
|
1470
|
-
showToolbar: true
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1473
|
-
};
|
|
1474
|
-
script.onerror = (err) => {
|
|
1475
|
-
console.debug('[DEV3000] Failed to load react-scan:', err);
|
|
1476
|
-
};
|
|
1477
|
-
document.head.appendChild(script);
|
|
1478
|
-
}
|
|
1479
|
-
`
|
|
1480
|
-
: ""}
|
|
1481
|
-
|
|
1482
1460
|
// Helper function to generate CSS selector for element
|
|
1483
1461
|
function getElementSelector(el) {
|
|
1484
1462
|
if (!el || el === document) return 'document';
|
|
@@ -1762,10 +1740,6 @@ export class CDPMonitor {
|
|
|
1762
1740
|
const reactState = value.reactState || null;
|
|
1763
1741
|
for (const interaction of interactions) {
|
|
1764
1742
|
this.logger("browser", `[INTERACTION] ${interaction.message}`);
|
|
1765
|
-
// Take screenshot when scroll settles
|
|
1766
|
-
if (interaction.message.startsWith("SCROLL_SETTLED")) {
|
|
1767
|
-
this.takeScreenshot("scroll-settled");
|
|
1768
|
-
}
|
|
1769
1743
|
}
|
|
1770
1744
|
for (const reactEvent of reactEvents) {
|
|
1771
1745
|
if (reactEvent.type === "renderer") {
|
|
@@ -1793,64 +1767,6 @@ export class CDPMonitor {
|
|
|
1793
1767
|
pollInteractions();
|
|
1794
1768
|
}, 1000);
|
|
1795
1769
|
}
|
|
1796
|
-
scheduleNetworkIdleScreenshot() {
|
|
1797
|
-
// Only take network-idle screenshots after a navigation event
|
|
1798
|
-
// This prevents screenshot spam from background AJAX/fetch polls
|
|
1799
|
-
if (!this.navigationInProgress) {
|
|
1800
|
-
return;
|
|
1801
|
-
}
|
|
1802
|
-
// Only schedule if we have 0 pending requests
|
|
1803
|
-
if (this.pendingRequests === 0) {
|
|
1804
|
-
if (this.networkIdleTimer) {
|
|
1805
|
-
clearTimeout(this.networkIdleTimer);
|
|
1806
|
-
}
|
|
1807
|
-
// Wait 500ms of network idle before taking screenshot
|
|
1808
|
-
this.networkIdleTimer = setTimeout(() => {
|
|
1809
|
-
this.takeScreenshot("navigation-settled");
|
|
1810
|
-
// Reset navigation flag since we've captured the settled state
|
|
1811
|
-
this.navigationInProgress = false;
|
|
1812
|
-
this.networkIdleTimer = null;
|
|
1813
|
-
}, 500);
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
async takeScreenshot(event) {
|
|
1817
|
-
if (!this.automaticScreenshots)
|
|
1818
|
-
return null;
|
|
1819
|
-
try {
|
|
1820
|
-
// Throttle screenshots to avoid spam
|
|
1821
|
-
const now = Date.now();
|
|
1822
|
-
const timeSinceLastScreenshot = now - this.lastScreenshotTime;
|
|
1823
|
-
// Special cases that should always take screenshots
|
|
1824
|
-
const priorityEvents = ["error", "crash"];
|
|
1825
|
-
// If not a priority event and we took a screenshot recently, skip it
|
|
1826
|
-
if (!priorityEvents.includes(event) && timeSinceLastScreenshot < this.minScreenshotInterval) {
|
|
1827
|
-
this.debugLog(`Skipping screenshot for ${event} - only ${timeSinceLastScreenshot}ms since last screenshot`);
|
|
1828
|
-
return null;
|
|
1829
|
-
}
|
|
1830
|
-
// Update last screenshot time
|
|
1831
|
-
this.lastScreenshotTime = now;
|
|
1832
|
-
const result = await this.sendCDPCommand("Page.captureScreenshot", {
|
|
1833
|
-
format: "png",
|
|
1834
|
-
quality: 80,
|
|
1835
|
-
clip: undefined, // Full viewport
|
|
1836
|
-
fromSurface: true
|
|
1837
|
-
});
|
|
1838
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
1839
|
-
const filename = `${timestamp}-${event}.png`;
|
|
1840
|
-
const screenshotPath = join(this.screenshotDir, filename);
|
|
1841
|
-
// Save the base64 image
|
|
1842
|
-
const resultWithData = result;
|
|
1843
|
-
const buffer = Buffer.from(resultWithData.data, "base64");
|
|
1844
|
-
writeFileSync(screenshotPath, buffer);
|
|
1845
|
-
// Log screenshot with file path
|
|
1846
|
-
this.logger("browser", `[SCREENSHOT] ${screenshotPath}`);
|
|
1847
|
-
return filename;
|
|
1848
|
-
}
|
|
1849
|
-
catch (error) {
|
|
1850
|
-
this.logger("browser", `[CDP] Screenshot failed: ${error}`);
|
|
1851
|
-
return null;
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
1770
|
// Enhanced replay functionality using CDP
|
|
1855
1771
|
async executeInteraction(interaction) {
|
|
1856
1772
|
if (!this.connection) {
|