dsh-zen-remote 2.3.11 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/CHANGELOG.md +39 -78
  2. package/README.md +94 -84
  3. package/README.zh-CN.md +90 -81
  4. package/bin/zen-remote.mjs +5 -0
  5. package/docs/interface.md +6 -6
  6. package/docs/remote-access.en.md +6 -4
  7. package/docs/remote-access.md +6 -4
  8. package/dsh-push.mjs +96 -10
  9. package/lib/adapters/dsh-better-sidebar/index.js +7 -3
  10. package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
  11. package/lib/admin-routes.js +10 -2
  12. package/lib/admin-routes.js.map +1 -1
  13. package/lib/cli/args.js +251 -0
  14. package/lib/cli/args.js.map +1 -0
  15. package/lib/cli/bootstrap.js +449 -0
  16. package/lib/cli/bootstrap.js.map +1 -0
  17. package/lib/cli/command-setup.js +484 -0
  18. package/lib/cli/command-setup.js.map +1 -0
  19. package/lib/cli/commands-client.js +359 -0
  20. package/lib/cli/commands-client.js.map +1 -0
  21. package/lib/cli/commands-host.js +1108 -0
  22. package/lib/cli/commands-host.js.map +1 -0
  23. package/lib/cli/commands-status.js +279 -0
  24. package/lib/cli/commands-status.js.map +1 -0
  25. package/lib/cli/config-writer.js +334 -0
  26. package/lib/cli/config-writer.js.map +1 -0
  27. package/lib/cli/context.js +10 -0
  28. package/lib/cli/context.js.map +1 -0
  29. package/lib/cli/i18n.js +620 -0
  30. package/lib/cli/i18n.js.map +1 -0
  31. package/lib/cli/ipc-client.js +160 -0
  32. package/lib/cli/ipc-client.js.map +1 -0
  33. package/lib/cli/ipc-server.js +464 -0
  34. package/lib/cli/ipc-server.js.map +1 -0
  35. package/lib/cli/main.js +245 -0
  36. package/lib/cli/main.js.map +1 -0
  37. package/lib/cli/qr-terminal.js +48 -0
  38. package/lib/cli/qr-terminal.js.map +1 -0
  39. package/lib/client-data/qr.js +286 -0
  40. package/lib/client-data/qr.js.map +1 -0
  41. package/lib/client-routes.js +308 -25
  42. package/lib/client-routes.js.map +1 -1
  43. package/lib/client.js +1013 -229
  44. package/lib/config.js +50 -12
  45. package/lib/config.js.map +1 -1
  46. package/lib/debug-report.js +28 -1
  47. package/lib/debug-report.js.map +1 -1
  48. package/lib/fetch-route-intercept.js +334 -26
  49. package/lib/fetch-route-intercept.js.map +1 -1
  50. package/lib/index.js +564 -363
  51. package/lib/index.js.map +1 -1
  52. package/lib/intercept.js +1833 -280
  53. package/lib/intercept.js.map +1 -1
  54. package/lib/lan-gate-server.cjs +36 -10
  55. package/lib/merge-streams.js +103 -11
  56. package/lib/merge-streams.js.map +1 -1
  57. package/lib/plugin-route-intercept.js +93 -63
  58. package/lib/plugin-route-intercept.js.map +1 -1
  59. package/lib/relay-access.js +8 -1
  60. package/lib/relay-access.js.map +1 -1
  61. package/lib/relay-client.js +133 -4
  62. package/lib/relay-client.js.map +1 -1
  63. package/lib/relay-pool.js +275 -0
  64. package/lib/relay-pool.js.map +1 -0
  65. package/lib/relay-server.js +269 -17
  66. package/lib/relay-server.js.map +1 -1
  67. package/lib/restart-fields.js +6 -4
  68. package/lib/restart-fields.js.map +1 -1
  69. package/lib/share-store.js +65 -27
  70. package/lib/share-store.js.map +1 -1
  71. package/lib/state-store.js +400 -0
  72. package/lib/state-store.js.map +1 -0
  73. package/lib/types/adapters/dsh-better-sidebar/index.d.ts +3 -2
  74. package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
  75. package/lib/types/admin-routes.d.ts.map +1 -1
  76. package/lib/types/cli/args.d.ts +78 -0
  77. package/lib/types/cli/args.d.ts.map +1 -0
  78. package/lib/types/cli/bootstrap.d.ts +170 -0
  79. package/lib/types/cli/bootstrap.d.ts.map +1 -0
  80. package/lib/types/cli/command-setup.d.ts +72 -0
  81. package/lib/types/cli/command-setup.d.ts.map +1 -0
  82. package/lib/types/cli/commands-client.d.ts +36 -0
  83. package/lib/types/cli/commands-client.d.ts.map +1 -0
  84. package/lib/types/cli/commands-host.d.ts +82 -0
  85. package/lib/types/cli/commands-host.d.ts.map +1 -0
  86. package/lib/types/cli/commands-status.d.ts +46 -0
  87. package/lib/types/cli/commands-status.d.ts.map +1 -0
  88. package/lib/types/cli/config-writer.d.ts +92 -0
  89. package/lib/types/cli/config-writer.d.ts.map +1 -0
  90. package/lib/types/cli/context.d.ts +39 -0
  91. package/lib/types/cli/context.d.ts.map +1 -0
  92. package/lib/types/cli/i18n.d.ts +32 -0
  93. package/lib/types/cli/i18n.d.ts.map +1 -0
  94. package/lib/types/cli/ipc-client.d.ts +83 -0
  95. package/lib/types/cli/ipc-client.d.ts.map +1 -0
  96. package/lib/types/cli/ipc-server.d.ts +113 -0
  97. package/lib/types/cli/ipc-server.d.ts.map +1 -0
  98. package/lib/types/cli/main.d.ts +57 -0
  99. package/lib/types/cli/main.d.ts.map +1 -0
  100. package/lib/types/cli/qr-terminal.d.ts +17 -0
  101. package/lib/types/cli/qr-terminal.d.ts.map +1 -0
  102. package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -1
  103. package/lib/types/client/current-session-report.d.ts +4 -0
  104. package/lib/types/client/current-session-report.d.ts.map +1 -1
  105. package/lib/types/client/effects/model-group-side.d.ts.map +1 -1
  106. package/lib/types/client/effects/remote-session.d.ts.map +1 -1
  107. package/lib/types/client/locales.d.ts +47 -3
  108. package/lib/types/client/locales.d.ts.map +1 -1
  109. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -1
  110. package/lib/types/client/settings/settings-css.d.ts +1 -1
  111. package/lib/types/client/settings/settings-css.d.ts.map +1 -1
  112. package/lib/types/client/styles/remote-session.css.d.ts +1 -1
  113. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -1
  114. package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -1
  115. package/lib/types/client/wizard/cards/ProxyCard.d.ts.map +1 -1
  116. package/lib/types/client/wizard/wizard-css.d.ts +1 -1
  117. package/lib/types/client/wizard/wizard-css.d.ts.map +1 -1
  118. package/lib/types/client-data/model-group-side.d.ts +23 -2
  119. package/lib/types/client-data/model-group-side.d.ts.map +1 -1
  120. package/lib/types/client-data/remote-status.d.ts +47 -1
  121. package/lib/types/client-data/remote-status.d.ts.map +1 -1
  122. package/lib/types/client-data/settings-form.d.ts +85 -1
  123. package/lib/types/client-data/settings-form.d.ts.map +1 -1
  124. package/lib/types/client-data/wizard.d.ts +56 -1
  125. package/lib/types/client-data/wizard.d.ts.map +1 -1
  126. package/lib/types/client-routes.d.ts +25 -1
  127. package/lib/types/client-routes.d.ts.map +1 -1
  128. package/lib/types/config.d.ts +40 -14
  129. package/lib/types/config.d.ts.map +1 -1
  130. package/lib/types/debug-report.d.ts.map +1 -1
  131. package/lib/types/fetch-route-intercept.d.ts +66 -8
  132. package/lib/types/fetch-route-intercept.d.ts.map +1 -1
  133. package/lib/types/index.d.ts +9 -2
  134. package/lib/types/index.d.ts.map +1 -1
  135. package/lib/types/intercept.d.ts +72 -10
  136. package/lib/types/intercept.d.ts.map +1 -1
  137. package/lib/types/merge-streams.d.ts +79 -1
  138. package/lib/types/merge-streams.d.ts.map +1 -1
  139. package/lib/types/plugin-route-intercept.d.ts +25 -9
  140. package/lib/types/plugin-route-intercept.d.ts.map +1 -1
  141. package/lib/types/relay-access.d.ts +3 -1
  142. package/lib/types/relay-access.d.ts.map +1 -1
  143. package/lib/types/relay-client.d.ts +23 -2
  144. package/lib/types/relay-client.d.ts.map +1 -1
  145. package/lib/types/relay-pool.d.ts +148 -0
  146. package/lib/types/relay-pool.d.ts.map +1 -0
  147. package/lib/types/relay-server.d.ts +30 -3
  148. package/lib/types/relay-server.d.ts.map +1 -1
  149. package/lib/types/restart-fields.d.ts +5 -5
  150. package/lib/types/restart-fields.d.ts.map +1 -1
  151. package/lib/types/share-store.d.ts.map +1 -1
  152. package/lib/types/state-store.d.ts +179 -0
  153. package/lib/types/state-store.d.ts.map +1 -0
  154. package/lib/types/upload-limits.d.ts +15 -0
  155. package/lib/types/upload-limits.d.ts.map +1 -1
  156. package/lib/types/virtual-id.d.ts +21 -0
  157. package/lib/types/virtual-id.d.ts.map +1 -1
  158. package/lib/types/wizard/probe.d.ts.map +1 -1
  159. package/lib/types/wizard/proxy-snippets.d.ts +3 -0
  160. package/lib/types/wizard/proxy-snippets.d.ts.map +1 -1
  161. package/lib/types/wizard/routes.d.ts +1 -0
  162. package/lib/types/wizard/routes.d.ts.map +1 -1
  163. package/lib/types/wizard/status.d.ts +9 -0
  164. package/lib/types/wizard/status.d.ts.map +1 -1
  165. package/lib/types/wizard/verify.d.ts +3 -0
  166. package/lib/types/wizard/verify.d.ts.map +1 -1
  167. package/lib/upload-limits.js +15 -0
  168. package/lib/upload-limits.js.map +1 -1
  169. package/lib/virtual-id.js +60 -0
  170. package/lib/virtual-id.js.map +1 -1
  171. package/lib/wizard/probe.js +46 -1
  172. package/lib/wizard/probe.js.map +1 -1
  173. package/lib/wizard/proxy-snippets.js +14 -1
  174. package/lib/wizard/proxy-snippets.js.map +1 -1
  175. package/lib/wizard/routes.js +23 -4
  176. package/lib/wizard/routes.js.map +1 -1
  177. package/lib/wizard/status.js +21 -0
  178. package/lib/wizard/status.js.map +1 -1
  179. package/lib/wizard/verify.js +31 -8
  180. package/lib/wizard/verify.js.map +1 -1
  181. package/package.json +8 -4
  182. package/pwa/icons/badge-96.png +0 -0
  183. package/pwa/icons/badge.svg +1 -0
  184. package/pwa/manifest.json +2 -1
  185. package/pwa/sw.js +3 -1
  186. package/scripts/check-debug-report.mjs +41 -1
  187. package/scripts/check-settings-form.mjs +14 -3
  188. package/scripts/check-settings-view.mjs +247 -0
  189. package/scripts/check-wizard-ui.mjs +37 -0
  190. package/src/adapters/dsh-better-sidebar/index.ts +7 -3
  191. package/src/admin-routes.ts +10 -2
  192. package/src/cli/args.ts +289 -0
  193. package/src/cli/bootstrap.ts +569 -0
  194. package/src/cli/command-setup.ts +540 -0
  195. package/src/cli/commands-client.ts +369 -0
  196. package/src/cli/commands-host.ts +1127 -0
  197. package/src/cli/commands-status.ts +296 -0
  198. package/src/cli/config-writer.ts +354 -0
  199. package/src/cli/context.ts +38 -0
  200. package/src/cli/i18n.ts +645 -0
  201. package/src/cli/ipc-client.ts +216 -0
  202. package/src/cli/ipc-server.ts +531 -0
  203. package/src/cli/main.ts +285 -0
  204. package/src/cli/qr-terminal.ts +49 -0
  205. package/src/client/RemoteStatusIcon.tsx +4 -1
  206. package/src/client/current-session-report.ts +28 -4
  207. package/src/client/effects/model-group-side.ts +19 -5
  208. package/src/client/effects/remote-session.ts +26 -6
  209. package/src/client/locales.ts +96 -6
  210. package/src/client/settings/SettingsSection.tsx +287 -92
  211. package/src/client/settings/settings-css.ts +18 -0
  212. package/src/client/styles/remote-session.css.ts +15 -1
  213. package/src/client/wizard/GatewayWizard.tsx +470 -109
  214. package/src/client/wizard/cards/ProxyCard.tsx +2 -1
  215. package/src/client/wizard/wizard-css.ts +23 -0
  216. package/src/client-data/model-group-side.ts +37 -10
  217. package/src/client-data/remote-status.ts +112 -17
  218. package/src/client-data/settings-form.ts +164 -8
  219. package/src/client-data/wizard.ts +112 -2
  220. package/src/client-routes.ts +334 -26
  221. package/src/config.ts +62 -16
  222. package/src/debug-report.ts +27 -1
  223. package/src/fetch-route-intercept.ts +381 -32
  224. package/src/index.ts +566 -367
  225. package/src/intercept.ts +1866 -286
  226. package/src/merge-streams.ts +155 -10
  227. package/src/plugin-route-intercept.ts +120 -71
  228. package/src/relay-access.ts +14 -3
  229. package/src/relay-client.ts +163 -5
  230. package/src/relay-pool.ts +387 -0
  231. package/src/relay-server.ts +312 -16
  232. package/src/restart-fields.ts +6 -4
  233. package/src/share-store.ts +59 -26
  234. package/src/state-store.ts +428 -0
  235. package/src/upload-limits.ts +16 -0
  236. package/src/virtual-id.ts +57 -0
  237. package/src/wizard/probe.ts +54 -2
  238. package/src/wizard/proxy-snippets.ts +17 -2
  239. package/src/wizard/routes.ts +24 -5
  240. package/src/wizard/status.ts +28 -0
  241. package/src/wizard/verify.ts +33 -11
  242. package/AGENTS.md +0 -185
  243. package/docs/adr/0001-plugin-bridge-forwards-plugin-routes.md +0 -15
  244. package/docs/adr/0002-relay-emulates-websocket.md +0 -8
@@ -12,8 +12,17 @@
12
12
  * row layer applies the same rule when the fields land there, so a client
13
13
  * that accepts something the resolver would silently drop would only set
14
14
  * the user up for a mystery.
15
+ *
16
+ * MS-6 adds the paired-pool overview pieces: the pool count is a first-class
17
+ * "configured" signal (MS-5 pairs straight into the pool store, not the
18
+ * row), and {@link deriveClientOverviewSummary} derives the overview card's
19
+ * counters and its two lights (connection health / pool fill) off the
20
+ * `client/status` pool list.
15
21
  */
16
22
 
23
+ import { MAX_PAIRED_SERVERS } from './settings-form.ts'
24
+ import type { ClientServerView } from './settings-form.ts'
25
+
17
26
  /** Wizard route constants — mirror of src/wizard/routes.ts. */
18
27
  export const WIZARD_STATUS_ROUTE = '/_dsh/zen-remote/admin/wizard/status'
19
28
  export const WIZARD_VERIFY_ROUTE = '/_dsh/zen-remote/admin/wizard/verify'
@@ -130,7 +139,12 @@ export function initialStep(status: WizardStatusBody | undefined): WizardStep {
130
139
 
131
140
  /**
132
141
  * Whether the inline overview card should render the configured status
133
- * dashboard instead of the first-time setup prompt.
142
+ * dashboard instead of the first-time setup prompt. MS-6: the paired pool's
143
+ * size is the client's first-class signal — since MS-5 a claim lands in the
144
+ * pool store and never touches the row's `serverUrl` / `deviceToken`, so a
145
+ * freshly multi-paired machine with an empty row must still read as
146
+ * configured. `> 0` answers true no matter what the other inputs say; the
147
+ * field is optional and an omitted one (or a zero) changes nothing.
134
148
  */
135
149
  export function isWizardConfigured(input: {
136
150
  effectiveClient: boolean
@@ -140,7 +154,9 @@ export function isWizardConfigured(input: {
140
154
  tokenConfigured: boolean
141
155
  clientServerUrl: string
142
156
  clientState: string | undefined
157
+ pairedServerCount?: number
143
158
  }): boolean {
159
+ if ((input.pairedServerCount ?? 0) > 0) return true
144
160
  if (input.effectiveClient) {
145
161
  return input.tokenConfigured || input.clientServerUrl.trim() !== '' || input.clientState === 'connected'
146
162
  }
@@ -312,6 +328,68 @@ const CLIENT_FAIL_STATES: ReadonlySet<string> = new Set([
312
328
  'unexpected',
313
329
  ])
314
330
 
331
+ /**
332
+ * The view states that will NOT come back on their own (MS-6): a revoked
333
+ * token, an incompatible interface, an address that no longer parses — each
334
+ * needs the settings page before that server serves again. They turn the
335
+ * overview's connection light red; a merely `unreachable` server is mid-retry
336
+ * and stays amber.
337
+ */
338
+ const CLIENT_DEAD_STATES: ReadonlySet<string> = new Set(['revoked', 'incompatible', 'invalid-url'])
339
+
340
+ /** The overview card's derived counters and lights (MS-6): the 第 3 格
341
+ * 「连接状态」 shows `M / N 台在线` with {@link WizardOverviewSummary.connectionLight}
342
+ * summarizing the pool's health, and the 第 4 格 「已配对服务端」 shows
343
+ * `已配对 N / maxServers 台` with {@link WizardOverviewSummary.serversLight}
344
+ * turning red exactly at the cap. Input is the DERIVED pool list
345
+ * ({@link ClientServerView} — the view vocabulary's state words), in pairing
346
+ * order; order never matters to the counts. */
347
+ export interface WizardOverviewSummary {
348
+ totalCount: number
349
+ onlineCount: number
350
+ /** Everything that is neither connected nor revoked (unreachable,
351
+ * unexpected, incompatible, invalid-url, …). */
352
+ offlineCount: number
353
+ revokedCount: number
354
+ maxServers: number
355
+ limitReached: boolean
356
+ connectionLight: LightState
357
+ serversLight: LightState
358
+ }
359
+
360
+ /**
361
+ * Derive the sub-client overview summary off one derived pool list: green
362
+ * only when EVERY paired server is connected; red when any server is in a
363
+ * state that will not recover on its own (revoked / incompatible /
364
+ * invalid-url) or when the pool is FULL — the cap is a servers fact, so a
365
+ * full pool of healthy connections still shows a red servers light while its
366
+ * connection light stays green; amber for the rest (nothing paired yet, or
367
+ * servers the retry ladder is still working on).
368
+ */
369
+ export function deriveClientOverviewSummary(
370
+ servers: readonly ClientServerView[],
371
+ maxServers: number = MAX_PAIRED_SERVERS,
372
+ ): WizardOverviewSummary {
373
+ const totalCount = servers.length
374
+ let onlineCount = 0
375
+ let revokedCount = 0
376
+ let deadCount = 0
377
+ for (const server of servers) {
378
+ if (server.state === 'connected') onlineCount += 1
379
+ if (server.state === 'revoked') revokedCount += 1
380
+ if (CLIENT_DEAD_STATES.has(server.state)) deadCount += 1
381
+ }
382
+ const offlineCount = totalCount - onlineCount - revokedCount
383
+ const limitReached = totalCount >= maxServers
384
+ const connectionLight: LightState = totalCount === 0
385
+ ? 'pending'
386
+ : onlineCount === totalCount
387
+ ? 'pass'
388
+ : deadCount > 0 ? 'fail' : 'pending'
389
+ const serversLight: LightState = limitReached ? 'fail' : totalCount > 0 ? 'pass' : 'pending'
390
+ return { totalCount, onlineCount, offlineCount, revokedCount, maxServers, limitReached, connectionLight, serversLight }
391
+ }
392
+
315
393
  /**
316
394
  * Derive the three status lights for the desktop sub-client verification
317
395
  * step (`role === 'client'`).
@@ -330,6 +408,27 @@ export function deriveClientWizardLights(input: {
330
408
  }
331
409
  }
332
410
 
411
+ /**
412
+ * The wizard step-3's three lights for ONE selected paired server (MS-8):
413
+ * the credential is green by the row's own existence (the token lives in the
414
+ * pool store, never in the row) and red only when the server refused it
415
+ * (`revoked`); the connection follows the same fail-set the global lights
416
+ * use (an offline server mid-retry is red here too — the step is staring at
417
+ * THAT one server, not the pool); sync is green on a connected row whose
418
+ * interface groups all match, red on a compat mismatch, amber otherwise.
419
+ * `undefined` (nothing paired, or nothing selected yet) keeps all three
420
+ * amber.
421
+ */
422
+ export function deriveClientServerLights(server: ClientServerView | undefined): ClientWizardLights {
423
+ if (server === undefined) return { credential: 'pending', connection: 'pending', sync: 'pending' }
424
+ const connected = server.state === 'connected'
425
+ return {
426
+ credential: server.state === 'revoked' ? 'fail' : 'pass',
427
+ connection: connected ? 'pass' : CLIENT_FAIL_STATES.has(server.state) ? 'fail' : 'pending',
428
+ sync: connected ? ((server.compat?.mismatchedGroups.length ?? 0) > 0 ? 'fail' : 'pass') : 'pending',
429
+ }
430
+ }
431
+
333
432
  /** Pairing-code countdown, whole seconds, never negative. */
334
433
  export function pairingSecondsLeft(expiresAt: number, now: number): number {
335
434
  return Math.max(0, Math.ceil((expiresAt - now) / 1000))
@@ -367,6 +466,7 @@ export interface SnippetInput {
367
466
  domain: string
368
467
  upstreamHost: string
369
468
  upstreamPort: number
469
+ lang?: 'zh' | 'en' | undefined
370
470
  }
371
471
 
372
472
  /** Same grammar the row layer and the host half enforce. */
@@ -380,8 +480,11 @@ function valid(input: SnippetInput): { domain: string; upstream: string; port: n
380
480
  export function nginxSnippetText(input: SnippetInput): string | null {
381
481
  const parts = valid(input)
382
482
  if (parts === null) return null
483
+ const headerComment = input.lang === 'en'
484
+ ? '# Place this in the http {} block (skip if you already have this map)'
485
+ : '# 这段放在 http {} 层(若已有同名 map 则跳过)'
383
486
  return [
384
- '# 这段放在 http {} 层(若已有同名 map 则跳过)',
487
+ headerComment,
385
488
  'map $http_upgrade $connection_upgrade {',
386
489
  ' default upgrade;',
387
490
  " '' close;",
@@ -420,6 +523,13 @@ export function caddySnippetText(input: SnippetInput): string | null {
420
523
  export function luckyStepTexts(input: SnippetInput): string[] | null {
421
524
  const parts = valid(input)
422
525
  if (parts === null) return null
526
+ if (input.lang === 'en') {
527
+ return [
528
+ 'In Lucky "Web Service", create a new rule listening on port 443 and bind your domain.',
529
+ `Set the backend (reverse proxy target) to http://${parts.upstream}:${parts.port} and enable WebSocket support.`,
530
+ 'Turn on the catch-all switch to allow all paths, then save.',
531
+ ]
532
+ }
423
533
  return [
424
534
  '在 Lucky 的「Web 服务」里新建一条规则,监听 443 端口,绑定你的域名。',
425
535
  `后端(反代目标)填 http://${parts.upstream}:${parts.port},开启 WebSocket 支持。`,