browser-debugger-cli 0.14.0 → 0.16.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/.claude/skills/bdg/SKILL.md +3 -2
- package/dist/cdp/methodTarget.d.ts +92 -0
- package/dist/cdp/methodTarget.js +159 -0
- package/dist/cdp/protocol.d.ts +16 -1
- package/dist/cdp/protocol.js +21 -0
- package/dist/cdp/schema.d.ts +55 -1
- package/dist/cdp/schema.js +134 -25
- package/dist/cdp/types.d.ts +3 -1
- package/dist/commands/cdp.d.ts +38 -1
- package/dist/commands/cdp.js +201 -133
- package/dist/commands/cleanup.js +21 -4
- package/dist/commands/dom/eval.d.ts +2 -1
- package/dist/commands/dom/eval.js +6 -21
- package/dist/commands/dom/formInteraction.js +8 -4
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- package/dist/commands/dom/helpers/index.d.ts +4 -4
- package/dist/commands/dom/helpers/index.js +3 -3
- package/dist/commands/dom/helpers/query.d.ts +2 -2
- package/dist/commands/dom/helpers/query.js +2 -2
- package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
- package/dist/commands/dom/helpers/screenshot.js +50 -668
- package/dist/commands/dom/screenshot.js +56 -36
- package/dist/commands/helpJson.d.ts +1 -1
- package/dist/commands/helpJson.js +3 -3
- package/dist/commands/helpTopic.js +10 -4
- package/dist/commands/network/har.js +18 -14
- package/dist/commands/optionBehaviors.js +24 -9
- package/dist/commands/shared/CommandRunner.d.ts +5 -0
- package/dist/commands/shared/CommandRunner.js +18 -3
- package/dist/commands/shared/interrupt.d.ts +40 -0
- package/dist/commands/shared/interrupt.js +73 -0
- package/dist/commands/shared/optionTypes.d.ts +3 -0
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/shared/startHelpers.d.ts +26 -3
- package/dist/commands/shared/startHelpers.js +145 -23
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/commands/types.d.ts +5 -0
- package/dist/connection/cdp.js +1 -16
- package/dist/connection/chromeIdentity.d.ts +24 -5
- package/dist/connection/chromeIdentity.js +53 -22
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +35 -2
- package/dist/connection/launcher.js +99 -12
- package/dist/connection/typed-cdp.d.ts +3 -2
- package/dist/constants.d.ts +3 -5
- package/dist/constants.js +3 -5
- package/dist/daemon/SessionController.d.ts +10 -5
- package/dist/daemon/SessionController.js +15 -8
- package/dist/daemon/ipcServer.js +1 -1
- package/dist/daemon/launcher.d.ts +22 -3
- package/dist/daemon/launcher.js +45 -8
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +9 -8
- package/dist/daemon/session/TelemetryStore.d.ts +5 -0
- package/dist/daemon/session/TelemetryStore.js +4 -0
- package/dist/daemon/session/captureGate.d.ts +59 -0
- package/dist/daemon/session/captureGate.js +96 -0
- package/dist/daemon/session/chromeConnection.d.ts +16 -1
- package/dist/daemon/session/chromeConnection.js +34 -4
- package/dist/daemon/session/collectors.d.ts +15 -0
- package/dist/daemon/session/collectors.js +39 -2
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +48 -13
- package/dist/daemon/session/downloads.d.ts +32 -0
- package/dist/daemon/session/downloads.js +96 -0
- package/dist/daemon/session/interactions.d.ts +3 -2
- package/dist/daemon/session/interactions.js +7 -2
- package/dist/daemon/session/plugins.js +6 -0
- package/dist/daemon.js +18520 -17014
- package/dist/errors/CommandError.d.ts +2 -0
- package/dist/errors/issues.d.ts +1 -1
- package/dist/errors/messages.d.ts +81 -0
- package/dist/errors/messages.js +198 -6
- package/dist/index.js +1446 -1078
- package/dist/ipc/client.d.ts +20 -2
- package/dist/ipc/client.js +32 -6
- package/dist/ipc/protocol/commands.d.ts +36 -2
- package/dist/ipc/protocol/commands.js +1 -0
- package/dist/ipc/protocol/domTypes.d.ts +24 -1
- package/dist/ipc/session/queries.d.ts +3 -0
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/IPCError.d.ts +9 -0
- package/dist/ipc/transport/IPCError.js +12 -0
- package/dist/ipc/transport/errors.d.ts +2 -1
- package/dist/ipc/transport/errors.js +4 -1
- package/dist/ipc/transport/index.d.ts +10 -2
- package/dist/ipc/transport/index.js +29 -4
- package/dist/runtime/dom/actionEffects.d.ts +48 -9
- package/dist/runtime/dom/actionEffects.js +269 -34
- package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
- package/dist/runtime/dom/actionEffectsScripts.js +101 -2
- package/dist/runtime/dom/captureArea.d.ts +35 -0
- package/dist/runtime/dom/captureArea.js +203 -0
- package/dist/runtime/dom/elementInfo.d.ts +13 -4
- package/dist/runtime/dom/elementInfo.js +12 -3
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- package/dist/runtime/page/bdgWorld.d.ts +9 -0
- package/dist/runtime/page/bdgWorld.js +11 -0
- package/dist/runtime/page/captureEmulation.d.ts +119 -0
- package/dist/runtime/page/captureEmulation.js +189 -0
- package/dist/runtime/page/captureScroll.d.ts +24 -0
- package/dist/runtime/page/captureScroll.js +124 -0
- package/dist/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/screenshot.d.ts +41 -0
- package/dist/runtime/page/screenshot.js +394 -0
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +52 -3
- package/dist/session/paths.js +179 -7
- package/dist/session/portClaims.d.ts +0 -8
- package/dist/session/portClaims.js +1 -22
- package/dist/session/sessionList.d.ts +5 -1
- package/dist/session/sessionList.js +5 -1
- package/dist/telemetry/downloads.d.ts +127 -0
- package/dist/telemetry/downloads.js +265 -0
- package/dist/telemetry/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +32 -9
- package/dist/telemetry/har/sanitize.d.ts +28 -0
- package/dist/telemetry/har/sanitize.js +184 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +78 -0
- package/dist/telemetry/har/sanitizeBody.js +541 -0
- package/dist/telemetry/har/types.d.ts +2 -0
- package/dist/telemetry/network.d.ts +4 -4
- package/dist/telemetry/network.js +38 -4
- package/dist/telemetry/networkRetention.d.ts +35 -14
- package/dist/telemetry/networkRetention.js +62 -26
- package/dist/types.d.ts +9 -14
- package/dist/ui/OutputBuilder.d.ts +3 -2
- package/dist/ui/OutputBuilder.js +4 -3
- package/dist/ui/formatters/cdp.d.ts +32 -9
- package/dist/ui/formatters/cdp.js +77 -6
- package/dist/ui/formatters/details.js +7 -15
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +7 -1
- package/dist/ui/formatters/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/formatters/status.js +6 -1
- package/dist/ui/formatting.d.ts +7 -0
- package/dist/ui/formatting.js +13 -0
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +27 -6
- package/dist/ui/messages/chrome.js +78 -12
- package/dist/ui/messages/commands.d.ts +71 -3
- package/dist/ui/messages/commands.js +98 -3
- package/dist/ui/messages/networkMessages.d.ts +50 -5
- package/dist/ui/messages/networkMessages.js +50 -6
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- package/dist/utils/async.d.ts +3 -2
- package/dist/utils/async.js +16 -3
- package/dist/utils/atomicFile.d.ts +2 -1
- package/dist/utils/atomicFile.js +5 -2
- package/dist/utils/directories.d.ts +41 -0
- package/dist/utils/directories.js +48 -0
- package/dist/utils/http.d.ts +11 -4
- package/dist/utils/http.js +5 -3
- package/package.json +18 -4
- /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
- /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
|
@@ -56,5 +56,51 @@ export declare function needsNoSandbox(): boolean;
|
|
|
56
56
|
* @returns True if running in Docker, false otherwise
|
|
57
57
|
*/
|
|
58
58
|
export declare function isDocker(): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Remove `HEADLESS` from this process's environment.
|
|
61
|
+
*
|
|
62
|
+
* chrome-launcher adds a bare `--headless` whenever the launching process has
|
|
63
|
+
* a non-empty `HEADLESS` variable (even `0` or `false`), whatever its
|
|
64
|
+
* `envVars` option says, which would make `--no-headless` launch headless.
|
|
65
|
+
* The daemon calls this before it launches Chrome.
|
|
66
|
+
*
|
|
67
|
+
* @param env - Environment to clean (defaults to `process.env`)
|
|
68
|
+
*/
|
|
69
|
+
export declare function dropLauncherHeadlessEnv(env?: NodeJS.ProcessEnv): void;
|
|
70
|
+
/**
|
|
71
|
+
* Build Chrome flags array from launch options.
|
|
72
|
+
*
|
|
73
|
+
* Uses chrome-launcher default flags as base (unless ignoreDefaultFlags is true)
|
|
74
|
+
* and layers bdg-specific overrides on top. Headless mode uses the new headless
|
|
75
|
+
* implementation for better compatibility.
|
|
76
|
+
*
|
|
77
|
+
* When running in Docker, automatically adds GPU-disabling flags to work around
|
|
78
|
+
* graphics limitations in containerized environments.
|
|
79
|
+
*
|
|
80
|
+
* Custom flags are passed via the chromeFlags option. The BDG_CHROME_FLAGS env var
|
|
81
|
+
* is parsed by the CLI and merged into chromeFlags before reaching this function.
|
|
82
|
+
* Feature lists from all sources end up in one `--disable-features` (and one
|
|
83
|
+
* `--enable-features`) flag, and each flag appears once.
|
|
84
|
+
*
|
|
85
|
+
* @param options - Launch options containing flag preferences
|
|
86
|
+
* @returns Array of Chrome command-line flags
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // Standard flags
|
|
91
|
+
* const flags = buildChromeFlags({ port: 9222 });
|
|
92
|
+
*
|
|
93
|
+
* // Headless with custom flags
|
|
94
|
+
* const flags = buildChromeFlags({
|
|
95
|
+
* port: 9222,
|
|
96
|
+
* headless: true,
|
|
97
|
+
* chromeFlags: ['--window-size=1920,1080']
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* // Docker environment (auto-detects)
|
|
101
|
+
* const flags = buildChromeFlags({ port: 9222 });
|
|
102
|
+
* // Includes --disable-gpu, --no-sandbox if in Docker
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
59
105
|
export declare function buildChromeFlags(options: FlagsBuilderOptions): string[];
|
|
60
106
|
//# sourceMappingURL=flagsBuilder.d.ts.map
|
|
@@ -68,6 +68,103 @@ export function isDocker() {
|
|
|
68
68
|
return false;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
const DISABLE_FEATURES = '--disable-features=';
|
|
72
|
+
const ENABLE_FEATURES = '--enable-features=';
|
|
73
|
+
/**
|
|
74
|
+
* Flags that bdg merges into one: Chrome reads only the last occurrence of
|
|
75
|
+
* these comma-separated feature lists, and chrome-launcher's defaults, bdg and
|
|
76
|
+
* users all pass them. `--disable-blink-features`/`--enable-blink-features`
|
|
77
|
+
* are not merged (neither bdg nor the defaults pass them), nor is `--js-flags`
|
|
78
|
+
* (space-separated V8 flags, not a feature list).
|
|
79
|
+
*/
|
|
80
|
+
const FEATURE_LIST_FLAGS = [DISABLE_FEATURES, ENABLE_FEATURES];
|
|
81
|
+
/**
|
|
82
|
+
* Bare name of a feature-list entry, without a `<Trial` or `:params` suffix.
|
|
83
|
+
*
|
|
84
|
+
* @param entry - Entry such as `Foo<Trial` or `Foo:param/1`
|
|
85
|
+
* @returns Feature name
|
|
86
|
+
*/
|
|
87
|
+
function featureName(entry) {
|
|
88
|
+
return entry.split(/[<:]/)[0] ?? entry;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Remove disabled features that are also enabled.
|
|
92
|
+
*
|
|
93
|
+
* Chrome lets `--disable-features` win over `--enable-features`, so without
|
|
94
|
+
* this a user's `--enable-features=MediaRouter` could not re-enable a feature
|
|
95
|
+
* chrome-launcher disables by default.
|
|
96
|
+
*
|
|
97
|
+
* @param features - Merged values per feature-list flag (changed in place)
|
|
98
|
+
*/
|
|
99
|
+
function dropReEnabledFeatures(features) {
|
|
100
|
+
const enabled = new Set([...(features.get(ENABLE_FEATURES) ?? [])].map(featureName));
|
|
101
|
+
const disabled = features.get(DISABLE_FEATURES);
|
|
102
|
+
disabled?.forEach((entry) => {
|
|
103
|
+
if (enabled.has(featureName(entry)))
|
|
104
|
+
disabled.delete(entry);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Merge every `--disable-features=` (and `--enable-features=`) flag into one
|
|
109
|
+
* and drop repeated flags.
|
|
110
|
+
*
|
|
111
|
+
* Chrome applies only the last occurrence of a feature-list flag, so bdg's or
|
|
112
|
+
* the user's list would otherwise switch chrome-launcher's defaults back on.
|
|
113
|
+
* The merged flag keeps the first one's position; its values keep their order
|
|
114
|
+
* without repeats, and an enabled feature is never also disabled. Only
|
|
115
|
+
* entries starting with `-` are deduplicated, so positional arguments such as
|
|
116
|
+
* URLs are passed as given.
|
|
117
|
+
*
|
|
118
|
+
* @param flags - Chrome flags in launch order
|
|
119
|
+
* @returns Flags with at most one flag per feature list and no repeated flags
|
|
120
|
+
*/
|
|
121
|
+
function mergeFeatureFlags(flags) {
|
|
122
|
+
const features = new Map();
|
|
123
|
+
const merged = [];
|
|
124
|
+
for (const flag of flags) {
|
|
125
|
+
const prefix = FEATURE_LIST_FLAGS.find((p) => flag.startsWith(p));
|
|
126
|
+
if (prefix) {
|
|
127
|
+
if (!features.has(prefix))
|
|
128
|
+
merged.push(prefix);
|
|
129
|
+
const values = flag.slice(prefix.length).split(',').filter(Boolean);
|
|
130
|
+
features.set(prefix, new Set([...(features.get(prefix) ?? []), ...values]));
|
|
131
|
+
}
|
|
132
|
+
else if (!flag.startsWith('-') || !merged.includes(flag)) {
|
|
133
|
+
merged.push(flag);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
dropReEnabledFeatures(features);
|
|
137
|
+
return merged.flatMap((flag) => {
|
|
138
|
+
const values = features.get(flag);
|
|
139
|
+
if (!values)
|
|
140
|
+
return [flag];
|
|
141
|
+
return values.size > 0 ? [flag + [...values].join(',')] : [];
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Remove `HEADLESS` from this process's environment.
|
|
146
|
+
*
|
|
147
|
+
* chrome-launcher adds a bare `--headless` whenever the launching process has
|
|
148
|
+
* a non-empty `HEADLESS` variable (even `0` or `false`), whatever its
|
|
149
|
+
* `envVars` option says, which would make `--no-headless` launch headless.
|
|
150
|
+
* The daemon calls this before it launches Chrome.
|
|
151
|
+
*
|
|
152
|
+
* @param env - Environment to clean (defaults to `process.env`)
|
|
153
|
+
*/
|
|
154
|
+
export function dropLauncherHeadlessEnv(env = process.env) {
|
|
155
|
+
delete env['HEADLESS'];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* chrome-launcher's default flags (plus its Linux sandbox flag). bdg passes
|
|
159
|
+
* them itself and tells chrome-launcher to skip its own copy, so each flag
|
|
160
|
+
* appears once; chrome-launcher adds `--remote-debugging-port`.
|
|
161
|
+
*
|
|
162
|
+
* @returns Default flags
|
|
163
|
+
*/
|
|
164
|
+
function defaultFlags() {
|
|
165
|
+
const flags = chromeLauncher.Launcher.defaultFlags();
|
|
166
|
+
return process.platform === 'linux' ? [...flags, '--disable-setuid-sandbox'] : flags;
|
|
167
|
+
}
|
|
71
168
|
/**
|
|
72
169
|
* Build Chrome flags array from launch options.
|
|
73
170
|
*
|
|
@@ -80,6 +177,8 @@ export function isDocker() {
|
|
|
80
177
|
*
|
|
81
178
|
* Custom flags are passed via the chromeFlags option. The BDG_CHROME_FLAGS env var
|
|
82
179
|
* is parsed by the CLI and merged into chromeFlags before reaching this function.
|
|
180
|
+
* Feature lists from all sources end up in one `--disable-features` (and one
|
|
181
|
+
* `--enable-features`) flag, and each flag appears once.
|
|
83
182
|
*
|
|
84
183
|
* @param options - Launch options containing flag preferences
|
|
85
184
|
* @returns Array of Chrome command-line flags
|
|
@@ -101,17 +200,6 @@ export function isDocker() {
|
|
|
101
200
|
* // Includes --disable-gpu, --no-sandbox if in Docker
|
|
102
201
|
* ```
|
|
103
202
|
*/
|
|
104
|
-
/**
|
|
105
|
-
* chrome-launcher's default flags (plus its Linux sandbox flag). bdg passes
|
|
106
|
-
* them itself and tells chrome-launcher to skip its own copy, so each flag
|
|
107
|
-
* appears once; chrome-launcher adds `--remote-debugging-port`.
|
|
108
|
-
*
|
|
109
|
-
* @returns Default flags
|
|
110
|
-
*/
|
|
111
|
-
function defaultFlags() {
|
|
112
|
-
const flags = chromeLauncher.Launcher.defaultFlags();
|
|
113
|
-
return process.platform === 'linux' ? [...flags, '--disable-setuid-sandbox'] : flags;
|
|
114
|
-
}
|
|
115
203
|
export function buildChromeFlags(options) {
|
|
116
204
|
const baseFlags = options.ignoreDefaultFlags ? [] : defaultFlags();
|
|
117
205
|
const bdgFlags = [
|
|
@@ -120,18 +208,14 @@ export function buildChromeFlags(options) {
|
|
|
120
208
|
];
|
|
121
209
|
const dockerFlags = isDocker() ? DOCKER_CHROME_FLAGS : [];
|
|
122
210
|
const sandboxFlags = needsNoSandbox() ? ['--no-sandbox'] : [];
|
|
123
|
-
// Custom flags from CLI option (env var BDG_CHROME_FLAGS is parsed by CLI and passed here)
|
|
124
211
|
const customFlags = options.chromeFlags ?? [];
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
];
|
|
134
|
-
}
|
|
135
|
-
return [...baseFlags, ...bdgFlags, ...dockerFlags, ...sandboxFlags, ...customFlags];
|
|
212
|
+
return mergeFeatureFlags([
|
|
213
|
+
...(options.headless ? [HEADLESS_FLAG] : []),
|
|
214
|
+
...baseFlags,
|
|
215
|
+
...bdgFlags,
|
|
216
|
+
...dockerFlags,
|
|
217
|
+
...sandboxFlags,
|
|
218
|
+
...customFlags,
|
|
219
|
+
]);
|
|
136
220
|
}
|
|
137
221
|
//# sourceMappingURL=flagsBuilder.js.map
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { LaunchedChrome, Logger } from './types.js';
|
|
2
2
|
import type { Options as ChromeLaunchOptions } from 'chrome-launcher';
|
|
3
|
+
import { ChromeLaunchError } from './errors.js';
|
|
3
4
|
/**
|
|
4
5
|
* Options that control how Chrome is launched for CDP sessions.
|
|
5
6
|
* Extended to support chrome-launcher advanced features.
|
|
6
7
|
*/
|
|
7
|
-
export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'connectionPollInterval' | 'maxConnectionRetries' | 'portStrictMode' | 'envVars' | '
|
|
8
|
+
export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'connectionPollInterval' | 'maxConnectionRetries' | 'portStrictMode' | 'envVars' | 'ignoreDefaultFlags' | 'chromeFlags' | 'chromePath'> {
|
|
8
9
|
/** Remote debugging port (defaults to 9222 when omitted) */
|
|
9
10
|
port?: number;
|
|
10
11
|
/**
|
|
@@ -29,6 +30,11 @@ export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'c
|
|
|
29
30
|
chromePath?: string;
|
|
30
31
|
/** bdg session directory: recorded as a marker flag on the Chrome command line, and holds the default profile */
|
|
31
32
|
sessionDir?: string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Ends the launch when aborted (the session was stopped): bdg stops waiting
|
|
35
|
+
* and kills Chrome as soon as chrome-launcher has spawned it
|
|
36
|
+
*/
|
|
37
|
+
signal?: AbortSignal | undefined;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* Launch Chrome with remote debugging enabled using chrome-launcher.
|
|
@@ -42,7 +48,7 @@ export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'c
|
|
|
42
48
|
*
|
|
43
49
|
* @param options - Launch configuration options
|
|
44
50
|
* @returns LaunchedChrome instance with PID and kill method
|
|
45
|
-
* @throws ChromeLaunchError if Chrome fails to launch, process dies immediately,
|
|
51
|
+
* @throws ChromeLaunchError if Chrome fails to launch, process dies immediately, CDP doesn't become available, or `options.signal` aborts
|
|
46
52
|
* @throws Error if user data directory cannot be created
|
|
47
53
|
*
|
|
48
54
|
* @remarks
|
|
@@ -50,6 +56,32 @@ export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'c
|
|
|
50
56
|
* Uses chrome-launcher for cross-platform Chrome detection and launching.
|
|
51
57
|
*/
|
|
52
58
|
export declare function launchChrome(options?: LaunchOptions): Promise<LaunchedChrome>;
|
|
59
|
+
/**
|
|
60
|
+
* State of the launch when chrome-launcher rejected.
|
|
61
|
+
*/
|
|
62
|
+
interface LaunchFailureContext {
|
|
63
|
+
/** Debugging port */
|
|
64
|
+
port: number;
|
|
65
|
+
/** Chrome's PID (0 if it was not spawned) */
|
|
66
|
+
pid: number;
|
|
67
|
+
/** Whether Chrome was still running when chrome-launcher rejected */
|
|
68
|
+
chromeAlive: boolean;
|
|
69
|
+
/** How long chrome-launcher waited for the port to open */
|
|
70
|
+
readyBudgetMs: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The error for a launch chrome-launcher rejected.
|
|
74
|
+
*
|
|
75
|
+
* A refused connection while Chrome is still running means Chrome did not
|
|
76
|
+
* open its port within the readiness budget (a slow start, e.g. the first
|
|
77
|
+
* one on a cold machine): the port was free right before the launch and
|
|
78
|
+
* nothing answers on it, so it is not a conflict, and Chrome did not crash.
|
|
79
|
+
*
|
|
80
|
+
* @param error - chrome-launcher's rejection
|
|
81
|
+
* @param context - Port, Chrome's PID and liveness, and the readiness budget
|
|
82
|
+
* @returns CHROME_PORT_NOT_OPENED for a slow start, else CHROME_LAUNCH_FAILED
|
|
83
|
+
*/
|
|
84
|
+
export declare function launchFailedError(error: unknown, { port, pid, chromeAlive, readyBudgetMs }: LaunchFailureContext): ChromeLaunchError;
|
|
53
85
|
/**
|
|
54
86
|
* Preferences for the launched profile: bdg's defaults (no translate prompt,
|
|
55
87
|
* no password manager or leak check, whose bubbles capture input) for
|
|
@@ -65,4 +97,5 @@ export declare function launchChrome(options?: LaunchOptions): Promise<LaunchedC
|
|
|
65
97
|
* @throws ChromeLaunchError if prefs file cannot be read or parsed, or prefs are not JSON
|
|
66
98
|
*/
|
|
67
99
|
export declare function resolveChromePrefs(options: LaunchOptions): Record<string, unknown>;
|
|
100
|
+
export {};
|
|
68
101
|
//# sourceMappingURL=launcher.d.ts.map
|
|
@@ -2,12 +2,14 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as chromeLauncher from 'chrome-launcher';
|
|
5
|
-
import { BDG_CHROME_PREFS, DEFAULT_CDP_PORT, CHROME_PROFILE_DIR, DEFAULT_CHROME_LOG_LEVEL,
|
|
5
|
+
import { BDG_CHROME_PREFS, DEFAULT_CDP_PORT, CHROME_PROFILE_DIR, DEFAULT_CHROME_LOG_LEVEL, } from '../constants.js';
|
|
6
|
+
import { chromePortNotOpenedMessage } from '../ui/messages/chrome.js';
|
|
7
|
+
import { delay } from '../utils/async.js';
|
|
6
8
|
import { makeDirectory } from '../utils/directories.js';
|
|
7
9
|
import { getErrorMessage } from '../utils/errors.js';
|
|
8
10
|
import { filterDefined } from '../utils/objects.js';
|
|
9
11
|
import { isProcessAlive } from '../utils/process.js';
|
|
10
|
-
import { verifyLaunchedChrome } from './chromeIdentity.js';
|
|
12
|
+
import { launchAbortedError, throwIfLaunchAborted, verifyLaunchedChrome, } from './chromeIdentity.js';
|
|
11
13
|
import { ChromeLaunchError } from './errors.js';
|
|
12
14
|
import { resolveChromeBinary } from './launcher/binaryResolver.js';
|
|
13
15
|
import { buildChromeFlags } from './launcher/flagsBuilder.js';
|
|
@@ -27,6 +29,8 @@ import { markStartupLogs, watchStartupExit } from './startupExit.js';
|
|
|
27
29
|
const CHROME_READY_POLL_MS = 50;
|
|
28
30
|
/** Checks before giving up: 25 s in all, as with chrome-launcher's defaults */
|
|
29
31
|
const CHROME_READY_POLL_ATTEMPTS = 500;
|
|
32
|
+
/** How often an aborted launch checks whether chrome-launcher has spawned Chrome yet */
|
|
33
|
+
const SPAWN_POLL_MS = 10;
|
|
30
34
|
const defaultLogger = {
|
|
31
35
|
info: (msg) => console.error(msg),
|
|
32
36
|
debug: () => { }, // No-op by default
|
|
@@ -43,7 +47,7 @@ const defaultLogger = {
|
|
|
43
47
|
*
|
|
44
48
|
* @param options - Launch configuration options
|
|
45
49
|
* @returns LaunchedChrome instance with PID and kill method
|
|
46
|
-
* @throws ChromeLaunchError if Chrome fails to launch, process dies immediately,
|
|
50
|
+
* @throws ChromeLaunchError if Chrome fails to launch, process dies immediately, CDP doesn't become available, or `options.signal` aborts
|
|
47
51
|
* @throws Error if user data directory cannot be created
|
|
48
52
|
*
|
|
49
53
|
* @remarks
|
|
@@ -79,13 +83,14 @@ export async function launchChrome(options = {}) {
|
|
|
79
83
|
logger.debug(`User data directory: ${userDataDir}`);
|
|
80
84
|
applyProfilePreferences(userDataDir, options, logger);
|
|
81
85
|
const chromeOptions = buildChromeOptions({ ...options, port });
|
|
86
|
+
throwIfLaunchAborted(options.signal);
|
|
82
87
|
const launcher = new chromeLauncher.Launcher(chromeOptions);
|
|
83
88
|
const logs = markStartupLogs(userDataDir);
|
|
84
89
|
const startup = watchStartupExit(() => launcher.chromeProcess, logs, userDataDir);
|
|
85
90
|
try {
|
|
86
91
|
const launchStart = Date.now();
|
|
87
92
|
logger.info('Waiting for Chrome to be ready...');
|
|
88
|
-
await
|
|
93
|
+
await launchUnlessAborted(launcher, startup.exited, options.signal);
|
|
89
94
|
startup.stop();
|
|
90
95
|
const launchDurationMs = Date.now() - launchStart;
|
|
91
96
|
logger.info(`✓ Chrome ready (${launchDurationMs}ms)`);
|
|
@@ -101,7 +106,7 @@ export async function launchChrome(options = {}) {
|
|
|
101
106
|
},
|
|
102
107
|
});
|
|
103
108
|
}
|
|
104
|
-
await verifyLaunchedChrome({ logs, port, pid: chromeProcessPid });
|
|
109
|
+
await verifyLaunchedChrome({ logs, port, pid: chromeProcessPid, signal: options.signal });
|
|
105
110
|
logger.info(`Chrome launched successfully (PID: ${chromeProcessPid}, ${launchDurationMs}ms)`);
|
|
106
111
|
return {
|
|
107
112
|
pid: chromeProcessPid,
|
|
@@ -117,20 +122,103 @@ export async function launchChrome(options = {}) {
|
|
|
117
122
|
}
|
|
118
123
|
catch (error) {
|
|
119
124
|
startup.stop();
|
|
125
|
+
const pid = launcher.chromeProcess?.pid ?? 0;
|
|
126
|
+
const chromeAlive = pid > 0 && isProcessAlive(pid);
|
|
120
127
|
launcher.kill();
|
|
121
128
|
launcher.destroyTmp();
|
|
122
129
|
if (error instanceof ChromeLaunchError) {
|
|
123
130
|
throw error;
|
|
124
131
|
}
|
|
125
|
-
throw
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
},
|
|
132
|
+
throw launchFailedError(error, {
|
|
133
|
+
port,
|
|
134
|
+
pid,
|
|
135
|
+
chromeAlive,
|
|
136
|
+
readyBudgetMs: readyBudgetMs(chromeOptions),
|
|
131
137
|
});
|
|
132
138
|
}
|
|
133
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* The error for a launch chrome-launcher rejected.
|
|
142
|
+
*
|
|
143
|
+
* A refused connection while Chrome is still running means Chrome did not
|
|
144
|
+
* open its port within the readiness budget (a slow start, e.g. the first
|
|
145
|
+
* one on a cold machine): the port was free right before the launch and
|
|
146
|
+
* nothing answers on it, so it is not a conflict, and Chrome did not crash.
|
|
147
|
+
*
|
|
148
|
+
* @param error - chrome-launcher's rejection
|
|
149
|
+
* @param context - Port, Chrome's PID and liveness, and the readiness budget
|
|
150
|
+
* @returns CHROME_PORT_NOT_OPENED for a slow start, else CHROME_LAUNCH_FAILED
|
|
151
|
+
*/
|
|
152
|
+
export function launchFailedError(error, { port, pid, chromeAlive, readyBudgetMs }) {
|
|
153
|
+
const cause = error instanceof Error ? { cause: error } : {};
|
|
154
|
+
if (chromeAlive && isConnectionRefused(error)) {
|
|
155
|
+
return new ChromeLaunchError(chromePortNotOpenedMessage(pid, port, readyBudgetMs), {
|
|
156
|
+
...cause,
|
|
157
|
+
issue: { code: 'CHROME_PORT_NOT_OPENED', context: { port, pid, waitedMs: readyBudgetMs } },
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return new ChromeLaunchError(`Failed to launch Chrome: ${getErrorMessage(error)}`, {
|
|
161
|
+
...cause,
|
|
162
|
+
issue: { code: 'CHROME_LAUNCH_FAILED', context: { port, reason: getErrorMessage(error) } },
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Whether an error is a refused TCP connection.
|
|
167
|
+
*
|
|
168
|
+
* @param error - Error to check
|
|
169
|
+
* @returns True for ECONNREFUSED
|
|
170
|
+
*/
|
|
171
|
+
function isConnectionRefused(error) {
|
|
172
|
+
return error?.code === 'ECONNREFUSED';
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* How long chrome-launcher waits for Chrome's debugging port to open.
|
|
176
|
+
*
|
|
177
|
+
* @param chromeOptions - chrome-launcher options
|
|
178
|
+
* @returns Poll interval times the number of polls
|
|
179
|
+
*/
|
|
180
|
+
function readyBudgetMs(chromeOptions) {
|
|
181
|
+
return ((chromeOptions.connectionPollInterval ?? CHROME_READY_POLL_MS) *
|
|
182
|
+
(chromeOptions.maxConnectionRetries ?? CHROME_READY_POLL_ATTEMPTS));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Run chrome-launcher's launch (spawn Chrome, wait for its port to open),
|
|
186
|
+
* ending early when Chrome exits or `signal` aborts.
|
|
187
|
+
*
|
|
188
|
+
* chrome-launcher cannot be cancelled: after an abort its port poller keeps
|
|
189
|
+
* running, bdg just stops waiting for it. An abort can arrive before
|
|
190
|
+
* chrome-launcher has spawned Chrome (it first checks whether the port
|
|
191
|
+
* answers), so this waits until Chrome is spawned or the launch has ended
|
|
192
|
+
* without one; the caller's kill then reaches that Chrome.
|
|
193
|
+
*
|
|
194
|
+
* @param launcher - chrome-launcher instance, not launched yet
|
|
195
|
+
* @param exited - Rejects when Chrome exits during startup
|
|
196
|
+
* @param signal - The launch's abort signal
|
|
197
|
+
* @throws ChromeLaunchError if `signal` aborts (before or during the launch)
|
|
198
|
+
* @throws Error from chrome-launcher or `exited`
|
|
199
|
+
*/
|
|
200
|
+
async function launchUnlessAborted(launcher, exited, signal) {
|
|
201
|
+
throwIfLaunchAborted(signal);
|
|
202
|
+
let settled = false;
|
|
203
|
+
const launching = launcher.launch().finally(() => {
|
|
204
|
+
settled = true;
|
|
205
|
+
});
|
|
206
|
+
let onAbort = () => { };
|
|
207
|
+
const aborted = new Promise((resolve) => {
|
|
208
|
+
onAbort = () => resolve('aborted');
|
|
209
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
210
|
+
});
|
|
211
|
+
try {
|
|
212
|
+
if ((await Promise.race([launching, exited, aborted])) !== 'aborted')
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
signal?.removeEventListener('abort', onAbort);
|
|
217
|
+
}
|
|
218
|
+
while (!launcher.chromeProcess && !settled)
|
|
219
|
+
await delay(SPAWN_POLL_MS);
|
|
220
|
+
throw launchAbortedError();
|
|
221
|
+
}
|
|
134
222
|
/**
|
|
135
223
|
* Get the default persistent user-data-dir path.
|
|
136
224
|
*
|
|
@@ -224,7 +312,6 @@ function buildChromeOptions(options) {
|
|
|
224
312
|
const chromePathOverride = resolveChromeBinary(options);
|
|
225
313
|
return {
|
|
226
314
|
logLevel: options.logLevel ?? DEFAULT_CHROME_LOG_LEVEL,
|
|
227
|
-
handleSIGINT: options.handleSIGINT ?? DEFAULT_CHROME_HANDLE_SIGINT,
|
|
228
315
|
ignoreDefaultFlags: true,
|
|
229
316
|
chromeFlags: buildChromeFlags(options),
|
|
230
317
|
userDataDir,
|
|
@@ -18,9 +18,10 @@ import type { Protocol } from 'devtools-protocol/types/protocol.js';
|
|
|
18
18
|
* Extract parameter type from a CDP command.
|
|
19
19
|
*
|
|
20
20
|
* If command has no parameters, returns empty object type.
|
|
21
|
-
* If command has single parameter
|
|
21
|
+
* If command has a single parameter (required, or optional when all its
|
|
22
|
+
* fields are), returns its type.
|
|
22
23
|
*/
|
|
23
|
-
type CommandParams<T extends keyof ProtocolMapping.Commands> = ProtocolMapping.Commands[T]['paramsType'] extends [
|
|
24
|
+
type CommandParams<T extends keyof ProtocolMapping.Commands> = ProtocolMapping.Commands[T]['paramsType'] extends [] ? Record<string, never> : ProtocolMapping.Commands[T]['paramsType'] extends [(infer P)?] ? NonNullable<P> : never;
|
|
24
25
|
/**
|
|
25
26
|
* Extract return type from a CDP command.
|
|
26
27
|
*/
|
package/dist/constants.d.ts
CHANGED
|
@@ -12,10 +12,6 @@ export declare const DEFAULT_CDP_PORT = 9222;
|
|
|
12
12
|
* Default Chrome launcher log level for quiet operation
|
|
13
13
|
*/
|
|
14
14
|
export declare const DEFAULT_CHROME_LOG_LEVEL = "silent";
|
|
15
|
-
/**
|
|
16
|
-
* Default SIGINT handling - bdg handles signals, not chrome-launcher
|
|
17
|
-
*/
|
|
18
|
-
export declare const DEFAULT_CHROME_HANDLE_SIGINT = false;
|
|
19
15
|
/**
|
|
20
16
|
* Persistent Chrome profile directory path (relative to user home)
|
|
21
17
|
*/
|
|
@@ -90,7 +86,7 @@ export declare const OBJECT_EXPANSION_FAILURE_THRESHOLD = 5;
|
|
|
90
86
|
*/
|
|
91
87
|
export declare const MAX_RESPONSE_SIZE: number;
|
|
92
88
|
/**
|
|
93
|
-
* Total size of the response bodies a session keeps (100MB)
|
|
89
|
+
* Total size of the request and response bodies a session keeps (100MB)
|
|
94
90
|
* Past this the oldest bodies are replaced by a placeholder; their requests stay
|
|
95
91
|
*/
|
|
96
92
|
export declare const MAX_TOTAL_BODY_BYTES: number;
|
|
@@ -124,6 +120,8 @@ export declare const CHROME_NETWORK_BUFFER_PER_RESOURCE: number;
|
|
|
124
120
|
export declare const CHROME_POST_DATA_LIMIT: number;
|
|
125
121
|
/** Matches `dom query` and `dom a11y query` list with `--json` and no `--limit` */
|
|
126
122
|
export declare const QUERY_JSON_LIST_LIMIT = 100;
|
|
123
|
+
/** Elements of an array result `dom eval --json` lists (the rest are counted as omitted) */
|
|
124
|
+
export declare const EVAL_JSON_ARRAY_LIMIT = 100;
|
|
127
125
|
/** Matches `dom layout` measures per command (the rest are counted as omitted) */
|
|
128
126
|
export declare const LAYOUT_ELEMENT_LIMIT = 100;
|
|
129
127
|
/**
|
package/dist/constants.js
CHANGED
|
@@ -15,10 +15,6 @@ export const DEFAULT_CDP_PORT = 9222;
|
|
|
15
15
|
* Default Chrome launcher log level for quiet operation
|
|
16
16
|
*/
|
|
17
17
|
export const DEFAULT_CHROME_LOG_LEVEL = 'silent';
|
|
18
|
-
/**
|
|
19
|
-
* Default SIGINT handling - bdg handles signals, not chrome-launcher
|
|
20
|
-
*/
|
|
21
|
-
export const DEFAULT_CHROME_HANDLE_SIGINT = false;
|
|
22
18
|
/**
|
|
23
19
|
* Persistent Chrome profile directory path (relative to user home)
|
|
24
20
|
*/
|
|
@@ -122,7 +118,7 @@ export const OBJECT_EXPANSION_FAILURE_THRESHOLD = 5;
|
|
|
122
118
|
*/
|
|
123
119
|
export const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
124
120
|
/**
|
|
125
|
-
* Total size of the response bodies a session keeps (100MB)
|
|
121
|
+
* Total size of the request and response bodies a session keeps (100MB)
|
|
126
122
|
* Past this the oldest bodies are replaced by a placeholder; their requests stay
|
|
127
123
|
*/
|
|
128
124
|
export const MAX_TOTAL_BODY_BYTES = 100 * 1024 * 1024;
|
|
@@ -165,6 +161,8 @@ export const CHROME_POST_DATA_LIMIT = 1 * 1024 * 1024; // 1MB
|
|
|
165
161
|
// ============================================================================
|
|
166
162
|
/** Matches `dom query` and `dom a11y query` list with `--json` and no `--limit` */
|
|
167
163
|
export const QUERY_JSON_LIST_LIMIT = 100;
|
|
164
|
+
/** Elements of an array result `dom eval --json` lists (the rest are counted as omitted) */
|
|
165
|
+
export const EVAL_JSON_ARRAY_LIMIT = 100;
|
|
168
166
|
/** Matches `dom layout` measures per command (the rest are counted as omitted) */
|
|
169
167
|
export const LAYOUT_ELEMENT_LIMIT = 100;
|
|
170
168
|
/**
|
|
@@ -41,7 +41,8 @@ export declare class SessionController {
|
|
|
41
41
|
stopActiveSession(): Promise<boolean>;
|
|
42
42
|
/**
|
|
43
43
|
* Error fields for a request that needs the session when there is none:
|
|
44
|
-
* still starting (85), or none at all
|
|
44
|
+
* still starting (85), or none at all (also while an abandoned start is
|
|
45
|
+
* torn down).
|
|
45
46
|
*
|
|
46
47
|
* @returns Error message, plus exit code and suggestion while starting
|
|
47
48
|
*/
|
|
@@ -78,15 +79,19 @@ export declare class SessionController {
|
|
|
78
79
|
* Execute a session command (dom_*, cdp_call, session_details, ...).
|
|
79
80
|
*
|
|
80
81
|
* @param request - Command request
|
|
82
|
+
* @param abandoned - Aborted when the requesting client disconnects
|
|
81
83
|
* @returns Command response, forwarding exit code and suggestion on failure
|
|
82
84
|
*/
|
|
83
|
-
command(request: ClientRequestUnion): Promise<unknown>;
|
|
85
|
+
command(request: ClientRequestUnion, abandoned?: AbortSignal): Promise<unknown>;
|
|
84
86
|
/**
|
|
85
87
|
* Start a session, or report the one already running.
|
|
86
88
|
*
|
|
87
|
-
* A start whose client disconnects (Ctrl-C
|
|
88
|
-
*
|
|
89
|
-
* learns that it exists.
|
|
89
|
+
* A start whose client disconnects (Ctrl-C, or a client killed without a
|
|
90
|
+
* clean disconnect) is abandoned: the session is stopped, whether it is
|
|
91
|
+
* still launching or has just started, since nobody learns that it exists.
|
|
92
|
+
* From then on the daemon reports itself shutting down (a new start gets
|
|
93
|
+
* the retryable `SESSION_SHUTTING_DOWN`, status shows the session ending),
|
|
94
|
+
* not starting, while Chrome is torn down.
|
|
90
95
|
*
|
|
91
96
|
* @param request - Start session request
|
|
92
97
|
* @param abandoned - Aborted when the requesting client disconnects
|
|
@@ -149,12 +149,13 @@ export class SessionController {
|
|
|
149
149
|
}
|
|
150
150
|
/**
|
|
151
151
|
* Error fields for a request that needs the session when there is none:
|
|
152
|
-
* still starting (85), or none at all
|
|
152
|
+
* still starting (85), or none at all (also while an abandoned start is
|
|
153
|
+
* torn down).
|
|
153
154
|
*
|
|
154
155
|
* @returns Error message, plus exit code and suggestion while starting
|
|
155
156
|
*/
|
|
156
157
|
noSessionError() {
|
|
157
|
-
if (!this.launching)
|
|
158
|
+
if (!this.launching || this.closing)
|
|
158
159
|
return { error: noActiveSessionMessage() };
|
|
159
160
|
return {
|
|
160
161
|
error: STARTING_ERROR,
|
|
@@ -190,7 +191,7 @@ export class SessionController {
|
|
|
190
191
|
};
|
|
191
192
|
const base = { type: 'status_response', sessionId: request.sessionId };
|
|
192
193
|
if (!this.session || this.session.stopRequested()) {
|
|
193
|
-
if (this.launching) {
|
|
194
|
+
if (this.launching && !this.closing) {
|
|
194
195
|
data.starting = { url: this.launching.url, since: this.launching.since };
|
|
195
196
|
}
|
|
196
197
|
if (this.session || this.closing)
|
|
@@ -250,6 +251,7 @@ export class SessionController {
|
|
|
250
251
|
},
|
|
251
252
|
currentNavigationId: data.currentNavigationId,
|
|
252
253
|
...(data.pageCrashedAt !== undefined && { pageCrashedAt: data.pageCrashedAt }),
|
|
254
|
+
...(data.downloads && { downloads: data.downloads }),
|
|
253
255
|
partial: true,
|
|
254
256
|
},
|
|
255
257
|
},
|
|
@@ -282,9 +284,10 @@ export class SessionController {
|
|
|
282
284
|
* Execute a session command (dom_*, cdp_call, session_details, ...).
|
|
283
285
|
*
|
|
284
286
|
* @param request - Command request
|
|
287
|
+
* @param abandoned - Aborted when the requesting client disconnects
|
|
285
288
|
* @returns Command response, forwarding exit code and suggestion on failure
|
|
286
289
|
*/
|
|
287
|
-
async command(request) {
|
|
290
|
+
async command(request, abandoned) {
|
|
288
291
|
const name = request.type.slice(0, -'_request'.length);
|
|
289
292
|
const base = { type: `${name}_response`, sessionId: request.sessionId };
|
|
290
293
|
if (!this.session) {
|
|
@@ -293,7 +296,7 @@ export class SessionController {
|
|
|
293
296
|
const { sessionId: _sessionId, type: _type, ...params } = request;
|
|
294
297
|
try {
|
|
295
298
|
const execute = this.session.execute.bind(this.session);
|
|
296
|
-
const data = await withTimeout(execute(name, params), commandTimeoutMs(name, params), 'Command');
|
|
299
|
+
const data = await withTimeout(execute(name, params, abandoned), commandTimeoutMs(name, params), 'Command');
|
|
297
300
|
return { ...base, status: 'ok', data };
|
|
298
301
|
}
|
|
299
302
|
catch (error) {
|
|
@@ -303,9 +306,12 @@ export class SessionController {
|
|
|
303
306
|
/**
|
|
304
307
|
* Start a session, or report the one already running.
|
|
305
308
|
*
|
|
306
|
-
* A start whose client disconnects (Ctrl-C
|
|
307
|
-
*
|
|
308
|
-
* learns that it exists.
|
|
309
|
+
* A start whose client disconnects (Ctrl-C, or a client killed without a
|
|
310
|
+
* clean disconnect) is abandoned: the session is stopped, whether it is
|
|
311
|
+
* still launching or has just started, since nobody learns that it exists.
|
|
312
|
+
* From then on the daemon reports itself shutting down (a new start gets
|
|
313
|
+
* the retryable `SESSION_SHUTTING_DOWN`, status shows the session ending),
|
|
314
|
+
* not starting, while Chrome is torn down.
|
|
309
315
|
*
|
|
310
316
|
* @param request - Start session request
|
|
311
317
|
* @param abandoned - Aborted when the requesting client disconnects
|
|
@@ -330,6 +336,7 @@ export class SessionController {
|
|
|
330
336
|
this.launching = launching;
|
|
331
337
|
const stopAbandoned = () => {
|
|
332
338
|
log.info('Client disconnected during start; stopping the session');
|
|
339
|
+
this.closing = true;
|
|
333
340
|
void launching.session?.stop('normal');
|
|
334
341
|
};
|
|
335
342
|
abandoned?.addEventListener('abort', stopAbandoned, { once: true });
|
package/dist/daemon/ipcServer.js
CHANGED
|
@@ -196,7 +196,7 @@ export class IPCServer {
|
|
|
196
196
|
*/
|
|
197
197
|
async route(message, disconnected) {
|
|
198
198
|
if (isCommandRequest(message.type)) {
|
|
199
|
-
return this.controller.command(message);
|
|
199
|
+
return this.controller.command(message, disconnected);
|
|
200
200
|
}
|
|
201
201
|
switch (message.type) {
|
|
202
202
|
case 'handshake_request':
|