browser-debugger-cli 0.8.0 → 0.9.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/README.md +4 -1
- package/dist/cdp/schema.d.ts +4 -1
- package/dist/cdp/schema.js +48 -7
- package/dist/commands/cdp.js +3 -2
- package/dist/commands/cleanup.d.ts +11 -0
- package/dist/commands/cleanup.js +161 -57
- package/dist/commands/console.d.ts +20 -1
- package/dist/commands/console.js +57 -17
- package/dist/commands/details.js +3 -2
- package/dist/commands/dom/DomElementResolver.d.ts +10 -3
- package/dist/commands/dom/DomElementResolver.js +35 -17
- package/dist/commands/dom/a11y.d.ts +10 -0
- package/dist/commands/dom/a11y.js +27 -5
- package/dist/commands/dom/eval.d.ts +3 -1
- package/dist/commands/dom/eval.js +29 -4
- package/dist/commands/dom/form.js +16 -62
- package/dist/commands/dom/formInteraction.js +152 -113
- package/dist/commands/dom/formSummary.d.ts +49 -0
- package/dist/commands/dom/formSummary.js +180 -0
- package/dist/commands/dom/frames.d.ts +2 -1
- package/dist/commands/dom/frames.js +17 -2
- package/dist/commands/dom/get.d.ts +6 -5
- package/dist/commands/dom/get.js +92 -82
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/query.d.ts +44 -17
- package/dist/commands/dom/helpers/query.js +244 -97
- package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
- package/dist/commands/dom/helpers/runElementCommand.js +97 -30
- package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
- package/dist/commands/dom/helpers/screenshot.js +164 -49
- package/dist/commands/dom/index.d.ts +3 -1
- package/dist/commands/dom/index.js +16 -6
- package/dist/commands/dom/layout.d.ts +14 -0
- package/dist/commands/dom/layout.js +54 -0
- package/dist/commands/dom/listeners.d.ts +5 -1
- package/dist/commands/dom/listeners.js +13 -3
- package/dist/commands/dom/query.js +2 -3
- package/dist/commands/dom/screenshot.d.ts +12 -2
- package/dist/commands/dom/screenshot.js +27 -3
- package/dist/commands/dom/semanticUtils.d.ts +6 -13
- package/dist/commands/dom/semanticUtils.js +15 -19
- package/dist/commands/dom/wait.d.ts +13 -0
- package/dist/commands/dom/wait.js +83 -0
- package/dist/commands/helpJson.js +2 -2
- package/dist/commands/network/list.js +4 -11
- package/dist/commands/optionBehaviors.js +112 -21
- package/dist/commands/page.d.ts +2 -1
- package/dist/commands/page.js +41 -5
- package/dist/commands/peek.js +4 -11
- package/dist/commands/sessions.d.ts +8 -0
- package/dist/commands/sessions.js +19 -0
- package/dist/commands/shared/CommandRunner.js +4 -4
- package/dist/commands/shared/dataFetcher.js +2 -2
- package/dist/commands/shared/followMode.d.ts +21 -1
- package/dist/commands/shared/followMode.js +29 -2
- package/dist/commands/shared/handleValidationError.d.ts +2 -2
- package/dist/commands/shared/handleValidationError.js +12 -3
- package/dist/commands/shared/optionTypes.d.ts +40 -5
- package/dist/commands/shared/startHelpers.js +12 -3
- package/dist/commands/shared/validation.d.ts +3 -2
- package/dist/commands/shared/validation.js +4 -3
- package/dist/commands/start.d.ts +63 -0
- package/dist/commands/start.js +115 -15
- package/dist/commands/status.js +29 -7
- package/dist/commands/stop.js +7 -6
- package/dist/commands/tail.js +4 -11
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands.js +2 -0
- package/dist/connection/chromeIdentity.d.ts +65 -0
- package/dist/connection/chromeIdentity.js +143 -0
- package/dist/connection/launcher/profilePreferences.d.ts +47 -0
- package/dist/connection/launcher/profilePreferences.js +151 -0
- package/dist/connection/launcher.d.ts +21 -2
- package/dist/connection/launcher.js +42 -16
- package/dist/connection/portReservation.d.ts +14 -4
- package/dist/connection/portReservation.js +21 -6
- package/dist/connection/startupExit.d.ts +8 -0
- package/dist/connection/startupExit.js +15 -6
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +9 -2
- package/dist/daemon/SessionController.js +23 -7
- package/dist/daemon/errors.d.ts +1 -1
- package/dist/daemon/errors.js +1 -1
- package/dist/daemon/launcher.d.ts +2 -1
- package/dist/daemon/launcher.js +5 -6
- package/dist/daemon/server/SocketServer.js +1 -2
- package/dist/daemon/session/Session.d.ts +13 -0
- package/dist/daemon/session/Session.js +57 -8
- package/dist/daemon/session/chromeConnection.d.ts +9 -0
- package/dist/daemon/session/chromeConnection.js +45 -8
- package/dist/daemon/session/commandRegistry.js +52 -62
- package/dist/daemon/session/interactions.d.ts +35 -9
- package/dist/daemon/session/interactions.js +36 -9
- package/dist/daemon/session/triggeredRequests.d.ts +67 -0
- package/dist/daemon/session/triggeredRequests.js +157 -0
- package/dist/daemon/session/types.d.ts +5 -1
- package/dist/daemon.js +5393 -1600
- package/dist/errors/messages.d.ts +387 -24
- package/dist/errors/messages.js +761 -67
- package/dist/index.js +3976 -1558
- package/dist/ipc/client.d.ts +12 -1
- package/dist/ipc/client.js +22 -3
- package/dist/ipc/protocol/commands.d.ts +89 -4
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +258 -7
- package/dist/ipc/session/lifecycle.d.ts +8 -1
- package/dist/ipc/session/queries.d.ts +5 -1
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/index.d.ts +2 -1
- package/dist/ipc/transport/index.js +2 -2
- package/dist/runtime/dom/actionEffects.d.ts +106 -0
- package/dist/runtime/dom/actionEffects.js +256 -0
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
- package/dist/runtime/dom/actionEffectsScripts.js +234 -0
- package/dist/runtime/dom/elementGeometry.d.ts +170 -0
- package/dist/runtime/dom/elementGeometry.js +553 -0
- package/dist/runtime/dom/elementInfo.d.ts +77 -0
- package/dist/runtime/dom/elementInfo.js +191 -0
- package/dist/runtime/dom/evalHelpers.d.ts +51 -6
- package/dist/runtime/dom/evalHelpers.js +136 -26
- package/dist/runtime/dom/eventListeners.d.ts +2 -1
- package/dist/runtime/dom/eventListeners.js +174 -47
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/formDiscovery.js +116 -16
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
- package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
- package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
- package/dist/runtime/dom/formFillHelpers/index.js +2 -2
- package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
- package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
- package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
- package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
- package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
- package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
- package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
- package/dist/runtime/dom/formSubmitHelpers.js +89 -15
- package/dist/runtime/dom/frameLayout.d.ts +60 -0
- package/dist/runtime/dom/frameLayout.js +140 -0
- package/dist/runtime/dom/frameOrigin.d.ts +50 -0
- package/dist/runtime/dom/frameOrigin.js +62 -0
- package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
- package/dist/runtime/dom/frameScopedConnection.js +252 -0
- package/dist/runtime/dom/frameSelection.d.ts +1 -1
- package/dist/runtime/dom/frameSelection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +25 -2
- package/dist/runtime/dom/frames.js +202 -63
- package/dist/runtime/dom/layout.d.ts +67 -0
- package/dist/runtime/dom/layout.js +333 -0
- package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
- package/dist/runtime/dom/listenerPageScripts.js +279 -0
- package/dist/runtime/dom/listenerSummary.d.ts +132 -11
- package/dist/runtime/dom/listenerSummary.js +344 -22
- package/dist/runtime/dom/pageActivity.d.ts +41 -0
- package/dist/runtime/dom/pageActivity.js +123 -0
- package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
- package/dist/runtime/dom/reactEventHelpers.js +212 -41
- package/dist/runtime/dom/targetNode.d.ts +80 -27
- package/dist/runtime/dom/targetNode.js +249 -33
- package/dist/runtime/dom/wait.d.ts +25 -0
- package/dist/runtime/dom/wait.js +199 -0
- package/dist/runtime/dom/waitCondition.d.ts +71 -0
- package/dist/runtime/dom/waitCondition.js +75 -0
- package/dist/runtime/page/emulation.d.ts +51 -0
- package/dist/runtime/page/emulation.js +80 -0
- package/dist/runtime/page/loadingState.d.ts +36 -0
- package/dist/runtime/page/loadingState.js +86 -0
- package/dist/runtime/page/navigation.d.ts +46 -2
- package/dist/runtime/page/navigation.js +69 -33
- package/dist/session/QueryCacheManager.d.ts +11 -1
- package/dist/session/QueryCacheManager.js +25 -3
- package/dist/session/chromeOwners.d.ts +34 -0
- package/dist/session/chromeOwners.js +51 -0
- package/dist/session/cleanup/staleSession.d.ts +11 -1
- package/dist/session/cleanup/staleSession.js +17 -6
- package/dist/session/cleanup/userCommands.js +2 -4
- package/dist/session/metadata.d.ts +5 -1
- package/dist/session/metadata.js +2 -1
- package/dist/session/paths.d.ts +77 -3
- package/dist/session/paths.js +111 -5
- package/dist/session/port.d.ts +31 -7
- package/dist/session/port.js +50 -43
- package/dist/session/portClaims.d.ts +66 -0
- package/dist/session/portClaims.js +284 -0
- package/dist/session/sessionList.d.ts +58 -0
- package/dist/session/sessionList.js +199 -0
- package/dist/session/sessionName.d.ts +46 -0
- package/dist/session/sessionName.js +97 -0
- package/dist/telemetry/a11y.d.ts +8 -3
- package/dist/telemetry/a11y.js +92 -28
- package/dist/telemetry/requestKinds.d.ts +32 -0
- package/dist/telemetry/requestKinds.js +61 -0
- package/dist/telemetry/requestState.d.ts +31 -0
- package/dist/telemetry/requestState.js +38 -0
- package/dist/types.d.ts +80 -3
- package/dist/ui/formatters/a11y.js +3 -0
- package/dist/ui/formatters/console/chronological.d.ts +8 -0
- package/dist/ui/formatters/console/chronological.js +17 -4
- package/dist/ui/formatters/console/json.js +3 -4
- package/dist/ui/formatters/console/shared.d.ts +12 -0
- package/dist/ui/formatters/console.d.ts +2 -2
- package/dist/ui/formatters/console.js +1 -1
- package/dist/ui/formatters/details.js +2 -1
- package/dist/ui/formatters/dom.d.ts +26 -14
- package/dist/ui/formatters/dom.js +65 -52
- package/dist/ui/formatters/form.js +29 -18
- package/dist/ui/formatters/layout.d.ts +31 -0
- package/dist/ui/formatters/layout.js +53 -0
- package/dist/ui/formatters/listeners.d.ts +3 -2
- package/dist/ui/formatters/listeners.js +73 -9
- package/dist/ui/formatters/networkHeaders.js +13 -0
- package/dist/ui/formatters/preview.js +2 -1
- package/dist/ui/formatters/requestStatus.d.ts +1 -17
- package/dist/ui/formatters/requestStatus.js +2 -30
- package/dist/ui/formatters/sessions.d.ts +12 -0
- package/dist/ui/formatters/sessions.js +40 -0
- package/dist/ui/formatters/status.d.ts +21 -2
- package/dist/ui/formatters/status.js +47 -10
- package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
- package/dist/ui/formatters/triggeredRequests.js +65 -0
- package/dist/ui/formatting.d.ts +10 -0
- package/dist/ui/formatting.js +28 -36
- package/dist/ui/messages/chrome.d.ts +9 -0
- package/dist/ui/messages/chrome.js +17 -5
- package/dist/ui/messages/commands.d.ts +388 -14
- package/dist/ui/messages/commands.js +664 -21
- package/dist/ui/messages/consoleMessages.d.ts +10 -0
- package/dist/ui/messages/consoleMessages.js +17 -0
- package/dist/ui/messages/hints.js +2 -1
- package/dist/ui/messages/preview.js +5 -4
- package/dist/ui/messages/session.d.ts +16 -21
- package/dist/ui/messages/session.js +28 -26
- package/dist/ui/messages/sessionCommand.d.ts +43 -0
- package/dist/ui/messages/sessionCommand.js +52 -0
- package/dist/utils/async.d.ts +8 -0
- package/dist/utils/async.js +19 -0
- package/dist/utils/http.d.ts +22 -1
- package/dist/utils/http.js +28 -9
- package/dist/utils/selectorFilters.d.ts +36 -8
- package/dist/utils/selectorFilters.js +267 -53
- package/dist/utils/shellDetection.d.ts +8 -2
- package/dist/utils/shellDetection.js +120 -33
- package/dist/utils/suggestions.d.ts +26 -0
- package/dist/utils/suggestions.js +73 -0
- package/dist/utils/taskMappings.js +10 -0
- package/dist/utils/url.d.ts +12 -2
- package/dist/utils/url.js +69 -7
- package/package.json +1 -1
- package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
- package/dist/ui/formatters/sessionFormatters.js +0 -121
package/dist/commands/start.js
CHANGED
|
@@ -7,11 +7,12 @@ import { startSessionViaDaemon } from './shared/startHelpers.js';
|
|
|
7
7
|
import { positiveIntRule } from './shared/validation.js';
|
|
8
8
|
import { PORT_OPTION_DESCRIPTION } from '../constants.js';
|
|
9
9
|
import { CommandError } from '../errors/index.js';
|
|
10
|
-
import { chromeWsUrlConflictError, invalidChromeFlagError, invalidUserDataDirError, missingStartUrlError, unknownCommandError, } from '../errors/messages.js';
|
|
10
|
+
import { chromeWsUrlConflictError, externalChromeUnreachableError, invalidChromeFlagError, notDevToolsEndpointError, invalidColorSchemeError, invalidUserDataDirError, invalidViewportError, missingStartUrlError, unknownCommandError, } from '../errors/messages.js';
|
|
11
11
|
import { startCommandHelpMessage } from '../ui/messages/commands.js';
|
|
12
12
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
13
|
+
import { probeDevToolsEndpoint } from '../utils/http.js';
|
|
13
14
|
import { findSimilar } from '../utils/suggestions.js';
|
|
14
|
-
import { validateChromeWsUrl, validateUrl } from '../utils/url.js';
|
|
15
|
+
import { devToolsHttpEndpoint, validateChromeWsUrl, validateUrl } from '../utils/url.js';
|
|
15
16
|
/**
|
|
16
17
|
* Check if a display server (X11 or Wayland) is available.
|
|
17
18
|
* Used to determine default headless mode.
|
|
@@ -63,7 +64,7 @@ export function extractUserDataDirFromFlags(flags) {
|
|
|
63
64
|
* @param command - Commander.js Command instance to apply options to
|
|
64
65
|
* @returns The modified Command instance with all telemetry options applied
|
|
65
66
|
*/
|
|
66
|
-
function applyCollectorOptions(command) {
|
|
67
|
+
export function applyCollectorOptions(command) {
|
|
67
68
|
// Default to headless if no display available
|
|
68
69
|
const defaultHeadless = !hasDisplay();
|
|
69
70
|
return command
|
|
@@ -75,10 +76,48 @@ function applyCollectorOptions(command) {
|
|
|
75
76
|
.addOption(new Option('--compact', 'No effect; kept for compatibility').hideHelp())
|
|
76
77
|
.option('--headless', 'Run Chrome without a window (default unless DISPLAY or WAYLAND_DISPLAY is set)', defaultHeadless)
|
|
77
78
|
.option('--no-headless', 'Show browser window')
|
|
78
|
-
.option('--chrome-ws-url <url>', 'Connect to existing Chrome
|
|
79
|
+
.option('--chrome-ws-url <url>', 'Connect to an existing Chrome: its DevTools port (9222, host:port, http://host:port), or a WebSocket URL: browser (ws://host:port/devtools/browser/<id>, uses the first tab) or page (.../devtools/page/<id>)')
|
|
79
80
|
.option('-q, --quiet', 'Quiet mode - minimal output for AI agents', false)
|
|
80
81
|
.addOption(jsonOption())
|
|
81
|
-
.option('--chrome-flags <flags>', 'Custom Chrome flags (space-separated, e.g., --chrome-flags="--ignore-certificate-errors --disable-web-security")')
|
|
82
|
+
.option('--chrome-flags <flags>', 'Custom Chrome flags (space-separated, e.g., --chrome-flags="--ignore-certificate-errors --disable-web-security")')
|
|
83
|
+
.option('--viewport <WxH>', 'Viewport size in CSS px for the whole session, e.g. 1280x800 (default: 1920x1080 window)')
|
|
84
|
+
.option('--color-scheme <scheme>', 'Emulate prefers-color-scheme for the session: light or dark (default: the system setting)');
|
|
85
|
+
}
|
|
86
|
+
/** Largest viewport side accepted by `--viewport` (CSS px) */
|
|
87
|
+
const MAX_VIEWPORT_SIDE = 10000;
|
|
88
|
+
/** Values of `--color-scheme` */
|
|
89
|
+
const COLOR_SCHEMES = ['light', 'dark'];
|
|
90
|
+
/**
|
|
91
|
+
* Parse a `--viewport` value: width and height in CSS px joined by `x`
|
|
92
|
+
* (`1280x800`; `X`, `×` and `,` work too).
|
|
93
|
+
*
|
|
94
|
+
* @param value - Option value
|
|
95
|
+
* @returns Viewport size
|
|
96
|
+
* @throws CommandError (81) for anything else, or a side outside 1-10000
|
|
97
|
+
*/
|
|
98
|
+
export function parseViewport(value) {
|
|
99
|
+
const match = /^\s*(\d+)\s*[xX×,]\s*(\d+)\s*$/.exec(value);
|
|
100
|
+
const width = Number(match?.[1]);
|
|
101
|
+
const height = Number(match?.[2]);
|
|
102
|
+
const valid = (side) => side >= 1 && side <= MAX_VIEWPORT_SIDE;
|
|
103
|
+
if (match && valid(width) && valid(height))
|
|
104
|
+
return { width, height };
|
|
105
|
+
const err = invalidViewportError(value, MAX_VIEWPORT_SIDE);
|
|
106
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Parse a `--color-scheme` value (case-insensitive).
|
|
110
|
+
*
|
|
111
|
+
* @param value - Option value
|
|
112
|
+
* @returns The scheme
|
|
113
|
+
* @throws CommandError (81) for another value, suggesting the closest one
|
|
114
|
+
*/
|
|
115
|
+
export function parseColorScheme(value) {
|
|
116
|
+
const scheme = COLOR_SCHEMES.find((candidate) => candidate === value.trim().toLowerCase());
|
|
117
|
+
if (scheme)
|
|
118
|
+
return scheme;
|
|
119
|
+
const err = invalidColorSchemeError(value, findSimilar(value, [...COLOR_SCHEMES]), COLOR_SCHEMES);
|
|
120
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
82
121
|
}
|
|
83
122
|
/**
|
|
84
123
|
* Transform CLI options into session options
|
|
@@ -118,6 +157,8 @@ function buildSessionOptions(options) {
|
|
|
118
157
|
quiet: options.quiet ?? false,
|
|
119
158
|
json: options.json ?? false,
|
|
120
159
|
chromeFlags,
|
|
160
|
+
viewport: options.viewport !== undefined ? parseViewport(options.viewport) : undefined,
|
|
161
|
+
colorScheme: options.colorScheme !== undefined ? parseColorScheme(options.colorScheme) : undefined,
|
|
121
162
|
};
|
|
122
163
|
}
|
|
123
164
|
/** Telemetry collected by every session. */
|
|
@@ -166,20 +207,21 @@ export function assertNotGroupSubcommand(program, argv) {
|
|
|
166
207
|
*
|
|
167
208
|
* @param url - Target URL
|
|
168
209
|
* @param options - Parsed command-line options from Commander
|
|
169
|
-
* @param
|
|
210
|
+
* @param program - Root command (registered command names for typo detection,
|
|
211
|
+
* and which options came from the command line)
|
|
170
212
|
* @returns The URL and normalized session options
|
|
171
213
|
* @throws CommandError on any invalid input
|
|
172
214
|
*/
|
|
173
|
-
function validateStartInput(url, options,
|
|
215
|
+
function validateStartInput(url, options, program) {
|
|
174
216
|
if (url === undefined) {
|
|
175
217
|
const err = missingStartUrlError();
|
|
176
218
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
177
219
|
}
|
|
178
|
-
assertNotCommandTypo(url,
|
|
220
|
+
assertNotCommandTypo(url, program.commands.map((command) => command.name()));
|
|
179
221
|
assertValidUrl(url);
|
|
180
222
|
if (options.chromeWsUrl !== undefined) {
|
|
181
223
|
assertValidChromeWsUrl(options.chromeWsUrl);
|
|
182
|
-
assertNoLaunchOptions(options);
|
|
224
|
+
assertNoLaunchOptions(options, program);
|
|
183
225
|
}
|
|
184
226
|
if (options.userDataDir !== undefined)
|
|
185
227
|
assertUserDataDir(options.userDataDir);
|
|
@@ -204,7 +246,8 @@ export function registerStartCommands(program) {
|
|
|
204
246
|
}
|
|
205
247
|
let validated;
|
|
206
248
|
try {
|
|
207
|
-
validated = validateStartInput(url, options, program
|
|
249
|
+
validated = validateStartInput(url, options, program);
|
|
250
|
+
validated.sessionOptions.chromeWsUrl = await resolveChromeWsUrl(validated.sessionOptions.chromeWsUrl);
|
|
208
251
|
}
|
|
209
252
|
catch (error) {
|
|
210
253
|
handleValidationError(error, options.json ?? false);
|
|
@@ -212,6 +255,49 @@ export function registerStartCommands(program) {
|
|
|
212
255
|
await startSessionViaDaemon(validated.url, validated.sessionOptions, SESSION_TELEMETRY);
|
|
213
256
|
});
|
|
214
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Turn a `--chrome-ws-url` that names the HTTP DevTools endpoint (a port,
|
|
260
|
+
* `host:port`, or `http://host:port`) into the browser's WebSocket URL from
|
|
261
|
+
* its `/json/version`, so users need not look it up themselves.
|
|
262
|
+
*
|
|
263
|
+
* @param value - Option value (WebSocket URLs are returned unchanged)
|
|
264
|
+
* @returns WebSocket URL, or undefined without the option
|
|
265
|
+
* @throws CommandError (101) when the endpoint does not answer or is not DevTools
|
|
266
|
+
*/
|
|
267
|
+
async function resolveChromeWsUrl(value) {
|
|
268
|
+
const endpoint = value === undefined ? null : devToolsHttpEndpoint(value);
|
|
269
|
+
if (endpoint === null)
|
|
270
|
+
return value;
|
|
271
|
+
const { hostname, port, protocol } = new URL(endpoint);
|
|
272
|
+
const secure = protocol === 'https:';
|
|
273
|
+
const defaultPort = secure ? 443 : 80;
|
|
274
|
+
const probe = await probeDevToolsEndpoint(Number(port) || defaultPort, undefined, {
|
|
275
|
+
host: hostname,
|
|
276
|
+
secure,
|
|
277
|
+
});
|
|
278
|
+
if (probe.kind === 'devtools')
|
|
279
|
+
return atEndpoint(probe.wsUrl, endpoint);
|
|
280
|
+
const err = probe.kind === 'not-devtools'
|
|
281
|
+
? notDevToolsEndpointError(endpoint)
|
|
282
|
+
: externalChromeUnreachableError(endpoint, false);
|
|
283
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.CDP_CONNECTION_FAILURE);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Point a WebSocket URL Chrome reported at the endpoint the user gave: Chrome
|
|
287
|
+
* reports its own host and port, which differ behind a proxy or forwarded
|
|
288
|
+
* port, and only ws:// (an https endpoint is a TLS proxy, so wss://).
|
|
289
|
+
*
|
|
290
|
+
* @param wsUrl - URL from `/json/version`
|
|
291
|
+
* @param endpoint - Endpoint origin the user gave
|
|
292
|
+
* @returns The URL with the endpoint's host, port and matching scheme
|
|
293
|
+
*/
|
|
294
|
+
function atEndpoint(wsUrl, endpoint) {
|
|
295
|
+
const resolved = new URL(wsUrl);
|
|
296
|
+
const target = new URL(endpoint);
|
|
297
|
+
resolved.protocol = target.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
298
|
+
resolved.host = target.host;
|
|
299
|
+
return resolved.toString();
|
|
300
|
+
}
|
|
215
301
|
/**
|
|
216
302
|
* Whether any start option was given on the command line (`bdg --port 9333`
|
|
217
303
|
* without a URL is an error, a bare `bdg` shows help).
|
|
@@ -244,16 +330,30 @@ function assertUserDataDir(value) {
|
|
|
244
330
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
245
331
|
}
|
|
246
332
|
/**
|
|
247
|
-
*
|
|
333
|
+
* Options given that only apply to a Chrome bdg launches. `--headless` has a
|
|
334
|
+
* default, so it counts only when given on the command line.
|
|
248
335
|
*
|
|
249
|
-
* @param options - Parsed options
|
|
250
|
-
* @
|
|
336
|
+
* @param options - Parsed options
|
|
337
|
+
* @param program - Command the options were parsed by
|
|
338
|
+
* @returns The conflicting flags, e.g. ["--port", "--headless"]
|
|
251
339
|
*/
|
|
252
|
-
function
|
|
253
|
-
const
|
|
340
|
+
export function launchOptionConflicts(options, program) {
|
|
341
|
+
const headlessGiven = program.getOptionValueSource('headless') === 'cli';
|
|
342
|
+
return [
|
|
254
343
|
...(options.port !== undefined ? ['--port'] : []),
|
|
255
344
|
...(options.userDataDir !== undefined ? ['--user-data-dir'] : []),
|
|
345
|
+
...(headlessGiven ? [options.headless ? '--headless' : '--no-headless'] : []),
|
|
256
346
|
];
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Reject options that only apply to a Chrome bdg launches.
|
|
350
|
+
*
|
|
351
|
+
* @param options - Parsed options (with `--chrome-ws-url`)
|
|
352
|
+
* @param program - Command the options were parsed by
|
|
353
|
+
* @throws CommandError (81) for `--port`, `-u` or `--[no-]headless`
|
|
354
|
+
*/
|
|
355
|
+
function assertNoLaunchOptions(options, program) {
|
|
356
|
+
const conflicts = launchOptionConflicts(options, program);
|
|
257
357
|
if (conflicts.length === 0)
|
|
258
358
|
return;
|
|
259
359
|
const err = chromeWsUrlConflictError(conflicts);
|
package/dist/commands/status.js
CHANGED
|
@@ -6,7 +6,9 @@ import { IPCTimeoutError } from '../ipc/transport/IPCError.js';
|
|
|
6
6
|
import { describeRunningChrome } from '../session/chrome.js';
|
|
7
7
|
import { findOrphanedChrome, removeStaleDaemonFiles } from '../session/cleanup/staleSession.js';
|
|
8
8
|
import { readLastSessionEnd } from '../session/lastSession.js';
|
|
9
|
+
import { getSessionName } from '../session/paths.js';
|
|
9
10
|
import { formatSessionStatus, formatStatusAsJson, formatNoSessionMessage, } from '../ui/formatters/status.js';
|
|
11
|
+
import { sessionCommand } from '../ui/messages/sessionCommand.js';
|
|
10
12
|
import { getErrorMessage } from '../utils/errors.js';
|
|
11
13
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
12
14
|
/**
|
|
@@ -30,6 +32,24 @@ function inactiveStatus(starting, ending) {
|
|
|
30
32
|
...(lastSession && { lastSession }),
|
|
31
33
|
};
|
|
32
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Add the selected session's name (named sessions only).
|
|
37
|
+
*
|
|
38
|
+
* @param data - Status data
|
|
39
|
+
* @returns Status data with `session` for a named session
|
|
40
|
+
*/
|
|
41
|
+
function withSessionName(data) {
|
|
42
|
+
const session = getSessionName();
|
|
43
|
+
return session === null ? data : { session, ...data };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Force-clean the selected session and start it again.
|
|
47
|
+
*
|
|
48
|
+
* @returns Command line
|
|
49
|
+
*/
|
|
50
|
+
function resetCommand() {
|
|
51
|
+
return `${sessionCommand('bdg cleanup --force')} && ${sessionCommand('bdg <url>')}`;
|
|
52
|
+
}
|
|
33
53
|
/**
|
|
34
54
|
* Register status command
|
|
35
55
|
*
|
|
@@ -56,7 +76,7 @@ export function registerStatusCommand(program) {
|
|
|
56
76
|
error: `Daemon error: ${response.error ?? 'Unknown error'}`,
|
|
57
77
|
exitCode: EXIT_CODES.SOFTWARE_ERROR,
|
|
58
78
|
errorContext: {
|
|
59
|
-
suggestion:
|
|
79
|
+
suggestion: `Try: ${resetCommand()}`,
|
|
60
80
|
},
|
|
61
81
|
};
|
|
62
82
|
}
|
|
@@ -67,7 +87,7 @@ export function registerStatusCommand(program) {
|
|
|
67
87
|
error: invalidResponseError('missing data'),
|
|
68
88
|
exitCode: EXIT_CODES.SOFTWARE_ERROR,
|
|
69
89
|
errorContext: {
|
|
70
|
-
suggestion:
|
|
90
|
+
suggestion: `This is unexpected. Try: ${resetCommand()}`,
|
|
71
91
|
},
|
|
72
92
|
};
|
|
73
93
|
}
|
|
@@ -83,7 +103,7 @@ export function registerStatusCommand(program) {
|
|
|
83
103
|
if (data.pageState) {
|
|
84
104
|
jsonOutput.pageState = data.pageState;
|
|
85
105
|
}
|
|
86
|
-
return { success: true, data: jsonOutput };
|
|
106
|
+
return { success: true, data: withSessionName(jsonOutput) };
|
|
87
107
|
}
|
|
88
108
|
const metadata = {
|
|
89
109
|
bdgPid: data.sessionMetadata.bdgPid,
|
|
@@ -94,6 +114,8 @@ export function registerStatusCommand(program) {
|
|
|
94
114
|
webSocketDebuggerUrl: data.sessionMetadata.webSocketDebuggerUrl,
|
|
95
115
|
activeTelemetry: data.sessionMetadata.activeTelemetry,
|
|
96
116
|
autoStopAt: data.sessionMetadata.autoStopAt,
|
|
117
|
+
viewport: data.sessionMetadata.viewport,
|
|
118
|
+
colorScheme: data.sessionMetadata.colorScheme,
|
|
97
119
|
};
|
|
98
120
|
latestMetadata = metadata;
|
|
99
121
|
latestSessionPid = data.sessionPid;
|
|
@@ -109,7 +131,7 @@ export function registerStatusCommand(program) {
|
|
|
109
131
|
if (data.pageState) {
|
|
110
132
|
jsonOutput.pageState = data.pageState;
|
|
111
133
|
}
|
|
112
|
-
return { success: true, data: jsonOutput };
|
|
134
|
+
return { success: true, data: withSessionName(jsonOutput) };
|
|
113
135
|
}
|
|
114
136
|
catch (error) {
|
|
115
137
|
const errorMessage = getErrorMessage(error);
|
|
@@ -126,14 +148,14 @@ export function registerStatusCommand(program) {
|
|
|
126
148
|
await removeStaleDaemonFiles();
|
|
127
149
|
latestMetadata = undefined;
|
|
128
150
|
latestSessionPid = undefined;
|
|
129
|
-
return { success: true, data: inactiveStatus() };
|
|
151
|
+
return { success: true, data: withSessionName(inactiveStatus()) };
|
|
130
152
|
}
|
|
131
153
|
return {
|
|
132
154
|
success: false,
|
|
133
155
|
error: `Error checking status: ${errorMessage}`,
|
|
134
156
|
exitCode: EXIT_CODES.SOFTWARE_ERROR,
|
|
135
157
|
errorContext: {
|
|
136
|
-
suggestion:
|
|
158
|
+
suggestion: `Try: ${sessionCommand('bdg cleanup --force')} to reset session state`,
|
|
137
159
|
},
|
|
138
160
|
};
|
|
139
161
|
}
|
|
@@ -144,7 +166,7 @@ export function registerStatusCommand(program) {
|
|
|
144
166
|
if (!latestMetadata || latestSessionPid === undefined) {
|
|
145
167
|
return formatNoSessionMessage();
|
|
146
168
|
}
|
|
147
|
-
return formatSessionStatus(latestMetadata, latestSessionPid, latestActivity, latestPageState, options.verbose ?? false);
|
|
169
|
+
return formatSessionStatus(latestMetadata, latestSessionPid, latestActivity, latestPageState, options.verbose ?? false, data.session);
|
|
148
170
|
});
|
|
149
171
|
});
|
|
150
172
|
}
|
package/dist/commands/stop.js
CHANGED
|
@@ -6,6 +6,7 @@ import { IPCTimeoutError } from '../ipc/transport/index.js';
|
|
|
6
6
|
import { joinLines } from '../ui/formatting.js';
|
|
7
7
|
import { chromeClosedMessage, orphanedDaemonsCleanedMessage, warningMessage, } from '../ui/messages/commands.js';
|
|
8
8
|
import { sessionStopped, STOP_MESSAGES, stopFailedError } from '../ui/messages/session.js';
|
|
9
|
+
import { noActiveSessionMessage, sessionCommand, startSessionSuggestion, } from '../ui/messages/sessionCommand.js';
|
|
9
10
|
import { getExitCodeForIPCError, isDaemonNotRunningError } from '../utils/errorMapping.js';
|
|
10
11
|
import { getErrorMessage } from '../utils/errors.js';
|
|
11
12
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
@@ -54,10 +55,10 @@ export function registerStopCommand(program) {
|
|
|
54
55
|
if (response.errorCode === IPCErrorCode.NO_SESSION) {
|
|
55
56
|
return {
|
|
56
57
|
success: false,
|
|
57
|
-
error: response.message ??
|
|
58
|
+
error: response.message ?? noActiveSessionMessage(),
|
|
58
59
|
exitCode: EXIT_CODES.RESOURCE_NOT_FOUND,
|
|
59
60
|
errorContext: {
|
|
60
|
-
suggestion:
|
|
61
|
+
suggestion: startSessionSuggestion(),
|
|
61
62
|
},
|
|
62
63
|
};
|
|
63
64
|
}
|
|
@@ -67,7 +68,7 @@ export function registerStopCommand(program) {
|
|
|
67
68
|
error: response.message ?? STOP_MESSAGES.FAILED,
|
|
68
69
|
exitCode,
|
|
69
70
|
errorContext: {
|
|
70
|
-
suggestion:
|
|
71
|
+
suggestion: `Check session status with: ${sessionCommand('bdg status')}`,
|
|
71
72
|
},
|
|
72
73
|
};
|
|
73
74
|
}
|
|
@@ -79,10 +80,10 @@ export function registerStopCommand(program) {
|
|
|
79
80
|
if (isDaemonNotRunningError(errorMessage)) {
|
|
80
81
|
return {
|
|
81
82
|
success: false,
|
|
82
|
-
error:
|
|
83
|
+
error: noActiveSessionMessage(),
|
|
83
84
|
exitCode: EXIT_CODES.RESOURCE_NOT_FOUND,
|
|
84
85
|
errorContext: {
|
|
85
|
-
suggestion:
|
|
86
|
+
suggestion: startSessionSuggestion(),
|
|
86
87
|
},
|
|
87
88
|
};
|
|
88
89
|
}
|
|
@@ -91,7 +92,7 @@ export function registerStopCommand(program) {
|
|
|
91
92
|
error: stopFailedError(errorMessage),
|
|
92
93
|
exitCode: EXIT_CODES.SOFTWARE_ERROR,
|
|
93
94
|
errorContext: {
|
|
94
|
-
suggestion:
|
|
95
|
+
suggestion: `Try: ${sessionCommand('bdg cleanup --force')} to reset session state`,
|
|
95
96
|
},
|
|
96
97
|
};
|
|
97
98
|
}
|
package/dist/commands/tail.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Tail command for continuous session monitoring.
|
|
3
3
|
*/
|
|
4
4
|
import { jsonOption, showBothSectionsWhenBothRequested } from './shared/commonOptions.js';
|
|
5
|
-
import {
|
|
5
|
+
import { noteFollowConnected } from './shared/daemonErrorHandler.js';
|
|
6
6
|
import { fetchPreviewOutput } from './shared/dataFetcher.js';
|
|
7
|
-
import { setupFollowMode } from './shared/followMode.js';
|
|
7
|
+
import { followFetchFailure, setupFollowMode, } from './shared/followMode.js';
|
|
8
8
|
import { handleValidationError } from './shared/handleValidationError.js';
|
|
9
9
|
import { MAX_LAST_ITEMS, positiveIntRule } from './shared/validation.js';
|
|
10
10
|
import { formatPreview } from '../ui/formatters/preview.js';
|
|
@@ -59,20 +59,13 @@ export function registerTailCommand(program) {
|
|
|
59
59
|
const showPreview = async () => {
|
|
60
60
|
const result = await fetchPreviewOutput({ lastN });
|
|
61
61
|
if (!result.success) {
|
|
62
|
-
|
|
63
|
-
json: options.json,
|
|
64
|
-
follow: true,
|
|
65
|
-
retryIntervalMs: interval,
|
|
66
|
-
exitCode: result.exitCode,
|
|
67
|
-
});
|
|
68
|
-
if (errorResult.shouldExit)
|
|
69
|
-
process.exit(errorResult.exitCode);
|
|
70
|
-
return;
|
|
62
|
+
return followFetchFailure(result, { json: options.json, retryIntervalMs: interval });
|
|
71
63
|
}
|
|
72
64
|
noteFollowConnected();
|
|
73
65
|
if (!options.json)
|
|
74
66
|
console.clear();
|
|
75
67
|
console.log(formatPreview(result.data, createPreviewOptions(options, lastN)));
|
|
68
|
+
return undefined;
|
|
76
69
|
};
|
|
77
70
|
await setupFollowMode(showPreview, {
|
|
78
71
|
startMessage: followingPreviewMessage,
|
package/dist/commands/types.d.ts
CHANGED
package/dist/commands.js
CHANGED
|
@@ -7,6 +7,7 @@ import { registerDomCommands } from './commands/dom/index.js';
|
|
|
7
7
|
import { registerNetworkCommands } from './commands/network/index.js';
|
|
8
8
|
import { registerPageCommands } from './commands/page.js';
|
|
9
9
|
import { registerPeekCommand } from './commands/peek.js';
|
|
10
|
+
import { registerSessionsCommand } from './commands/sessions.js';
|
|
10
11
|
import { registerStartCommands } from './commands/start.js';
|
|
11
12
|
import { registerStatusCommand } from './commands/status.js';
|
|
12
13
|
import { registerStopCommand } from './commands/stop.js';
|
|
@@ -27,6 +28,7 @@ export const commandRegistry = [
|
|
|
27
28
|
registerStartCommands,
|
|
28
29
|
addCommandGroup('Session Management:'),
|
|
29
30
|
registerStatusCommand,
|
|
31
|
+
registerSessionsCommand,
|
|
30
32
|
registerStopCommand,
|
|
31
33
|
registerCleanupCommand,
|
|
32
34
|
addCommandGroup('Data Inspection:'),
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity of the Chrome bdg launched.
|
|
3
|
+
*
|
|
4
|
+
* chrome-launcher only waits until something answers on 127.0.0.1:<port>. That
|
|
5
|
+
* may be another process: when 127.0.0.1:<port> is taken, Chrome listens on
|
|
6
|
+
* [::1]:<port> instead and the readiness check is answered by the other one.
|
|
7
|
+
*
|
|
8
|
+
* Chrome prints `DevTools listening on ws://<host>:<port>/devtools/browser/<id>`
|
|
9
|
+
* to stderr (chrome-launcher writes it to `chrome-err.log` in the profile).
|
|
10
|
+
* The browser path is unique per browser, so comparing it with the one
|
|
11
|
+
* 127.0.0.1:<port> reports tells whether that is the launched Chrome.
|
|
12
|
+
* (Chrome's `DevToolsActivePort` file holds the same, but Chrome writes it only
|
|
13
|
+
* for `--remote-debugging-port=0`.)
|
|
14
|
+
*
|
|
15
|
+
* A Chrome whose output lacks the line (stderr redirected, a Chromium build
|
|
16
|
+
* that does not print it) is accepted with a warning when its browser answers
|
|
17
|
+
* on 127.0.0.1 and nothing listens on [::1]: the port was free on both right
|
|
18
|
+
* before the launch, and a second listener is how a conflict shows.
|
|
19
|
+
*/
|
|
20
|
+
import { type StartupLogs } from './startupExit.js';
|
|
21
|
+
/**
|
|
22
|
+
* The debugging endpoint a Chrome announced for itself.
|
|
23
|
+
*/
|
|
24
|
+
export interface DevToolsEndpoint {
|
|
25
|
+
/** Address the debugging server listens on (`127.0.0.1`, `[::1]`) */
|
|
26
|
+
host: string;
|
|
27
|
+
/** Port the debugging server listens on */
|
|
28
|
+
port: number;
|
|
29
|
+
/** Browser WebSocket path, `/devtools/browser/<id>` */
|
|
30
|
+
browserPath: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Find the endpoint Chrome announced in its output (the last announcement).
|
|
34
|
+
*
|
|
35
|
+
* @param lines - Chrome's output lines
|
|
36
|
+
* @returns Endpoint, or null if there is no complete announcement
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseDevToolsListening(lines: readonly string[]): DevToolsEndpoint | null;
|
|
39
|
+
/**
|
|
40
|
+
* Wait for Chrome to announce its endpoint.
|
|
41
|
+
*
|
|
42
|
+
* @param logs - Chrome's log positions from before the launch
|
|
43
|
+
* @param isRunning - Whether Chrome is still running (waiting stops when not)
|
|
44
|
+
* @param timeoutMs - Longest wait
|
|
45
|
+
* @returns Endpoint, or null if Chrome exited or announced none in time
|
|
46
|
+
*/
|
|
47
|
+
export declare function waitForDevToolsEndpoint(logs: StartupLogs, isRunning: () => boolean, timeoutMs?: number): Promise<DevToolsEndpoint | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Check that the Chrome answering on 127.0.0.1:<port> is the one just
|
|
50
|
+
* launched, so bdg never drives another session's browser.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Chrome's log positions from before the launch, requested
|
|
53
|
+
* port (free on 127.0.0.1 and ::1 right before the launch), Chrome's PID and
|
|
54
|
+
* longest wait for its announcement
|
|
55
|
+
* @throws ChromeLaunchError: CHROME_DIED_AFTER_LAUNCH if Chrome exits first;
|
|
56
|
+
* PORT_IN_USE if another browser or process answers on the port;
|
|
57
|
+
* CHROME_LAUNCH_FAILED if Chrome announced nothing and nothing answers
|
|
58
|
+
*/
|
|
59
|
+
export declare function verifyLaunchedChrome(options: {
|
|
60
|
+
logs: StartupLogs;
|
|
61
|
+
port: number;
|
|
62
|
+
pid: number;
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
}): Promise<void>;
|
|
65
|
+
//# sourceMappingURL=chromeIdentity.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity of the Chrome bdg launched.
|
|
3
|
+
*
|
|
4
|
+
* chrome-launcher only waits until something answers on 127.0.0.1:<port>. That
|
|
5
|
+
* may be another process: when 127.0.0.1:<port> is taken, Chrome listens on
|
|
6
|
+
* [::1]:<port> instead and the readiness check is answered by the other one.
|
|
7
|
+
*
|
|
8
|
+
* Chrome prints `DevTools listening on ws://<host>:<port>/devtools/browser/<id>`
|
|
9
|
+
* to stderr (chrome-launcher writes it to `chrome-err.log` in the profile).
|
|
10
|
+
* The browser path is unique per browser, so comparing it with the one
|
|
11
|
+
* 127.0.0.1:<port> reports tells whether that is the launched Chrome.
|
|
12
|
+
* (Chrome's `DevToolsActivePort` file holds the same, but Chrome writes it only
|
|
13
|
+
* for `--remote-debugging-port=0`.)
|
|
14
|
+
*
|
|
15
|
+
* A Chrome whose output lacks the line (stderr redirected, a Chromium build
|
|
16
|
+
* that does not print it) is accepted with a warning when its browser answers
|
|
17
|
+
* on 127.0.0.1 and nothing listens on [::1]: the port was free on both right
|
|
18
|
+
* before the launch, and a second listener is how a conflict shows.
|
|
19
|
+
*/
|
|
20
|
+
import { createLogger } from '../ui/logging/index.js';
|
|
21
|
+
import { delay } from '../utils/async.js';
|
|
22
|
+
import { fetchBrowserWsUrl } from '../utils/http.js';
|
|
23
|
+
import { isProcessAlive } from '../utils/process.js';
|
|
24
|
+
import { ChromeLaunchError } from './errors.js';
|
|
25
|
+
import { acceptsConnections } from './portReservation.js';
|
|
26
|
+
import { readStartupLines } from './startupExit.js';
|
|
27
|
+
/** Chrome's stderr line announcing its debugging endpoint */
|
|
28
|
+
const LISTENING_PATTERN = /^DevTools listening on (ws:\/\/\S+)/;
|
|
29
|
+
/** How long to wait for Chrome to announce its endpoint after the port answered */
|
|
30
|
+
const ENDPOINT_WAIT_MS = 10000;
|
|
31
|
+
const ENDPOINT_POLL_MS = 50;
|
|
32
|
+
const log = createLogger('chrome');
|
|
33
|
+
/**
|
|
34
|
+
* Find the endpoint Chrome announced in its output (the last announcement).
|
|
35
|
+
*
|
|
36
|
+
* @param lines - Chrome's output lines
|
|
37
|
+
* @returns Endpoint, or null if there is no complete announcement
|
|
38
|
+
*/
|
|
39
|
+
export function parseDevToolsListening(lines) {
|
|
40
|
+
for (const line of [...lines].reverse()) {
|
|
41
|
+
const match = LISTENING_PATTERN.exec(line.trim());
|
|
42
|
+
if (!match?.[1])
|
|
43
|
+
continue;
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(match[1]);
|
|
46
|
+
const port = Number(url.port);
|
|
47
|
+
if (!port || !url.pathname.startsWith('/devtools/browser/'))
|
|
48
|
+
continue;
|
|
49
|
+
return { host: url.hostname, port, browserPath: url.pathname };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
log.debug(`Ignoring malformed DevTools announcement: ${String(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Wait for Chrome to announce its endpoint.
|
|
59
|
+
*
|
|
60
|
+
* @param logs - Chrome's log positions from before the launch
|
|
61
|
+
* @param isRunning - Whether Chrome is still running (waiting stops when not)
|
|
62
|
+
* @param timeoutMs - Longest wait
|
|
63
|
+
* @returns Endpoint, or null if Chrome exited or announced none in time
|
|
64
|
+
*/
|
|
65
|
+
export async function waitForDevToolsEndpoint(logs, isRunning, timeoutMs = ENDPOINT_WAIT_MS) {
|
|
66
|
+
const deadline = Date.now() + timeoutMs;
|
|
67
|
+
for (;;) {
|
|
68
|
+
const endpoint = parseDevToolsListening(readStartupLines(logs));
|
|
69
|
+
if (endpoint || !isRunning() || Date.now() >= deadline)
|
|
70
|
+
return endpoint;
|
|
71
|
+
await delay(ENDPOINT_POLL_MS);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check that the Chrome answering on 127.0.0.1:<port> is the one just
|
|
76
|
+
* launched, so bdg never drives another session's browser.
|
|
77
|
+
*
|
|
78
|
+
* @param options - Chrome's log positions from before the launch, requested
|
|
79
|
+
* port (free on 127.0.0.1 and ::1 right before the launch), Chrome's PID and
|
|
80
|
+
* longest wait for its announcement
|
|
81
|
+
* @throws ChromeLaunchError: CHROME_DIED_AFTER_LAUNCH if Chrome exits first;
|
|
82
|
+
* PORT_IN_USE if another browser or process answers on the port;
|
|
83
|
+
* CHROME_LAUNCH_FAILED if Chrome announced nothing and nothing answers
|
|
84
|
+
*/
|
|
85
|
+
export async function verifyLaunchedChrome(options) {
|
|
86
|
+
const { logs, port, pid, timeoutMs } = options;
|
|
87
|
+
const isRunning = () => isProcessAlive(pid);
|
|
88
|
+
const endpoint = await waitForDevToolsEndpoint(logs, isRunning, timeoutMs);
|
|
89
|
+
if (!endpoint && !isRunning()) {
|
|
90
|
+
throw new ChromeLaunchError(`Chrome died immediately after launch (PID: ${pid})`, {
|
|
91
|
+
issue: { code: 'CHROME_DIED_AFTER_LAUNCH', context: { port, pid } },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (!endpoint)
|
|
95
|
+
return acceptUnannouncedChrome(logs, port);
|
|
96
|
+
if (endpoint.port !== port) {
|
|
97
|
+
throw portTakenError(port, `Chrome listens on port ${endpoint.port} instead`);
|
|
98
|
+
}
|
|
99
|
+
const answering = await fetchBrowserWsUrl(port, log);
|
|
100
|
+
if (!answering || new URL(answering).pathname !== endpoint.browserPath) {
|
|
101
|
+
throw portTakenError(port, `another process answers on 127.0.0.1 (Chrome listens on ${endpoint.host})`);
|
|
102
|
+
}
|
|
103
|
+
log.debug(`Chrome on port ${port} is the launched one (${endpoint.browserPath})`);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Accept a Chrome that did not announce its endpoint, if a browser answers on
|
|
107
|
+
* 127.0.0.1:<port> and nothing listens on [::1]:<port> (two listeners mean a
|
|
108
|
+
* conflict).
|
|
109
|
+
*
|
|
110
|
+
* @param logs - Chrome's log positions (the stderr log is named in messages)
|
|
111
|
+
* @param port - Requested port
|
|
112
|
+
* @throws ChromeLaunchError: PORT_IN_USE for a second listener;
|
|
113
|
+
* CHROME_LAUNCH_FAILED if no browser answers
|
|
114
|
+
*/
|
|
115
|
+
async function acceptUnannouncedChrome(logs, port) {
|
|
116
|
+
const errLog = logs.files[0]?.file ?? 'chrome-err.log';
|
|
117
|
+
const missing = `Chrome did not print "DevTools listening on" to ${errLog}`;
|
|
118
|
+
if (!(await fetchBrowserWsUrl(port, log))) {
|
|
119
|
+
throw new ChromeLaunchError(`${missing}, and no browser answers on port ${port}`, {
|
|
120
|
+
issue: {
|
|
121
|
+
code: 'CHROME_LAUNCH_FAILED',
|
|
122
|
+
context: { port, reason: `${missing}, and no browser answers on port ${port}` },
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (await acceptsConnections(port, '::1')) {
|
|
127
|
+
throw portTakenError(port, `${missing}, and both 127.0.0.1 and [::1] answer`);
|
|
128
|
+
}
|
|
129
|
+
log.info(`Warning: ${missing}; using the browser on 127.0.0.1:${port}, which was free before the launch`);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The error for a port another process answers on.
|
|
133
|
+
*
|
|
134
|
+
* @param port - Requested port
|
|
135
|
+
* @param reason - What was found
|
|
136
|
+
* @returns Launch error with the PORT_IN_USE issue
|
|
137
|
+
*/
|
|
138
|
+
function portTakenError(port, reason) {
|
|
139
|
+
return new ChromeLaunchError(`Port ${port} is already in use: ${reason}`, {
|
|
140
|
+
issue: { code: 'PORT_IN_USE', context: { port, reason } },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=chromeIdentity.js.map
|