@warlock.js/core 5.32.0 → 5.34.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/CHANGELOG.md +64 -46
- package/esm/application/application-config-types.d.mts +8 -0
- package/esm/application/application-config-types.d.mts.map +1 -1
- package/esm/application/boot-signal.d.mts +8 -8
- package/esm/application/boot-signal.d.mts.map +1 -1
- package/esm/application/boot-signal.mjs +15 -9
- package/esm/application/boot-signal.mjs.map +1 -1
- package/esm/cli/commands/ai/ai-command.action.mjs +140 -0
- package/esm/cli/commands/ai/ai-command.action.mjs.map +1 -0
- package/esm/cli/commands/ai.command.mjs +50 -0
- package/esm/cli/commands/ai.command.mjs.map +1 -0
- package/esm/cli/commands/check/check-command.action.mjs +4 -0
- package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
- package/esm/cli/commands/check.command.mjs +6 -1
- package/esm/cli/commands/check.command.mjs.map +1 -1
- package/esm/cli/commands/e2e.command.mjs +63 -0
- package/esm/cli/commands/e2e.command.mjs.map +1 -0
- package/esm/cli/commands/load-e2e-spec.mjs +47 -0
- package/esm/cli/commands/load-e2e-spec.mjs.map +1 -0
- package/esm/cli/commands/pm2/pm2-command.action.mjs +91 -0
- package/esm/cli/commands/pm2/pm2-command.action.mjs.map +1 -0
- package/esm/cli/commands/pm2/pm2-config.mjs +115 -0
- package/esm/cli/commands/pm2/pm2-config.mjs.map +1 -0
- package/esm/cli/commands/pm2.command.mjs +53 -0
- package/esm/cli/commands/pm2.command.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +6 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs +1 -4
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/shutdown-timeout.mjs +7 -0
- package/esm/connectors/shutdown-timeout.mjs.map +1 -0
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs +8 -1
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +1 -1
- package/esm/dev-server/health-checker/default-health-checkers.mjs +2 -16
- package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +1 -2
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.d.mts +1 -1
- package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
- package/esm/e2e/browser.d.mts +4 -0
- package/esm/e2e/browser.d.mts.map +1 -0
- package/esm/e2e/browser.mjs +88 -0
- package/esm/e2e/browser.mjs.map +1 -0
- package/esm/e2e/cdp.d.mts +152 -0
- package/esm/e2e/cdp.d.mts.map +1 -0
- package/esm/e2e/cdp.mjs +106 -0
- package/esm/e2e/cdp.mjs.map +1 -0
- package/esm/e2e/fixture.mjs +195 -0
- package/esm/e2e/fixture.mjs.map +1 -0
- package/esm/e2e/index.d.mts +5 -0
- package/esm/e2e/index.mjs +6 -0
- package/esm/e2e/owned-process.mjs +193 -0
- package/esm/e2e/owned-process.mjs.map +1 -0
- package/esm/e2e/page.d.mts +64 -0
- package/esm/e2e/page.d.mts.map +1 -0
- package/esm/e2e/page.mjs +376 -0
- package/esm/e2e/page.mjs.map +1 -0
- package/esm/e2e/run-e2e.d.mts +17 -0
- package/esm/e2e/run-e2e.d.mts.map +1 -0
- package/esm/e2e/run-e2e.mjs +143 -0
- package/esm/e2e/run-e2e.mjs.map +1 -0
- package/esm/e2e/types.d.mts +123 -0
- package/esm/e2e/types.d.mts.map +1 -0
- package/esm/e2e/types.mjs +24 -0
- package/esm/e2e/types.mjs.map +1 -0
- package/esm/generations/features/index.mjs +2 -0
- package/esm/generations/features/index.mjs.map +1 -1
- package/esm/generations/features/process.feature.mjs +65 -0
- package/esm/generations/features/process.feature.mjs.map +1 -0
- package/esm/http/port-preflight.d.mts.map +1 -1
- package/esm/http/port-preflight.mjs +2 -0
- package/esm/http/port-preflight.mjs.map +1 -1
- package/esm/index.d.mts +7 -2
- package/esm/index.mjs +7 -2
- package/esm/secrets/errors.d.mts +9 -0
- package/esm/secrets/errors.d.mts.map +1 -0
- package/esm/secrets/errors.mjs +24 -0
- package/esm/secrets/errors.mjs.map +1 -0
- package/esm/secrets/file-backend.mjs +167 -0
- package/esm/secrets/file-backend.mjs.map +1 -0
- package/esm/secrets/index.d.mts +6 -0
- package/esm/secrets/index.mjs +6 -0
- package/esm/secrets/keyring-backend.mjs +50 -0
- package/esm/secrets/keyring-backend.mjs.map +1 -0
- package/esm/secrets/memory-backend.d.mts +12 -0
- package/esm/secrets/memory-backend.d.mts.map +1 -0
- package/esm/secrets/memory-backend.mjs +28 -0
- package/esm/secrets/memory-backend.mjs.map +1 -0
- package/esm/secrets/resolve-driver.mjs +14 -0
- package/esm/secrets/resolve-driver.mjs.map +1 -0
- package/esm/secrets/secret-store.d.mts +25 -0
- package/esm/secrets/secret-store.d.mts.map +1 -0
- package/esm/secrets/secret-store.mjs +120 -0
- package/esm/secrets/secret-store.mjs.map +1 -0
- package/esm/secrets/secrets.d.mts +11 -0
- package/esm/secrets/secrets.d.mts.map +1 -0
- package/esm/secrets/secrets.mjs +72 -0
- package/esm/secrets/secrets.mjs.map +1 -0
- package/esm/secrets/types.d.mts +26 -0
- package/esm/secrets/types.d.mts.map +1 -0
- package/esm/socket/index.d.mts +2 -2
- package/esm/socket/index.mjs +2 -2
- package/esm/socket/utils.d.mts +7 -0
- package/esm/socket/utils.d.mts.map +1 -1
- package/esm/socket/utils.mjs +12 -1
- package/esm/socket/utils.mjs.map +1 -1
- package/esm/tests/fakes/index.d.mts +2 -1
- package/esm/tests/fakes/index.mjs +2 -1
- package/esm/tests/fakes/secrets-fake.d.mts +13 -0
- package/esm/tests/fakes/secrets-fake.d.mts.map +1 -0
- package/esm/tests/fakes/secrets-fake.mjs +50 -0
- package/esm/tests/fakes/secrets-fake.mjs.map +1 -0
- package/esm/tests/fakes/test-fakes.d.mts +2 -2
- package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
- package/esm/tests/fakes/test-fakes.mjs +5 -2
- package/esm/tests/fakes/test-fakes.mjs.map +1 -1
- package/esm/tests/index.d.mts +2 -1
- package/esm/tests/index.mjs +2 -1
- package/esm/tests/vitest-setup.d.mts +1 -1
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/tooling/check/lint-step.d.mts +2 -8
- package/esm/tooling/check/lint-step.d.mts.map +1 -1
- package/esm/tooling/check/lint-step.mjs +1 -42
- package/esm/tooling/check/lint-step.mjs.map +1 -1
- package/esm/tooling/check/run-check.d.mts +2 -0
- package/esm/tooling/check/run-check.d.mts.map +1 -1
- package/esm/tooling/check/run-check.mjs +3 -1
- package/esm/tooling/check/run-check.mjs.map +1 -1
- package/esm/tooling/problem.d.mts +1 -1
- package/esm/tooling/problem.d.mts.map +1 -1
- package/esm/tooling/problem.mjs.map +1 -1
- package/esm/updater/package-manager.mjs +5 -1
- package/esm/updater/package-manager.mjs.map +1 -1
- package/esm/upgrade/codemods/index.mjs +3 -0
- package/esm/upgrade/codemods/index.mjs.map +1 -1
- package/esm/upgrade/codemods/oxc-toolchain.mjs +191 -0
- package/esm/upgrade/codemods/oxc-toolchain.mjs.map +1 -0
- package/esm/upgrade/codemods/oxlint-rule-schemas.mjs +6 -0
- package/esm/upgrade/codemods/oxlint-rule-schemas.mjs.map +1 -0
- package/esm/upgrade/codemods/port-eslint-rules.mjs +343 -0
- package/esm/upgrade/codemods/port-eslint-rules.mjs.map +1 -0
- package/esm/upgrade/codemods/toolchain-templates.mjs +244 -0
- package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -0
- package/esm/upgrade/codemods/validate-oxlint-rule.mjs +60 -0
- package/esm/upgrade/codemods/validate-oxlint-rule.mjs.map +1 -0
- package/esm/upgrade/notes.mjs +14 -7
- package/esm/upgrade/notes.mjs.map +1 -1
- package/esm/upgrade/run-upgrade.mjs +25 -6
- package/esm/upgrade/run-upgrade.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +1 -1
- package/llms-full.txt +538 -29
- package/llms.txt +5 -3
- package/package.json +29 -20
- package/skills/README.md +2 -1
- package/skills/create-module/SKILL.md +2 -2
- package/skills/index.md +1 -1
- package/skills/run-app/SKILL.md +115 -6
- package/skills/store-secrets/SKILL.md +141 -0
- package/skills/test-browser/SKILL.md +119 -0
- package/skills/test-fakes/SKILL.md +13 -11
- package/skills/upgrade-from-5-29/SKILL.md +85 -7
- package/skills/warlock-check/SKILL.md +7 -3
- package/skills/warlock-doctor/SKILL.md +8 -0
- package/skills/wire-socket/SKILL.md +19 -0
- package/skills/write-cli-command/SKILL.md +21 -0
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +0 -154
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +0 -1
- package/esm/dev-server/health-checker/workers/eslint-health.worker.d.mts +0 -1
- package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +0 -192
- package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +0 -1
|
@@ -68,7 +68,14 @@ var SocketConnector = class extends BaseConnector {
|
|
|
68
68
|
server.on("error", (error) => {
|
|
69
69
|
log.error("socket", "connection", error, { port: socketConfig.port });
|
|
70
70
|
});
|
|
71
|
-
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
const onBindError = (error) => reject(error);
|
|
73
|
+
server.once("error", onBindError);
|
|
74
|
+
server.listen(socketConfig.port, () => {
|
|
75
|
+
server.removeListener("error", onBindError);
|
|
76
|
+
resolve();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
72
79
|
this.ownsRawServer = true;
|
|
73
80
|
}
|
|
74
81
|
container.set("socket.rawServer", server);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket-connector.mjs","names":["config","createHttpsServer","createHttpServer"],"sources":["../../../../../../../../packages/core/src/connectors/socket-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { createServer as createHttpServer } from \"http\";\nimport { createServer as createHttpsServer } from \"https\";\nimport type { Server } from \"socket.io\";\nimport { container } from \"../container\";\nimport { environment } from \"../utils/environment\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, type ConnectorName, ConnectorPriority } from \"./types\";\n\n/**\n * Shown when a project enables sockets (a `socket` config is present) but the\n * optional `socket.io` peer is not installed.\n */\nconst SOCKET_INSTALL_INSTRUCTIONS = `\nRealtime socket server requires the socket.io package.\nInstall it with:\n\n warlock add socket\n\nOr manually:\n\n npm install socket.io\n pnpm add socket.io\n yarn add socket.io\n`.trim();\n\nconst SINGLE_SERVER_WARNING =\n \"socket: no adapter configured — broadcasts only reach clients connected to this server. Use @socket.io/redis-adapter (socket.adapter) behind a load balancer, with sticky sessions for the polling transport.\";\n\n/**\n * Socket Connector\n * Manages Socket server (Socket.IO) lifecycle\n */\nexport class SocketConnector extends BaseConnector {\n public readonly name: ConnectorName = \"socket\";\n public readonly priority = ConnectorPriority.SOCKET;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger Socket server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/socket.ts\"];\n\n protected socket?: Server;\n\n /**\n * Whether this connector created the underlying node HTTP(S) server.\n *\n * When `http` is present, socket binds onto the HTTP connector's shared\n * `fastify.server` and must NOT close it on shutdown — the HTTP connector\n * owns the graceful drain. Socket only closes the node server when it\n * created one itself (http absent). See {@link shutdown}.\n */\n protected ownsRawServer = false;\n\n /**\n * The shared server socket.io attached to, kept for the detach.\n */\n protected sharedServer?: any;\n\n /**\n * Listener changes socket.io/engine.io made on the shared server at attach:\n * the listeners they added, and the ones they removed (engine.io lifts the\n * existing `request` listeners into its own wrapper). Undone on detach so\n * repeated dev restarts don't stack engines on Fastify's server.\n */\n protected attachedListeners: {\n added: Array<[string, (...args: any[]) => void]>;\n removed: Array<[string, (...args: any[]) => void]>;\n } = { added: [], removed: [] };\n\n /**\n * Boot the connector\n */\n public async boot() {\n const socketConfig = config.get(\"socket\");\n\n if (!socketConfig) return;\n\n // socket.io is an optional peer — load it lazily so projects that don't use\n // realtime sockets never need it installed (mirrors the mail/storage drivers).\n let SocketServer: typeof import(\"socket.io\").Server;\n try {\n ({ Server: SocketServer } = await import(\"socket.io\"));\n } catch {\n throw new Error(SOCKET_INSTALL_INSTRUCTIONS);\n }\n\n log.info(\"socket\", \"connection\", \"Starting Socket.IO server\");\n\n // now we have two cases\n // 1. http is used, then use it (shared — the HTTP connector owns it)\n // 2. http is not used, then create a new server (we own it)\n let server;\n const fastify = container.tryGet(\"http.server\");\n if (fastify) {\n server = fastify.server;\n this.ownsRawServer = false;\n } else {\n server = socketConfig.ssl\n ? createHttpsServer({ key: socketConfig.ssl.key, cert: socketConfig.ssl.cert })\n : createHttpServer();\n // Without a handler, EADDRINUSE is an unhandled 'error' event that crashes\n // the process with no context.\n server.on(\"error\", (error: Error) => {\n log.error(\"socket\", \"connection\", error, { port: socketConfig.port });\n });\n server.listen(socketConfig.port);\n this.ownsRawServer = true;\n }\n\n container.set(\"socket.rawServer\", server);\n\n const before = this.snapshotListeners(server);\n\n this.socket = new SocketServer(server, {\n // The shared raw server may carry OTHER `upgrade` consumers — Vite's HMR\n // websocket does exactly that when `WebConnector` is registered\n // (`web/src/server/web-connector.ts`, `server.hmr.server = fastify.server`).\n // engine.io's default `destroyUpgrade: true` schedules a destroy for every\n // upgrade whose path is not `/socket.io`\n // (`engine.io/build/server.js:676-695`: `setTimeout(… socket.end(), 1000)`),\n // and the only thing saving a foreign websocket today is that it wrote its\n // handshake bytes synchronously and so trips the `bytesWritten <= 0` guard\n // before the 1s timer fires. That is an undocumented race, not a contract.\n // Turning the destroy off removes the coupling outright and makes the\n // shared server safe for any app-owned websocket route.\n // A project can still override it via `socket.options`.\n destroyUpgrade: false,\n ...socketConfig.options,\n });\n\n if (!this.ownsRawServer) {\n this.sharedServer = server;\n this.attachedListeners = this.diffListeners(before, this.snapshotListeners(server));\n }\n\n if (socketConfig.adapter) {\n this.socket.adapter((await socketConfig.adapter(this.socket)) as never);\n } else if (environment() === \"production\" && !socketConfig.silenceSingleServerWarning) {\n log.warn(\"socket\", \"adapter\", SINGLE_SERVER_WARNING);\n }\n\n container.set(\"socket\", this.socket);\n }\n\n /**\n * Initialize Socket server\n */\n public async start(): Promise<void> {\n const socketConfig = config.get(\"socket\");\n\n // `this.socket` is only set by boot() once a socket config is present —\n // use it (not a never-assigned field) to detect a successful boot.\n if (!socketConfig || !this.socket) return;\n\n log.success(\"socket\", \"connection\", \"Established Socket.IO server\");\n\n this.active = true;\n }\n\n /**\n * Restart re-boots too: the closed `io` must be replaced and the new\n * `socket` config read, otherwise a config edit in dev restarts nothing.\n */\n public async restart(): Promise<void> {\n await this.shutdown();\n await this.boot();\n await this.start();\n }\n\n /**\n * Shutdown Socket server\n *\n * Always closes the Socket.IO layer (and awaits the drain). The underlying\n * node HTTP(S) server is only closed here when this connector created it\n * ({@link ownsRawServer}); when `http` owns the shared server, we leave it\n * for the HTTP connector to drain and close, so the two connectors don't\n * race to `close()` the same socket.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n const socket = container.tryGet(\"socket\");\n if (socket) {\n if (this.ownsRawServer) {\n // We own the raw server: `io.close()` closes it too, so it is the only\n // close needed (a second `rawServer.close()` always rejects with\n // ERR_SERVER_NOT_RUNNING).\n await new Promise<void>((resolve) => {\n socket.close(() => resolve());\n });\n } else {\n // Shared mode: `io.close()` calls `httpServer.close()`, which would close\n // Fastify's server from here and bypass its graceful drain. Detach\n // instead: disconnect the clients and close the engine only.\n socket.of(\"/\").disconnectSockets(true);\n socket.engine.close();\n this.restoreListeners();\n }\n }\n\n this.active = false;\n }\n\n /**\n * Current listeners of the events socket.io touches on a node server.\n */\n private snapshotListeners(server: any): Map<string, Array<(...args: any[]) => void>> {\n const snapshot = new Map<string, Array<(...args: any[]) => void>>();\n\n for (const event of [\"request\", \"upgrade\", \"close\", \"listening\"]) {\n snapshot.set(event, server?.listeners?.(event)?.slice() ?? []);\n }\n\n return snapshot;\n }\n\n private diffListeners(\n before: Map<string, Array<(...args: any[]) => void>>,\n after: Map<string, Array<(...args: any[]) => void>>,\n ) {\n const added: Array<[string, (...args: any[]) => void]> = [];\n const removed: Array<[string, (...args: any[]) => void]> = [];\n\n for (const [event, listeners] of before) {\n const now = after.get(event) ?? [];\n\n for (const listener of now) if (!listeners.includes(listener)) added.push([event, listener]);\n for (const listener of listeners)\n if (!now.includes(listener)) removed.push([event, listener]);\n }\n\n return { added, removed };\n }\n\n /**\n * Undo what attaching did to the shared server, leaving listeners other\n * consumers added since (e.g. Vite's HMR upgrade handler) untouched.\n */\n private restoreListeners(): void {\n const server = this.sharedServer;\n\n if (!server) return;\n\n const { added, removed } = this.attachedListeners;\n\n for (const [event, listener] of added) server.removeListener(event, listener);\n for (const [event, listener] of removed) server.on(event, listener);\n\n this.attachedListeners = { added: [], removed: [] };\n this.sharedServer = undefined;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"socket-connector.mjs","names":["config","createHttpsServer","createHttpServer"],"sources":["../../../../../../../../packages/core/src/connectors/socket-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { createServer as createHttpServer, type Server as HttpServer } from \"http\";\nimport { createServer as createHttpsServer } from \"https\";\nimport type { Server } from \"socket.io\";\nimport { container } from \"../container\";\nimport { environment } from \"../utils/environment\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, type ConnectorName, ConnectorPriority } from \"./types\";\n\n/**\n * Shown when a project enables sockets (a `socket` config is present) but the\n * optional `socket.io` peer is not installed.\n */\nconst SOCKET_INSTALL_INSTRUCTIONS = `\nRealtime socket server requires the socket.io package.\nInstall it with:\n\n warlock add socket\n\nOr manually:\n\n npm install socket.io\n pnpm add socket.io\n yarn add socket.io\n`.trim();\n\nconst SINGLE_SERVER_WARNING =\n \"socket: no adapter configured — broadcasts only reach clients connected to this server. Use @socket.io/redis-adapter (socket.adapter) behind a load balancer, with sticky sessions for the polling transport.\";\n\n/**\n * Socket Connector\n * Manages Socket server (Socket.IO) lifecycle\n */\nexport class SocketConnector extends BaseConnector {\n public readonly name: ConnectorName = \"socket\";\n public readonly priority = ConnectorPriority.SOCKET;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger Socket server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/socket.ts\"];\n\n protected socket?: Server;\n\n /**\n * Whether this connector created the underlying node HTTP(S) server.\n *\n * When `http` is present, socket binds onto the HTTP connector's shared\n * `fastify.server` and must NOT close it on shutdown — the HTTP connector\n * owns the graceful drain. Socket only closes the node server when it\n * created one itself (http absent). See {@link shutdown}.\n */\n protected ownsRawServer = false;\n\n /**\n * The shared server socket.io attached to, kept for the detach.\n */\n protected sharedServer?: any;\n\n /**\n * Listener changes socket.io/engine.io made on the shared server at attach:\n * the listeners they added, and the ones they removed (engine.io lifts the\n * existing `request` listeners into its own wrapper). Undone on detach so\n * repeated dev restarts don't stack engines on Fastify's server.\n */\n protected attachedListeners: {\n added: Array<[string, (...args: any[]) => void]>;\n removed: Array<[string, (...args: any[]) => void]>;\n } = { added: [], removed: [] };\n\n /**\n * Boot the connector\n */\n public async boot() {\n const socketConfig = config.get(\"socket\");\n\n if (!socketConfig) return;\n\n // socket.io is an optional peer — load it lazily so projects that don't use\n // realtime sockets never need it installed (mirrors the mail/storage drivers).\n let SocketServer: typeof import(\"socket.io\").Server;\n try {\n ({ Server: SocketServer } = await import(\"socket.io\"));\n } catch {\n throw new Error(SOCKET_INSTALL_INSTRUCTIONS);\n }\n\n log.info(\"socket\", \"connection\", \"Starting Socket.IO server\");\n\n // now we have two cases\n // 1. http is used, then use it (shared — the HTTP connector owns it)\n // 2. http is not used, then create a new server (we own it)\n let server: HttpServer;\n const fastify = container.tryGet(\"http.server\");\n if (fastify) {\n server = fastify.server;\n this.ownsRawServer = false;\n } else {\n server = socketConfig.ssl\n ? createHttpsServer({ key: socketConfig.ssl.key, cert: socketConfig.ssl.cert })\n : createHttpServer();\n // Without a handler, EADDRINUSE is an unhandled 'error' event that crashes\n // the process with no context.\n server.on(\"error\", (error: Error) => {\n log.error(\"socket\", \"connection\", error, { port: socketConfig.port });\n });\n // Boot must await the socket-only listener too: Application.markBooted\n // reports readiness to PM2 after the late phase resolves. Logging a bind\n // error alone would otherwise still let the app announce readiness.\n await new Promise<void>((resolve, reject) => {\n const onBindError = (error: Error) => reject(error);\n server.once(\"error\", onBindError);\n server.listen(socketConfig.port, () => {\n server.removeListener(\"error\", onBindError);\n resolve();\n });\n });\n this.ownsRawServer = true;\n }\n\n container.set(\"socket.rawServer\", server);\n\n const before = this.snapshotListeners(server);\n\n this.socket = new SocketServer(server, {\n // The shared raw server may carry OTHER `upgrade` consumers — Vite's HMR\n // websocket does exactly that when `WebConnector` is registered\n // (`web/src/server/web-connector.ts`, `server.hmr.server = fastify.server`).\n // engine.io's default `destroyUpgrade: true` schedules a destroy for every\n // upgrade whose path is not `/socket.io`\n // (`engine.io/build/server.js:676-695`: `setTimeout(… socket.end(), 1000)`),\n // and the only thing saving a foreign websocket today is that it wrote its\n // handshake bytes synchronously and so trips the `bytesWritten <= 0` guard\n // before the 1s timer fires. That is an undocumented race, not a contract.\n // Turning the destroy off removes the coupling outright and makes the\n // shared server safe for any app-owned websocket route.\n // A project can still override it via `socket.options`.\n destroyUpgrade: false,\n ...socketConfig.options,\n });\n\n if (!this.ownsRawServer) {\n this.sharedServer = server;\n this.attachedListeners = this.diffListeners(before, this.snapshotListeners(server));\n }\n\n if (socketConfig.adapter) {\n this.socket.adapter((await socketConfig.adapter(this.socket)) as never);\n } else if (environment() === \"production\" && !socketConfig.silenceSingleServerWarning) {\n log.warn(\"socket\", \"adapter\", SINGLE_SERVER_WARNING);\n }\n\n container.set(\"socket\", this.socket);\n }\n\n /**\n * Initialize Socket server\n */\n public async start(): Promise<void> {\n const socketConfig = config.get(\"socket\");\n\n // `this.socket` is only set by boot() once a socket config is present —\n // use it (not a never-assigned field) to detect a successful boot.\n if (!socketConfig || !this.socket) return;\n\n log.success(\"socket\", \"connection\", \"Established Socket.IO server\");\n\n this.active = true;\n }\n\n /**\n * Restart re-boots too: the closed `io` must be replaced and the new\n * `socket` config read, otherwise a config edit in dev restarts nothing.\n */\n public async restart(): Promise<void> {\n await this.shutdown();\n await this.boot();\n await this.start();\n }\n\n /**\n * Shutdown Socket server\n *\n * Always closes the Socket.IO layer (and awaits the drain). The underlying\n * node HTTP(S) server is only closed here when this connector created it\n * ({@link ownsRawServer}); when `http` owns the shared server, we leave it\n * for the HTTP connector to drain and close, so the two connectors don't\n * race to `close()` the same socket.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n const socket = container.tryGet(\"socket\");\n if (socket) {\n if (this.ownsRawServer) {\n // We own the raw server: `io.close()` closes it too, so it is the only\n // close needed (a second `rawServer.close()` always rejects with\n // ERR_SERVER_NOT_RUNNING).\n await new Promise<void>((resolve) => {\n socket.close(() => resolve());\n });\n } else {\n // Shared mode: `io.close()` calls `httpServer.close()`, which would close\n // Fastify's server from here and bypass its graceful drain. Detach\n // instead: disconnect the clients and close the engine only.\n socket.of(\"/\").disconnectSockets(true);\n socket.engine.close();\n this.restoreListeners();\n }\n }\n\n this.active = false;\n }\n\n /**\n * Current listeners of the events socket.io touches on a node server.\n */\n private snapshotListeners(server: any): Map<string, Array<(...args: any[]) => void>> {\n const snapshot = new Map<string, Array<(...args: any[]) => void>>();\n\n for (const event of [\"request\", \"upgrade\", \"close\", \"listening\"]) {\n snapshot.set(event, server?.listeners?.(event)?.slice() ?? []);\n }\n\n return snapshot;\n }\n\n private diffListeners(\n before: Map<string, Array<(...args: any[]) => void>>,\n after: Map<string, Array<(...args: any[]) => void>>,\n ) {\n const added: Array<[string, (...args: any[]) => void]> = [];\n const removed: Array<[string, (...args: any[]) => void]> = [];\n\n for (const [event, listeners] of before) {\n const now = after.get(event) ?? [];\n\n for (const listener of now) if (!listeners.includes(listener)) added.push([event, listener]);\n for (const listener of listeners)\n if (!now.includes(listener)) removed.push([event, listener]);\n }\n\n return { added, removed };\n }\n\n /**\n * Undo what attaching did to the shared server, leaving listeners other\n * consumers added since (e.g. Vite's HMR upgrade handler) untouched.\n */\n private restoreListeners(): void {\n const server = this.sharedServer;\n\n if (!server) return;\n\n const { added, removed } = this.attachedListeners;\n\n for (const [event, listener] of added) server.removeListener(event, listener);\n for (const [event, listener] of removed) server.on(event, listener);\n\n this.attachedListeners = { added: [], removed: [] };\n this.sharedServer = undefined;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BaseHealthChecker } from "./base-health-checker.mjs";
|
|
2
1
|
import { OxlintRunner } from "../oxlint-runner.mjs";
|
|
2
|
+
import { BaseHealthChecker } from "./base-health-checker.mjs";
|
|
3
3
|
|
|
4
4
|
//#region ../../packages/core/src/dev-server/health-checker/checkers/oxlint-health-checker.ts
|
|
5
5
|
var OxlintHealthChecker = class extends BaseHealthChecker {
|
|
@@ -1,31 +1,17 @@
|
|
|
1
|
-
import { EslintHealthChecker } from "./checkers/eslint-health-checker.mjs";
|
|
2
1
|
import { findOxlint } from "./oxlint-runner.mjs";
|
|
3
2
|
import { OxlintHealthChecker } from "./checkers/oxlint-health-checker.mjs";
|
|
4
3
|
import { TypescriptHealthChecker } from "./checkers/typescript-health-checker.mjs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
7
4
|
|
|
8
5
|
//#region ../../packages/core/src/dev-server/health-checker/default-health-checkers.ts
|
|
9
|
-
/** Whether the app has an ESLint flat config at its root. */
|
|
10
|
-
function hasEslint(cwd) {
|
|
11
|
-
return [
|
|
12
|
-
"js",
|
|
13
|
-
"mjs",
|
|
14
|
-
"cjs",
|
|
15
|
-
"ts",
|
|
16
|
-
"mts",
|
|
17
|
-
"cts"
|
|
18
|
-
].some((extension) => existsSync(path.join(cwd, `eslint.config.${extension}`)));
|
|
19
|
-
}
|
|
20
6
|
/** Explicit arrays remain custom-only; built-in booleans select among available checkers. */
|
|
21
7
|
function defaultHealthCheckers(options = {}, cwd = process.cwd()) {
|
|
8
|
+
if ("eslint" in options) throw new Error("ESLint support was removed in 5.34.0. Remove healthCheckers.eslint and use Oxlint/Oxfmt; run npx warlock upgrade --from 5.33 --to 5.34.0.");
|
|
22
9
|
const checkers = [];
|
|
23
10
|
if (options.typescript !== false) checkers.push(new TypescriptHealthChecker());
|
|
24
|
-
if (options.eslint !== false && hasEslint(cwd)) checkers.push(new EslintHealthChecker());
|
|
25
11
|
if (options.oxlint !== false && findOxlint(cwd)) checkers.push(new OxlintHealthChecker());
|
|
26
12
|
return checkers;
|
|
27
13
|
}
|
|
28
14
|
|
|
29
15
|
//#endregion
|
|
30
|
-
export { defaultHealthCheckers
|
|
16
|
+
export { defaultHealthCheckers };
|
|
31
17
|
//# sourceMappingURL=default-health-checkers.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-health-checkers.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/health-checker/default-health-checkers.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"default-health-checkers.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/health-checker/default-health-checkers.ts"],"sourcesContent":["import { OxlintHealthChecker } from \"./checkers/oxlint-health-checker\";\nimport { TypescriptHealthChecker } from \"./checkers/typescript-health-checker\";\nimport type {\n BuiltInHealthCheckerOptions,\n FileHealthCheckerContract,\n} from \"./file-health-checker.contract\";\nimport { findOxlint } from \"./oxlint-runner\";\n\n/** Explicit arrays remain custom-only; built-in booleans select among available checkers. */\nexport function defaultHealthCheckers(\n options: BuiltInHealthCheckerOptions = {},\n cwd = process.cwd(),\n): FileHealthCheckerContract[] {\n if (\"eslint\" in options)\n throw new Error(\n \"ESLint support was removed in 5.34.0. Remove healthCheckers.eslint and use Oxlint/Oxfmt; run npx warlock upgrade --from 5.33 --to 5.34.0.\",\n );\n const checkers: FileHealthCheckerContract[] = [];\n if (options.typescript !== false) checkers.push(new TypescriptHealthChecker());\n if (options.oxlint !== false && findOxlint(cwd)) checkers.push(new OxlintHealthChecker());\n return checkers;\n}\n"],"mappings":";;;;;;AASA,SAAgB,sBACd,UAAuC,CAAC,GACxC,MAAM,QAAQ,IAAI,GACW;CAC7B,IAAI,YAAY,SACd,MAAM,IAAI,MACR,2IACF;CACF,MAAM,WAAwC,CAAC;CAC/C,IAAI,QAAQ,eAAe,OAAO,SAAS,KAAK,IAAI,wBAAwB,CAAC;CAC7E,IAAI,QAAQ,WAAW,SAAS,WAAW,GAAG,GAAG,SAAS,KAAK,IAAI,oBAAoB,CAAC;CACxF,OAAO;AACT"}
|
|
@@ -59,10 +59,9 @@ export interface FileHealthCheckerContract {
|
|
|
59
59
|
*/
|
|
60
60
|
stats(): Promise<HealthCheckerFilesStats>;
|
|
61
61
|
}
|
|
62
|
-
/** Select
|
|
62
|
+
/** Select background checks; TypeScript and configured Oxlint run by default. */
|
|
63
63
|
export type BuiltInHealthCheckerOptions = {
|
|
64
64
|
typescript?: boolean;
|
|
65
|
-
eslint?: boolean;
|
|
66
65
|
oxlint?: boolean;
|
|
67
66
|
};
|
|
68
67
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-health-checker.contract.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/health-checker/file-health-checker.contract.ts"],"mappings":";;;YAGY;EACV;EACA;IACE;IACA;;EAEF;IACE;IACA;;EAEF;IACE;IACA;;;iBAIa;;;;EAIf;;;;;;;;;;;;;;EAeA;;;;;EAMA;;EAGA,oBAAoB;;;;EAKpB,cAAc,OAAO,gBAAgB;;;;EAKrC,WAAW,MAAM;;;;EAKjB,MAAM,MAAM,cAAc,QAAQ;;;;EAKlC,SAAS,QAAQ;;;YAIP;EACV;EACA
|
|
1
|
+
{"version":3,"file":"file-health-checker.contract.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/health-checker/file-health-checker.contract.ts"],"mappings":";;;YAGY;EACV;EACA;IACE;IACA;;EAEF;IACE;IACA;;EAEF;IACE;IACA;;;iBAIa;;;;EAIf;;;;;;;;;;;;;;EAeA;;;;;EAMA;;EAGA,oBAAoB;;;;EAKpB,cAAc,OAAO,gBAAgB;;;;EAKrC,WAAW,MAAM;;;;EAKjB,MAAM,MAAM,cAAc,QAAQ;;;;EAKlC,SAAS,QAAQ;;;YAIP;EACV;EACA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-health-result.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/health-checker/file-health-result.ts"],"sourcesContent":["type FileHealthStats = {\n /**\n * File Health State\n */\n state: \"healthy\" | \"defective\";\n /**\n * Number of errors\n */\n errors: number;\n /**\n * Number of warnings\n */\n warnings: number;\n};\n\ntype FileHealthMessage = {\n /**\n * Message\n */\n message: string;\n /**\n * Type of the message\n */\n type: \"error\" | \"warning\";\n /**\n * Line number\n */\n lineNumber: number;\n /**\n * Column number\n */\n columnNumber: number;\n /**\n * Length of the error/warning span\n */\n length?: number;\n /**\n *
|
|
1
|
+
{"version":3,"file":"file-health-result.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/health-checker/file-health-result.ts"],"sourcesContent":["type FileHealthStats = {\n /**\n * File Health State\n */\n state: \"healthy\" | \"defective\";\n /**\n * Number of errors\n */\n errors: number;\n /**\n * Number of warnings\n */\n warnings: number;\n};\n\ntype FileHealthMessage = {\n /**\n * Message\n */\n message: string;\n /**\n * Type of the message\n */\n type: \"error\" | \"warning\";\n /**\n * Line number\n */\n lineNumber: number;\n /**\n * Column number\n */\n columnNumber: number;\n /**\n * Length of the error/warning span\n */\n length?: number;\n /**\n * Linter rule ID\n */\n ruleId?: string;\n};\n\nexport class FileHealthResult {\n /**\n * Result of the health check\n */\n public result: \"healthy\" | \"defective\" = \"healthy\";\n\n /**\n * Messages list (either for errors or warnings)\n */\n public messages: FileHealthMessage[] = [];\n\n /**\n * Mark as healthy\n */\n public markAsHealthy(): void {\n this.result = \"healthy\";\n }\n\n /**\n * Add errors\n */\n public addErrors(messages: FileHealthMessage[]): void {\n this.result = \"defective\";\n this.messages.push(...messages);\n }\n\n /**\n * Add warnings\n */\n public addWarnings(messages: FileHealthMessage[]): void {\n this.result = \"defective\";\n this.messages.push(...messages);\n }\n\n /**\n * Get file health stats\n */\n public getStats(): FileHealthStats {\n return {\n state: this.result,\n errors: this.messages.filter((message) => message.type === \"error\").length,\n warnings: this.messages.filter((message) => message.type === \"warning\").length,\n };\n }\n}\n"],"mappings":";AA0CA,IAAa,mBAAb,MAA8B;;gBAIa;kBAKF,CAAC;;;;;CAKxC,AAAO,gBAAsB;EAC3B,KAAK,SAAS;CAChB;;;;CAKA,AAAO,UAAU,UAAqC;EACpD,KAAK,SAAS;EACd,KAAK,SAAS,KAAK,GAAG,QAAQ;CAChC;;;;CAKA,AAAO,YAAY,UAAqC;EACtD,KAAK,SAAS;EACd,KAAK,SAAS,KAAK,GAAG,QAAQ;CAChC;;;;CAKA,AAAO,WAA4B;EACjC,OAAO;GACL,OAAO,KAAK;GACZ,QAAQ,KAAK,SAAS,QAAQ,YAAY,QAAQ,SAAS,OAAO,CAAC,CAAC;GACpE,UAAU,KAAK,SAAS,QAAQ,YAAY,QAAQ,SAAS,SAAS,CAAC,CAAC;EAC1E;CACF;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/e2e/browser.ts"],"mappings":";wBA2CsB,kBACpB,gCACC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CdpConnection } from "./cdp.mjs";
|
|
2
|
+
import { OwnedProcess } from "./owned-process.mjs";
|
|
3
|
+
import { isolatedEnvironment } from "./fixture.mjs";
|
|
4
|
+
import { delimiter, join, resolve } from "node:path";
|
|
5
|
+
import { constants } from "node:fs";
|
|
6
|
+
import { access, mkdir, readFile } from "node:fs/promises";
|
|
7
|
+
import { setTimeout } from "node:timers/promises";
|
|
8
|
+
|
|
9
|
+
//#region ../../packages/core/src/e2e/browser.ts
|
|
10
|
+
function browserCandidates(platform, env) {
|
|
11
|
+
const names = platform === "win32" ? ["chrome.exe", "msedge.exe"] : [
|
|
12
|
+
"google-chrome",
|
|
13
|
+
"google-chrome-stable",
|
|
14
|
+
"chromium",
|
|
15
|
+
"chromium-browser",
|
|
16
|
+
"microsoft-edge",
|
|
17
|
+
"microsoft-edge-stable"
|
|
18
|
+
];
|
|
19
|
+
const paths = (env.PATH ?? "").split(platform === "win32" ? ";" : delimiter).filter(Boolean).flatMap((path) => names.map((name) => join(path, name)));
|
|
20
|
+
if (platform === "darwin") paths.push("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", join(env.HOME ?? "", "Applications/Google Chrome.app/Contents/MacOS/Google Chrome"), join(env.HOME ?? "", "Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"));
|
|
21
|
+
if (platform === "win32") for (const root of [
|
|
22
|
+
env.PROGRAMFILES,
|
|
23
|
+
env["PROGRAMFILES(X86)"],
|
|
24
|
+
env.LOCALAPPDATA
|
|
25
|
+
].filter((path) => Boolean(path))) paths.push(join(root, "Google", "Chrome", "Application", "chrome.exe"), join(root, "Microsoft", "Edge", "Application", "msedge.exe"));
|
|
26
|
+
return paths;
|
|
27
|
+
}
|
|
28
|
+
async function findBrowserBinary(override = process.env.WARLOCK_E2E_BROWSER) {
|
|
29
|
+
const candidates = override ? [resolve(override)] : browserCandidates(process.platform, process.env);
|
|
30
|
+
for (const candidate of candidates) try {
|
|
31
|
+
await access(candidate, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
32
|
+
return candidate;
|
|
33
|
+
} catch {}
|
|
34
|
+
if (override) throw new Error(`Browser override is not executable: ${override}`);
|
|
35
|
+
}
|
|
36
|
+
var OwnedBrowser = class {
|
|
37
|
+
constructor(root, evidence) {
|
|
38
|
+
this.root = root;
|
|
39
|
+
this.evidence = evidence;
|
|
40
|
+
}
|
|
41
|
+
async start(options = {}, signal) {
|
|
42
|
+
const binary = await findBrowserBinary(options.binary);
|
|
43
|
+
if (!binary) throw new Error("No Chrome or Edge binary found. Install one or set --browser / WARLOCK_E2E_BROWSER to its executable path.");
|
|
44
|
+
if (options.args?.some((arg) => !arg.startsWith("--") || /^--(?:user-data-dir|remote-debugging|headless)/.test(arg))) throw new Error("Browser args must be flags and cannot replace the owned profile, debugging endpoint, or headless mode");
|
|
45
|
+
const profile = join(this.root, "browser-profile");
|
|
46
|
+
await mkdir(profile, { recursive: true });
|
|
47
|
+
this.process = await OwnedProcess.start("browser", [
|
|
48
|
+
binary,
|
|
49
|
+
"--headless",
|
|
50
|
+
"--remote-debugging-port=0",
|
|
51
|
+
`--user-data-dir=${profile}`,
|
|
52
|
+
...options.args ?? [],
|
|
53
|
+
"about:blank"
|
|
54
|
+
], this.root, isolatedEnvironment(this.root, {}), this.root, this.evidence.processes, (owned) => {
|
|
55
|
+
this.process = owned;
|
|
56
|
+
});
|
|
57
|
+
const deadline = Date.now() + 15e3;
|
|
58
|
+
let reason = "DevToolsActivePort is not ready";
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
signal?.throwIfAborted();
|
|
61
|
+
this.process.assertRunning();
|
|
62
|
+
try {
|
|
63
|
+
const port = Number((await readFile(join(profile, "DevToolsActivePort"), "utf8")).split("\n")[0]);
|
|
64
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new Error("Invalid owned browser debugging port");
|
|
65
|
+
const response = await fetch(`http://127.0.0.1:${port}/json/list`, { signal: AbortSignal.timeout(1e3) });
|
|
66
|
+
if (!response.ok) throw new Error(`CDP discovery returned HTTP ${response.status}`);
|
|
67
|
+
const target = (await response.json()).find((candidate) => candidate.type === "page" && candidate.url === "about:blank");
|
|
68
|
+
if (!target?.webSocketDebuggerUrl) throw new Error("Owned browser has no about:blank page target");
|
|
69
|
+
const endpoint = new URL(target.webSocketDebuggerUrl);
|
|
70
|
+
if (endpoint.protocol !== "ws:" || endpoint.hostname !== "127.0.0.1" || Number(endpoint.port) !== port) throw new Error("CDP discovery returned a non-owned endpoint");
|
|
71
|
+
this.connection = await CdpConnection.connect(endpoint.href);
|
|
72
|
+
return this.connection;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
reason = String(error);
|
|
75
|
+
await setTimeout(50);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`Browser startup timed out: ${reason}\n${this.process.logs().slice(-8e3)}`);
|
|
79
|
+
}
|
|
80
|
+
async stop() {
|
|
81
|
+
this.connection?.close();
|
|
82
|
+
await this.process?.stop();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { OwnedBrowser, browserCandidates, findBrowserBinary };
|
|
88
|
+
//# sourceMappingURL=browser.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.mjs","names":["delay"],"sources":["../../../../../../../../packages/core/src/e2e/browser.ts"],"sourcesContent":["import { constants } from \"node:fs\";\nimport { access, mkdir, readFile } from \"node:fs/promises\";\nimport { delimiter, join, resolve } from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { CdpConnection } from \"./cdp\";\nimport { isolatedEnvironment } from \"./fixture\";\nimport { OwnedProcess } from \"./owned-process\";\nimport type { BrowserOptions, E2EEvidence } from \"./types\";\n\nexport function browserCandidates(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): string[] {\n const names =\n platform === \"win32\"\n ? [\"chrome.exe\", \"msedge.exe\"]\n : [\n \"google-chrome\",\n \"google-chrome-stable\",\n \"chromium\",\n \"chromium-browser\",\n \"microsoft-edge\",\n \"microsoft-edge-stable\",\n ];\n const paths = (env.PATH ?? \"\")\n .split(platform === \"win32\" ? \";\" : delimiter)\n .filter(Boolean)\n .flatMap((path) => names.map((name) => join(path, name)));\n if (platform === \"darwin\")\n paths.push(\n \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\",\n \"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\",\n join(env.HOME ?? \"\", \"Applications/Google Chrome.app/Contents/MacOS/Google Chrome\"),\n join(env.HOME ?? \"\", \"Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\"),\n );\n if (platform === \"win32\")\n for (const root of [env.PROGRAMFILES, env[\"PROGRAMFILES(X86)\"], env.LOCALAPPDATA].filter(\n (path): path is string => Boolean(path),\n ))\n paths.push(\n join(root, \"Google\", \"Chrome\", \"Application\", \"chrome.exe\"),\n join(root, \"Microsoft\", \"Edge\", \"Application\", \"msedge.exe\"),\n );\n return paths;\n}\n\nexport async function findBrowserBinary(\n override = process.env.WARLOCK_E2E_BROWSER,\n): Promise<string | undefined> {\n const candidates = override\n ? [resolve(override)]\n : browserCandidates(process.platform, process.env);\n for (const candidate of candidates) {\n try {\n await access(candidate, process.platform === \"win32\" ? constants.F_OK : constants.X_OK);\n return candidate;\n } catch {\n /* Try the next known installation. */\n }\n }\n if (override) throw new Error(`Browser override is not executable: ${override}`);\n return undefined;\n}\n\nexport class OwnedBrowser {\n connection?: CdpConnection;\n process?: OwnedProcess;\n constructor(\n private root: string,\n private evidence: E2EEvidence,\n ) {}\n\n async start(options: BrowserOptions = {}, signal?: AbortSignal): Promise<CdpConnection> {\n const binary = await findBrowserBinary(options.binary);\n if (!binary)\n throw new Error(\n \"No Chrome or Edge binary found. Install one or set --browser / WARLOCK_E2E_BROWSER to its executable path.\",\n );\n if (\n options.args?.some(\n (arg) =>\n !arg.startsWith(\"--\") || /^--(?:user-data-dir|remote-debugging|headless)/.test(arg),\n )\n )\n throw new Error(\n \"Browser args must be flags and cannot replace the owned profile, debugging endpoint, or headless mode\",\n );\n const profile = join(this.root, \"browser-profile\");\n await mkdir(profile, { recursive: true });\n // Headless flag: https://developer.chrome.com/docs/chromium/headless\n // Port 0 and discovery: https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target\n // Profile required by Chrome 136+: https://developer.chrome.com/blog/remote-debugging-port\n this.process = await OwnedProcess.start(\n \"browser\",\n [\n binary,\n \"--headless\",\n \"--remote-debugging-port=0\",\n `--user-data-dir=${profile}`,\n ...(options.args ?? []),\n \"about:blank\",\n ],\n this.root,\n isolatedEnvironment(this.root, {}),\n this.root,\n this.evidence.processes,\n (owned) => {\n this.process = owned;\n },\n );\n const deadline = Date.now() + 15_000;\n let reason = \"DevToolsActivePort is not ready\";\n while (Date.now() < deadline) {\n signal?.throwIfAborted();\n this.process.assertRunning();\n try {\n const port = Number(\n (await readFile(join(profile, \"DevToolsActivePort\"), \"utf8\")).split(\"\\n\")[0],\n );\n if (!Number.isInteger(port) || port <= 0 || port > 65535)\n throw new Error(\"Invalid owned browser debugging port\");\n // HTTP discovery endpoints: https://chromedevtools.github.io/devtools-protocol/#http-endpoints\n const response = await fetch(`http://127.0.0.1:${port}/json/list`, {\n signal: AbortSignal.timeout(1000),\n });\n if (!response.ok) throw new Error(`CDP discovery returned HTTP ${response.status}`);\n const targets = (await response.json()) as {\n type: string;\n url: string;\n webSocketDebuggerUrl?: string;\n }[];\n const target = targets.find(\n (candidate) => candidate.type === \"page\" && candidate.url === \"about:blank\",\n );\n if (!target?.webSocketDebuggerUrl)\n throw new Error(\"Owned browser has no about:blank page target\");\n const endpoint = new URL(target.webSocketDebuggerUrl);\n if (\n endpoint.protocol !== \"ws:\" ||\n endpoint.hostname !== \"127.0.0.1\" ||\n Number(endpoint.port) !== port\n )\n throw new Error(\"CDP discovery returned a non-owned endpoint\");\n this.connection = await CdpConnection.connect(endpoint.href);\n return this.connection;\n } catch (error) {\n reason = String(error);\n await delay(50);\n }\n }\n throw new Error(`Browser startup timed out: ${reason}\\n${this.process.logs().slice(-8000)}`);\n }\n\n async stop(): Promise<void> {\n this.connection?.close();\n await this.process?.stop();\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//#region ../../packages/core/src/e2e/cdp.d.ts
|
|
2
|
+
/** Wire shapes verified against the official protocol viewer and its source JSON:
|
|
3
|
+
* https://chromedevtools.github.io/devtools-protocol/
|
|
4
|
+
* https://github.com/ChromeDevTools/devtools-protocol/tree/master/json
|
|
5
|
+
* Only the fields consumed by this harness are modeled here.
|
|
6
|
+
*/
|
|
7
|
+
type RemoteObject = {
|
|
8
|
+
value?: unknown;
|
|
9
|
+
description?: string;
|
|
10
|
+
};
|
|
11
|
+
type ExceptionDetails = {
|
|
12
|
+
text: string;
|
|
13
|
+
exception?: RemoteObject;
|
|
14
|
+
};
|
|
15
|
+
export type CdpMethods = {
|
|
16
|
+
"Runtime.enable": {
|
|
17
|
+
params: Record<string, never>;
|
|
18
|
+
result: Record<string, never>;
|
|
19
|
+
};
|
|
20
|
+
"Runtime.evaluate": {
|
|
21
|
+
params: {
|
|
22
|
+
expression: string;
|
|
23
|
+
returnByValue: boolean;
|
|
24
|
+
awaitPromise: boolean;
|
|
25
|
+
silent: boolean;
|
|
26
|
+
};
|
|
27
|
+
result: {
|
|
28
|
+
result: RemoteObject;
|
|
29
|
+
exceptionDetails?: ExceptionDetails;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
"Page.enable": {
|
|
33
|
+
params: Record<string, never>;
|
|
34
|
+
result: Record<string, never>;
|
|
35
|
+
};
|
|
36
|
+
"Page.navigate": {
|
|
37
|
+
params: {
|
|
38
|
+
url: string;
|
|
39
|
+
};
|
|
40
|
+
result: {
|
|
41
|
+
frameId: string;
|
|
42
|
+
loaderId?: string;
|
|
43
|
+
errorText?: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
"Page.captureScreenshot": {
|
|
47
|
+
params: {
|
|
48
|
+
format: "png";
|
|
49
|
+
};
|
|
50
|
+
result: {
|
|
51
|
+
data: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
"Emulation.setDeviceMetricsOverride": {
|
|
55
|
+
params: {
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
deviceScaleFactor: number;
|
|
59
|
+
mobile: boolean;
|
|
60
|
+
};
|
|
61
|
+
result: Record<string, never>;
|
|
62
|
+
};
|
|
63
|
+
"Input.dispatchMouseEvent": {
|
|
64
|
+
params: {
|
|
65
|
+
type: "mouseMoved" | "mousePressed" | "mouseReleased";
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
button?: "left";
|
|
69
|
+
buttons?: number;
|
|
70
|
+
clickCount?: number;
|
|
71
|
+
};
|
|
72
|
+
result: Record<string, never>;
|
|
73
|
+
};
|
|
74
|
+
"Input.insertText": {
|
|
75
|
+
params: {
|
|
76
|
+
text: string;
|
|
77
|
+
};
|
|
78
|
+
result: Record<string, never>;
|
|
79
|
+
};
|
|
80
|
+
"Input.dispatchKeyEvent": {
|
|
81
|
+
params: {
|
|
82
|
+
type: "keyDown" | "keyUp";
|
|
83
|
+
key: string;
|
|
84
|
+
code: string;
|
|
85
|
+
windowsVirtualKeyCode: number;
|
|
86
|
+
};
|
|
87
|
+
result: Record<string, never>;
|
|
88
|
+
};
|
|
89
|
+
"Network.enable": {
|
|
90
|
+
params: Record<string, never>;
|
|
91
|
+
result: Record<string, never>;
|
|
92
|
+
};
|
|
93
|
+
"Log.enable": {
|
|
94
|
+
params: Record<string, never>;
|
|
95
|
+
result: Record<string, never>;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
export type CdpEvents = {
|
|
99
|
+
"Page.loadEventFired": {
|
|
100
|
+
timestamp: number;
|
|
101
|
+
};
|
|
102
|
+
"Runtime.consoleAPICalled": {
|
|
103
|
+
type: string;
|
|
104
|
+
args: RemoteObject[];
|
|
105
|
+
};
|
|
106
|
+
"Runtime.exceptionThrown": {
|
|
107
|
+
exceptionDetails: ExceptionDetails;
|
|
108
|
+
};
|
|
109
|
+
"Log.entryAdded": {
|
|
110
|
+
entry: {
|
|
111
|
+
level: string;
|
|
112
|
+
text: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
"Network.requestWillBeSent": {
|
|
116
|
+
requestId: string;
|
|
117
|
+
request: {
|
|
118
|
+
url: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
"Network.responseReceived": {
|
|
122
|
+
requestId: string;
|
|
123
|
+
response: {
|
|
124
|
+
url: string;
|
|
125
|
+
status: number;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
"Network.loadingFailed": {
|
|
129
|
+
requestId: string;
|
|
130
|
+
errorText: string;
|
|
131
|
+
canceled?: boolean;
|
|
132
|
+
};
|
|
133
|
+
"Network.loadingFinished": {
|
|
134
|
+
requestId: string;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
export declare class CdpConnection {
|
|
138
|
+
private socket;
|
|
139
|
+
private sequence;
|
|
140
|
+
private pending;
|
|
141
|
+
private listeners;
|
|
142
|
+
private closed;
|
|
143
|
+
private constructor();
|
|
144
|
+
static connect(url: string, timeoutMs?: number): Promise<CdpConnection>;
|
|
145
|
+
command<K extends keyof CdpMethods>(method: K, params: CdpMethods[K]["params"], timeoutMs?: number): Promise<CdpMethods[K]["result"]>;
|
|
146
|
+
on<K extends keyof CdpEvents>(method: K, listener: (params: CdpEvents[K]) => void): () => void;
|
|
147
|
+
private fail;
|
|
148
|
+
close(): void;
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
export {};
|
|
152
|
+
//# sourceMappingURL=cdp.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/e2e/cdp.ts"],"mappings":";;;;;;KAKK;EAAiB;EAAiB;;KAClC;EAAqB;EAAc,YAAY;;YACxC;EAEV;IAAoB,QAAQ;IAAuB,QAAQ;;EAE3D;IACE;MAAU;MAAoB;MAAwB;MAAuB;;IAC7E;MAAU,QAAQ;MAAc,mBAAmB;;;EAGrD;IAAiB,QAAQ;IAAuB,QAAQ;;EAExD;IACE;MAAU;;IACV;MAAU;MAAiB;MAAmB;;;EAGhD;IAA4B;MAAU;;IAAiB;MAAU;;;EAEjE;IACE;MAAU;MAAe;MAAgB;MAA2B;;IACpE,QAAQ;;EAGV;IACE;MACE;MACA;MACA;MACA;MACA;MACA;;IAEF,QAAQ;;EAGV;IAAsB;MAAU;;IAAgB,QAAQ;;EAExD;IACE;MAAU;MAA2B;MAAa;MAAc;;IAChE,QAAQ;;EAGV;IAAoB,QAAQ;IAAuB,QAAQ;;EAE3D;IAAgB,QAAQ;IAAuB,QAAQ;;;YAE7C;EAEV;IAAyB;;EAEzB;IAA8B;IAAc,MAAM;;EAElD;IAA6B,kBAAkB;;EAE/C;IAAoB;MAAS;MAAe;;;EAE5C;IAA+B;IAAmB;MAAW;;;EAE7D;IAA8B;IAAmB;MAAY;MAAa;;;EAE1E;IAA2B;IAAmB;IAAmB;;EAEjE;IAA6B;;;qBAQlB;UAMiB;UALpB;UACA;UACA;UACA;UAED;SA4BM,QAAQ,aAAa,qBAAqB,QAAQ;EAiC/D,QAAQ,gBAAgB,YACtB,QAAQ,GACR,QAAQ,WAAW,cACnB,qBACC,QAAQ,WAAW;EA0BtB,GAAG,gBAAgB,WAAW,QAAQ,GAAG,WAAW,QAAQ,UAAU;UAU9D;EASR"}
|
package/esm/e2e/cdp.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//#region ../../packages/core/src/e2e/cdp.ts
|
|
2
|
+
var CdpConnection = class CdpConnection {
|
|
3
|
+
constructor(socket) {
|
|
4
|
+
this.socket = socket;
|
|
5
|
+
this.sequence = 0;
|
|
6
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
7
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
8
|
+
this.closed = false;
|
|
9
|
+
socket.addEventListener("message", ({ data }) => {
|
|
10
|
+
try {
|
|
11
|
+
const message = JSON.parse(String(data));
|
|
12
|
+
if (typeof message.id === "number") {
|
|
13
|
+
const pending = this.pending.get(message.id);
|
|
14
|
+
if (!pending) return;
|
|
15
|
+
clearTimeout(pending.timer);
|
|
16
|
+
this.pending.delete(message.id);
|
|
17
|
+
if (message.error) pending.reject(/* @__PURE__ */ new Error(`CDP: ${message.error.message}`));
|
|
18
|
+
else pending.resolve(message.result);
|
|
19
|
+
} else if (message.method) for (const listener of this.listeners.get(message.method) ?? []) listener(message.params);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
this.fail(/* @__PURE__ */ new Error(`Invalid CDP message: ${String(error)}`));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
socket.addEventListener("close", () => this.fail(/* @__PURE__ */ new Error("CDP connection closed")));
|
|
25
|
+
socket.addEventListener("error", () => this.fail(/* @__PURE__ */ new Error("CDP WebSocket error")));
|
|
26
|
+
}
|
|
27
|
+
static async connect(url, timeoutMs = 1e4) {
|
|
28
|
+
if (typeof WebSocket !== "function") throw new Error("Browser e2e requires Node's global WebSocket (Node >=22.15, without --no-experimental-websocket)");
|
|
29
|
+
const socket = new WebSocket(url);
|
|
30
|
+
const connection = new CdpConnection(socket);
|
|
31
|
+
await new Promise((resolve, reject) => {
|
|
32
|
+
const cleanup = () => {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
socket.removeEventListener("open", opened);
|
|
35
|
+
socket.removeEventListener("error", failed);
|
|
36
|
+
socket.removeEventListener("close", failed);
|
|
37
|
+
};
|
|
38
|
+
const opened = () => {
|
|
39
|
+
cleanup();
|
|
40
|
+
resolve();
|
|
41
|
+
};
|
|
42
|
+
const failed = () => {
|
|
43
|
+
cleanup();
|
|
44
|
+
socket.close();
|
|
45
|
+
reject(/* @__PURE__ */ new Error(`Cannot connect to owned browser CDP endpoint ${url}`));
|
|
46
|
+
};
|
|
47
|
+
const timer = setTimeout(failed, timeoutMs);
|
|
48
|
+
socket.addEventListener("open", opened, { once: true });
|
|
49
|
+
socket.addEventListener("error", failed, { once: true });
|
|
50
|
+
socket.addEventListener("close", failed, { once: true });
|
|
51
|
+
});
|
|
52
|
+
return connection;
|
|
53
|
+
}
|
|
54
|
+
command(method, params, timeoutMs = 1e4) {
|
|
55
|
+
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error(`CDP closed before ${method}`));
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const id = ++this.sequence;
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
this.pending.delete(id);
|
|
60
|
+
reject(/* @__PURE__ */ new Error(`CDP timeout after ${timeoutMs}ms: ${method}`));
|
|
61
|
+
}, Math.max(1, timeoutMs));
|
|
62
|
+
this.pending.set(id, {
|
|
63
|
+
resolve: (value) => resolve(value),
|
|
64
|
+
reject,
|
|
65
|
+
timer
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
this.socket.send(JSON.stringify({
|
|
69
|
+
id,
|
|
70
|
+
method,
|
|
71
|
+
params
|
|
72
|
+
}));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
this.pending.delete(id);
|
|
76
|
+
reject(error);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
on(method, listener) {
|
|
81
|
+
const wrapped = (params) => listener(params);
|
|
82
|
+
const listeners = this.listeners.get(method) ?? /* @__PURE__ */ new Set();
|
|
83
|
+
listeners.add(wrapped);
|
|
84
|
+
this.listeners.set(method, listeners);
|
|
85
|
+
return () => {
|
|
86
|
+
listeners.delete(wrapped);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
fail(error) {
|
|
90
|
+
this.closed = true;
|
|
91
|
+
for (const pending of this.pending.values()) {
|
|
92
|
+
clearTimeout(pending.timer);
|
|
93
|
+
pending.reject(error);
|
|
94
|
+
}
|
|
95
|
+
this.pending.clear();
|
|
96
|
+
}
|
|
97
|
+
close() {
|
|
98
|
+
this.fail(/* @__PURE__ */ new Error("CDP connection closed by harness"));
|
|
99
|
+
this.listeners.clear();
|
|
100
|
+
this.socket.close();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
export { CdpConnection };
|
|
106
|
+
//# sourceMappingURL=cdp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.mjs","names":[],"sources":["../../../../../../../../packages/core/src/e2e/cdp.ts"],"sourcesContent":["/** Wire shapes verified against the official protocol viewer and its source JSON:\n * https://chromedevtools.github.io/devtools-protocol/\n * https://github.com/ChromeDevTools/devtools-protocol/tree/master/json\n * Only the fields consumed by this harness are modeled here.\n */\ntype RemoteObject = { value?: unknown; description?: string };\ntype ExceptionDetails = { text: string; exception?: RemoteObject };\nexport type CdpMethods = {\n // https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-enable\n \"Runtime.enable\": { params: Record<string, never>; result: Record<string, never> };\n // https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate\n \"Runtime.evaluate\": {\n params: { expression: string; returnByValue: boolean; awaitPromise: boolean; silent: boolean };\n result: { result: RemoteObject; exceptionDetails?: ExceptionDetails };\n };\n // https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-enable\n \"Page.enable\": { params: Record<string, never>; result: Record<string, never> };\n // https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-navigate\n \"Page.navigate\": {\n params: { url: string };\n result: { frameId: string; loaderId?: string; errorText?: string };\n };\n // https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot\n \"Page.captureScreenshot\": { params: { format: \"png\" }; result: { data: string } };\n // https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride\n \"Emulation.setDeviceMetricsOverride\": {\n params: { width: number; height: number; deviceScaleFactor: number; mobile: boolean };\n result: Record<string, never>;\n };\n // https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchMouseEvent\n \"Input.dispatchMouseEvent\": {\n params: {\n type: \"mouseMoved\" | \"mousePressed\" | \"mouseReleased\";\n x: number;\n y: number;\n button?: \"left\";\n buttons?: number;\n clickCount?: number;\n };\n result: Record<string, never>;\n };\n // https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-insertText\n \"Input.insertText\": { params: { text: string }; result: Record<string, never> };\n // https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchKeyEvent\n \"Input.dispatchKeyEvent\": {\n params: { type: \"keyDown\" | \"keyUp\"; key: string; code: string; windowsVirtualKeyCode: number };\n result: Record<string, never>;\n };\n // https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-enable\n \"Network.enable\": { params: Record<string, never>; result: Record<string, never> };\n // https://chromedevtools.github.io/devtools-protocol/tot/Log/#method-enable\n \"Log.enable\": { params: Record<string, never>; result: Record<string, never> };\n};\nexport type CdpEvents = {\n // https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-loadEventFired\n \"Page.loadEventFired\": { timestamp: number };\n // https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#event-consoleAPICalled\n \"Runtime.consoleAPICalled\": { type: string; args: RemoteObject[] };\n // https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#event-exceptionThrown\n \"Runtime.exceptionThrown\": { exceptionDetails: ExceptionDetails };\n // https://chromedevtools.github.io/devtools-protocol/tot/Log/#event-entryAdded\n \"Log.entryAdded\": { entry: { level: string; text: string } };\n // https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-requestWillBeSent\n \"Network.requestWillBeSent\": { requestId: string; request: { url: string } };\n // https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-responseReceived\n \"Network.responseReceived\": { requestId: string; response: { url: string; status: number } };\n // https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-loadingFailed\n \"Network.loadingFailed\": { requestId: string; errorText: string; canceled?: boolean };\n // https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-loadingFinished\n \"Network.loadingFinished\": { requestId: string };\n};\ntype Pending = {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\nexport class CdpConnection {\n private sequence = 0;\n private pending = new Map<number, Pending>();\n private listeners = new Map<string, Set<(params: unknown) => void>>();\n private closed = false;\n\n private constructor(private socket: WebSocket) {\n socket.addEventListener(\"message\", ({ data }) => {\n try {\n const message = JSON.parse(String(data)) as {\n id?: number;\n method?: string;\n params?: unknown;\n result?: unknown;\n error?: { message: string };\n };\n if (typeof message.id === \"number\") {\n const pending = this.pending.get(message.id);\n if (!pending) return;\n clearTimeout(pending.timer);\n this.pending.delete(message.id);\n if (message.error) pending.reject(new Error(`CDP: ${message.error.message}`));\n else pending.resolve(message.result);\n } else if (message.method) {\n for (const listener of this.listeners.get(message.method) ?? []) listener(message.params);\n }\n } catch (error) {\n this.fail(new Error(`Invalid CDP message: ${String(error)}`));\n }\n });\n socket.addEventListener(\"close\", () => this.fail(new Error(\"CDP connection closed\")));\n socket.addEventListener(\"error\", () => this.fail(new Error(\"CDP WebSocket error\")));\n }\n\n static async connect(url: string, timeoutMs = 10_000): Promise<CdpConnection> {\n // Stable since Node 22.4, present in the minimum supported Node 22.15.\n // https://nodejs.org/download/release/v22.15.0/docs/api/globals.html#websocket\n if (typeof WebSocket !== \"function\")\n throw new Error(\n \"Browser e2e requires Node's global WebSocket (Node >=22.15, without --no-experimental-websocket)\",\n );\n const socket = new WebSocket(url);\n const connection = new CdpConnection(socket);\n await new Promise<void>((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timer);\n socket.removeEventListener(\"open\", opened);\n socket.removeEventListener(\"error\", failed);\n socket.removeEventListener(\"close\", failed);\n };\n const opened = () => {\n cleanup();\n resolve();\n };\n const failed = () => {\n cleanup();\n socket.close();\n reject(new Error(`Cannot connect to owned browser CDP endpoint ${url}`));\n };\n const timer = setTimeout(failed, timeoutMs);\n socket.addEventListener(\"open\", opened, { once: true });\n socket.addEventListener(\"error\", failed, { once: true });\n socket.addEventListener(\"close\", failed, { once: true });\n });\n return connection;\n }\n\n command<K extends keyof CdpMethods>(\n method: K,\n params: CdpMethods[K][\"params\"],\n timeoutMs = 10_000,\n ): Promise<CdpMethods[K][\"result\"]> {\n if (this.closed) return Promise.reject(new Error(`CDP closed before ${method}`));\n return new Promise<CdpMethods[K][\"result\"]>((resolve, reject) => {\n const id = ++this.sequence;\n const timer = setTimeout(\n () => {\n this.pending.delete(id);\n reject(new Error(`CDP timeout after ${timeoutMs}ms: ${method}`));\n },\n Math.max(1, timeoutMs),\n );\n this.pending.set(id, {\n resolve: (value) => resolve(value as CdpMethods[K][\"result\"]),\n reject,\n timer,\n });\n try {\n this.socket.send(JSON.stringify({ id, method, params }));\n } catch (error) {\n clearTimeout(timer);\n this.pending.delete(id);\n reject(error);\n }\n });\n }\n\n on<K extends keyof CdpEvents>(method: K, listener: (params: CdpEvents[K]) => void): () => void {\n const wrapped = (params: unknown) => listener(params as CdpEvents[K]);\n const listeners = this.listeners.get(method) ?? new Set();\n listeners.add(wrapped);\n this.listeners.set(method, listeners);\n return () => {\n listeners.delete(wrapped);\n };\n }\n\n private fail(error: Error): void {\n this.closed = true;\n for (const pending of this.pending.values()) {\n clearTimeout(pending.timer);\n pending.reject(error);\n }\n this.pending.clear();\n }\n\n close(): void {\n this.fail(new Error(\"CDP connection closed by harness\"));\n this.listeners.clear();\n this.socket.close();\n }\n}\n"],"mappings":"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"}
|