browser-debugger-cli 0.8.0 → 0.10.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 (304) hide show
  1. package/README.md +7 -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 +29 -6
  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 +189 -119
  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/keyAttributes.d.ts +20 -0
  27. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  28. package/dist/commands/dom/helpers/query.d.ts +44 -17
  29. package/dist/commands/dom/helpers/query.js +300 -106
  30. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  31. package/dist/commands/dom/helpers/runElementCommand.js +98 -30
  32. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  33. package/dist/commands/dom/helpers/screenshot.js +239 -51
  34. package/dist/commands/dom/index.d.ts +4 -1
  35. package/dist/commands/dom/index.js +22 -7
  36. package/dist/commands/dom/inspect.d.ts +15 -0
  37. package/dist/commands/dom/inspect.js +82 -0
  38. package/dist/commands/dom/layout.d.ts +14 -0
  39. package/dist/commands/dom/layout.js +54 -0
  40. package/dist/commands/dom/listeners.d.ts +5 -1
  41. package/dist/commands/dom/listeners.js +15 -5
  42. package/dist/commands/dom/query.js +2 -3
  43. package/dist/commands/dom/screenshot.d.ts +12 -2
  44. package/dist/commands/dom/screenshot.js +27 -3
  45. package/dist/commands/dom/semanticUtils.d.ts +16 -10
  46. package/dist/commands/dom/semanticUtils.js +53 -16
  47. package/dist/commands/dom/wait.d.ts +13 -0
  48. package/dist/commands/dom/wait.js +83 -0
  49. package/dist/commands/helpJson.js +2 -2
  50. package/dist/commands/network/list.js +17 -13
  51. package/dist/commands/optionBehaviors.js +154 -21
  52. package/dist/commands/page.d.ts +3 -2
  53. package/dist/commands/page.js +100 -5
  54. package/dist/commands/peek.js +4 -11
  55. package/dist/commands/sessions.d.ts +8 -0
  56. package/dist/commands/sessions.js +19 -0
  57. package/dist/commands/shared/CommandRunner.js +4 -4
  58. package/dist/commands/shared/commonOptions.d.ts +4 -0
  59. package/dist/commands/shared/commonOptions.js +9 -0
  60. package/dist/commands/shared/dataFetcher.js +2 -2
  61. package/dist/commands/shared/followMode.d.ts +21 -1
  62. package/dist/commands/shared/followMode.js +29 -2
  63. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  64. package/dist/commands/shared/handleValidationError.js +12 -3
  65. package/dist/commands/shared/optionTypes.d.ts +61 -5
  66. package/dist/commands/shared/startHelpers.d.ts +66 -0
  67. package/dist/commands/shared/startHelpers.js +103 -13
  68. package/dist/commands/shared/validation.d.ts +14 -2
  69. package/dist/commands/shared/validation.js +20 -3
  70. package/dist/commands/start.d.ts +63 -0
  71. package/dist/commands/start.js +115 -15
  72. package/dist/commands/status.js +29 -7
  73. package/dist/commands/stop.js +7 -6
  74. package/dist/commands/tail.js +4 -11
  75. package/dist/commands/types.d.ts +2 -0
  76. package/dist/commands.js +2 -0
  77. package/dist/connection/chromeIdentity.d.ts +65 -0
  78. package/dist/connection/chromeIdentity.js +143 -0
  79. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  80. package/dist/connection/launcher/profilePreferences.js +151 -0
  81. package/dist/connection/launcher.d.ts +21 -2
  82. package/dist/connection/launcher.js +42 -16
  83. package/dist/connection/portReservation.d.ts +14 -4
  84. package/dist/connection/portReservation.js +21 -6
  85. package/dist/connection/startupExit.d.ts +8 -0
  86. package/dist/connection/startupExit.js +15 -6
  87. package/dist/constants.d.ts +6 -2
  88. package/dist/constants.js +9 -2
  89. package/dist/daemon/SessionController.js +23 -7
  90. package/dist/daemon/errors.d.ts +1 -1
  91. package/dist/daemon/errors.js +1 -1
  92. package/dist/daemon/launcher.d.ts +10 -2
  93. package/dist/daemon/launcher.js +8 -7
  94. package/dist/daemon/server/SocketServer.js +1 -2
  95. package/dist/daemon/session/Session.d.ts +20 -0
  96. package/dist/daemon/session/Session.js +80 -9
  97. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  98. package/dist/daemon/session/chromeConnection.js +45 -8
  99. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  100. package/dist/daemon/session/commandRegistry.js +113 -67
  101. package/dist/daemon/session/interactions.d.ts +48 -9
  102. package/dist/daemon/session/interactions.js +46 -9
  103. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  104. package/dist/daemon/session/triggeredRequests.js +157 -0
  105. package/dist/daemon/session/types.d.ts +5 -1
  106. package/dist/daemon.js +10630 -3601
  107. package/dist/errors/messages.d.ts +456 -24
  108. package/dist/errors/messages.js +862 -67
  109. package/dist/index.js +6915 -3401
  110. package/dist/ipc/client.d.ts +21 -1
  111. package/dist/ipc/client.js +35 -3
  112. package/dist/ipc/protocol/commands.d.ts +145 -5
  113. package/dist/ipc/protocol/commands.js +4 -0
  114. package/dist/ipc/protocol/domTypes.d.ts +291 -7
  115. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  116. package/dist/ipc/protocol/inspectTypes.js +10 -0
  117. package/dist/ipc/session/lifecycle.d.ts +8 -1
  118. package/dist/ipc/session/queries.d.ts +5 -1
  119. package/dist/ipc/session/types.d.ts +5 -0
  120. package/dist/ipc/transport/index.d.ts +2 -1
  121. package/dist/ipc/transport/index.js +2 -2
  122. package/dist/runtime/dom/actionEffects.d.ts +185 -0
  123. package/dist/runtime/dom/actionEffects.js +402 -0
  124. package/dist/runtime/dom/actionEffectsScripts.d.ts +90 -0
  125. package/dist/runtime/dom/actionEffectsScripts.js +426 -0
  126. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  127. package/dist/runtime/dom/elementGeometry.js +553 -0
  128. package/dist/runtime/dom/elementInfo.d.ts +103 -0
  129. package/dist/runtime/dom/elementInfo.js +256 -0
  130. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  131. package/dist/runtime/dom/evalHelpers.js +136 -26
  132. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  133. package/dist/runtime/dom/eventListeners.js +184 -47
  134. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  135. package/dist/runtime/dom/formDiscovery.js +116 -16
  136. package/dist/runtime/dom/formFillHelpers/fill.d.ts +9 -0
  137. package/dist/runtime/dom/formFillHelpers/fill.js +178 -14
  138. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  139. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  140. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  141. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  142. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  143. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  144. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  145. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  146. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  147. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  148. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  149. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  150. package/dist/runtime/dom/frameLayout.js +140 -0
  151. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  152. package/dist/runtime/dom/frameOrigin.js +62 -0
  153. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  154. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  155. package/dist/runtime/dom/frameSelection.d.ts +12 -1
  156. package/dist/runtime/dom/frameSelection.js +22 -3
  157. package/dist/runtime/dom/frames.d.ts +61 -5
  158. package/dist/runtime/dom/frames.js +329 -75
  159. package/dist/runtime/dom/inspect.d.ts +28 -0
  160. package/dist/runtime/dom/inspect.js +557 -0
  161. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  162. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  163. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  164. package/dist/runtime/dom/inspectCascade.js +371 -0
  165. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  166. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  167. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  168. package/dist/runtime/dom/inspectHints.js +305 -0
  169. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  170. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  171. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  172. package/dist/runtime/dom/inspectModel.js +184 -0
  173. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  174. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  175. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  176. package/dist/runtime/dom/inspectRules.js +101 -0
  177. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  178. package/dist/runtime/dom/inspectScripts.js +263 -0
  179. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  180. package/dist/runtime/dom/inspectTree.js +134 -0
  181. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  182. package/dist/runtime/dom/inspectVariables.js +94 -0
  183. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  184. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  185. package/dist/runtime/dom/layout.d.ts +71 -0
  186. package/dist/runtime/dom/layout.js +340 -0
  187. package/dist/runtime/dom/listenerPageScripts.d.ts +72 -0
  188. package/dist/runtime/dom/listenerPageScripts.js +365 -0
  189. package/dist/runtime/dom/listenerSummary.d.ts +136 -11
  190. package/dist/runtime/dom/listenerSummary.js +361 -22
  191. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  192. package/dist/runtime/dom/pageActivity.js +123 -0
  193. package/dist/runtime/dom/reactEventHelpers.d.ts +63 -2
  194. package/dist/runtime/dom/reactEventHelpers.js +220 -41
  195. package/dist/runtime/dom/targetNode.d.ts +80 -27
  196. package/dist/runtime/dom/targetNode.js +249 -33
  197. package/dist/runtime/dom/wait.d.ts +25 -0
  198. package/dist/runtime/dom/wait.js +199 -0
  199. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  200. package/dist/runtime/dom/waitCondition.js +75 -0
  201. package/dist/runtime/page/emulation.d.ts +71 -0
  202. package/dist/runtime/page/emulation.js +117 -0
  203. package/dist/runtime/page/loadingState.d.ts +36 -0
  204. package/dist/runtime/page/loadingState.js +86 -0
  205. package/dist/runtime/page/navigation.d.ts +46 -2
  206. package/dist/runtime/page/navigation.js +69 -33
  207. package/dist/session/QueryCacheManager.d.ts +11 -1
  208. package/dist/session/QueryCacheManager.js +25 -3
  209. package/dist/session/chromeOwners.d.ts +34 -0
  210. package/dist/session/chromeOwners.js +51 -0
  211. package/dist/session/cleanup/staleSession.d.ts +11 -1
  212. package/dist/session/cleanup/staleSession.js +17 -6
  213. package/dist/session/cleanup/userCommands.js +2 -4
  214. package/dist/session/metadata.d.ts +5 -1
  215. package/dist/session/metadata.js +2 -1
  216. package/dist/session/paths.d.ts +77 -3
  217. package/dist/session/paths.js +111 -5
  218. package/dist/session/port.d.ts +31 -7
  219. package/dist/session/port.js +50 -43
  220. package/dist/session/portClaims.d.ts +66 -0
  221. package/dist/session/portClaims.js +284 -0
  222. package/dist/session/sessionList.d.ts +58 -0
  223. package/dist/session/sessionList.js +199 -0
  224. package/dist/session/sessionName.d.ts +46 -0
  225. package/dist/session/sessionName.js +97 -0
  226. package/dist/telemetry/a11y.d.ts +18 -3
  227. package/dist/telemetry/a11y.js +170 -29
  228. package/dist/telemetry/console.d.ts +1 -0
  229. package/dist/telemetry/console.js +100 -5
  230. package/dist/telemetry/network.js +3 -1
  231. package/dist/telemetry/requestKinds.d.ts +32 -0
  232. package/dist/telemetry/requestKinds.js +61 -0
  233. package/dist/telemetry/requestState.d.ts +31 -0
  234. package/dist/telemetry/requestState.js +38 -0
  235. package/dist/types.d.ts +112 -3
  236. package/dist/ui/formatters/a11y.js +3 -0
  237. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  238. package/dist/ui/formatters/console/chronological.js +17 -4
  239. package/dist/ui/formatters/console/json.js +3 -4
  240. package/dist/ui/formatters/console/shared.d.ts +12 -0
  241. package/dist/ui/formatters/console.d.ts +2 -2
  242. package/dist/ui/formatters/console.js +1 -1
  243. package/dist/ui/formatters/details.d.ts +8 -0
  244. package/dist/ui/formatters/details.js +61 -4
  245. package/dist/ui/formatters/dom.d.ts +27 -14
  246. package/dist/ui/formatters/dom.js +88 -59
  247. package/dist/ui/formatters/form.js +29 -18
  248. package/dist/ui/formatters/inspect.d.ts +39 -0
  249. package/dist/ui/formatters/inspect.js +596 -0
  250. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  251. package/dist/ui/formatters/keyAttributes.js +84 -0
  252. package/dist/ui/formatters/layout.d.ts +31 -0
  253. package/dist/ui/formatters/layout.js +53 -0
  254. package/dist/ui/formatters/listeners.d.ts +3 -2
  255. package/dist/ui/formatters/listeners.js +73 -9
  256. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  257. package/dist/ui/formatters/networkHeaders.js +36 -3
  258. package/dist/ui/formatters/networkList.d.ts +29 -1
  259. package/dist/ui/formatters/networkList.js +86 -20
  260. package/dist/ui/formatters/preview.js +2 -1
  261. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  262. package/dist/ui/formatters/requestStatus.js +2 -30
  263. package/dist/ui/formatters/sessions.d.ts +12 -0
  264. package/dist/ui/formatters/sessions.js +40 -0
  265. package/dist/ui/formatters/status.d.ts +21 -2
  266. package/dist/ui/formatters/status.js +47 -10
  267. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  268. package/dist/ui/formatters/triggeredRequests.js +65 -0
  269. package/dist/ui/formatting.d.ts +19 -0
  270. package/dist/ui/formatting.js +31 -36
  271. package/dist/ui/messages/chrome.d.ts +9 -0
  272. package/dist/ui/messages/chrome.js +17 -5
  273. package/dist/ui/messages/commands.d.ts +504 -14
  274. package/dist/ui/messages/commands.js +835 -21
  275. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  276. package/dist/ui/messages/consoleMessages.js +17 -0
  277. package/dist/ui/messages/hints.js +2 -1
  278. package/dist/ui/messages/networkMessages.d.ts +14 -0
  279. package/dist/ui/messages/networkMessages.js +18 -0
  280. package/dist/ui/messages/preview.js +5 -4
  281. package/dist/ui/messages/session.d.ts +30 -21
  282. package/dist/ui/messages/session.js +48 -26
  283. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  284. package/dist/ui/messages/sessionCommand.js +52 -0
  285. package/dist/utils/async.d.ts +17 -0
  286. package/dist/utils/async.js +36 -0
  287. package/dist/utils/color.d.ts +84 -0
  288. package/dist/utils/color.js +376 -0
  289. package/dist/utils/cssValues.d.ts +109 -0
  290. package/dist/utils/cssValues.js +236 -0
  291. package/dist/utils/http.d.ts +22 -1
  292. package/dist/utils/http.js +28 -9
  293. package/dist/utils/selectorFilters.d.ts +48 -8
  294. package/dist/utils/selectorFilters.js +296 -53
  295. package/dist/utils/shellDetection.d.ts +8 -2
  296. package/dist/utils/shellDetection.js +120 -33
  297. package/dist/utils/suggestions.d.ts +26 -0
  298. package/dist/utils/suggestions.js +73 -0
  299. package/dist/utils/taskMappings.js +10 -0
  300. package/dist/utils/url.d.ts +12 -2
  301. package/dist/utils/url.js +69 -7
  302. package/package.json +1 -1
  303. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  304. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -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
@@ -3,14 +3,15 @@
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
8
  import * as fs from 'fs';
9
9
  import * as net from 'net';
10
10
  import { isPortAnswering } from '../connection/portReservation.js';
11
11
  import { DEFAULT_CDP_PORT } from '../constants.js';
12
12
  import { CommandError } from '../errors/index.js';
13
- import { ensureSessionDir, getSessionFilePath } from './paths.js';
13
+ import { ensureSessionDir, getSessionFilePath, getSessionName } from './paths.js';
14
+ import { portsClaimedByOtherSessions, readPortFile, withPortLock } from './portClaims.js';
14
15
  import { EXIT_CODES } from '../utils/exitCodes.js';
15
16
  /**
16
17
  * Port range for automatic selection.
@@ -20,7 +21,7 @@ const PORT_RANGE_START = DEFAULT_CDP_PORT;
20
21
  const PORT_RANGE_END = 9322; // Allow 100 ports for concurrent sessions
21
22
  /**
22
23
  * Check if a port is available (not in use by any process, including one
23
- * listening on all interfaces).
24
+ * listening on all interfaces or on ::1 only).
24
25
  *
25
26
  * @param port - Port number to check
26
27
  * @returns Promise resolving to true if port is available
@@ -40,20 +41,31 @@ async function isPortAvailable(port) {
40
41
  });
41
42
  });
42
43
  }
44
+ /**
45
+ * First port tried for this session: the default session starts at
46
+ * DEFAULT_CDP_PORT, named sessions one above so they leave it to the default
47
+ * session.
48
+ *
49
+ * @returns First candidate port
50
+ */
51
+ export function firstCandidatePort() {
52
+ return getSessionName() === null ? PORT_RANGE_START : PORT_RANGE_START + 1;
53
+ }
43
54
  /**
44
55
  * Find an available port starting from a given port.
45
56
  *
46
57
  * @param startPort - Port to start scanning from
58
+ * @param claimed - Ports to skip (claimed by other sessions)
47
59
  * @returns Promise resolving to an available port
48
- * @throws Error if no available port found in range
60
+ * @throws CommandError if no available port found in range
49
61
  */
50
- async function findAvailablePort(startPort = PORT_RANGE_START) {
62
+ export async function findAvailablePort(startPort = PORT_RANGE_START, claimed = new Set()) {
51
63
  for (let port = startPort; port <= PORT_RANGE_END; port++) {
52
- if (await isPortAvailable(port)) {
64
+ if (!claimed.has(port) && (await isPortAvailable(port))) {
53
65
  return port;
54
66
  }
55
67
  }
56
- throw new CommandError(`No available port found in range ${PORT_RANGE_START}-${PORT_RANGE_END}.`, { suggestion: 'Stop some bdg sessions or Chrome instances and retry.' }, EXIT_CODES.SOFTWARE_ERROR);
68
+ throw new CommandError(`No available port found in range ${startPort}-${PORT_RANGE_END}.`, { suggestion: 'Stop some bdg sessions or Chrome instances and retry.' }, EXIT_CODES.SOFTWARE_ERROR);
57
69
  }
58
70
  /**
59
71
  * Read the saved port from the session directory.
@@ -61,21 +73,8 @@ async function findAvailablePort(startPort = PORT_RANGE_START) {
61
73
  * @returns Saved port number or null if not found/invalid
62
74
  */
63
75
  export function readSessionPort() {
64
- try {
65
- const portPath = getSessionFilePath('PORT');
66
- if (!fs.existsSync(portPath)) {
67
- return null;
68
- }
69
- const content = fs.readFileSync(portPath, 'utf-8').trim();
70
- const port = parseInt(content, 10);
71
- if (isNaN(port) || port < 1 || port > 65535) {
72
- return null;
73
- }
74
- return port;
75
- }
76
- catch {
77
- return null;
78
- }
76
+ const portPath = getSessionFilePath('PORT');
77
+ return fs.existsSync(portPath) ? readPortFile(portPath) : null;
79
78
  }
80
79
  /**
81
80
  * Save the port to the session directory.
@@ -91,34 +90,42 @@ export function writeSessionPort(port) {
91
90
  * Get or allocate a port for this session.
92
91
  *
93
92
  * Logic:
94
- * 1. If a port is explicitly provided, use it (user override)
95
- * 2. Check if there's a saved port in the session directory
96
- * 3. If saved port exists and is available, reuse it (session stability)
97
- * 4. Otherwise, find a new available port and save it
93
+ * 1. If a port is explicitly provided, use it (user override; a named session
94
+ * saves and claims it under the lock so other sessions skip it)
95
+ * 2. Otherwise, under a lock shared by all sessions on the machine (any
96
+ * BDG_SESSION_DIR), reuse the saved port if it is free and no other
97
+ * running session claims it (session stability)
98
+ * 3. Otherwise, take the first free, unclaimed port from
99
+ * {@link firstCandidatePort} upwards and save it (the claim)
98
100
  *
99
- * This provides session isolation: different BDG_SESSION_DIR values
100
- * will automatically use different ports.
101
+ * The claim is also recorded in the machine-wide registry (under the lock),
102
+ * so sessions of other base directories skip it too.
103
+ *
104
+ * This provides session isolation: named sessions and different
105
+ * BDG_SESSION_DIR values automatically use different ports, even when they
106
+ * start at the same time.
101
107
  *
102
108
  * @param explicitPort - User-provided port (takes precedence)
103
109
  * @returns Promise resolving to the port to use
104
110
  */
105
111
  export async function getSessionPort(explicitPort) {
106
- // User explicitly specified a port - use it directly
107
112
  if (explicitPort !== undefined && explicitPort !== null) {
108
- return explicitPort;
109
- }
110
- // Check for saved session port
111
- const savedPort = readSessionPort();
112
- if (savedPort !== null) {
113
- // Verify the saved port is still available
114
- if (await isPortAvailable(savedPort)) {
115
- return savedPort;
116
- }
117
- // Saved port is in use by another process, need to find a new one
113
+ if (getSessionName() === null)
114
+ return explicitPort;
115
+ return withPortLock((recordClaim) => {
116
+ writeSessionPort(explicitPort);
117
+ recordClaim(explicitPort);
118
+ return Promise.resolve(explicitPort);
119
+ });
118
120
  }
119
- // Find a new available port
120
- const newPort = await findAvailablePort();
121
- writeSessionPort(newPort);
122
- return newPort;
121
+ return withPortLock(async (recordClaim) => {
122
+ const claimed = portsClaimedByOtherSessions();
123
+ const savedPort = readSessionPort();
124
+ const reuse = savedPort !== null && !claimed.has(savedPort) && (await isPortAvailable(savedPort));
125
+ const port = reuse ? savedPort : await findAvailablePort(firstCandidatePort(), claimed);
126
+ writeSessionPort(port);
127
+ recordClaim(port);
128
+ return port;
129
+ });
123
130
  }
124
131
  //# sourceMappingURL=port.js.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Port claims across concurrent sessions.
3
+ *
4
+ * Sessions started at the same time must not pick the same CDP port: Chrome
5
+ * binds it only after the choice is made. A session claims its port by
6
+ * writing `port.txt` while holding a lock; a running session's claim is
7
+ * skipped by the others.
8
+ *
9
+ * Ports belong to the machine, not to a session directory, so the lock and a
10
+ * registry of claiming session directories live in one directory per user
11
+ * under the OS temp directory: sessions of different `BDG_SESSION_DIR`s see
12
+ * each other's claims too. That directory is used only if it is a real
13
+ * directory owned by the user that nobody else can write to; otherwise the
14
+ * lock falls back to the base session directory and only that directory's
15
+ * claims are seen (the launched Chrome's identity is still checked).
16
+ */
17
+ /** Records a port claim; a no-op when the registry cannot be used safely */
18
+ export type RecordPortClaim = (port: number) => void;
19
+ /**
20
+ * Read a port number from a `port.txt` file.
21
+ *
22
+ * @param portPath - File path
23
+ * @returns Port, or null if missing or invalid
24
+ */
25
+ export declare function readPortFile(portPath: string): number | null;
26
+ /**
27
+ * Directory holding the port lock and the claims registry, shared by every
28
+ * session of the user on this machine.
29
+ *
30
+ * @returns `$BDG_PORT_REGISTRY_DIR`, else `<os temp dir>/bdg-ports-<uid>`
31
+ */
32
+ export declare function getPortRegistryDir(): string;
33
+ /**
34
+ * Whether a path is a directory the current user can trust: a real directory
35
+ * (not a symlink), owned by the user, not writable by group or others.
36
+ *
37
+ * @param dir - Directory
38
+ * @returns Why it cannot be trusted, or null if it can
39
+ */
40
+ export declare function untrustedDirReason(dir: string): string | null;
41
+ /**
42
+ * Session directories other than the selected one that bdg knows of: those of
43
+ * this base directory, and those of any base directory found in the
44
+ * machine-wide registry. They need not hold a running session.
45
+ *
46
+ * @returns Session directories
47
+ */
48
+ export declare function otherSessionDirs(): string[];
49
+ /**
50
+ * Ports claimed by other sessions that are running or starting (their daemon
51
+ * socket exists), see {@link otherSessionDirs}.
52
+ *
53
+ * @returns Claimed ports
54
+ */
55
+ export declare function portsClaimedByOtherSessions(): Set<number>;
56
+ /**
57
+ * Run port selection under the lock shared by all sessions of the user on
58
+ * this machine (in the trusted registry directory; else in the base session
59
+ * directory). If the lock cannot be taken in time, the selection runs anyway.
60
+ *
61
+ * @param select - Chooses the port; gets a function recording the claim in
62
+ * the registry, a no-op without the registry lock
63
+ * @returns The chosen port
64
+ */
65
+ export declare function withPortLock(select: (recordClaim: RecordPortClaim) => Promise<number>): Promise<number>;
66
+ //# sourceMappingURL=portClaims.d.ts.map