windows-development-sandbox 1.5.0 → 1.7.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 (448) hide show
  1. package/README.md +1 -0
  2. package/config.example.json +17 -1
  3. package/dist/cli/args.d.ts +13 -2
  4. package/dist/cli/args.js +95 -24
  5. package/dist/cli/commands/accounts.js +45 -17
  6. package/dist/cli/commands/context.d.ts +2 -0
  7. package/dist/cli/commands/context.js +9 -2
  8. package/dist/cli/commands/data/clean.js +32 -9
  9. package/dist/cli/commands/index.d.ts +1 -0
  10. package/dist/cli/commands/index.js +1 -0
  11. package/dist/cli/commands/launch/add-root.d.ts +3 -2
  12. package/dist/cli/commands/launch/add-root.js +11 -10
  13. package/dist/cli/commands/launch/launch.js +57 -4
  14. package/dist/cli/commands/launch/prepare.js +3 -1
  15. package/dist/cli/commands/maintenance/audit.js +1 -1
  16. package/dist/cli/commands/maintenance/clean.d.ts +1 -1
  17. package/dist/cli/commands/maintenance/clean.js +17 -3
  18. package/dist/cli/commands/maintenance/config.d.ts +1 -1
  19. package/dist/cli/commands/maintenance/config.js +112 -10
  20. package/dist/cli/commands/maintenance/doctor.d.ts +1 -1
  21. package/dist/cli/commands/maintenance/doctor.js +2 -4
  22. package/dist/cli/commands/maintenance/migrate.d.ts +2 -1
  23. package/dist/cli/commands/maintenance/migrate.js +19 -4
  24. package/dist/cli/commands/maintenance/sync.js +2 -0
  25. package/dist/cli/commands/maintenance/update.d.ts +2 -0
  26. package/dist/cli/commands/maintenance/update.js +27 -4
  27. package/dist/cli/commands/projects/here.js +12 -7
  28. package/dist/cli/commands/projects/projects.js +26 -17
  29. package/dist/cli/commands/registry.js +7 -0
  30. package/dist/cli/commands/repair/checks/agents.d.ts +2 -0
  31. package/dist/cli/commands/repair/checks/agents.js +70 -0
  32. package/dist/cli/commands/repair/checks/common.d.ts +8 -0
  33. package/dist/cli/commands/repair/checks/common.js +22 -0
  34. package/dist/cli/commands/repair/checks/data.d.ts +2 -0
  35. package/dist/cli/commands/repair/checks/data.js +241 -0
  36. package/dist/cli/commands/repair/checks/index.d.ts +2 -0
  37. package/dist/cli/commands/repair/checks/index.js +20 -0
  38. package/dist/cli/commands/repair/checks/install.d.ts +5 -0
  39. package/dist/cli/commands/repair/checks/install.js +318 -0
  40. package/dist/cli/commands/repair/checks/machine.d.ts +2 -0
  41. package/dist/cli/commands/repair/checks/machine.js +232 -0
  42. package/dist/cli/commands/repair/checks/network.d.ts +6 -0
  43. package/dist/cli/commands/repair/checks/network.js +60 -0
  44. package/dist/cli/commands/repair/checks/pc.d.ts +4 -0
  45. package/dist/cli/commands/repair/checks/pc.js +55 -0
  46. package/dist/cli/commands/repair/checks/sandbox.d.ts +2 -0
  47. package/dist/cli/commands/repair/checks/sandbox.js +267 -0
  48. package/dist/cli/commands/repair/checks/settings.d.ts +2 -0
  49. package/dist/cli/commands/repair/checks/settings.js +169 -0
  50. package/dist/cli/commands/repair/checks/vpn.d.ts +2 -0
  51. package/dist/cli/commands/repair/checks/vpn.js +53 -0
  52. package/dist/cli/commands/repair/engine.d.ts +32 -0
  53. package/dist/cli/commands/repair/engine.js +127 -0
  54. package/dist/cli/commands/repair/env.d.ts +7 -0
  55. package/dist/cli/commands/repair/env.js +49 -0
  56. package/dist/cli/commands/repair/index.d.ts +1 -9
  57. package/dist/cli/commands/repair/index.js +2 -54
  58. package/dist/cli/commands/repair/log.d.ts +17 -0
  59. package/dist/cli/commands/repair/log.js +82 -0
  60. package/dist/cli/commands/repair/printer.d.ts +32 -0
  61. package/dist/cli/commands/repair/printer.js +132 -0
  62. package/dist/cli/commands/repair/run.d.ts +33 -0
  63. package/dist/cli/commands/repair/run.js +133 -0
  64. package/dist/cli/commands/repair/types.d.ts +97 -9
  65. package/dist/cli/commands/repair/types.js +11 -7
  66. package/dist/cli/commands/resume.js +2 -2
  67. package/dist/cli/commands/sessions/list.js +5 -2
  68. package/dist/cli/commands/sessions/status.d.ts +2 -0
  69. package/dist/cli/commands/sessions/status.js +27 -6
  70. package/dist/cli/commands/setup/index.d.ts +5 -0
  71. package/dist/cli/commands/setup/index.js +25 -1
  72. package/dist/cli/commands/storage/index.d.ts +2 -0
  73. package/dist/cli/commands/storage/index.js +2 -0
  74. package/dist/cli/commands/storage/offer.d.ts +3 -0
  75. package/dist/cli/commands/storage/offer.js +85 -0
  76. package/dist/cli/commands/storage/storage.d.ts +8 -0
  77. package/dist/cli/commands/storage/storage.js +171 -0
  78. package/dist/cli/commands/user-config.js +1 -1
  79. package/dist/cli/commands/vpn.d.ts +4 -0
  80. package/dist/cli/commands/vpn.js +75 -0
  81. package/dist/cli/commands/web.d.ts +4 -0
  82. package/dist/cli/commands/web.js +95 -0
  83. package/dist/cli/history.js +1 -1
  84. package/dist/cli/index.js +46 -3
  85. package/dist/cli/manifest.d.ts +303 -29
  86. package/dist/cli/manifest.js +173 -43
  87. package/dist/cli/menu/add-agent.d.ts +47 -0
  88. package/dist/cli/menu/add-agent.js +168 -0
  89. package/dist/cli/menu/apps.d.ts +31 -2
  90. package/dist/cli/menu/apps.js +44 -5
  91. package/dist/cli/menu/command-fields.d.ts +18 -0
  92. package/dist/cli/menu/command-fields.js +61 -0
  93. package/dist/cli/menu/command-form.d.ts +22 -0
  94. package/dist/cli/menu/command-form.js +169 -46
  95. package/dist/cli/menu/data-folder.d.ts +18 -0
  96. package/dist/cli/menu/data-folder.js +99 -0
  97. package/dist/cli/menu/menu.d.ts +1 -1
  98. package/dist/cli/menu/menu.js +11 -8
  99. package/dist/cli/menu/pages/accounts-page.js +55 -24
  100. package/dist/cli/menu/pages/agents-page.d.ts +66 -0
  101. package/dist/cli/menu/pages/agents-page.js +338 -0
  102. package/dist/cli/menu/pages/apps-page.js +1 -2
  103. package/dist/cli/menu/pages/list-page.d.ts +16 -3
  104. package/dist/cli/menu/pages/list-page.js +40 -8
  105. package/dist/cli/menu/pages/maintenance-page.d.ts +9 -0
  106. package/dist/cli/menu/pages/maintenance-page.js +161 -72
  107. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
  108. package/dist/cli/menu/pages/sandbox-page.js +132 -43
  109. package/dist/cli/menu/pages/sessions-page.d.ts +4 -0
  110. package/dist/cli/menu/pages/sessions-page.js +41 -25
  111. package/dist/cli/menu/pages/settings-page.d.ts +8 -4
  112. package/dist/cli/menu/pages/settings-page.js +36 -13
  113. package/dist/cli/menu/settings/categories.d.ts +2 -0
  114. package/dist/cli/menu/settings/categories.js +19 -44
  115. package/dist/cli/menu/settings/collections/collection-editor.d.ts +7 -1
  116. package/dist/cli/menu/settings/collections/collection-editor.js +12 -8
  117. package/dist/cli/menu/settings/collections/map-editor.js +5 -1
  118. package/dist/cli/menu/settings/collections/object-list-editor.js +2 -2
  119. package/dist/cli/menu/settings/collections/object-map-editor.js +2 -2
  120. package/dist/cli/menu/settings/collections/string-list-editor.d.ts +11 -1
  121. package/dist/cli/menu/settings/collections/string-list-editor.js +23 -0
  122. package/dist/cli/menu/settings/index.d.ts +1 -1
  123. package/dist/cli/menu/settings/index.js +1 -1
  124. package/dist/cli/menu/settings/object-editor.d.ts +5 -1
  125. package/dist/cli/menu/settings/object-editor.js +15 -4
  126. package/dist/cli/menu/settings/path-kind.d.ts +20 -0
  127. package/dist/cli/menu/settings/path-kind.js +44 -0
  128. package/dist/cli/menu/settings/schema-node.d.ts +9 -0
  129. package/dist/cli/menu/settings/schema-node.js +26 -0
  130. package/dist/cli/menu/settings/value-editor.d.ts +5 -0
  131. package/dist/cli/menu/settings/value-editor.js +51 -18
  132. package/dist/cli/menu/several.js +7 -5
  133. package/dist/cli/menu/shell.d.ts +37 -10
  134. package/dist/cli/menu/shell.js +90 -10
  135. package/dist/cli/menu/vpn-login.d.ts +16 -0
  136. package/dist/cli/menu/vpn-login.js +66 -0
  137. package/dist/cli/output/print.d.ts +2 -0
  138. package/dist/cli/output/print.js +8 -1
  139. package/dist/cli/services/accounts-service.d.ts +2 -0
  140. package/dist/cli/services/accounts-service.js +2 -2
  141. package/dist/cli/services/health-service.d.ts +0 -6
  142. package/dist/cli/services/health-service.js +5 -42
  143. package/dist/cli/services/index.d.ts +3 -1
  144. package/dist/cli/services/index.js +3 -1
  145. package/dist/cli/services/migrate-service.js +1 -1
  146. package/dist/cli/services/update-service.d.ts +2 -0
  147. package/dist/cli/services/update-service.js +13 -6
  148. package/dist/cli/services/vpn-service.d.ts +28 -0
  149. package/dist/cli/services/vpn-service.js +46 -0
  150. package/dist/cli/services/web-service.d.ts +22 -0
  151. package/dist/cli/services/web-service.js +111 -0
  152. package/dist/cli/tui/app.d.ts +6 -3
  153. package/dist/cli/tui/app.js +12 -5
  154. package/dist/cli/tui/dialogs/path-listing.d.ts +43 -0
  155. package/dist/cli/tui/dialogs/path-listing.js +127 -0
  156. package/dist/cli/tui/dialogs/path-picker.d.ts +67 -0
  157. package/dist/cli/tui/dialogs/path-picker.js +423 -0
  158. package/dist/cli/tui/dialogs/text-screen.d.ts +6 -0
  159. package/dist/cli/tui/dialogs/text-screen.js +10 -3
  160. package/dist/cli/tui/frame.d.ts +7 -10
  161. package/dist/cli/tui/frame.js +19 -10
  162. package/dist/cli/tui/index.d.ts +4 -3
  163. package/dist/cli/tui/index.js +2 -1
  164. package/dist/cli/tui/list/list-screen.d.ts +14 -2
  165. package/dist/cli/tui/list/list-screen.js +68 -32
  166. package/dist/cli/tui/list/list-types.d.ts +8 -4
  167. package/dist/cli/tui/nav.d.ts +29 -0
  168. package/dist/cli/tui/nav.js +67 -0
  169. package/dist/cli/validate.d.ts +1 -1
  170. package/dist/cli/validate.js +3 -3
  171. package/dist/cli/version.d.ts +2 -0
  172. package/dist/cli/version.js +11 -0
  173. package/dist/core/config/defaults.js +5 -1
  174. package/dist/core/config/loader.js +12 -2
  175. package/dist/core/local-config/overrides.js +1 -0
  176. package/dist/core/prompt.d.ts +19 -0
  177. package/dist/core/prompt.js +43 -3
  178. package/dist/core/schema/common.d.ts +6 -0
  179. package/dist/core/schema/common.js +8 -0
  180. package/dist/core/schema/config.d.ts +23 -0
  181. package/dist/core/schema/config.js +10 -1
  182. package/dist/core/schema/harness.d.ts +1 -0
  183. package/dist/core/schema/harness.js +8 -2
  184. package/dist/core/schema/index.d.ts +3 -1
  185. package/dist/core/schema/index.js +3 -1
  186. package/dist/core/schema/profile.d.ts +1 -0
  187. package/dist/core/schema/profile.js +3 -0
  188. package/dist/core/schema/redact.d.ts +13 -0
  189. package/dist/core/schema/redact.js +75 -0
  190. package/dist/core/schema/sandbox.js +1 -0
  191. package/dist/core/schema/vpn.d.ts +17 -0
  192. package/dist/core/schema/vpn.js +22 -0
  193. package/dist/core/schema/web.d.ts +6 -0
  194. package/dist/core/schema/web.js +14 -0
  195. package/dist/core/types.d.ts +26 -0
  196. package/dist/guest/agents/agent-files.d.ts +49 -0
  197. package/dist/guest/agents/agent-files.js +63 -0
  198. package/dist/guest/agents/agent-installer.js +2 -9
  199. package/dist/guest/agents/local-state.d.ts +12 -0
  200. package/dist/guest/agents/local-state.js +198 -0
  201. package/dist/guest/agents/sbx-agent.d.ts +2 -0
  202. package/dist/guest/agents/sbx-agent.js +35 -0
  203. package/dist/guest/boot/boot.d.ts +4 -0
  204. package/dist/guest/boot/boot.js +50 -11
  205. package/dist/guest/daemon/daemon.js +4 -1
  206. package/dist/guest/daemon/exec.js +2 -1
  207. package/dist/guest/daemon/idle-shutdown.js +2 -0
  208. package/dist/guest/daemon/sessions.js +4 -1
  209. package/dist/guest/entries/agent.d.ts +1 -0
  210. package/dist/guest/entries/agent.js +10 -0
  211. package/dist/guest/entries/daemon.js +2 -0
  212. package/dist/guest/runtime/csc.d.ts +1 -0
  213. package/dist/guest/runtime/csc.js +1 -0
  214. package/dist/guest/runtime/secrets.d.ts +12 -0
  215. package/dist/guest/runtime/secrets.js +63 -0
  216. package/dist/guest/setup/claude-profile.d.ts +2 -1
  217. package/dist/guest/setup/claude-profile.js +12 -1
  218. package/dist/guest/setup/environment.d.ts +6 -0
  219. package/dist/guest/setup/environment.js +56 -6
  220. package/dist/guest/setup/path-plan.d.ts +32 -0
  221. package/dist/guest/setup/path-plan.js +67 -0
  222. package/dist/guest/setup/shell-path.d.ts +1 -0
  223. package/dist/guest/setup/shell-path.js +22 -0
  224. package/dist/guest/toolchains/languages/python.js +1 -1
  225. package/dist/guest/toolchains/runtimes.js +2 -2
  226. package/dist/guest/vpn/index.d.ts +1 -0
  227. package/dist/guest/vpn/index.js +1 -0
  228. package/dist/guest/vpn/openvpn.d.ts +9 -0
  229. package/dist/guest/vpn/openvpn.js +47 -0
  230. package/dist/guest/vpn/tunnel.d.ts +17 -0
  231. package/dist/guest/vpn/tunnel.js +78 -0
  232. package/dist/guest/vpn/vpn.d.ts +6 -0
  233. package/dist/guest/vpn/vpn.js +126 -0
  234. package/dist/host/channel/attach.d.ts +3 -1
  235. package/dist/host/channel/attach.js +1 -1
  236. package/dist/host/channel/channel.d.ts +7 -0
  237. package/dist/host/channel/channel.js +26 -2
  238. package/dist/host/health/acl-read.d.ts +17 -0
  239. package/dist/host/health/acl-read.js +45 -0
  240. package/dist/host/health/config-backup.d.ts +11 -0
  241. package/dist/host/health/config-backup.js +73 -0
  242. package/dist/host/health/index.d.ts +6 -0
  243. package/dist/host/health/index.js +7 -0
  244. package/dist/host/health/install-state.d.ts +33 -0
  245. package/dist/host/health/install-state.js +110 -0
  246. package/dist/host/health/path-copies.d.ts +32 -0
  247. package/dist/host/health/path-copies.js +82 -0
  248. package/dist/host/health/stale.d.ts +45 -0
  249. package/dist/host/health/stale.js +120 -0
  250. package/dist/host/health/system.d.ts +37 -0
  251. package/dist/host/health/system.js +87 -0
  252. package/dist/host/install/copies.d.ts +13 -0
  253. package/dist/host/install/copies.js +63 -0
  254. package/dist/host/install/index.d.ts +1 -0
  255. package/dist/host/install/index.js +1 -0
  256. package/dist/host/launch/generate.js +25 -1
  257. package/dist/host/launch/session-file.js +4 -1
  258. package/dist/host/launch/stage.js +1 -1
  259. package/dist/host/migrate/backup.js +3 -0
  260. package/dist/host/migrate/migrations/config-file.d.ts +2 -0
  261. package/dist/host/migrate/migrations/config-file.js +7 -0
  262. package/dist/host/migrate/migrations/follow-machine-storage.js +9 -1
  263. package/dist/host/migrate/migrations/pin-storage-root.d.ts +4 -0
  264. package/dist/host/migrate/migrations/pin-storage-root.js +46 -0
  265. package/dist/host/migrate/migrations/unpin-config-defaults.js +4 -1
  266. package/dist/host/migrate/registry.js +2 -1
  267. package/dist/host/profile/accounts.d.ts +25 -1
  268. package/dist/host/profile/accounts.js +96 -13
  269. package/dist/host/profile/agent-accounts.d.ts +5 -1
  270. package/dist/host/profile/agent-accounts.js +35 -5
  271. package/dist/host/profile/index.d.ts +2 -1
  272. package/dist/host/profile/index.js +1 -0
  273. package/dist/host/profile/plugin-share.d.ts +26 -0
  274. package/dist/host/profile/plugin-share.js +146 -0
  275. package/dist/host/projects/join.d.ts +7 -1
  276. package/dist/host/projects/join.js +21 -4
  277. package/dist/host/projects/registry.d.ts +16 -10
  278. package/dist/host/projects/registry.js +74 -6
  279. package/dist/host/sandbox/platform.d.ts +2 -0
  280. package/dist/host/sandbox/platform.js +21 -2
  281. package/dist/host/secrets/index.d.ts +2 -0
  282. package/dist/host/secrets/index.js +2 -0
  283. package/dist/host/secrets/launch-secrets.d.ts +17 -0
  284. package/dist/host/secrets/launch-secrets.js +93 -0
  285. package/dist/host/secrets/secret-store.d.ts +25 -0
  286. package/dist/host/secrets/secret-store.js +89 -0
  287. package/dist/host/sharing/acl.d.ts +16 -2
  288. package/dist/host/sharing/acl.js +35 -10
  289. package/dist/host/sharing/current.js +11 -2
  290. package/dist/host/sharing/doctor.d.ts +2 -0
  291. package/dist/host/sharing/doctor.js +44 -0
  292. package/dist/host/sharing/group-access.d.ts +6 -0
  293. package/dist/host/sharing/group-access.js +24 -0
  294. package/dist/host/sharing/index.d.ts +2 -1
  295. package/dist/host/sharing/index.js +2 -1
  296. package/dist/host/sharing/lifecycle.d.ts +8 -2
  297. package/dist/host/sharing/lifecycle.js +16 -7
  298. package/dist/host/storage/cleanup.d.ts +2 -0
  299. package/dist/host/storage/cleanup.js +2 -1
  300. package/dist/host/storage/history.d.ts +21 -0
  301. package/dist/host/storage/history.js +49 -0
  302. package/dist/host/storage/index.d.ts +4 -0
  303. package/dist/host/storage/index.js +4 -0
  304. package/dist/host/storage/move.d.ts +95 -0
  305. package/dist/host/storage/move.js +384 -0
  306. package/dist/host/storage/offer.d.ts +11 -0
  307. package/dist/host/storage/offer.js +36 -0
  308. package/dist/host/storage/root.d.ts +18 -0
  309. package/dist/host/storage/root.js +77 -0
  310. package/dist/host/tools/windows-terminal.d.ts +2 -0
  311. package/dist/host/tools/windows-terminal.js +23 -5
  312. package/dist/host/update/index.d.ts +1 -1
  313. package/dist/host/update/index.js +1 -1
  314. package/dist/host/update/notice.d.ts +2 -0
  315. package/dist/host/update/notice.js +13 -1
  316. package/dist/host/update/update-state.d.ts +6 -0
  317. package/dist/host/update/update-state.js +2 -0
  318. package/dist/host/update/updater.d.ts +19 -0
  319. package/dist/host/update/updater.js +88 -12
  320. package/dist/host/vpn/index.d.ts +4 -0
  321. package/dist/host/vpn/index.js +4 -0
  322. package/dist/host/vpn/login.d.ts +24 -0
  323. package/dist/host/vpn/login.js +79 -0
  324. package/dist/host/vpn/nordvpn.d.ts +21 -0
  325. package/dist/host/vpn/nordvpn.js +77 -0
  326. package/dist/host/vpn/prepare.d.ts +17 -0
  327. package/dist/host/vpn/prepare.js +77 -0
  328. package/dist/host/vpn/status.d.ts +12 -0
  329. package/dist/host/vpn/status.js +53 -0
  330. package/dist/host/web/auth.d.ts +38 -0
  331. package/dist/host/web/auth.js +96 -0
  332. package/dist/host/web/backend.d.ts +14 -0
  333. package/dist/host/web/backend.js +1 -0
  334. package/dist/host/web/bridge.d.ts +27 -0
  335. package/dist/host/web/bridge.js +144 -0
  336. package/dist/host/web/browser.d.ts +1 -0
  337. package/dist/host/web/browser.js +10 -0
  338. package/dist/host/web/control.d.ts +17 -0
  339. package/dist/host/web/control.js +67 -0
  340. package/dist/host/web/index.d.ts +9 -0
  341. package/dist/host/web/index.js +8 -0
  342. package/dist/host/web/runner.d.ts +14 -0
  343. package/dist/host/web/runner.js +45 -0
  344. package/dist/host/web/server.d.ts +40 -0
  345. package/dist/host/web/server.js +218 -0
  346. package/dist/host/web/state.d.ts +20 -0
  347. package/dist/host/web/state.js +41 -0
  348. package/dist/host/web/static-files.d.ts +13 -0
  349. package/dist/host/web/static-files.js +57 -0
  350. package/dist/runtime/agent.js +27614 -0
  351. package/dist/runtime/daemon.js +1012 -618
  352. package/dist/runtime/db.js +236 -106
  353. package/dist/runtime/main.js +1093 -250
  354. package/dist/runtime/netproxy.js +102 -3
  355. package/dist/runtime/view.js +102 -3
  356. package/dist/selftest/accounts.test.js +40 -0
  357. package/dist/selftest/agent-paths.test.d.ts +2 -0
  358. package/dist/selftest/agent-paths.test.js +115 -0
  359. package/dist/selftest/channel.test.js +5 -0
  360. package/dist/selftest/cli.test.js +34 -4
  361. package/dist/selftest/credentials.test.d.ts +2 -0
  362. package/dist/selftest/credentials.test.js +198 -0
  363. package/dist/selftest/generate.test.js +1 -1
  364. package/dist/selftest/health.test.d.ts +2 -0
  365. package/dist/selftest/health.test.js +470 -0
  366. package/dist/selftest/host-sim.test.js +2 -0
  367. package/dist/selftest/index.js +19 -0
  368. package/dist/selftest/layout.test.js +2 -0
  369. package/dist/selftest/menu-screens.d.ts +7 -0
  370. package/dist/selftest/menu-screens.js +65 -16
  371. package/dist/selftest/menu.test.js +574 -21
  372. package/dist/selftest/migrate.test.js +1 -1
  373. package/dist/selftest/personalization.test.js +3 -1
  374. package/dist/selftest/projects.test.js +39 -1
  375. package/dist/selftest/shared-use.test.js +64 -2
  376. package/dist/selftest/sharing.test.js +50 -1
  377. package/dist/selftest/storage.test.d.ts +2 -0
  378. package/dist/selftest/storage.test.js +363 -0
  379. package/dist/selftest/support/fixtures.d.ts +14 -0
  380. package/dist/selftest/support/releases.js +1 -1
  381. package/dist/selftest/toolchains.test.js +21 -0
  382. package/dist/selftest/update-service.test.js +87 -3
  383. package/dist/selftest/vpn.test.d.ts +2 -0
  384. package/dist/selftest/vpn.test.js +279 -0
  385. package/dist/selftest/web.test.d.ts +2 -0
  386. package/dist/selftest/web.test.js +304 -0
  387. package/dist/shared/apps/catalogue.js +31 -0
  388. package/dist/shared/channel/index.d.ts +1 -0
  389. package/dist/shared/channel/index.js +1 -0
  390. package/dist/shared/channel/run-dir.d.ts +6 -1
  391. package/dist/shared/channel/run-dir.js +12 -1
  392. package/dist/shared/channel/schema.d.ts +19 -0
  393. package/dist/shared/channel/schema.js +11 -0
  394. package/dist/shared/channel/secrets.d.ts +28 -0
  395. package/dist/shared/channel/secrets.js +44 -0
  396. package/dist/shared/vpn/download.d.ts +13 -0
  397. package/dist/shared/vpn/download.js +14 -0
  398. package/dist/shared/vpn/kill-switch.d.ts +18 -0
  399. package/dist/shared/vpn/kill-switch.js +81 -0
  400. package/dist/shared/vpn/ovpn.d.ts +23 -0
  401. package/dist/shared/vpn/ovpn.js +244 -0
  402. package/dist/shared/web/index.d.ts +1 -0
  403. package/dist/shared/web/index.js +1 -0
  404. package/dist/shared/web/protocol.d.ts +61 -0
  405. package/dist/shared/web/protocol.js +35 -0
  406. package/dist/sync/sync-cycle.js +5 -1
  407. package/dist/sync/sync-state.d.ts +1 -0
  408. package/dist/sync/sync-state.js +1 -0
  409. package/dist/sync/watcher.js +10 -0
  410. package/dist/web/index.html +13 -0
  411. package/dist/web/main-CXESL347.js +45 -0
  412. package/dist/web/main-VLR7DVQ4.css +1 -0
  413. package/native/csharp/FolderSecurity.cs +29 -0
  414. package/native/scripts/protect-data.ps1 +29 -0
  415. package/native/scripts/read-folder-sddl.ps1 +10 -0
  416. package/native/scripts/set-folder-access.ps1 +49 -7
  417. package/node_modules/ws/LICENSE +20 -0
  418. package/node_modules/ws/README.md +548 -0
  419. package/node_modules/ws/browser.js +8 -0
  420. package/node_modules/ws/index.js +22 -0
  421. package/node_modules/ws/lib/buffer-util.js +131 -0
  422. package/node_modules/ws/lib/constants.js +19 -0
  423. package/node_modules/ws/lib/event-target.js +292 -0
  424. package/node_modules/ws/lib/extension.js +203 -0
  425. package/node_modules/ws/lib/limiter.js +55 -0
  426. package/node_modules/ws/lib/permessage-deflate.js +530 -0
  427. package/node_modules/ws/lib/receiver.js +743 -0
  428. package/node_modules/ws/lib/sender.js +607 -0
  429. package/node_modules/ws/lib/stream.js +161 -0
  430. package/node_modules/ws/lib/subprotocol.js +62 -0
  431. package/node_modules/ws/lib/validation.js +152 -0
  432. package/node_modules/ws/lib/websocket-server.js +562 -0
  433. package/node_modules/ws/lib/websocket.js +1421 -0
  434. package/node_modules/ws/package.json +74 -0
  435. package/node_modules/ws/wrapper.mjs +21 -0
  436. package/package.json +15 -5
  437. package/sandbox/csharp/EnvBroadcast.cs +33 -0
  438. package/sandbox/csharp/PathSync.cs +90 -0
  439. package/sandbox/scripts/install-shell-path.ps1 +73 -0
  440. package/sandbox/scripts/set-machine-environment.ps1 +37 -4
  441. package/schemas/config.schema.json +79 -4
  442. package/schemas/sandbox-config.schema.json +14 -1
  443. package/dist/cli/commands/repair/checks.d.ts +0 -2
  444. package/dist/cli/commands/repair/checks.js +0 -246
  445. package/dist/cli/menu/pages/start-page.d.ts +0 -43
  446. package/dist/cli/menu/pages/start-page.js +0 -192
  447. package/dist/cli/tui/tab-bar.d.ts +0 -9
  448. package/dist/cli/tui/tab-bar.js +0 -33
@@ -0,0 +1,67 @@
1
+ // Talking to a running web server from the CLI: is it up, its address (with the token fragment), and stopping it. The
2
+ // state file says where it is and holds the token, so only you (and SYSTEM / Administrators) can do any of this.
3
+ import http from 'node:http';
4
+ import { CSRF_HEADER, CSRF_VALUE } from '../../shared/web/index.js';
5
+ function request(state, method, path, body, cookie) {
6
+ return new Promise((resolve, reject) => {
7
+ const payload = body === undefined ? undefined : JSON.stringify(body);
8
+ const req = http.request({
9
+ host: '127.0.0.1',
10
+ port: state.port,
11
+ method,
12
+ path,
13
+ timeout: 3000,
14
+ headers: {
15
+ Origin: 'http://127.0.0.1:' + state.port,
16
+ [CSRF_HEADER]: CSRF_VALUE,
17
+ ...(payload ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) } : {}),
18
+ ...(cookie ? { Cookie: cookie } : {}),
19
+ },
20
+ }, (res) => {
21
+ res.resume();
22
+ resolve(res);
23
+ });
24
+ req.on('timeout', () => req.destroy(new Error('timed out')));
25
+ req.on('error', reject);
26
+ req.end(payload);
27
+ });
28
+ }
29
+ export class WebControl {
30
+ store;
31
+ constructor(store) {
32
+ this.store = store;
33
+ }
34
+ /** Running means the state file exists and its port answers as our server. */
35
+ async status() {
36
+ const state = this.store.read();
37
+ if (!state)
38
+ return { running: false, state: null };
39
+ try {
40
+ return { running: (await request(state, 'GET', '/api/health')).statusCode === 200, state };
41
+ }
42
+ catch {
43
+ return { running: false, state };
44
+ }
45
+ }
46
+ /** http://127.0.0.1:<port>/#k=<token>: the fragment never goes to the server. */
47
+ url(state) {
48
+ return 'http://127.0.0.1:' + state.port + '/#k=' + state.token;
49
+ }
50
+ /** Signs in with the token and asks the server to stop. False when it does not answer. */
51
+ async stop(state) {
52
+ try {
53
+ const login = await request(state, 'POST', '/api/login', { token: state.token });
54
+ const cookie = (login.headers['set-cookie'] ?? [])[0]?.split(';')[0];
55
+ if (login.statusCode !== 200 || !cookie)
56
+ return false;
57
+ return (await request(state, 'POST', '/api/stop', {}, cookie)).statusCode === 200;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ /** The state file of a server that is gone. */
64
+ clear() {
65
+ this.store.clear();
66
+ }
67
+ }
@@ -0,0 +1,9 @@
1
+ export { WebAuth, newToken, sameSecret, hostAllowed, originAllowed, csrfOk, cookieValue, cookieHeader, } from './auth.js';
2
+ export type { WebBackend } from './backend.js';
3
+ export { openBrowser } from './browser.js';
4
+ export { TerminalBridge } from './bridge.js';
5
+ export { WebControl, type WebStatus } from './control.js';
6
+ export { WebRunner } from './runner.js';
7
+ export { WebServer, CONTENT_SECURITY_POLICY, type WebServerOptions } from './server.js';
8
+ export { StaticFiles } from './static-files.js';
9
+ export { WebStore, WebStateSchema, type WebState } from './state.js';
@@ -0,0 +1,8 @@
1
+ export { WebAuth, newToken, sameSecret, hostAllowed, originAllowed, csrfOk, cookieValue, cookieHeader, } from './auth.js';
2
+ export { openBrowser } from './browser.js';
3
+ export { TerminalBridge } from './bridge.js';
4
+ export { WebControl } from './control.js';
5
+ export { WebRunner } from './runner.js';
6
+ export { WebServer, CONTENT_SECURITY_POLICY } from './server.js';
7
+ export { StaticFiles } from './static-files.js';
8
+ export { WebStore, WebStateSchema } from './state.js';
@@ -0,0 +1,14 @@
1
+ import type { WebBackend } from './backend.js';
2
+ export interface WebRunOptions {
3
+ readonly storageRoot: string;
4
+ readonly port: number;
5
+ readonly backend: WebBackend;
6
+ readonly staticRoot: string;
7
+ }
8
+ export declare class WebRunner {
9
+ private readonly options;
10
+ private readonly store;
11
+ constructor(options: WebRunOptions);
12
+ /** Runs until stopped (from the browser, the CLI, or a signal). */
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,45 @@
1
+ // The background process behind `sandbox web start`: starts the server, records pid, port and token in the private
2
+ // state file, and removes it again when the server stops.
3
+ import { fail } from '../../core/util/index.js';
4
+ import { newToken } from './auth.js';
5
+ import { WebServer } from './server.js';
6
+ import { WebStore } from './state.js';
7
+ export class WebRunner {
8
+ options;
9
+ store;
10
+ constructor(options) {
11
+ this.options = options;
12
+ this.store = new WebStore(options.storageRoot);
13
+ }
14
+ /** Runs until stopped (from the browser, the CLI, or a signal). */
15
+ async run() {
16
+ const token = newToken();
17
+ let stop = () => undefined;
18
+ const stopped = new Promise((resolve) => (stop = resolve));
19
+ const server = new WebServer({
20
+ port: this.options.port,
21
+ token,
22
+ backend: this.options.backend,
23
+ staticRoot: this.options.staticRoot,
24
+ onStop: stop,
25
+ });
26
+ try {
27
+ await server.start();
28
+ }
29
+ catch (e) {
30
+ if (e.code === 'EADDRINUSE')
31
+ fail('Port ' + this.options.port + ' is taken by another program.', 'Pick another with the web.port setting, then run sandbox web start again.');
32
+ throw e;
33
+ }
34
+ this.store.write({ pid: process.pid, port: server.port, token, startedAt: new Date().toISOString() });
35
+ process.once('SIGINT', stop);
36
+ process.once('SIGTERM', stop);
37
+ try {
38
+ await stopped;
39
+ }
40
+ finally {
41
+ this.store.clear();
42
+ await server.stop();
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,40 @@
1
+ import type net from 'node:net';
2
+ import type { Endpoint } from '../../shared/pty/index.js';
3
+ import type { WebBackend } from './backend.js';
4
+ /** No inline script: the page and its script are files from this server. Inline styles are what xterm.js needs. */
5
+ export declare const CONTENT_SECURITY_POLICY: string;
6
+ export interface WebServerOptions {
7
+ /** 0 picks a free port (tests). */
8
+ readonly port: number;
9
+ readonly token: string;
10
+ readonly backend: WebBackend;
11
+ /** The folder of the built app (dist/web). */
12
+ readonly staticRoot: string;
13
+ /** Called when a signed-in browser (or the CLI) asks the server to stop. */
14
+ readonly onStop?: () => void;
15
+ /** How terminals reach a session (tests use a fake on loopback). */
16
+ readonly dial?: (endpoint: Endpoint) => Promise<net.Socket>;
17
+ }
18
+ export declare class WebServer {
19
+ private readonly options;
20
+ private readonly server;
21
+ private readonly sockets;
22
+ private readonly auth;
23
+ private readonly files;
24
+ private terminals;
25
+ private actualPort;
26
+ constructor(options: WebServerOptions);
27
+ get port(): number;
28
+ start(): Promise<void>;
29
+ stop(): Promise<void>;
30
+ private headers;
31
+ private json;
32
+ private handle;
33
+ private serveFile;
34
+ /** A call that changes something: the custom header, and an Origin that is this page. */
35
+ private requireSameSite;
36
+ private api;
37
+ /** A small JSON body (application/json only). */
38
+ private body;
39
+ private upgrade;
40
+ }
@@ -0,0 +1,218 @@
1
+ // The web UI's HTTP server: the app's files, a small JSON API for sessions, and the terminal WebSocket. Loopback only;
2
+ // nothing works without the token's cookie, and Host / Origin / CSRF checks keep other sites out (docs/web-ui.md).
3
+ import fs from 'node:fs';
4
+ import http from 'node:http';
5
+ import { WebSocketServer } from 'ws';
6
+ import { UserError, safeText } from '../../core/util/index.js';
7
+ import { MAX_CLIENT_MESSAGE_BYTES, MAX_COLS, MAX_ROWS, MIN_COLS, MIN_ROWS, } from '../../shared/web/index.js';
8
+ import { cookieHeader, csrfOk, hostAllowed, originAllowed, WebAuth } from './auth.js';
9
+ import { TerminalBridge } from './bridge.js';
10
+ import { StaticFiles } from './static-files.js';
11
+ /** No inline script: the page and its script are files from this server. Inline styles are what xterm.js needs. */
12
+ export const CONTENT_SECURITY_POLICY = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; " +
13
+ "connect-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'";
14
+ const MAX_BODY_BYTES = 16 * 1024;
15
+ const MAX_TERMINALS = 16;
16
+ const SESSION_PATH = /^\/api\/sessions\/([A-Za-z0-9_-]{1,64})\/terminal$/;
17
+ class HttpFailure extends Error {
18
+ status;
19
+ constructor(status, message) {
20
+ super(message);
21
+ this.status = status;
22
+ }
23
+ }
24
+ export class WebServer {
25
+ options;
26
+ server;
27
+ sockets = new WebSocketServer({
28
+ noServer: true,
29
+ maxPayload: MAX_CLIENT_MESSAGE_BYTES,
30
+ perMessageDeflate: false,
31
+ });
32
+ auth;
33
+ files;
34
+ terminals = 0;
35
+ actualPort = 0;
36
+ constructor(options) {
37
+ this.options = options;
38
+ this.auth = new WebAuth(options.token);
39
+ this.files = new StaticFiles(options.staticRoot);
40
+ this.server = http.createServer((req, res) => void this.handle(req, res));
41
+ this.server.on('upgrade', (req, socket, head) => this.upgrade(req, socket, head));
42
+ this.server.headersTimeout = 15000;
43
+ this.server.requestTimeout = 0; // creating a session waits for the agent to start
44
+ }
45
+ get port() {
46
+ return this.actualPort;
47
+ }
48
+ start() {
49
+ return new Promise((resolve, reject) => {
50
+ this.server.once('error', reject);
51
+ this.server.listen(this.options.port, '127.0.0.1', () => {
52
+ this.server.off('error', reject);
53
+ this.actualPort = this.server.address().port;
54
+ resolve();
55
+ });
56
+ });
57
+ }
58
+ stop() {
59
+ for (const client of this.sockets.clients)
60
+ client.close(1001, 'server stopping');
61
+ this.server.closeAllConnections();
62
+ return new Promise((resolve) => this.server.close(() => resolve()));
63
+ }
64
+ headers(extra = {}) {
65
+ return {
66
+ 'Content-Security-Policy': CONTENT_SECURITY_POLICY,
67
+ 'X-Content-Type-Options': 'nosniff',
68
+ 'X-Frame-Options': 'DENY',
69
+ 'Referrer-Policy': 'no-referrer',
70
+ 'Cross-Origin-Opener-Policy': 'same-origin',
71
+ 'Cross-Origin-Resource-Policy': 'same-origin',
72
+ ...extra,
73
+ };
74
+ }
75
+ json(res, status, body, extra = {}) {
76
+ res.writeHead(status, this.headers({ 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store', ...extra }));
77
+ res.end(JSON.stringify(body));
78
+ }
79
+ async handle(req, res) {
80
+ try {
81
+ if (!hostAllowed(req.headers.host, this.actualPort))
82
+ throw new HttpFailure(421, 'Wrong host name.');
83
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
84
+ if (url.pathname.startsWith('/api/'))
85
+ await this.api(req, res, url.pathname);
86
+ else
87
+ this.serveFile(req, res, url.pathname);
88
+ }
89
+ catch (e) {
90
+ if (e instanceof HttpFailure)
91
+ this.json(res, e.status, { error: e.message });
92
+ else if (e instanceof UserError)
93
+ this.json(res, 409, { error: safeText(e.message).slice(0, 500) });
94
+ else
95
+ this.json(res, 500, { error: 'Something went wrong.' });
96
+ }
97
+ }
98
+ serveFile(req, res, pathname) {
99
+ if (req.method !== 'GET' && req.method !== 'HEAD')
100
+ throw new HttpFailure(405, 'Not allowed.');
101
+ const file = this.files.find(pathname);
102
+ if (!file)
103
+ throw new HttpFailure(404, 'Not found.');
104
+ const size = fs.statSync(file.file).size;
105
+ res.writeHead(200, this.headers({
106
+ 'Content-Type': file.type,
107
+ 'Content-Length': size,
108
+ 'Cache-Control': file.immutable ? 'public, max-age=31536000, immutable' : 'no-store',
109
+ }));
110
+ if (req.method === 'HEAD')
111
+ res.end();
112
+ else
113
+ fs.createReadStream(file.file).pipe(res);
114
+ }
115
+ /** A call that changes something: the custom header, and an Origin that is this page. */
116
+ requireSameSite(req) {
117
+ if (!csrfOk(req.headers) || !originAllowed(req.headers.origin, req.headers.host, this.actualPort))
118
+ throw new HttpFailure(403, 'Refused.');
119
+ }
120
+ async api(req, res, pathname) {
121
+ const method = req.method ?? 'GET';
122
+ const { backend } = this.options;
123
+ if (pathname === '/api/health' && method === 'GET')
124
+ return this.json(res, 200, { ok: true });
125
+ if (pathname === '/api/login' && method === 'POST') {
126
+ this.requireSameSite(req);
127
+ const body = await this.body(req);
128
+ const result = this.auth.login(body?.token);
129
+ if (!result.ok)
130
+ throw new HttpFailure(result.status, result.status === 429 ? 'Too many tries.' : 'Wrong token.');
131
+ return this.json(res, 200, { ok: true }, { 'Set-Cookie': cookieHeader(result.cookie) });
132
+ }
133
+ if (!this.auth.signedIn(req.headers.cookie))
134
+ throw new HttpFailure(401, 'Not signed in.');
135
+ if (pathname === '/api/info' && method === 'GET')
136
+ return this.json(res, 200, await backend.info());
137
+ if (pathname === '/api/sessions' && method === 'GET')
138
+ return this.json(res, 200, await backend.sessions());
139
+ if (pathname === '/api/sessions' && method === 'POST') {
140
+ this.requireSameSite(req);
141
+ const request = newSessionRequest(await this.body(req));
142
+ if (!request)
143
+ throw new HttpFailure(400, 'Choose an agent and a project.');
144
+ return this.json(res, 201, await backend.create(request));
145
+ }
146
+ if (pathname === '/api/stop' && method === 'POST') {
147
+ this.requireSameSite(req);
148
+ this.json(res, 200, { ok: true });
149
+ setTimeout(() => this.options.onStop?.(), 100);
150
+ return;
151
+ }
152
+ throw new HttpFailure(404, 'Not found.');
153
+ }
154
+ /** A small JSON body (application/json only). */
155
+ body(req) {
156
+ return new Promise((resolve, reject) => {
157
+ if (!/^application\/json\b/.test(req.headers['content-type'] ?? ''))
158
+ return reject(new HttpFailure(415, 'Send JSON.'));
159
+ const chunks = [];
160
+ let size = 0;
161
+ req.on('data', (chunk) => {
162
+ size += chunk.length;
163
+ if (size > MAX_BODY_BYTES) {
164
+ req.destroy();
165
+ reject(new HttpFailure(413, 'Too big.'));
166
+ }
167
+ else
168
+ chunks.push(chunk);
169
+ });
170
+ req.on('end', () => {
171
+ try {
172
+ resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
173
+ }
174
+ catch {
175
+ reject(new HttpFailure(400, 'Not JSON.'));
176
+ }
177
+ });
178
+ req.on('error', reject);
179
+ });
180
+ }
181
+ upgrade(req, socket, head) {
182
+ const refuse = (status) => {
183
+ socket.end('HTTP/1.1 ' + status + '\r\nConnection: close\r\n\r\n');
184
+ };
185
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
186
+ const match = SESSION_PATH.exec(url.pathname);
187
+ if (!match)
188
+ return refuse('404 Not Found');
189
+ // The Origin must be this page (a page of another site can open WebSockets too) and the cookie is the login.
190
+ if (!originAllowed(req.headers.origin, req.headers.host, this.actualPort))
191
+ return refuse('403 Forbidden');
192
+ if (!this.auth.signedIn(req.headers.cookie))
193
+ return refuse('401 Unauthorized');
194
+ if (this.terminals >= MAX_TERMINALS)
195
+ return refuse('503 Service Unavailable');
196
+ const endpoint = this.options.backend.endpoint(match[1]);
197
+ if (!endpoint)
198
+ return refuse('404 Not Found');
199
+ const cols = bounded(url.searchParams.get('cols'), MIN_COLS, MAX_COLS, 120);
200
+ const rows = bounded(url.searchParams.get('rows'), MIN_ROWS, MAX_ROWS, 30);
201
+ this.sockets.handleUpgrade(req, socket, head, (ws) => {
202
+ this.terminals++;
203
+ ws.on('close', () => this.terminals--);
204
+ void new TerminalBridge(ws, endpoint, cols, rows, this.options.dial).start();
205
+ });
206
+ }
207
+ }
208
+ function bounded(text, min, max, fallback) {
209
+ const n = Number(text);
210
+ return text !== null && Number.isInteger(n) && n >= min && n <= max ? n : fallback;
211
+ }
212
+ function newSessionRequest(body) {
213
+ if (typeof body !== 'object' || body === null)
214
+ return null;
215
+ const { harness, project } = body;
216
+ const ok = (v) => typeof v === 'string' && /^[A-Za-z0-9_.-]{1,64}$/.test(v);
217
+ return ok(harness) && ok(project) ? { harness, project } : null;
218
+ }
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export declare const WebStateSchema: z.ZodObject<{
3
+ pid: z.ZodNumber;
4
+ port: z.ZodNumber;
5
+ token: z.ZodString;
6
+ startedAt: z.ZodString;
7
+ }, z.core.$strict>;
8
+ export type WebState = z.output<typeof WebStateSchema>;
9
+ export declare class WebStore {
10
+ private readonly storageRoot;
11
+ private readonly protect;
12
+ private readonly file;
13
+ constructor(storageRoot: string, protect?: (dir: string) => void);
14
+ get dir(): string;
15
+ /** Creates the folder and makes it private (the token goes in it). */
16
+ prepare(): void;
17
+ read(): WebState | null;
18
+ write(state: WebState): void;
19
+ clear(): void;
20
+ }
@@ -0,0 +1,41 @@
1
+ // The server's state file: its process, port and token, in a folder only you can read (<storage>\web). It is not in a
2
+ // sandbox's data folder, so a sandbox can neither read the token nor plant a fake file.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { z } from 'zod';
6
+ import { JsonFile } from '../../shared/io/index.js';
7
+ import { protectPrivateFolder } from '../sharing/index.js';
8
+ export const WebStateSchema = z.strictObject({
9
+ pid: z.number().int().positive(),
10
+ port: z.number().int().min(1).max(65535),
11
+ token: z.string().regex(/^[0-9a-f]{64}$/),
12
+ startedAt: z.string().max(64),
13
+ });
14
+ export class WebStore {
15
+ storageRoot;
16
+ protect;
17
+ file;
18
+ constructor(storageRoot, protect = protectPrivateFolder) {
19
+ this.storageRoot = storageRoot;
20
+ this.protect = protect;
21
+ this.file = new JsonFile(WebStateSchema, path.join(this.dir, 'server.json'));
22
+ }
23
+ get dir() {
24
+ return path.join(this.storageRoot, 'web');
25
+ }
26
+ /** Creates the folder and makes it private (the token goes in it). */
27
+ prepare() {
28
+ fs.mkdirSync(this.dir, { recursive: true });
29
+ this.protect(this.dir);
30
+ }
31
+ read() {
32
+ return this.file.read();
33
+ }
34
+ write(state) {
35
+ this.prepare();
36
+ this.file.write(state);
37
+ }
38
+ clear() {
39
+ fs.rmSync(this.file.file, { force: true });
40
+ }
41
+ }
@@ -0,0 +1,13 @@
1
+ export interface StaticFile {
2
+ readonly file: string;
3
+ readonly type: string;
4
+ /** Hashed asset names never change, so browsers may keep them. */
5
+ readonly immutable: boolean;
6
+ }
7
+ export declare class StaticFiles {
8
+ private readonly root;
9
+ constructor(root: string);
10
+ /** The file for a URL path, or null when it is not a file inside the root. Extension-less paths are the app. */
11
+ find(urlPath: string): StaticFile | null;
12
+ private regular;
13
+ }
@@ -0,0 +1,57 @@
1
+ // The app's files from dist/web: nothing outside that folder is ever read, and links in it are not followed.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { isWithin } from '../../shared/paths/index.js';
5
+ const TYPES = {
6
+ '.html': 'text/html; charset=utf-8',
7
+ '.js': 'text/javascript; charset=utf-8',
8
+ '.css': 'text/css; charset=utf-8',
9
+ '.json': 'application/json; charset=utf-8',
10
+ '.svg': 'image/svg+xml',
11
+ '.png': 'image/png',
12
+ '.ico': 'image/x-icon',
13
+ '.woff': 'font/woff',
14
+ '.woff2': 'font/woff2',
15
+ '.map': 'application/json; charset=utf-8',
16
+ };
17
+ export class StaticFiles {
18
+ root;
19
+ constructor(root) {
20
+ this.root = root;
21
+ }
22
+ /** The file for a URL path, or null when it is not a file inside the root. Extension-less paths are the app. */
23
+ find(urlPath) {
24
+ let decoded;
25
+ try {
26
+ decoded = decodeURIComponent(urlPath);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ if (decoded.includes('\0') || decoded.includes('\\') || decoded.split('/').includes('..'))
32
+ return null;
33
+ const rel = decoded === '/' ? '/index.html' : decoded;
34
+ const file = this.regular(rel);
35
+ if (file)
36
+ return file;
37
+ // The app has its own pages (client side): anything that is not a file name is the app.
38
+ return path.extname(rel) === '' ? this.regular('/index.html') : null;
39
+ }
40
+ regular(rel) {
41
+ const file = path.resolve(this.root, '.' + rel);
42
+ if (!isWithin(this.root, file) || file === path.resolve(this.root))
43
+ return null;
44
+ try {
45
+ if (!fs.lstatSync(file).isFile())
46
+ return null;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ const ext = path.extname(file).toLowerCase();
52
+ const type = TYPES[ext];
53
+ if (!type)
54
+ return null;
55
+ return { file, type, immutable: /-[A-Z0-9]{8}\.(js|css|woff2?)$/.test(path.basename(file)) };
56
+ }
57
+ }