windows-development-sandbox 1.20.0 → 1.23.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 (331) hide show
  1. package/config.example.json +8 -1
  2. package/dist/app/features/access/access.def.js +5 -2
  3. package/dist/app/features/access/access.run.js +7 -3
  4. package/dist/app/features/accounts/accounts.def.d.ts +29 -1
  5. package/dist/app/features/accounts/accounts.def.js +10 -4
  6. package/dist/app/features/accounts/accounts.run.d.ts +1 -0
  7. package/dist/app/features/accounts/accounts.run.js +7 -2
  8. package/dist/app/features/accounts/services.def.d.ts +4 -1
  9. package/dist/app/features/accounts/services.def.js +15 -14
  10. package/dist/app/features/accounts/services.run.d.ts +1 -0
  11. package/dist/app/features/accounts/services.run.js +41 -15
  12. package/dist/app/features/defs.d.ts +39 -2
  13. package/dist/app/features/engine/engine.run.js +1 -1
  14. package/dist/app/features/github/github.def.d.ts +44 -3
  15. package/dist/app/features/github/github.def.js +145 -63
  16. package/dist/app/features/github/github.run.d.ts +9 -2
  17. package/dist/app/features/github/github.run.js +43 -5
  18. package/dist/app/features/health/health.def.js +8 -2
  19. package/dist/app/features/logs/logs.def.js +0 -8
  20. package/dist/app/features/projects/projects.def.js +2 -2
  21. package/dist/app/features/projects/projects.run.js +5 -5
  22. package/dist/app/features/sandbox/sandbox.def.js +7 -3
  23. package/dist/app/features/sessions/sessions.def.d.ts +4 -4
  24. package/dist/app/features/sessions/sessions.def.js +0 -5
  25. package/dist/app/features/sessions/sessions.run.d.ts +1 -1
  26. package/dist/app/features/sessions/sessions.run.js +2 -2
  27. package/dist/app/features/settings/categories.def.js +2 -0
  28. package/dist/app/features/settings/settings-rows.js +1 -1
  29. package/dist/app/features/settings/settings.def.js +1 -1
  30. package/dist/app/features/settings/settings.run.js +3 -3
  31. package/dist/app/features/storage/storage.run.js +18 -9
  32. package/dist/app/features/updates/update-lines.js +2 -2
  33. package/dist/app/features/updates/updates.def.js +1 -1
  34. package/dist/app/features/updates/updates.run.js +1 -1
  35. package/dist/app/features/web/web.def.js +2 -2
  36. package/dist/app/features/web/web.run.js +12 -4
  37. package/dist/app/kernel/errors.d.ts +7 -0
  38. package/dist/app/kernel/errors.js +18 -3
  39. package/dist/app/kernel/rpc.d.ts +5 -0
  40. package/dist/app/kernel/view.d.ts +11 -1
  41. package/dist/bin/agent-sandbox.js +2 -2
  42. package/dist/bin/{chunk-DK4NTBUF.js → chunk-JETGP2C3.js} +172 -48
  43. package/dist/bin/{chunk-2IMT7ZCT.js → chunk-XZKOPGNH.js} +1 -1
  44. package/dist/bin/{chunk-Z2P5OGKL.js → chunk-ZTTOF5V3.js} +2430 -1688
  45. package/dist/bin/claude-host.js +2 -2
  46. package/dist/bin/claude-sbx.js +2 -2
  47. package/dist/bin/codex-sbx.js +2 -2
  48. package/dist/bin/gemini-sbx.js +2 -2
  49. package/dist/bin/sandbox.js +2 -2
  50. package/dist/bin/sbx-hide.js +2 -2
  51. package/dist/bin/sbx-show.js +2 -2
  52. package/dist/cli/manifest.d.ts +2 -2
  53. package/dist/cli/manifest.js +2 -1
  54. package/dist/cli/menu/coverage-exempt.d.ts +17 -0
  55. package/dist/cli/menu/coverage-exempt.js +61 -0
  56. package/dist/cli/menu/io-menu.d.ts +2 -0
  57. package/dist/cli/menu/io-menu.js +50 -5
  58. package/dist/cli/menu/pages/accounts-page.js +1 -1
  59. package/dist/cli/menu/pages/agents-page.js +8 -2
  60. package/dist/cli/menu/pages/maintenance-page.d.ts +6 -0
  61. package/dist/cli/menu/pages/maintenance-page.js +9 -2
  62. package/dist/cli/menu/pages/sessions-page.d.ts +2 -0
  63. package/dist/cli/menu/pages/sessions-page.js +12 -4
  64. package/dist/cli/menu/pages/settings-page.js +12 -1
  65. package/dist/cli/menu/remote-menu.js +1 -1
  66. package/dist/cli/menu/shell.d.ts +5 -0
  67. package/dist/cli/menu/shell.js +13 -0
  68. package/dist/cli/report.js +2 -0
  69. package/dist/cli/tui/app.d.ts +5 -0
  70. package/dist/cli/tui/app.js +2 -0
  71. package/dist/core/config/defaults.js +3 -1
  72. package/dist/core/schema/accounts.d.ts +6 -0
  73. package/dist/core/schema/accounts.js +13 -11
  74. package/dist/core/schema/config.d.ts +20 -8
  75. package/dist/core/schema/config.js +4 -4
  76. package/dist/core/schema/github.js +1 -1
  77. package/dist/core/schema/profile.js +8 -4
  78. package/dist/core/schema/resume.js +1 -1
  79. package/dist/core/schema/sandbox.js +3 -3
  80. package/dist/core/schema/vpn.js +1 -1
  81. package/dist/core/schema/web.js +3 -3
  82. package/dist/core/types.d.ts +6 -0
  83. package/dist/core/util/errors.d.ts +11 -1
  84. package/dist/core/util/errors.js +7 -1
  85. package/dist/core/util/index.d.ts +1 -1
  86. package/dist/core/util/index.js +1 -1
  87. package/dist/guest/accounts/adapter.d.ts +2 -0
  88. package/dist/guest/accounts/adapters.js +4 -0
  89. package/dist/guest/accounts/apply.d.ts +6 -2
  90. package/dist/guest/accounts/kinds/cfx.d.ts +7 -0
  91. package/dist/guest/accounts/kinds/cfx.js +33 -3
  92. package/dist/guest/accounts/kinds/comet.d.ts +7 -0
  93. package/dist/guest/accounts/kinds/comet.js +50 -0
  94. package/dist/guest/accounts/kinds/perplexity.d.ts +2 -0
  95. package/dist/guest/accounts/kinds/perplexity.js +12 -0
  96. package/dist/guest/accounts/watch.d.ts +2 -0
  97. package/dist/guest/accounts/watch.js +12 -2
  98. package/dist/guest/agents/agent-installer.js +1 -1
  99. package/dist/guest/agents/profile-links.d.ts +2 -0
  100. package/dist/guest/agents/profile-links.js +18 -4
  101. package/dist/guest/apps/after-install.js +19 -0
  102. package/dist/guest/apps/app-installer.d.ts +11 -0
  103. package/dist/guest/apps/app-installer.js +69 -6
  104. package/dist/guest/apps/comet-unpack.d.ts +20 -0
  105. package/dist/guest/apps/comet-unpack.js +127 -0
  106. package/dist/guest/apps/index.d.ts +1 -1
  107. package/dist/guest/apps/index.js +1 -1
  108. package/dist/guest/apps/resolvers-agents-web.d.ts +2 -0
  109. package/dist/guest/apps/resolvers-agents-web.js +7 -0
  110. package/dist/guest/apps/resolvers.js +2 -0
  111. package/dist/guest/apps/shortcuts.d.ts +2 -0
  112. package/dist/guest/apps/shortcuts.js +1 -0
  113. package/dist/guest/apps/unpack.js +14 -4
  114. package/dist/guest/boot/boot.d.ts +45 -16
  115. package/dist/guest/boot/boot.js +316 -145
  116. package/dist/guest/boot/graph.d.ts +252 -0
  117. package/dist/guest/boot/graph.js +257 -0
  118. package/dist/guest/boot/guide-lines.d.ts +25 -0
  119. package/dist/guest/boot/guide-lines.js +41 -0
  120. package/dist/guest/boot/guide.d.ts +1 -4
  121. package/dist/guest/boot/guide.js +6 -8
  122. package/dist/guest/boot/runner.d.ts +77 -0
  123. package/dist/guest/boot/runner.js +215 -0
  124. package/dist/guest/browser/cdp.d.ts +71 -0
  125. package/dist/guest/browser/cdp.js +306 -0
  126. package/dist/guest/browser/comet.d.ts +28 -0
  127. package/dist/guest/browser/comet.js +155 -0
  128. package/dist/guest/daemon/project-provisioner.js +6 -1
  129. package/dist/guest/devtools/fivem.d.ts +1 -1
  130. package/dist/guest/devtools/fivem.js +243 -3
  131. package/dist/guest/entries/daemon.js +2 -0
  132. package/dist/guest/runtime/log.d.ts +12 -0
  133. package/dist/guest/runtime/log.js +29 -5
  134. package/dist/guest/runtime/pools.d.ts +24 -0
  135. package/dist/guest/runtime/pools.js +60 -0
  136. package/dist/guest/runtime/process.d.ts +4 -0
  137. package/dist/guest/runtime/process.js +38 -1
  138. package/dist/guest/runtime/status.d.ts +5 -1
  139. package/dist/guest/runtime/status.js +19 -0
  140. package/dist/guest/setup/addon-mcp.d.ts +1 -1
  141. package/dist/guest/setup/addon-mcp.js +52 -12
  142. package/dist/guest/setup/agent-addons.d.ts +3 -2
  143. package/dist/guest/setup/agent-addons.js +12 -3
  144. package/dist/guest/setup/environment.d.ts +1 -1
  145. package/dist/guest/setup/environment.js +25 -12
  146. package/dist/guest/setup/headroom.d.ts +8 -3
  147. package/dist/guest/setup/headroom.js +44 -15
  148. package/dist/guest/setup/path-plan.d.ts +28 -0
  149. package/dist/guest/setup/path-plan.js +69 -2
  150. package/dist/guest/setup/shell-path.d.ts +1 -1
  151. package/dist/guest/setup/shell-path.js +3 -3
  152. package/dist/guest/toolchains/fivem/artifacts.d.ts +48 -1
  153. package/dist/guest/toolchains/fivem/artifacts.js +97 -4
  154. package/dist/guest/toolchains/fivem/fivem.d.ts +32 -1
  155. package/dist/guest/toolchains/fivem/fivem.js +168 -3
  156. package/dist/guest/toolchains/fivem/scaffold.d.ts +35 -0
  157. package/dist/guest/toolchains/fivem/scaffold.js +105 -0
  158. package/dist/guest/toolchains/fivem/serve-plan.d.ts +34 -0
  159. package/dist/guest/toolchains/fivem/serve-plan.js +61 -0
  160. package/dist/guest/toolchains/languages/go.js +1 -1
  161. package/dist/guest/toolchains/languages/node.js +2 -1
  162. package/dist/guest/toolchains/languages/php.d.ts +2 -0
  163. package/dist/guest/toolchains/languages/php.js +13 -1
  164. package/dist/guest/toolchains/languages/python-tools.js +1 -1
  165. package/dist/guest/toolchains/languages/ruby.js +1 -1
  166. package/dist/guest/toolchains/native/binstall.js +1 -1
  167. package/dist/guest/toolchains/native/msvc/packages.js +10 -4
  168. package/dist/guest/toolchains/native/rust.d.ts +2 -0
  169. package/dist/guest/toolchains/native/rust.js +11 -4
  170. package/dist/guest/toolchains/native/unreal.d.ts +2 -0
  171. package/dist/guest/toolchains/native/unreal.js +16 -6
  172. package/dist/guest/toolchains/native/vcruntime.d.ts +2 -0
  173. package/dist/guest/toolchains/native/vcruntime.js +10 -2
  174. package/dist/guest/toolchains/native/visual-studio.d.ts +3 -0
  175. package/dist/guest/toolchains/native/visual-studio.js +44 -26
  176. package/dist/guest/toolchains/native/vs-request.d.ts +2 -0
  177. package/dist/guest/toolchains/native/vs-request.js +2 -0
  178. package/dist/guest/toolchains/plan.js +19 -8
  179. package/dist/guest/toolchains/scheduler.d.ts +2 -21
  180. package/dist/guest/toolchains/scheduler.js +7 -121
  181. package/dist/guest/toolchains/setup.d.ts +15 -2
  182. package/dist/guest/toolchains/setup.js +39 -20
  183. package/dist/guest/toolchains/split.d.ts +28 -0
  184. package/dist/guest/toolchains/split.js +60 -0
  185. package/dist/guest/toolchains/steam/client.js +2 -1
  186. package/dist/guest/toolchains/step-scheduler.d.ts +51 -0
  187. package/dist/guest/toolchains/step-scheduler.js +147 -0
  188. package/dist/guest/toolchains/step.d.ts +13 -0
  189. package/dist/guest/toolchains/tools/git.js +18 -0
  190. package/dist/guest/vpn/openvpn.js +3 -4
  191. package/dist/guest/vpn/wireguard.js +11 -4
  192. package/dist/host/accounts/deliver.js +7 -0
  193. package/dist/host/accounts/handoff.js +2 -6
  194. package/dist/host/accounts/index.d.ts +1 -1
  195. package/dist/host/accounts/index.js +1 -1
  196. package/dist/host/accounts/kinds/cfx.d.ts +1 -1
  197. package/dist/host/accounts/kinds/cfx.js +47 -4
  198. package/dist/host/accounts/kinds/comet.d.ts +2 -0
  199. package/dist/host/accounts/kinds/comet.js +6 -0
  200. package/dist/host/accounts/kinds/perplexity.d.ts +2 -0
  201. package/dist/host/accounts/kinds/perplexity.js +30 -0
  202. package/dist/host/accounts/leases.d.ts +7 -1
  203. package/dist/host/accounts/leases.js +13 -2
  204. package/dist/host/accounts/source.d.ts +7 -1
  205. package/dist/host/accounts/source.js +27 -0
  206. package/dist/host/accounts/sources.js +4 -0
  207. package/dist/host/github/deliver.d.ts +1 -1
  208. package/dist/host/github/deliver.js +20 -1
  209. package/dist/host/github/index.d.ts +2 -1
  210. package/dist/host/github/index.js +1 -0
  211. package/dist/host/github/token.d.ts +13 -1
  212. package/dist/host/github/token.js +19 -2
  213. package/dist/host/github/verify.d.ts +27 -0
  214. package/dist/host/github/verify.js +61 -0
  215. package/dist/host/launch/hints/fivem.d.ts +3 -1
  216. package/dist/host/launch/hints/fivem.js +68 -2
  217. package/dist/host/launch/session-file.js +4 -0
  218. package/dist/host/launch/toolchains.d.ts +2 -1
  219. package/dist/host/launch/toolchains.js +5 -3
  220. package/dist/host/vpn/nordvpn.d.ts +2 -4
  221. package/dist/host/vpn/nordvpn.js +2 -24
  222. package/dist/runtime/agent.js +405 -90
  223. package/dist/runtime/daemon.js +3010 -1383
  224. package/dist/runtime/db.js +333 -90
  225. package/dist/runtime/main.js +4720 -1954
  226. package/dist/runtime/netproxy.js +286 -69
  227. package/dist/runtime/tool.js +2852 -1780
  228. package/dist/runtime/view.js +286 -69
  229. package/dist/selftest/agent-paths.test.js +39 -1
  230. package/dist/selftest/agents-web.test.d.ts +2 -0
  231. package/dist/selftest/agents-web.test.js +647 -0
  232. package/dist/selftest/app.test.js +5 -2
  233. package/dist/selftest/boot-runner.test.d.ts +2 -0
  234. package/dist/selftest/boot-runner.test.js +456 -0
  235. package/dist/selftest/channel.test.js +15 -0
  236. package/dist/selftest/fivem.test.js +249 -3
  237. package/dist/selftest/github.test.js +94 -9
  238. package/dist/selftest/guest-boot.test.js +3 -0
  239. package/dist/selftest/health.test.js +1 -1
  240. package/dist/selftest/index.js +8 -0
  241. package/dist/selftest/layout.test.js +5 -0
  242. package/dist/selftest/menu-coverage.test.d.ts +2 -0
  243. package/dist/selftest/menu-coverage.test.js +46 -0
  244. package/dist/selftest/menu-screens.js +37 -1
  245. package/dist/selftest/persisted-installs.test.js +6 -3
  246. package/dist/selftest/service-accounts.test.js +3 -2
  247. package/dist/selftest/support/engine-golden.js +27 -0
  248. package/dist/selftest/support/fixtures.d.ts +6 -0
  249. package/dist/selftest/support/web-preview.d.ts +34 -1
  250. package/dist/selftest/support/web-preview.js +21 -18
  251. package/dist/selftest/toolchains.test.js +23 -1
  252. package/dist/selftest/ui-text.test.d.ts +7 -0
  253. package/dist/selftest/ui-text.test.js +300 -0
  254. package/dist/shared/accounts/index.d.ts +2 -0
  255. package/dist/shared/accounts/index.js +2 -0
  256. package/dist/shared/accounts/kinds/comet.d.ts +12 -0
  257. package/dist/shared/accounts/kinds/comet.js +64 -0
  258. package/dist/shared/accounts/kinds/perplexity.d.ts +8 -0
  259. package/dist/shared/accounts/kinds/perplexity.js +37 -0
  260. package/dist/shared/accounts/kinds.d.ts +24 -5
  261. package/dist/shared/accounts/kinds.js +86 -4
  262. package/dist/shared/accounts/payloads.d.ts +81 -0
  263. package/dist/shared/accounts/payloads.js +39 -1
  264. package/dist/shared/apps/agent-addons.d.ts +33 -5
  265. package/dist/shared/apps/agent-addons.js +95 -3
  266. package/dist/shared/apps/catalogue-agents-web.d.ts +14 -0
  267. package/dist/shared/apps/catalogue-agents-web.js +112 -0
  268. package/dist/shared/apps/catalogue-modding.js +32 -1
  269. package/dist/shared/apps/catalogue.d.ts +27 -3
  270. package/dist/shared/apps/catalogue.js +2 -1
  271. package/dist/shared/apps/index.d.ts +3 -1
  272. package/dist/shared/apps/index.js +3 -1
  273. package/dist/shared/channel/schema.d.ts +21 -3
  274. package/dist/shared/channel/schema.js +7 -0
  275. package/dist/shared/channel/secrets.d.ts +19 -0
  276. package/dist/shared/concurrency/index.d.ts +3 -0
  277. package/dist/shared/concurrency/index.js +3 -0
  278. package/dist/shared/concurrency/installer-retry.d.ts +6 -0
  279. package/dist/shared/concurrency/installer-retry.js +10 -0
  280. package/dist/shared/concurrency/limiter.d.ts +19 -0
  281. package/dist/shared/concurrency/limiter.js +67 -0
  282. package/dist/shared/concurrency/pools.d.ts +7 -0
  283. package/dist/shared/concurrency/pools.js +9 -0
  284. package/dist/shared/download/archive.d.ts +2 -0
  285. package/dist/shared/download/archive.js +11 -1
  286. package/dist/shared/download/bounded-fetch.d.ts +13 -0
  287. package/dist/shared/download/bounded-fetch.js +40 -0
  288. package/dist/shared/download/downloader.d.ts +5 -0
  289. package/dist/shared/download/downloader.js +50 -38
  290. package/dist/shared/download/index.d.ts +2 -1
  291. package/dist/shared/download/index.js +2 -1
  292. package/dist/shared/readiness.d.ts +2 -2
  293. package/dist/shared/readiness.js +2 -2
  294. package/dist/web/index.html +1 -1
  295. package/dist/web/{main-OUUQYNK4.js → main-CCILLX2M.js} +67 -67
  296. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  297. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  298. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  299. package/package.json +1 -1
  300. package/sandbox/addons/browser-use-mcp/requirements.in +1 -0
  301. package/sandbox/addons/browser-use-mcp/requirements.txt +2350 -0
  302. package/sandbox/addons/chrome-devtools-mcp/package-lock.json +38 -0
  303. package/sandbox/addons/chrome-devtools-mcp/package.json +7 -0
  304. package/sandbox/addons/comet-mcp/package-lock.json +1277 -0
  305. package/sandbox/addons/comet-mcp/package.json +7 -0
  306. package/sandbox/addons/perplexity-mcp/package-lock.json +1655 -0
  307. package/sandbox/addons/perplexity-mcp/package.json +7 -0
  308. package/sandbox/scripts/add-firewall-port.ps1 +18 -4
  309. package/sandbox/scripts/set-machine-environment.ps1 +30 -15
  310. package/sandbox/templates/fivem/README.txt +6 -2
  311. package/sandbox/templates/fivem/cs/Client/Client.csproj +15 -0
  312. package/sandbox/templates/fivem/cs/Client/ClientMain.cs +20 -0
  313. package/sandbox/templates/fivem/cs/Server/Server.csproj +14 -0
  314. package/sandbox/templates/fivem/cs/Server/ServerMain.cs +20 -0
  315. package/sandbox/templates/fivem/cs/fxmanifest.lua +11 -0
  316. package/sandbox/templates/fivem/js/client.js +12 -0
  317. package/sandbox/templates/fivem/js/fxmanifest.lua +9 -0
  318. package/sandbox/templates/fivem/js/server.js +6 -0
  319. package/sandbox/templates/fivem/lua/client.lua +8 -0
  320. package/sandbox/templates/fivem/lua/fxmanifest.lua +10 -0
  321. package/sandbox/templates/fivem/lua/server.lua +6 -0
  322. package/sandbox/templates/fivem/server.cfg +37 -0
  323. package/sandbox/templates/fivem/ts/build.js +17 -0
  324. package/sandbox/templates/fivem/ts/fxmanifest.lua +9 -0
  325. package/sandbox/templates/fivem/ts/package.json +16 -0
  326. package/sandbox/templates/fivem/ts/src/client/client.ts +12 -0
  327. package/sandbox/templates/fivem/ts/src/client/tsconfig.json +13 -0
  328. package/sandbox/templates/fivem/ts/src/server/server.ts +6 -0
  329. package/sandbox/templates/fivem/ts/src/server/tsconfig.json +13 -0
  330. package/schemas/config.schema.json +56 -27
  331. package/schemas/sandbox-config.schema.json +37 -14
@@ -0,0 +1,77 @@
1
+ export interface BootTaskDef<Id extends string = string> {
2
+ readonly id: Id;
3
+ readonly stage: number;
4
+ readonly title: string;
5
+ readonly after: readonly Id[];
6
+ /** A failure stops the tasks that come after it and fails the boot (otherwise it is a warning). */
7
+ readonly critical: boolean;
8
+ /** Starts only after the gate (`run('background')`) and logs to the file only. */
9
+ readonly background?: boolean;
10
+ /** Runs alone (the hooks inherit the terminal). */
11
+ readonly exclusive?: boolean;
12
+ }
13
+ export type TaskRun = () => Promise<void> | void;
14
+ export interface TaskOutcome {
15
+ readonly id: string;
16
+ readonly title: string;
17
+ readonly state: 'done' | 'failed' | 'skipped';
18
+ readonly ms: number;
19
+ readonly error?: string;
20
+ }
21
+ /** Where the boot stands, for the host's progress line. */
22
+ export interface RunnerStatus {
23
+ readonly phase: 'booting' | 'provisioning';
24
+ /** The lowest stage that still has an unfinished foreground task. */
25
+ readonly step: number;
26
+ /** Titles of the tasks that run now. */
27
+ readonly running: readonly string[];
28
+ readonly message: string;
29
+ }
30
+ export interface RunnerOptions {
31
+ /** `quiet`: file only (background tasks). */
32
+ say(line: string, quiet?: boolean): void;
33
+ warn(line: string, quiet?: boolean): void;
34
+ /** Milliseconds. */
35
+ now(): number;
36
+ dryRun: boolean;
37
+ /** Tasks at once (default: 1 in a dry run, else 4). */
38
+ parallel?: number;
39
+ stages: number;
40
+ stageTitle(stage: number): string;
41
+ status?(status: RunnerStatus): void;
42
+ /** Runs a task's work so that its log lines carry the task (`prefix()` is empty while it runs alone). */
43
+ inContext?<T>(ctx: {
44
+ prefix: () => string;
45
+ quiet: boolean;
46
+ }, work: () => T): T;
47
+ }
48
+ export declare const seconds: (ms: number) => string;
49
+ /** Dependencies must exist, ids be unique, the graph acyclic and no foreground task wait for a background one. */
50
+ export declare function checkGraph(defs: readonly BootTaskDef[]): void;
51
+ export declare class BootRunner<Id extends string> {
52
+ private readonly defs;
53
+ private readonly impl;
54
+ private readonly o;
55
+ private readonly byId;
56
+ private readonly state;
57
+ private readonly outcomes;
58
+ private readonly announced;
59
+ private readonly running;
60
+ constructor(defs: readonly BootTaskDef<Id>[], impl: Readonly<Record<Id, TaskRun>>, o: RunnerOptions);
61
+ /** Every outcome so far, in declaration order. */
62
+ results(): TaskOutcome[];
63
+ /**
64
+ * Runs the foreground tasks, or (after the gate) the background ones. Resolves with the outcomes of this run;
65
+ * rejects with the first critical error once the tasks that were running have finished.
66
+ */
67
+ run(which: 'foreground' | 'background'): Promise<TaskOutcome[]>;
68
+ private isFinished;
69
+ /** The task cannot have run properly: skipped, or failed with a critical failure. */
70
+ private isBlocked;
71
+ private skip;
72
+ private begin;
73
+ private finish;
74
+ private report;
75
+ }
76
+ /** "Boot ready in 31.8s. Slowest: Headroom 28.1s, Developer tools 7.0s. In the background: Visual Studio (...)". */
77
+ export declare function bootSummary(totalMs: number, outcomes: readonly TaskOutcome[], background: readonly string[], logPath: string): string;
@@ -0,0 +1,215 @@
1
+ // Runs the boot's tasks: each starts when the tasks it comes after are finished, a few at a time, in declaration order.
2
+ // Pure (no runtime imports): the boot injects printing, the clock, the log context and the status.
3
+ const PARALLEL = 4;
4
+ const CYAN = '\x1b[36m';
5
+ const RESET = '\x1b[0m';
6
+ export const seconds = (ms) => (ms / 1000).toFixed(1) + 's';
7
+ /** Dependencies must exist, ids be unique, the graph acyclic and no foreground task wait for a background one. */
8
+ export function checkGraph(defs) {
9
+ const byId = new Map();
10
+ for (const d of defs) {
11
+ if (byId.has(d.id))
12
+ throw new Error('Boot task "' + d.id + '" is defined twice');
13
+ byId.set(d.id, d);
14
+ }
15
+ for (const d of defs)
16
+ for (const a of d.after) {
17
+ const dep = byId.get(a);
18
+ if (!dep)
19
+ throw new Error('Boot task "' + d.id + '" comes after "' + a + '", which does not exist');
20
+ if (dep.background && !d.background)
21
+ throw new Error('Boot task "' + d.id + '" waits for the background task "' + a + '"');
22
+ }
23
+ // Kahn: whatever cannot be taken off the front is in a cycle.
24
+ const left = new Map(defs.map((d) => [d.id, d.after.length]));
25
+ const queue = defs.filter((d) => d.after.length === 0).map((d) => d.id);
26
+ let taken = 0;
27
+ while (queue.length) {
28
+ const id = queue.shift();
29
+ taken++;
30
+ for (const d of defs)
31
+ if (d.after.includes(id)) {
32
+ left.set(d.id, left.get(d.id) - 1);
33
+ if (left.get(d.id) === 0)
34
+ queue.push(d.id);
35
+ }
36
+ }
37
+ if (taken !== defs.length)
38
+ throw new Error('The boot tasks depend on each other in a circle');
39
+ }
40
+ export class BootRunner {
41
+ defs;
42
+ impl;
43
+ o;
44
+ byId = new Map();
45
+ state = new Map();
46
+ outcomes = new Map();
47
+ announced = new Set();
48
+ running = new Set();
49
+ constructor(defs, impl, o) {
50
+ this.defs = defs;
51
+ this.impl = impl;
52
+ this.o = o;
53
+ checkGraph(defs);
54
+ for (const d of defs) {
55
+ this.byId.set(d.id, d);
56
+ this.state.set(d.id, 'pending');
57
+ }
58
+ }
59
+ /** Every outcome so far, in declaration order. */
60
+ results() {
61
+ return this.defs.flatMap((d) => (this.outcomes.has(d.id) ? [this.outcomes.get(d.id)] : []));
62
+ }
63
+ /**
64
+ * Runs the foreground tasks, or (after the gate) the background ones. Resolves with the outcomes of this run;
65
+ * rejects with the first critical error once the tasks that were running have finished.
66
+ */
67
+ run(which) {
68
+ const todo = this.defs.filter((d) => Boolean(d.background) === (which === 'background'));
69
+ const quiet = which === 'background';
70
+ const parallel = this.o.parallel ?? (this.o.dryRun ? 1 : PARALLEL);
71
+ return new Promise((resolve, reject) => {
72
+ let firstCritical;
73
+ let failed = false;
74
+ const pending = () => todo.filter((d) => this.state.get(d.id) === 'pending');
75
+ const settle = () => {
76
+ if (this.running.size === 0 && pending().length === 0) {
77
+ if (failed)
78
+ reject(firstCritical);
79
+ else
80
+ resolve(todo.map((d) => this.outcomes.get(d.id)));
81
+ }
82
+ };
83
+ const start = (d) => {
84
+ this.state.set(d.id, 'running');
85
+ this.running.add(d.id);
86
+ const began = this.o.now();
87
+ if (!quiet)
88
+ this.begin(d);
89
+ const prefix = () => (this.running.size > 1 && !d.exclusive ? d.title : '');
90
+ const work = async () => {
91
+ await this.impl[d.id]();
92
+ };
93
+ const call = () => (this.o.inContext ? this.o.inContext({ prefix, quiet }, work) : work());
94
+ // A synchronous throw from a task is a failure too: the call starts inside a promise.
95
+ void Promise.resolve()
96
+ .then(call)
97
+ .then(() => this.finish(d, began, quiet, undefined), (e) => {
98
+ this.finish(d, began, quiet, e);
99
+ if (d.critical && firstCritical === undefined) {
100
+ firstCritical = e;
101
+ failed = true;
102
+ }
103
+ })
104
+ .finally(() => {
105
+ this.running.delete(d.id);
106
+ if (!quiet)
107
+ this.report(d.stage);
108
+ pump();
109
+ });
110
+ };
111
+ const pump = () => {
112
+ // Tasks that come after a failed critical (or skipped) task are skipped, and so is whatever comes after those.
113
+ for (let again = true; again;) {
114
+ again = false;
115
+ for (const d of pending()) {
116
+ const bad = d.after.find((a) => this.isBlocked(a));
117
+ if (bad) {
118
+ this.skip(d, this.byId.get(bad).title + ' failed');
119
+ again = true;
120
+ }
121
+ }
122
+ }
123
+ for (const d of pending()) {
124
+ if (this.running.size >= parallel)
125
+ break;
126
+ if (!d.after.every((a) => this.isFinished(a)))
127
+ continue;
128
+ if (d.exclusive) {
129
+ // Waits until nothing runs, and holds back everything declared after it.
130
+ if (this.running.size === 0)
131
+ start(d);
132
+ break;
133
+ }
134
+ start(d);
135
+ }
136
+ settle();
137
+ };
138
+ pump();
139
+ });
140
+ }
141
+ isFinished(id) {
142
+ const s = this.state.get(id);
143
+ return s === 'done' || s === 'failed' || s === 'skipped';
144
+ }
145
+ /** The task cannot have run properly: skipped, or failed with a critical failure. */
146
+ isBlocked(id) {
147
+ const s = this.state.get(id);
148
+ return s === 'skipped' || (s === 'failed' && this.byId.get(id).critical);
149
+ }
150
+ skip(d, why) {
151
+ this.state.set(d.id, 'skipped');
152
+ this.outcomes.set(d.id, { id: d.id, title: d.title, state: 'skipped', ms: 0, error: why });
153
+ this.o.say('[' + d.stage + '/' + this.o.stages + '] ' + d.title + ': skipped (' + why + ')');
154
+ }
155
+ begin(d) {
156
+ if (!this.announced.has(d.stage)) {
157
+ this.announced.add(d.stage);
158
+ this.o.say('\n' + CYAN + '[' + d.stage + '/' + this.o.stages + '] ' + this.o.stageTitle(d.stage) + RESET);
159
+ }
160
+ this.o.say('[' + d.stage + '/' + this.o.stages + '] ' + d.title + ': started');
161
+ this.report(d.stage);
162
+ }
163
+ finish(d, began, quiet, error) {
164
+ const ms = this.o.now() - began;
165
+ const message = error === undefined ? undefined : error instanceof Error ? error.message : String(error);
166
+ this.state.set(d.id, message === undefined ? 'done' : 'failed');
167
+ this.outcomes.set(d.id, {
168
+ id: d.id,
169
+ title: d.title,
170
+ state: message === undefined ? 'done' : 'failed',
171
+ ms,
172
+ ...(message === undefined ? {} : { error: message }),
173
+ });
174
+ const tag = '[' + d.stage + '/' + this.o.stages + '] ' + d.title;
175
+ if (message === undefined) {
176
+ if (!quiet)
177
+ this.o.say(tag + ': done in ' + seconds(ms));
178
+ }
179
+ else if (d.critical) {
180
+ this.o.warn(tag + ': FAILED in ' + seconds(ms) + ': ' + message, quiet);
181
+ }
182
+ else {
183
+ this.o.warn(d.title + ' failed (continuing): ' + message, quiet);
184
+ if (!quiet)
185
+ this.o.say(tag + ': FAILED in ' + seconds(ms) + ': ' + message);
186
+ }
187
+ }
188
+ report(stageHint) {
189
+ if (!this.o.status)
190
+ return;
191
+ const open = this.defs.filter((d) => !d.background && !this.isFinished(d.id));
192
+ const step = open.length ? Math.min(...open.map((d) => d.stage)) : stageHint;
193
+ const stage1Open = open.some((d) => d.stage === 1);
194
+ const running = this.defs.filter((d) => this.running.has(d.id)).map((d) => d.title);
195
+ this.o.status({
196
+ phase: stage1Open ? 'booting' : 'provisioning',
197
+ step,
198
+ running,
199
+ message: '[' + step + '/' + this.o.stages + '] ' + this.o.stageTitle(step),
200
+ });
201
+ }
202
+ }
203
+ /** "Boot ready in 31.8s. Slowest: Headroom 28.1s, Developer tools 7.0s. In the background: Visual Studio (...)". */
204
+ export function bootSummary(totalMs, outcomes, background, logPath) {
205
+ const slowest = [...outcomes]
206
+ .filter((o) => o.state !== 'skipped')
207
+ .sort((a, b) => b.ms - a.ms)
208
+ .slice(0, 3)
209
+ .map((o) => o.title + ' ' + seconds(o.ms));
210
+ return ('Boot ready in ' +
211
+ seconds(totalMs) +
212
+ '.' +
213
+ (slowest.length ? ' Slowest: ' + slowest.join(', ') + '.' : '') +
214
+ (background.length ? ' In the background: ' + background.join(', ') + ' (installing, see ' + logPath + ').' : ''));
215
+ }
@@ -0,0 +1,71 @@
1
+ import type { Duplex } from 'node:stream';
2
+ /** The largest message accepted from the browser (its whole cookie jar can be big). */
3
+ export declare const MAX_MESSAGE_BYTES: number;
4
+ export declare const OPCODE: {
5
+ readonly continuation: 0;
6
+ readonly text: 1;
7
+ readonly binary: 2;
8
+ readonly close: 8;
9
+ readonly ping: 9;
10
+ readonly pong: 10;
11
+ };
12
+ /** Where a browser's DevTools listen: its port on 127.0.0.1 and the browser target's path. */
13
+ export interface DevToolsEndpoint {
14
+ readonly port: number;
15
+ readonly path: string;
16
+ }
17
+ /** The DevToolsActivePort file's two lines (port, then /devtools/browser/<id>), or null when it is not that. */
18
+ export declare function parseDevToolsActivePort(text: string): DevToolsEndpoint | null;
19
+ /** One WebSocket frame. A client's frames are masked (`mask` given); a server's never are. */
20
+ export declare function encodeFrame(opcode: number, payload: Buffer, mask?: Buffer | null): Buffer;
21
+ export interface Frame {
22
+ readonly fin: boolean;
23
+ readonly opcode: number;
24
+ readonly payload: Buffer;
25
+ }
26
+ /**
27
+ * Cuts a byte stream into frames. Refuses (throws) reserved bits, unknown opcodes, a control frame that is long or
28
+ * split, a frame bigger than `maxBytes`, and masking the wrong way round (`masked`: what the other side must do).
29
+ */
30
+ export declare class FrameReader {
31
+ private readonly maxBytes;
32
+ private readonly masked;
33
+ private buffer;
34
+ constructor(maxBytes?: number, masked?: boolean);
35
+ push(chunk: Buffer): Frame[];
36
+ private next;
37
+ }
38
+ /** The Sec-WebSocket-Accept a server must answer to `key` (RFC 6455). */
39
+ export declare function acceptFor(key: string): string;
40
+ /** Parses the server's handshake answer: the bytes after it, or throws when it is not a switch to WebSocket. */
41
+ export declare function readHandshake(head: Buffer, key: string): {
42
+ rest: Buffer;
43
+ } | null;
44
+ /** One connection to a browser's DevTools: call() sends a command and resolves with its result. */
45
+ export declare class CdpConnection {
46
+ private readonly socket;
47
+ private nextId;
48
+ private readonly pending;
49
+ private readonly reader;
50
+ private fragments;
51
+ private fragmentBytes;
52
+ private closed;
53
+ private constructor();
54
+ /** Connects to the endpoint on 127.0.0.1 (never another host). */
55
+ static open(endpoint: DevToolsEndpoint, timeoutMs?: number): Promise<CdpConnection>;
56
+ /** The handshake over a socket that is connecting or connected (the self-test gives a fake one). */
57
+ static over(socket: Duplex, endpoint: DevToolsEndpoint, timeoutMs?: number): Promise<CdpConnection>;
58
+ private listen;
59
+ private onBytes;
60
+ private onFrame;
61
+ private onMessage;
62
+ private send;
63
+ private shut;
64
+ /** Sends a command (to a page's session in flat mode when `sessionId` is given) and waits for its result. */
65
+ call<T = unknown>(method: string, params?: Readonly<Record<string, unknown>>, o?: {
66
+ readonly sessionId?: string;
67
+ readonly timeoutMs?: number;
68
+ }): Promise<T>;
69
+ /** Closes the connection (the browser keeps running). */
70
+ close(): void;
71
+ }
@@ -0,0 +1,306 @@
1
+ // A small client of the Chrome DevTools Protocol (CDP) for a Chromium browser in this sandbox (Comet): the WebSocket
2
+ // handshake and frames over a plain TCP socket to 127.0.0.1 (Node 22.0.0 has no stable WebSocket), JSON calls with
3
+ // ids, nothing else. Everything the browser sends is bounded: the handshake's headers, each frame and each message.
4
+ // The port comes from the profile's DevToolsActivePort file (the browser starts with --remote-debugging-port=0 or a
5
+ // fixed port). No runtime imports: the self-test drives it over a fake socket.
6
+ import crypto from 'node:crypto';
7
+ import net from 'node:net';
8
+ /** The largest message accepted from the browser (its whole cookie jar can be big). */
9
+ export const MAX_MESSAGE_BYTES = 8 * 1024 * 1024;
10
+ const MAX_HEADER_BYTES = 16 * 1024;
11
+ const WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
12
+ const DEFAULT_TIMEOUT_MS = 10_000;
13
+ export const OPCODE = { continuation: 0, text: 1, binary: 2, close: 8, ping: 9, pong: 10 };
14
+ /** The DevToolsActivePort file's two lines (port, then /devtools/browser/<id>), or null when it is not that. */
15
+ export function parseDevToolsActivePort(text) {
16
+ const [portLine = '', pathLine = ''] = text.slice(0, 512).split(/\r?\n/);
17
+ if (!/^\d{1,5}$/.test(portLine.trim()))
18
+ return null;
19
+ const port = Number(portLine.trim());
20
+ const target = pathLine.trim();
21
+ if (port < 1 || port > 65535 || !/^\/devtools\/browser\/[A-Za-z0-9-]{1,64}$/.test(target))
22
+ return null;
23
+ return { port, path: target };
24
+ }
25
+ /** One WebSocket frame. A client's frames are masked (`mask` given); a server's never are. */
26
+ export function encodeFrame(opcode, payload, mask = crypto.randomBytes(4)) {
27
+ const len = payload.length;
28
+ const head = [0x80 | (opcode & 0x0f)];
29
+ const maskBit = mask ? 0x80 : 0;
30
+ if (len < 126)
31
+ head.push(maskBit | len);
32
+ else if (len < 65536)
33
+ head.push(maskBit | 126, (len >> 8) & 0xff, len & 0xff);
34
+ else {
35
+ head.push(maskBit | 127, 0, 0, 0, 0);
36
+ head.push((len >>> 24) & 0xff, (len >>> 16) & 0xff, (len >>> 8) & 0xff, len & 0xff);
37
+ }
38
+ if (!mask)
39
+ return Buffer.concat([Buffer.from(head), payload]);
40
+ const body = Buffer.alloc(len);
41
+ for (let i = 0; i < len; i++)
42
+ body[i] = payload[i] ^ mask[i & 3];
43
+ return Buffer.concat([Buffer.from(head), mask.subarray(0, 4), body]);
44
+ }
45
+ /**
46
+ * Cuts a byte stream into frames. Refuses (throws) reserved bits, unknown opcodes, a control frame that is long or
47
+ * split, a frame bigger than `maxBytes`, and masking the wrong way round (`masked`: what the other side must do).
48
+ */
49
+ export class FrameReader {
50
+ maxBytes;
51
+ masked;
52
+ buffer = Buffer.alloc(0);
53
+ constructor(maxBytes = MAX_MESSAGE_BYTES, masked = false) {
54
+ this.maxBytes = maxBytes;
55
+ this.masked = masked;
56
+ }
57
+ push(chunk) {
58
+ this.buffer = this.buffer.length ? Buffer.concat([this.buffer, chunk]) : chunk;
59
+ const frames = [];
60
+ for (let frame = this.next(); frame; frame = this.next())
61
+ frames.push(frame);
62
+ return frames;
63
+ }
64
+ next() {
65
+ const b = this.buffer;
66
+ if (b.length < 2)
67
+ return null;
68
+ const fin = (b[0] & 0x80) !== 0;
69
+ if (b[0] & 0x70)
70
+ throw new Error('DevTools: a frame with reserved bits set');
71
+ const opcode = b[0] & 0x0f;
72
+ if (![0, 1, 2, 8, 9, 10].includes(opcode))
73
+ throw new Error('DevTools: an unknown frame type (' + opcode + ')');
74
+ const isMasked = (b[1] & 0x80) !== 0;
75
+ if (isMasked !== this.masked)
76
+ throw new Error('DevTools: a frame masked the wrong way');
77
+ let len = b[1] & 0x7f;
78
+ let at = 2;
79
+ if (len === 126) {
80
+ if (b.length < 4)
81
+ return null;
82
+ len = b.readUInt16BE(2);
83
+ at = 4;
84
+ }
85
+ else if (len === 127) {
86
+ if (b.length < 10)
87
+ return null;
88
+ if (b.readUInt32BE(2) !== 0)
89
+ throw new Error('DevTools: a frame bigger than ' + this.maxBytes + ' bytes');
90
+ len = b.readUInt32BE(6);
91
+ at = 10;
92
+ }
93
+ if (opcode >= 8 && (len > 125 || !fin))
94
+ throw new Error('DevTools: a control frame that is long or split');
95
+ if (len > this.maxBytes)
96
+ throw new Error('DevTools: a frame bigger than ' + this.maxBytes + ' bytes');
97
+ const maskAt = at;
98
+ if (isMasked)
99
+ at += 4;
100
+ if (b.length < at + len)
101
+ return null;
102
+ let payload = Buffer.from(b.subarray(at, at + len));
103
+ if (isMasked) {
104
+ const mask = b.subarray(maskAt, maskAt + 4);
105
+ payload = Buffer.from(payload.map((byte, i) => byte ^ mask[i & 3]));
106
+ }
107
+ this.buffer = b.subarray(at + len);
108
+ return { fin, opcode, payload };
109
+ }
110
+ }
111
+ /** The Sec-WebSocket-Accept a server must answer to `key` (RFC 6455). */
112
+ export function acceptFor(key) {
113
+ return crypto
114
+ .createHash('sha1')
115
+ .update(key + WS_GUID)
116
+ .digest('base64');
117
+ }
118
+ /** Parses the server's handshake answer: the bytes after it, or throws when it is not a switch to WebSocket. */
119
+ export function readHandshake(head, key) {
120
+ const end = head.indexOf('\r\n\r\n');
121
+ if (end < 0) {
122
+ if (head.length > MAX_HEADER_BYTES)
123
+ throw new Error('DevTools: the handshake answer is too long');
124
+ return null;
125
+ }
126
+ const lines = head.subarray(0, end).toString('latin1').split('\r\n');
127
+ if (!/^HTTP\/1\.1 101\b/.test(lines[0] ?? ''))
128
+ throw new Error('DevTools: refused the connection (' + (lines[0] ?? '') + ')');
129
+ const accept = lines
130
+ .slice(1)
131
+ .map((l) => /^sec-websocket-accept:\s*(\S+)\s*$/i.exec(l)?.[1])
132
+ .find((v) => v !== undefined);
133
+ if (accept !== acceptFor(key))
134
+ throw new Error('DevTools: the handshake answer does not match');
135
+ return { rest: head.subarray(end + 4) };
136
+ }
137
+ /** One connection to a browser's DevTools: call() sends a command and resolves with its result. */
138
+ export class CdpConnection {
139
+ socket;
140
+ nextId = 1;
141
+ pending = new Map();
142
+ reader = new FrameReader();
143
+ fragments = [];
144
+ fragmentBytes = 0;
145
+ closed = false;
146
+ constructor(socket) {
147
+ this.socket = socket;
148
+ }
149
+ /** Connects to the endpoint on 127.0.0.1 (never another host). */
150
+ static open(endpoint, timeoutMs = DEFAULT_TIMEOUT_MS) {
151
+ const socket = net.connect({ host: '127.0.0.1', port: endpoint.port });
152
+ return CdpConnection.over(socket, endpoint, timeoutMs);
153
+ }
154
+ /** The handshake over a socket that is connecting or connected (the self-test gives a fake one). */
155
+ static over(socket, endpoint, timeoutMs = DEFAULT_TIMEOUT_MS) {
156
+ const key = crypto.randomBytes(16).toString('base64');
157
+ return new Promise((resolve, reject) => {
158
+ let head = Buffer.alloc(0);
159
+ const timer = setTimeout(() => fail(new Error('DevTools: no answer within ' + timeoutMs + ' ms')), timeoutMs);
160
+ const cleanup = () => {
161
+ clearTimeout(timer);
162
+ socket.off('data', onData);
163
+ socket.off('error', fail);
164
+ socket.off('close', onClose);
165
+ };
166
+ const fail = (e) => {
167
+ cleanup();
168
+ socket.destroy();
169
+ reject(e);
170
+ };
171
+ const onClose = () => fail(new Error('DevTools: the connection closed during the handshake'));
172
+ const onData = (chunk) => {
173
+ try {
174
+ head = Buffer.concat([head, chunk]);
175
+ const done = readHandshake(head, key);
176
+ if (!done)
177
+ return;
178
+ cleanup();
179
+ const connection = new CdpConnection(socket);
180
+ connection.listen();
181
+ if (done.rest.length)
182
+ connection.onBytes(done.rest);
183
+ resolve(connection);
184
+ }
185
+ catch (e) {
186
+ fail(e instanceof Error ? e : new Error(String(e)));
187
+ }
188
+ };
189
+ socket.on('data', onData);
190
+ socket.on('error', fail);
191
+ socket.on('close', onClose);
192
+ socket.write('GET ' +
193
+ endpoint.path +
194
+ ' HTTP/1.1\r\nHost: 127.0.0.1:' +
195
+ endpoint.port +
196
+ '\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: ' +
197
+ key +
198
+ '\r\nSec-WebSocket-Version: 13\r\n\r\n');
199
+ });
200
+ }
201
+ listen() {
202
+ this.socket.on('data', (chunk) => this.onBytes(chunk));
203
+ this.socket.on('error', (e) => this.shut(e));
204
+ this.socket.on('close', () => this.shut(new Error('DevTools: the connection closed')));
205
+ }
206
+ onBytes(chunk) {
207
+ try {
208
+ for (const frame of this.reader.push(chunk))
209
+ this.onFrame(frame);
210
+ }
211
+ catch (e) {
212
+ this.shut(e instanceof Error ? e : new Error(String(e)));
213
+ }
214
+ }
215
+ onFrame(frame) {
216
+ if (frame.opcode === OPCODE.ping) {
217
+ this.send(OPCODE.pong, frame.payload);
218
+ return;
219
+ }
220
+ if (frame.opcode === OPCODE.pong)
221
+ return;
222
+ if (frame.opcode === OPCODE.close) {
223
+ this.shut(new Error('DevTools: the browser closed the connection'));
224
+ return;
225
+ }
226
+ if (frame.opcode !== OPCODE.continuation && this.fragments.length)
227
+ throw new Error('DevTools: a new message before the last one ended');
228
+ if (frame.opcode === OPCODE.continuation && !this.fragments.length)
229
+ throw new Error('DevTools: a continuation without a message');
230
+ this.fragmentBytes += frame.payload.length;
231
+ if (this.fragmentBytes > MAX_MESSAGE_BYTES)
232
+ throw new Error('DevTools: a message bigger than ' + MAX_MESSAGE_BYTES);
233
+ this.fragments.push(frame.payload);
234
+ if (!frame.fin)
235
+ return;
236
+ const text = Buffer.concat(this.fragments).toString('utf8');
237
+ this.fragments = [];
238
+ this.fragmentBytes = 0;
239
+ this.onMessage(text);
240
+ }
241
+ onMessage(text) {
242
+ let message;
243
+ try {
244
+ message = JSON.parse(text);
245
+ }
246
+ catch {
247
+ return; // Not JSON: nothing waits for it.
248
+ }
249
+ if (typeof message !== 'object' || message === null)
250
+ return;
251
+ const m = message;
252
+ if (typeof m.id !== 'number')
253
+ return; // An event: not asked for.
254
+ const waiting = this.pending.get(m.id);
255
+ if (!waiting)
256
+ return;
257
+ this.pending.delete(m.id);
258
+ clearTimeout(waiting.timer);
259
+ if (m.error)
260
+ waiting.reject(new Error('DevTools: ' + String(m.error.message ?? 'the call failed').slice(0, 300)));
261
+ else
262
+ waiting.resolve(m.result ?? {});
263
+ }
264
+ send(opcode, payload) {
265
+ if (!this.closed)
266
+ this.socket.write(encodeFrame(opcode, payload));
267
+ }
268
+ shut(error) {
269
+ if (this.closed)
270
+ return;
271
+ this.closed = true;
272
+ for (const [, p] of this.pending) {
273
+ clearTimeout(p.timer);
274
+ p.reject(error);
275
+ }
276
+ this.pending.clear();
277
+ this.socket.destroy();
278
+ }
279
+ /** Sends a command (to a page's session in flat mode when `sessionId` is given) and waits for its result. */
280
+ call(method, params = {}, o = {}) {
281
+ if (this.closed)
282
+ return Promise.reject(new Error('DevTools: the connection is closed'));
283
+ const id = this.nextId++;
284
+ return new Promise((resolve, reject) => {
285
+ const timer = setTimeout(() => {
286
+ this.pending.delete(id);
287
+ reject(new Error('DevTools: ' + method + ' did not answer'));
288
+ }, o.timeoutMs ?? DEFAULT_TIMEOUT_MS);
289
+ this.pending.set(id, { resolve: resolve, reject, timer });
290
+ const body = { id, method, params, ...(o.sessionId ? { sessionId: o.sessionId } : {}) };
291
+ this.send(OPCODE.text, Buffer.from(JSON.stringify(body), 'utf8'));
292
+ });
293
+ }
294
+ /** Closes the connection (the browser keeps running). */
295
+ close() {
296
+ if (this.closed)
297
+ return;
298
+ try {
299
+ this.socket.write(encodeFrame(OPCODE.close, Buffer.alloc(0)));
300
+ }
301
+ catch {
302
+ // Already gone: nothing to tell the browser.
303
+ }
304
+ this.shut(new Error('DevTools: the connection is closed'));
305
+ }
306
+ }