browser-debugger-cli 0.15.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.
Files changed (133) hide show
  1. package/.claude/skills/bdg/SKILL.md +2 -1
  2. package/dist/cdp/methodTarget.d.ts +92 -0
  3. package/dist/cdp/methodTarget.js +159 -0
  4. package/dist/cdp/protocol.d.ts +16 -1
  5. package/dist/cdp/protocol.js +21 -0
  6. package/dist/cdp/schema.d.ts +55 -1
  7. package/dist/cdp/schema.js +134 -25
  8. package/dist/cdp/types.d.ts +3 -1
  9. package/dist/commands/cdp.d.ts +38 -1
  10. package/dist/commands/cdp.js +200 -133
  11. package/dist/commands/cleanup.js +18 -4
  12. package/dist/commands/dom/formInteraction.js +8 -4
  13. package/dist/commands/dom/helpers/index.d.ts +4 -4
  14. package/dist/commands/dom/helpers/index.js +3 -3
  15. package/dist/commands/dom/helpers/query.d.ts +2 -2
  16. package/dist/commands/dom/helpers/query.js +2 -2
  17. package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
  18. package/dist/commands/dom/helpers/screenshot.js +50 -668
  19. package/dist/commands/dom/screenshot.js +56 -36
  20. package/dist/commands/optionBehaviors.js +18 -8
  21. package/dist/commands/shared/CommandRunner.d.ts +5 -0
  22. package/dist/commands/shared/CommandRunner.js +18 -3
  23. package/dist/commands/shared/interrupt.d.ts +40 -0
  24. package/dist/commands/shared/interrupt.js +73 -0
  25. package/dist/commands/shared/optionTypes.d.ts +2 -0
  26. package/dist/commands/shared/startHelpers.d.ts +26 -3
  27. package/dist/commands/shared/startHelpers.js +145 -23
  28. package/dist/commands/types.d.ts +5 -0
  29. package/dist/connection/cdp.js +1 -16
  30. package/dist/connection/chromeIdentity.d.ts +24 -5
  31. package/dist/connection/chromeIdentity.js +53 -22
  32. package/dist/connection/launcher.d.ts +34 -1
  33. package/dist/connection/launcher.js +98 -10
  34. package/dist/connection/typed-cdp.d.ts +3 -2
  35. package/dist/constants.d.ts +1 -1
  36. package/dist/constants.js +1 -1
  37. package/dist/daemon/SessionController.d.ts +10 -5
  38. package/dist/daemon/SessionController.js +15 -8
  39. package/dist/daemon/ipcServer.js +1 -1
  40. package/dist/daemon/launcher.d.ts +5 -0
  41. package/dist/daemon/launcher.js +8 -1
  42. package/dist/daemon/session/Session.d.ts +5 -1
  43. package/dist/daemon/session/Session.js +9 -8
  44. package/dist/daemon/session/TelemetryStore.d.ts +5 -0
  45. package/dist/daemon/session/TelemetryStore.js +4 -0
  46. package/dist/daemon/session/captureGate.d.ts +59 -0
  47. package/dist/daemon/session/captureGate.js +96 -0
  48. package/dist/daemon/session/chromeConnection.d.ts +16 -1
  49. package/dist/daemon/session/chromeConnection.js +34 -4
  50. package/dist/daemon/session/collectors.d.ts +15 -0
  51. package/dist/daemon/session/collectors.js +39 -2
  52. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  53. package/dist/daemon/session/commandRegistry.js +46 -11
  54. package/dist/daemon/session/downloads.d.ts +32 -0
  55. package/dist/daemon/session/downloads.js +96 -0
  56. package/dist/daemon/session/interactions.d.ts +3 -2
  57. package/dist/daemon/session/interactions.js +7 -2
  58. package/dist/daemon/session/plugins.js +6 -0
  59. package/dist/daemon.js +12843 -11482
  60. package/dist/errors/CommandError.d.ts +2 -0
  61. package/dist/errors/issues.d.ts +1 -1
  62. package/dist/errors/messages.d.ts +58 -0
  63. package/dist/errors/messages.js +112 -0
  64. package/dist/index.js +999 -1020
  65. package/dist/ipc/client.d.ts +14 -1
  66. package/dist/ipc/client.js +21 -4
  67. package/dist/ipc/protocol/commands.d.ts +32 -2
  68. package/dist/ipc/protocol/commands.js +1 -0
  69. package/dist/ipc/protocol/domTypes.d.ts +24 -1
  70. package/dist/ipc/session/queries.d.ts +3 -0
  71. package/dist/ipc/session/types.d.ts +5 -0
  72. package/dist/ipc/transport/IPCError.d.ts +9 -0
  73. package/dist/ipc/transport/IPCError.js +12 -0
  74. package/dist/ipc/transport/errors.d.ts +2 -1
  75. package/dist/ipc/transport/errors.js +4 -1
  76. package/dist/ipc/transport/index.d.ts +4 -2
  77. package/dist/ipc/transport/index.js +13 -3
  78. package/dist/runtime/dom/actionEffects.d.ts +48 -9
  79. package/dist/runtime/dom/actionEffects.js +269 -34
  80. package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
  81. package/dist/runtime/dom/actionEffectsScripts.js +101 -2
  82. package/dist/runtime/dom/captureArea.d.ts +35 -0
  83. package/dist/runtime/dom/captureArea.js +203 -0
  84. package/dist/runtime/dom/elementInfo.d.ts +10 -8
  85. package/dist/runtime/dom/elementInfo.js +8 -6
  86. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  87. package/dist/runtime/page/bdgWorld.d.ts +9 -0
  88. package/dist/runtime/page/bdgWorld.js +11 -0
  89. package/dist/runtime/page/captureEmulation.d.ts +119 -0
  90. package/dist/runtime/page/captureEmulation.js +189 -0
  91. package/dist/runtime/page/captureScroll.d.ts +24 -0
  92. package/dist/runtime/page/captureScroll.js +124 -0
  93. package/dist/runtime/page/screenshot.d.ts +41 -0
  94. package/dist/runtime/page/screenshot.js +394 -0
  95. package/dist/session/paths.d.ts +14 -0
  96. package/dist/session/paths.js +25 -0
  97. package/dist/telemetry/downloads.d.ts +127 -0
  98. package/dist/telemetry/downloads.js +265 -0
  99. package/dist/telemetry/har/builder.js +22 -7
  100. package/dist/telemetry/har/sanitize.d.ts +7 -3
  101. package/dist/telemetry/har/sanitize.js +52 -6
  102. package/dist/telemetry/har/sanitizeBody.d.ts +47 -7
  103. package/dist/telemetry/har/sanitizeBody.js +429 -56
  104. package/dist/telemetry/har/types.d.ts +2 -0
  105. package/dist/telemetry/network.d.ts +4 -4
  106. package/dist/telemetry/network.js +38 -4
  107. package/dist/telemetry/networkRetention.d.ts +35 -14
  108. package/dist/telemetry/networkRetention.js +62 -26
  109. package/dist/types.d.ts +9 -14
  110. package/dist/ui/OutputBuilder.d.ts +3 -2
  111. package/dist/ui/OutputBuilder.js +4 -3
  112. package/dist/ui/formatters/cdp.d.ts +32 -9
  113. package/dist/ui/formatters/cdp.js +77 -6
  114. package/dist/ui/formatters/details.js +7 -15
  115. package/dist/ui/formatters/preview.d.ts +2 -0
  116. package/dist/ui/formatters/preview.js +7 -1
  117. package/dist/ui/formatters/status.js +6 -1
  118. package/dist/ui/formatting.d.ts +7 -0
  119. package/dist/ui/formatting.js +13 -0
  120. package/dist/ui/logging/logger.d.ts +1 -1
  121. package/dist/ui/messages/chrome.d.ts +13 -0
  122. package/dist/ui/messages/chrome.js +26 -0
  123. package/dist/ui/messages/commands.d.ts +71 -3
  124. package/dist/ui/messages/commands.js +98 -3
  125. package/dist/ui/messages/networkMessages.d.ts +24 -5
  126. package/dist/ui/messages/networkMessages.js +31 -8
  127. package/dist/utils/async.d.ts +3 -2
  128. package/dist/utils/async.js +16 -3
  129. package/dist/utils/http.d.ts +11 -4
  130. package/dist/utils/http.js +5 -3
  131. package/package.json +18 -4
  132. /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
  133. /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
@@ -92,8 +92,11 @@ export declare function getHARData(): Promise<HARDataResponse>;
92
92
  *
93
93
  * @param url - Target URL to navigate to
94
94
  * @param options - Session configuration options
95
+ * @param signal - Cancels the start: closes the connection, so the daemon
96
+ * abandons the session it is starting
95
97
  * @returns Start session response with daemon and Chrome PIDs
96
98
  * @throws Error if connection fails, session already running, or Chrome launch fails
99
+ * @throws IPCCancelledError if `signal` aborts first
97
100
  *
98
101
  * @example
99
102
  * ```typescript
@@ -107,7 +110,7 @@ export declare function getHARData(): Promise<HARDataResponse>;
107
110
  * }
108
111
  * ```
109
112
  */
110
- export declare function startSession(url: string, options?: SessionOptions): Promise<StartSessionResponse>;
113
+ export declare function startSession(url: string, options?: SessionOptions, signal?: AbortSignal): Promise<StartSessionResponse>;
111
114
  /**
112
115
  * Request session stop from the daemon.
113
116
  * Stops telemetry collection and closes Chrome.
@@ -258,6 +261,16 @@ export declare function domAudit(params: NoType<(typeof COMMANDS)['dom_audit']['
258
261
  export declare function cssSearch(params: NoType<(typeof COMMANDS)['css_search']['requestSchema']>): Promise<ClientResponse<'css_search'>>;
259
262
  /** What one element looks like: styles, box, layout and child tree. */
260
263
  export declare function domInspect(params: NoType<(typeof COMMANDS)['dom_inspect']['requestSchema']>): Promise<ClientResponse<'dom_inspect'>>;
264
+ /**
265
+ * Capture the page or one element; the daemon puts back the emulation the
266
+ * capture changed before it answers.
267
+ *
268
+ * @param params - What to capture and how
269
+ * @param signal - Cancels the capture (the daemon skips it and only restores)
270
+ * @returns The image and what was captured
271
+ * @throws IPCCancelledError if `signal` aborts first
272
+ */
273
+ export declare function domScreenshot(params: NoType<(typeof COMMANDS)['dom_screenshot']['requestSchema']>, signal?: AbortSignal): Promise<ClientResponse<'dom_screenshot'>>;
261
274
  /**
262
275
  * Wait until elements appear, become visible, contain a text or are gone,
263
276
  * and/or the page has loaded.
@@ -117,8 +117,11 @@ export async function getHARData() {
117
117
  *
118
118
  * @param url - Target URL to navigate to
119
119
  * @param options - Session configuration options
120
+ * @param signal - Cancels the start: closes the connection, so the daemon
121
+ * abandons the session it is starting
120
122
  * @returns Start session response with daemon and Chrome PIDs
121
123
  * @throws Error if connection fails, session already running, or Chrome launch fails
124
+ * @throws IPCCancelledError if `signal` aborts first
122
125
  *
123
126
  * @example
124
127
  * ```typescript
@@ -132,7 +135,7 @@ export async function getHARData() {
132
135
  * }
133
136
  * ```
134
137
  */
135
- export async function startSession(url, options) {
138
+ export async function startSession(url, options, signal) {
136
139
  const request = withSession({
137
140
  type: 'start_session_request',
138
141
  url,
@@ -151,7 +154,7 @@ export async function startSession(url, options) {
151
154
  }),
152
155
  });
153
156
  await assertResponsive();
154
- return sendRequest(request, 'start session', 'start_session_response');
157
+ return sendRequest(request, 'start session', 'start_session_response', undefined, undefined, signal);
155
158
  }
156
159
  /**
157
160
  * Request session stop from the daemon.
@@ -180,10 +183,12 @@ export async function stopSession() {
180
183
  * @param commandName - Name of the command to send
181
184
  * @param params - Command parameters (without type field)
182
185
  * @param timeoutMs - How long to wait (default: IPC timeout; page work can take long)
186
+ * @param signal - Cancels the command: closes the connection, which the daemon notices
183
187
  * @returns Command response from the session
184
188
  * @throws Error if connection fails or command execution fails
189
+ * @throws IPCCancelledError if `signal` aborts first
185
190
  */
186
- async function sendCommand(commandName, params, timeoutMs) {
191
+ async function sendCommand(commandName, params, timeoutMs, signal) {
187
192
  const request = {
188
193
  ...params,
189
194
  type: `${commandName}_request`,
@@ -191,7 +196,7 @@ async function sendCommand(commandName, params, timeoutMs) {
191
196
  };
192
197
  if (timeoutMs === undefined || timeoutMs > getQuickIPCRequestTimeout())
193
198
  await assertResponsive();
194
- return sendRequest(request, commandName, `${commandName}_response`, timeoutMs);
199
+ return sendRequest(request, commandName, `${commandName}_response`, timeoutMs, undefined, signal);
195
200
  }
196
201
  /**
197
202
  * Get details for a specific network request or console message.
@@ -376,6 +381,18 @@ export async function cssSearch(params) {
376
381
  export async function domInspect(params) {
377
382
  return sendCommand('dom_inspect', params);
378
383
  }
384
+ /**
385
+ * Capture the page or one element; the daemon puts back the emulation the
386
+ * capture changed before it answers.
387
+ *
388
+ * @param params - What to capture and how
389
+ * @param signal - Cancels the capture (the daemon skips it and only restores)
390
+ * @returns The image and what was captured
391
+ * @throws IPCCancelledError if `signal` aborts first
392
+ */
393
+ export async function domScreenshot(params, signal) {
394
+ return sendCommand('dom_screenshot', params, undefined, signal);
395
+ }
379
396
  /** Time the client gives `dom wait` beyond its --timeout (the daemon reports the timeout first) */
380
397
  const WAIT_IPC_MARGIN_MS = 10_000;
381
398
  /**
@@ -6,10 +6,10 @@
6
6
  */
7
7
  import type { HintDetails } from '../../errors/notices.js';
8
8
  import type { AuditCheck, AuditResult, CssSearchResult } from './auditTypes.js';
9
- import type { ClickResult, FillResult, LayoutResult, ListenersResult, PressKeyResult, RawFormData, ScrollResult, SubmitResult } from './domTypes.js';
9
+ import type { ClickResult, DownloadInfo, FillResult, LayoutResult, ListenersResult, PressKeyResult, RawFormData, ScrollResult, SubmitResult } from './domTypes.js';
10
10
  import type { InspectResult } from './inspectTypes.js';
11
11
  import type { PageState, SessionActivity } from '../session/types.js';
12
- import type { ColorScheme, NetworkRequest, ViewportSize } from '../../types.js';
12
+ import type { ColorScheme, NetworkRequest, ScreenshotResult, ViewportSize } from '../../types.js';
13
13
  /**
14
14
  * Session peek command request schema.
15
15
  */
@@ -78,6 +78,8 @@ export interface SessionPeekData {
78
78
  droppedNetwork?: number;
79
79
  /** Response bodies evicted at the total body budget (the oldest) */
80
80
  evictedNetworkBodies?: number;
81
+ /** Downloads that began during the session, oldest first (left out when none) */
82
+ downloads?: DownloadInfo[];
81
83
  /** Whether there are more network items available. */
82
84
  hasMoreNetwork?: boolean;
83
85
  /** Whether there are more console items available. */
@@ -378,6 +380,33 @@ export interface DomInspectCommand {
378
380
  why?: string;
379
381
  }
380
382
  export type DomInspectData = InspectResult;
383
+ /**
384
+ * dom_screenshot: capture the page, or one element, as an image. The daemon
385
+ * changes the page's emulation for the capture and puts it back before it
386
+ * answers, so an interrupted CLI cannot leave it changed.
387
+ */
388
+ export interface DomScreenshotCommand {
389
+ format: 'png' | 'jpeg';
390
+ /** JPEG quality (default 90) */
391
+ quality?: number;
392
+ /** Keep the full size instead of scaling down to the token budget */
393
+ noResize?: boolean;
394
+ /** Element to capture (the page when absent) */
395
+ backendNodeId?: number;
396
+ /** Element capture: CSS px of page added around the captured area */
397
+ padding?: number;
398
+ /** Page capture: the whole page (default true) */
399
+ fullPage?: boolean;
400
+ /** Page capture: selector scrolled into view first */
401
+ scroll?: string;
402
+ }
403
+ /** A captured image and what it shows */
404
+ export interface DomScreenshotData {
405
+ /** The image, base64-encoded */
406
+ image: string;
407
+ /** What was captured (all a screenshot reports but the file it is written to) */
408
+ screenshot: Omit<ScreenshotResult, 'path'>;
409
+ }
381
410
  /**
382
411
  * dom_form_discover: run the form discovery script and return raw form data.
383
412
  */
@@ -413,6 +442,7 @@ export type RegistryShape = {
413
442
  dom_audit: CommandDef<DomAuditCommand, DomAuditData>;
414
443
  css_search: CommandDef<CssSearchCommand, CssSearchData>;
415
444
  dom_inspect: CommandDef<DomInspectCommand, DomInspectData>;
445
+ dom_screenshot: CommandDef<DomScreenshotCommand, DomScreenshotData>;
416
446
  dom_wait: CommandDef<DomWaitCommand, DomWaitData>;
417
447
  page_navigate: CommandDef<PageNavigateCommand, PageNavigationResult>;
418
448
  page_emulate: CommandDef<PageEmulateCommand, PageEmulationResult>;
@@ -38,6 +38,7 @@ export const COMMANDS = {
38
38
  dom_audit: defineCommand(),
39
39
  css_search: defineCommand(),
40
40
  dom_inspect: defineCommand(),
41
+ dom_screenshot: defineCommand(),
41
42
  dom_wait: defineCommand(),
42
43
  };
43
44
  //# sourceMappingURL=commands.js.map
@@ -67,6 +67,27 @@ export interface ShownElement {
67
67
  /** The element, e.g. `div.figcaption` */
68
68
  element: string;
69
69
  }
70
+ /** State of a download: still running, saved, or stopped before it finished */
71
+ export type DownloadState = 'inProgress' | 'completed' | 'canceled';
72
+ /** A file download a command started */
73
+ export interface DownloadInfo {
74
+ /** URL of the downloaded resource */
75
+ url: string;
76
+ /** File name the page or server suggested */
77
+ suggestedFilename: string;
78
+ /**
79
+ * Absolute path of the file once saved: for a Chrome bdg launched,
80
+ * `<session dir>/downloads/<name>` (`(1)`, `(2)`… added when the name is
81
+ * taken), set while it still runs; for an attached Chrome only when Chrome
82
+ * says where it saved it. Absent for a canceled download
83
+ */
84
+ path?: string;
85
+ state: DownloadState;
86
+ /** Bytes received so far (all of them once completed) */
87
+ bytes?: number;
88
+ /** Why bdg canceled it, e.g. its downloads directory could not be created */
89
+ reason?: string;
90
+ }
70
91
  /** What the page was still working on when an action returned */
71
92
  export interface PendingChanges {
72
93
  /** Content requests (documents, fetch/XHR, scripts) the action started that were still running */
@@ -75,7 +96,7 @@ export interface PendingChanges {
75
96
  navigation?: true;
76
97
  /** A loading indicator that appeared during the action and was still shown, e.g. `div#loading` */
77
98
  loading?: string;
78
- /** The DOM was still changing (several bursts of changes, the last one under 150 ms ago) */
99
+ /** The DOM was still changing (several bursts of changes, the last one under 150 ms of quiet time ago) */
79
100
  domChanging?: true;
80
101
  /** The page did not answer within 250 ms (a long-running script) */
81
102
  busy?: true;
@@ -98,6 +119,8 @@ export interface ActionEffects {
98
119
  pending?: PendingChanges;
99
120
  /** All built-ins the page replaced that bdg's action scripts use (when the warning mentions them) */
100
121
  replacedBuiltins?: string[];
122
+ /** Downloads that began during the action (absent when none did) */
123
+ downloads?: DownloadInfo[];
101
124
  }
102
125
  /** A filled field's value differing from the one given */
103
126
  export interface FillValueMismatch {
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import type { IPCMessage } from './lifecycle.js';
7
7
  import type { PageState, SessionActivity } from './types.js';
8
+ import type { DownloadInfo } from '../protocol/domTypes.js';
8
9
  import type { ColorScheme, NetworkRequest, TelemetryType, ViewportSize } from '../../types.js';
9
10
  /**
10
11
  * Status request (client → daemon).
@@ -100,6 +101,8 @@ export interface PeekResponseData {
100
101
  console: number;
101
102
  };
102
103
  currentNavigationId?: number;
104
+ /** Downloads that began during the session, oldest first */
105
+ downloads?: DownloadInfo[];
103
106
  partial?: boolean;
104
107
  };
105
108
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Common types used across session messages and session commands.
5
5
  */
6
+ import type { DownloadInfo } from '../protocol/domTypes.js';
6
7
  import type { ColorScheme, ViewportSize } from '../../types.js';
7
8
  /**
8
9
  * Session activity metrics.
@@ -20,6 +21,10 @@ export interface SessionActivity {
20
21
  lastNetworkRequestAt?: number;
21
22
  /** Timestamp of last console message. */
22
23
  lastConsoleMessageAt?: number;
24
+ /** Downloads that began during the session, oldest first (left out when none). */
25
+ downloads?: DownloadInfo[];
26
+ /** Why downloads do not go to the session directory (refused, or not redirected), while they do not */
27
+ downloadsWarning?: string;
23
28
  }
24
29
  /**
25
30
  * Current page state.
@@ -79,4 +79,13 @@ export declare class IPCEarlyCloseError extends IPCError {
79
79
  readonly requestName: string;
80
80
  constructor(requestName: string);
81
81
  }
82
+ /**
83
+ * Error thrown when the client cancels a request (its abort signal fired),
84
+ * closing the connection before the response arrived.
85
+ */
86
+ export declare class IPCCancelledError extends IPCError {
87
+ readonly name = "IPCCancelledError";
88
+ readonly requestName: string;
89
+ constructor(requestName: string);
90
+ }
82
91
  //# sourceMappingURL=IPCError.d.ts.map
@@ -106,4 +106,16 @@ export class IPCEarlyCloseError extends IPCError {
106
106
  this.requestName = requestName;
107
107
  }
108
108
  }
109
+ /**
110
+ * Error thrown when the client cancels a request (its abort signal fired),
111
+ * closing the connection before the response arrived.
112
+ */
113
+ export class IPCCancelledError extends IPCError {
114
+ name = 'IPCCancelledError';
115
+ requestName;
116
+ constructor(requestName) {
117
+ super(`${requestName} request cancelled`, EXIT_CODES.SOFTWARE_ERROR);
118
+ this.requestName = requestName;
119
+ }
120
+ }
109
121
  //# sourceMappingURL=IPCError.js.map
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Formats transport-layer errors with context using structured error classes.
5
5
  */
6
- import { IPCConnectionError, IPCParseError, IPCTimeoutError, IPCEarlyCloseError } from './IPCError.js';
6
+ import { IPCCancelledError, IPCConnectionError, IPCParseError, IPCTimeoutError, IPCEarlyCloseError } from './IPCError.js';
7
7
  export declare function formatConnectionError(requestName: string, socketPath: string, error: Error): IPCConnectionError;
8
8
  export declare function formatParseError(requestName: string, error: unknown): IPCParseError;
9
9
  export declare function formatTimeoutError(requestName: string, timeoutMs: number): IPCTimeoutError;
10
10
  export declare function formatEarlyCloseError(requestName: string): IPCEarlyCloseError;
11
+ export declare function formatCancelledError(requestName: string): IPCCancelledError;
11
12
  //# sourceMappingURL=errors.d.ts.map
@@ -4,7 +4,7 @@
4
4
  * Formats transport-layer errors with context using structured error classes.
5
5
  */
6
6
  import { getErrorMessage } from '../../utils/errors.js';
7
- import { IPCConnectionError, IPCParseError, IPCTimeoutError, IPCEarlyCloseError, } from './IPCError.js';
7
+ import { IPCCancelledError, IPCConnectionError, IPCParseError, IPCTimeoutError, IPCEarlyCloseError, } from './IPCError.js';
8
8
  export function formatConnectionError(requestName, socketPath, error) {
9
9
  const code = error.code;
10
10
  const message = [
@@ -25,4 +25,7 @@ export function formatTimeoutError(requestName, timeoutMs) {
25
25
  export function formatEarlyCloseError(requestName) {
26
26
  return new IPCEarlyCloseError(requestName);
27
27
  }
28
+ export function formatCancelledError(requestName) {
29
+ return new IPCCancelledError(requestName);
30
+ }
28
31
  //# sourceMappingURL=errors.js.map
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Handles Unix domain socket communication with JSONL protocol.
5
5
  */
6
- export { IPCError, IPCConnectionError, IPCTimeoutError, IPCParseError, IPCEarlyCloseError, } from './IPCError.js';
6
+ export { IPCError, IPCCancelledError, IPCConnectionError, IPCTimeoutError, IPCParseError, IPCEarlyCloseError, } from './IPCError.js';
7
7
  type WithTypeAndSession = {
8
8
  type: string;
9
9
  sessionId: string;
@@ -17,6 +17,8 @@ type WithTypeAndSession = {
17
17
  * @param expectedType - Response type to validate, if any
18
18
  * @param timeoutMs - How long to wait for the response (default: IPC timeout)
19
19
  * @param socketPath - Daemon socket (default: the selected session's)
20
+ * @param signal - Closes the connection and rejects when aborted (the daemon
21
+ * sees the client disconnect, e.g. an interrupted start is cancelled)
20
22
  * @returns The daemon's response
21
23
  * @throws CommandError (103) before connecting when the socket's session
22
24
  * directory cannot be trusted (see {@link secureSessionDir}): a socket
@@ -25,5 +27,5 @@ type WithTypeAndSession = {
25
27
  * needs write access to a directory of the chain, which the check has
26
28
  * just found only the user has
27
29
  */
28
- export declare function sendRequest<TRequest extends WithTypeAndSession, TResponse extends WithTypeAndSession>(request: TRequest, requestName: string, expectedType?: string, timeoutMs?: number, socketPath?: string): Promise<TResponse>;
30
+ export declare function sendRequest<TRequest extends WithTypeAndSession, TResponse extends WithTypeAndSession>(request: TRequest, requestName: string, expectedType?: string, timeoutMs?: number, socketPath?: string, signal?: AbortSignal): Promise<TResponse>;
29
31
  //# sourceMappingURL=index.d.ts.map
@@ -10,11 +10,11 @@ import { untrustedSessionDirError } from '../../errors/messages.js';
10
10
  import { getDaemonSocketPath, secureSessionDir } from '../../session/paths.js';
11
11
  import { createLogger } from '../../ui/logging/index.js';
12
12
  import { EXIT_CODES } from '../../utils/exitCodes.js';
13
- import { formatConnectionError, formatEarlyCloseError, formatParseError, formatTimeoutError, } from './errors.js';
13
+ import { formatCancelledError, formatConnectionError, formatEarlyCloseError, formatParseError, formatTimeoutError, } from './errors.js';
14
14
  import { JSONLBuffer, parseJSONLFrame, toJSONLFrame } from './jsonl.js';
15
15
  import { createSocket } from './socket.js';
16
16
  import { validateResponseType, validateSessionId } from './validation.js';
17
- export { IPCError, IPCConnectionError, IPCTimeoutError, IPCParseError, IPCEarlyCloseError, } from './IPCError.js';
17
+ export { IPCError, IPCCancelledError, IPCConnectionError, IPCTimeoutError, IPCParseError, IPCEarlyCloseError, } from './IPCError.js';
18
18
  const log = createLogger('client');
19
19
  /**
20
20
  * Send IPC request and wait for response.
@@ -25,6 +25,8 @@ const log = createLogger('client');
25
25
  * @param expectedType - Response type to validate, if any
26
26
  * @param timeoutMs - How long to wait for the response (default: IPC timeout)
27
27
  * @param socketPath - Daemon socket (default: the selected session's)
28
+ * @param signal - Closes the connection and rejects when aborted (the daemon
29
+ * sees the client disconnect, e.g. an interrupted start is cancelled)
28
30
  * @returns The daemon's response
29
31
  * @throws CommandError (103) before connecting when the socket's session
30
32
  * directory cannot be trusted (see {@link secureSessionDir}): a socket
@@ -33,19 +35,25 @@ const log = createLogger('client');
33
35
  * needs write access to a directory of the chain, which the check has
34
36
  * just found only the user has
35
37
  */
36
- export async function sendRequest(request, requestName, expectedType, timeoutMs = getIPCRequestTimeout(), socketPath = getDaemonSocketPath()) {
38
+ export async function sendRequest(request, requestName, expectedType, timeoutMs = getIPCRequestTimeout(), socketPath = getDaemonSocketPath(), signal) {
37
39
  const untrusted = secureSessionDir(path.dirname(socketPath));
38
40
  if (untrusted) {
39
41
  const err = untrustedSessionDirError(untrusted);
40
42
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SESSION_FILE_ERROR);
41
43
  }
42
44
  return new Promise((resolve, reject) => {
45
+ if (signal?.aborted) {
46
+ reject(formatCancelledError(requestName));
47
+ return;
48
+ }
43
49
  const buffer = new JSONLBuffer();
44
50
  let resolved = false;
51
+ let onAbort = () => { };
45
52
  const resolveOnce = (cleanup, error, response) => {
46
53
  if (resolved)
47
54
  return;
48
55
  resolved = true;
56
+ signal?.removeEventListener('abort', onAbort);
49
57
  cleanup();
50
58
  if (error) {
51
59
  reject(error);
@@ -91,6 +99,8 @@ export async function sendRequest(request, requestName, expectedType, timeoutMs
91
99
  resolveOnce(cleanup, formatTimeoutError(requestName, timeoutMs));
92
100
  },
93
101
  });
102
+ onAbort = () => resolveOnce(cleanup, formatCancelledError(requestName));
103
+ signal?.addEventListener('abort', onAbort, { once: true });
94
104
  });
95
105
  }
96
106
  //# sourceMappingURL=index.js.map
@@ -5,9 +5,13 @@
5
5
  * working on the result. Costs one page script sent before the action (not
6
6
  * waited for: CDP runs it before the action's own scripts) and one read
7
7
  * after it, plus a second look 300 ms later when nothing seemed to happen.
8
+ * While watching, a timer in bdg's world notes the stalls during which the
9
+ * page's tasks could not run ({@link STALL_WATCH_START_SCRIPT}); they do not
10
+ * count as quiet time when deciding whether the DOM kept changing.
8
11
  * Worst case, when the page does not answer (a navigation is pending, or a
9
12
  * long script runs), the snapshot is given up after {@link START_TIMEOUT_MS}
10
- * and each read after {@link READ_TIMEOUT_MS}.
13
+ * and each read after {@link READ_TIMEOUT_MS}, plus as long again, once per
14
+ * action, to ask whether the page ran a long task ({@link pageAnswer}).
11
15
  */
12
16
  import type { CDPConnection } from '../../connection/cdp.js';
13
17
  import type { ActionEffects, NewMessage, PageNavigation, PendingChanges, ShownElement, TriggeredRequest } from '../../ipc/protocol/domTypes.js';
@@ -20,10 +24,20 @@ export interface SeenMessage {
20
24
  element: string;
21
25
  }
22
26
  export type { NavigationEvents };
27
+ /**
28
+ * A stall: a stretch during which the page's tasks could not run (a long
29
+ * task, or a renderer running the page's tasks late), as how long ago it
30
+ * began and ended at a read (ms, `[began, ended]`)
31
+ */
32
+ export type StallAges = [number, number];
23
33
  /** Signs that the page was still working at a read (same document only) */
24
34
  export interface SettleSignals {
35
+ /** Page time of the read (ms, `performance.now()`) */
36
+ at?: number;
25
37
  /** How long ago each recent burst of structural DOM changes was (ms, newest last) */
26
38
  burstAges: number[];
39
+ /** Stalls up to the read, oldest first (when its bursts made them matter) */
40
+ stalls?: StallAges[];
27
41
  /** A loading indicator shown since the action began, described */
28
42
  loading: string | null;
29
43
  }
@@ -97,21 +111,43 @@ export declare function newMessages(before: SeenMessage[], after: SeenMessage[],
97
111
  * @returns Elements to report
98
112
  */
99
113
  export declare function shownElements(shown: ShownElement[], messages: NewMessage[]): ShownElement[];
114
+ /**
115
+ * How long the page was quiet between two moments of a read: the time
116
+ * between them less the stalls in it, during which the page's tasks could
117
+ * not run, so it could not change the DOM either.
118
+ *
119
+ * @param fromAge - Earlier moment, as an age at the read (ms)
120
+ * @param toAge - Later moment, as an age at the read (ms)
121
+ * @param stalls - Stalls of the read (they do not overlap)
122
+ * @returns Quiet time (ms)
123
+ */
124
+ export declare function quietMs(fromAge: number, toAge: number, stalls?: StallAges[]): number;
100
125
  /**
101
126
  * Whether a read's DOM looks busy, worth a second look: at least
102
127
  * {@link BUSY_BURSTS} bursts of structural changes within
103
- * {@link BUSY_WINDOW_MS}, the last within {@link BUSY_RECENT_MS}. Text-only
104
- * changes (clocks) and style changes (animations) are not bursts.
128
+ * {@link BUSY_WINDOW_MS}, and quiet for at most {@link BUSY_RECENT_MS} since
129
+ * the last ({@link quietMs}: stalls do not count). A single burst older than
130
+ * that counts when the page stalled for most of the time since, quiet for
131
+ * at most {@link LONE_BURST_QUIET_MS}: on a renderer running the page's
132
+ * timers late, a page's second step may not have come by the first read.
133
+ * Text-only changes (clocks) and style changes (animations) are not bursts.
105
134
  *
106
135
  * @param settle - Signals of the read
107
136
  * @returns True when the DOM may still be changing
108
137
  */
109
138
  export declare function domLooksBusy(settle: SettleSignals | undefined): boolean;
110
139
  /**
111
- * Whether the DOM kept changing during the second look: at least
112
- * {@link BUSY_BURSTS} new bursts within the time since the first read (a
113
- * render that ends in two commits, or a poller updating once a second, does
114
- * not count).
140
+ * Whether the DOM kept changing during the second look: at least one new
141
+ * burst since the first read, and no quiet gap longer than
142
+ * {@link BUSY_RECENT_MS} from the last burst the first read saw, through the
143
+ * new ones, to the second read. Stalls in a gap, during which the page's
144
+ * tasks could not run, are not quiet ({@link quietMs}): a page whose steps
145
+ * come 250 ms apart around a 200 ms long task, or whose timers a starved
146
+ * renderer runs 150 ms late, keeps changing. A page changing every 140 ms
147
+ * keeps changing; changes more than 150 ms apart while the page could run,
148
+ * a render that ended over 150 ms before the second read and a poller
149
+ * updating every 300 ms do not. A short render whose last commit came within
150
+ * 150 ms of the second read counts as changing.
115
151
  *
116
152
  * @param settle - Signals of the second read
117
153
  * @param sinceMs - Time since the first read
@@ -182,8 +218,11 @@ export interface ActionEffectsWatch {
182
218
  }
183
219
  /**
184
220
  * Start watching an action's effects: listen for main-frame navigations,
185
- * document statuses, requests and new windows, and send the page snapshot
186
- * without waiting for it.
221
+ * document statuses, requests and new windows, send the page snapshot
222
+ * without waiting for it, and start the stall watch in bdg's world. That
223
+ * creates bdg's world for the reads now, while the page is idle (created at
224
+ * the first read, it would wait for a page busy after the action and could
225
+ * leave the read no time to answer).
187
226
  *
188
227
  * @param cdp - CDP connection
189
228
  * @returns Watch to collect from after the action