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,912 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session Manager - Manages lifecycle of parallel Claude Code sessions
|
|
4
|
+
* Supports multiple Workers within a single session for parallel browser operations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.SessionManager = void 0;
|
|
8
|
+
exports.getSessionManager = getSessionManager;
|
|
9
|
+
const client_1 = require("./cdp/client");
|
|
10
|
+
const connection_pool_1 = require("./cdp/connection-pool");
|
|
11
|
+
const pool_1 = require("./chrome/pool");
|
|
12
|
+
const global_1 = require("./config/global");
|
|
13
|
+
const request_queue_1 = require("./utils/request-queue");
|
|
14
|
+
const ref_id_manager_1 = require("./utils/ref-id-manager");
|
|
15
|
+
const router_1 = require("./router");
|
|
16
|
+
// Helper to get target ID (internal puppeteer property)
|
|
17
|
+
function getTargetId(target) {
|
|
18
|
+
return target._targetId;
|
|
19
|
+
}
|
|
20
|
+
const DEFAULT_CONFIG = {
|
|
21
|
+
sessionTTL: 30 * 60 * 1000, // 30 minutes
|
|
22
|
+
cleanupInterval: 60 * 1000, // 1 minute
|
|
23
|
+
autoCleanup: true,
|
|
24
|
+
maxSessions: 100,
|
|
25
|
+
maxWorkersPerSession: 50,
|
|
26
|
+
useConnectionPool: true, // Enabled by default for faster page creation
|
|
27
|
+
useDefaultContext: true, // Use Chrome profile's cookies/sessions by default
|
|
28
|
+
usePool: false, // Disabled by default; enable for multi-Chrome origin isolation
|
|
29
|
+
};
|
|
30
|
+
class SessionManager {
|
|
31
|
+
sessions = new Map();
|
|
32
|
+
targetToWorker = new Map();
|
|
33
|
+
cdpClient;
|
|
34
|
+
connectionPool = null;
|
|
35
|
+
chromePool = null;
|
|
36
|
+
cdpFactory;
|
|
37
|
+
queueManager;
|
|
38
|
+
eventListeners = [];
|
|
39
|
+
browserRouter = null;
|
|
40
|
+
// TTL & Stats
|
|
41
|
+
config;
|
|
42
|
+
cleanupTimer = null;
|
|
43
|
+
startTime = Date.now();
|
|
44
|
+
totalSessionsCreated = 0;
|
|
45
|
+
totalSessionsCleaned = 0;
|
|
46
|
+
lastCleanupTime = null;
|
|
47
|
+
constructor(cdpClient, config) {
|
|
48
|
+
this.cdpClient = cdpClient || (0, client_1.getCDPClient)();
|
|
49
|
+
this.queueManager = new request_queue_1.RequestQueueManager();
|
|
50
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
51
|
+
this.cdpFactory = (0, client_1.getCDPClientFactory)();
|
|
52
|
+
if (this.config.useConnectionPool) {
|
|
53
|
+
this.connectionPool = (0, connection_pool_1.getCDPConnectionPool)();
|
|
54
|
+
}
|
|
55
|
+
if (this.config.usePool) {
|
|
56
|
+
this.chromePool = (0, pool_1.getChromePool)({ autoLaunch: (0, global_1.getGlobalConfig)().autoLaunch });
|
|
57
|
+
}
|
|
58
|
+
if (this.config.autoCleanup) {
|
|
59
|
+
this.startAutoCleanup();
|
|
60
|
+
}
|
|
61
|
+
// Register target destroyed listener
|
|
62
|
+
this.cdpClient.addTargetDestroyedListener((targetId) => {
|
|
63
|
+
this.onTargetClosed(targetId);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the CDPClient for a specific worker (may be on a different Chrome instance)
|
|
68
|
+
*/
|
|
69
|
+
getCDPClientForWorker(sessionId, workerId) {
|
|
70
|
+
const worker = this.getWorker(sessionId, workerId);
|
|
71
|
+
if (worker?.port) {
|
|
72
|
+
const client = this.cdpFactory.get(worker.port);
|
|
73
|
+
if (client)
|
|
74
|
+
return client;
|
|
75
|
+
}
|
|
76
|
+
return this.cdpClient;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Start automatic cleanup interval
|
|
80
|
+
*/
|
|
81
|
+
startAutoCleanup() {
|
|
82
|
+
if (this.cleanupTimer) {
|
|
83
|
+
clearInterval(this.cleanupTimer);
|
|
84
|
+
}
|
|
85
|
+
this.cleanupTimer = setInterval(async () => {
|
|
86
|
+
try {
|
|
87
|
+
const deleted = await this.cleanupInactiveSessions(this.config.sessionTTL);
|
|
88
|
+
if (deleted.length > 0) {
|
|
89
|
+
console.error(`[SessionManager] Auto-cleanup: removed ${deleted.length} inactive session(s)`);
|
|
90
|
+
}
|
|
91
|
+
this.lastCleanupTime = Date.now();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error('[SessionManager] Auto-cleanup error:', error);
|
|
95
|
+
}
|
|
96
|
+
}, this.config.cleanupInterval);
|
|
97
|
+
// Don't prevent process exit
|
|
98
|
+
this.cleanupTimer.unref();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Stop automatic cleanup
|
|
102
|
+
*/
|
|
103
|
+
stopAutoCleanup() {
|
|
104
|
+
if (this.cleanupTimer) {
|
|
105
|
+
clearInterval(this.cleanupTimer);
|
|
106
|
+
this.cleanupTimer = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get session manager statistics
|
|
111
|
+
*/
|
|
112
|
+
getStats() {
|
|
113
|
+
let totalTargets = 0;
|
|
114
|
+
let totalWorkers = 0;
|
|
115
|
+
for (const session of this.sessions.values()) {
|
|
116
|
+
totalWorkers += session.workers.size;
|
|
117
|
+
for (const worker of session.workers.values()) {
|
|
118
|
+
totalTargets += worker.targets.size;
|
|
119
|
+
}
|
|
120
|
+
// Also count legacy targets
|
|
121
|
+
totalTargets += session.targets.size;
|
|
122
|
+
}
|
|
123
|
+
const stats = {
|
|
124
|
+
activeSessions: this.sessions.size,
|
|
125
|
+
totalTargets,
|
|
126
|
+
totalWorkers,
|
|
127
|
+
totalSessionsCreated: this.totalSessionsCreated,
|
|
128
|
+
totalSessionsCleaned: this.totalSessionsCleaned,
|
|
129
|
+
uptime: Date.now() - this.startTime,
|
|
130
|
+
lastCleanup: this.lastCleanupTime,
|
|
131
|
+
memoryUsage: process.memoryUsage().heapUsed,
|
|
132
|
+
};
|
|
133
|
+
if (this.connectionPool) {
|
|
134
|
+
stats.connectionPool = this.connectionPool.getStats();
|
|
135
|
+
}
|
|
136
|
+
return stats;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get current configuration
|
|
140
|
+
*/
|
|
141
|
+
getConfig() {
|
|
142
|
+
return { ...this.config };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Update configuration
|
|
146
|
+
*/
|
|
147
|
+
updateConfig(config) {
|
|
148
|
+
this.config = { ...this.config, ...config };
|
|
149
|
+
// Restart cleanup timer if interval changed
|
|
150
|
+
if (config.cleanupInterval !== undefined || config.autoCleanup !== undefined) {
|
|
151
|
+
this.stopAutoCleanup();
|
|
152
|
+
if (this.config.autoCleanup) {
|
|
153
|
+
this.startAutoCleanup();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Ensure connected to Chrome
|
|
159
|
+
*/
|
|
160
|
+
async ensureConnected() {
|
|
161
|
+
if (!this.cdpClient.isConnected()) {
|
|
162
|
+
await this.cdpClient.connect();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ==================== SESSION MANAGEMENT ====================
|
|
166
|
+
/**
|
|
167
|
+
* Create a new session with a default worker
|
|
168
|
+
*/
|
|
169
|
+
async createSession(options = {}) {
|
|
170
|
+
await this.ensureConnected();
|
|
171
|
+
const id = options.id || crypto.randomUUID();
|
|
172
|
+
if (this.sessions.has(id)) {
|
|
173
|
+
return this.sessions.get(id);
|
|
174
|
+
}
|
|
175
|
+
// Check max sessions limit
|
|
176
|
+
if (this.sessions.size >= this.config.maxSessions) {
|
|
177
|
+
const deleted = await this.cleanupInactiveSessions(this.config.sessionTTL);
|
|
178
|
+
if (deleted.length === 0 && this.sessions.size >= this.config.maxSessions) {
|
|
179
|
+
throw new Error(`Maximum session limit (${this.config.maxSessions}) reached.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const name = options.name || `Session ${id.slice(0, 8)}`;
|
|
183
|
+
const defaultWorkerId = 'default';
|
|
184
|
+
// Create default worker - use default context if configured (shares Chrome profile's cookies)
|
|
185
|
+
// or create isolated browser context for session isolation
|
|
186
|
+
const defaultContext = this.config.useDefaultContext
|
|
187
|
+
? null // null means use default browser context (shares cookies with Chrome profile)
|
|
188
|
+
: await this.cdpClient.createBrowserContext();
|
|
189
|
+
const defaultWorker = {
|
|
190
|
+
id: defaultWorkerId,
|
|
191
|
+
name: 'Default Worker',
|
|
192
|
+
targets: new Set(),
|
|
193
|
+
context: defaultContext,
|
|
194
|
+
createdAt: Date.now(),
|
|
195
|
+
lastActivityAt: Date.now(),
|
|
196
|
+
};
|
|
197
|
+
const session = {
|
|
198
|
+
id,
|
|
199
|
+
workers: new Map([[defaultWorkerId, defaultWorker]]),
|
|
200
|
+
defaultWorkerId,
|
|
201
|
+
targets: new Set(), // Legacy support
|
|
202
|
+
createdAt: Date.now(),
|
|
203
|
+
lastActivityAt: Date.now(),
|
|
204
|
+
name,
|
|
205
|
+
context: defaultContext, // Legacy support
|
|
206
|
+
};
|
|
207
|
+
this.sessions.set(id, session);
|
|
208
|
+
this.totalSessionsCreated++;
|
|
209
|
+
this.emitEvent({ type: 'session:created', sessionId: id, timestamp: Date.now() });
|
|
210
|
+
console.error(`[SessionManager] Created session ${id} with default worker`);
|
|
211
|
+
return session;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get or create a session
|
|
215
|
+
*/
|
|
216
|
+
async getOrCreateSession(sessionId) {
|
|
217
|
+
let session = this.sessions.get(sessionId);
|
|
218
|
+
if (!session) {
|
|
219
|
+
session = await this.createSession({ id: sessionId });
|
|
220
|
+
}
|
|
221
|
+
return session;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get an existing session
|
|
225
|
+
*/
|
|
226
|
+
getSession(sessionId) {
|
|
227
|
+
return this.sessions.get(sessionId);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Update last activity timestamp
|
|
231
|
+
*/
|
|
232
|
+
touchSession(sessionId) {
|
|
233
|
+
const session = this.sessions.get(sessionId);
|
|
234
|
+
if (session) {
|
|
235
|
+
session.lastActivityAt = Date.now();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Delete a session and clean up all workers
|
|
240
|
+
*/
|
|
241
|
+
async deleteSession(sessionId) {
|
|
242
|
+
const session = this.sessions.get(sessionId);
|
|
243
|
+
if (!session) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Delete all workers
|
|
247
|
+
for (const workerId of session.workers.keys()) {
|
|
248
|
+
await this.deleteWorkerInternal(session, workerId);
|
|
249
|
+
}
|
|
250
|
+
// Clean up all worker queues
|
|
251
|
+
for (const workerId of session.workers.keys()) {
|
|
252
|
+
this.queueManager.deleteQueue(`${sessionId}:${workerId}`);
|
|
253
|
+
}
|
|
254
|
+
this.queueManager.deleteQueue(sessionId);
|
|
255
|
+
// Clean up ref IDs
|
|
256
|
+
(0, ref_id_manager_1.getRefIdManager)().clearSessionRefs(sessionId);
|
|
257
|
+
// Remove session
|
|
258
|
+
this.sessions.delete(sessionId);
|
|
259
|
+
this.emitEvent({ type: 'session:deleted', sessionId, timestamp: Date.now() });
|
|
260
|
+
console.error(`[SessionManager] Deleted session ${sessionId}`);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Clean up inactive sessions
|
|
264
|
+
*/
|
|
265
|
+
async cleanupInactiveSessions(maxAgeMs) {
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
const deletedSessions = [];
|
|
268
|
+
for (const [sessionId, session] of this.sessions) {
|
|
269
|
+
if (now - session.lastActivityAt > maxAgeMs) {
|
|
270
|
+
await this.deleteSession(sessionId);
|
|
271
|
+
deletedSessions.push(sessionId);
|
|
272
|
+
this.totalSessionsCleaned++;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Trigger browser-level GC after bulk cleanup
|
|
276
|
+
if (deletedSessions.length > 0) {
|
|
277
|
+
try {
|
|
278
|
+
const pages = await this.cdpClient.getPages();
|
|
279
|
+
if (pages.length > 0) {
|
|
280
|
+
await this.cdpClient.triggerGC(pages[0]);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Best-effort GC
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return deletedSessions;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Force cleanup all sessions
|
|
291
|
+
*/
|
|
292
|
+
async cleanupAllSessions() {
|
|
293
|
+
const count = this.sessions.size;
|
|
294
|
+
const sessionIds = Array.from(this.sessions.keys());
|
|
295
|
+
for (const sessionId of sessionIds) {
|
|
296
|
+
await this.deleteSession(sessionId);
|
|
297
|
+
this.totalSessionsCleaned++;
|
|
298
|
+
}
|
|
299
|
+
// Clean up Chrome pool and factory connections
|
|
300
|
+
if (this.chromePool) {
|
|
301
|
+
await this.chromePool.cleanup();
|
|
302
|
+
}
|
|
303
|
+
await this.cdpFactory.disconnectAll();
|
|
304
|
+
return count;
|
|
305
|
+
}
|
|
306
|
+
// ==================== WORKER MANAGEMENT ====================
|
|
307
|
+
/**
|
|
308
|
+
* Create a new worker within a session
|
|
309
|
+
* Each worker has its own isolated browser context (cookies, localStorage, etc.)
|
|
310
|
+
*/
|
|
311
|
+
async createWorker(sessionId, options = {}) {
|
|
312
|
+
await this.ensureConnected();
|
|
313
|
+
const session = await this.getOrCreateSession(sessionId);
|
|
314
|
+
// Check max workers limit
|
|
315
|
+
if (session.workers.size >= this.config.maxWorkersPerSession) {
|
|
316
|
+
throw new Error(`Maximum workers per session (${this.config.maxWorkersPerSession}) reached.`);
|
|
317
|
+
}
|
|
318
|
+
const workerId = options.id || `worker-${crypto.randomUUID().slice(0, 8)}`;
|
|
319
|
+
if (session.workers.has(workerId)) {
|
|
320
|
+
return session.workers.get(workerId);
|
|
321
|
+
}
|
|
322
|
+
const name = options.name || `Worker ${workerId}`;
|
|
323
|
+
// Create browser context: shared (null = copies cookies from Chrome profile) or isolated
|
|
324
|
+
const context = options.shareCookies
|
|
325
|
+
? null
|
|
326
|
+
: await this.cdpClient.createBrowserContext();
|
|
327
|
+
// If pool is enabled and targetUrl provided, acquire a separate Chrome instance
|
|
328
|
+
let workerPort;
|
|
329
|
+
let workerPoolOrigin;
|
|
330
|
+
if (this.chromePool && options.targetUrl) {
|
|
331
|
+
try {
|
|
332
|
+
const origin = new URL(options.targetUrl).origin;
|
|
333
|
+
const poolInstance = await this.chromePool.acquireInstance(origin);
|
|
334
|
+
workerPort = poolInstance.port;
|
|
335
|
+
workerPoolOrigin = origin;
|
|
336
|
+
// Ensure CDPClient for this port is connected
|
|
337
|
+
const workerCdpClient = this.cdpFactory.getOrCreate(workerPort, {
|
|
338
|
+
autoLaunch: (0, global_1.getGlobalConfig)().autoLaunch,
|
|
339
|
+
});
|
|
340
|
+
if (!workerCdpClient.isConnected()) {
|
|
341
|
+
await workerCdpClient.connect();
|
|
342
|
+
}
|
|
343
|
+
console.error(`[SessionManager] Worker ${workerId} assigned to Chrome instance on port ${workerPort} for origin ${origin}`);
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
console.error(`[SessionManager] Pool acquisition failed, falling back to default:`, err);
|
|
347
|
+
workerPort = undefined;
|
|
348
|
+
workerPoolOrigin = undefined;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const worker = {
|
|
352
|
+
id: workerId,
|
|
353
|
+
name,
|
|
354
|
+
targets: new Set(),
|
|
355
|
+
context,
|
|
356
|
+
createdAt: Date.now(),
|
|
357
|
+
lastActivityAt: Date.now(),
|
|
358
|
+
port: workerPort,
|
|
359
|
+
poolOrigin: workerPoolOrigin,
|
|
360
|
+
};
|
|
361
|
+
session.workers.set(workerId, worker);
|
|
362
|
+
this.touchSession(sessionId);
|
|
363
|
+
this.emitEvent({
|
|
364
|
+
type: 'worker:created',
|
|
365
|
+
sessionId,
|
|
366
|
+
workerId,
|
|
367
|
+
timestamp: Date.now(),
|
|
368
|
+
});
|
|
369
|
+
console.error(`[SessionManager] Created worker ${workerId} in session ${sessionId}`);
|
|
370
|
+
return worker;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Get a worker by ID
|
|
374
|
+
*/
|
|
375
|
+
getWorker(sessionId, workerId) {
|
|
376
|
+
const session = this.sessions.get(sessionId);
|
|
377
|
+
if (!session)
|
|
378
|
+
return undefined;
|
|
379
|
+
return session.workers.get(workerId);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get or create a worker
|
|
383
|
+
*/
|
|
384
|
+
async getOrCreateWorker(sessionId, workerId) {
|
|
385
|
+
const session = await this.getOrCreateSession(sessionId);
|
|
386
|
+
// If no workerId specified, use default worker
|
|
387
|
+
const targetWorkerId = workerId || session.defaultWorkerId;
|
|
388
|
+
let worker = session.workers.get(targetWorkerId);
|
|
389
|
+
if (!worker) {
|
|
390
|
+
worker = await this.createWorker(sessionId, { id: targetWorkerId });
|
|
391
|
+
}
|
|
392
|
+
return worker;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* List all workers in a session
|
|
396
|
+
*/
|
|
397
|
+
getWorkers(sessionId) {
|
|
398
|
+
const session = this.sessions.get(sessionId);
|
|
399
|
+
if (!session)
|
|
400
|
+
return [];
|
|
401
|
+
const workers = [];
|
|
402
|
+
for (const worker of session.workers.values()) {
|
|
403
|
+
workers.push({
|
|
404
|
+
id: worker.id,
|
|
405
|
+
name: worker.name,
|
|
406
|
+
targetCount: worker.targets.size,
|
|
407
|
+
createdAt: worker.createdAt,
|
|
408
|
+
lastActivityAt: worker.lastActivityAt,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return workers;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Delete a worker and its resources
|
|
415
|
+
*/
|
|
416
|
+
async deleteWorker(sessionId, workerId) {
|
|
417
|
+
const session = this.sessions.get(sessionId);
|
|
418
|
+
if (!session)
|
|
419
|
+
return;
|
|
420
|
+
// Can't delete default worker
|
|
421
|
+
if (workerId === session.defaultWorkerId) {
|
|
422
|
+
throw new Error('Cannot delete the default worker. Delete the session instead.');
|
|
423
|
+
}
|
|
424
|
+
await this.deleteWorkerInternal(session, workerId);
|
|
425
|
+
this.emitEvent({
|
|
426
|
+
type: 'worker:deleted',
|
|
427
|
+
sessionId,
|
|
428
|
+
workerId,
|
|
429
|
+
timestamp: Date.now(),
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Internal worker deletion (also used for cleanup)
|
|
434
|
+
*/
|
|
435
|
+
async deleteWorkerInternal(session, workerId) {
|
|
436
|
+
const worker = session.workers.get(workerId);
|
|
437
|
+
if (!worker)
|
|
438
|
+
return;
|
|
439
|
+
// Determine which CDPClient to use for this worker
|
|
440
|
+
const workerCdpClient = worker.port
|
|
441
|
+
? (this.cdpFactory.get(worker.port) || this.cdpClient)
|
|
442
|
+
: this.cdpClient;
|
|
443
|
+
// Close all pages in this worker (return to pool if available)
|
|
444
|
+
for (const targetId of worker.targets) {
|
|
445
|
+
try {
|
|
446
|
+
if (this.connectionPool && this.config.useConnectionPool) {
|
|
447
|
+
const page = await workerCdpClient.getPageByTargetId(targetId);
|
|
448
|
+
if (page && !page.isClosed()) {
|
|
449
|
+
await this.connectionPool.releasePage(page);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
await workerCdpClient.closePage(targetId);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
await workerCdpClient.closePage(targetId);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
// Page might already be closed
|
|
461
|
+
}
|
|
462
|
+
this.targetToWorker.delete(targetId);
|
|
463
|
+
}
|
|
464
|
+
// Close the browser context (only if it's an isolated context, not the default)
|
|
465
|
+
if (worker.context) {
|
|
466
|
+
try {
|
|
467
|
+
await workerCdpClient.closeBrowserContext(worker.context);
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
// Context might already be closed
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// Release Chrome pool instance if worker had one
|
|
474
|
+
if (worker.port && worker.poolOrigin && this.chromePool) {
|
|
475
|
+
this.chromePool.releaseInstance(worker.port, worker.poolOrigin);
|
|
476
|
+
console.error(`[SessionManager] Released pool instance port ${worker.port} for origin ${worker.poolOrigin}`);
|
|
477
|
+
}
|
|
478
|
+
// Clean up ref IDs for this worker
|
|
479
|
+
for (const targetId of worker.targets) {
|
|
480
|
+
(0, ref_id_manager_1.getRefIdManager)().clearTargetRefs(session.id, targetId);
|
|
481
|
+
}
|
|
482
|
+
session.workers.delete(workerId);
|
|
483
|
+
console.error(`[SessionManager] Deleted worker ${workerId} from session ${session.id}`);
|
|
484
|
+
}
|
|
485
|
+
// ==================== TARGET/PAGE MANAGEMENT ====================
|
|
486
|
+
/**
|
|
487
|
+
* Create a new page/target for a worker
|
|
488
|
+
* @param sessionId Session ID
|
|
489
|
+
* @param url Optional URL to navigate to
|
|
490
|
+
* @param workerId Optional worker ID (uses default worker if not specified)
|
|
491
|
+
*/
|
|
492
|
+
async createTarget(sessionId, url, workerId) {
|
|
493
|
+
await this.ensureConnected();
|
|
494
|
+
const worker = await this.getOrCreateWorker(sessionId, workerId);
|
|
495
|
+
// Create page — try connection pool first for pre-warmed pages, fall back to direct creation
|
|
496
|
+
const cdpClient = this.getCDPClientForWorker(sessionId, worker.id);
|
|
497
|
+
let page;
|
|
498
|
+
if (this.connectionPool && this.config.useConnectionPool) {
|
|
499
|
+
try {
|
|
500
|
+
const poolPage = await this.connectionPool.acquirePage();
|
|
501
|
+
// Navigate the pre-warmed page to the target URL
|
|
502
|
+
if (url) {
|
|
503
|
+
await poolPage.goto(url, { waitUntil: 'domcontentloaded' });
|
|
504
|
+
}
|
|
505
|
+
// Copy cookies from the worker's browser context if available
|
|
506
|
+
// (pool pages start blank — replicate what cdpClient.createPage() does for contexts)
|
|
507
|
+
if (worker.context) {
|
|
508
|
+
try {
|
|
509
|
+
const cookies = await worker.context.cookies();
|
|
510
|
+
if (cookies.length > 0) {
|
|
511
|
+
await poolPage.setCookie(...cookies);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
// Best-effort cookie copy
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
page = poolPage;
|
|
519
|
+
console.error(`[SessionManager] Acquired page from pool for session ${sessionId}`);
|
|
520
|
+
}
|
|
521
|
+
catch (err) {
|
|
522
|
+
console.error(`[SessionManager] Pool acquire failed, falling back to direct creation:`, err);
|
|
523
|
+
page = await cdpClient.createPage(url, worker.context);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
page = await cdpClient.createPage(url, worker.context);
|
|
528
|
+
}
|
|
529
|
+
const targetId = getTargetId(page.target());
|
|
530
|
+
worker.targets.add(targetId);
|
|
531
|
+
worker.lastActivityAt = Date.now();
|
|
532
|
+
this.targetToWorker.set(targetId, { sessionId, workerId: worker.id });
|
|
533
|
+
this.emitEvent({
|
|
534
|
+
type: 'session:target-added',
|
|
535
|
+
sessionId,
|
|
536
|
+
workerId: worker.id,
|
|
537
|
+
targetId,
|
|
538
|
+
timestamp: Date.now(),
|
|
539
|
+
});
|
|
540
|
+
this.touchSession(sessionId);
|
|
541
|
+
return { targetId, page, workerId: worker.id };
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Register a pre-acquired page as a target for a worker.
|
|
545
|
+
* Used by workflow engine when pages are batch-acquired from the pool
|
|
546
|
+
* to avoid per-page replenishment (about:blank proliferation fix).
|
|
547
|
+
*/
|
|
548
|
+
registerExistingTarget(sessionId, workerId, targetId) {
|
|
549
|
+
const session = this.sessions.get(sessionId);
|
|
550
|
+
if (!session) {
|
|
551
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
552
|
+
}
|
|
553
|
+
const worker = session.workers.get(workerId);
|
|
554
|
+
if (!worker) {
|
|
555
|
+
throw new Error(`Worker ${workerId} not found in session ${sessionId}`);
|
|
556
|
+
}
|
|
557
|
+
worker.targets.add(targetId);
|
|
558
|
+
worker.lastActivityAt = Date.now();
|
|
559
|
+
this.targetToWorker.set(targetId, { sessionId, workerId });
|
|
560
|
+
this.emitEvent({
|
|
561
|
+
type: 'session:target-added',
|
|
562
|
+
sessionId,
|
|
563
|
+
workerId,
|
|
564
|
+
targetId,
|
|
565
|
+
timestamp: Date.now(),
|
|
566
|
+
});
|
|
567
|
+
this.touchSession(sessionId);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Check if a target is still valid (page not closed)
|
|
571
|
+
*/
|
|
572
|
+
async isTargetValid(targetId) {
|
|
573
|
+
try {
|
|
574
|
+
const page = await this.cdpClient.getPageByTargetId(targetId);
|
|
575
|
+
return page !== null && !page.isClosed();
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Get page for a target
|
|
583
|
+
* @param sessionId Session ID
|
|
584
|
+
* @param targetId Target/Tab ID
|
|
585
|
+
* @param workerId Optional worker ID for validation
|
|
586
|
+
* @param toolName Optional MCP tool name for hybrid BrowserRouter routing
|
|
587
|
+
*/
|
|
588
|
+
async getPage(sessionId, targetId, workerId, toolName) {
|
|
589
|
+
const ownerInfo = this.targetToWorker.get(targetId);
|
|
590
|
+
if (!ownerInfo || ownerInfo.sessionId !== sessionId) {
|
|
591
|
+
throw new Error(`Target ${targetId} does not belong to session ${sessionId}`);
|
|
592
|
+
}
|
|
593
|
+
if (workerId && ownerInfo.workerId !== workerId) {
|
|
594
|
+
throw new Error(`Target ${targetId} does not belong to worker ${workerId}`);
|
|
595
|
+
}
|
|
596
|
+
const cdpClient = this.getCDPClientForWorker(sessionId, ownerInfo.workerId);
|
|
597
|
+
// Validate target is still valid
|
|
598
|
+
try {
|
|
599
|
+
const page = await cdpClient.getPageByTargetId(targetId);
|
|
600
|
+
if (!page || page.isClosed()) {
|
|
601
|
+
this.onTargetClosed(targetId);
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
// Route through BrowserRouter if hybrid mode is active and toolName provided
|
|
605
|
+
if (this.browserRouter && toolName) {
|
|
606
|
+
const result = await this.browserRouter.route(toolName, page);
|
|
607
|
+
return result.page;
|
|
608
|
+
}
|
|
609
|
+
return page;
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
this.onTargetClosed(targetId);
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Get all pages for a worker
|
|
618
|
+
*/
|
|
619
|
+
async getWorkerPages(sessionId, workerId) {
|
|
620
|
+
const worker = this.getWorker(sessionId, workerId);
|
|
621
|
+
if (!worker)
|
|
622
|
+
return [];
|
|
623
|
+
const cdpClient = this.getCDPClientForWorker(sessionId, workerId);
|
|
624
|
+
const pages = [];
|
|
625
|
+
for (const targetId of worker.targets) {
|
|
626
|
+
const page = await cdpClient.getPageByTargetId(targetId);
|
|
627
|
+
if (page) {
|
|
628
|
+
pages.push(page);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return pages;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Get target IDs for a session (all workers)
|
|
635
|
+
*/
|
|
636
|
+
getSessionTargetIds(sessionId) {
|
|
637
|
+
const session = this.sessions.get(sessionId);
|
|
638
|
+
if (!session)
|
|
639
|
+
return [];
|
|
640
|
+
const allTargets = [];
|
|
641
|
+
for (const worker of session.workers.values()) {
|
|
642
|
+
allTargets.push(...worker.targets);
|
|
643
|
+
}
|
|
644
|
+
return allTargets;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Get target IDs for a specific worker
|
|
648
|
+
*/
|
|
649
|
+
getWorkerTargetIds(sessionId, workerId) {
|
|
650
|
+
const worker = this.getWorker(sessionId, workerId);
|
|
651
|
+
if (!worker)
|
|
652
|
+
return [];
|
|
653
|
+
return Array.from(worker.targets);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Validate target ownership (legacy method, checks session only)
|
|
657
|
+
*/
|
|
658
|
+
validateTargetOwnership(sessionId, targetId) {
|
|
659
|
+
const ownerInfo = this.targetToWorker.get(targetId);
|
|
660
|
+
return ownerInfo?.sessionId === sessionId;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Get the worker ID that owns a target
|
|
664
|
+
*/
|
|
665
|
+
getTargetWorkerId(targetId) {
|
|
666
|
+
return this.targetToWorker.get(targetId)?.workerId;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Close a specific target/tab
|
|
670
|
+
* @param sessionId Session ID
|
|
671
|
+
* @param targetId Target/Tab ID to close
|
|
672
|
+
* @returns true if closed, false if not found
|
|
673
|
+
*/
|
|
674
|
+
async closeTarget(sessionId, targetId) {
|
|
675
|
+
const ownerInfo = this.targetToWorker.get(targetId);
|
|
676
|
+
if (!ownerInfo || ownerInfo.sessionId !== sessionId) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
try {
|
|
680
|
+
// Close the page via CDP (use worker's CDPClient if on pool)
|
|
681
|
+
const cdpClient = this.getCDPClientForWorker(sessionId, ownerInfo.workerId);
|
|
682
|
+
if (this.connectionPool && this.config.useConnectionPool) {
|
|
683
|
+
// Return the page to the pool for reuse instead of destroying it
|
|
684
|
+
try {
|
|
685
|
+
const page = await cdpClient.getPageByTargetId(targetId);
|
|
686
|
+
if (page && !page.isClosed()) {
|
|
687
|
+
await this.connectionPool.releasePage(page);
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
await cdpClient.closePage(targetId);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch {
|
|
694
|
+
// If pool release fails, fall back to direct close
|
|
695
|
+
await cdpClient.closePage(targetId);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
// closePage() already triggers GC internally before closing
|
|
700
|
+
await cdpClient.closePage(targetId);
|
|
701
|
+
}
|
|
702
|
+
// Clean up internal state
|
|
703
|
+
const session = this.sessions.get(sessionId);
|
|
704
|
+
if (session) {
|
|
705
|
+
const worker = session.workers.get(ownerInfo.workerId);
|
|
706
|
+
if (worker) {
|
|
707
|
+
worker.targets.delete(targetId);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// Clean up ref IDs
|
|
711
|
+
(0, ref_id_manager_1.getRefIdManager)().clearTargetRefs(sessionId, targetId);
|
|
712
|
+
// Remove from mapping
|
|
713
|
+
this.targetToWorker.delete(targetId);
|
|
714
|
+
this.emitEvent({
|
|
715
|
+
type: 'session:target-closed',
|
|
716
|
+
sessionId,
|
|
717
|
+
workerId: ownerInfo.workerId,
|
|
718
|
+
targetId,
|
|
719
|
+
timestamp: Date.now(),
|
|
720
|
+
});
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
// Page might already be closed
|
|
725
|
+
this.onTargetClosed(targetId);
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Close all tabs in a worker (without deleting the worker)
|
|
731
|
+
* @param sessionId Session ID
|
|
732
|
+
* @param workerId Worker ID
|
|
733
|
+
* @returns Number of tabs closed
|
|
734
|
+
*/
|
|
735
|
+
async closeWorkerTabs(sessionId, workerId) {
|
|
736
|
+
const worker = this.getWorker(sessionId, workerId);
|
|
737
|
+
if (!worker)
|
|
738
|
+
return 0;
|
|
739
|
+
const targetIds = Array.from(worker.targets);
|
|
740
|
+
let closedCount = 0;
|
|
741
|
+
for (const targetId of targetIds) {
|
|
742
|
+
if (await this.closeTarget(sessionId, targetId)) {
|
|
743
|
+
closedCount++;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return closedCount;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Execute a CDP command through the session's queue
|
|
750
|
+
*/
|
|
751
|
+
async executeCDP(sessionId, targetId, method, params) {
|
|
752
|
+
if (!this.validateTargetOwnership(sessionId, targetId)) {
|
|
753
|
+
throw new Error(`Target ${targetId} does not belong to session ${sessionId}`);
|
|
754
|
+
}
|
|
755
|
+
this.touchSession(sessionId);
|
|
756
|
+
const ownerInfo = this.targetToWorker.get(targetId);
|
|
757
|
+
const cdpClient = ownerInfo
|
|
758
|
+
? this.getCDPClientForWorker(sessionId, ownerInfo.workerId)
|
|
759
|
+
: this.cdpClient;
|
|
760
|
+
const workerQueueKey = ownerInfo ? `${sessionId}:${ownerInfo.workerId}` : sessionId;
|
|
761
|
+
return this.queueManager.enqueue(workerQueueKey, async () => {
|
|
762
|
+
const page = await cdpClient.getPageByTargetId(targetId);
|
|
763
|
+
if (!page) {
|
|
764
|
+
throw new Error(`Page not found for target ${targetId}`);
|
|
765
|
+
}
|
|
766
|
+
return cdpClient.send(page, method, params);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Handle target closed event
|
|
771
|
+
*/
|
|
772
|
+
onTargetClosed(targetId) {
|
|
773
|
+
const ownerInfo = this.targetToWorker.get(targetId);
|
|
774
|
+
if (ownerInfo) {
|
|
775
|
+
const session = this.sessions.get(ownerInfo.sessionId);
|
|
776
|
+
if (session) {
|
|
777
|
+
const worker = session.workers.get(ownerInfo.workerId);
|
|
778
|
+
if (worker) {
|
|
779
|
+
worker.targets.delete(targetId);
|
|
780
|
+
}
|
|
781
|
+
this.targetToWorker.delete(targetId);
|
|
782
|
+
this.emitEvent({
|
|
783
|
+
type: 'session:target-removed',
|
|
784
|
+
sessionId: ownerInfo.sessionId,
|
|
785
|
+
workerId: ownerInfo.workerId,
|
|
786
|
+
targetId,
|
|
787
|
+
timestamp: Date.now(),
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// ==================== SESSION INFO ====================
|
|
793
|
+
/**
|
|
794
|
+
* Get session info (for serialization)
|
|
795
|
+
*/
|
|
796
|
+
getSessionInfo(sessionId) {
|
|
797
|
+
const session = this.sessions.get(sessionId);
|
|
798
|
+
if (!session)
|
|
799
|
+
return undefined;
|
|
800
|
+
let totalTargets = 0;
|
|
801
|
+
const workers = [];
|
|
802
|
+
for (const worker of session.workers.values()) {
|
|
803
|
+
totalTargets += worker.targets.size;
|
|
804
|
+
workers.push({
|
|
805
|
+
id: worker.id,
|
|
806
|
+
name: worker.name,
|
|
807
|
+
targetCount: worker.targets.size,
|
|
808
|
+
createdAt: worker.createdAt,
|
|
809
|
+
lastActivityAt: worker.lastActivityAt,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
return {
|
|
813
|
+
id: session.id,
|
|
814
|
+
targetCount: totalTargets,
|
|
815
|
+
workerCount: session.workers.size,
|
|
816
|
+
workers,
|
|
817
|
+
createdAt: session.createdAt,
|
|
818
|
+
lastActivityAt: session.lastActivityAt,
|
|
819
|
+
name: session.name,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Get all session infos
|
|
824
|
+
*/
|
|
825
|
+
getAllSessionInfos() {
|
|
826
|
+
const infos = [];
|
|
827
|
+
for (const sessionId of this.sessions.keys()) {
|
|
828
|
+
const info = this.getSessionInfo(sessionId);
|
|
829
|
+
if (info) {
|
|
830
|
+
infos.push(info);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return infos;
|
|
834
|
+
}
|
|
835
|
+
// ==================== EVENT HANDLING ====================
|
|
836
|
+
/**
|
|
837
|
+
* Add event listener
|
|
838
|
+
*/
|
|
839
|
+
addEventListener(listener) {
|
|
840
|
+
this.eventListeners.push(listener);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Remove event listener
|
|
844
|
+
*/
|
|
845
|
+
removeEventListener(listener) {
|
|
846
|
+
const index = this.eventListeners.indexOf(listener);
|
|
847
|
+
if (index !== -1) {
|
|
848
|
+
this.eventListeners.splice(index, 1);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Emit event to all listeners
|
|
853
|
+
*/
|
|
854
|
+
emitEvent(event) {
|
|
855
|
+
for (const listener of this.eventListeners) {
|
|
856
|
+
try {
|
|
857
|
+
listener(event);
|
|
858
|
+
}
|
|
859
|
+
catch (e) {
|
|
860
|
+
console.error('Session event listener error:', e);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Get the number of active sessions
|
|
866
|
+
*/
|
|
867
|
+
get sessionCount() {
|
|
868
|
+
return this.sessions.size;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Get CDPClient
|
|
872
|
+
*/
|
|
873
|
+
getCDPClient() {
|
|
874
|
+
return this.cdpClient;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Initialize hybrid mode with BrowserRouter
|
|
878
|
+
*/
|
|
879
|
+
async initHybrid(config) {
|
|
880
|
+
if (this.browserRouter)
|
|
881
|
+
return; // Already initialized
|
|
882
|
+
this.browserRouter = new router_1.BrowserRouter(config);
|
|
883
|
+
await this.browserRouter.initialize();
|
|
884
|
+
console.error('[SessionManager] Hybrid mode initialized');
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Get the BrowserRouter (for stats/escalation)
|
|
888
|
+
*/
|
|
889
|
+
getBrowserRouter() {
|
|
890
|
+
return this.browserRouter;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Cleanup hybrid mode
|
|
894
|
+
*/
|
|
895
|
+
async cleanupHybrid() {
|
|
896
|
+
if (this.browserRouter) {
|
|
897
|
+
await this.browserRouter.cleanup();
|
|
898
|
+
this.browserRouter = null;
|
|
899
|
+
console.error('[SessionManager] Hybrid mode cleaned up');
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
exports.SessionManager = SessionManager;
|
|
904
|
+
// Singleton instance
|
|
905
|
+
let sessionManagerInstance = null;
|
|
906
|
+
function getSessionManager() {
|
|
907
|
+
if (!sessionManagerInstance) {
|
|
908
|
+
sessionManagerInstance = new SessionManager();
|
|
909
|
+
}
|
|
910
|
+
return sessionManagerInstance;
|
|
911
|
+
}
|
|
912
|
+
//# sourceMappingURL=session-manager.js.map
|