@walkhi/code-relax 0.1.0-beta.6 → 0.1.0-beta.65

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 (855) hide show
  1. package/README.md +67 -45
  2. package/dist/bin/cleanup-accounts.mjs +63 -0
  3. package/dist/bin/codex-remote.mjs +362 -227
  4. package/dist/bin/manage-relay-admin.mjs +20 -0
  5. package/dist/bin/migrate-relay-db.mjs +109 -0
  6. package/dist/bin/publish-android-release.mjs +63 -0
  7. package/dist/bin/publish-harmony-release.mjs +46 -0
  8. package/dist/bin/release-upload-server.mjs +190 -0
  9. package/dist/bin/self-relay-admin.mjs +8 -0
  10. package/dist/bin/self-relay-demo.mjs +2 -2
  11. package/dist/bin/self-relay-server.mjs +43 -30
  12. package/dist/bin/set-relay-admin-password.mjs +11 -0
  13. package/dist/build-platform.json +1 -0
  14. package/dist/get-shared-service-url.ps1 +12 -4
  15. package/dist/shared/app-server-events.cjs +457 -238
  16. package/dist/shared/code-browser.cjs +159 -0
  17. package/dist/shared/file-changes.cjs +31 -0
  18. package/dist/shared/file-citations.cjs +117 -0
  19. package/dist/shared/file-mentions.cjs +92 -0
  20. package/dist/shared/message-delivery.cjs +115 -0
  21. package/dist/shared/p2p-data-channel.cjs +22 -0
  22. package/dist/shared/p2p-diagnostics.cjs +1 -1
  23. package/dist/shared/p2p-file-channel.cjs +87 -0
  24. package/dist/shared/p2p-ice-policy.cjs +22 -0
  25. package/dist/shared/plan-pricing.cjs +32 -0
  26. package/dist/shared/question-attention.cjs +69 -0
  27. package/dist/shared/release-version.cjs +31 -0
  28. package/dist/shared/server-requests.cjs +311 -67
  29. package/dist/shared/service-endpoints.cjs +21 -0
  30. package/dist/shared/timeline-sync.cjs +173 -67
  31. package/dist/skills/code-relax/SKILL.md +22 -17
  32. package/dist/src/archived-threads.mjs +22 -0
  33. package/dist/src/bounded-process-log.mjs +63 -0
  34. package/dist/src/celsius-client.mjs +107 -0
  35. package/dist/src/code-browser.mjs +100 -0
  36. package/dist/src/desktop-launcher.mjs +53 -42
  37. package/dist/src/dots-approval-timeline.mjs +99 -0
  38. package/dist/src/dots-avatar.mjs +42 -0
  39. package/dist/src/dots-chat-projection.mjs +50 -0
  40. package/dist/src/dots-chat-server.mjs +498 -0
  41. package/dist/src/dots-message-images.mjs +319 -0
  42. package/dist/src/dots-profile.mjs +77 -0
  43. package/dist/src/dots-room-activity.mjs +178 -0
  44. package/dist/src/dots-room-client.mjs +137 -0
  45. package/dist/src/dots-room-projection.mjs +94 -0
  46. package/dist/src/dots-schedules.mjs +66 -0
  47. package/dist/src/dots-summary-tasks.mjs +32 -0
  48. package/dist/src/dots-task-catalog.mjs +430 -0
  49. package/dist/src/dots-task-files.mjs +52 -0
  50. package/dist/src/file-transfer-range.mjs +22 -0
  51. package/dist/src/file-upload-session.mjs +146 -0
  52. package/dist/src/hosted-history-app-server.mjs +103 -0
  53. package/dist/src/hosted-history-cache.mjs +113 -0
  54. package/dist/src/lan-socket-server.mjs +266 -37
  55. package/dist/src/managed-app-server.mjs +373 -213
  56. package/dist/src/managed-endpoint.mjs +25 -0
  57. package/dist/src/message-images.mjs +0 -18
  58. package/dist/src/message-upload-files.mjs +109 -0
  59. package/dist/src/onboarding.mjs +9 -28
  60. package/dist/src/plan-pricing.mjs +47 -0
  61. package/dist/src/platform/README.md +7 -12
  62. package/dist/src/platform/codex-protocol.mjs +46 -0
  63. package/dist/src/platform/codex-runtime.mjs +3 -0
  64. package/dist/src/platform/credential-store.mjs +19 -0
  65. package/dist/src/platform/desktop-host.mjs +3 -0
  66. package/dist/src/platform/desktop-permission-mode.mjs +17 -0
  67. package/dist/src/platform/desktop-project-registration.mjs +3 -0
  68. package/dist/src/platform/desktop-project-store.mjs +219 -0
  69. package/dist/src/platform/file-opener.mjs +9 -0
  70. package/dist/src/platform/linux/codex-runtime.mjs +56 -0
  71. package/dist/src/platform/linux/desktop-host.mjs +87 -0
  72. package/dist/src/platform/linux/desktop-installation.mjs +36 -0
  73. package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
  74. package/dist/src/platform/linux/network-info.mjs +11 -0
  75. package/dist/src/platform/linux/process.mjs +53 -0
  76. package/dist/src/platform/linux/project-locations.mjs +17 -0
  77. package/dist/src/platform/linux/resident-startup.mjs +52 -0
  78. package/dist/src/platform/linux/retained-runtime.mjs +76 -0
  79. package/dist/src/platform/linux/service-host.mjs +18 -0
  80. package/dist/src/platform/linux/system.mjs +50 -0
  81. package/dist/src/platform/linux/user-environment.mjs +9 -0
  82. package/dist/src/platform/macos/codex-runtime.mjs +125 -0
  83. package/dist/src/platform/macos/desktop-host.mjs +86 -0
  84. package/dist/src/platform/macos/desktop-project-registration.mjs +11 -0
  85. package/dist/src/platform/macos/network-info.mjs +8 -0
  86. package/dist/src/platform/macos/project-locations.mjs +8 -0
  87. package/dist/src/platform/macos/resident-startup.mjs +84 -0
  88. package/dist/src/platform/macos/retained-runtime.mjs +127 -0
  89. package/dist/src/platform/macos/service-host.mjs +18 -0
  90. package/dist/src/platform/macos/system.mjs +29 -0
  91. package/dist/src/platform/macos/user-environment.mjs +9 -0
  92. package/dist/src/platform/native-package.mjs +58 -0
  93. package/dist/src/platform/network-info.mjs +3 -0
  94. package/dist/src/platform/path-identity.mjs +5 -0
  95. package/dist/src/platform/posix/network-candidates.mjs +23 -0
  96. package/dist/src/platform/posix/runtime-paths.mjs +25 -0
  97. package/dist/src/platform/power-inhibitor.mjs +22 -0
  98. package/dist/src/platform/project-locations.mjs +3 -0
  99. package/dist/src/platform/runtime-paths.mjs +8 -0
  100. package/dist/src/platform/select.mjs +5 -0
  101. package/dist/src/platform/service-host.mjs +3 -0
  102. package/dist/src/platform/user-environment.mjs +3 -0
  103. package/dist/src/platform/windows/activate-desktop.ps1 +49 -0
  104. package/dist/src/platform/windows/codex-runtime.mjs +215 -0
  105. package/dist/src/platform/windows/desktop-helper.mjs +179 -0
  106. package/dist/src/platform/windows/desktop-host.mjs +45 -18
  107. package/dist/src/platform/windows/desktop-info.ps1 +27 -5
  108. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -0
  109. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -0
  110. package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -14
  111. package/dist/src/platform/windows/dots-profile.mjs +49 -0
  112. package/dist/src/platform/windows/keep-awake.ps1 +20 -0
  113. package/dist/src/platform/windows/launch-worker.mjs +41 -42
  114. package/dist/src/platform/windows/project-locations.mjs +16 -0
  115. package/dist/src/platform/windows/resident-start.js +20 -0
  116. package/dist/src/platform/windows/resident-startup.ps1 +76 -20
  117. package/dist/src/platform/windows/rustdesk-video-probe.mjs +168 -0
  118. package/dist/src/platform/windows/service-host.mjs +15 -23
  119. package/dist/src/platform/windows/start-hidden-console.ps1 +11 -2
  120. package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
  121. package/dist/src/platform/windows/user-environment.mjs +52 -50
  122. package/dist/src/project-create.mjs +121 -0
  123. package/dist/src/project-management.mjs +59 -0
  124. package/dist/src/project-remove.mjs +55 -0
  125. package/dist/src/project-update.mjs +59 -0
  126. package/dist/src/relay-config-import.mjs +45 -0
  127. package/dist/src/runtime-discovery.mjs +47 -0
  128. package/dist/src/self-relay/account-cleanup.mjs +30 -0
  129. package/dist/src/self-relay/account-connections.mjs +52 -0
  130. package/dist/src/self-relay/admin-auth.mjs +59 -0
  131. package/dist/src/self-relay/admin-cloud.mjs +101 -0
  132. package/dist/src/self-relay/admin-login-page.mjs +7 -0
  133. package/dist/src/self-relay/admin-page.mjs +87 -0
  134. package/dist/src/self-relay/admin-server.mjs +105 -0
  135. package/dist/src/self-relay/admin-state.mjs +85 -19
  136. package/dist/src/self-relay/admin-style.mjs +58 -0
  137. package/dist/src/self-relay/admin-workstations-page.mjs +95 -0
  138. package/dist/src/self-relay/admin-workstations.mjs +143 -0
  139. package/dist/src/self-relay/android-releases.mjs +61 -0
  140. package/dist/src/self-relay/client.mjs +45 -45
  141. package/dist/src/self-relay/connection-reporting.mjs +38 -0
  142. package/dist/src/self-relay/connector.mjs +331 -49
  143. package/dist/src/self-relay/demo.mjs +3 -4
  144. package/dist/src/self-relay/device-info.mjs +27 -0
  145. package/dist/src/self-relay/device-store.mjs +10 -2
  146. package/dist/src/self-relay/diagnostics.mjs +54 -0
  147. package/dist/src/self-relay/email-accounts.mjs +183 -0
  148. package/dist/src/self-relay/file-wire.mjs +13 -0
  149. package/dist/src/self-relay/lan-authorizations.mjs +44 -2
  150. package/dist/src/self-relay/lifecycle.mjs +255 -262
  151. package/dist/src/self-relay/long-pairing.mjs +40 -0
  152. package/dist/src/self-relay/mysql-state.mjs +118 -0
  153. package/dist/src/self-relay/p2p-probe.mjs +168 -123
  154. package/dist/src/self-relay/phone-connections.mjs +55 -0
  155. package/dist/src/self-relay/privacy-page.mjs +26 -0
  156. package/dist/src/self-relay/public-releases.mjs +67 -0
  157. package/dist/src/self-relay/rtc-file-bridge.mjs +46 -0
  158. package/dist/src/self-relay/server.mjs +714 -116
  159. package/dist/src/self-relay/usage-daily.mjs +55 -0
  160. package/dist/src/self-relay/usage-meter.mjs +141 -0
  161. package/dist/src/server.mjs +1566 -1793
  162. package/dist/src/service-control.mjs +17 -7
  163. package/dist/src/service-doctor.mjs +148 -65
  164. package/dist/src/service-health.mjs +2 -2
  165. package/dist/src/service-install.mjs +16 -10
  166. package/dist/src/service-lifecycle.mjs +51 -47
  167. package/dist/src/service-power.mjs +58 -0
  168. package/dist/src/service-restart.mjs +45 -12
  169. package/dist/src/service-update-worker.mjs +42 -0
  170. package/dist/src/service-update.mjs +80 -0
  171. package/dist/src/shared-app-server.mjs +489 -305
  172. package/dist/src/shared-catalog.mjs +132 -50
  173. package/dist/src/shared-recovery.mjs +24 -14
  174. package/dist/src/shared-thread-stream.mjs +136 -51
  175. package/dist/src/skill-install.mjs +0 -13
  176. package/dist/src/turn-model-history.mjs +47 -0
  177. package/dist/src/usage-reset.mjs +17 -0
  178. package/dist/src/workstation-downloads.mjs +120 -0
  179. package/package.json +35 -37
  180. package/tools/postinstall.mjs +186 -118
  181. package/dist/src/app-server-client.mjs +0 -468
  182. package/dist/src/app-server-tasks.mjs +0 -358
  183. package/dist/src/debug-ui.mjs +0 -131
  184. package/dist/src/platform/windows/credential-store.mjs +0 -27
  185. package/dist/src/platform/windows/credential-store.ps1 +0 -49
  186. package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
  187. package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
  188. package/dist/src/platform/windows/desktop-tools.mjs +0 -48
  189. package/dist/src/thread-catalog.mjs +0 -47
  190. package/dist/tools/find-desktop-pipe.mjs +0 -10
  191. package/dist/web/activity-view.js +0 -283
  192. package/dist/web/approval-ui.js +0 -188
  193. package/dist/web/capabilities.js +0 -57
  194. package/dist/web/chat-transport.js +0 -202
  195. package/dist/web/chat.css +0 -782
  196. package/dist/web/chat.js +0 -3830
  197. package/dist/web/client-platform.js +0 -11
  198. package/dist/web/community-view.js +0 -20
  199. package/dist/web/community.css +0 -77
  200. package/dist/web/community.html +0 -37
  201. package/dist/web/composer-controller.js +0 -101
  202. package/dist/web/conversation-controller.js +0 -99
  203. package/dist/web/disclosure-state-controller.js +0 -95
  204. package/dist/web/draft-controller.js +0 -103
  205. package/dist/web/harmony-platform.js +0 -52
  206. package/dist/web/highlight-powershell.min.js +0 -39
  207. package/dist/web/highlight.min.js +0 -1262
  208. package/dist/web/history-cache.js +0 -240
  209. package/dist/web/history-controller.js +0 -167
  210. package/dist/web/index.html +0 -325
  211. package/dist/web/lan-session.js +0 -122
  212. package/dist/web/link-action-controller.js +0 -212
  213. package/dist/web/message-send-controller.js +0 -177
  214. package/dist/web/message-view.js +0 -98
  215. package/dist/web/p2p-data-channel.js +0 -50
  216. package/dist/web/p2p-diagnostics.js +0 -28
  217. package/dist/web/p2p-probe.js +0 -123
  218. package/dist/web/page-resume.js +0 -28
  219. package/dist/web/pending-message-store.js +0 -108
  220. package/dist/web/queue-controller.js +0 -82
  221. package/dist/web/resources.json +0 -1
  222. package/dist/web/self-relay-session.js +0 -129
  223. package/dist/web/server-requests.js +0 -67
  224. package/dist/web/station-connection-controller.js +0 -75
  225. package/dist/web/task-list-view.js +0 -296
  226. package/dist/web/thread-attention-controller.js +0 -124
  227. package/dist/web/thread-context-controller.js +0 -30
  228. package/dist/web/thread-list-controller.js +0 -61
  229. package/dist/web/thread-list-sync.js +0 -86
  230. package/dist/web/thread-title-controller.js +0 -57
  231. package/dist/web/timeline-formatters.js +0 -249
  232. package/dist/web/timeline-reducer.js +0 -81
  233. package/dist/web/timeline-renderer.js +0 -161
  234. package/dist/web/timeline-scroll-controller.js +0 -50
  235. package/dist/web/timeline-sync.js +0 -67
  236. package/dist/web/usage-controller.js +0 -258
  237. package/dist/web/vendor/README.md +0 -14
  238. package/dist/web/vendor/highlight-powershell.min.js +0 -39
  239. package/dist/web/vendor/highlight.LICENSE.txt +0 -29
  240. package/dist/web/vendor/highlight.min.js +0 -1262
  241. package/dist/web/vendor/lucide.LICENSE.txt +0 -17
  242. package/node_modules/@img/colour/LICENSE.md +0 -82
  243. package/node_modules/@img/colour/README.md +0 -15
  244. package/node_modules/@img/colour/color.cjs +0 -1596
  245. package/node_modules/@img/colour/index.cjs +0 -1
  246. package/node_modules/@img/colour/index.d.ts +0 -929
  247. package/node_modules/@img/colour/package.json +0 -58
  248. package/node_modules/@img/sharp-win32-x64/LICENSE +0 -191
  249. package/node_modules/@img/sharp-win32-x64/README.md +0 -59
  250. package/node_modules/@img/sharp-win32-x64/index.cjs +0 -1
  251. package/node_modules/@img/sharp-win32-x64/lib/libvips-42.dll +0 -0
  252. package/node_modules/@img/sharp-win32-x64/lib/libvips-cpp-8.18.6.dll +0 -0
  253. package/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64-0.35.4.node +0 -0
  254. package/node_modules/@img/sharp-win32-x64/package.json +0 -40
  255. package/node_modules/@img/sharp-win32-x64/versions.json +0 -30
  256. package/node_modules/@node-datachannel/win32-x64-msvc/node_datachannel.node +0 -0
  257. package/node_modules/@node-datachannel/win32-x64-msvc/package.json +0 -20
  258. package/node_modules/ansi-regex/index.d.ts +0 -37
  259. package/node_modules/ansi-regex/index.js +0 -10
  260. package/node_modules/ansi-regex/license +0 -9
  261. package/node_modules/ansi-regex/package.json +0 -55
  262. package/node_modules/ansi-regex/readme.md +0 -78
  263. package/node_modules/ansi-styles/index.d.ts +0 -345
  264. package/node_modules/ansi-styles/index.js +0 -163
  265. package/node_modules/ansi-styles/license +0 -9
  266. package/node_modules/ansi-styles/package.json +0 -56
  267. package/node_modules/ansi-styles/readme.md +0 -152
  268. package/node_modules/camelcase/index.d.ts +0 -63
  269. package/node_modules/camelcase/index.js +0 -76
  270. package/node_modules/camelcase/license +0 -9
  271. package/node_modules/camelcase/package.json +0 -43
  272. package/node_modules/camelcase/readme.md +0 -99
  273. package/node_modules/cliui/CHANGELOG.md +0 -76
  274. package/node_modules/cliui/LICENSE.txt +0 -14
  275. package/node_modules/cliui/README.md +0 -115
  276. package/node_modules/cliui/index.js +0 -354
  277. package/node_modules/cliui/package.json +0 -65
  278. package/node_modules/color-convert/CHANGELOG.md +0 -54
  279. package/node_modules/color-convert/LICENSE +0 -21
  280. package/node_modules/color-convert/README.md +0 -68
  281. package/node_modules/color-convert/conversions.js +0 -839
  282. package/node_modules/color-convert/index.js +0 -81
  283. package/node_modules/color-convert/package.json +0 -48
  284. package/node_modules/color-convert/route.js +0 -97
  285. package/node_modules/color-name/LICENSE +0 -8
  286. package/node_modules/color-name/README.md +0 -11
  287. package/node_modules/color-name/index.js +0 -152
  288. package/node_modules/color-name/package.json +0 -28
  289. package/node_modules/decamelize/index.js +0 -13
  290. package/node_modules/decamelize/license +0 -21
  291. package/node_modules/decamelize/package.json +0 -38
  292. package/node_modules/decamelize/readme.md +0 -48
  293. package/node_modules/detect-libc/LICENSE +0 -201
  294. package/node_modules/detect-libc/README.md +0 -163
  295. package/node_modules/detect-libc/index.d.ts +0 -14
  296. package/node_modules/detect-libc/lib/detect-libc.js +0 -313
  297. package/node_modules/detect-libc/lib/elf.js +0 -39
  298. package/node_modules/detect-libc/lib/filesystem.js +0 -51
  299. package/node_modules/detect-libc/lib/process.js +0 -24
  300. package/node_modules/detect-libc/package.json +0 -44
  301. package/node_modules/dijkstrajs/.travis.yml +0 -4
  302. package/node_modules/dijkstrajs/CONTRIBUTING.md +0 -8
  303. package/node_modules/dijkstrajs/LICENSE.md +0 -19
  304. package/node_modules/dijkstrajs/README.md +0 -18
  305. package/node_modules/dijkstrajs/dijkstra.js +0 -165
  306. package/node_modules/dijkstrajs/package.json +0 -60
  307. package/node_modules/dijkstrajs/test/dijkstra.test.js +0 -96
  308. package/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  309. package/node_modules/emoji-regex/README.md +0 -73
  310. package/node_modules/emoji-regex/es2015/index.js +0 -6
  311. package/node_modules/emoji-regex/es2015/text.js +0 -6
  312. package/node_modules/emoji-regex/index.d.ts +0 -23
  313. package/node_modules/emoji-regex/index.js +0 -6
  314. package/node_modules/emoji-regex/package.json +0 -50
  315. package/node_modules/emoji-regex/text.js +0 -6
  316. package/node_modules/find-up/index.d.ts +0 -137
  317. package/node_modules/find-up/index.js +0 -89
  318. package/node_modules/find-up/license +0 -9
  319. package/node_modules/find-up/package.json +0 -53
  320. package/node_modules/find-up/readme.md +0 -156
  321. package/node_modules/get-caller-file/LICENSE.md +0 -6
  322. package/node_modules/get-caller-file/README.md +0 -41
  323. package/node_modules/get-caller-file/index.d.ts +0 -2
  324. package/node_modules/get-caller-file/index.js +0 -22
  325. package/node_modules/get-caller-file/index.js.map +0 -1
  326. package/node_modules/get-caller-file/package.json +0 -42
  327. package/node_modules/is-fullwidth-code-point/index.d.ts +0 -17
  328. package/node_modules/is-fullwidth-code-point/index.js +0 -50
  329. package/node_modules/is-fullwidth-code-point/license +0 -9
  330. package/node_modules/is-fullwidth-code-point/package.json +0 -42
  331. package/node_modules/is-fullwidth-code-point/readme.md +0 -39
  332. package/node_modules/locate-path/index.d.ts +0 -83
  333. package/node_modules/locate-path/index.js +0 -65
  334. package/node_modules/locate-path/license +0 -9
  335. package/node_modules/locate-path/package.json +0 -45
  336. package/node_modules/locate-path/readme.md +0 -122
  337. package/node_modules/node-datachannel/.clang-format +0 -17
  338. package/node_modules/node-datachannel/.editorconfig +0 -12
  339. package/node_modules/node-datachannel/.eslintignore +0 -8
  340. package/node_modules/node-datachannel/.eslintrc.json +0 -27
  341. package/node_modules/node-datachannel/.gitmodules +0 -3
  342. package/node_modules/node-datachannel/.prettierignore +0 -7
  343. package/node_modules/node-datachannel/.prettierrc +0 -13
  344. package/node_modules/node-datachannel/API.md +0 -247
  345. package/node_modules/node-datachannel/BULDING.md +0 -26
  346. package/node_modules/node-datachannel/CMakeLists.txt +0 -153
  347. package/node_modules/node-datachannel/LICENSE +0 -373
  348. package/node_modules/node-datachannel/README.md +0 -129
  349. package/node_modules/node-datachannel/dist/cjs/index.cjs +0 -11
  350. package/node_modules/node-datachannel/dist/cjs/index.cjs.map +0 -1
  351. package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs +0 -101
  352. package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs.map +0 -1
  353. package/node_modules/node-datachannel/dist/cjs/lib/index.cjs +0 -91
  354. package/node_modules/node-datachannel/dist/cjs/lib/index.cjs.map +0 -1
  355. package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs +0 -94
  356. package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs.map +0 -1
  357. package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs +0 -45
  358. package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs.map +0 -1
  359. package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs +0 -8
  360. package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs.map +0 -1
  361. package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs +0 -86
  362. package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs.map +0 -1
  363. package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs +0 -34
  364. package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs.map +0 -1
  365. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs +0 -34
  366. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs.map +0 -1
  367. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs +0 -214
  368. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs.map +0 -1
  369. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs +0 -53
  370. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs.map +0 -1
  371. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs +0 -83
  372. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs.map +0 -1
  373. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs +0 -132
  374. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs.map +0 -1
  375. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs +0 -94
  376. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs.map +0 -1
  377. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs +0 -434
  378. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs.map +0 -1
  379. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs +0 -59
  380. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs.map +0 -1
  381. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs +0 -45
  382. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs.map +0 -1
  383. package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs +0 -42
  384. package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs.map +0 -1
  385. package/node_modules/node-datachannel/dist/esm/index.mjs +0 -8
  386. package/node_modules/node-datachannel/dist/esm/index.mjs.map +0 -1
  387. package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs +0 -78
  388. package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs.map +0 -1
  389. package/node_modules/node-datachannel/dist/esm/lib/index.mjs +0 -64
  390. package/node_modules/node-datachannel/dist/esm/lib/index.mjs.map +0 -1
  391. package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs +0 -78
  392. package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs.map +0 -1
  393. package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs +0 -43
  394. package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs.map +0 -1
  395. package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs +0 -6
  396. package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs.map +0 -1
  397. package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs +0 -82
  398. package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs.map +0 -1
  399. package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs +0 -28
  400. package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs.map +0 -1
  401. package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs +0 -30
  402. package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs.map +0 -1
  403. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs +0 -210
  404. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs.map +0 -1
  405. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs +0 -49
  406. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs.map +0 -1
  407. package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs +0 -79
  408. package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs.map +0 -1
  409. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs +0 -128
  410. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs.map +0 -1
  411. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs +0 -89
  412. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs.map +0 -1
  413. package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs +0 -430
  414. package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs.map +0 -1
  415. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs +0 -55
  416. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs.map +0 -1
  417. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs +0 -41
  418. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs.map +0 -1
  419. package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs +0 -27
  420. package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs.map +0 -1
  421. package/node_modules/node-datachannel/dist/types/lib/datachannel-stream.d.ts +0 -24
  422. package/node_modules/node-datachannel/dist/types/lib/index.d.ts +0 -241
  423. package/node_modules/node-datachannel/dist/types/lib/types.d.ts +0 -119
  424. package/node_modules/node-datachannel/dist/types/lib/websocket-server.d.ts +0 -13
  425. package/node_modules/node-datachannel/dist/types/lib/websocket.d.ts +0 -25
  426. package/node_modules/node-datachannel/dist/types/polyfill/Events.d.ts +0 -15
  427. package/node_modules/node-datachannel/dist/types/polyfill/RTCCertificate.d.ts +0 -9
  428. package/node_modules/node-datachannel/dist/types/polyfill/RTCDataChannel.d.ts +0 -29
  429. package/node_modules/node-datachannel/dist/types/polyfill/RTCDtlsTransport.d.ts +0 -15
  430. package/node_modules/node-datachannel/dist/types/polyfill/RTCError.d.ts +0 -17
  431. package/node_modules/node-datachannel/dist/types/polyfill/RTCIceCandidate.d.ts +0 -21
  432. package/node_modules/node-datachannel/dist/types/polyfill/RTCIceTransport.d.ts +0 -30
  433. package/node_modules/node-datachannel/dist/types/polyfill/RTCPeerConnection.d.ts +0 -64
  434. package/node_modules/node-datachannel/dist/types/polyfill/RTCSctpTransport.d.ts +0 -15
  435. package/node_modules/node-datachannel/dist/types/polyfill/RTCSessionDescription.d.ts +0 -10
  436. package/node_modules/node-datachannel/dist/types/polyfill/index.d.ts +0 -26
  437. package/node_modules/node-datachannel/jest.config.ts +0 -14
  438. package/node_modules/node-datachannel/package.json +0 -133
  439. package/node_modules/node-datachannel/rollup.config.mjs +0 -67
  440. package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.cpp +0 -530
  441. package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.h +0 -63
  442. package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.cpp +0 -172
  443. package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.h +0 -43
  444. package/node_modules/node-datachannel/src/cpp/main.cpp +0 -58
  445. package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.cpp +0 -457
  446. package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.h +0 -52
  447. package/node_modules/node-datachannel/src/cpp/media-av1packetization.cpp +0 -24
  448. package/node_modules/node-datachannel/src/cpp/media-av1packetization.h +0 -11
  449. package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.cpp +0 -126
  450. package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.h +0 -29
  451. package/node_modules/node-datachannel/src/cpp/media-direction.cpp +0 -43
  452. package/node_modules/node-datachannel/src/cpp/media-direction.h +0 -10
  453. package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.cpp +0 -126
  454. package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.h +0 -30
  455. package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.cpp +0 -126
  456. package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.h +0 -30
  457. package/node_modules/node-datachannel/src/cpp/media-h26xseparator.cpp +0 -32
  458. package/node_modules/node-datachannel/src/cpp/media-h26xseparator.h +0 -11
  459. package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.cpp +0 -31
  460. package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.h +0 -11
  461. package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.cpp +0 -79
  462. package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.h +0 -28
  463. package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.cpp +0 -68
  464. package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.h +0 -28
  465. package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.cpp +0 -57
  466. package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.h +0 -28
  467. package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.cpp +0 -93
  468. package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.h +0 -30
  469. package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.cpp +0 -167
  470. package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.h +0 -35
  471. package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.cpp +0 -95
  472. package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.h +0 -30
  473. package/node_modules/node-datachannel/src/cpp/media-track-wrapper.cpp +0 -458
  474. package/node_modules/node-datachannel/src/cpp/media-track-wrapper.h +0 -61
  475. package/node_modules/node-datachannel/src/cpp/media-video-wrapper.cpp +0 -526
  476. package/node_modules/node-datachannel/src/cpp/media-video-wrapper.h +0 -56
  477. package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.cpp +0 -1298
  478. package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.h +0 -89
  479. package/node_modules/node-datachannel/src/cpp/rtc-wrapper.cpp +0 -205
  480. package/node_modules/node-datachannel/src/cpp/rtc-wrapper.h +0 -24
  481. package/node_modules/node-datachannel/src/cpp/thread-safe-callback.cpp +0 -57
  482. package/node_modules/node-datachannel/src/cpp/thread-safe-callback.h +0 -47
  483. package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.cpp +0 -275
  484. package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.h +0 -41
  485. package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.cpp +0 -796
  486. package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.h +0 -63
  487. package/node_modules/node-datachannel/src/index.ts +0 -9
  488. package/node_modules/node-datachannel/src/lib/datachannel-stream.ts +0 -100
  489. package/node_modules/node-datachannel/src/lib/index.ts +0 -290
  490. package/node_modules/node-datachannel/src/lib/node-datachannel.ts +0 -84
  491. package/node_modules/node-datachannel/src/lib/types.ts +0 -169
  492. package/node_modules/node-datachannel/src/lib/websocket-server.ts +0 -37
  493. package/node_modules/node-datachannel/src/lib/websocket.ts +0 -26
  494. package/node_modules/node-datachannel/src/polyfill/Events.ts +0 -82
  495. package/node_modules/node-datachannel/src/polyfill/Exception.ts +0 -37
  496. package/node_modules/node-datachannel/src/polyfill/README.md +0 -41
  497. package/node_modules/node-datachannel/src/polyfill/RTCCertificate.ts +0 -21
  498. package/node_modules/node-datachannel/src/polyfill/RTCDataChannel.ts +0 -225
  499. package/node_modules/node-datachannel/src/polyfill/RTCDtlsTransport.ts +0 -46
  500. package/node_modules/node-datachannel/src/polyfill/RTCError.ts +0 -78
  501. package/node_modules/node-datachannel/src/polyfill/RTCIceCandidate.ts +0 -128
  502. package/node_modules/node-datachannel/src/polyfill/RTCIceTransport.ts +0 -90
  503. package/node_modules/node-datachannel/src/polyfill/RTCPeerConnection.ts +0 -527
  504. package/node_modules/node-datachannel/src/polyfill/RTCSctpTransport.ts +0 -51
  505. package/node_modules/node-datachannel/src/polyfill/RTCSessionDescription.ts +0 -41
  506. package/node_modules/node-datachannel/src/polyfill/index.ts +0 -38
  507. package/node_modules/node-datachannel/tsconfig.json +0 -21
  508. package/node_modules/p-limit/index.d.ts +0 -38
  509. package/node_modules/p-limit/index.js +0 -57
  510. package/node_modules/p-limit/license +0 -9
  511. package/node_modules/p-limit/package.json +0 -52
  512. package/node_modules/p-limit/readme.md +0 -101
  513. package/node_modules/p-locate/index.d.ts +0 -64
  514. package/node_modules/p-locate/index.js +0 -52
  515. package/node_modules/p-locate/license +0 -9
  516. package/node_modules/p-locate/package.json +0 -53
  517. package/node_modules/p-locate/readme.md +0 -90
  518. package/node_modules/p-try/index.d.ts +0 -39
  519. package/node_modules/p-try/index.js +0 -9
  520. package/node_modules/p-try/license +0 -9
  521. package/node_modules/p-try/package.json +0 -42
  522. package/node_modules/p-try/readme.md +0 -58
  523. package/node_modules/path-exists/index.d.ts +0 -28
  524. package/node_modules/path-exists/index.js +0 -23
  525. package/node_modules/path-exists/license +0 -9
  526. package/node_modules/path-exists/package.json +0 -39
  527. package/node_modules/path-exists/readme.md +0 -52
  528. package/node_modules/pngjs/.eslintignore +0 -2
  529. package/node_modules/pngjs/.eslintrc.json +0 -19
  530. package/node_modules/pngjs/.prettierignore +0 -3
  531. package/node_modules/pngjs/LICENSE +0 -20
  532. package/node_modules/pngjs/README.md +0 -429
  533. package/node_modules/pngjs/browser.js +0 -16096
  534. package/node_modules/pngjs/coverage/lcov-report/base.css +0 -354
  535. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +0 -881
  536. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +0 -554
  537. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +0 -77
  538. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +0 -647
  539. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +0 -176
  540. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +0 -200
  541. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  542. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +0 -593
  543. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +0 -152
  544. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +0 -143
  545. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +0 -611
  546. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +0 -359
  547. package/node_modules/pngjs/coverage/lcov-report/index.html +0 -426
  548. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +0 -365
  549. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +0 -230
  550. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +0 -248
  551. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +0 -467
  552. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +0 -128
  553. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +0 -575
  554. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +0 -404
  555. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +0 -950
  556. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +0 -116
  557. package/node_modules/pngjs/coverage/lcov-report/png.js.html +0 -662
  558. package/node_modules/pngjs/coverage/lcov-report/prettify.css +0 -101
  559. package/node_modules/pngjs/coverage/lcov-report/prettify.js +0 -1007
  560. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  561. package/node_modules/pngjs/coverage/lcov-report/sorter.js +0 -169
  562. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +0 -584
  563. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +0 -215
  564. package/node_modules/pngjs/coverage/lcov.info +0 -2177
  565. package/node_modules/pngjs/lib/bitmapper.js +0 -267
  566. package/node_modules/pngjs/lib/bitpacker.js +0 -158
  567. package/node_modules/pngjs/lib/chunkstream.js +0 -189
  568. package/node_modules/pngjs/lib/constants.js +0 -32
  569. package/node_modules/pngjs/lib/crc.js +0 -40
  570. package/node_modules/pngjs/lib/filter-pack.js +0 -171
  571. package/node_modules/pngjs/lib/filter-parse-async.js +0 -24
  572. package/node_modules/pngjs/lib/filter-parse-sync.js +0 -21
  573. package/node_modules/pngjs/lib/filter-parse.js +0 -177
  574. package/node_modules/pngjs/lib/format-normaliser.js +0 -93
  575. package/node_modules/pngjs/lib/interlace.js +0 -95
  576. package/node_modules/pngjs/lib/packer-async.js +0 -50
  577. package/node_modules/pngjs/lib/packer-sync.js +0 -56
  578. package/node_modules/pngjs/lib/packer.js +0 -129
  579. package/node_modules/pngjs/lib/paeth-predictor.js +0 -16
  580. package/node_modules/pngjs/lib/parser-async.js +0 -165
  581. package/node_modules/pngjs/lib/parser-sync.js +0 -108
  582. package/node_modules/pngjs/lib/parser.js +0 -290
  583. package/node_modules/pngjs/lib/png-sync.js +0 -12
  584. package/node_modules/pngjs/lib/png.js +0 -194
  585. package/node_modules/pngjs/lib/sync-inflate.js +0 -168
  586. package/node_modules/pngjs/lib/sync-reader.js +0 -45
  587. package/node_modules/pngjs/package.json +0 -73
  588. package/node_modules/qrcode/README.md +0 -770
  589. package/node_modules/qrcode/bin/qrcode +0 -159
  590. package/node_modules/qrcode/helper/to-sjis-browser.js +0 -2
  591. package/node_modules/qrcode/helper/to-sjis.js +0 -105
  592. package/node_modules/qrcode/lib/browser.js +0 -76
  593. package/node_modules/qrcode/lib/can-promise.js +0 -7
  594. package/node_modules/qrcode/lib/core/alignment-pattern.js +0 -83
  595. package/node_modules/qrcode/lib/core/alphanumeric-data.js +0 -59
  596. package/node_modules/qrcode/lib/core/bit-buffer.js +0 -37
  597. package/node_modules/qrcode/lib/core/bit-matrix.js +0 -65
  598. package/node_modules/qrcode/lib/core/byte-data.js +0 -30
  599. package/node_modules/qrcode/lib/core/error-correction-code.js +0 -135
  600. package/node_modules/qrcode/lib/core/error-correction-level.js +0 -50
  601. package/node_modules/qrcode/lib/core/finder-pattern.js +0 -22
  602. package/node_modules/qrcode/lib/core/format-info.js +0 -29
  603. package/node_modules/qrcode/lib/core/galois-field.js +0 -69
  604. package/node_modules/qrcode/lib/core/kanji-data.js +0 -54
  605. package/node_modules/qrcode/lib/core/mask-pattern.js +0 -234
  606. package/node_modules/qrcode/lib/core/mode.js +0 -167
  607. package/node_modules/qrcode/lib/core/numeric-data.js +0 -43
  608. package/node_modules/qrcode/lib/core/polynomial.js +0 -62
  609. package/node_modules/qrcode/lib/core/qrcode.js +0 -495
  610. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +0 -56
  611. package/node_modules/qrcode/lib/core/regex.js +0 -31
  612. package/node_modules/qrcode/lib/core/segments.js +0 -330
  613. package/node_modules/qrcode/lib/core/utils.js +0 -63
  614. package/node_modules/qrcode/lib/core/version-check.js +0 -9
  615. package/node_modules/qrcode/lib/core/version.js +0 -163
  616. package/node_modules/qrcode/lib/index.js +0 -12
  617. package/node_modules/qrcode/lib/renderer/canvas.js +0 -63
  618. package/node_modules/qrcode/lib/renderer/png.js +0 -78
  619. package/node_modules/qrcode/lib/renderer/svg-tag.js +0 -81
  620. package/node_modules/qrcode/lib/renderer/svg.js +0 -19
  621. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +0 -85
  622. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +0 -49
  623. package/node_modules/qrcode/lib/renderer/terminal.js +0 -9
  624. package/node_modules/qrcode/lib/renderer/utf8.js +0 -71
  625. package/node_modules/qrcode/lib/renderer/utils.js +0 -99
  626. package/node_modules/qrcode/lib/server.js +0 -138
  627. package/node_modules/qrcode/license +0 -10
  628. package/node_modules/qrcode/package.json +0 -77
  629. package/node_modules/require-directory/.jshintrc +0 -67
  630. package/node_modules/require-directory/.npmignore +0 -1
  631. package/node_modules/require-directory/.travis.yml +0 -3
  632. package/node_modules/require-directory/LICENSE +0 -22
  633. package/node_modules/require-directory/README.markdown +0 -184
  634. package/node_modules/require-directory/index.js +0 -86
  635. package/node_modules/require-directory/package.json +0 -40
  636. package/node_modules/require-main-filename/CHANGELOG.md +0 -26
  637. package/node_modules/require-main-filename/LICENSE.txt +0 -14
  638. package/node_modules/require-main-filename/README.md +0 -26
  639. package/node_modules/require-main-filename/index.js +0 -18
  640. package/node_modules/require-main-filename/package.json +0 -35
  641. package/node_modules/semver/LICENSE +0 -15
  642. package/node_modules/semver/README.md +0 -680
  643. package/node_modules/semver/bin/semver.js +0 -195
  644. package/node_modules/semver/classes/comparator.js +0 -143
  645. package/node_modules/semver/classes/index.js +0 -7
  646. package/node_modules/semver/classes/range.js +0 -577
  647. package/node_modules/semver/classes/semver.js +0 -350
  648. package/node_modules/semver/functions/clean.js +0 -8
  649. package/node_modules/semver/functions/cmp.js +0 -54
  650. package/node_modules/semver/functions/coerce.js +0 -62
  651. package/node_modules/semver/functions/compare-build.js +0 -9
  652. package/node_modules/semver/functions/compare-loose.js +0 -5
  653. package/node_modules/semver/functions/compare.js +0 -7
  654. package/node_modules/semver/functions/diff.js +0 -60
  655. package/node_modules/semver/functions/eq.js +0 -5
  656. package/node_modules/semver/functions/gt.js +0 -5
  657. package/node_modules/semver/functions/gte.js +0 -5
  658. package/node_modules/semver/functions/inc.js +0 -21
  659. package/node_modules/semver/functions/lt.js +0 -5
  660. package/node_modules/semver/functions/lte.js +0 -5
  661. package/node_modules/semver/functions/major.js +0 -5
  662. package/node_modules/semver/functions/minor.js +0 -5
  663. package/node_modules/semver/functions/neq.js +0 -5
  664. package/node_modules/semver/functions/parse.js +0 -18
  665. package/node_modules/semver/functions/patch.js +0 -5
  666. package/node_modules/semver/functions/prerelease.js +0 -8
  667. package/node_modules/semver/functions/rcompare.js +0 -5
  668. package/node_modules/semver/functions/rsort.js +0 -5
  669. package/node_modules/semver/functions/satisfies.js +0 -12
  670. package/node_modules/semver/functions/sort.js +0 -5
  671. package/node_modules/semver/functions/truncate.js +0 -48
  672. package/node_modules/semver/functions/valid.js +0 -8
  673. package/node_modules/semver/index.js +0 -93
  674. package/node_modules/semver/internal/constants.js +0 -37
  675. package/node_modules/semver/internal/debug.js +0 -11
  676. package/node_modules/semver/internal/identifiers.js +0 -29
  677. package/node_modules/semver/internal/lrucache.js +0 -42
  678. package/node_modules/semver/internal/parse-options.js +0 -17
  679. package/node_modules/semver/internal/re.js +0 -223
  680. package/node_modules/semver/package.json +0 -78
  681. package/node_modules/semver/preload.js +0 -4
  682. package/node_modules/semver/range.bnf +0 -17
  683. package/node_modules/semver/ranges/gtr.js +0 -6
  684. package/node_modules/semver/ranges/intersects.js +0 -9
  685. package/node_modules/semver/ranges/ltr.js +0 -6
  686. package/node_modules/semver/ranges/max-satisfying.js +0 -27
  687. package/node_modules/semver/ranges/min-satisfying.js +0 -26
  688. package/node_modules/semver/ranges/min-version.js +0 -63
  689. package/node_modules/semver/ranges/outside.js +0 -82
  690. package/node_modules/semver/ranges/simplify.js +0 -49
  691. package/node_modules/semver/ranges/subset.js +0 -249
  692. package/node_modules/semver/ranges/to-comparators.js +0 -10
  693. package/node_modules/semver/ranges/valid.js +0 -13
  694. package/node_modules/set-blocking/CHANGELOG.md +0 -26
  695. package/node_modules/set-blocking/LICENSE.txt +0 -14
  696. package/node_modules/set-blocking/README.md +0 -31
  697. package/node_modules/set-blocking/index.js +0 -7
  698. package/node_modules/set-blocking/package.json +0 -42
  699. package/node_modules/sharp/LICENSE +0 -191
  700. package/node_modules/sharp/README.md +0 -112
  701. package/node_modules/sharp/dist/channel.cjs +0 -177
  702. package/node_modules/sharp/dist/channel.mjs +0 -177
  703. package/node_modules/sharp/dist/colour.cjs +0 -199
  704. package/node_modules/sharp/dist/colour.mjs +0 -199
  705. package/node_modules/sharp/dist/composite.cjs +0 -213
  706. package/node_modules/sharp/dist/composite.mjs +0 -213
  707. package/node_modules/sharp/dist/constructor.cjs +0 -511
  708. package/node_modules/sharp/dist/constructor.mjs +0 -511
  709. package/node_modules/sharp/dist/index.cjs +0 -25
  710. package/node_modules/sharp/dist/index.d.cts +0 -1999
  711. package/node_modules/sharp/dist/index.d.mts +0 -2046
  712. package/node_modules/sharp/dist/index.mjs +0 -25
  713. package/node_modules/sharp/dist/input.cjs +0 -819
  714. package/node_modules/sharp/dist/input.mjs +0 -819
  715. package/node_modules/sharp/dist/is.cjs +0 -143
  716. package/node_modules/sharp/dist/is.mjs +0 -143
  717. package/node_modules/sharp/dist/libvips.cjs +0 -212
  718. package/node_modules/sharp/dist/libvips.mjs +0 -212
  719. package/node_modules/sharp/dist/operation.cjs +0 -1002
  720. package/node_modules/sharp/dist/operation.mjs +0 -1002
  721. package/node_modules/sharp/dist/output.cjs +0 -1785
  722. package/node_modules/sharp/dist/output.mjs +0 -1785
  723. package/node_modules/sharp/dist/resize.cjs +0 -617
  724. package/node_modules/sharp/dist/resize.mjs +0 -617
  725. package/node_modules/sharp/dist/sharp.cjs +0 -174
  726. package/node_modules/sharp/dist/sharp.mjs +0 -174
  727. package/node_modules/sharp/dist/utility.cjs +0 -301
  728. package/node_modules/sharp/dist/utility.mjs +0 -301
  729. package/node_modules/sharp/install/build.js +0 -38
  730. package/node_modules/sharp/lib/index.d.ts +0 -1999
  731. package/node_modules/sharp/package.json +0 -227
  732. package/node_modules/sharp/src/binding.gyp +0 -303
  733. package/node_modules/sharp/src/common.cc +0 -1186
  734. package/node_modules/sharp/src/common.h +0 -421
  735. package/node_modules/sharp/src/metadata.cc +0 -404
  736. package/node_modules/sharp/src/metadata.h +0 -95
  737. package/node_modules/sharp/src/operations.cc +0 -516
  738. package/node_modules/sharp/src/operations.h +0 -137
  739. package/node_modules/sharp/src/pipeline.cc +0 -1948
  740. package/node_modules/sharp/src/pipeline.h +0 -422
  741. package/node_modules/sharp/src/sharp.cc +0 -43
  742. package/node_modules/sharp/src/stats.cc +0 -186
  743. package/node_modules/sharp/src/stats.h +0 -62
  744. package/node_modules/sharp/src/utilities.cc +0 -289
  745. package/node_modules/sharp/src/utilities.h +0 -22
  746. package/node_modules/string-width/index.d.ts +0 -29
  747. package/node_modules/string-width/index.js +0 -47
  748. package/node_modules/string-width/license +0 -9
  749. package/node_modules/string-width/package.json +0 -56
  750. package/node_modules/string-width/readme.md +0 -50
  751. package/node_modules/strip-ansi/index.d.ts +0 -17
  752. package/node_modules/strip-ansi/index.js +0 -4
  753. package/node_modules/strip-ansi/license +0 -9
  754. package/node_modules/strip-ansi/package.json +0 -54
  755. package/node_modules/strip-ansi/readme.md +0 -46
  756. package/node_modules/which-module/LICENSE +0 -13
  757. package/node_modules/which-module/README.md +0 -58
  758. package/node_modules/which-module/index.js +0 -9
  759. package/node_modules/which-module/package.json +0 -41
  760. package/node_modules/wrap-ansi/index.js +0 -186
  761. package/node_modules/wrap-ansi/license +0 -9
  762. package/node_modules/wrap-ansi/package.json +0 -61
  763. package/node_modules/wrap-ansi/readme.md +0 -97
  764. package/node_modules/ws/LICENSE +0 -20
  765. package/node_modules/ws/README.md +0 -548
  766. package/node_modules/ws/browser.js +0 -8
  767. package/node_modules/ws/index.js +0 -22
  768. package/node_modules/ws/lib/buffer-util.js +0 -131
  769. package/node_modules/ws/lib/constants.js +0 -19
  770. package/node_modules/ws/lib/event-target.js +0 -292
  771. package/node_modules/ws/lib/extension.js +0 -203
  772. package/node_modules/ws/lib/limiter.js +0 -55
  773. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  774. package/node_modules/ws/lib/receiver.js +0 -743
  775. package/node_modules/ws/lib/sender.js +0 -607
  776. package/node_modules/ws/lib/stream.js +0 -161
  777. package/node_modules/ws/lib/subprotocol.js +0 -62
  778. package/node_modules/ws/lib/validation.js +0 -152
  779. package/node_modules/ws/lib/websocket-server.js +0 -562
  780. package/node_modules/ws/lib/websocket.js +0 -1407
  781. package/node_modules/ws/package.json +0 -74
  782. package/node_modules/ws/wrapper.mjs +0 -21
  783. package/node_modules/y18n/CHANGELOG.md +0 -35
  784. package/node_modules/y18n/LICENSE +0 -13
  785. package/node_modules/y18n/README.md +0 -109
  786. package/node_modules/y18n/index.js +0 -188
  787. package/node_modules/y18n/package.json +0 -39
  788. package/node_modules/yargs/CHANGELOG.md +0 -420
  789. package/node_modules/yargs/LICENSE +0 -21
  790. package/node_modules/yargs/README.md +0 -140
  791. package/node_modules/yargs/build/lib/apply-extends.d.ts +0 -2
  792. package/node_modules/yargs/build/lib/apply-extends.js +0 -65
  793. package/node_modules/yargs/build/lib/argsert.d.ts +0 -2
  794. package/node_modules/yargs/build/lib/argsert.js +0 -65
  795. package/node_modules/yargs/build/lib/command.d.ts +0 -64
  796. package/node_modules/yargs/build/lib/command.js +0 -416
  797. package/node_modules/yargs/build/lib/common-types.d.ts +0 -36
  798. package/node_modules/yargs/build/lib/common-types.js +0 -25
  799. package/node_modules/yargs/build/lib/completion-templates.d.ts +0 -2
  800. package/node_modules/yargs/build/lib/completion-templates.js +0 -50
  801. package/node_modules/yargs/build/lib/completion.d.ts +0 -21
  802. package/node_modules/yargs/build/lib/completion.js +0 -135
  803. package/node_modules/yargs/build/lib/is-promise.d.ts +0 -1
  804. package/node_modules/yargs/build/lib/is-promise.js +0 -9
  805. package/node_modules/yargs/build/lib/levenshtein.d.ts +0 -1
  806. package/node_modules/yargs/build/lib/levenshtein.js +0 -58
  807. package/node_modules/yargs/build/lib/middleware.d.ts +0 -10
  808. package/node_modules/yargs/build/lib/middleware.js +0 -57
  809. package/node_modules/yargs/build/lib/obj-filter.d.ts +0 -1
  810. package/node_modules/yargs/build/lib/obj-filter.js +0 -14
  811. package/node_modules/yargs/build/lib/parse-command.d.ts +0 -11
  812. package/node_modules/yargs/build/lib/parse-command.js +0 -36
  813. package/node_modules/yargs/build/lib/process-argv.d.ts +0 -2
  814. package/node_modules/yargs/build/lib/process-argv.js +0 -31
  815. package/node_modules/yargs/build/lib/usage.d.ts +0 -49
  816. package/node_modules/yargs/build/lib/usage.js +0 -540
  817. package/node_modules/yargs/build/lib/validation.d.ts +0 -34
  818. package/node_modules/yargs/build/lib/validation.js +0 -330
  819. package/node_modules/yargs/build/lib/yargs.d.ts +0 -274
  820. package/node_modules/yargs/build/lib/yargs.js +0 -1190
  821. package/node_modules/yargs/build/lib/yerror.d.ts +0 -4
  822. package/node_modules/yargs/build/lib/yerror.js +0 -11
  823. package/node_modules/yargs/index.js +0 -40
  824. package/node_modules/yargs/locales/be.json +0 -46
  825. package/node_modules/yargs/locales/de.json +0 -46
  826. package/node_modules/yargs/locales/en.json +0 -51
  827. package/node_modules/yargs/locales/es.json +0 -46
  828. package/node_modules/yargs/locales/fi.json +0 -49
  829. package/node_modules/yargs/locales/fr.json +0 -53
  830. package/node_modules/yargs/locales/hi.json +0 -49
  831. package/node_modules/yargs/locales/hu.json +0 -46
  832. package/node_modules/yargs/locales/id.json +0 -50
  833. package/node_modules/yargs/locales/it.json +0 -46
  834. package/node_modules/yargs/locales/ja.json +0 -51
  835. package/node_modules/yargs/locales/ko.json +0 -49
  836. package/node_modules/yargs/locales/nb.json +0 -44
  837. package/node_modules/yargs/locales/nl.json +0 -49
  838. package/node_modules/yargs/locales/nn.json +0 -44
  839. package/node_modules/yargs/locales/pirate.json +0 -13
  840. package/node_modules/yargs/locales/pl.json +0 -49
  841. package/node_modules/yargs/locales/pt.json +0 -45
  842. package/node_modules/yargs/locales/pt_BR.json +0 -48
  843. package/node_modules/yargs/locales/ru.json +0 -46
  844. package/node_modules/yargs/locales/th.json +0 -46
  845. package/node_modules/yargs/locales/tr.json +0 -48
  846. package/node_modules/yargs/locales/zh_CN.json +0 -48
  847. package/node_modules/yargs/locales/zh_TW.json +0 -47
  848. package/node_modules/yargs/package.json +0 -92
  849. package/node_modules/yargs/yargs.js +0 -14
  850. package/node_modules/yargs-parser/CHANGELOG.md +0 -601
  851. package/node_modules/yargs-parser/LICENSE.txt +0 -14
  852. package/node_modules/yargs-parser/README.md +0 -449
  853. package/node_modules/yargs-parser/index.js +0 -1032
  854. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +0 -40
  855. package/node_modules/yargs-parser/package.json +0 -46
@@ -1,18 +1,42 @@
1
- import http from 'node:http';
2
- import { deviceStore, validId } from './device-store.mjs';
3
- import { randomUUID } from 'node:crypto';
4
- import { WebSocketServer } from 'ws';
1
+ import { privacyPage } from './privacy-page.mjs';
2
+ import { createAndroidReleaseHandler } from './android-releases.mjs';
3
+ import { androidReleaseStorage } from '../../relay-admin/android-release-storage.mjs';
4
+ import { longPairing } from './long-pairing.mjs';
5
+ import http from 'node:http';
6
+ import fs from 'node:fs';
7
+ import { isIP } from 'node:net';
8
+ import { deviceStore, validId } from './device-store.mjs';
9
+ import { randomUUID, timingSafeEqual } from 'node:crypto';
10
+ import { WebSocketServer } from 'ws';
5
11
  import { secret, digest, matches, validSecret, send, decode } from './wire.mjs';
12
+ import { routedFileBytes, parseFileBytes } from './file-wire.mjs';
6
13
  import { readDebugAllowlist, debugAllowed } from './debug-allowlist.mjs';
7
14
  import { validProbeSignal } from './p2p-probe.mjs';
8
- import { relayAdminState } from './admin-state.mjs';
15
+ import { MAX_ADMIN_REQUEST_BYTES, relayAdminState } from './admin-state.mjs';
16
+ import { openRelayMysql } from './mysql-state.mjs';
17
+ import { openEmailAccounts } from './email-accounts.mjs';
18
+ import { accountConnections } from './account-connections.mjs';
19
+ import { usageMeter, usageReport } from './usage-meter.mjs';
20
+ import { relayDiagnostics } from './diagnostics.mjs';
21
+ import { connectionInfo, deviceInfo } from './device-info.mjs';
22
+ import { listWorkstations, workstationDetail, workstationSummary, resolveWorkstationAccounts, resolveWorkstationConnections } from './admin-workstations.mjs';
23
+ import { phoneConnectionEvents, validPhoneConnection } from './phone-connections.mjs';
9
24
 
10
- // Always loopback; a TLS reverse proxy supplies the private-trial Internet endpoint.
25
+ // Always loopback; a TLS reverse proxy supplies the private-trial Internet endpoint.
11
26
  export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlMs = 120000,
12
- maxConnections = 32, maxClientsPerHost = 16, maxPayload = 8 * 1024 * 1024, stateFile,
13
- maxRelaySessions = 8, adminStateFile, publicRelayUrl = '', appDownloadUrl = '', debugAllowlistFile, log = () => {} } = {}) {
27
+ maxConnections = 128, maxPayload = 8 * 1024 * 1024, stateFile,
28
+ maxRelaySessions = 8, adminStateFile, mysqlConfigFile, accountConfigFile, usageDiagnosticKeyFile,
29
+ publicRelayUrl = '', appDownloadUrl = '', debugAllowlistFile, longPairingFile,
30
+ androidReleaseConfigFile, androidReleaseCredentialFile, log = () => {} } = {}) {
31
+ const androidReleases = createAndroidReleaseHandler({
32
+ ...(androidReleaseConfigFile ? androidReleaseStorage(androidReleaseConfigFile, androidReleaseCredentialFile) : {}), log });
33
+ const longInvitations = longPairing(longPairingFile);
34
+ longInvitations.list();
35
+ function longInvitation(code) {
36
+ try { return longInvitations.find(code); }
37
+ catch (error) { log({ event: 'long-pairing-error', message: error.message }); return undefined; }
38
+ }
14
39
  if (!validSecret(hostKey)) throw new Error('Invalid host credential');
15
- if (!Number.isSafeInteger(maxClientsPerHost) || maxClientsPerHost < 1 || maxClientsPerHost > 64) throw new Error('Invalid mobile device limit');
16
40
  if (!Number.isSafeInteger(maxRelaySessions) || maxRelaySessions < 1 || maxRelaySessions > 256) throw new Error('Invalid relay session limit');
17
41
  if (publicRelayUrl) relayPublicAddress(publicRelayUrl);
18
42
  // Harmony's native WebSocket supplies the endpoint Origin automatically.
@@ -20,24 +44,113 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
20
44
  const nativeOrigins = publicRelayUrl ? ['http:', 'https:'].map(protocol => `${protocol}//${new URL(publicRelayUrl).host}`) : [];
21
45
  if (appDownloadUrl && !/^https:\/\/[^\s]+$/.test(appDownloadUrl)) throw new Error('Invalid app download URL');
22
46
  const hostHash = digest(hostKey), rooms = new Map();
23
- const store = deviceStore(stateFile);
47
+ const database = mysqlConfigFile ? await openRelayMysql(mysqlConfigFile, maxRelaySessions) : null;
48
+ if (accountConfigFile && !database?.schemaVersions.includes('003_relay_usage')) throw new Error('Relay account and usage schema is required');
49
+ const accounts = accountConfigFile ? openEmailAccounts(database.pool, accountConfigFile) : null;
50
+ const accountSockets = accountConnections();
51
+ const meter = accounts ? usageMeter(database.pool) : null;
52
+ const cancelledAccountIds = new Set();
53
+ const accountCancellationWork = new Map();
54
+ const diagnostics = database?.schemaVersions.includes('006_relay_diagnostics')
55
+ ? relayDiagnostics(database.pool, error => log({ event: 'relay-diagnostic-error', message: error.message })) : null;
56
+ const adminAccessFile = '/etc/codex-relay/admin-access.json';
57
+ const adminAccessKey = fs.existsSync(adminAccessFile) ? JSON.parse(fs.readFileSync(adminAccessFile, 'utf8')).key : '';
58
+ if (adminAccessKey && !validSecret(adminAccessKey)) throw new Error('Invalid admin access key');
59
+ const diagnosticKey = usageDiagnosticKeyFile ? fs.readFileSync(usageDiagnosticKeyFile, 'utf8').trim() : '';
60
+ if (diagnosticKey && !/^[A-Za-z0-9_-]{43}$/.test(diagnosticKey)) throw new Error('Invalid usage diagnostic key');
61
+ const store = database?.devices || deviceStore(stateFile);
62
+ const phoneEvents = phoneConnectionEvents(database?.pool);
24
63
  const debugAllowlist = readDebugAllowlist(debugAllowlistFile);
25
- const adminState = relayAdminState(adminStateFile, maxRelaySessions);
64
+ const adminState = database?.admin || relayAdminState(adminStateFile, maxRelaySessions);
26
65
  let activeRelaySessions = 0;
27
- for (const record of store.entries()) rooms.set(record.id, { ...record, durable: true, connections: new Map(), observers: new Map() });
66
+ for (const record of store.entries()) rooms.set(record.id, { ...record, durable: true, connections: new Map(), observers: new Map(), uploadClients: new Map() });
67
+ let commitTail = Promise.resolve();
68
+ function queueStore(operation) {
69
+ const pending = commitTail.then(operation);
70
+ commitTail = pending.catch(() => {});
71
+ return pending;
72
+ }
28
73
  function commit(room, changes) {
29
- const next = { ...room, ...changes, updatedAt: new Date().toISOString() };
30
- if (room.durable) {
31
- const { id, hostHash, clients, revision, status, createdAt, updatedAt, officialComputer } = next;
32
- store.save({ id, hostHash, clients, revision, status, createdAt, updatedAt, ...(officialComputer ? { officialComputer } : {}) });
33
- }
34
- Object.assign(room, changes, { updatedAt: next.updatedAt });
35
- }
36
- function invitation(room) {
37
- const code = secret(); room.pairingHash = digest(code); room.expires = Date.now() + pairingTtlMs;
38
- return { code, expires: room.expires };
39
- }
74
+ return queueStore(async () => {
75
+ changes = typeof changes === 'function' ? changes(room) : changes;
76
+ const next = { ...room, ...changes, updatedAt: new Date().toISOString() };
77
+ if (room.durable) {
78
+ const { id, hostHash, clients, revision, status, createdAt, updatedAt, officialComputer } = next;
79
+ await store.save({ id, hostHash, clients, revision, status, createdAt, updatedAt, ...(officialComputer ? { officialComputer } : {}) });
80
+ }
81
+ Object.assign(room, changes, { updatedAt: next.updatedAt });
82
+ });
83
+ }
84
+ function registerRoom(id, durable, credential) {
85
+ return queueStore(async () => {
86
+ // Resolve concurrent first connections before writing the device identity.
87
+ if (durable && rooms.has(id)) return rooms.get(id);
88
+ const now = new Date().toISOString();
89
+ const record = { id, revision: 0, status: 'active', clients: [], createdAt: now, updatedAt: now,
90
+ ...(durable ? { hostHash: digest(credential) } : {}) };
91
+ if (durable) await store.save(record);
92
+ const room = { ...record, durable, connections: new Map(), observers: new Map(), uploadClients: new Map() };
93
+ rooms.set(id, room);
94
+ return room;
95
+ });
96
+ }
97
+ function recordConnection(room, clientId, at, info) {
98
+ return queueStore(async () => {
99
+ const client = room.clients.find(item => item.id === clientId);
100
+ if (!client) return false;
101
+ const updated = room.durable ? await store.recordConnection(room.id, clientId, at, info) : connectionInfo(client, at, info);
102
+ if (!updated) return false;
103
+ room.clients = room.clients.map(item => item.id === clientId ? updated : item);
104
+ return true;
105
+ });
106
+ }
107
+ async function manageDevices(room, ws, message, currentClientId) {
108
+ try {
109
+ if (!validId(message.commandId)) throw new Error('Invalid device management request');
110
+ if (message.type === 'devices-list') {
111
+ await commitTail;
112
+ if (currentClientId && !room.clients.some(item => item.id === currentClientId)) throw new Error('配对授权已撤销');
113
+ const clients = room.clients.map(({ id, createdAt, lastConnectedAt, platform, deviceName }) => ({
114
+ id, createdAt, lastConnectedAt: lastConnectedAt || null, platform: platform || null, deviceName: deviceName || null,
115
+ current: id === currentClientId,
116
+ })).sort((a, b) => (b.lastConnectedAt || b.createdAt).localeCompare(a.lastConnectedAt || a.createdAt));
117
+ send(ws, { type: message.type, commandId: message.commandId, clients, workstationOnline: Boolean(room.host) });
118
+ return;
119
+ }
120
+ if (!validId(message.id) || message.confirmed !== true) throw new Error('请确认要撤销的配对授权');
121
+ await commit(room, current => {
122
+ if (currentClientId && !current.clients.some(item => item.id === currentClientId)) throw new Error('配对授权已撤销');
123
+ return { clients: current.clients.filter(item => item.id !== message.id), revision: current.revision + 1 };
124
+ });
125
+ if (room.host) send(room.host, { type: 'authorization-changed', clients: room.clients, revision: room.revision });
126
+ // Acknowledge self-revocation before closing this connection.
127
+ send(ws, { type: message.type, commandId: message.commandId, id: message.id, revoked: true, workstationOnline: Boolean(room.host) });
128
+ const connection = room.connections.get(message.id);
129
+ if (connection) {
130
+ releaseRelay(connection);
131
+ if (room.host) send(room.host, { type: 'detach', session: connection.session });
132
+ room.connections.delete(message.id);
133
+ connection.ws.close(4401, 'Device authorization revoked');
134
+ }
135
+ const observer = room.observers?.get(message.id);
136
+ room.observers?.delete(message.id);
137
+ observer?.ws.close(4401, 'Device authorization revoked');
138
+ for (const [key, upload] of room.uploadClients || []) if (upload.clientId === message.id) {
139
+ room.uploadClients.delete(key);
140
+ if (room.uploadHost) send(room.uploadHost, { type: 'file-close', session: upload.session });
141
+ upload.ws.close(4401, 'Device authorization revoked');
142
+ }
143
+ for (const [socket, id] of room.deviceManagers || []) if (id === message.id) {
144
+ room.deviceManagers.delete(socket); socket.close(4401, 'Device authorization revoked');
145
+ }
146
+ } catch (error) { send(ws, { type: message.type, commandId: message.commandId, error: error.message }); }
147
+ }
148
+ function invitation(room) {
149
+ const code = secret(); room.pairingHash = digest(code); room.expires = Date.now() + pairingTtlMs;
150
+ return { code, expires: room.expires };
151
+ }
40
152
  let listenUrl = '';
153
+ let adminTail = Promise.resolve();
41
154
  function relayUrl(request) {
42
155
  if (publicRelayUrl) return relayPublicAddress(publicRelayUrl);
43
156
  const host = request.headers.host || new URL(listenUrl).host;
@@ -46,12 +159,31 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
46
159
  }
47
160
  function findInvitation(code) {
48
161
  if (!validSecret(code)) return undefined;
49
- return [...rooms.values()].find(room => room.status === 'active' && room.pairingHash && Date.now() < room.expires && matches(code, room.pairingHash));
162
+ const room = [...rooms.values()].find(room => room.status === 'active' && room.pairingHash && Date.now() < room.expires && matches(code, room.pairingHash));
163
+ if (room) return { room, expires: room.expires };
164
+ const invite = longInvitation(code), target = invite && rooms.get(invite.room);
165
+ return target?.status === 'active' ? { room: target, expires: invite.expires } : undefined;
50
166
  }
51
167
  const server = http.createServer((request, response) => {
52
168
  const target = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
169
+ if (target.pathname.startsWith('/code-relax/android/')) {
170
+ void androidReleases(request, response, target); return;
171
+ }
53
172
  if (target.pathname === '/admin' || target.pathname.startsWith('/admin/')) {
54
- void handleAdmin(request, response, target); return;
173
+ adminTail = adminTail.then(() => handleAdmin(request, response, target)).catch(() => {
174
+ if (!response.headersSent) adminReply(response, 500, { error: 'Admin operation failed' });
175
+ });
176
+ return;
177
+ }
178
+ if (target.pathname === '/account/privacy' && request.method === 'GET') {
179
+ response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache',
180
+ 'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'",
181
+ 'X-Content-Type-Options': 'nosniff' });
182
+ response.end(privacyPage); return;
183
+ }
184
+ if (accounts && target.pathname.startsWith('/account/')) {
185
+ void handleAccount(request, response, target).catch(() => accountReply(response, 500, { error: 'Account operation failed' }));
186
+ return;
55
187
  }
56
188
  if (request.method === 'GET' && target.pathname === '/announcement') {
57
189
  const body = JSON.stringify({ announcement: adminState.read().announcement });
@@ -61,19 +193,19 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
61
193
  response.writeHead(404, { 'Cache-Control': 'no-store' }); response.end(); return;
62
194
  }
63
195
  const code = target.searchParams.get('invite') || '';
64
- const room = findInvitation(code);
65
- if (!room) {
196
+ const invitation = findInvitation(code);
197
+ if (!invitation) {
66
198
  const json = target.pathname === '/pair/resolve' || /application\/json/i.test(request.headers.accept || '');
67
199
  const body = json ? JSON.stringify({ error: '配对邀请已过期或已使用' }) : pairingPage({ expired: true });
68
200
  response.writeHead(410, pairingHeaders(json ? 'application/json; charset=utf-8' : 'text/html; charset=utf-8', body)); response.end(body); return;
69
201
  }
70
202
  if (target.pathname === '/pair/resolve' || /application\/json/i.test(request.headers.accept || '')) {
71
- const body = JSON.stringify({ version: 6, relayUrl: relayUrl(request), room: room.id, expires: room.expires });
203
+ const body = JSON.stringify({ version: 6, relayUrl: relayUrl(request), room: invitation.room.id, expires: invitation.expires });
72
204
  response.writeHead(200, pairingHeaders('application/json; charset=utf-8', body)); response.end(body); return;
73
205
  }
74
206
  const secure = request.socket.encrypted || request.headers['x-forwarded-proto'] === 'https';
75
207
  const current = new URL(target.pathname + target.search, `${secure ? 'https' : 'http'}://${request.headers.host || new URL(listenUrl).host}`);
76
- const body = pairingPage({ deepLink: `codex-remote://pair?url=${encodeURIComponent(current.href)}`, expires: room.expires, appDownloadUrl });
208
+ const body = pairingPage({ deepLink: `codex-remote://pair?url=${encodeURIComponent(current.href)}`, expires: invitation.expires, appDownloadUrl });
77
209
  response.writeHead(200, pairingHeaders('text/html; charset=utf-8', body)); response.end(body);
78
210
  });
79
211
  function adminReply(response, status, body, type = 'application/json; charset=utf-8') {
@@ -82,30 +214,171 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
82
214
  'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'" });
83
215
  response.end(value);
84
216
  }
217
+ function accountReply(response, status, body) {
218
+ if (response.headersSent || response.destroyed) return;
219
+ const value = JSON.stringify(body);
220
+ response.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(value),
221
+ 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff' });
222
+ response.end(value);
223
+ }
224
+ function cancelAccountConnections(userId, token) {
225
+ const pending = accountCancellationWork.get(userId);
226
+ if (pending) return pending;
227
+ if (cancelledAccountIds.has(userId)) return Promise.resolve();
228
+ cancelledAccountIds.add(userId);
229
+ const work = (async () => {
230
+ try { await accounts.cancelAccount(token); }
231
+ catch (failure) { cancelledAccountIds.delete(userId); throw failure; }
232
+ for (const room of rooms.values()) {
233
+ for (const [key, arm] of room.usageArms || []) if (arm.userId === userId) room.usageArms.delete(key);
234
+ for (const [key, task] of room.usageTasks || []) if (task.userId === userId) room.usageTasks.delete(key);
235
+ for (const [clientId, connection] of room.connections) {
236
+ if (connection.userId !== userId) continue;
237
+ releaseRelay(connection); room.connections.delete(clientId);
238
+ if (room.host) send(room.host, { type: 'detach', session: connection.session });
239
+ connection.ws.close(4431, 'Account cancelled');
240
+ }
241
+ for (const [clientId, observer] of room.observers) {
242
+ if (observer.userId !== userId) continue;
243
+ room.observers.delete(clientId); observer.ws.close(4431, 'Account cancelled');
244
+ }
245
+ for (const [clientId, upload] of room.uploadClients) {
246
+ if (upload.userId !== userId) continue;
247
+ room.uploadClients.delete(clientId);
248
+ if (room.uploadHost) send(room.uploadHost, { type: 'file-close', session: upload.session });
249
+ upload.ws.close(4431, 'Account cancelled');
250
+ }
251
+ }
252
+ })().finally(() => accountCancellationWork.delete(userId));
253
+ accountCancellationWork.set(userId, work);
254
+ return work;
255
+ }
256
+ async function handleAccount(request, response, target) {
257
+ try {
258
+ if (target.pathname === '/account/config' && request.method === 'GET') {
259
+ accountReply(response, 200, { captchaAppId: accounts.captchaAppId }); return;
260
+ }
261
+ const bearer = /^Bearer ([A-Za-z0-9_-]{43})$/.exec(request.headers.authorization || '')?.[1];
262
+ if (target.pathname === '/account/me' && request.method === 'GET') {
263
+ const user = await accounts.current(bearer);
264
+ accountReply(response, 200, { user, usage: await accounts.usage(user.id) }); return;
265
+ }
266
+ if (target.pathname === '/account/logout' && request.method === 'POST') {
267
+ const result = await accounts.logout(bearer);
268
+ accountSockets.revoke(bearer);
269
+ accountReply(response, 200, result); return;
270
+ }
271
+ if (target.pathname === '/account/me' && request.method === 'DELETE') {
272
+ const user = await accounts.current(bearer);
273
+ await cancelAccountConnections(user.id, bearer);
274
+ accountReply(response, 200, { ok: true }); return;
275
+ }
276
+ if (request.method !== 'POST' || request.headers['content-type'] !== 'application/json') {
277
+ accountReply(response, 404, { error: 'Account route not found' }); return;
278
+ }
279
+ const body = JSON.parse(await readBody(request, 4096));
280
+ if (!body || typeof body !== 'object' || Array.isArray(body)) { accountReply(response, 400, { error: 'Invalid request body' }); return; }
281
+ if (target.pathname === '/account/email/request') {
282
+ const ip = request.headers['x-real-ip'] || request.socket.remoteAddress || '';
283
+ if (typeof ip !== 'string' || !isIP(ip)) { accountReply(response, 400, { error: 'Invalid client address' }); return; }
284
+ accountReply(response, 202, await accounts.requestCode(body, ip));
285
+ } else if (target.pathname === '/account/email/verify') {
286
+ accountReply(response, 200, await accounts.verifyCode(body));
287
+ } else accountReply(response, 404, { error: 'Account route not found' });
288
+ } catch (failure) {
289
+ accountReply(response, failure.status || 503, { error: failure.status ? failure.message : 'Account service unavailable' });
290
+ }
291
+ }
85
292
  async function handleAdmin(request, response, target) {
86
- if (request.method === 'GET' && target.pathname === '/admin') {
87
- adminReply(response, 200, adminPage(), 'text/html; charset=utf-8'); return;
293
+ const accessKey = request.headers['x-code-relax-admin-key'];
294
+ const internalAccess = adminAccessKey && validSecret(accessKey) &&
295
+ timingSafeEqual(Buffer.from(accessKey), Buffer.from(adminAccessKey));
296
+ const token = /^Bearer ([A-Za-z0-9_-]{43})$/.exec(request.headers.authorization || '')?.[1];
297
+ const diagnosticRoute = ['/admin/api/usage-diagnostics', '/admin/api/usage-diagnostics/drain'].includes(target.pathname);
298
+ const diagnosticAccess = diagnosticRoute && diagnosticKey && token &&
299
+ timingSafeEqual(Buffer.from(token), Buffer.from(diagnosticKey));
300
+ if (!internalAccess && !diagnosticAccess) { adminReply(response, 404, { error: 'Not found' }); return; }
301
+ if (request.method === 'GET' && target.pathname === '/admin/api/workstations') {
302
+ const workstations = await resolveWorkstationAccounts(listWorkstations(rooms, { details: true }), database?.pool);
303
+ await resolveWorkstationConnections(workstations, phoneEvents);
304
+ adminReply(response, 200, { sampledAt: new Date().toISOString(), workstations: workstations.map(workstationSummary) }); return;
305
+ }
306
+ if (request.method === 'GET' && target.pathname.startsWith('/admin/api/workstations/')) {
307
+ const id = target.pathname.slice('/admin/api/workstations/'.length);
308
+ const room = validId(id) && rooms.get(id);
309
+ if (!room) { adminReply(response, 404, { error: '未找到工作站' }); return; }
310
+ const [workstation] = await resolveWorkstationAccounts([
311
+ workstationDetail(room, debugAllowlist.devices.some(entry => entry.deviceId === room.id))], database?.pool);
312
+ await resolveWorkstationConnections([workstation], phoneEvents);
313
+ workstation.connectionEvents = await phoneEvents.list(room.id);
314
+ adminReply(response, 200, { sampledAt: new Date().toISOString(), workstation }); return;
88
315
  }
89
316
  if (request.method === 'GET' && target.pathname === '/admin/api/status') {
90
317
  const state = adminState.read();
91
318
  adminReply(response, 200, { maxRelaySessions: state.maxRelaySessions, activeRelaySessions,
92
319
  activeExemptRelaySessions: [...rooms.values()].reduce((sum, room) => sum + [...room.connections.values()].filter(connection => connection.relayExempt).length, 0),
93
320
  websocketConnections: wss.clients.size, onlineComputers: [...rooms.values()].filter(room => room.host).length,
94
- activePhones: [...rooms.values()].reduce((sum, room) => sum + room.connections.size, 0), announcement: state.announcement }); return;
321
+ activePhones: [...rooms.values()].reduce((sum, room) => sum + room.connections.size, 0),
322
+ processRssBytes: process.memoryUsage().rss, processUptimeSeconds: process.uptime(),
323
+ announcement: state.announcement, announcements: state.announcements }); return;
324
+ }
325
+ if (request.method === 'GET' && target.pathname === '/admin/api/usage' && meter) {
326
+ const page = Number(target.searchParams.get('page') || '1');
327
+ const view = target.searchParams.get('view') || 'user';
328
+ if (!Number.isSafeInteger(page) || page < 1 || page > 100000 ||
329
+ !['user', 'computer'].includes(view) || target.searchParams.has('user') ||
330
+ target.searchParams.has('computer')) {
331
+ adminReply(response, 400, { error: 'Invalid usage query' }); return;
332
+ }
333
+ adminReply(response, 200, await usageReport(database.pool, page, view)); return;
334
+ }
335
+ const diagnosticSource = target.searchParams.get('source') || 'usage';
336
+ if (target.pathname.startsWith('/admin/api/usage-diagnostics') && !['usage', 'account-cleanup'].includes(diagnosticSource)) {
337
+ adminReply(response, 400, { error: 'Invalid diagnostic source' }); return;
338
+ }
339
+ if (diagnostics && target.pathname === '/admin/api/usage-diagnostics') {
340
+ if (request.method === 'GET') adminReply(response, 200, await diagnostics.read(diagnosticSource));
341
+ else if (request.method === 'DELETE') adminReply(response, 200, await diagnostics.clear(diagnosticSource));
342
+ else adminReply(response, 405, { error: 'Unsupported diagnostic request' });
343
+ return;
344
+ }
345
+ if (diagnostics && target.pathname === '/admin/api/usage-diagnostics/drain') {
346
+ if (request.method === 'POST') adminReply(response, 200, await diagnostics.drain(diagnosticSource));
347
+ else adminReply(response, 405, { error: 'Unsupported diagnostic request' });
348
+ return;
95
349
  }
96
350
  if (!['PUT', 'DELETE'].includes(request.method || '') || request.headers['content-type'] !== 'application/json' ||
97
351
  request.headers['x-code-relax-admin'] !== '1') { adminReply(response, 405, { error: 'Unsupported admin request' }); return; }
98
352
  try {
99
- const body = request.method === 'DELETE' ? {} : JSON.parse(await readBody(request, 4096));
353
+ const body = request.method === 'DELETE' ? {} : JSON.parse(await readBody(request, MAX_ADMIN_REQUEST_BYTES));
100
354
  if (target.pathname === '/admin/api/settings' && request.method === 'PUT') {
101
- const state = adminState.setMaxRelaySessions(body.maxRelaySessions);
355
+ const state = await adminState.setMaxRelaySessions(body.maxRelaySessions);
102
356
  log({ event: 'admin-relay-limit-updated', maxRelaySessions: state.maxRelaySessions });
103
- } else if (target.pathname === '/admin/api/announcement' && request.method === 'PUT') {
104
- const state = adminState.publish(body.title, body.message); broadcastAnnouncement(state.announcement);
105
- log({ event: 'admin-announcement-published', announcementId: state.announcement.id });
106
- } else if (target.pathname === '/admin/api/announcement' && request.method === 'DELETE') {
107
- const state = adminState.withdraw(); broadcastAnnouncement(state.announcement);
108
- log({ event: 'admin-announcement-withdrawn' });
357
+ } else if (target.pathname === '/admin/api/announcements' && request.method === 'PUT') {
358
+ const state = await adminState.createAnnouncement(body.title, body.message, body.publish === true);
359
+ if (body.publish === true) {
360
+ broadcastAnnouncement(state.announcement);
361
+ log({ event: 'admin-announcement-published', announcementId: state.announcement.id });
362
+ } else log({ event: 'admin-announcement-draft-created', announcementId: state.announcements[0].id });
363
+ } else if (target.pathname.startsWith('/admin/api/announcements/')) {
364
+ const parts = target.pathname.slice('/admin/api/announcements/'.length).split('/');
365
+ const id = parts[0];
366
+ if (!/^[\w-]{16,128}$/.test(id)) { adminReply(response, 404, { error: 'Admin route not found' }); return; }
367
+ if (parts.length === 1 && request.method === 'PUT') {
368
+ await adminState.updateDraft(id, body.title, body.message);
369
+ log({ event: 'admin-announcement-draft-updated', announcementId: id });
370
+ } else if (parts.length === 1 && request.method === 'DELETE') {
371
+ await adminState.deleteAnnouncement(id);
372
+ log({ event: 'admin-announcement-deleted', announcementId: id });
373
+ } else if (parts.length === 2 && parts[1] === 'publish' && request.method === 'PUT') {
374
+ const state = await adminState.publishDraft(id, body.title, body.message);
375
+ broadcastAnnouncement(state.announcement);
376
+ log({ event: 'admin-announcement-published', announcementId: id });
377
+ } else if (parts.length === 2 && parts[1] === 'withdraw' && request.method === 'PUT') {
378
+ const state = await adminState.withdraw(id);
379
+ broadcastAnnouncement(state.announcement);
380
+ log({ event: 'admin-announcement-withdrawn', announcementId: id });
381
+ } else { adminReply(response, 404, { error: 'Admin route not found' }); return; }
109
382
  } else { adminReply(response, 404, { error: 'Admin route not found' }); return; }
110
383
  adminReply(response, 200, { ok: true });
111
384
  } catch (error) { adminReply(response, 400, { error: error.message }); }
@@ -137,23 +410,44 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
137
410
  serverNoContextTakeover: true, clientNoContextTakeover: true,
138
411
  threshold: 1024, concurrencyLimit: 4, zlibDeflateOptions: { level: 3 },
139
412
  } });
140
- let admissionStart = Date.now(), admissions = 0;
141
- server.on('upgrade', (request, socket, head) => {
413
+ let admissionStart = Date.now(), admissions = 0;
414
+ server.on('upgrade', (request, socket, head) => {
142
415
  if (request.url !== '/relay' || (request.headers.origin && request.headers.origin !== 'null' && !nativeOrigins.includes(request.headers.origin))) {
143
416
  log({ event: 'upgrade-rejected', reason: request.url !== '/relay' ? 'path' : 'origin' });
144
- socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n'); return;
145
- }
146
- if (Date.now() - admissionStart >= 60000) { admissionStart = Date.now(); admissions = 0; }
147
- if (wss.clients.size >= maxConnections || ++admissions > 60) {
148
- socket.end('HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n\r\n'); return;
149
- }
150
- wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws));
151
- });
152
- wss.on('connection', ws => {
417
+ socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n'); return;
418
+ }
419
+ if (Date.now() - admissionStart >= 60000) { admissionStart = Date.now(); admissions = 0; }
420
+ if (wss.clients.size >= maxConnections || ++admissions > 240) {
421
+ socket.end('HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n\r\n'); return;
422
+ }
423
+ wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request));
424
+ });
425
+ wss.on('connection', (ws, request) => {
426
+ const address = request.headers['x-real-ip'];
427
+ ws.adminInfo = { connectedAt: new Date().toISOString(),
428
+ address: typeof address === 'string' && isIP(address) ? address : request.socket.remoteAddress };
153
429
  let role, room, session, clientId, alive = true;
154
- const authTimer = setTimeout(() => ws.close(1008, 'Authentication timeout'), 5000);
155
- ws.on('pong', () => { alive = true; });
156
- const heartbeat = setInterval(() => { if (!alive) ws.terminate(); else { alive = false; ws.ping(); } }, 30000);
430
+ const phoneEventIds = new Set();
431
+ let accountConnection;
432
+ function invalidateAccountConnection() {
433
+ if (role === 'client' && room?.connections.get(clientId)?.ws === ws) {
434
+ releaseRelay(room.connections.get(clientId));
435
+ room.connections.delete(clientId);
436
+ // Detach also closes the workstation RTC peer; closing only the
437
+ // phone's signaling socket would leave the direct data path alive.
438
+ if (room.host) send(room.host, { type: 'detach', session });
439
+ } else if (role === 'file-client' && room?.uploadClients.get(session)?.ws === ws) {
440
+ room.uploadClients.delete(session);
441
+ if (room.uploadHost) send(room.uploadHost, { type: 'file-close', session });
442
+ }
443
+ ws.close(4431, 'Account session invalid');
444
+ }
445
+ const authTimer = setTimeout(() => ws.close(1008, 'Authentication timeout'), 5000);
446
+ ws.on('pong', () => { alive = true; });
447
+ const heartbeat = setInterval(() => {
448
+ sampleInboundQueue();
449
+ if (!alive) ws.terminate(); else { alive = false; ws.ping(); }
450
+ }, 30000);
157
451
  const fail = (code = 4401, reason = 'Device authorization rejected') => ws.close(code, reason);
158
452
  const queueClientMessage = (connection, message, pause = 0) => {
159
453
  const bytes = Buffer.byteLength(JSON.stringify(message));
@@ -167,27 +461,133 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
167
461
  send(connection.ws, message);
168
462
  }).catch(() => connection.ws.close(1011, 'Relay delivery failed'));
169
463
  };
170
- ws.on('error', () => {}); // close owns routing cleanup.
171
- ws.on('close', (code, reason) => {
172
- log({ event: 'closed', role: role || 'unauthenticated', device: room?.id, code, reason: reason.toString() });
173
- clearTimeout(authTimer); clearInterval(heartbeat);
464
+ ws.on('error', () => {}); // close owns routing cleanup.
465
+ ws.on('close', (code, reason) => {
466
+ log({ event: 'closed', role: role || 'unauthenticated', device: room?.id, code, reason: reason.toString() });
467
+ clearTimeout(authTimer); clearInterval(heartbeat);
468
+ accountConnection?.dispose();
174
469
  if (role === 'host' && room?.host === ws) {
470
+ for (const task of room.usageTasks?.values() || []) {
471
+ void meter?.lost(task).catch(error => log({ event: 'usage-error', message: error.message }));
472
+ }
473
+ room.usageTasks?.clear(); room.usageArms?.clear();
175
474
  room.host = undefined; room.hostName = undefined; room.lanCandidates = [];
475
+ room.uploadHost?.close(4410, 'Computer offline');
476
+ for (const client of room.uploadClients?.values() || []) client.ws.close(code === 4432 ? 4432 : 4410, code === 4432 ? 'Computer inbound queue limit exceeded' : 'Computer offline');
176
477
  for (const observer of room.observers.values()) send(observer.ws, { type: 'observing', online: false });
177
478
  if (!room.durable) rooms.delete(room.id);
178
- for (const connection of room.connections.values()) connection.ws.close(4410, 'Computer offline; authorization retained');
479
+ for (const connection of room.connections.values()) connection.ws.close(
480
+ code === 4432 ? 4432 : 4410,
481
+ code === 4432 ? 'Computer inbound queue limit exceeded' : 'Computer offline; authorization retained');
179
482
  } else if (role === 'client' && room?.connections.get(clientId)?.ws === ws) {
180
- releaseRelay(room.connections.get(clientId));
483
+ const connection = room.connections.get(clientId);
484
+ releaseRelay(connection);
181
485
  room.connections.delete(clientId);
182
486
  if (room.host) send(room.host, { type: 'detach', session });
183
487
  } else if (role === 'observer' && room?.observers.get(clientId)?.ws === ws) {
184
488
  room.observers.delete(clientId);
185
- }
186
- });
187
- ws.on('message', (bytes, binary) => {
188
- try {
189
- const message = decode(bytes, binary);
190
- if (!role) {
489
+ } else if (role === 'file-host' && room?.uploadHost === ws) {
490
+ room.uploadHost = undefined;
491
+ for (const client of room.uploadClients?.values() || []) client.ws.close(code === 4432 ? 4432 : 4410, code === 4432 ? 'File receiver inbound queue limit exceeded' : 'File receiver offline');
492
+ } else if (role === 'file-client' && room?.uploadClients?.get(session)?.ws === ws) {
493
+ room.uploadClients.delete(session);
494
+ if (room.uploadHost) send(room.uploadHost, { type: 'file-close', session });
495
+ }
496
+ });
497
+ let messageTail = Promise.resolve();
498
+ // Include the in-flight message: async database work must not permit an
499
+ // unbounded chain of closures retaining incoming payloads.
500
+ let pendingMessages = 0, pendingBytes = 0;
501
+ const maxPendingMessages = 128, maxPendingBytes = 16 * 1024 * 1024;
502
+ let sampleStarted = Date.now(), peakMessages = 0, peakBytes = 0, maxWaitMs = 0, receivedMessages = 0;
503
+ function sampleInboundQueue() {
504
+ const now = Date.now();
505
+ if (now - sampleStarted < 60000) return;
506
+ // Summarize only intervals with an actual backlog; never log payloads or credentials.
507
+ if (peakMessages > 1) log({ event: 'inbound-queue-sample', role: role || 'unauthenticated',
508
+ windowMs: now - sampleStarted, receivedMessages, peakMessages, peakBytes, maxWaitMs,
509
+ pendingMessages, pendingBytes });
510
+ sampleStarted = now; peakMessages = pendingMessages; peakBytes = pendingBytes;
511
+ maxWaitMs = 0; receivedMessages = 0;
512
+ }
513
+ ws.on('message', (bytes, binary) => {
514
+ if (ws.readyState !== ws.OPEN) return;
515
+ const size = bytes.length;
516
+ if (pendingMessages >= maxPendingMessages || pendingBytes + size > maxPendingBytes) {
517
+ log({ event: 'inbound-queue-limit', role: role || 'unauthenticated',
518
+ pendingMessages, pendingBytes, incomingBytes: size, maxWaitMs });
519
+ ws.close(4432, 'Inbound queue limit exceeded');
520
+ return;
521
+ }
522
+ pendingMessages++; pendingBytes += size;
523
+ receivedMessages++;
524
+ peakMessages = Math.max(peakMessages, pendingMessages); peakBytes = Math.max(peakBytes, pendingBytes);
525
+ const enqueuedAt = Date.now();
526
+ messageTail = messageTail.then(async () => {
527
+ try {
528
+ maxWaitMs = Math.max(maxWaitMs, Date.now() - enqueuedAt);
529
+ if (ws.readyState !== ws.OPEN) return;
530
+ if (accountConnection && !accountConnection.valid()) return;
531
+ if (binary) {
532
+ if (role === 'file-host' && room.uploadHost === ws) {
533
+ const frame = parseFileBytes(bytes);
534
+ const client = room.uploadClients.get(frame.session);
535
+ if (!client || client.ws.readyState !== ws.OPEN) return;
536
+ if (client.ws.bufferedAmount > 1024 * 1024) return client.ws.close(1013, 'File receiver too slow');
537
+ client.ws.send(frame.data); return;
538
+ }
539
+ if (role === 'file-client' && room.uploadClients?.get(session)?.ws === ws) {
540
+ if (room.uploadHost?.readyState !== ws.OPEN) return fail(4410, 'File receiver offline');
541
+ if (room.uploadHost.bufferedAmount > 1024 * 1024) return fail(1013, 'File receiver too slow');
542
+ room.uploadHost.send(routedFileBytes(session, bytes)); return;
543
+ }
544
+ return fail(1008, 'Invalid binary frame');
545
+ }
546
+ const message = decode(bytes, binary);
547
+ if (!role) {
548
+ let userId;
549
+ if (!['host', 'file-host', 'observe', 'device-management'].includes(message.type) &&
550
+ !(['pair', 'resume'].includes(message.type) && message.management === true)) {
551
+ if (!['pair', 'resume', 'observe', 'file-client'].includes(message.type)) return fail();
552
+ if (!accounts || !validSecret(message.accountToken)) return fail(4431, 'Account login required');
553
+ accountConnection = accountSockets.track(message.accountToken, invalidateAccountConnection);
554
+ try {
555
+ const user = await accounts.current(message.accountToken);
556
+ if (!accountConnection.activate(user.expiresAt)) return;
557
+ userId = user.id;
558
+ ws.adminInfo.account = { id: user.id, email: user.email };
559
+ }
560
+ catch { return fail(4431, 'Account session invalid'); }
561
+ if (ws.readyState !== ws.OPEN) return;
562
+ if (cancelledAccountIds.has(userId)) return fail(4431, 'Account cancelled');
563
+ }
564
+ if (message.type === 'file-host') {
565
+ room = rooms.get(message.deviceId);
566
+ if (!room?.host || room.status !== 'active' || !matches(message.deviceSecret, room.hostHash)) return fail();
567
+ role = 'file-host'; clearTimeout(authTimer);
568
+ room.uploadClients ||= new Map();
569
+ room.uploadHost?.close(4409, 'File receiver replaced');
570
+ room.uploadHost = ws;
571
+ send(ws, { type: 'file-host-ready' }); return;
572
+ }
573
+ if (message.type === 'file-client') {
574
+ room = rooms.get(message.room);
575
+ const client = room?.clients.find(item => item.id === message.clientId);
576
+ if (!room?.host || !room.uploadHost || room.status !== 'active' || !client ||
577
+ !matches(message.credential, client.hash)) return fail();
578
+ room.uploadClients ||= new Map();
579
+ if (!['upload', 'download'].includes(message.purpose)) return fail(1008, 'Invalid file purpose');
580
+ const same = [...room.uploadClients.values()].filter(item => item.clientId === message.clientId && item.purpose === message.purpose);
581
+ const all = [...rooms.values()].flatMap(item => [...(item.uploadClients?.values() || [])]);
582
+ if (same.length >= (message.purpose === 'upload' ? 1 : 3) ||
583
+ all.length >= 8 || (message.purpose === 'upload' && all.filter(item => item.purpose === 'upload').length >= 2)) {
584
+ return fail(4429, 'Too many file transfers');
585
+ }
586
+ role = 'file-client'; clientId = message.clientId; session = randomUUID(); clearTimeout(authTimer);
587
+ if (!await recordConnection(room, clientId, new Date().toISOString(), deviceInfo(message))) return fail(4401, 'Device authorization revoked');
588
+ room.uploadClients.set(session, { ws, session, clientId, userId, purpose: message.purpose });
589
+ send(room.uploadHost, { type: 'file-open', session, purpose: message.purpose }); return;
590
+ }
191
591
  if (message.type === 'host') {
192
592
  const durable = message.deviceId !== undefined;
193
593
  if (durable && (!validId(message.deviceId) || !validSecret(message.deviceSecret))) return fail();
@@ -199,21 +599,25 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
199
599
  }
200
600
  catch { return fail(1008, 'Invalid LAN candidates'); }
201
601
  room = durable ? rooms.get(message.deviceId) : undefined;
202
- if (room && (room.status !== 'active' || !matches(message.deviceSecret, room.hostHash))) return fail();
203
602
  if (!room) {
204
- // Durable computers prove future ownership with their generated device secret.
205
- // The legacy deployment key remains accepted, but is no longer required for first registration.
206
603
  if (!durable && !matches(message.key, hostHash)) return fail();
207
- if (rooms.size >= 256) return fail(4429, 'Device limit reached');
208
- const now = new Date().toISOString();
209
- room = { id: durable ? message.deviceId : randomUUID(), durable,
210
- revision: 0, status: 'active', clients: [], connections: new Map(), observers: new Map(), createdAt: now, updatedAt: now,
211
- ...(durable ? { hostHash: digest(message.deviceSecret) } : {}) };
212
- commit(room, {}); rooms.set(room.id, room);
213
- }
604
+ room = await registerRoom(durable ? message.deviceId : randomUUID(), durable, message.deviceSecret);
605
+ if (ws.readyState !== ws.OPEN) return;
606
+ }
607
+ if (durable && (room.status !== 'active' || !matches(message.deviceSecret, room.hostHash))) return fail();
214
608
  const oldHost = room.host;
215
609
  room.observers ||= new Map();
610
+ if (oldHost && oldHost !== ws) {
611
+ for (const task of room.usageTasks?.values() || []) {
612
+ try { await meter?.lost(task); } catch (error) { log({ event: 'usage-error', message: error.message }); }
613
+ }
614
+ room.usageTasks?.clear(); room.usageArms?.clear();
615
+ }
216
616
  room.host = ws; room.hostName = hostName; room.lanCandidates = lanCandidates;
617
+ room.adminPhoneConnections = undefined;
618
+ room.adminLastOnlineAt = ws.adminInfo.connectedAt; room.adminLastHostName = hostName;
619
+ room.usageTasks ||= new Map(); room.usageArms ||= new Map();
620
+ if (oldHost && oldHost !== ws) room.uploadHost?.close(4409, 'Computer connection replaced');
217
621
  room.p2pProbe = message.p2pProbe === 1; room.p2pProbeRepeat = message.p2pProbeRepeat === true;
218
622
  role = 'host'; clearTimeout(authTimer);
219
623
  if (oldHost && oldHost !== ws) {
@@ -232,32 +636,50 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
232
636
  room = rooms.get(message.room);
233
637
  if (!room || room.status !== 'active') return fail();
234
638
  let credential;
235
- if (message.type === 'pair' && room.pairingHash && Date.now() < room.expires && matches(message.code, room.pairingHash)) {
639
+ const longInvite = message.type === 'pair' ? longInvitation(message.code) : undefined;
640
+ const reusableInvite = longInvite?.room === room.id;
641
+ if (message.type === 'pair' && !room.pairingInProgress && (reusableInvite ||
642
+ (room.pairingHash && Date.now() < room.expires && matches(message.code, room.pairingHash)))) {
236
643
  if (!room.host) return fail(4410, 'Computer offline; authorization retained');
237
644
  credential = message.credential || secret();
238
645
  clientId = message.clientId || (!room.durable ? randomUUID() : undefined);
239
646
  if (!validSecret(credential) || !validId(clientId)) return fail();
240
647
  const existing = room.clients.find(client => client.id === clientId);
241
- if (!existing && room.clients.length >= maxClientsPerHost) return fail(4429, 'Mobile device limit reached');
242
648
  const now = new Date().toISOString();
243
- const client = { id: clientId, hash: digest(credential), createdAt: existing?.createdAt || now, updatedAt: now };
244
- commit(room, { clients: [...room.clients.filter(item => item.id !== clientId), client], revision: room.revision + 1 });
245
- room.pairingHash = undefined;
649
+ const client = { ...existing, id: clientId, hash: digest(credential), createdAt: existing?.createdAt || now, updatedAt: now,
650
+ ...deviceInfo(message), lastConnectedAt: now };
651
+ room.pairingInProgress = true;
652
+ try {
653
+ await commit(room, current => ({ clients: [...current.clients.filter(item => item.id !== clientId), client], revision: current.revision + 1 }));
654
+ if (!reusableInvite) room.pairingHash = undefined;
655
+ } finally { room.pairingInProgress = false; }
656
+ if (!room.host) return fail(4410, 'Computer offline; authorization retained');
246
657
  send(room.host, { type: 'authorization-changed', clients: room.clients, revision: room.revision });
247
658
  } else {
248
659
  clientId = message.clientId;
249
660
  const client = room.clients.find(item => item.id === clientId) ||
250
661
  (!room.durable && !clientId ? room.clients.find(item => matches(message.credential, item.hash)) : undefined);
251
662
  if (client) clientId = client.id;
252
- if (!['resume', 'pair', 'observe'].includes(message.type) || !client || !matches(message.credential, client.hash)) {
663
+ if (!['resume', 'pair', 'observe', 'device-management'].includes(message.type) || !client || !matches(message.credential, client.hash)) {
253
664
  return fail(message.type === 'pair' ? 4408 : 4401, message.type === 'pair' ? 'Pairing code expired or used' : 'Device authorization rejected');
254
665
  }
255
666
  }
667
+ if (ws.readyState !== ws.OPEN) return;
668
+ if (accountConnection && !accountConnection.valid()) return;
669
+ if (cancelledAccountIds.has(userId)) return fail(4431, 'Account cancelled');
670
+ if (!await recordConnection(room, clientId, new Date().toISOString(), deviceInfo(message))) return fail(4401, 'Device authorization revoked');
671
+ if (ws.readyState !== ws.OPEN) return;
672
+ if (message.type === 'device-management') {
673
+ role = 'device-manager'; clearTimeout(authTimer); room.deviceManagers ||= new Map();
674
+ room.deviceManagers.set(ws, clientId);
675
+ ws.once('close', () => room.deviceManagers.delete(ws));
676
+ send(ws, { type: 'device-management-ready' }); return;
677
+ }
256
678
  if (message.type === 'observe') {
257
679
  role = 'observer'; clearTimeout(authTimer); room.observers ||= new Map();
258
680
  const oldObserver = room.observers.get(clientId);
259
681
  if (oldObserver?.ws && oldObserver.ws !== ws) oldObserver.ws.close(4409, 'Metadata connection replaced by same identity');
260
- room.observers.set(clientId, { ws });
682
+ room.observers.set(clientId, { ws, userId });
261
683
  send(ws, { type: 'observing', online: Boolean(room.host) });
262
684
  send(ws, { type: 'debug-permissions', enabled: debugAllowed(debugAllowlist, room.id, room.clients.find(client => client.id === clientId)) });
263
685
  const announcement = adminState.read().announcement;
@@ -275,7 +697,8 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
275
697
  send(room.host, { type: 'detach', session: oldConnection.session });
276
698
  oldConnection.ws.close(4409, 'Phone connection replaced by same identity');
277
699
  }
278
- room.connections.set(clientId, { ws, session, p2pProbe: room.p2pProbe && message.p2pProbe === 1,
700
+ room.connections.set(clientId, { ws, session, p2pProbe: Boolean(userId) && room.p2pProbe && message.p2pProbe === 1,
701
+ userId,
279
702
  repeat: room.p2pProbeRepeat, signals: 0, probeStarted: 0, outbound: Promise.resolve(), queuedBytes: 0,
280
703
  relayAdmitted: false, relayExempt: false,
281
704
  capacityExempt: debugAllowed(debugAllowlist, room.id, room.clients.find(client => client.id === clientId)) });
@@ -284,10 +707,133 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
284
707
  if (room.lanCandidates?.length) send(ws, { type: 'lan-candidates', candidates: room.lanCandidates });
285
708
  const announcement = adminState.read().announcement;
286
709
  if (announcement) send(ws, { type: 'announcement', announcement });
287
- send(room.host, { type: 'attach', session, clientId, p2pProbe: room.connections.get(clientId).p2pProbe ? 1 : 0 }); return;
288
- }
710
+ send(room.host, { type: 'attach', session, clientId, chat: Boolean(userId),
711
+ p2pProbe: room.connections.get(clientId).p2pProbe ? 1 : 0 }); return;
712
+ }
713
+ if (role === 'device-manager') {
714
+ if (!room.clients.some(item => item.id === clientId)) return fail(4401, 'Device authorization revoked');
715
+ if (!['devices-list', 'device-revoke'].includes(message.type)) return fail(1008, 'Invalid device management command');
716
+ await manageDevices(room, ws, message, clientId); return;
717
+ }
289
718
  if (role === 'host') {
719
+ if (['phone-connection', 'phone-connections'].includes(message.type)) {
720
+ if (room.host !== ws) return;
721
+ if (message.type === 'phone-connections') {
722
+ if (!Array.isArray(message.active) || message.active.length > 128 ||
723
+ message.active.some(item => !validPhoneConnection(item))) return fail(1008, 'Invalid phone connection snapshot');
724
+ room.adminPhoneConnections = new Map(message.active.filter(item => room.clients.some(client => client.id === item.clientId))
725
+ .map(item => [item.connectionId, { connectionId: item.connectionId, clientId: item.clientId, mode: item.mode }]));
726
+ return;
727
+ }
728
+ const event = message.event;
729
+ if (!validPhoneConnection(event)) return fail(1008, 'Invalid phone connection event');
730
+ // Revocation can overtake the workstation's final close event.
731
+ if (!room.clients.some(client => client.id === event.clientId)) {
732
+ send(ws, { type: 'phone-connection-ack', id: event.id }); return;
733
+ }
734
+ try { await phoneEvents.record(room.id, event); }
735
+ catch (error) { log({ event: 'phone-connection-error', message: error.message }); return; }
736
+ if (room.host !== ws) return;
737
+ if (phoneEventIds.has(event.id)) { send(ws, { type: 'phone-connection-ack', id: event.id }); return; }
738
+ phoneEventIds.add(event.id);
739
+ if (phoneEventIds.size > 256) phoneEventIds.delete(phoneEventIds.values().next().value);
740
+ room.adminPhoneConnections ||= new Map();
741
+ if (event.state === 'connected') room.adminPhoneConnections.set(event.connectionId,
742
+ { connectionId: event.connectionId, clientId: event.clientId, mode: event.mode });
743
+ else room.adminPhoneConnections.delete(event.connectionId);
744
+ send(ws, { type: 'phone-connection-ack', id: event.id }); return;
745
+ }
290
746
  if (room.host !== ws) return;
747
+ if (['devices-list', 'device-revoke'].includes(message.type)) { await manageDevices(room, ws, message); return; }
748
+ if (message.type === 'device-connected') {
749
+ const at = new Date(Math.min(Date.parse(message.at), Date.now())).toISOString();
750
+ if (!validId(message.clientId)) return fail(1008, 'Invalid mobile identity');
751
+ await recordConnection(room, message.clientId, at, deviceInfo(message));
752
+ room.adminLanConnections ||= new Map();
753
+ if (room.clients.some(client => client.id === message.clientId) &&
754
+ (!room.adminLanConnections.has(message.clientId) || room.adminLanConnections.get(message.clientId) < at)) {
755
+ room.adminLanConnections.set(message.clientId, at);
756
+ }
757
+ return;
758
+ }
759
+ if (meter && ['usage-arm', 'usage-message', 'usage-cancel', 'usage-turn', 'usage-observer-lost'].includes(message.type)) {
760
+ if (message.type === 'usage-observer-lost') {
761
+ for (const task of room.usageTasks.values()) {
762
+ try { await meter.lost(task); } catch (error) { log({ event: 'usage-error', message: error.message }); }
763
+ }
764
+ room.usageTasks.clear();
765
+ return;
766
+ }
767
+ const armKey = `${message.session}:${message.requestId}`;
768
+ const connection = [...room.connections.values()].find(item => item.session === message.session);
769
+ if (message.type === 'usage-arm') {
770
+ if (!connection?.userId || !Number.isSafeInteger(message.requestId) || message.requestId < 1 ||
771
+ (message.threadId !== null && (!plainText(message.threadId, 128, false)))) {
772
+ diagnostics?.record('usage', 'arm-rejected', !connection?.userId ? 'account-missing' : 'invalid-request');
773
+ return;
774
+ }
775
+ for (const [key, arm] of room.usageArms) if (Date.now() - arm.at > 120000) room.usageArms.delete(key);
776
+ if (room.usageArms.size >= 256) return;
777
+ room.usageArms.set(armKey, { userId: connection.userId,
778
+ clientId: [...room.connections.entries()].find(([, item]) => item === connection)?.[0],
779
+ session: message.session, at: Date.now() });
780
+ return;
781
+ }
782
+ if (message.type === 'usage-cancel') {
783
+ room.usageArms.delete(armKey); return;
784
+ }
785
+ if (message.type === 'usage-message') {
786
+ const arm = room.usageArms.get(armKey);
787
+ room.usageArms.delete(armKey);
788
+ if (!arm || Date.now() - arm.at > 120000 || !plainText(message.messageKey, 128, false)) {
789
+ diagnostics?.record('usage', 'message-rejected', !arm ? 'arm-missing' : 'expired-or-invalid');
790
+ return;
791
+ }
792
+ try {
793
+ await meter.action(arm, room.id, arm.clientId, 'message', message.messageKey);
794
+ if (['start', 'steer'].includes(message.turnKind) &&
795
+ plainText(message.threadId, 80, false) && plainText(message.turnId, 80, false)) {
796
+ const key = `${message.threadId}:${message.turnId}`;
797
+ if (key.length <= 128) {
798
+ const counted = await meter.action(arm, room.id, arm.clientId, 'turn', key);
799
+ if (counted && message.turnKind === 'steer') diagnostics?.record('usage', 'turn-recovered', 'steer');
800
+ if (!room.usageTasks.has(key) && message.observerReady === true && message.completed !== true &&
801
+ (message.turnKind === 'start' || message.active === true)) {
802
+ const task = await meter.turnOrigin(room.id, key);
803
+ if (task) {
804
+ await meter.pulse(task, true, room.id, task.clientId, counted ? key : randomUUID());
805
+ room.usageTasks.set(key, task);
806
+ }
807
+ }
808
+ }
809
+ }
810
+ } catch (error) { diagnostics?.record('usage', 'message-error', 'database'); log({ event: 'usage-error', message: error.message }); }
811
+ return;
812
+ }
813
+ if (!['turn/started', 'turn/completed', 'heartbeat', 'resume', 'inactive'].includes(message.method) ||
814
+ !plainText(message.threadId, 80, false) || !plainText(message.turnId, 80, false)) return;
815
+ const key = `${message.threadId}:${message.turnId}`;
816
+ if (key.length > 128) return;
817
+ let task = room.usageTasks.get(key);
818
+ try {
819
+ if (message.method === 'resume' && !task) {
820
+ task = await meter.turnOrigin(room.id, key);
821
+ if (!task) { diagnostics?.record('usage', 'turn-resume', 'origin-missing'); return; }
822
+ await meter.pulse(task, true, room.id, task.clientId, `${key}:${randomUUID()}`);
823
+ diagnostics?.record('usage', 'turn-resume', 'confirmed');
824
+ room.usageTasks.set(key, task);
825
+ } else if (task && message.method === 'heartbeat') await meter.pulse(task, true, room.id, task.clientId);
826
+ else if (task && message.method === 'inactive') {
827
+ await meter.lost(task);
828
+ room.usageTasks.delete(key);
829
+ }
830
+ else if (task && message.method === 'turn/completed') {
831
+ await meter.pulse(task, false, room.id, task.clientId);
832
+ room.usageTasks.delete(key);
833
+ }
834
+ } catch (error) { log({ event: 'usage-error', message: error.message }); }
835
+ return;
836
+ }
291
837
  if (message.type === 'lan-candidates') {
292
838
  room.lanCandidates = normalizeLanCandidates(message.candidates);
293
839
  for (const connection of room.connections.values()) send(connection.ws, { type: 'lan-candidates', candidates: room.lanCandidates });
@@ -300,8 +846,23 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
300
846
  if (message.type === 'pair-code') {
301
847
  send(ws, { type: 'pair-code', commandId: message.commandId, room: room.id, ...invitation(room) }); return;
302
848
  }
849
+ if (['invite-create', 'invite-list', 'invite-revoke'].includes(message.type)) {
850
+ try {
851
+ if (!room.durable) throw new Error('长期邀请需要持久工作站身份');
852
+ let result;
853
+ if (message.type === 'invite-create') result = longInvitations.create(room.id, message.expires);
854
+ else if (message.type === 'invite-list') result = { invitations: longInvitations.list().filter(item => item.room === room.id) };
855
+ else {
856
+ if (!longInvitations.list().some(item => item.id === message.id && item.room === room.id)) throw new Error('未找到本工作站的邀请');
857
+ longInvitations.revoke(message.id);
858
+ result = { id: message.id, revoked: true };
859
+ }
860
+ send(ws, { type: message.type, commandId: message.commandId, ...result });
861
+ } catch (error) { send(ws, { type: message.type, commandId: message.commandId, error: error.message }); }
862
+ return;
863
+ }
303
864
  if (message.type === 'revoke') {
304
- commit(room, { clients: [], revision: room.revision + 1 });
865
+ await commit(room, current => ({ clients: [], revision: current.revision + 1 }));
305
866
  room.pairingHash = undefined;
306
867
  for (const connection of room.connections.values()) {
307
868
  releaseRelay(connection);
@@ -309,8 +870,12 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
309
870
  connection.ws.close(4401, 'Device authorization revoked');
310
871
  }
311
872
  room.connections.clear();
873
+ for (const client of room.uploadClients?.values() || []) client.ws.close(4401, 'Device authorization revoked');
874
+ room.uploadClients?.clear();
312
875
  for (const observer of room.observers.values()) observer.ws.close(4401, 'Device authorization revoked');
313
876
  room.observers.clear();
877
+ for (const socket of room.deviceManagers?.keys() || []) socket.close(4401, 'Device authorization revoked');
878
+ room.deviceManagers?.clear();
314
879
  send(ws, { type: 'authorization-changed', clients: [], revision: room.revision });
315
880
  send(ws, { type: 'revoked', commandId: message.commandId, room: room.id }); return;
316
881
  }
@@ -320,49 +885,78 @@ export async function startSelfRelay({ port = 0, hostKey = secret(), pairingTtlM
320
885
  else if (message.type === 'ready') queueClientMessage(connection, { type: 'ready', p2pProbe: connection.p2pProbe ? 1 : 0,
321
886
  p2pProbeRepeat: connection.p2pProbe && connection.repeat });
322
887
  else if (message.type === 'p2p-probe' && connection.p2pProbe && validProbeSignal(message)) {
323
- if (connection.probeId !== message.probeId) return;
324
- if (++connection.signals > 150) return fail(1008, 'Probe signal limit');
888
+ const diagnostic = message.purpose === 'diagnostic';
889
+ const file = message.purpose === 'file';
890
+ if (message.purpose && !diagnostic && !file) return fail(1008, 'Invalid probe purpose');
891
+ if (connection[file ? 'fileProbeId' : diagnostic ? 'diagnosticProbeId' : 'probeId'] !== message.probeId) return;
892
+ if (++connection[file ? 'fileSignals' : diagnostic ? 'diagnosticSignals' : 'signals'] > 150) return fail(1008, 'Probe signal limit');
325
893
  send(connection.ws, { ...message, session: undefined });
326
894
  }
327
895
  else if (message.type === 'detach') connection.ws.close(1011, 'Local Bridge unavailable');
328
896
  else fail();
897
+ } else if (role === 'file-host' && room.uploadHost === ws) {
898
+ const client = room.uploadClients.get(message.session);
899
+ if (!client) return;
900
+ if (message.type === 'file-frame' && typeof message.payload === 'string' && message.payload.length <= 512 * 1024) {
901
+ if (client.ws.bufferedAmount > 1024 * 1024) return client.ws.close(1013, 'File receiver too slow');
902
+ send(client.ws, { type: 'file-frame', payload: message.payload });
903
+ } else if (message.type === 'file-close') client.ws.close(1011, 'File receiver unavailable');
904
+ else fail(1008, 'Invalid file frame');
905
+ } else if (role === 'file-client' && room.uploadClients?.get(session)?.ws === ws) {
906
+ if (message.type !== 'file-frame' || typeof message.payload !== 'string' || message.payload.length > 512 * 1024) return fail(1008, 'Invalid file frame');
907
+ if (room.uploadHost?.bufferedAmount > 1024 * 1024) return fail(1013, 'File receiver too slow');
908
+ send(room.uploadHost, { type: 'file-frame', session, payload: message.payload });
329
909
  } else if (role === 'observer' && room.observers.get(clientId)?.ws === ws && message.type === 'official-computer') {
330
910
  const computer = normalizeOfficialComputer(message);
331
- commit(room, { officialComputer: computer });
911
+ await commit(room, { officialComputer: computer });
332
912
  send(ws, { type: 'official-computer-saved', computer });
333
913
  } else if (role === 'client' && room.connections.get(clientId)?.ws === ws && message.type === 'p2p-probe') {
334
914
  const connection = room.connections.get(clientId);
335
915
  if (!connection.p2pProbe || !validProbeSignal(message)) return fail(1008, 'Invalid probe signal');
916
+ const diagnostic = message.purpose === 'diagnostic';
917
+ const file = message.purpose === 'file';
918
+ if (message.purpose && !diagnostic && !file) return fail(1008, 'Invalid probe purpose');
919
+ const idField = file ? 'fileProbeId' : diagnostic ? 'diagnosticProbeId' : 'probeId';
920
+ const signalsField = file ? 'fileSignals' : diagnostic ? 'diagnosticSignals' : 'signals';
921
+ const startedField = file ? 'fileProbeStarted' : diagnostic ? 'diagnosticProbeStarted' : 'probeStarted';
336
922
  if (message.kind === 'start') {
337
- if (connection.probeId && (!connection.repeat || Date.now() - connection.probeStarted < 2000)) {
338
- send(ws, { type: 'p2p-probe', version: 1, probeId: message.probeId, kind: 'close' }); return;
923
+ if (connection[idField] && (!connection.repeat || Date.now() - connection[startedField] < 2000)) {
924
+ send(ws, { type: 'p2p-probe', version: 1, probeId: message.probeId,
925
+ ...(message.purpose ? { purpose: message.purpose } : {}), kind: 'close' }); return;
339
926
  }
340
- connection.probeId = message.probeId; connection.signals = 0; connection.probeStarted = Date.now();
927
+ connection[idField] = message.probeId; connection[signalsField] = 0; connection[startedField] = Date.now();
341
928
  }
342
- if (connection.probeId !== message.probeId) return;
343
- if (++connection.signals > 150) return fail(1008, 'Probe signal limit');
929
+ if (connection[idField] !== message.probeId) return;
930
+ if (++connection[signalsField] > 150) return fail(1008, 'Probe signal limit');
344
931
  send(room.host, { ...message, session });
932
+ if (diagnostic && message.kind === 'close') connection.diagnosticProbeId = undefined;
345
933
  } else if (room.connections.get(clientId)?.ws === ws && message.type === 'p2p-route' && message.direct === 1) {
346
934
  releaseRelay(room.connections.get(clientId));
935
+ room.connections.get(clientId).adminRoute = 'direct';
347
936
  } else if (room.connections.get(clientId)?.ws === ws && message.type === 'data' && typeof message.payload === 'string') {
348
937
  const connection = room.connections.get(clientId);
349
- if (!controlPayload(message.payload) && !admitRelay(connection, room)) {
938
+ if (!connection.userId && !managementPayload(message.payload)) return fail(4431, 'Account login required');
939
+ if (connection.userId && !controlPayload(message.payload) && !admitRelay(connection, room)) {
350
940
  ws.close(4428, 'Server busy'); return;
351
941
  }
942
+ if (connection.userId && !controlPayload(message.payload)) connection.adminRoute = 'relay';
352
943
  send(room.host, { type: 'data', session, payload: message.payload });
353
- } else fail();
354
- } catch (error) { log({ event: 'request-error', role, message: 'Relay operation failed' }); ws.close(1011, 'Relay operation failed'); }
355
- });
356
- });
944
+ } else fail();
945
+ } catch (error) { log({ event: 'request-error', role, message: 'Relay operation failed' }); ws.close(1011, 'Relay operation failed'); }
946
+ finally { pendingMessages--; pendingBytes -= size; }
947
+ }).catch(() => ws.close(1011, 'Relay operation failed'));
948
+ });
949
+ });
357
950
  await new Promise((resolve, reject) => { server.once('error', reject); server.listen(port, '127.0.0.1', resolve); });
358
951
  listenUrl = `ws://127.0.0.1:${server.address().port}/relay`;
359
952
  return {
360
953
  url: listenUrl, hostKey,
361
- async close() {
362
- for (const ws of wss.clients) ws.terminate();
363
- await new Promise(resolve => wss.close(resolve));
364
- await new Promise(resolve => server.close(resolve));
365
- },
954
+ async close() {
955
+ for (const ws of wss.clients) ws.terminate();
956
+ await new Promise(resolve => wss.close(resolve));
957
+ await new Promise(resolve => server.close(resolve));
958
+ if (database) await database.close();
959
+ },
366
960
  };
367
961
  }
368
962
 
@@ -436,12 +1030,26 @@ function plainText(value, limit, empty) {
436
1030
  return typeof value === 'string' && (empty || value.trim().length > 0) && value.trim().length <= limit && !/[\u0000-\u001f\u007f]/.test(value);
437
1031
  }
438
1032
 
1033
+ // Device-authorized management must not become an anonymous chat tunnel.
1034
+ function managementPayload(value) {
1035
+ try {
1036
+ const message = JSON.parse(value);
1037
+ if (['unsubscribe', 'cancel', 'response-ack'].includes(message.type)) return true;
1038
+ if (message.type === 'subscribe') return message.path === '/api/shared-recovery/events';
1039
+ if (message.type !== 'request') return false;
1040
+ const method = message.method || 'GET';
1041
+ if (method === 'GET') return ['/api/health', '/api/official-remote/status'].includes(message.path) ||
1042
+ /^\/api\/threads\/[0-9a-f-]{28,64}\/model-changes(?:\?|$)/i.test(message.path);
1043
+ return method === 'POST' && message.path === '/api/official-remote/pairing';
1044
+ } catch { return false; }
1045
+ }
1046
+
439
1047
  function controlPayload(value) {
440
1048
  try {
441
1049
  const message = JSON.parse(value);
442
1050
  if (message.type === 'unsubscribe') return true;
443
1051
  return ['request', 'subscribe'].includes(message.type) && typeof message.path === 'string' &&
444
- (message.path === '/api/health' || message.path.startsWith('/api/shared-recovery'));
1052
+ (message.path === '/api/health' || ['/api/desktop/start', '/api/desktop/restart'].includes(message.path) || message.path.startsWith('/api/shared-recovery'));
445
1053
  } catch { return false; }
446
1054
  }
447
1055
 
@@ -456,13 +1064,3 @@ function readBody(request, limit) {
456
1064
  request.on('end', () => resolve(value)); request.on('error', reject);
457
1065
  });
458
1066
  }
459
-
460
- function adminPage() {
461
- return `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Code Relax 管理</title><style>
462
- body{margin:0;background:#0d1210;color:#eef4f0;font:15px system-ui,sans-serif}main{max-width:760px;margin:40px auto;padding:0 20px}section{background:#151d19;border:1px solid #2a3931;border-radius:14px;padding:18px;margin:16px 0}h1{font-size:24px}h2{font-size:17px}label{display:block;margin:12px 0 6px;color:#aab7af}input,textarea{box-sizing:border-box;width:100%;padding:11px;border:1px solid #43564b;border-radius:9px;background:#0d1210;color:#eef4f0}textarea{min-height:120px;resize:vertical}button{margin:12px 8px 0 0;padding:10px 16px;border:0;border-radius:9px;background:#2c7653;color:white}button.secondary{background:#39443e}pre{white-space:pre-wrap;color:#b9c8bf}.row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.metric{padding:12px;background:#0d1210;border-radius:9px}.metric b{display:block;font-size:22px}</style><main><h1>Code Relax 管理</h1><section><h2>运行状态</h2><div id="metrics" class="row"></div><button onclick="load()">刷新</button></section><section><h2>服务器转发容量</h2><label>最大并发 WSS 转发会话</label><input id="limit" type="number" min="1" max="256"><button onclick="saveLimit()">保存</button></section><section><h2>系统公告</h2><label>标题</label><input id="title" maxlength="80"><label>正文</label><textarea id="message" maxlength="2000"></textarea><button onclick="publish()">发布</button><button class="secondary" onclick="withdraw()">撤下</button><pre id="current"></pre></section><pre id="result"></pre><script>
463
- const request=async(path,options={})=>{const response=await fetch(path,options);const body=await response.json();if(!response.ok)throw new Error(body.error||('HTTP '+response.status));return body};
464
- async function load(){try{const s=await request('/admin/api/status');limit.value=s.maxRelaySessions;metrics.innerHTML='<div class="metric"><b>'+s.activeRelaySessions+'</b>受限转发 / '+s.maxRelaySessions+'</div><div class="metric"><b>'+s.activeExemptRelaySessions+'</b>白名单转发</div><div class="metric"><b>'+s.websocketConnections+'</b>WebSocket</div><div class="metric"><b>'+s.onlineComputers+'</b>在线工作站</div><div class="metric"><b>'+s.activePhones+'</b>活动手机</div>';current.textContent=s.announcement?s.announcement.title+'\\n'+s.announcement.message+'\\n'+s.announcement.publishedAt:'当前无公告';}catch(e){result.textContent=e.message}}
465
- const write=(method,path,body)=>request(path,{method,headers:{'Content-Type':'application/json','X-Code-Relax-Admin':'1'},body:JSON.stringify(body)}).then(()=>{result.textContent='已保存';return load()}).catch(e=>result.textContent=e.message);
466
- function saveLimit(){return write('PUT','/admin/api/settings',{maxRelaySessions:Number(limit.value)})}function publish(){return write('PUT','/admin/api/announcement',{title:title.value,message:message.value})}function withdraw(){return write('DELETE','/admin/api/announcement',{})}load();
467
- </script></main></html>`;
468
- }