browser-debugger-cli 0.13.0 → 0.15.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 +101 -187
- package/README.md +4 -4
- package/dist/commands/cdp.js +1 -0
- package/dist/commands/cleanup.js +3 -0
- package/dist/commands/console.js +5 -1
- package/dist/commands/dom/a11y.d.ts +1 -1
- package/dist/commands/dom/a11y.js +20 -20
- package/dist/commands/dom/eval.d.ts +3 -1
- package/dist/commands/dom/eval.js +8 -5
- package/dist/commands/dom/formInteraction.js +1 -1
- package/dist/commands/dom/get.js +25 -7
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +2 -1
- package/dist/commands/dom/query.js +5 -3
- package/dist/commands/dom/screenshot.js +1 -0
- package/dist/commands/helpJson.d.ts +1 -1
- package/dist/commands/helpJson.js +4 -4
- package/dist/commands/helpTopic.js +10 -4
- package/dist/commands/network/har.js +18 -14
- package/dist/commands/network/list.js +46 -3
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +60 -42
- package/dist/commands/peek.js +3 -0
- package/dist/commands/shared/CommandRunner.js +13 -13
- package/dist/commands/shared/daemonErrorHandler.js +2 -2
- package/dist/commands/shared/dataFetcher.d.ts +4 -2
- package/dist/commands/shared/dataFetcher.js +11 -3
- package/dist/commands/shared/handleValidationError.js +3 -3
- package/dist/commands/shared/optionTypes.d.ts +15 -3
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +1 -1
- package/dist/connection/launcher.js +1 -2
- package/dist/constants.d.ts +31 -5
- package/dist/constants.js +37 -5
- package/dist/daemon/SessionController.js +2 -0
- package/dist/daemon/launcher.d.ts +17 -3
- package/dist/daemon/launcher.js +37 -7
- package/dist/daemon/session/Session.d.ts +2 -1
- package/dist/daemon/session/Session.js +10 -2
- package/dist/daemon/session/TelemetryStore.d.ts +7 -0
- package/dist/daemon/session/TelemetryStore.js +6 -0
- package/dist/daemon/session/commandRegistry.js +25 -7
- package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
- package/dist/daemon/session/matchedStylesReset.js +46 -0
- package/dist/daemon/session/plugins.js +1 -0
- package/dist/daemon/session/triggeredRequests.d.ts +0 -5
- package/dist/daemon/session/triggeredRequests.js +13 -7
- package/dist/daemon.js +8742 -8315
- package/dist/errors/messages.d.ts +31 -0
- package/dist/errors/messages.js +96 -6
- package/dist/index.js +1129 -548
- package/dist/ipc/client.d.ts +6 -1
- package/dist/ipc/client.js +11 -2
- package/dist/ipc/protocol/commands.d.ts +8 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
- package/dist/ipc/session/types.d.ts +5 -1
- package/dist/ipc/transport/index.d.ts +6 -0
- package/dist/ipc/transport/index.js +16 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +53 -0
- package/dist/runtime/dom/elementGeometry.d.ts +23 -0
- package/dist/runtime/dom/elementGeometry.js +17 -15
- package/dist/runtime/dom/elementInfo.d.ts +13 -4
- package/dist/runtime/dom/elementInfo.js +15 -5
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- package/dist/runtime/dom/inspect.d.ts +17 -3
- package/dist/runtime/dom/inspect.js +40 -26
- package/dist/runtime/dom/inspectModel.d.ts +3 -3
- package/dist/runtime/dom/inspectRules.d.ts +29 -3
- package/dist/runtime/dom/inspectRules.js +205 -11
- package/dist/runtime/dom/layout.d.ts +0 -2
- package/dist/runtime/dom/layout.js +1 -2
- package/dist/runtime/dom/reactEventHelpers.d.ts +4 -1
- package/dist/runtime/dom/reactEventHelpers.js +9 -2
- package/dist/runtime/dom/targetNode.d.ts +10 -6
- package/dist/runtime/dom/targetNode.js +15 -8
- package/dist/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +38 -3
- package/dist/session/paths.js +154 -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/a11y.d.ts +15 -1
- package/dist/telemetry/a11y.js +83 -0
- package/dist/telemetry/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +11 -3
- package/dist/telemetry/har/sanitize.d.ts +24 -0
- package/dist/telemetry/har/sanitize.js +138 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
- package/dist/telemetry/har/sanitizeBody.js +168 -0
- package/dist/telemetry/network.d.ts +13 -16
- package/dist/telemetry/network.js +30 -52
- package/dist/telemetry/networkRetention.d.ts +83 -0
- package/dist/telemetry/networkRetention.js +117 -0
- package/dist/types.d.ts +26 -0
- package/dist/ui/OutputBuilder.d.ts +10 -0
- package/dist/ui/OutputBuilder.js +12 -0
- package/dist/ui/formatters/a11y.d.ts +5 -7
- package/dist/ui/formatters/a11y.js +7 -61
- package/dist/ui/formatters/console/chronological.js +4 -4
- package/dist/ui/formatters/console/follow.d.ts +4 -2
- package/dist/ui/formatters/console/follow.js +6 -3
- package/dist/ui/formatters/console/json.d.ts +3 -6
- package/dist/ui/formatters/console/json.js +9 -13
- package/dist/ui/formatters/console/shared.d.ts +17 -2
- package/dist/ui/formatters/console/shared.js +17 -0
- package/dist/ui/formatters/console/summarize.d.ts +2 -2
- package/dist/ui/formatters/console/summarize.js +22 -7
- package/dist/ui/formatters/console.d.ts +1 -1
- package/dist/ui/formatters/console.js +1 -5
- package/dist/ui/formatters/details.js +1 -1
- package/dist/ui/formatters/dom.d.ts +13 -4
- package/dist/ui/formatters/dom.js +25 -7
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/longValues.d.ts +14 -0
- package/dist/ui/formatters/longValues.js +23 -0
- package/dist/ui/formatters/networkList.d.ts +8 -2
- package/dist/ui/formatters/networkList.js +11 -2
- package/dist/ui/formatters/preview.d.ts +4 -1
- package/dist/ui/formatters/preview.js +55 -13
- package/dist/ui/formatters/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/messages/chrome.d.ts +34 -7
- package/dist/ui/messages/chrome.js +81 -15
- package/dist/ui/messages/commands.d.ts +29 -8
- package/dist/ui/messages/commands.js +36 -8
- package/dist/ui/messages/networkMessages.d.ts +50 -0
- package/dist/ui/messages/networkMessages.js +66 -0
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- 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 +9 -2
- package/dist/utils/http.js +4 -3
- package/dist/utils/strings.d.ts +19 -0
- package/dist/utils/strings.js +16 -0
- package/package.json +2 -2
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* before the launch, and a second listener is how a conflict shows.
|
|
19
19
|
*/
|
|
20
20
|
import { createLogger } from '../ui/logging/index.js';
|
|
21
|
+
import { chromeNotAnsweringReason, portTakenByReason } from '../ui/messages/chrome.js';
|
|
21
22
|
import { delay } from '../utils/async.js';
|
|
22
|
-
import { fetchBrowserWsUrl } from '../utils/http.js';
|
|
23
|
+
import { CDP_HTTP_TIMEOUT_MS, fetchBrowserWsUrl, probeDevToolsEndpoint, } from '../utils/http.js';
|
|
23
24
|
import { isProcessAlive } from '../utils/process.js';
|
|
24
25
|
import { ChromeLaunchError } from './errors.js';
|
|
25
26
|
import { acceptsConnections } from './portReservation.js';
|
|
@@ -29,6 +30,8 @@ const LISTENING_PATTERN = /^DevTools listening on (ws:\/\/\S+)/;
|
|
|
29
30
|
/** How long to wait for Chrome to announce its endpoint after the port answered */
|
|
30
31
|
const ENDPOINT_WAIT_MS = 10000;
|
|
31
32
|
const ENDPOINT_POLL_MS = 50;
|
|
33
|
+
/** Shortest wait for one answer, also when the deadline has passed */
|
|
34
|
+
const MIN_ANSWER_WAIT_MS = 1000;
|
|
32
35
|
const log = createLogger('chrome');
|
|
33
36
|
/**
|
|
34
37
|
* Find the endpoint Chrome announced in its output (the last announcement).
|
|
@@ -75,32 +78,75 @@ export async function waitForDevToolsEndpoint(logs, isRunning, timeoutMs = ENDPO
|
|
|
75
78
|
* Check that the Chrome answering on 127.0.0.1:<port> is the one just
|
|
76
79
|
* launched, so bdg never drives another session's browser.
|
|
77
80
|
*
|
|
81
|
+
* A Chrome that announced 127.0.0.1:<port> but does not answer yet (a slow
|
|
82
|
+
* start, its `/json/version` request timing out) is asked again until the
|
|
83
|
+
* deadline; only an answer from something else is a port conflict. A Chrome
|
|
84
|
+
* that fell back to [::1] is asked once: 127.0.0.1 is held by something else.
|
|
85
|
+
*
|
|
78
86
|
* @param options - Chrome's log positions from before the launch, requested
|
|
79
87
|
* port (free on 127.0.0.1 and ::1 right before the launch), Chrome's PID and
|
|
80
|
-
* longest wait for its announcement
|
|
88
|
+
* longest wait for its announcement and its answer
|
|
81
89
|
* @throws ChromeLaunchError: CHROME_DIED_AFTER_LAUNCH if Chrome exits first;
|
|
82
90
|
* PORT_IN_USE if another browser or process answers on the port;
|
|
83
|
-
* CHROME_LAUNCH_FAILED if Chrome announced nothing and nothing answers
|
|
91
|
+
* CHROME_LAUNCH_FAILED if Chrome announced nothing and nothing answers, or
|
|
92
|
+
* announced the port but did not answer on it in time
|
|
84
93
|
*/
|
|
85
94
|
export async function verifyLaunchedChrome(options) {
|
|
86
|
-
const { logs, port, pid, timeoutMs } = options;
|
|
95
|
+
const { logs, port, pid, timeoutMs = ENDPOINT_WAIT_MS } = options;
|
|
96
|
+
const started = Date.now();
|
|
87
97
|
const isRunning = () => isProcessAlive(pid);
|
|
88
98
|
const endpoint = await waitForDevToolsEndpoint(logs, isRunning, timeoutMs);
|
|
89
|
-
if (!endpoint && !isRunning())
|
|
90
|
-
throw
|
|
91
|
-
issue: { code: 'CHROME_DIED_AFTER_LAUNCH', context: { port, pid } },
|
|
92
|
-
});
|
|
93
|
-
}
|
|
99
|
+
if (!endpoint && !isRunning())
|
|
100
|
+
throw diedError(port, pid);
|
|
94
101
|
if (!endpoint)
|
|
95
102
|
return acceptUnannouncedChrome(logs, port);
|
|
96
103
|
if (endpoint.port !== port) {
|
|
97
104
|
throw portTakenError(port, `Chrome listens on port ${endpoint.port} instead`);
|
|
98
105
|
}
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
const onLoopback = endpoint.host === '127.0.0.1';
|
|
107
|
+
const deadline = onLoopback ? started + timeoutMs : Date.now();
|
|
108
|
+
const answer = await waitForAnswer(port, deadline, isRunning);
|
|
109
|
+
if (answer.kind === 'devtools' && new URL(answer.wsUrl).pathname === endpoint.browserPath) {
|
|
110
|
+
log.debug(`Chrome on port ${port} is the launched one (${endpoint.browserPath})`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (answer.kind === 'unreachable' && !isRunning())
|
|
114
|
+
throw diedError(port, pid);
|
|
115
|
+
if (answer.kind === 'unreachable' && onLoopback) {
|
|
116
|
+
throw slowStartError(port, Date.now() - started);
|
|
117
|
+
}
|
|
118
|
+
throw portTakenError(port, portTakenByReason(answerSource(answer), endpoint.host));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Ask 127.0.0.1:<port> for its DevTools version until something answers,
|
|
122
|
+
* Chrome exits or the deadline passes (at least once). Each request waits at
|
|
123
|
+
* most until the deadline (but at least MIN_ANSWER_WAIT_MS).
|
|
124
|
+
*
|
|
125
|
+
* @param port - Requested port
|
|
126
|
+
* @param deadline - Time (ms since epoch) to stop asking
|
|
127
|
+
* @param isRunning - Whether Chrome is still running
|
|
128
|
+
* @returns The first answer, or the last `unreachable` result
|
|
129
|
+
*/
|
|
130
|
+
async function waitForAnswer(port, deadline, isRunning) {
|
|
131
|
+
for (;;) {
|
|
132
|
+
const remaining = Math.max(deadline - Date.now(), MIN_ANSWER_WAIT_MS);
|
|
133
|
+
const timeoutMs = Math.min(CDP_HTTP_TIMEOUT_MS, remaining);
|
|
134
|
+
const answer = await probeDevToolsEndpoint(port, undefined, { timeoutMs });
|
|
135
|
+
if (answer.kind !== 'unreachable' || !isRunning() || Date.now() >= deadline)
|
|
136
|
+
return answer;
|
|
137
|
+
await delay(ENDPOINT_POLL_MS);
|
|
102
138
|
}
|
|
103
|
-
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* What answered on 127.0.0.1:<port> instead of the launched Chrome.
|
|
142
|
+
*
|
|
143
|
+
* @param answer - The answer
|
|
144
|
+
* @returns A browser, another process, or nothing
|
|
145
|
+
*/
|
|
146
|
+
function answerSource(answer) {
|
|
147
|
+
if (answer.kind === 'devtools')
|
|
148
|
+
return 'browser';
|
|
149
|
+
return answer.kind === 'not-devtools' ? 'process' : 'nothing';
|
|
104
150
|
}
|
|
105
151
|
/**
|
|
106
152
|
* Accept a Chrome that did not announce its endpoint, if a browser answers on
|
|
@@ -128,6 +174,32 @@ async function acceptUnannouncedChrome(logs, port) {
|
|
|
128
174
|
}
|
|
129
175
|
log.info(`Warning: ${missing}; using the browser on 127.0.0.1:${port}, which was free before the launch`);
|
|
130
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* The error for a Chrome that exited during the launch checks.
|
|
179
|
+
*
|
|
180
|
+
* @param port - Requested port
|
|
181
|
+
* @param pid - Chrome's PID
|
|
182
|
+
* @returns Launch error with the CHROME_DIED_AFTER_LAUNCH issue
|
|
183
|
+
*/
|
|
184
|
+
function diedError(port, pid) {
|
|
185
|
+
return new ChromeLaunchError(`Chrome died immediately after launch (PID: ${pid})`, {
|
|
186
|
+
issue: { code: 'CHROME_DIED_AFTER_LAUNCH', context: { port, pid } },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The error for a Chrome that announced the port but did not answer on it in
|
|
191
|
+
* time.
|
|
192
|
+
*
|
|
193
|
+
* @param port - Requested port
|
|
194
|
+
* @param waitedMs - How long bdg waited
|
|
195
|
+
* @returns Launch error with the CHROME_LAUNCH_FAILED issue
|
|
196
|
+
*/
|
|
197
|
+
function slowStartError(port, waitedMs) {
|
|
198
|
+
const reason = chromeNotAnsweringReason(port, waitedMs);
|
|
199
|
+
return new ChromeLaunchError(reason, {
|
|
200
|
+
issue: { code: 'CHROME_LAUNCH_FAILED', context: { port, reason } },
|
|
201
|
+
});
|
|
202
|
+
}
|
|
131
203
|
/**
|
|
132
204
|
* The error for a port another process answers on.
|
|
133
205
|
*
|
|
@@ -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
|
|
@@ -4,7 +4,7 @@ import type { Options as ChromeLaunchOptions } from 'chrome-launcher';
|
|
|
4
4
|
* Options that control how Chrome is launched for CDP sessions.
|
|
5
5
|
* Extended to support chrome-launcher advanced features.
|
|
6
6
|
*/
|
|
7
|
-
export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'connectionPollInterval' | 'maxConnectionRetries' | 'portStrictMode' | 'envVars' | '
|
|
7
|
+
export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'connectionPollInterval' | 'maxConnectionRetries' | 'portStrictMode' | 'envVars' | 'ignoreDefaultFlags' | 'chromeFlags' | 'chromePath'> {
|
|
8
8
|
/** Remote debugging port (defaults to 9222 when omitted) */
|
|
9
9
|
port?: number;
|
|
10
10
|
/**
|
|
@@ -2,7 +2,7 @@ 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
6
|
import { makeDirectory } from '../utils/directories.js';
|
|
7
7
|
import { getErrorMessage } from '../utils/errors.js';
|
|
8
8
|
import { filterDefined } from '../utils/objects.js';
|
|
@@ -224,7 +224,6 @@ function buildChromeOptions(options) {
|
|
|
224
224
|
const chromePathOverride = resolveChromeBinary(options);
|
|
225
225
|
return {
|
|
226
226
|
logLevel: options.logLevel ?? DEFAULT_CHROME_LOG_LEVEL,
|
|
227
|
-
handleSIGINT: options.handleSIGINT ?? DEFAULT_CHROME_HANDLE_SIGINT,
|
|
228
227
|
ignoreDefaultFlags: true,
|
|
229
228
|
chromeFlags: buildChromeFlags(options),
|
|
230
229
|
userDataDir,
|
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
|
*/
|
|
@@ -58,7 +54,8 @@ export declare const DOCKER_CHROME_FLAGS: string[];
|
|
|
58
54
|
*/
|
|
59
55
|
export declare const BDG_CHROME_PREFS: Record<string, unknown>;
|
|
60
56
|
/**
|
|
61
|
-
*
|
|
57
|
+
* Finished network requests kept: past this the oldest are dropped, so the
|
|
58
|
+
* newest are kept (requests in flight are never dropped)
|
|
62
59
|
* Prevents memory issues in long-running sessions with high network activity
|
|
63
60
|
*/
|
|
64
61
|
export declare const MAX_NETWORK_REQUESTS = 10000;
|
|
@@ -88,6 +85,24 @@ export declare const OBJECT_EXPANSION_FAILURE_THRESHOLD = 5;
|
|
|
88
85
|
* Can be overridden with --max-body-size flag
|
|
89
86
|
*/
|
|
90
87
|
export declare const MAX_RESPONSE_SIZE: number;
|
|
88
|
+
/**
|
|
89
|
+
* Total size of the response bodies a session keeps (100MB)
|
|
90
|
+
* Past this the oldest bodies are replaced by a placeholder; their requests stay
|
|
91
|
+
*/
|
|
92
|
+
export declare const MAX_TOTAL_BODY_BYTES: number;
|
|
93
|
+
/**
|
|
94
|
+
* Characters of one value `dom get --raw` and `dom eval` print (human output,
|
|
95
|
+
* and eval string results and outer HTML in JSON)
|
|
96
|
+
*/
|
|
97
|
+
export declare const MAX_VALUE_LENGTH = 20000;
|
|
98
|
+
/**
|
|
99
|
+
* Characters of a console message text in human output (`console`, `peek`)
|
|
100
|
+
*/
|
|
101
|
+
export declare const MAX_CONSOLE_TEXT_LENGTH = 200;
|
|
102
|
+
/**
|
|
103
|
+
* Characters of a console message text in JSON output (`console`, `peek`)
|
|
104
|
+
*/
|
|
105
|
+
export declare const MAX_CONSOLE_JSON_TEXT_LENGTH = 10000;
|
|
91
106
|
/**
|
|
92
107
|
* Total Chrome network buffer size (50MB)
|
|
93
108
|
* Limits total memory used by Chrome for preserving network payloads
|
|
@@ -103,6 +118,17 @@ export declare const CHROME_NETWORK_BUFFER_PER_RESOURCE: number;
|
|
|
103
118
|
* Limits size of POST body data included in requestWillBeSent notification
|
|
104
119
|
*/
|
|
105
120
|
export declare const CHROME_POST_DATA_LIMIT: number;
|
|
121
|
+
/** Matches `dom query` and `dom a11y query` list with `--json` and no `--limit` */
|
|
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;
|
|
125
|
+
/** Matches `dom layout` measures per command (the rest are counted as omitted) */
|
|
126
|
+
export declare const LAYOUT_ELEMENT_LIMIT = 100;
|
|
127
|
+
/**
|
|
128
|
+
* Requests listed in an action's result (a click that loads a page triggers
|
|
129
|
+
* its whole load); notable ones are kept before assets
|
|
130
|
+
*/
|
|
131
|
+
export declare const MAX_TRIGGERED_REQUESTS = 50;
|
|
106
132
|
/**
|
|
107
133
|
* Default page readiness timeout (2 seconds)
|
|
108
134
|
* Maximum time to wait for page to be ready before proceeding
|
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
|
*/
|
|
@@ -87,7 +83,8 @@ export const BDG_CHROME_PREFS = {
|
|
|
87
83
|
// DATA COLLECTION LIMITS
|
|
88
84
|
// ============================================================================
|
|
89
85
|
/**
|
|
90
|
-
*
|
|
86
|
+
* Finished network requests kept: past this the oldest are dropped, so the
|
|
87
|
+
* newest are kept (requests in flight are never dropped)
|
|
91
88
|
* Prevents memory issues in long-running sessions with high network activity
|
|
92
89
|
*/
|
|
93
90
|
export const MAX_NETWORK_REQUESTS = 10000;
|
|
@@ -120,6 +117,27 @@ export const OBJECT_EXPANSION_FAILURE_THRESHOLD = 5;
|
|
|
120
117
|
* Can be overridden with --max-body-size flag
|
|
121
118
|
*/
|
|
122
119
|
export const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
120
|
+
/**
|
|
121
|
+
* Total size of the response bodies a session keeps (100MB)
|
|
122
|
+
* Past this the oldest bodies are replaced by a placeholder; their requests stay
|
|
123
|
+
*/
|
|
124
|
+
export const MAX_TOTAL_BODY_BYTES = 100 * 1024 * 1024;
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// OUTPUT VALUE LIMITS (lifted by --full)
|
|
127
|
+
// ============================================================================
|
|
128
|
+
/**
|
|
129
|
+
* Characters of one value `dom get --raw` and `dom eval` print (human output,
|
|
130
|
+
* and eval string results and outer HTML in JSON)
|
|
131
|
+
*/
|
|
132
|
+
export const MAX_VALUE_LENGTH = 20_000;
|
|
133
|
+
/**
|
|
134
|
+
* Characters of a console message text in human output (`console`, `peek`)
|
|
135
|
+
*/
|
|
136
|
+
export const MAX_CONSOLE_TEXT_LENGTH = 200;
|
|
137
|
+
/**
|
|
138
|
+
* Characters of a console message text in JSON output (`console`, `peek`)
|
|
139
|
+
*/
|
|
140
|
+
export const MAX_CONSOLE_JSON_TEXT_LENGTH = 10_000;
|
|
123
141
|
// ============================================================================
|
|
124
142
|
// CHROME CDP BUFFER LIMITS
|
|
125
143
|
// ============================================================================
|
|
@@ -139,6 +157,20 @@ export const CHROME_NETWORK_BUFFER_PER_RESOURCE = 10 * 1024 * 1024; // 10MB
|
|
|
139
157
|
*/
|
|
140
158
|
export const CHROME_POST_DATA_LIMIT = 1 * 1024 * 1024; // 1MB
|
|
141
159
|
// ============================================================================
|
|
160
|
+
// JSON LIST LIMITS
|
|
161
|
+
// ============================================================================
|
|
162
|
+
/** Matches `dom query` and `dom a11y query` list with `--json` and no `--limit` */
|
|
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;
|
|
166
|
+
/** Matches `dom layout` measures per command (the rest are counted as omitted) */
|
|
167
|
+
export const LAYOUT_ELEMENT_LIMIT = 100;
|
|
168
|
+
/**
|
|
169
|
+
* Requests listed in an action's result (a click that loads a page triggers
|
|
170
|
+
* its whole load); notable ones are kept before assets
|
|
171
|
+
*/
|
|
172
|
+
export const MAX_TRIGGERED_REQUESTS = 50;
|
|
173
|
+
// ============================================================================
|
|
142
174
|
// TIMEOUTS & INTERVALS
|
|
143
175
|
// ============================================================================
|
|
144
176
|
/**
|
|
@@ -245,6 +245,8 @@ export class SessionController {
|
|
|
245
245
|
network: data.totalNetwork,
|
|
246
246
|
console: data.totalConsole,
|
|
247
247
|
...(data.droppedConsole && { consoleDropped: data.droppedConsole }),
|
|
248
|
+
...(data.droppedNetwork && { networkDropped: data.droppedNetwork }),
|
|
249
|
+
...(data.evictedNetworkBodies && { networkBodiesEvicted: data.evictedNetworkBodies }),
|
|
248
250
|
},
|
|
249
251
|
currentNavigationId: data.currentNavigationId,
|
|
250
252
|
...(data.pageCrashedAt !== undefined && { pageCrashedAt: data.pageCrashedAt }),
|
|
@@ -16,6 +16,8 @@ export interface SpawnedDaemon {
|
|
|
16
16
|
* Ensure a daemon is running, spawning one if needed.
|
|
17
17
|
*
|
|
18
18
|
* @returns The daemon it spawned, or undefined when one was already running
|
|
19
|
+
* @throws SessionDirError if the session directory is unusable or untrusted
|
|
20
|
+
* (checked first, so a socket planted there is never taken for a daemon)
|
|
19
21
|
* @throws DaemonStartupError if the daemon script is missing or the daemon
|
|
20
22
|
* does not accept connections in time
|
|
21
23
|
*/
|
|
@@ -24,9 +26,21 @@ export declare function launchDaemon(): Promise<SpawnedDaemon | undefined>;
|
|
|
24
26
|
* Check that the session directory can hold the daemon's files before
|
|
25
27
|
* spawning it (otherwise the daemon dies and only its log says why).
|
|
26
28
|
*
|
|
27
|
-
* @throws SessionDirError (103) for a file
|
|
28
|
-
* directory (a pseudo-filesystem like `/proc`)
|
|
29
|
-
*
|
|
29
|
+
* @throws SessionDirError (103) for a file, a path that cannot hold a
|
|
30
|
+
* directory (a pseudo-filesystem like `/proc`) or an untrusted directory
|
|
31
|
+
* (see {@link secureSessionDir}, which also tightens the user's own 0755
|
|
32
|
+
* directories to 0700), (81) for a too-long path like a named session's,
|
|
33
|
+
* (82) when not writable
|
|
30
34
|
*/
|
|
31
35
|
export declare function assertUsableSessionDir(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Open the daemon log for the daemon's output, creating it 0600. A symlink in
|
|
38
|
+
* its place is refused, not followed: it would append the log to the file it
|
|
39
|
+
* points to.
|
|
40
|
+
*
|
|
41
|
+
* @param logPath - Daemon log path
|
|
42
|
+
* @returns File descriptor
|
|
43
|
+
* @throws SessionDirError (103) when it cannot be opened (`ELOOP` for a symlink)
|
|
44
|
+
*/
|
|
45
|
+
export declare function openDaemonLog(logPath: string): number;
|
|
32
46
|
//# sourceMappingURL=launcher.d.ts.map
|
package/dist/daemon/launcher.js
CHANGED
|
@@ -10,10 +10,10 @@ import { spawn } from 'child_process';
|
|
|
10
10
|
import fs from 'fs';
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
import { DaemonStartupError, SessionDirError } from './errors.js';
|
|
13
|
-
import { sessionDirIsFileError, sessionDirNotWritableError, socketPathTooLongError, } from '../errors/messages.js';
|
|
13
|
+
import { daemonLogNotOpenedError, sessionDirIsFileError, sessionDirNotWritableError, socketPathTooLongError, untrustedSessionDirError, } from '../errors/messages.js';
|
|
14
14
|
import { killSessionChromes, readLiveDaemonPid } from '../session/cleanup/staleSession.js';
|
|
15
15
|
import { isDaemonAlive, isDaemonSocketGone } from '../session/daemonSocket.js';
|
|
16
|
-
import { MAX_DAEMON_SOCKET_PATH_BYTES, ensureSessionDir, getSessionDir, getSessionFilePath, } from '../session/paths.js';
|
|
16
|
+
import { MAX_DAEMON_SOCKET_PATH_BYTES, ensureSessionDir, getSessionDir, getSessionFilePath, secureSessionDir, } from '../session/paths.js';
|
|
17
17
|
import { createLogger } from '../ui/logging/index.js';
|
|
18
18
|
import { delay } from '../utils/async.js';
|
|
19
19
|
import { directoryProblem } from '../utils/directories.js';
|
|
@@ -28,10 +28,13 @@ const DAEMON_READY_POLL_MS = 20;
|
|
|
28
28
|
* Ensure a daemon is running, spawning one if needed.
|
|
29
29
|
*
|
|
30
30
|
* @returns The daemon it spawned, or undefined when one was already running
|
|
31
|
+
* @throws SessionDirError if the session directory is unusable or untrusted
|
|
32
|
+
* (checked first, so a socket planted there is never taken for a daemon)
|
|
31
33
|
* @throws DaemonStartupError if the daemon script is missing or the daemon
|
|
32
34
|
* does not accept connections in time
|
|
33
35
|
*/
|
|
34
36
|
export async function launchDaemon() {
|
|
37
|
+
assertUsableSessionDir();
|
|
35
38
|
if (await isDaemonAlive()) {
|
|
36
39
|
log.debug('Daemon already running');
|
|
37
40
|
return undefined;
|
|
@@ -39,11 +42,10 @@ export async function launchDaemon() {
|
|
|
39
42
|
if (!fs.existsSync(DAEMON_SCRIPT_PATH)) {
|
|
40
43
|
throw new DaemonStartupError(`Daemon script not found at ${DAEMON_SCRIPT_PATH}. Did you run 'npm run build'?`, 'DAEMON_SCRIPT_NOT_FOUND');
|
|
41
44
|
}
|
|
42
|
-
assertUsableSessionDir();
|
|
43
45
|
await stopUnreachableDaemon();
|
|
44
46
|
const logPath = join(getSessionDir(), 'daemon.log');
|
|
45
47
|
rotateLog(logPath);
|
|
46
|
-
const logFd =
|
|
48
|
+
const logFd = openDaemonLog(logPath);
|
|
47
49
|
log.debug(`Starting daemon: ${DAEMON_SCRIPT_PATH}`);
|
|
48
50
|
const daemon = spawn(process.execPath, [DAEMON_SCRIPT_PATH], {
|
|
49
51
|
detached: true,
|
|
@@ -97,9 +99,11 @@ async function stopUnreachableDaemon() {
|
|
|
97
99
|
* Check that the session directory can hold the daemon's files before
|
|
98
100
|
* spawning it (otherwise the daemon dies and only its log says why).
|
|
99
101
|
*
|
|
100
|
-
* @throws SessionDirError (103) for a file
|
|
101
|
-
* directory (a pseudo-filesystem like `/proc`)
|
|
102
|
-
*
|
|
102
|
+
* @throws SessionDirError (103) for a file, a path that cannot hold a
|
|
103
|
+
* directory (a pseudo-filesystem like `/proc`) or an untrusted directory
|
|
104
|
+
* (see {@link secureSessionDir}, which also tightens the user's own 0755
|
|
105
|
+
* directories to 0700), (81) for a too-long path like a named session's,
|
|
106
|
+
* (82) when not writable
|
|
103
107
|
*/
|
|
104
108
|
export function assertUsableSessionDir() {
|
|
105
109
|
const dir = getSessionDir();
|
|
@@ -116,6 +120,9 @@ export function assertUsableSessionDir() {
|
|
|
116
120
|
if (problem) {
|
|
117
121
|
fail(sessionDirNotWritableError(dir, problem.reason), problem.denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR);
|
|
118
122
|
}
|
|
123
|
+
const untrusted = secureSessionDir(dir);
|
|
124
|
+
if (untrusted)
|
|
125
|
+
fail(untrustedSessionDirError(untrusted));
|
|
119
126
|
try {
|
|
120
127
|
ensureSessionDir();
|
|
121
128
|
fs.accessSync(dir, fs.constants.W_OK);
|
|
@@ -126,6 +133,29 @@ export function assertUsableSessionDir() {
|
|
|
126
133
|
fail(sessionDirNotWritableError(dir, code ?? getErrorMessage(error)), denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR);
|
|
127
134
|
}
|
|
128
135
|
}
|
|
136
|
+
/** Flags opening the daemon log for appending, refusing (not following) a symlink */
|
|
137
|
+
const DAEMON_LOG_FLAGS = fs.constants.O_WRONLY |
|
|
138
|
+
fs.constants.O_APPEND |
|
|
139
|
+
fs.constants.O_CREAT |
|
|
140
|
+
(fs.constants.O_NOFOLLOW ?? 0);
|
|
141
|
+
/**
|
|
142
|
+
* Open the daemon log for the daemon's output, creating it 0600. A symlink in
|
|
143
|
+
* its place is refused, not followed: it would append the log to the file it
|
|
144
|
+
* points to.
|
|
145
|
+
*
|
|
146
|
+
* @param logPath - Daemon log path
|
|
147
|
+
* @returns File descriptor
|
|
148
|
+
* @throws SessionDirError (103) when it cannot be opened (`ELOOP` for a symlink)
|
|
149
|
+
*/
|
|
150
|
+
export function openDaemonLog(logPath) {
|
|
151
|
+
try {
|
|
152
|
+
return fs.openSync(logPath, DAEMON_LOG_FLAGS, 0o600);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const err = daemonLogNotOpenedError(logPath, error.code ?? getErrorMessage(error));
|
|
156
|
+
throw new SessionDirError(err.message, err.suggestion);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
129
159
|
/** Size above which the daemon log is rotated when a daemon starts */
|
|
130
160
|
const MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
131
161
|
/**
|
|
@@ -83,7 +83,8 @@ export declare class Session {
|
|
|
83
83
|
* Execute a registered command against this session. After a renderer
|
|
84
84
|
* crash only {@link RUN_ON_CRASHED_PAGE} commands run (`bdg cdp` only for
|
|
85
85
|
* {@link CRASH_SAFE_CDP} methods); others fail with exit 107 instead of
|
|
86
|
-
* waiting for a page that cannot answer.
|
|
86
|
+
* waiting for a page that cannot answer. Commands that may change the page
|
|
87
|
+
* drop `dom inspect`'s kept matched rules ({@link withMatchedStylesReset}).
|
|
87
88
|
*
|
|
88
89
|
* @param name - Command name
|
|
89
90
|
* @param params - Command parameters
|