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.
Files changed (251) hide show
  1. package/README.md +4 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +27 -5
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +152 -113
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -23,6 +23,7 @@ import { readFile, rm, writeFile } from 'fs/promises';
23
23
  import { join } from 'path';
24
24
  import { getSessionDir } from './paths.js';
25
25
  import { createLogger } from '../ui/logging/index.js';
26
+ import { sessionCommand } from '../ui/messages/sessionCommand.js';
26
27
  import { getErrorMessage } from '../utils/errors.js';
27
28
  const log = createLogger('session');
28
29
  /**
@@ -34,6 +35,27 @@ const CACHE_VERSION = 2;
34
35
  * Selector recorded for `dom form` results (fields carry their own selectors).
35
36
  */
36
37
  export const FORM_DISCOVERY_CACHE_SELECTOR = 'form:auto-discovered';
38
+ /** Prefix of the selector recorded for `dom a11y query` results (followed by the pattern) */
39
+ export const A11Y_CACHE_SELECTOR_PREFIX = 'a11y ';
40
+ /**
41
+ * The list an index refers to, from the selector recorded with the cache.
42
+ *
43
+ * @param index - The index the user gave
44
+ * @param cacheSelector - Selector recorded with the cached results
45
+ * @returns The command whose results are cached, and its query
46
+ */
47
+ export function indexSourceOf(index, cacheSelector) {
48
+ if (cacheSelector === FORM_DISCOVERY_CACHE_SELECTOR)
49
+ return { index, command: 'dom form' };
50
+ if (cacheSelector.startsWith(A11Y_CACHE_SELECTOR_PREFIX)) {
51
+ return {
52
+ index,
53
+ command: 'dom a11y query',
54
+ query: cacheSelector.slice(A11Y_CACHE_SELECTOR_PREFIX.length),
55
+ };
56
+ }
57
+ return { index, command: 'dom query', query: cacheSelector };
58
+ }
37
59
  /**
38
60
  * Singleton manager for the on-disk query cache.
39
61
  */
@@ -89,7 +111,7 @@ export class QueryCacheManager {
89
111
  valid: false,
90
112
  cache: null,
91
113
  error: 'No cached query results found',
92
- suggestion: 'Run "bdg dom query <selector>" first to generate indexed results',
114
+ suggestion: `Run "${sessionCommand('bdg dom query <selector>')}" first to generate indexed results`,
93
115
  };
94
116
  }
95
117
  const { version, ...cache } = raw;
@@ -99,8 +121,8 @@ export class QueryCacheManager {
99
121
  cache: null,
100
122
  error: 'Cached query results are from an older bdg version',
101
123
  suggestion: cache.selector === FORM_DISCOVERY_CACHE_SELECTOR
102
- ? 'Re-run "bdg dom form" to refresh cached results'
103
- : `Re-run "bdg dom query ${cache.selector}" to refresh cached results`,
124
+ ? `Re-run "${sessionCommand('bdg dom form')}" to refresh cached results`
125
+ : `Re-run "${sessionCommand(`bdg dom query ${cache.selector}`)}" to refresh cached results`,
104
126
  };
105
127
  }
106
128
  return { valid: true, cache };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Which other bdg session uses a Chrome, so `--chrome-ws-url` never takes over
3
+ * a tab another session drives, nor a Chrome another session launched (and
4
+ * closes when it stops).
5
+ *
6
+ * Sessions are found in this base directory and, through the machine-wide
7
+ * port registry, in other base directories whose sessions claimed a port.
8
+ */
9
+ /**
10
+ * A running session and the Chrome tab it drives.
11
+ */
12
+ export interface ChromeOwner {
13
+ /** Session name, or null for a default session */
14
+ name: string | null;
15
+ /** Session directory */
16
+ dir: string;
17
+ /** Base directory of the session (its `BDG_SESSION_DIR`) */
18
+ baseDir: string;
19
+ /** Target (tab) the session drives */
20
+ targetId: string;
21
+ /** Whether the session launched that Chrome (it closes Chrome when it stops) */
22
+ launched: boolean;
23
+ }
24
+ /**
25
+ * Another running session that a new session attaching to a Chrome would
26
+ * collide with: one that launched that Chrome, or one driving the tab the
27
+ * new session would use.
28
+ *
29
+ * @param chromeTargetIds - Ids of every target of the Chrome being attached to
30
+ * @param targetId - Tab the new session would drive
31
+ * @returns The conflicting session, or null
32
+ */
33
+ export declare function findConflictingOwner(chromeTargetIds: readonly string[], targetId: string): Promise<ChromeOwner | null>;
34
+ //# sourceMappingURL=chromeOwners.d.ts.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Which other bdg session uses a Chrome, so `--chrome-ws-url` never takes over
3
+ * a tab another session drives, nor a Chrome another session launched (and
4
+ * closes when it stops).
5
+ *
6
+ * Sessions are found in this base directory and, through the machine-wide
7
+ * port registry, in other base directories whose sessions claimed a port.
8
+ */
9
+ import * as fs from 'fs';
10
+ import { probeDaemonSocket } from './daemonSocket.js';
11
+ import { sessionFilePathIn, sessionOfDir } from './paths.js';
12
+ import { otherSessionDirs } from './portClaims.js';
13
+ import { createLogger, logDebugError } from '../ui/logging/index.js';
14
+ const log = createLogger('session');
15
+ /**
16
+ * The tab a running session drives, from its metadata.
17
+ *
18
+ * @param dir - Session directory
19
+ * @returns Owner info, or null without a running session or metadata
20
+ */
21
+ async function readOwner(dir) {
22
+ if ((await probeDaemonSocket(sessionFilePathIn(dir, 'DAEMON_SOCKET'))) !== 'alive')
23
+ return null;
24
+ try {
25
+ const meta = JSON.parse(fs.readFileSync(sessionFilePathIn(dir, 'METADATA'), 'utf8'));
26
+ if (typeof meta.targetId !== 'string')
27
+ return null;
28
+ const launched = typeof meta.chromePid === 'number' && meta.chromePid > 0;
29
+ return { ...sessionOfDir(dir), dir, targetId: meta.targetId, launched };
30
+ }
31
+ catch (error) {
32
+ logDebugError(log, `read the metadata in ${dir}`, error);
33
+ return null;
34
+ }
35
+ }
36
+ /**
37
+ * Another running session that a new session attaching to a Chrome would
38
+ * collide with: one that launched that Chrome, or one driving the tab the
39
+ * new session would use.
40
+ *
41
+ * @param chromeTargetIds - Ids of every target of the Chrome being attached to
42
+ * @param targetId - Tab the new session would drive
43
+ * @returns The conflicting session, or null
44
+ */
45
+ export async function findConflictingOwner(chromeTargetIds, targetId) {
46
+ const owners = await Promise.all(otherSessionDirs().map(readOwner));
47
+ return (owners.find((owner) => owner !== null &&
48
+ chromeTargetIds.includes(owner.targetId) &&
49
+ (owner.launched || owner.targetId === targetId)) ?? null);
50
+ }
51
+ //# sourceMappingURL=chromeOwners.js.map
@@ -26,6 +26,15 @@ export declare function removeStaleDaemonFiles(): Promise<boolean>;
26
26
  * @returns Chrome PID, or null
27
27
  */
28
28
  export declare function findOrphanedChrome(): number | null;
29
+ /**
30
+ * Whether a process is the Chrome bdg launched for a session directory: its
31
+ * command line carries the directory's marker flag.
32
+ *
33
+ * @param pid - Process ID
34
+ * @param sessionDir - Session directory
35
+ * @returns True for that session's Chrome
36
+ */
37
+ export declare function isSessionChrome(pid: number, sessionDir: string): boolean;
29
38
  /**
30
39
  * Kill the Chrome recorded in chrome.pid, if it is still the Chrome bdg launched
31
40
  * for this session directory (verified by its marker flag).
@@ -43,7 +52,8 @@ export declare function reapOrphanedChrome(): Promise<boolean>;
43
52
  /**
44
53
  * Read the PID of a live bdg daemon from daemon.pid.
45
54
  *
55
+ * @param dir - Session directory (defaults to the selected session's)
46
56
  * @returns Daemon PID if the process is alive and is a bdg daemon, else null
47
57
  */
48
- export declare function readLiveDaemonPid(): number | null;
58
+ export declare function readLiveDaemonPid(dir?: string): number | null;
49
59
  //# sourceMappingURL=staleSession.d.ts.map
@@ -10,8 +10,8 @@ import { chromeSessionMarkerFlag } from '../../connection/launcher/flagsBuilder.
10
10
  import { QueryCacheManager } from '../QueryCacheManager.js';
11
11
  import { clearChromePid, readChromePid } from '../chrome.js';
12
12
  import { probeDaemonSocket } from '../daemonSocket.js';
13
- import { getSessionDir, getSessionFilePath } from '../paths.js';
14
- import { readDaemonPid } from '../pid.js';
13
+ import { getSessionDir, getSessionFilePath, sessionFilePathIn } from '../paths.js';
14
+ import { readPidFromFile } from '../pid.js';
15
15
  import { createLogger, logDebugError } from '../../ui/logging/index.js';
16
16
  import { delay } from '../../utils/async.js';
17
17
  import { safeRemoveFile } from '../../utils/file.js';
@@ -66,13 +66,23 @@ export function findOrphanedChrome() {
66
66
  const chromePid = readChromePid();
67
67
  if (!chromePid)
68
68
  return null;
69
- if (hasArgument(getProcessCommand(chromePid), chromeSessionMarkerFlag(getSessionDir()))) {
69
+ if (isSessionChrome(chromePid, getSessionDir()))
70
70
  return chromePid;
71
- }
72
71
  log.debug(`PID ${chromePid} is no longer a bdg Chrome; dropping chrome.pid`);
73
72
  clearChromePid();
74
73
  return null;
75
74
  }
75
+ /**
76
+ * Whether a process is the Chrome bdg launched for a session directory: its
77
+ * command line carries the directory's marker flag.
78
+ *
79
+ * @param pid - Process ID
80
+ * @param sessionDir - Session directory
81
+ * @returns True for that session's Chrome
82
+ */
83
+ export function isSessionChrome(pid, sessionDir) {
84
+ return hasArgument(getProcessCommand(pid), chromeSessionMarkerFlag(sessionDir));
85
+ }
76
86
  /**
77
87
  * Kill the Chrome recorded in chrome.pid, if it is still the Chrome bdg launched
78
88
  * for this session directory (verified by its marker flag).
@@ -113,10 +123,11 @@ export async function reapOrphanedChrome() {
113
123
  /**
114
124
  * Read the PID of a live bdg daemon from daemon.pid.
115
125
  *
126
+ * @param dir - Session directory (defaults to the selected session's)
116
127
  * @returns Daemon PID if the process is alive and is a bdg daemon, else null
117
128
  */
118
- export function readLiveDaemonPid() {
119
- const pid = readDaemonPid();
129
+ export function readLiveDaemonPid(dir = getSessionDir()) {
130
+ const pid = readPidFromFile(sessionFilePathIn(dir, 'DAEMON_PID'));
120
131
  if (!pid || !isProcessAlive(pid))
121
132
  return null;
122
133
  return hasArgument(getProcessCommand(pid), DAEMON_SCRIPT_PATH) ? pid : null;
@@ -5,7 +5,7 @@ import * as fs from 'fs';
5
5
  import { killOrphanedChrome, readLiveDaemonPid, removeStaleDaemonFiles, } from './staleSession.js';
6
6
  import { isDaemonAlive } from '../daemonSocket.js';
7
7
  import { clearLastSessionEnd } from '../lastSession.js';
8
- import { getSessionFilePath } from '../paths.js';
8
+ import { SESSION_STATE_FILES, getSessionFilePath } from '../paths.js';
9
9
  import { createLogger } from '../../ui/logging/index.js';
10
10
  import { getErrorMessage } from '../../utils/errors.js';
11
11
  const log = createLogger('cleanup');
@@ -39,15 +39,13 @@ export async function performSessionCleanup(options) {
39
39
  warnings,
40
40
  };
41
41
  }
42
- /** Files a session leaves behind (stale ones are what cleanup removes) */
43
- const SESSION_FILE_TYPES = ['DAEMON_PID', 'DAEMON_SOCKET', 'CHROME_PID', 'METADATA'];
44
42
  /**
45
43
  * How many session files exist.
46
44
  *
47
45
  * @returns Number of existing session files
48
46
  */
49
47
  function countSessionFiles() {
50
- return SESSION_FILE_TYPES.filter((type) => fs.existsSync(getSessionFilePath(type))).length;
48
+ return SESSION_STATE_FILES.filter((type) => fs.existsSync(getSessionFilePath(type))).length;
51
49
  }
52
50
  /**
53
51
  * SIGKILL a live bdg daemon and wait briefly for its socket to go away.
@@ -4,7 +4,7 @@
4
4
  * Handles reading/writing session metadata (Chrome PID, CDP port, target info, etc).
5
5
  * WHY: Metadata persistence enables `bdg status` and other commands to inspect active sessions.
6
6
  */
7
- import type { TelemetryType } from '../types.js';
7
+ import type { ColorScheme, TelemetryType, ViewportSize } from '../types.js';
8
8
  /**
9
9
  * Session metadata stored in ~/.bdg/session.meta.json
10
10
  */
@@ -18,6 +18,10 @@ export interface SessionMetadata {
18
18
  activeTelemetry?: TelemetryType[] | undefined;
19
19
  /** When `--timeout` stops the session (epoch ms) */
20
20
  autoStopAt?: number | undefined;
21
+ /** Viewport the page is emulated at (`--viewport`) */
22
+ viewport?: ViewportSize | undefined;
23
+ /** `prefers-color-scheme` the page is emulated with (`--color-scheme`) */
24
+ colorScheme?: ColorScheme | undefined;
21
25
  }
22
26
  /**
23
27
  * Options for reading session metadata.
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import * as fs from 'fs';
8
8
  import { createLogger } from '../ui/logging/index.js';
9
+ import { sessionCommand } from '../ui/messages/sessionCommand.js';
9
10
  import { AtomicFileWriter } from '../utils/atomicFile.js';
10
11
  import { getErrorMessage } from '../utils/errors.js';
11
12
  import { getSessionFilePath, ensureSessionDir } from './paths.js';
@@ -20,7 +21,7 @@ const log = createLogger('session');
20
21
  function handleCorruptedMetadata(metaPath, error, options) {
21
22
  if (options?.warnOnCorruption) {
22
23
  log.info(`Session metadata corrupted (cannot read details): ${getErrorMessage(error)}`);
23
- log.info('Troubleshooting: Run "bdg cleanup" to remove corrupted files');
24
+ log.info(`Troubleshooting: Run "${sessionCommand('bdg cleanup')}" to remove corrupted files`);
24
25
  }
25
26
  if (options?.selfHealOnCorruption) {
26
27
  try {
@@ -2,6 +2,7 @@
2
2
  * Session path generation and management.
3
3
  *
4
4
  * Centralized path generation for all session-related files in ~/.bdg/
5
+ * (or `~/.bdg/sessions/<name>/` for a named session).
5
6
  * WHY: Single source of truth for file locations prevents path inconsistencies.
6
7
  */
7
8
  /**
@@ -17,18 +18,91 @@ declare const SESSION_FILES: {
17
18
  readonly PORT: "port.txt";
18
19
  readonly LAST_SESSION: "last-session.json";
19
20
  };
21
+ /** Environment variable holding the selected session name (set by `--session`) */
22
+ export declare const SESSION_NAME_ENV = "BDG_SESSION";
23
+ /** Longest Unix socket path (sun_path is 104 bytes on macOS, 108 on Linux, NUL included) */
24
+ export declare const MAX_SOCKET_PATH_BYTES: number;
25
+ /**
26
+ * Longest public daemon socket path: the daemon first listens on
27
+ * `<path>.<pid>` and then links it into place, so the pid suffix (up to 7
28
+ * digits) must fit too.
29
+ */
30
+ export declare const MAX_DAEMON_SOCKET_PATH_BYTES: number;
31
+ /**
32
+ * A session directory: the default session (`name` null) or a named one.
33
+ */
34
+ export interface SessionDirEntry {
35
+ /** Session name, or null for the default session */
36
+ name: string | null;
37
+ /** Absolute session directory */
38
+ dir: string;
39
+ }
20
40
  /**
21
41
  * Session file type for type-safe path generation
22
42
  */
23
43
  export type SessionFileType = keyof typeof SESSION_FILES;
24
44
  /**
25
- * Get the session directory path (~/.bdg)
45
+ * Files a running session keeps in its directory; left behind (stale) when
46
+ * its daemon dies without tearing down.
47
+ */
48
+ export declare const SESSION_STATE_FILES: readonly ["DAEMON_PID", "DAEMON_SOCKET", "CHROME_PID", "METADATA"];
49
+ /**
50
+ * Get the base session directory: `$BDG_SESSION_DIR`, else `~/.bdg`.
51
+ *
52
+ * It is the default session's directory and holds named sessions under
53
+ * `sessions/<name>/`. Uses os.homedir() dynamically to support test
54
+ * environment variable changes.
55
+ *
56
+ * @returns Full path to the base session directory
57
+ */
58
+ export declare function getSessionBaseDir(): string;
59
+ /**
60
+ * The selected session name (`--session` / `BDG_SESSION`), lower-cased:
61
+ * session names are case-insensitive.
62
+ *
63
+ * @returns Session name, or null for the default session
64
+ */
65
+ export declare function getSessionName(): string | null;
66
+ /**
67
+ * Directory of a named session.
26
68
  *
27
- * Uses os.homedir() dynamically to support test environment variable changes.
69
+ * @param name - Session name
70
+ * @returns `<base>/sessions/<name>`
71
+ */
72
+ export declare function getNamedSessionDir(name: string): string;
73
+ /**
74
+ * Get the directory of the selected session: the base directory for the
75
+ * default session, `<base>/sessions/<name>` for a named one.
28
76
  *
29
- * @returns Full path to session directory
77
+ * @returns Full path to the session directory
30
78
  */
31
79
  export declare function getSessionDir(): string;
80
+ /**
81
+ * Every session directory on disk: the default one, then named sessions
82
+ * sorted by name. Directories need not hold a running session.
83
+ *
84
+ * @returns Session directory entries
85
+ */
86
+ export declare function listSessionDirs(): SessionDirEntry[];
87
+ /**
88
+ * Which session a directory holds, from its location: `<base>/sessions/<name>`
89
+ * is a named session, any other directory a default session (its own base).
90
+ *
91
+ * @param dir - Absolute session directory
92
+ * @returns Session name (null for a default session) and base directory
93
+ */
94
+ export declare function sessionOfDir(dir: string): {
95
+ name: string | null;
96
+ baseDir: string;
97
+ };
98
+ /**
99
+ * Path of a session file in a given session directory.
100
+ *
101
+ * @param dir - Session directory
102
+ * @param fileType - The type of session file
103
+ * @returns Full path to the file
104
+ */
105
+ export declare function sessionFilePathIn(dir: string, fileType: SessionFileType): string;
32
106
  /**
33
107
  * Get the path to a session file by type.
34
108
  *
@@ -2,11 +2,14 @@
2
2
  * Session path generation and management.
3
3
  *
4
4
  * Centralized path generation for all session-related files in ~/.bdg/
5
+ * (or `~/.bdg/sessions/<name>/` for a named session).
5
6
  * WHY: Single source of truth for file locations prevents path inconsistencies.
6
7
  */
7
8
  import * as fs from 'fs';
8
9
  import * as os from 'os';
9
10
  import * as path from 'path';
11
+ import { createLogger, logDebugError } from '../ui/logging/index.js';
12
+ const log = createLogger('session');
10
13
  /**
11
14
  * Session file paths relative to ~/.bdg/
12
15
  * Centralized definition for all session-related files.
@@ -21,20 +24,123 @@ const SESSION_FILES = {
21
24
  LAST_SESSION: 'last-session.json',
22
25
  };
23
26
  const SESSION_DIR_OVERRIDE_ENV = 'BDG_SESSION_DIR';
27
+ /** Environment variable holding the selected session name (set by `--session`) */
28
+ export const SESSION_NAME_ENV = 'BDG_SESSION';
29
+ /** Subdirectory of the base session directory that holds named sessions */
30
+ const NAMED_SESSIONS_DIR = 'sessions';
31
+ /** Longest Unix socket path (sun_path is 104 bytes on macOS, 108 on Linux, NUL included) */
32
+ export const MAX_SOCKET_PATH_BYTES = process.platform === 'darwin' ? 103 : 107;
24
33
  /**
25
- * Get the session directory path (~/.bdg)
34
+ * Longest public daemon socket path: the daemon first listens on
35
+ * `<path>.<pid>` and then links it into place, so the pid suffix (up to 7
36
+ * digits) must fit too.
37
+ */
38
+ export const MAX_DAEMON_SOCKET_PATH_BYTES = MAX_SOCKET_PATH_BYTES - '.9999999'.length;
39
+ /**
40
+ * Files a running session keeps in its directory; left behind (stale) when
41
+ * its daemon dies without tearing down.
42
+ */
43
+ export const SESSION_STATE_FILES = [
44
+ 'DAEMON_PID',
45
+ 'DAEMON_SOCKET',
46
+ 'CHROME_PID',
47
+ 'METADATA',
48
+ ];
49
+ /**
50
+ * Get the base session directory: `$BDG_SESSION_DIR`, else `~/.bdg`.
26
51
  *
27
- * Uses os.homedir() dynamically to support test environment variable changes.
52
+ * It is the default session's directory and holds named sessions under
53
+ * `sessions/<name>/`. Uses os.homedir() dynamically to support test
54
+ * environment variable changes.
28
55
  *
29
- * @returns Full path to session directory
56
+ * @returns Full path to the base session directory
30
57
  */
31
- export function getSessionDir() {
58
+ export function getSessionBaseDir() {
32
59
  const override = process.env[SESSION_DIR_OVERRIDE_ENV];
33
60
  if (override && override.trim().length > 0) {
34
61
  return path.isAbsolute(override) ? override : path.resolve(override);
35
62
  }
36
63
  return path.join(os.homedir(), '.bdg');
37
64
  }
65
+ /**
66
+ * The selected session name (`--session` / `BDG_SESSION`), lower-cased:
67
+ * session names are case-insensitive.
68
+ *
69
+ * @returns Session name, or null for the default session
70
+ */
71
+ export function getSessionName() {
72
+ const name = process.env[SESSION_NAME_ENV]?.trim();
73
+ if (!name)
74
+ return null;
75
+ return name.toLowerCase();
76
+ }
77
+ /**
78
+ * Directory of a named session.
79
+ *
80
+ * @param name - Session name
81
+ * @returns `<base>/sessions/<name>`
82
+ */
83
+ export function getNamedSessionDir(name) {
84
+ return path.join(getSessionBaseDir(), NAMED_SESSIONS_DIR, name);
85
+ }
86
+ /**
87
+ * Get the directory of the selected session: the base directory for the
88
+ * default session, `<base>/sessions/<name>` for a named one.
89
+ *
90
+ * @returns Full path to the session directory
91
+ */
92
+ export function getSessionDir() {
93
+ const name = getSessionName();
94
+ return name === null ? getSessionBaseDir() : getNamedSessionDir(name);
95
+ }
96
+ /**
97
+ * Every session directory on disk: the default one, then named sessions
98
+ * sorted by name. Directories need not hold a running session.
99
+ *
100
+ * @returns Session directory entries
101
+ */
102
+ export function listSessionDirs() {
103
+ const namedRoot = path.join(getSessionBaseDir(), NAMED_SESSIONS_DIR);
104
+ let names = [];
105
+ try {
106
+ names = fs
107
+ .readdirSync(namedRoot, { withFileTypes: true })
108
+ .filter((entry) => entry.isDirectory())
109
+ .map((entry) => entry.name)
110
+ .sort();
111
+ }
112
+ catch (error) {
113
+ logDebugError(log, `list ${namedRoot}`, error);
114
+ }
115
+ return [
116
+ { name: null, dir: getSessionBaseDir() },
117
+ ...names.map((name) => ({ name, dir: path.join(namedRoot, name) })),
118
+ ];
119
+ }
120
+ /**
121
+ * Which session a directory holds, from its location: `<base>/sessions/<name>`
122
+ * is a named session, any other directory a default session (its own base).
123
+ *
124
+ * @param dir - Absolute session directory
125
+ * @returns Session name (null for a default session) and base directory
126
+ */
127
+ export function sessionOfDir(dir) {
128
+ const parent = path.dirname(dir);
129
+ if (path.basename(parent) === NAMED_SESSIONS_DIR) {
130
+ return { name: path.basename(dir), baseDir: path.dirname(parent) };
131
+ }
132
+ return { name: null, baseDir: dir };
133
+ }
134
+ /**
135
+ * Path of a session file in a given session directory.
136
+ *
137
+ * @param dir - Session directory
138
+ * @param fileType - The type of session file
139
+ * @returns Full path to the file
140
+ */
141
+ export function sessionFilePathIn(dir, fileType) {
142
+ return path.join(dir, SESSION_FILES[fileType]);
143
+ }
38
144
  /**
39
145
  * Get the path to a session file by type.
40
146
  *
@@ -48,7 +154,7 @@ export function getSessionDir() {
48
154
  * ```
49
155
  */
50
156
  export function getSessionFilePath(fileType) {
51
- return path.join(getSessionDir(), SESSION_FILES[fileType]);
157
+ return sessionFilePathIn(getSessionDir(), fileType);
52
158
  }
53
159
  /**
54
160
  * Get the path to the daemon's Unix domain socket.
@@ -3,8 +3,25 @@
3
3
  *
4
4
  * Handles automatic port selection and persistence for session isolation.
5
5
  * Each session directory can have its own persistent port, enabling multiple
6
- * concurrent bdg sessions with different BDG_SESSION_DIR values.
6
+ * concurrent bdg sessions (named sessions or different BDG_SESSION_DIR values).
7
7
  */
8
+ /**
9
+ * First port tried for this session: the default session starts at
10
+ * DEFAULT_CDP_PORT, named sessions one above so they leave it to the default
11
+ * session.
12
+ *
13
+ * @returns First candidate port
14
+ */
15
+ export declare function firstCandidatePort(): number;
16
+ /**
17
+ * Find an available port starting from a given port.
18
+ *
19
+ * @param startPort - Port to start scanning from
20
+ * @param claimed - Ports to skip (claimed by other sessions)
21
+ * @returns Promise resolving to an available port
22
+ * @throws CommandError if no available port found in range
23
+ */
24
+ export declare function findAvailablePort(startPort?: number, claimed?: ReadonlySet<number>): Promise<number>;
8
25
  /**
9
26
  * Read the saved port from the session directory.
10
27
  *
@@ -21,13 +38,20 @@ export declare function writeSessionPort(port: number): void;
21
38
  * Get or allocate a port for this session.
22
39
  *
23
40
  * Logic:
24
- * 1. If a port is explicitly provided, use it (user override)
25
- * 2. Check if there's a saved port in the session directory
26
- * 3. If saved port exists and is available, reuse it (session stability)
27
- * 4. Otherwise, find a new available port and save it
41
+ * 1. If a port is explicitly provided, use it (user override; a named session
42
+ * saves and claims it under the lock so other sessions skip it)
43
+ * 2. Otherwise, under a lock shared by all sessions on the machine (any
44
+ * BDG_SESSION_DIR), reuse the saved port if it is free and no other
45
+ * running session claims it (session stability)
46
+ * 3. Otherwise, take the first free, unclaimed port from
47
+ * {@link firstCandidatePort} upwards and save it (the claim)
48
+ *
49
+ * The claim is also recorded in the machine-wide registry (under the lock),
50
+ * so sessions of other base directories skip it too.
28
51
  *
29
- * This provides session isolation: different BDG_SESSION_DIR values
30
- * will automatically use different ports.
52
+ * This provides session isolation: named sessions and different
53
+ * BDG_SESSION_DIR values automatically use different ports, even when they
54
+ * start at the same time.
31
55
  *
32
56
  * @param explicitPort - User-provided port (takes precedence)
33
57
  * @returns Promise resolving to the port to use