dsh-zen-remote 1.1.16 → 2.0.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 (255) hide show
  1. package/AGENTS.md +54 -9
  2. package/README.md +249 -190
  3. package/README.zh-CN.md +247 -201
  4. package/assets/home.png +0 -0
  5. package/assets/info.png +0 -0
  6. package/assets/pairing.png +0 -0
  7. package/assets/session.png +0 -0
  8. package/assets/sheet.png +0 -0
  9. package/cordis.patch.yml +12 -11
  10. package/cordis.patch.yml.example +24 -9
  11. package/docs/interface.md +57 -2
  12. package/docs/remote-access.en.md +99 -58
  13. package/docs/remote-access.md +99 -42
  14. package/dsh-push.mjs +67 -54
  15. package/lan-gate.mjs +88 -20
  16. package/lib/activity.js +168 -0
  17. package/lib/activity.js.map +1 -0
  18. package/lib/admin-routes.js +526 -0
  19. package/lib/admin-routes.js.map +1 -0
  20. package/lib/client-pairing.js +224 -0
  21. package/lib/client-pairing.js.map +1 -0
  22. package/lib/client-routes.js +674 -0
  23. package/lib/client-routes.js.map +1 -0
  24. package/lib/client.js +10051 -4777
  25. package/lib/config.js +346 -0
  26. package/lib/config.js.map +1 -0
  27. package/lib/fetch-route-intercept.js +418 -0
  28. package/lib/fetch-route-intercept.js.map +1 -0
  29. package/lib/fingerprint.js +363 -0
  30. package/lib/fingerprint.js.map +1 -0
  31. package/lib/http.js +69 -0
  32. package/lib/http.js.map +1 -0
  33. package/lib/index.js +675 -54
  34. package/lib/index.js.map +1 -1
  35. package/lib/intercept-shape.js +81 -0
  36. package/lib/intercept-shape.js.map +1 -0
  37. package/lib/intercept.js +3040 -0
  38. package/lib/intercept.js.map +1 -0
  39. package/lib/lan-gate-server.cjs +532 -121
  40. package/lib/merge-streams.js +1165 -0
  41. package/lib/merge-streams.js.map +1 -0
  42. package/lib/relay-access.js +496 -0
  43. package/lib/relay-access.js.map +1 -0
  44. package/lib/relay-client.js +1303 -0
  45. package/lib/relay-client.js.map +1 -0
  46. package/lib/relay-filter.js +282 -0
  47. package/lib/relay-filter.js.map +1 -0
  48. package/lib/relay-server.js +1792 -0
  49. package/lib/relay-server.js.map +1 -0
  50. package/lib/restart-fields.js +34 -0
  51. package/lib/restart-fields.js.map +1 -0
  52. package/lib/restart-watcher.js +106 -0
  53. package/lib/restart-watcher.js.map +1 -0
  54. package/lib/session-reference.js +157 -0
  55. package/lib/session-reference.js.map +1 -0
  56. package/lib/share-export.js +16 -1
  57. package/lib/share-export.js.map +1 -1
  58. package/lib/share-ops.js +86 -0
  59. package/lib/share-ops.js.map +1 -0
  60. package/lib/share-store.js +294 -0
  61. package/lib/share-store.js.map +1 -0
  62. package/lib/types/activity.d.ts +99 -0
  63. package/lib/types/activity.d.ts.map +1 -0
  64. package/lib/types/admin-routes.d.ts +129 -0
  65. package/lib/types/admin-routes.d.ts.map +1 -0
  66. package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -1
  67. package/lib/types/client/MobileHome.d.ts +1 -1
  68. package/lib/types/client/MobileHome.d.ts.map +1 -1
  69. package/lib/types/client/MobileHomeChips.d.ts +1 -1
  70. package/lib/types/client/MobileHomeChips.d.ts.map +1 -1
  71. package/lib/types/client/MobileSessionHeader.d.ts +13 -2
  72. package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
  73. package/lib/types/client/MobileSessionInfo.d.ts +20 -3
  74. package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
  75. package/lib/types/client/RemoteComposerBanner.d.ts +42 -0
  76. package/lib/types/client/RemoteComposerBanner.d.ts.map +1 -0
  77. package/lib/types/client/RemoteHeaderIcon.d.ts +37 -0
  78. package/lib/types/client/RemoteHeaderIcon.d.ts.map +1 -0
  79. package/lib/types/client/RemoteShareMenu.d.ts +28 -0
  80. package/lib/types/client/RemoteShareMenu.d.ts.map +1 -0
  81. package/lib/types/client/RemoteStatusIcon.d.ts +36 -0
  82. package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -0
  83. package/lib/types/client/compat/desktop.d.ts +21 -0
  84. package/lib/types/client/compat/desktop.d.ts.map +1 -0
  85. package/lib/types/client/compat/types.d.ts +57 -0
  86. package/lib/types/client/compat/types.d.ts.map +1 -1
  87. package/lib/types/client/current-session-report.d.ts +37 -0
  88. package/lib/types/client/current-session-report.d.ts.map +1 -0
  89. package/lib/types/client/effects/header-status.d.ts.map +1 -1
  90. package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
  91. package/lib/types/client/effects/permission-sheet.d.ts +39 -0
  92. package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
  93. package/lib/types/client/effects/remote-session.d.ts +25 -0
  94. package/lib/types/client/effects/remote-session.d.ts.map +1 -0
  95. package/lib/types/client/effects/turn-fold.d.ts +9 -0
  96. package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
  97. package/lib/types/client/index.d.ts.map +1 -1
  98. package/lib/types/client/locales.d.ts +187 -2
  99. package/lib/types/client/locales.d.ts.map +1 -1
  100. package/lib/types/client/remote-fetch.d.ts +51 -0
  101. package/lib/types/client/remote-fetch.d.ts.map +1 -0
  102. package/lib/types/client/remote-group-expansion.d.ts +41 -0
  103. package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
  104. package/lib/types/client/remote-share-css.d.ts +19 -0
  105. package/lib/types/client/remote-share-css.d.ts.map +1 -0
  106. package/lib/types/client/remote-share-register.d.ts +113 -0
  107. package/lib/types/client/remote-share-register.d.ts.map +1 -0
  108. package/lib/types/client/remote-status-css.d.ts +15 -0
  109. package/lib/types/client/remote-status-css.d.ts.map +1 -0
  110. package/lib/types/client/remote-status-register.d.ts +46 -0
  111. package/lib/types/client/remote-status-register.d.ts.map +1 -0
  112. package/lib/types/client/settings/SettingsSection.d.ts +46 -0
  113. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
  114. package/lib/types/client/settings/register-settings.d.ts +58 -0
  115. package/lib/types/client/settings/register-settings.d.ts.map +1 -0
  116. package/lib/types/client/settings/settings-css.d.ts +10 -0
  117. package/lib/types/client/settings/settings-css.d.ts.map +1 -0
  118. package/lib/types/client/share/share-preview.d.ts.map +1 -1
  119. package/lib/types/client/styles/composer.css.d.ts +1 -1
  120. package/lib/types/client/styles/composer.css.d.ts.map +1 -1
  121. package/lib/types/client/styles/header.css.d.ts +1 -1
  122. package/lib/types/client/styles/header.css.d.ts.map +1 -1
  123. package/lib/types/client/styles/info.css.d.ts +1 -1
  124. package/lib/types/client/styles/info.css.d.ts.map +1 -1
  125. package/lib/types/client/styles/remote-session.css.d.ts +2 -0
  126. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
  127. package/lib/types/client-data/current-session.d.ts +41 -0
  128. package/lib/types/client-data/current-session.d.ts.map +1 -0
  129. package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
  130. package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
  131. package/lib/types/client-data/remote-status.d.ts +148 -0
  132. package/lib/types/client-data/remote-status.d.ts.map +1 -0
  133. package/lib/types/client-data/settings-form.d.ts +774 -0
  134. package/lib/types/client-data/settings-form.d.ts.map +1 -0
  135. package/lib/types/client-data/shares.d.ts +211 -0
  136. package/lib/types/client-data/shares.d.ts.map +1 -0
  137. package/lib/types/client-pairing.d.ts +114 -0
  138. package/lib/types/client-pairing.d.ts.map +1 -0
  139. package/lib/types/client-routes.d.ts +112 -0
  140. package/lib/types/client-routes.d.ts.map +1 -0
  141. package/lib/types/config.d.ts +200 -0
  142. package/lib/types/config.d.ts.map +1 -0
  143. package/lib/types/fetch-route-intercept.d.ts +156 -0
  144. package/lib/types/fetch-route-intercept.d.ts.map +1 -0
  145. package/lib/types/fingerprint.d.ts +164 -0
  146. package/lib/types/fingerprint.d.ts.map +1 -0
  147. package/lib/types/http.d.ts +41 -0
  148. package/lib/types/http.d.ts.map +1 -0
  149. package/lib/types/index.d.ts +56 -24
  150. package/lib/types/index.d.ts.map +1 -1
  151. package/lib/types/intercept-shape.d.ts +32 -0
  152. package/lib/types/intercept-shape.d.ts.map +1 -0
  153. package/lib/types/intercept.d.ts +432 -0
  154. package/lib/types/intercept.d.ts.map +1 -0
  155. package/lib/types/merge-streams.d.ts +393 -0
  156. package/lib/types/merge-streams.d.ts.map +1 -0
  157. package/lib/types/relay-access.d.ts +310 -0
  158. package/lib/types/relay-access.d.ts.map +1 -0
  159. package/lib/types/relay-client.d.ts +274 -0
  160. package/lib/types/relay-client.d.ts.map +1 -0
  161. package/lib/types/relay-filter.d.ts +103 -0
  162. package/lib/types/relay-filter.d.ts.map +1 -0
  163. package/lib/types/relay-server.d.ts +208 -0
  164. package/lib/types/relay-server.d.ts.map +1 -0
  165. package/lib/types/restart-fields.d.ts +22 -0
  166. package/lib/types/restart-fields.d.ts.map +1 -0
  167. package/lib/types/restart-watcher.d.ts +83 -0
  168. package/lib/types/restart-watcher.d.ts.map +1 -0
  169. package/lib/types/session-reference.d.ts +66 -0
  170. package/lib/types/session-reference.d.ts.map +1 -0
  171. package/lib/types/share-export.d.ts +14 -1
  172. package/lib/types/share-export.d.ts.map +1 -1
  173. package/lib/types/share-ops.d.ts +79 -0
  174. package/lib/types/share-ops.d.ts.map +1 -0
  175. package/lib/types/share-store.d.ts +87 -0
  176. package/lib/types/share-store.d.ts.map +1 -0
  177. package/lib/types/virtual-id.d.ts +38 -0
  178. package/lib/types/virtual-id.d.ts.map +1 -0
  179. package/lib/virtual-id.js +52 -0
  180. package/lib/virtual-id.js.map +1 -0
  181. package/llms.txt +17 -14
  182. package/package.json +60 -50
  183. package/scripts/build-client.mjs +5 -3
  184. package/scripts/check-host-shadow.mjs +19 -2
  185. package/scripts/check-settings-form.mjs +933 -0
  186. package/scripts/check-shares.mjs +397 -0
  187. package/scripts/check-upload-endpoint.mjs +59 -3
  188. package/scripts/sync-doc-version.mjs +8 -15
  189. package/scripts/verify-fingerprint-live.mjs +111 -0
  190. package/src/activity.ts +218 -0
  191. package/src/admin-routes.ts +627 -0
  192. package/src/client/MobileAttachButton.tsx +2 -2
  193. package/src/client/MobileAttachChips.tsx +3 -3
  194. package/src/client/MobileDrawerFooter.tsx +7 -4
  195. package/src/client/MobileHome.tsx +20 -16
  196. package/src/client/MobileHomeChips.tsx +16 -16
  197. package/src/client/MobileNavOverlay.tsx +2 -2
  198. package/src/client/MobileNavToggle.tsx +3 -3
  199. package/src/client/MobileSessionHeader.tsx +63 -42
  200. package/src/client/MobileSessionInfo.tsx +118 -17
  201. package/src/client/RemoteComposerBanner.tsx +76 -0
  202. package/src/client/RemoteHeaderIcon.tsx +103 -0
  203. package/src/client/RemoteShareMenu.tsx +57 -0
  204. package/src/client/RemoteStatusIcon.tsx +91 -0
  205. package/src/client/compat/desktop.ts +22 -0
  206. package/src/client/compat/types.ts +62 -0
  207. package/src/client/current-session-report.ts +164 -0
  208. package/src/client/effects/header-status.ts +21 -17
  209. package/src/client/effects/keyboard-guard.ts +65 -1
  210. package/src/client/effects/permission-sheet.ts +149 -0
  211. package/src/client/effects/remote-session.ts +68 -0
  212. package/src/client/effects/turn-fold.ts +52 -0
  213. package/src/client/index.tsx +151 -36
  214. package/src/client/locales.ts +396 -2
  215. package/src/client/remote-fetch.ts +125 -0
  216. package/src/client/remote-group-expansion.ts +190 -0
  217. package/src/client/remote-share-css.ts +54 -0
  218. package/src/client/remote-share-register.ts +244 -0
  219. package/src/client/remote-status-css.ts +64 -0
  220. package/src/client/remote-status-register.ts +107 -0
  221. package/src/client/settings/SettingsSection.tsx +1229 -0
  222. package/src/client/settings/register-settings.ts +116 -0
  223. package/src/client/settings/settings-css.ts +236 -0
  224. package/src/client/share/share-preview.tsx +6 -3
  225. package/src/client/share/share-sheet.tsx +3 -3
  226. package/src/client/styles/composer.css.ts +119 -26
  227. package/src/client/styles/header.css.ts +7 -7
  228. package/src/client/styles/info.css.ts +74 -0
  229. package/src/client/styles/remote-session.css.ts +251 -0
  230. package/src/client-data/current-session.ts +57 -0
  231. package/src/client-data/remote-group-expansion.ts +145 -0
  232. package/src/client-data/remote-status.ts +374 -0
  233. package/src/client-data/settings-form.ts +1371 -0
  234. package/src/client-data/shares.ts +467 -0
  235. package/src/client-pairing.ts +244 -0
  236. package/src/client-routes.ts +732 -0
  237. package/src/config.ts +447 -0
  238. package/src/fetch-route-intercept.ts +510 -0
  239. package/src/fingerprint.ts +408 -0
  240. package/src/http.ts +68 -0
  241. package/src/index.ts +719 -62
  242. package/src/intercept-shape.ts +87 -0
  243. package/src/intercept.ts +3362 -0
  244. package/src/merge-streams.ts +1272 -0
  245. package/src/relay-access.ts +669 -0
  246. package/src/relay-client.ts +1514 -0
  247. package/src/relay-filter.ts +291 -0
  248. package/src/relay-server.ts +2025 -0
  249. package/src/restart-fields.ts +36 -0
  250. package/src/restart-watcher.ts +140 -0
  251. package/src/session-reference.ts +156 -0
  252. package/src/share-export.ts +26 -1
  253. package/src/share-ops.ts +114 -0
  254. package/src/share-store.ts +373 -0
  255. package/src/virtual-id.ts +59 -0
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Pure pairing/probe logic for the sub-client half (T16): the address
3
+ * normalizer the claim route validates user input with, and the two
4
+ * classifiers that turn one HTTP round-trip each into a shape the settings
5
+ * block can render. No I/O, no clocks, no process reads — the route module
6
+ * owns the sockets and the timeouts, this module owns the decisions.
7
+ *
8
+ * IPv4/IPv6 range checks are hand-rolled because the address arrives as a
9
+ * STRING inside an already-parsed URL and the point is to keep the private-
10
+ * network allowance of `normalizeServerUrl` exactly as narrow as specified
11
+ * (172.16/12, 100.64/10 etc. — off-by-one range bugs here would either lock
12
+ * LAN users out or open plain-http pairing to the public internet).
13
+ */
14
+ import { unwrapVolatile } from './config.js';
15
+ /** The gateway claim endpoint, resolved against the normalized server URL. */
16
+ export const CLAIM_PATH = '/lan-gate/pair/claim-desktop';
17
+ /** The relay ping path the client backend probes (the gateway admits this
18
+ * prefix for desktop-client devices and forwards to the host relay route). */
19
+ export const RELAY_PING_PATH = '/_dsh/zen-remote/relay/ping';
20
+ /** One private-network allowance for plain http, as (label, predicate). IPv4
21
+ * ranges are `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`,
22
+ * `100.64.0.0/10` (CGNAT/Tailscale), `127.0.0.0/8`; IPv6: `::1`, `fc00::/7`,
23
+ * `fe80::/10`; plus the hostnames `localhost` and `*.local`. */
24
+ function isHttpAllowedHost(hostname) {
25
+ if (hostname === 'localhost' || hostname.endsWith('.local'))
26
+ return true;
27
+ if (hostname.includes(':'))
28
+ return isAllowedIpv6(hostname);
29
+ return isAllowedIpv4(hostname);
30
+ }
31
+ /** Dotted-quad check against the allowed ranges. The hostname comes from a
32
+ * parsed URL, so it is already canonical (the WHATWG parser folds exotic
33
+ * spellings like `010.0x0A.0.3` into plain decimal before we ever see it). */
34
+ function isAllowedIpv4(hostname) {
35
+ const parts = hostname.split('.');
36
+ if (parts.length !== 4)
37
+ return false;
38
+ const bytes = [];
39
+ for (const part of parts) {
40
+ if (!/^\d{1,3}$/.test(part))
41
+ return false;
42
+ const value = Number(part);
43
+ if (value > 255)
44
+ return false;
45
+ bytes.push(value);
46
+ }
47
+ const [a, b] = bytes;
48
+ if (a === 10 || a === 127)
49
+ return true; // 10.0.0.0/8, 127.0.0.0/8
50
+ if (a === 172 && b >= 16 && b <= 31)
51
+ return true; // 172.16.0.0/12
52
+ if (a === 192 && b === 168)
53
+ return true; // 192.168.0.0/16
54
+ if (a === 100 && b >= 64 && b <= 127)
55
+ return true; // 100.64.0.0/10
56
+ return false;
57
+ }
58
+ /** IPv6 check against `::1`, `fc00::/7` and `fe80::/10`. Hostnames of parsed
59
+ * URLs keep the square brackets; the zone suffix (`%25…`) is rejected —
60
+ * link-local with a zone id is a machine-local spelling anyway. */
61
+ function isAllowedIpv6(bracketed) {
62
+ const host = bracketed.startsWith('[') && bracketed.endsWith(']') ? bracketed.slice(1, -1) : bracketed;
63
+ if (!host.includes('%') && host === '::1')
64
+ return true;
65
+ const groups = ipv6Groups(host);
66
+ if (groups === undefined)
67
+ return false;
68
+ const first = groups[0];
69
+ return (first >= 0xfc00 && first <= 0xfdff) || (first >= 0xfe80 && first <= 0xfebf);
70
+ }
71
+ /** Expand an IPv6 string into its 8 numeric groups, honoring one `::`.
72
+ * Anything malformed — a second `::`, a non-hex or over-long group, more
73
+ * than 8 groups without `::` — is rejected (undefined), never guessed. */
74
+ function ipv6Groups(host) {
75
+ const halves = host.split('::');
76
+ if (halves.length > 2)
77
+ return undefined;
78
+ const parseGroups = (text) => {
79
+ if (text === '')
80
+ return [];
81
+ const groups = [];
82
+ for (const raw of text.split(':')) {
83
+ if (!/^[0-9a-fA-F]{1,4}$/.test(raw))
84
+ return undefined;
85
+ groups.push(parseInt(raw, 16));
86
+ }
87
+ return groups;
88
+ };
89
+ const head = parseGroups(halves[0]);
90
+ if (head === undefined)
91
+ return undefined;
92
+ const tail = halves.length === 2 ? parseGroups(halves[1]) : [];
93
+ if (tail === undefined)
94
+ return undefined;
95
+ const missing = 8 - head.length - tail.length;
96
+ if (missing < 0 || (halves.length === 1 && missing !== 0))
97
+ return undefined;
98
+ return [...head, ...new Array(missing).fill(0), ...tail];
99
+ }
100
+ /**
101
+ * Normalize one user-typed server address to the exact string the client
102
+ * stores and probes: surrounding whitespace and trailing slashes gone, no
103
+ * path (beyond `/`), no query, no fragment, no embedded credentials, and a
104
+ * scheme of `http:` (private hosts only) or `https:` (always).
105
+ */
106
+ export function normalizeServerUrl(input) {
107
+ if (typeof input !== 'string')
108
+ return { ok: false, reason: 'invalid' };
109
+ let parsed;
110
+ try {
111
+ parsed = new URL(input.trim().replace(/\/+$/u, ''));
112
+ }
113
+ catch {
114
+ return { ok: false, reason: 'invalid' };
115
+ }
116
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
117
+ return { ok: false, reason: 'invalid' };
118
+ if (parsed.pathname !== '/' || parsed.search !== '' || parsed.hash !== '')
119
+ return { ok: false, reason: 'invalid' };
120
+ if (parsed.username !== '' || parsed.password !== '')
121
+ return { ok: false, reason: 'invalid' };
122
+ if (parsed.protocol === 'http:' && !isHttpAllowedHost(parsed.hostname)) {
123
+ return { ok: false, reason: 'insecure-http' };
124
+ }
125
+ return { ok: true, url: parsed.origin };
126
+ }
127
+ /**
128
+ * The volatile row field's current server address, read through the loader's
129
+ * `{ get() }` wrapper and normalized exactly like the claim route validates
130
+ * user input. undefined when the field is unset or no longer legal: a
131
+ * hand-edited row must never have the pairing token sent to an address the
132
+ * claim route would have refused. Used by the relay client's address getter
133
+ * in src/index.ts; the status route reads the row itself because it needs
134
+ * the unset / invalid distinction its own two states answer with.
135
+ */
136
+ export function normalizedRowServerUrl(row) {
137
+ const record = row !== null && typeof row === 'object' && !Array.isArray(row) ? row : {};
138
+ const value = unwrapVolatile(record.serverUrl);
139
+ const serverUrl = typeof value === 'string' ? value.trim() : '';
140
+ if (serverUrl === '')
141
+ return undefined;
142
+ const normalized = normalizeServerUrl(serverUrl);
143
+ return normalized.ok ? normalized.url : undefined;
144
+ }
145
+ /** First string value of a possibly-unknown field, for the success shape. */
146
+ function stringOf(value) {
147
+ return typeof value === 'string' ? value : '';
148
+ }
149
+ /**
150
+ * Classify one claim round-trip. `body` may be anything the server returned
151
+ * (including unparsable garbage — the route passes `undefined` then): only
152
+ * the exact happy shape counts as success, everything else lands in the four
153
+ * refusal buckets the settings block has copy for.
154
+ */
155
+ export function classifyClaimResponse(status, body) {
156
+ const record = body !== null && typeof body === 'object' && !Array.isArray(body)
157
+ ? body
158
+ : {};
159
+ if (status >= 200 && status < 300 && record.ok === true) {
160
+ const token = stringOf(record.token);
161
+ if (token === '')
162
+ return { ok: false, code: 'unexpected' };
163
+ return {
164
+ ok: true,
165
+ token,
166
+ deviceId: stringOf(record.id),
167
+ deviceName: stringOf(record.name),
168
+ };
169
+ }
170
+ if (status === 403 && record.reason === 'role-mismatch') {
171
+ const message = stringOf(record.message);
172
+ return message === '' ? { ok: false, code: 'role-mismatch' } : { ok: false, code: 'role-mismatch', message };
173
+ }
174
+ if (status === 403 && record.reason === 'bad-code')
175
+ return { ok: false, code: 'bad-code' };
176
+ if (status === 429 && record.reason === 'locked') {
177
+ const retryAfterMs = typeof record.retryAfterMs === 'number' && Number.isFinite(record.retryAfterMs)
178
+ ? Math.max(0, record.retryAfterMs)
179
+ : 0;
180
+ return { ok: false, code: 'locked', retryAfterMs };
181
+ }
182
+ return { ok: false, code: 'unexpected' };
183
+ }
184
+ /**
185
+ * Classify one probe of `<serverUrl>${RELAY_PING_PATH}`:
186
+ *
187
+ * - no HTTP answer at all → `unreachable` (down, wrong address, timeout);
188
+ * - 401 with the relay route's own `relay-unauthorized` error → `unexpected`
189
+ * (T23a-fix): the gateway ACCEPTED the token but the server's internal
190
+ * secret disagrees — a server-side fault that must not unpair a valid
191
+ * device; only the gateway's own pairing wall (`reason: 'unpaired'`) is
192
+ * a dead token;
193
+ * - any other 401 → `revoked` — the gateway treats an invalid or revoked
194
+ * desktop-client token as unpaired and answers its 401 wall;
195
+ * - 403 with `reason: 'relay-only'` → `unexpected`: the token was ACCEPTED
196
+ * but the server refuses the relay prefix — only possible when the server
197
+ * is not running the 2.0.0 gateway;
198
+ * - any other status (200 ping ok, 404 from a pre-relay server, even 3xx)
199
+ * means the gateway accepted the token → `connected`.
200
+ */
201
+ export function classifyProbe(result) {
202
+ if (result.kind === 'error')
203
+ return 'unreachable';
204
+ if (result.status === 401) {
205
+ const body = result.body !== null && typeof result.body === 'object' && !Array.isArray(result.body)
206
+ ? result.body
207
+ : undefined;
208
+ const error = body !== undefined && body.error !== null && typeof body.error === 'object' && !Array.isArray(body.error)
209
+ ? body.error
210
+ : undefined;
211
+ if (error !== undefined && error.code === 'relay-unauthorized')
212
+ return 'unexpected';
213
+ return 'revoked';
214
+ }
215
+ if (result.status === 403) {
216
+ const reason = result.body !== null && typeof result.body === 'object' && !Array.isArray(result.body)
217
+ ? result.body.reason
218
+ : undefined;
219
+ if (reason === 'relay-only')
220
+ return 'unexpected';
221
+ }
222
+ return 'connected';
223
+ }
224
+ //# sourceMappingURL=client-pairing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-pairing.js","sourceRoot":"","sources":["../src/client-pairing.ts"],"names":[],"mappings":"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"}