claude-chrome-parallel 1.0.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/LICENSE +21 -0
- package/README.md +501 -0
- package/assets/demo.svg +278 -0
- package/dist/cdp/client.d.ts +218 -0
- package/dist/cdp/client.d.ts.map +1 -0
- package/dist/cdp/client.js +797 -0
- package/dist/cdp/client.js.map +1 -0
- package/dist/cdp/connection-pool.d.ts +125 -0
- package/dist/cdp/connection-pool.d.ts.map +1 -0
- package/dist/cdp/connection-pool.js +443 -0
- package/dist/cdp/connection-pool.js.map +1 -0
- package/dist/cdp/screenshot-scheduler.d.ts +54 -0
- package/dist/cdp/screenshot-scheduler.d.ts.map +1 -0
- package/dist/cdp/screenshot-scheduler.js +87 -0
- package/dist/cdp/screenshot-scheduler.js.map +1 -0
- package/dist/chrome/launcher.d.ts +55 -0
- package/dist/chrome/launcher.d.ts.map +1 -0
- package/dist/chrome/launcher.js +383 -0
- package/dist/chrome/launcher.js.map +1 -0
- package/dist/chrome/pool.d.ts +54 -0
- package/dist/chrome/pool.d.ts.map +1 -0
- package/dist/chrome/pool.js +301 -0
- package/dist/chrome/pool.js.map +1 -0
- package/dist/chrome/profile-detector.d.ts +52 -0
- package/dist/chrome/profile-detector.d.ts.map +1 -0
- package/dist/chrome/profile-detector.js +246 -0
- package/dist/chrome/profile-detector.js.map +1 -0
- package/dist/cli/claude-session.d.ts +11 -0
- package/dist/cli/claude-session.js +349 -0
- package/dist/cli/claude-session.js.map +1 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +858 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +16 -0
- package/dist/cli/install.js +185 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/uninstall.d.ts +7 -0
- package/dist/cli/uninstall.js +126 -0
- package/dist/cli/uninstall.js.map +1 -0
- package/dist/cli/update-check.d.ts +9 -0
- package/dist/cli/update-check.js +141 -0
- package/dist/cli/update-check.js.map +1 -0
- package/dist/config/config-recovery.d.ts +69 -0
- package/dist/config/config-recovery.d.ts.map +1 -0
- package/dist/config/config-recovery.js +302 -0
- package/dist/config/config-recovery.js.map +1 -0
- package/dist/config/global.d.ts +49 -0
- package/dist/config/global.d.ts.map +1 -0
- package/dist/config/global.js +24 -0
- package/dist/config/global.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +23 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/session-isolator.d.ts +76 -0
- package/dist/config/session-isolator.d.ts.map +1 -0
- package/dist/config/session-isolator.js +268 -0
- package/dist/config/session-isolator.js.map +1 -0
- package/dist/dashboard/activity-tracker.d.ts +76 -0
- package/dist/dashboard/activity-tracker.d.ts.map +1 -0
- package/dist/dashboard/activity-tracker.js +219 -0
- package/dist/dashboard/activity-tracker.js.map +1 -0
- package/dist/dashboard/ansi.d.ts +117 -0
- package/dist/dashboard/ansi.d.ts.map +1 -0
- package/dist/dashboard/ansi.js +199 -0
- package/dist/dashboard/ansi.js.map +1 -0
- package/dist/dashboard/index.d.ts +110 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +412 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/keyboard-handler.d.ts +43 -0
- package/dist/dashboard/keyboard-handler.d.ts.map +1 -0
- package/dist/dashboard/keyboard-handler.js +215 -0
- package/dist/dashboard/keyboard-handler.js.map +1 -0
- package/dist/dashboard/operation-controller.d.ts +76 -0
- package/dist/dashboard/operation-controller.d.ts.map +1 -0
- package/dist/dashboard/operation-controller.js +167 -0
- package/dist/dashboard/operation-controller.js.map +1 -0
- package/dist/dashboard/renderer.d.ts +76 -0
- package/dist/dashboard/renderer.d.ts.map +1 -0
- package/dist/dashboard/renderer.js +193 -0
- package/dist/dashboard/renderer.js.map +1 -0
- package/dist/dashboard/types.d.ts +56 -0
- package/dist/dashboard/types.d.ts.map +1 -0
- package/dist/dashboard/types.js +12 -0
- package/dist/dashboard/types.js.map +1 -0
- package/dist/dashboard/views/main-view.d.ts +23 -0
- package/dist/dashboard/views/main-view.d.ts.map +1 -0
- package/dist/dashboard/views/main-view.js +143 -0
- package/dist/dashboard/views/main-view.js.map +1 -0
- package/dist/dashboard/views/sessions-view.d.ts +22 -0
- package/dist/dashboard/views/sessions-view.d.ts.map +1 -0
- package/dist/dashboard/views/sessions-view.js +104 -0
- package/dist/dashboard/views/sessions-view.js.map +1 -0
- package/dist/dashboard/views/tabs-view.d.ts +21 -0
- package/dist/dashboard/views/tabs-view.d.ts.map +1 -0
- package/dist/dashboard/views/tabs-view.js +92 -0
- package/dist/dashboard/views/tabs-view.js.map +1 -0
- package/dist/hints/hint-engine.d.ts +77 -0
- package/dist/hints/hint-engine.d.ts.map +1 -0
- package/dist/hints/hint-engine.js +191 -0
- package/dist/hints/hint-engine.js.map +1 -0
- package/dist/hints/index.d.ts +8 -0
- package/dist/hints/index.d.ts.map +1 -0
- package/dist/hints/index.js +11 -0
- package/dist/hints/index.js.map +1 -0
- package/dist/hints/pattern-learner.d.ts +76 -0
- package/dist/hints/pattern-learner.d.ts.map +1 -0
- package/dist/hints/pattern-learner.js +254 -0
- package/dist/hints/pattern-learner.js.map +1 -0
- package/dist/hints/rules/composite-suggestions.d.ts +6 -0
- package/dist/hints/rules/composite-suggestions.d.ts.map +1 -0
- package/dist/hints/rules/composite-suggestions.js +66 -0
- package/dist/hints/rules/composite-suggestions.js.map +1 -0
- package/dist/hints/rules/error-recovery.d.ts +7 -0
- package/dist/hints/rules/error-recovery.d.ts.map +1 -0
- package/dist/hints/rules/error-recovery.js +55 -0
- package/dist/hints/rules/error-recovery.js.map +1 -0
- package/dist/hints/rules/learned-rules.d.ts +13 -0
- package/dist/hints/rules/learned-rules.d.ts.map +1 -0
- package/dist/hints/rules/learned-rules.js +27 -0
- package/dist/hints/rules/learned-rules.js.map +1 -0
- package/dist/hints/rules/repetition-detection.d.ts +7 -0
- package/dist/hints/rules/repetition-detection.d.ts.map +1 -0
- package/dist/hints/rules/repetition-detection.js +82 -0
- package/dist/hints/rules/repetition-detection.js.map +1 -0
- package/dist/hints/rules/sequence-detection.d.ts +6 -0
- package/dist/hints/rules/sequence-detection.d.ts.map +1 -0
- package/dist/hints/rules/sequence-detection.js +89 -0
- package/dist/hints/rules/sequence-detection.js.map +1 -0
- package/dist/hints/rules/success-hints.d.ts +6 -0
- package/dist/hints/rules/success-hints.d.ts.map +1 -0
- package/dist/hints/rules/success-hints.js +62 -0
- package/dist/hints/rules/success-hints.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +272 -0
- package/dist/index.js.map +1 -0
- package/dist/lightpanda/launcher.d.ts +58 -0
- package/dist/lightpanda/launcher.d.ts.map +1 -0
- package/dist/lightpanda/launcher.js +199 -0
- package/dist/lightpanda/launcher.js.map +1 -0
- package/dist/mcp-server.d.ts +129 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +641 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/memory/domain-memory.d.ts +68 -0
- package/dist/memory/domain-memory.d.ts.map +1 -0
- package/dist/memory/domain-memory.js +227 -0
- package/dist/memory/domain-memory.js.map +1 -0
- package/dist/orchestration/plan-executor.d.ts +19 -0
- package/dist/orchestration/plan-executor.d.ts.map +1 -0
- package/dist/orchestration/plan-executor.js +284 -0
- package/dist/orchestration/plan-executor.js.map +1 -0
- package/dist/orchestration/plan-registry.d.ts +55 -0
- package/dist/orchestration/plan-registry.d.ts.map +1 -0
- package/dist/orchestration/plan-registry.js +255 -0
- package/dist/orchestration/plan-registry.js.map +1 -0
- package/dist/orchestration/state-manager.d.ts +127 -0
- package/dist/orchestration/state-manager.d.ts.map +1 -0
- package/dist/orchestration/state-manager.js +438 -0
- package/dist/orchestration/state-manager.js.map +1 -0
- package/dist/orchestration/workflow-engine.d.ts +162 -0
- package/dist/orchestration/workflow-engine.d.ts.map +1 -0
- package/dist/orchestration/workflow-engine.js +731 -0
- package/dist/orchestration/workflow-engine.js.map +1 -0
- package/dist/resources/usage-guide.d.ts +13 -0
- package/dist/resources/usage-guide.d.ts.map +1 -0
- package/dist/resources/usage-guide.js +101 -0
- package/dist/resources/usage-guide.js.map +1 -0
- package/dist/router/browser-router.d.ts +51 -0
- package/dist/router/browser-router.d.ts.map +1 -0
- package/dist/router/browser-router.js +178 -0
- package/dist/router/browser-router.js.map +1 -0
- package/dist/router/cookie-sync.d.ts +48 -0
- package/dist/router/cookie-sync.d.ts.map +1 -0
- package/dist/router/cookie-sync.js +106 -0
- package/dist/router/cookie-sync.js.map +1 -0
- package/dist/router/index.d.ts +5 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +10 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/tool-routing-registry.d.ts +21 -0
- package/dist/router/tool-routing-registry.d.ts.map +1 -0
- package/dist/router/tool-routing-registry.js +90 -0
- package/dist/router/tool-routing-registry.js.map +1 -0
- package/dist/session-manager.d.ts +251 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +912 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/tools/batch-execute.d.ts +11 -0
- package/dist/tools/batch-execute.d.ts.map +1 -0
- package/dist/tools/batch-execute.js +226 -0
- package/dist/tools/batch-execute.js.map +1 -0
- package/dist/tools/click-element.d.ts +8 -0
- package/dist/tools/click-element.d.ts.map +1 -0
- package/dist/tools/click-element.js +455 -0
- package/dist/tools/click-element.js.map +1 -0
- package/dist/tools/computer.d.ts +6 -0
- package/dist/tools/computer.d.ts.map +1 -0
- package/dist/tools/computer.js +638 -0
- package/dist/tools/computer.js.map +1 -0
- package/dist/tools/console-capture.d.ts +6 -0
- package/dist/tools/console-capture.d.ts.map +1 -0
- package/dist/tools/console-capture.js +320 -0
- package/dist/tools/console-capture.js.map +1 -0
- package/dist/tools/cookies.d.ts +6 -0
- package/dist/tools/cookies.d.ts.map +1 -0
- package/dist/tools/cookies.js +263 -0
- package/dist/tools/cookies.js.map +1 -0
- package/dist/tools/drag-drop.d.ts +6 -0
- package/dist/tools/drag-drop.d.ts.map +1 -0
- package/dist/tools/drag-drop.js +252 -0
- package/dist/tools/drag-drop.js.map +1 -0
- package/dist/tools/emulate-device.d.ts +6 -0
- package/dist/tools/emulate-device.d.ts.map +1 -0
- package/dist/tools/emulate-device.js +221 -0
- package/dist/tools/emulate-device.js.map +1 -0
- package/dist/tools/file-upload.d.ts +6 -0
- package/dist/tools/file-upload.d.ts.map +1 -0
- package/dist/tools/file-upload.js +208 -0
- package/dist/tools/file-upload.js.map +1 -0
- package/dist/tools/fill-form.d.ts +8 -0
- package/dist/tools/fill-form.d.ts.map +1 -0
- package/dist/tools/fill-form.js +342 -0
- package/dist/tools/fill-form.js.map +1 -0
- package/dist/tools/find.d.ts +6 -0
- package/dist/tools/find.d.ts.map +1 -0
- package/dist/tools/find.js +330 -0
- package/dist/tools/find.js.map +1 -0
- package/dist/tools/form-input.d.ts +6 -0
- package/dist/tools/form-input.d.ts.map +1 -0
- package/dist/tools/form-input.js +181 -0
- package/dist/tools/form-input.js.map +1 -0
- package/dist/tools/geolocation.d.ts +6 -0
- package/dist/tools/geolocation.d.ts.map +1 -0
- package/dist/tools/geolocation.js +172 -0
- package/dist/tools/geolocation.js.map +1 -0
- package/dist/tools/http-auth.d.ts +6 -0
- package/dist/tools/http-auth.d.ts.map +1 -0
- package/dist/tools/http-auth.js +136 -0
- package/dist/tools/http-auth.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +104 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/javascript.d.ts +6 -0
- package/dist/tools/javascript.d.ts.map +1 -0
- package/dist/tools/javascript.js +138 -0
- package/dist/tools/javascript.js.map +1 -0
- package/dist/tools/lightweight-scroll.d.ts +11 -0
- package/dist/tools/lightweight-scroll.d.ts.map +1 -0
- package/dist/tools/lightweight-scroll.js +266 -0
- package/dist/tools/lightweight-scroll.js.map +1 -0
- package/dist/tools/memory.d.ts +10 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +141 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/tools/navigate.d.ts +6 -0
- package/dist/tools/navigate.d.ts.map +1 -0
- package/dist/tools/navigate.js +241 -0
- package/dist/tools/navigate.js.map +1 -0
- package/dist/tools/network.d.ts +6 -0
- package/dist/tools/network.d.ts.map +1 -0
- package/dist/tools/network.js +215 -0
- package/dist/tools/network.js.map +1 -0
- package/dist/tools/orchestration.d.ts +6 -0
- package/dist/tools/orchestration.d.ts.map +1 -0
- package/dist/tools/orchestration.js +746 -0
- package/dist/tools/orchestration.js.map +1 -0
- package/dist/tools/page-content.d.ts +6 -0
- package/dist/tools/page-content.d.ts.map +1 -0
- package/dist/tools/page-content.js +120 -0
- package/dist/tools/page-content.js.map +1 -0
- package/dist/tools/page-pdf.d.ts +6 -0
- package/dist/tools/page-pdf.d.ts.map +1 -0
- package/dist/tools/page-pdf.js +245 -0
- package/dist/tools/page-pdf.js.map +1 -0
- package/dist/tools/page-reload.d.ts +6 -0
- package/dist/tools/page-reload.d.ts.map +1 -0
- package/dist/tools/page-reload.js +89 -0
- package/dist/tools/page-reload.js.map +1 -0
- package/dist/tools/performance-metrics.d.ts +6 -0
- package/dist/tools/performance-metrics.d.ts.map +1 -0
- package/dist/tools/performance-metrics.js +158 -0
- package/dist/tools/performance-metrics.js.map +1 -0
- package/dist/tools/read-page.d.ts +6 -0
- package/dist/tools/read-page.d.ts.map +1 -0
- package/dist/tools/read-page.js +287 -0
- package/dist/tools/read-page.js.map +1 -0
- package/dist/tools/request-intercept.d.ts +6 -0
- package/dist/tools/request-intercept.d.ts.map +1 -0
- package/dist/tools/request-intercept.js +439 -0
- package/dist/tools/request-intercept.js.map +1 -0
- package/dist/tools/selector-query.d.ts +6 -0
- package/dist/tools/selector-query.d.ts.map +1 -0
- package/dist/tools/selector-query.js +206 -0
- package/dist/tools/selector-query.js.map +1 -0
- package/dist/tools/shutdown.d.ts +12 -0
- package/dist/tools/shutdown.d.ts.map +1 -0
- package/dist/tools/shutdown.js +120 -0
- package/dist/tools/shutdown.js.map +1 -0
- package/dist/tools/storage.d.ts +6 -0
- package/dist/tools/storage.d.ts.map +1 -0
- package/dist/tools/storage.js +264 -0
- package/dist/tools/storage.js.map +1 -0
- package/dist/tools/tabs-close.d.ts +6 -0
- package/dist/tools/tabs-close.d.ts.map +1 -0
- package/dist/tools/tabs-close.js +124 -0
- package/dist/tools/tabs-close.js.map +1 -0
- package/dist/tools/tabs-context.d.ts +6 -0
- package/dist/tools/tabs-context.d.ts.map +1 -0
- package/dist/tools/tabs-context.js +92 -0
- package/dist/tools/tabs-context.js.map +1 -0
- package/dist/tools/tabs-create.d.ts +6 -0
- package/dist/tools/tabs-create.d.ts.map +1 -0
- package/dist/tools/tabs-create.js +73 -0
- package/dist/tools/tabs-create.js.map +1 -0
- package/dist/tools/user-agent.d.ts +6 -0
- package/dist/tools/user-agent.d.ts.map +1 -0
- package/dist/tools/user-agent.js +128 -0
- package/dist/tools/user-agent.js.map +1 -0
- package/dist/tools/wait-and-click.d.ts +8 -0
- package/dist/tools/wait-and-click.d.ts.map +1 -0
- package/dist/tools/wait-and-click.js +290 -0
- package/dist/tools/wait-and-click.js.map +1 -0
- package/dist/tools/wait-for.d.ts +6 -0
- package/dist/tools/wait-for.d.ts.map +1 -0
- package/dist/tools/wait-for.js +248 -0
- package/dist/tools/wait-for.js.map +1 -0
- package/dist/tools/worker-create.d.ts +7 -0
- package/dist/tools/worker-create.d.ts.map +1 -0
- package/dist/tools/worker-create.js +62 -0
- package/dist/tools/worker-create.js.map +1 -0
- package/dist/tools/worker-delete.d.ts +6 -0
- package/dist/tools/worker-delete.d.ts.map +1 -0
- package/dist/tools/worker-delete.js +80 -0
- package/dist/tools/worker-delete.js.map +1 -0
- package/dist/tools/worker-list.d.ts +6 -0
- package/dist/tools/worker-list.d.ts.map +1 -0
- package/dist/tools/worker-list.js +67 -0
- package/dist/tools/worker-list.js.map +1 -0
- package/dist/tools/xpath-query.d.ts +6 -0
- package/dist/tools/xpath-query.d.ts.map +1 -0
- package/dist/tools/xpath-query.js +230 -0
- package/dist/tools/xpath-query.js.map +1 -0
- package/dist/types/browser-backend.d.ts +30 -0
- package/dist/types/browser-backend.d.ts.map +1 -0
- package/dist/types/browser-backend.js +9 -0
- package/dist/types/browser-backend.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/mcp.d.ts +54 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +14 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/types/plan-cache.d.ts +121 -0
- package/dist/types/plan-cache.d.ts.map +1 -0
- package/dist/types/plan-cache.js +9 -0
- package/dist/types/plan-cache.js.map +1 -0
- package/dist/types/profile.d.ts +76 -0
- package/dist/types/profile.d.ts.map +1 -0
- package/dist/types/profile.js +35 -0
- package/dist/types/profile.js.map +1 -0
- package/dist/types/session.d.ts +65 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +6 -0
- package/dist/types/session.js.map +1 -0
- package/dist/types/tool-manifest.d.ts +52 -0
- package/dist/types/tool-manifest.d.ts.map +1 -0
- package/dist/types/tool-manifest.js +37 -0
- package/dist/types/tool-manifest.js.map +1 -0
- package/dist/utils/atomic-file.d.ts +50 -0
- package/dist/utils/atomic-file.d.ts.map +1 -0
- package/dist/utils/atomic-file.js +217 -0
- package/dist/utils/atomic-file.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +22 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-validator.d.ts +40 -0
- package/dist/utils/json-validator.d.ts.map +1 -0
- package/dist/utils/json-validator.js +295 -0
- package/dist/utils/json-validator.js.map +1 -0
- package/dist/utils/ref-id-manager.d.ts +26 -0
- package/dist/utils/ref-id-manager.d.ts.map +1 -0
- package/dist/utils/ref-id-manager.js +81 -0
- package/dist/utils/ref-id-manager.js.map +1 -0
- package/dist/utils/request-queue.d.ts +37 -0
- package/dist/utils/request-queue.d.ts.map +1 -0
- package/dist/utils/request-queue.js +110 -0
- package/dist/utils/request-queue.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CDP Client - Wrapper around puppeteer-core for Chrome DevTools Protocol
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.CDPClientFactory = exports.CDPClient = void 0;
|
|
10
|
+
exports.getCDPClient = getCDPClient;
|
|
11
|
+
exports.getCDPClientFactory = getCDPClientFactory;
|
|
12
|
+
const puppeteer_core_1 = __importDefault(require("puppeteer-core"));
|
|
13
|
+
const launcher_1 = require("../chrome/launcher");
|
|
14
|
+
const global_1 = require("../config/global");
|
|
15
|
+
// Helper to get target ID (internal puppeteer property)
|
|
16
|
+
function getTargetId(target) {
|
|
17
|
+
// Access the internal _targetId property
|
|
18
|
+
return target._targetId;
|
|
19
|
+
}
|
|
20
|
+
class CDPClient {
|
|
21
|
+
browser = null;
|
|
22
|
+
sessions = new Map();
|
|
23
|
+
port;
|
|
24
|
+
maxReconnectAttempts;
|
|
25
|
+
reconnectDelayMs;
|
|
26
|
+
heartbeatIntervalMs;
|
|
27
|
+
heartbeatTimer = null;
|
|
28
|
+
connectionState = 'disconnected';
|
|
29
|
+
eventListeners = [];
|
|
30
|
+
targetDestroyedListeners = [];
|
|
31
|
+
reconnectAttempts = 0;
|
|
32
|
+
autoLaunch;
|
|
33
|
+
cookieSourceCache = new Map();
|
|
34
|
+
cookieDataCache = new Map();
|
|
35
|
+
targetIdIndex = new Map();
|
|
36
|
+
inFlightCookieScans = new Map();
|
|
37
|
+
static COOKIE_CACHE_TTL = 300000; // 5 minutes
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
const globalConfig = (0, global_1.getGlobalConfig)();
|
|
40
|
+
this.port = options.port || globalConfig.port;
|
|
41
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts || 3;
|
|
42
|
+
this.reconnectDelayMs = options.reconnectDelayMs || 1000;
|
|
43
|
+
this.heartbeatIntervalMs = options.heartbeatIntervalMs || 5000;
|
|
44
|
+
// Use explicit option if provided, otherwise use global config
|
|
45
|
+
this.autoLaunch = options.autoLaunch !== undefined ? options.autoLaunch : globalConfig.autoLaunch;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get current connection state
|
|
49
|
+
*/
|
|
50
|
+
getConnectionState() {
|
|
51
|
+
return this.connectionState;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Add connection event listener
|
|
55
|
+
*/
|
|
56
|
+
addConnectionListener(listener) {
|
|
57
|
+
this.eventListeners.push(listener);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Remove connection event listener
|
|
61
|
+
*/
|
|
62
|
+
removeConnectionListener(listener) {
|
|
63
|
+
const index = this.eventListeners.indexOf(listener);
|
|
64
|
+
if (index !== -1) {
|
|
65
|
+
this.eventListeners.splice(index, 1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Add target destroyed listener
|
|
70
|
+
*/
|
|
71
|
+
addTargetDestroyedListener(listener) {
|
|
72
|
+
this.targetDestroyedListeners.push(listener);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Handle target destroyed event
|
|
76
|
+
*/
|
|
77
|
+
onTargetDestroyed(targetId) {
|
|
78
|
+
this.sessions.delete(targetId);
|
|
79
|
+
// Clean up cookie source cache entries pointing to this target
|
|
80
|
+
for (const [key, entry] of this.cookieSourceCache) {
|
|
81
|
+
if (entry.targetId === targetId) {
|
|
82
|
+
this.cookieSourceCache.delete(key);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Clean up cookie data cache for this target
|
|
86
|
+
this.cookieDataCache.delete(targetId);
|
|
87
|
+
this.targetIdIndex.delete(targetId);
|
|
88
|
+
for (const listener of this.targetDestroyedListeners) {
|
|
89
|
+
try {
|
|
90
|
+
listener(targetId);
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
console.error('[CDPClient] Target destroyed listener error:', e);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Emit connection event
|
|
99
|
+
*/
|
|
100
|
+
emitConnectionEvent(event) {
|
|
101
|
+
for (const listener of this.eventListeners) {
|
|
102
|
+
try {
|
|
103
|
+
listener(event);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
console.error('[CDPClient] Event listener error:', e);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Start heartbeat monitoring
|
|
112
|
+
*/
|
|
113
|
+
startHeartbeat() {
|
|
114
|
+
this.stopHeartbeat();
|
|
115
|
+
this.heartbeatTimer = setInterval(() => {
|
|
116
|
+
this.checkConnection();
|
|
117
|
+
}, this.heartbeatIntervalMs);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Stop heartbeat monitoring
|
|
121
|
+
*/
|
|
122
|
+
stopHeartbeat() {
|
|
123
|
+
if (this.heartbeatTimer) {
|
|
124
|
+
clearInterval(this.heartbeatTimer);
|
|
125
|
+
this.heartbeatTimer = null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check connection health
|
|
130
|
+
*/
|
|
131
|
+
async checkConnection() {
|
|
132
|
+
if (!this.browser) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
// Simple check - try to get browser version
|
|
137
|
+
if (!this.browser.isConnected()) {
|
|
138
|
+
console.error('[CDPClient] Heartbeat: Connection lost, attempting reconnect...');
|
|
139
|
+
await this.handleDisconnect();
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error('[CDPClient] Heartbeat check failed:', error);
|
|
146
|
+
await this.handleDisconnect();
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Handle disconnection with automatic reconnection
|
|
152
|
+
*/
|
|
153
|
+
async handleDisconnect() {
|
|
154
|
+
if (this.connectionState === 'reconnecting') {
|
|
155
|
+
return; // Already reconnecting
|
|
156
|
+
}
|
|
157
|
+
this.connectionState = 'reconnecting';
|
|
158
|
+
this.emitConnectionEvent({
|
|
159
|
+
type: 'disconnected',
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
});
|
|
162
|
+
// Clear existing sessions
|
|
163
|
+
this.sessions.clear();
|
|
164
|
+
this.targetIdIndex.clear();
|
|
165
|
+
this.inFlightCookieScans.clear();
|
|
166
|
+
this.browser = null;
|
|
167
|
+
// Attempt reconnection
|
|
168
|
+
while (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
169
|
+
this.reconnectAttempts++;
|
|
170
|
+
console.error(`[CDPClient] Reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}...`);
|
|
171
|
+
this.emitConnectionEvent({
|
|
172
|
+
type: 'reconnecting',
|
|
173
|
+
timestamp: Date.now(),
|
|
174
|
+
attempt: this.reconnectAttempts,
|
|
175
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
await this.connectInternal();
|
|
178
|
+
console.error('[CDPClient] Reconnection successful');
|
|
179
|
+
this.reconnectAttempts = 0;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error(`[CDPClient] Reconnect attempt ${this.reconnectAttempts} failed:`, error);
|
|
184
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
185
|
+
await new Promise(resolve => setTimeout(resolve, this.reconnectDelayMs));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// All attempts failed
|
|
190
|
+
this.connectionState = 'disconnected';
|
|
191
|
+
this.emitConnectionEvent({
|
|
192
|
+
type: 'reconnect_failed',
|
|
193
|
+
timestamp: Date.now(),
|
|
194
|
+
error: `Failed after ${this.maxReconnectAttempts} attempts`,
|
|
195
|
+
});
|
|
196
|
+
console.error('[CDPClient] All reconnection attempts failed');
|
|
197
|
+
this.reconnectAttempts = 0;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Internal connect logic
|
|
201
|
+
*/
|
|
202
|
+
async connectInternal() {
|
|
203
|
+
const launcher = (0, launcher_1.getChromeLauncher)(this.port);
|
|
204
|
+
const instance = await launcher.ensureChrome({ autoLaunch: this.autoLaunch });
|
|
205
|
+
this.browser = await puppeteer_core_1.default.connect({
|
|
206
|
+
browserWSEndpoint: instance.wsEndpoint,
|
|
207
|
+
defaultViewport: null,
|
|
208
|
+
});
|
|
209
|
+
// Set up disconnect handler
|
|
210
|
+
this.browser.on('disconnected', () => {
|
|
211
|
+
console.error('[CDPClient] Browser disconnected');
|
|
212
|
+
this.handleDisconnect();
|
|
213
|
+
});
|
|
214
|
+
// Set up target destroyed handler
|
|
215
|
+
this.browser.on('targetdestroyed', (target) => {
|
|
216
|
+
const targetId = getTargetId(target);
|
|
217
|
+
console.error(`[CDPClient] Target destroyed: ${targetId}`);
|
|
218
|
+
this.onTargetDestroyed(targetId);
|
|
219
|
+
});
|
|
220
|
+
// Maintain target-to-page index for O(1) lookups
|
|
221
|
+
this.browser.on('targetcreated', async (target) => {
|
|
222
|
+
if (target.type() === 'page') {
|
|
223
|
+
try {
|
|
224
|
+
const page = await target.page();
|
|
225
|
+
if (page) {
|
|
226
|
+
this.targetIdIndex.set(getTargetId(target), page);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Target may have been destroyed before we could index it
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
this.connectionState = 'connected';
|
|
235
|
+
this.emitConnectionEvent({
|
|
236
|
+
type: 'connected',
|
|
237
|
+
timestamp: Date.now(),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Connect to Chrome instance
|
|
242
|
+
*/
|
|
243
|
+
async connect() {
|
|
244
|
+
if (this.browser && this.browser.isConnected()) {
|
|
245
|
+
// Verify connection is actually working by checking Chrome endpoint
|
|
246
|
+
try {
|
|
247
|
+
const launcher = (0, launcher_1.getChromeLauncher)(this.port);
|
|
248
|
+
const instance = await launcher.ensureChrome({ autoLaunch: this.autoLaunch });
|
|
249
|
+
const currentWsUrl = instance.wsEndpoint;
|
|
250
|
+
// Check if the browser's WebSocket URL matches current Chrome
|
|
251
|
+
const browserWsUrl = this.browser.wsEndpoint();
|
|
252
|
+
if (browserWsUrl !== currentWsUrl) {
|
|
253
|
+
console.error('[CDPClient] WebSocket URL mismatch, reconnecting...');
|
|
254
|
+
await this.forceReconnect();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
console.error('[CDPClient] Connection check failed, reconnecting...');
|
|
261
|
+
await this.forceReconnect();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
this.connectionState = 'connecting';
|
|
266
|
+
await this.connectInternal();
|
|
267
|
+
this.startHeartbeat();
|
|
268
|
+
console.error('[CDPClient] Connected to Chrome');
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Force reconnect by disconnecting and reconnecting
|
|
272
|
+
*/
|
|
273
|
+
async forceReconnect() {
|
|
274
|
+
this.stopHeartbeat();
|
|
275
|
+
if (this.browser) {
|
|
276
|
+
try {
|
|
277
|
+
this.browser.removeAllListeners('disconnected');
|
|
278
|
+
await this.browser.disconnect();
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Ignore disconnect errors
|
|
282
|
+
}
|
|
283
|
+
this.browser = null;
|
|
284
|
+
this.sessions.clear();
|
|
285
|
+
}
|
|
286
|
+
this.connectionState = 'connecting';
|
|
287
|
+
await this.connectInternal();
|
|
288
|
+
this.startHeartbeat();
|
|
289
|
+
console.error('[CDPClient] Reconnected to Chrome');
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Disconnect from Chrome
|
|
293
|
+
*/
|
|
294
|
+
async disconnect() {
|
|
295
|
+
this.stopHeartbeat();
|
|
296
|
+
if (this.browser) {
|
|
297
|
+
try {
|
|
298
|
+
await this.browser.disconnect();
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// Browser might already be disconnected
|
|
302
|
+
}
|
|
303
|
+
this.browser = null;
|
|
304
|
+
this.sessions.clear();
|
|
305
|
+
this.connectionState = 'disconnected';
|
|
306
|
+
console.error('[CDPClient] Disconnected from Chrome');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get browser instance
|
|
311
|
+
*/
|
|
312
|
+
getBrowser() {
|
|
313
|
+
if (!this.browser) {
|
|
314
|
+
throw new Error('Not connected to Chrome. Call connect() first.');
|
|
315
|
+
}
|
|
316
|
+
return this.browser;
|
|
317
|
+
}
|
|
318
|
+
// Default viewport for consistent debugging experience
|
|
319
|
+
static DEFAULT_VIEWPORT = { width: 1920, height: 1080 };
|
|
320
|
+
/**
|
|
321
|
+
* Create a new isolated browser context for session isolation
|
|
322
|
+
* Each context has its own cookies, localStorage, sessionStorage
|
|
323
|
+
*/
|
|
324
|
+
async createBrowserContext() {
|
|
325
|
+
const browser = this.getBrowser();
|
|
326
|
+
const context = await browser.createBrowserContext();
|
|
327
|
+
console.error(`[CDPClient] Created new browser context`);
|
|
328
|
+
return context;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Close a browser context and all its pages
|
|
332
|
+
*/
|
|
333
|
+
async closeBrowserContext(context) {
|
|
334
|
+
try {
|
|
335
|
+
await context.close();
|
|
336
|
+
console.error(`[CDPClient] Closed browser context`);
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
// Context may already be closed
|
|
340
|
+
console.error(`[CDPClient] Error closing browser context:`, e);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Check if a hostname is localhost
|
|
345
|
+
*/
|
|
346
|
+
isLocalhost(url) {
|
|
347
|
+
try {
|
|
348
|
+
const hostname = new URL(url).hostname;
|
|
349
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Calculate domain match score between two URLs
|
|
357
|
+
* Higher score = better match
|
|
358
|
+
*/
|
|
359
|
+
domainMatchScore(candidateUrl, targetDomain) {
|
|
360
|
+
try {
|
|
361
|
+
const candidateHostname = new URL(candidateUrl).hostname;
|
|
362
|
+
const candidateParts = candidateHostname.split('.').reverse();
|
|
363
|
+
const targetParts = targetDomain.split('.').reverse();
|
|
364
|
+
// Exact match
|
|
365
|
+
if (candidateHostname === targetDomain) {
|
|
366
|
+
return 100;
|
|
367
|
+
}
|
|
368
|
+
// Count matching TLD parts from right to left
|
|
369
|
+
let matchingParts = 0;
|
|
370
|
+
for (let i = 0; i < Math.min(candidateParts.length, targetParts.length); i++) {
|
|
371
|
+
if (candidateParts[i] === targetParts[i]) {
|
|
372
|
+
matchingParts++;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Subdomain match (e.g., api.example.com matches example.com)
|
|
379
|
+
if (matchingParts >= 2) {
|
|
380
|
+
return 50 + matchingParts * 10;
|
|
381
|
+
}
|
|
382
|
+
// Same TLD only (e.g., both .com)
|
|
383
|
+
if (matchingParts === 1) {
|
|
384
|
+
return 10;
|
|
385
|
+
}
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return 0;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Find an authenticated page with cookies to copy from.
|
|
394
|
+
* Returns the targetId of a page that has cookies in Chrome's default context.
|
|
395
|
+
*
|
|
396
|
+
* Promise coalescing: concurrent callers for the same domain share one probe
|
|
397
|
+
* instead of independently hammering Chrome with 20 simultaneous scans.
|
|
398
|
+
*
|
|
399
|
+
* @param targetDomain Optional domain to prioritize when selecting cookie source
|
|
400
|
+
*/
|
|
401
|
+
async findAuthenticatedPageTargetId(targetDomain) {
|
|
402
|
+
// Check cache first (stale targetId is handled gracefully: copyCookiesViaCDP returns 0)
|
|
403
|
+
const cacheKey = targetDomain || '*';
|
|
404
|
+
const cached = this.cookieSourceCache.get(cacheKey);
|
|
405
|
+
if (cached && Date.now() - cached.timestamp < CDPClient.COOKIE_CACHE_TTL) {
|
|
406
|
+
console.error(`[CDPClient] Cache hit for cookie source (domain: ${cacheKey}): ${cached.targetId.slice(0, 8)}`);
|
|
407
|
+
return cached.targetId;
|
|
408
|
+
}
|
|
409
|
+
// Promise coalescing: if a scan for this domain is already in-flight, reuse it
|
|
410
|
+
const existing = this.inFlightCookieScans.get(cacheKey);
|
|
411
|
+
if (existing) {
|
|
412
|
+
console.error(`[CDPClient] Coalescing cookie scan for domain: ${cacheKey}`);
|
|
413
|
+
return existing;
|
|
414
|
+
}
|
|
415
|
+
// Start the scan and register it so concurrent callers share this promise
|
|
416
|
+
const scanPromise = this._doFindAuthenticatedPageTargetId(targetDomain, cacheKey);
|
|
417
|
+
this.inFlightCookieScans.set(cacheKey, scanPromise);
|
|
418
|
+
try {
|
|
419
|
+
return await scanPromise;
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
this.inFlightCookieScans.delete(cacheKey);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Internal implementation of the authenticated-page probe.
|
|
427
|
+
* Uses Target.attachToTarget (multiplexed CDP) instead of raw WebSocket connections.
|
|
428
|
+
* Uses Target.getTargets result directly instead of /json/list HTTP calls.
|
|
429
|
+
*/
|
|
430
|
+
async _doFindAuthenticatedPageTargetId(targetDomain, cacheKey) {
|
|
431
|
+
const browser = this.getBrowser();
|
|
432
|
+
const session = await browser.target().createCDPSession();
|
|
433
|
+
try {
|
|
434
|
+
const { targetInfos } = await session.send('Target.getTargets');
|
|
435
|
+
// Filter to candidate pages (not chrome://, not login pages, etc.)
|
|
436
|
+
let candidates = targetInfos.filter(target => target.type === 'page' &&
|
|
437
|
+
!target.url.startsWith('chrome://') &&
|
|
438
|
+
!target.url.startsWith('chrome-extension://') &&
|
|
439
|
+
target.url !== 'about:blank' &&
|
|
440
|
+
!target.url.includes('/login') &&
|
|
441
|
+
!target.url.includes('/signin') &&
|
|
442
|
+
!target.url.includes('/auth'));
|
|
443
|
+
if (candidates.length === 0) {
|
|
444
|
+
console.error('[CDPClient] No candidate pages found for cookie source');
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
// If targeting an external domain (not localhost), exclude localhost pages
|
|
448
|
+
if (targetDomain && !this.isLocalhost(`https://${targetDomain}`)) {
|
|
449
|
+
const externalCandidates = candidates.filter(c => !this.isLocalhost(c.url));
|
|
450
|
+
if (externalCandidates.length > 0) {
|
|
451
|
+
console.error(`[CDPClient] Filtered out ${candidates.length - externalCandidates.length} localhost pages for external domain target`);
|
|
452
|
+
candidates = externalCandidates;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// Sort candidates by domain match score (highest first)
|
|
456
|
+
if (targetDomain) {
|
|
457
|
+
candidates.sort((a, b) => {
|
|
458
|
+
const scoreA = this.domainMatchScore(a.url, targetDomain);
|
|
459
|
+
const scoreB = this.domainMatchScore(b.url, targetDomain);
|
|
460
|
+
return scoreB - scoreA;
|
|
461
|
+
});
|
|
462
|
+
console.error(`[CDPClient] Sorted ${candidates.length} candidates by domain match to ${targetDomain}`);
|
|
463
|
+
}
|
|
464
|
+
// Check each candidate to find one with actual cookies (in priority order).
|
|
465
|
+
// Uses Target.attachToTarget over the existing multiplexed session — no raw WebSocket,
|
|
466
|
+
// no /json/list HTTP round-trip.
|
|
467
|
+
for (const candidate of candidates) {
|
|
468
|
+
let attachedSessionId = null;
|
|
469
|
+
try {
|
|
470
|
+
const { sessionId } = await session.send('Target.attachToTarget', {
|
|
471
|
+
targetId: candidate.targetId,
|
|
472
|
+
flatten: true,
|
|
473
|
+
});
|
|
474
|
+
attachedSessionId = sessionId;
|
|
475
|
+
// Send Network.getAllCookies through the flat CDP session
|
|
476
|
+
const result = await session.send('Network.getAllCookies', undefined, { sessionId });
|
|
477
|
+
const cookieCount = result?.cookies?.length || 0;
|
|
478
|
+
if (cookieCount > 0) {
|
|
479
|
+
const domainScore = targetDomain ? this.domainMatchScore(candidate.url, targetDomain) : 0;
|
|
480
|
+
console.error(`[CDPClient] Found authenticated page ${candidate.targetId.slice(0, 8)} at ${candidate.url.slice(0, 50)} (${cookieCount} cookies, domain score: ${domainScore})`);
|
|
481
|
+
this.cookieSourceCache.set(cacheKey, { targetId: candidate.targetId, timestamp: Date.now() });
|
|
482
|
+
return candidate.targetId;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
// Target may be unresponsive or already detached — skip
|
|
487
|
+
}
|
|
488
|
+
finally {
|
|
489
|
+
if (attachedSessionId) {
|
|
490
|
+
await session.send('Target.detachFromTarget', { sessionId: attachedSessionId }).catch(() => { });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
console.error('[CDPClient] No pages with cookies found');
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
finally {
|
|
498
|
+
await session.detach().catch(() => { });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Copy all cookies from authenticated page to destination page.
|
|
503
|
+
* Uses Target.attachToTarget (multiplexed CDP) to bypass Puppeteer's context isolation —
|
|
504
|
+
* no raw WebSocket connections, no /json/list HTTP calls.
|
|
505
|
+
*/
|
|
506
|
+
async copyCookiesViaCDP(sourceTargetId, destPage) {
|
|
507
|
+
console.error(`[CDPClient] copyCookiesViaCDP called with sourceTargetId: ${sourceTargetId.slice(0, 8)}`);
|
|
508
|
+
try {
|
|
509
|
+
// Check cookie data cache first — avoids re-probing Chrome entirely
|
|
510
|
+
const cachedData = this.cookieDataCache.get(sourceTargetId);
|
|
511
|
+
if (cachedData && Date.now() - cachedData.timestamp < CDPClient.COOKIE_CACHE_TTL) {
|
|
512
|
+
console.error(`[CDPClient] Cache hit for cookie data (${cachedData.cookies.length} cookies), skipping CDP attach`);
|
|
513
|
+
const destSession = await destPage.createCDPSession();
|
|
514
|
+
try {
|
|
515
|
+
const cookiesToSet = cachedData.cookies.map(c => ({
|
|
516
|
+
name: c.name,
|
|
517
|
+
value: c.value,
|
|
518
|
+
domain: c.domain,
|
|
519
|
+
path: c.path,
|
|
520
|
+
expires: c.expires,
|
|
521
|
+
httpOnly: c.httpOnly,
|
|
522
|
+
secure: c.secure,
|
|
523
|
+
sameSite: c.sameSite,
|
|
524
|
+
}));
|
|
525
|
+
await destSession.send('Network.setCookies', { cookies: cookiesToSet });
|
|
526
|
+
console.error(`[CDPClient] Successfully copied ${cachedData.cookies.length} cookies (from cache)`);
|
|
527
|
+
return cachedData.cookies.length;
|
|
528
|
+
}
|
|
529
|
+
finally {
|
|
530
|
+
await destSession.detach().catch(() => { });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Attach to the source target via the multiplexed browser CDP session
|
|
534
|
+
const browser = this.getBrowser();
|
|
535
|
+
const browserSession = await browser.target().createCDPSession();
|
|
536
|
+
let attachedSessionId = null;
|
|
537
|
+
try {
|
|
538
|
+
// Verify the target exists before attaching
|
|
539
|
+
const { targetInfos } = await browserSession.send('Target.getTargets');
|
|
540
|
+
const sourceInfo = targetInfos.find(t => t.targetId === sourceTargetId);
|
|
541
|
+
if (!sourceInfo) {
|
|
542
|
+
console.error(`[CDPClient] Source target not found: ${sourceTargetId.slice(0, 8)}`);
|
|
543
|
+
console.error(`[CDPClient] Available targets: ${targetInfos.map(t => t.targetId.slice(0, 8) + ' ' + t.url.slice(0, 40)).join(', ')}`);
|
|
544
|
+
return 0;
|
|
545
|
+
}
|
|
546
|
+
console.error(`[CDPClient] Attaching to source target at ${sourceInfo.url.slice(0, 50)}`);
|
|
547
|
+
const { sessionId } = await browserSession.send('Target.attachToTarget', {
|
|
548
|
+
targetId: sourceTargetId,
|
|
549
|
+
flatten: true,
|
|
550
|
+
});
|
|
551
|
+
attachedSessionId = sessionId;
|
|
552
|
+
// Fetch cookies through the flat session (no raw WebSocket, no /json/list)
|
|
553
|
+
const result = await browserSession.send('Network.getAllCookies', undefined, { sessionId });
|
|
554
|
+
const cookies = result?.cookies || [];
|
|
555
|
+
// Store in cookie data cache
|
|
556
|
+
this.cookieDataCache.set(sourceTargetId, { cookies, timestamp: Date.now() });
|
|
557
|
+
if (cookies.length === 0) {
|
|
558
|
+
console.error('[CDPClient] No cookies found in source page');
|
|
559
|
+
return 0;
|
|
560
|
+
}
|
|
561
|
+
console.error(`[CDPClient] Found ${cookies.length} cookies, setting on destination page`);
|
|
562
|
+
// Set cookies on destination page via its own CDPSession
|
|
563
|
+
const destSession = await destPage.createCDPSession();
|
|
564
|
+
try {
|
|
565
|
+
const cookiesToSet = cookies.map(c => ({
|
|
566
|
+
name: c.name,
|
|
567
|
+
value: c.value,
|
|
568
|
+
domain: c.domain,
|
|
569
|
+
path: c.path,
|
|
570
|
+
expires: c.expires,
|
|
571
|
+
httpOnly: c.httpOnly,
|
|
572
|
+
secure: c.secure,
|
|
573
|
+
sameSite: c.sameSite,
|
|
574
|
+
}));
|
|
575
|
+
await destSession.send('Network.setCookies', { cookies: cookiesToSet });
|
|
576
|
+
console.error(`[CDPClient] Successfully copied ${cookies.length} cookies`);
|
|
577
|
+
return cookies.length;
|
|
578
|
+
}
|
|
579
|
+
finally {
|
|
580
|
+
await destSession.detach().catch(() => { });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
finally {
|
|
584
|
+
if (attachedSessionId) {
|
|
585
|
+
await browserSession.send('Target.detachFromTarget', { sessionId: attachedSessionId }).catch(() => { });
|
|
586
|
+
}
|
|
587
|
+
await browserSession.detach().catch(() => { });
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
console.error('[CDPClient] Error in copyCookiesViaCDP:', error);
|
|
592
|
+
return 0;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Create a new page with default viewport
|
|
597
|
+
* @param url Optional URL to navigate to
|
|
598
|
+
* @param context Optional browser context for session isolation (null/undefined = use Chrome's default context with cookies)
|
|
599
|
+
*/
|
|
600
|
+
async createPage(url, context) {
|
|
601
|
+
let page;
|
|
602
|
+
const browser = this.getBrowser();
|
|
603
|
+
// Extract domain from URL for cookie source prioritization
|
|
604
|
+
let targetDomain;
|
|
605
|
+
if (url) {
|
|
606
|
+
try {
|
|
607
|
+
targetDomain = new URL(url).hostname;
|
|
608
|
+
console.error(`[CDPClient] createPage targeting domain: ${targetDomain}`);
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
// Invalid URL, proceed without domain preference
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (context) {
|
|
615
|
+
// Create page in isolated context (for worker isolation)
|
|
616
|
+
page = await context.newPage();
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
// Create page and copy cookies from an authenticated page
|
|
620
|
+
// This allows the new page to share the authenticated session
|
|
621
|
+
page = await browser.newPage();
|
|
622
|
+
// Find an authenticated page to copy cookies from (with domain preference)
|
|
623
|
+
const authPageTargetId = await this.findAuthenticatedPageTargetId(targetDomain);
|
|
624
|
+
if (authPageTargetId) {
|
|
625
|
+
await this.copyCookiesViaCDP(authPageTargetId, page);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// Set default viewport for consistent debugging experience
|
|
629
|
+
await page.setViewport(CDPClient.DEFAULT_VIEWPORT);
|
|
630
|
+
if (url) {
|
|
631
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
632
|
+
}
|
|
633
|
+
return page;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Get all page targets
|
|
637
|
+
*/
|
|
638
|
+
async getPages() {
|
|
639
|
+
const browser = this.getBrowser();
|
|
640
|
+
return browser.pages();
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Get page by target ID
|
|
644
|
+
*/
|
|
645
|
+
async getPageByTargetId(targetId) {
|
|
646
|
+
// Fast path: check index first (O(1))
|
|
647
|
+
const indexed = this.targetIdIndex.get(targetId);
|
|
648
|
+
if (indexed && !indexed.isClosed()) {
|
|
649
|
+
return indexed;
|
|
650
|
+
}
|
|
651
|
+
// Fallback: linear scan (for pages created before indexing started)
|
|
652
|
+
const browser = this.getBrowser();
|
|
653
|
+
const targets = browser.targets();
|
|
654
|
+
for (const target of targets) {
|
|
655
|
+
if (getTargetId(target) === targetId && target.type() === 'page') {
|
|
656
|
+
const page = await target.page();
|
|
657
|
+
if (page) {
|
|
658
|
+
// Populate index for future lookups
|
|
659
|
+
this.targetIdIndex.set(targetId, page);
|
|
660
|
+
}
|
|
661
|
+
return page;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// Clean stale index entry
|
|
665
|
+
this.targetIdIndex.delete(targetId);
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Get CDP session for a page
|
|
670
|
+
*/
|
|
671
|
+
async getCDPSession(page) {
|
|
672
|
+
const target = page.target();
|
|
673
|
+
const targetId = getTargetId(target);
|
|
674
|
+
let session = this.sessions.get(targetId);
|
|
675
|
+
if (!session) {
|
|
676
|
+
session = await page.createCDPSession();
|
|
677
|
+
this.sessions.set(targetId, session);
|
|
678
|
+
}
|
|
679
|
+
return session;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Execute CDP command on a page
|
|
683
|
+
*/
|
|
684
|
+
async send(page, method, params) {
|
|
685
|
+
const session = await this.getCDPSession(page);
|
|
686
|
+
return session.send(method, params);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Get all targets
|
|
690
|
+
*/
|
|
691
|
+
getTargets() {
|
|
692
|
+
return this.getBrowser().targets();
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Find target by ID
|
|
696
|
+
*/
|
|
697
|
+
findTarget(targetId) {
|
|
698
|
+
return this.getTargets().find((t) => getTargetId(t) === targetId);
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Trigger garbage collection on a page (best-effort)
|
|
702
|
+
*/
|
|
703
|
+
async triggerGC(page) {
|
|
704
|
+
try {
|
|
705
|
+
const session = await this.getCDPSession(page);
|
|
706
|
+
await session.send('HeapProfiler.collectGarbage');
|
|
707
|
+
}
|
|
708
|
+
catch {
|
|
709
|
+
// Best-effort: silently ignore GC failures
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Close a page by target ID
|
|
714
|
+
*/
|
|
715
|
+
async closePage(targetId) {
|
|
716
|
+
const page = await this.getPageByTargetId(targetId);
|
|
717
|
+
if (page) {
|
|
718
|
+
await this.triggerGC(page);
|
|
719
|
+
await page.close();
|
|
720
|
+
this.sessions.delete(targetId);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Check if connected
|
|
725
|
+
*/
|
|
726
|
+
isConnected() {
|
|
727
|
+
return this.browser !== null && this.browser.isConnected();
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Get the port this client is connected to
|
|
731
|
+
*/
|
|
732
|
+
getPort() {
|
|
733
|
+
return this.port;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Create a CDPClient instance for a specific port
|
|
737
|
+
*/
|
|
738
|
+
static createForPort(port, options) {
|
|
739
|
+
return new CDPClient({ ...options, port });
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
exports.CDPClient = CDPClient;
|
|
743
|
+
// Singleton instance
|
|
744
|
+
let clientInstance = null;
|
|
745
|
+
function getCDPClient(options) {
|
|
746
|
+
if (!clientInstance) {
|
|
747
|
+
clientInstance = new CDPClient(options);
|
|
748
|
+
}
|
|
749
|
+
return clientInstance;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Factory for managing multiple CDPClient instances (one per Chrome port)
|
|
753
|
+
*/
|
|
754
|
+
class CDPClientFactory {
|
|
755
|
+
clients = new Map();
|
|
756
|
+
/**
|
|
757
|
+
* Get an existing client for the given port, or create a new one
|
|
758
|
+
*/
|
|
759
|
+
getOrCreate(port, options) {
|
|
760
|
+
let client = this.clients.get(port);
|
|
761
|
+
if (!client) {
|
|
762
|
+
client = CDPClient.createForPort(port, options);
|
|
763
|
+
this.clients.set(port, client);
|
|
764
|
+
}
|
|
765
|
+
return client;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Get an existing client for the given port, or undefined if not found
|
|
769
|
+
*/
|
|
770
|
+
get(port) {
|
|
771
|
+
return this.clients.get(port);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get all managed client instances
|
|
775
|
+
*/
|
|
776
|
+
getAll() {
|
|
777
|
+
return Array.from(this.clients.values());
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Disconnect all managed clients
|
|
781
|
+
*/
|
|
782
|
+
async disconnectAll() {
|
|
783
|
+
const disconnectPromises = Array.from(this.clients.values()).map(client => client.disconnect().catch(err => console.error(`[CDPClientFactory] Error disconnecting client on port ${client.getPort()}:`, err)));
|
|
784
|
+
await Promise.all(disconnectPromises);
|
|
785
|
+
this.clients.clear();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
exports.CDPClientFactory = CDPClientFactory;
|
|
789
|
+
// Singleton factory instance
|
|
790
|
+
let factoryInstance = null;
|
|
791
|
+
function getCDPClientFactory() {
|
|
792
|
+
if (!factoryInstance) {
|
|
793
|
+
factoryInstance = new CDPClientFactory();
|
|
794
|
+
}
|
|
795
|
+
return factoryInstance;
|
|
796
|
+
}
|
|
797
|
+
//# sourceMappingURL=client.js.map
|