herdr-remote 0.2.38 → 0.3.31

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 (203) hide show
  1. package/dist/chunks/353DMT35.js +3 -0
  2. package/dist/chunks/42KARAL4.js +31 -0
  3. package/dist/chunks/7AUQHB3O.js +10 -0
  4. package/dist/chunks/7SMZ43LI.js +9 -0
  5. package/dist/chunks/AM3OEMFK.js +16 -0
  6. package/dist/chunks/ANNJESOL.js +10 -0
  7. package/dist/chunks/AWB4A65P.js +63 -0
  8. package/dist/chunks/C2UUZY6J.js +2 -0
  9. package/dist/chunks/DGUZCGQN.js +3 -0
  10. package/dist/chunks/H24ZP5D5.js +16 -0
  11. package/dist/chunks/I6NRCYLI.js +3 -0
  12. package/dist/chunks/JJGEV4EZ.js +3 -0
  13. package/dist/chunks/KRSURB5L.js +5 -0
  14. package/dist/chunks/KRVF37OW.js +3 -0
  15. package/dist/chunks/KUDCC7ZO.js +6 -0
  16. package/dist/chunks/M3NRPJK2.js +77 -0
  17. package/dist/chunks/M4AFZHKH.js +3 -0
  18. package/dist/chunks/M52LJEES.js +6 -0
  19. package/dist/chunks/O2LNVFUT.js +3 -0
  20. package/dist/chunks/OJPBI7KB.js +3 -0
  21. package/dist/chunks/OMN6C7J6.js +3 -0
  22. package/dist/chunks/PBMBFWLQ.js +3 -0
  23. package/dist/chunks/PBRQ3HEG.js +3 -0
  24. package/dist/chunks/PFFVGFPL.js +45 -0
  25. package/dist/chunks/QRGNEKME.js +5 -0
  26. package/dist/chunks/TNLCNVA2.js +3 -0
  27. package/dist/chunks/U27JJKJ6.js +3 -0
  28. package/dist/chunks/U6QAYG3E.js +3 -0
  29. package/dist/chunks/UEOQE77N.js +4 -0
  30. package/dist/chunks/WSACSE45.js +4 -0
  31. package/dist/chunks/WY7WHEJ4.js +3 -0
  32. package/dist/chunks/XJKWLK43.js +4 -0
  33. package/dist/chunks/XN6D5S55.js +3 -0
  34. package/dist/chunks/YD3MIP7B.js +5 -0
  35. package/dist/chunks/YXDQ62BB.js +4 -0
  36. package/dist/cli.js +21 -294
  37. package/dist/connector/admin-broker.js +3 -302
  38. package/dist/connector/main.js +20 -18
  39. package/dist/harfbuzz-subset.wasm +0 -0
  40. package/dist/postinstall.js +17 -0
  41. package/herdr-plugin.toml +1 -1
  42. package/node_modules/herdr-remote-relay/bin/herdr-remote-relay.js +33 -92
  43. package/node_modules/herdr-remote-relay/package.json +3 -46
  44. package/package.json +8 -27
  45. package/dist/agent-status.js +0 -130
  46. package/dist/binary-plist.js +0 -157
  47. package/dist/config.js +0 -278
  48. package/dist/connector/admin-broker-setup.js +0 -40
  49. package/dist/connector/admin-shell.js +0 -67
  50. package/dist/connector/admin-tab.js +0 -90
  51. package/dist/connector/adminBrokerEndpoint.js +0 -68
  52. package/dist/connector/agent-watch.js +0 -123
  53. package/dist/connector/elevatedPty.js +0 -141
  54. package/dist/connector/font-server.js +0 -165
  55. package/dist/connector/herdr-prefix-keys.js +0 -28
  56. package/dist/connector/host-connector.js +0 -433
  57. package/dist/connector/host-hello.js +0 -32
  58. package/dist/connector/lock.js +0 -73
  59. package/dist/connector/sessions.js +0 -381
  60. package/dist/connector/update-report.js +0 -70
  61. package/dist/connector/wire.js +0 -42
  62. package/dist/exit-codes.js +0 -13
  63. package/dist/font-subset.js +0 -110
  64. package/dist/herdr-api.js +0 -254
  65. package/dist/herdr-command.js +0 -245
  66. package/dist/herdr-plugin.js +0 -120
  67. package/dist/herdr-server.js +0 -190
  68. package/dist/i18n/en.js +0 -257
  69. package/dist/i18n/index.js +0 -48
  70. package/dist/i18n/zh.js +0 -256
  71. package/dist/keepalive/detached.js +0 -97
  72. package/dist/keepalive/environment.js +0 -44
  73. package/dist/keepalive/index.js +0 -129
  74. package/dist/keepalive/launchd.js +0 -117
  75. package/dist/keepalive/systemd.js +0 -112
  76. package/dist/keepalive/types.js +0 -1
  77. package/dist/keepalive/windows-service-setup.js +0 -138
  78. package/dist/keepalive/windows-service.js +0 -272
  79. package/dist/keepalive/windows-task.js +0 -191
  80. package/dist/lib/process.js +0 -132
  81. package/dist/lifecycle.js +0 -79
  82. package/dist/net-interfaces.js +0 -82
  83. package/dist/node-pty-loader.js +0 -80
  84. package/dist/pasted-files.js +0 -115
  85. package/dist/paths.js +0 -74
  86. package/dist/pty-session.js +0 -133
  87. package/dist/relay-client.js +0 -106
  88. package/dist/relay-urls.js +0 -99
  89. package/dist/runtime.js +0 -107
  90. package/dist/service.js +0 -267
  91. package/dist/settings-model.js +0 -298
  92. package/dist/socket-discovery.js +0 -70
  93. package/dist/supervisor.js +0 -323
  94. package/dist/terminal-font/deps.js +0 -83
  95. package/dist/terminal-font/files.js +0 -357
  96. package/dist/terminal-font/formats.js +0 -355
  97. package/dist/terminal-font/index.js +0 -187
  98. package/dist/terminal-font/readers.js +0 -373
  99. package/dist/terminal-font/terminals.js +0 -147
  100. package/dist/terminal-palette.js +0 -399
  101. package/dist/tui/App.js +0 -199
  102. package/dist/tui/api.js +0 -91
  103. package/dist/tui/components/TextField.js +0 -50
  104. package/dist/tui/components/common.js +0 -80
  105. package/dist/tui/index.js +0 -36
  106. package/dist/tui/mouse/index.js +0 -164
  107. package/dist/tui/mouse/protocol.js +0 -96
  108. package/dist/tui/mouse/source.js +0 -79
  109. package/dist/tui/screens/About.js +0 -172
  110. package/dist/tui/screens/Herdr.js +0 -75
  111. package/dist/tui/screens/Keepalive.js +0 -141
  112. package/dist/tui/screens/Overview.js +0 -36
  113. package/dist/tui/screens/Pair.js +0 -74
  114. package/dist/tui/screens/Relay.js +0 -243
  115. package/dist/tui/screens/Services.js +0 -63
  116. package/dist/tui/screens/Wizard.js +0 -241
  117. package/dist/tui/terminal.js +0 -39
  118. package/dist/tui/theme.js +0 -33
  119. package/dist/update-command.js +0 -118
  120. package/dist/updater/in-place.js +0 -83
  121. package/dist/updater/npm.js +0 -62
  122. package/dist/updater.js +0 -401
  123. package/node_modules/herdr-remote-relay/README.md +0 -76
  124. package/node_modules/herdr-remote-relay/README.zh-CN.md +0 -76
  125. package/node_modules/herdr-remote-relay/config.example.json +0 -18
  126. package/node_modules/herdr-remote-relay/deploy/cloudflared/config.yml.example +0 -9
  127. package/node_modules/herdr-remote-relay/deploy/docker/compose.yaml +0 -23
  128. package/node_modules/herdr-remote-relay/deploy/nginx/herdr-remote-relay.conf.example +0 -49
  129. package/node_modules/herdr-remote-relay/deploy/systemd/herdr-remote-relay.service +0 -41
  130. package/node_modules/herdr-remote-relay/deploy/systemd/relay.env.example +0 -25
  131. package/node_modules/herdr-remote-relay/dist/auth-store.d.ts +0 -114
  132. package/node_modules/herdr-remote-relay/dist/auth-store.js +0 -289
  133. package/node_modules/herdr-remote-relay/dist/metrics.d.ts +0 -76
  134. package/node_modules/herdr-remote-relay/dist/metrics.js +0 -249
  135. package/node_modules/herdr-remote-relay/dist/protocol/frames.d.ts +0 -27
  136. package/node_modules/herdr-remote-relay/dist/protocol/frames.js +0 -110
  137. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.d.ts +0 -8
  138. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.js +0 -34
  139. package/node_modules/herdr-remote-relay/dist/protocol/http.d.ts +0 -165
  140. package/node_modules/herdr-remote-relay/dist/protocol/http.js +0 -3
  141. package/node_modules/herdr-remote-relay/dist/protocol/index.d.ts +0 -7
  142. package/node_modules/herdr-remote-relay/dist/protocol/index.js +0 -9
  143. package/node_modules/herdr-remote-relay/dist/protocol/messages.d.ts +0 -362
  144. package/node_modules/herdr-remote-relay/dist/protocol/messages.js +0 -20
  145. package/node_modules/herdr-remote-relay/dist/protocol/paste.d.ts +0 -14
  146. package/node_modules/herdr-remote-relay/dist/protocol/paste.js +0 -50
  147. package/node_modules/herdr-remote-relay/dist/protocol/release.d.ts +0 -8
  148. package/node_modules/herdr-remote-relay/dist/protocol/release.js +0 -17
  149. package/node_modules/herdr-remote-relay/dist/protocol/terminal.d.ts +0 -79
  150. package/node_modules/herdr-remote-relay/dist/protocol/terminal.js +0 -150
  151. package/node_modules/herdr-remote-relay/dist/relay-config.d.ts +0 -74
  152. package/node_modules/herdr-remote-relay/dist/relay-config.js +0 -316
  153. package/node_modules/herdr-remote-relay/dist/relay-server.d.ts +0 -67
  154. package/node_modules/herdr-remote-relay/dist/relay-server.js +0 -196
  155. package/node_modules/herdr-remote-relay/dist/scroll-input.d.ts +0 -17
  156. package/node_modules/herdr-remote-relay/dist/scroll-input.js +0 -124
  157. package/node_modules/herdr-remote-relay/dist/server/client-channel.d.ts +0 -3
  158. package/node_modules/herdr-remote-relay/dist/server/client-channel.js +0 -396
  159. package/node_modules/herdr-remote-relay/dist/server/font-proxy.d.ts +0 -19
  160. package/node_modules/herdr-remote-relay/dist/server/font-proxy.js +0 -155
  161. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.d.ts +0 -27
  162. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.js +0 -83
  163. package/node_modules/herdr-remote-relay/dist/server/host-channel.d.ts +0 -3
  164. package/node_modules/herdr-remote-relay/dist/server/host-channel.js +0 -271
  165. package/node_modules/herdr-remote-relay/dist/server/http-api.d.ts +0 -4
  166. package/node_modules/herdr-remote-relay/dist/server/http-api.js +0 -219
  167. package/node_modules/herdr-remote-relay/dist/server/maintenance.d.ts +0 -5
  168. package/node_modules/herdr-remote-relay/dist/server/maintenance.js +0 -64
  169. package/node_modules/herdr-remote-relay/dist/server/requests.d.ts +0 -34
  170. package/node_modules/herdr-remote-relay/dist/server/requests.js +0 -142
  171. package/node_modules/herdr-remote-relay/dist/server/sessions.d.ts +0 -36
  172. package/node_modules/herdr-remote-relay/dist/server/sessions.js +0 -230
  173. package/node_modules/herdr-remote-relay/dist/server/sockets.d.ts +0 -12
  174. package/node_modules/herdr-remote-relay/dist/server/sockets.js +0 -64
  175. package/node_modules/herdr-remote-relay/dist/server/status.d.ts +0 -11
  176. package/node_modules/herdr-remote-relay/dist/server/status.js +0 -99
  177. package/node_modules/herdr-remote-relay/dist/server/transport.d.ts +0 -16
  178. package/node_modules/herdr-remote-relay/dist/server/transport.js +0 -110
  179. package/node_modules/herdr-remote-relay/dist/server/types.d.ts +0 -133
  180. package/node_modules/herdr-remote-relay/dist/server/types.js +0 -3
  181. package/node_modules/herdr-remote-relay/dist/server/version.d.ts +0 -2
  182. package/node_modules/herdr-remote-relay/dist/server/version.js +0 -5
  183. package/node_modules/herdr-remote-relay/dist/state.d.ts +0 -6
  184. package/node_modules/herdr-remote-relay/dist/state.js +0 -47
  185. package/node_modules/ws/LICENSE +0 -20
  186. package/node_modules/ws/README.md +0 -548
  187. package/node_modules/ws/browser.js +0 -8
  188. package/node_modules/ws/index.js +0 -22
  189. package/node_modules/ws/lib/buffer-util.js +0 -131
  190. package/node_modules/ws/lib/constants.js +0 -19
  191. package/node_modules/ws/lib/event-target.js +0 -292
  192. package/node_modules/ws/lib/extension.js +0 -203
  193. package/node_modules/ws/lib/limiter.js +0 -55
  194. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  195. package/node_modules/ws/lib/receiver.js +0 -743
  196. package/node_modules/ws/lib/sender.js +0 -607
  197. package/node_modules/ws/lib/stream.js +0 -161
  198. package/node_modules/ws/lib/subprotocol.js +0 -62
  199. package/node_modules/ws/lib/validation.js +0 -152
  200. package/node_modules/ws/lib/websocket-server.js +0 -562
  201. package/node_modules/ws/lib/websocket.js +0 -1407
  202. package/node_modules/ws/package.json +0 -74
  203. package/node_modules/ws/wrapper.mjs +0 -21
package/dist/updater.js DELETED
@@ -1,401 +0,0 @@
1
- // Self-update for the installed CLI.
2
- //
3
- // Releases come from GitHub first and npm second: a GitHub Release installs the
4
- // moment it exists, while npm can take many minutes after publishing before a
5
- // version resolves. Both carry the same self-contained tarball, which bundles
6
- // the relay, so one install updates both.
7
- //
8
- // The update path is only meaningful for a package installed with npm. A
9
- // source checkout is managed by git and a linked development copy by the
10
- // developer, so this refuses to touch either: silently running `npm install -g`
11
- // over a checkout would replace the tree someone is working in.
12
- import fs from 'node:fs';
13
- import os from 'node:os';
14
- import path from 'node:path';
15
- import { spawn } from 'node:child_process';
16
- import { createRequire } from 'node:module';
17
- import { cliTarballUrl, GITHUB_RELEASES_URL, LATEST_CLI_TARBALL_URL, LATEST_RELEASE_MANIFEST_URL, } from 'herdr-remote-relay/protocol';
18
- import { PACKAGE_ROOT } from './paths.js';
19
- import { heldByAnotherProcess, installInPlace } from './updater/in-place.js';
20
- import { runNpm } from './updater/npm.js';
21
- const PACKAGE_NAME = 'herdr-remote';
22
- const RELAY_PACKAGE = 'herdr-remote-relay';
23
- const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
24
- /**
25
- * A public mirror, tried last.
26
- *
27
- * `registry.npmjs.org` is not reachable from every network this runs on — in
28
- * mainland China it usually is not — and "could not reach npm registry" on a
29
- * machine that installs packages perfectly well is a bug report, not a
30
- * diagnosis. The mirror is read-only and only ever asked for a version number.
31
- */
32
- const MIRROR_REGISTRY = 'https://registry.npmmirror.com';
33
- function currentVersion() {
34
- try {
35
- return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
36
- }
37
- catch {
38
- return '0.0.0';
39
- }
40
- }
41
- /** The relay bundled with this herdr-remote, read from disk: an update replaces it. */
42
- function installedRelayVersion() {
43
- try {
44
- const manifest = createRequire(import.meta.url).resolve(`${RELAY_PACKAGE}/package.json`);
45
- return JSON.parse(fs.readFileSync(manifest, 'utf8')).version;
46
- }
47
- catch {
48
- return null;
49
- }
50
- }
51
- /**
52
- * How this copy got here.
53
- *
54
- * - `npm` installed with npm, from a release tarball or a registry; updating is `npm install -g`.
55
- * - `linked` `npm link`ed into a global tree from a working copy.
56
- * - `source` run straight out of a checkout.
57
- *
58
- * The distinction is drawn from the path rather than from npm, which would
59
- * mean shelling out on every render.
60
- */
61
- function installKind() {
62
- const root = PACKAGE_ROOT;
63
- // A real install always lives inside a node_modules tree.
64
- if (!root.split(path.sep).includes('node_modules'))
65
- return 'source';
66
- try {
67
- // `npm link` leaves a symlink where a published install has a directory.
68
- if (fs.lstatSync(root).isSymbolicLink())
69
- return 'linked';
70
- }
71
- catch {
72
- // Unreadable: treat as a normal install and let npm report the problem.
73
- }
74
- return 'npm';
75
- }
76
- /** `HERDR_REMOTE_UPDATE_CHECK=0` turns the automatic checks off (offline machines, tests). */
77
- function updateChecksEnabled(env = process.env) {
78
- return env.HERDR_REMOTE_UPDATE_CHECK !== '0';
79
- }
80
- function canSelfUpdate() {
81
- return installKind() === 'npm';
82
- }
83
- function normalizeRegistry(value) {
84
- if (typeof value !== 'string')
85
- return null;
86
- const trimmed = value.trim().replace(/\/+$/, '');
87
- if (!/^https?:\/\//i.test(trimmed))
88
- return null;
89
- return trimmed;
90
- }
91
- /** The `registry=` line from an npmrc file, if it has one. */
92
- function registryFromNpmrc(filePath) {
93
- let contents;
94
- try {
95
- contents = fs.readFileSync(filePath, 'utf8');
96
- }
97
- catch {
98
- return null;
99
- }
100
- let found = null;
101
- for (const rawLine of contents.split(/\r?\n/)) {
102
- const line = rawLine.trim();
103
- if (!line || line.startsWith('#') || line.startsWith(';'))
104
- continue;
105
- const match = /^registry\s*=\s*(.+)$/i.exec(line);
106
- // The last assignment wins, as it does for npm itself.
107
- if (match)
108
- found = normalizeRegistry(match[1].replace(/^["']|["']$/g, ''));
109
- }
110
- return found;
111
- }
112
- /**
113
- * Where to ask, in the order to ask.
114
- *
115
- * Whatever npm itself is configured to use comes first: a machine behind a
116
- * corporate proxy or on a mirror has already answered this question, and
117
- * ignoring that answer is what made the check fail on a machine where
118
- * `npm install` works. The public registry and then a public mirror follow, so
119
- * a private registry that does not carry this package is not the end of it.
120
- */
121
- function registryCandidates({ env = process.env, home = os.homedir(), cwd = process.cwd(), } = {}) {
122
- const candidates = [];
123
- const add = (value) => {
124
- const normalized = normalizeRegistry(value);
125
- if (normalized && !candidates.includes(normalized))
126
- candidates.push(normalized);
127
- };
128
- // npm exports its whole config into the environment of anything it runs.
129
- add(env.npm_config_registry);
130
- add(env.NPM_CONFIG_REGISTRY);
131
- add(env.HERDR_REMOTE_REGISTRY);
132
- add(registryFromNpmrc(path.join(cwd, '.npmrc')));
133
- add(registryFromNpmrc(path.join(home, '.npmrc')));
134
- add(DEFAULT_REGISTRY);
135
- add(MIRROR_REGISTRY);
136
- return candidates;
137
- }
138
- /** Compare two `MAJOR.MINOR.PATCH` strings. Returns 1, -1 or 0. */
139
- function compareVersions(a, b) {
140
- const parse = (value) => String(value)
141
- .split('-')[0]
142
- .split('.')
143
- .map((part) => Number.parseInt(part, 10) || 0);
144
- const left = parse(a);
145
- const right = parse(b);
146
- for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
147
- const diff = (left[index] || 0) - (right[index] || 0);
148
- if (diff > 0)
149
- return 1;
150
- if (diff < 0)
151
- return -1;
152
- }
153
- return 0;
154
- }
155
- /** One JSON request with a deadline. Never throws. */
156
- async function fetchJson(url, { timeoutMs, fetchImpl }) {
157
- const controller = new AbortController();
158
- const timer = setTimeout(() => controller.abort(), timeoutMs);
159
- try {
160
- const response = await fetchImpl(url, {
161
- signal: controller.signal,
162
- headers: { Accept: 'application/json', 'Cache-Control': 'no-cache' },
163
- });
164
- if (!response.ok)
165
- return { ok: false, message: `HTTP ${response.status ?? '?'}` };
166
- return { ok: true, body: await response.json() };
167
- }
168
- catch (error) {
169
- const failure = error;
170
- return {
171
- ok: false,
172
- message: failure?.name === 'AbortError' ? 'timed out' : String(failure?.message || error),
173
- };
174
- }
175
- finally {
176
- clearTimeout(timer);
177
- }
178
- }
179
- /**
180
- * One registry, one question: what is the latest published version?
181
- *
182
- * The dist-tags endpoint first: a few bytes, answered fresh by npmjs and
183
- * npmmirror alike. `/<package>/latest` is the fallback for registries without
184
- * it, asked for plain JSON. It used to be asked for npm's abbreviated install
185
- * format, which npmjs refuses on that path with a 406, so every check quietly
186
- * fell through to a mirror that had not synced the release yet.
187
- */
188
- async function askRegistry(registry, { timeoutMs, fetchImpl }) {
189
- const endpoints = [
190
- {
191
- url: `${registry}/-/package/${PACKAGE_NAME}/dist-tags`,
192
- read: (body) => body?.latest,
193
- },
194
- { url: `${registry}/${PACKAGE_NAME}/latest`, read: (body) => body?.version },
195
- ];
196
- let message = null;
197
- for (const endpoint of endpoints) {
198
- const attempt = await fetchJson(endpoint.url, { timeoutMs, fetchImpl });
199
- if (!attempt.ok) {
200
- message = attempt.message;
201
- continue;
202
- }
203
- const latest = endpoint.read(attempt.body);
204
- if (typeof latest === 'string' && latest)
205
- return { ok: true, latest };
206
- message = 'registry answered without a version';
207
- }
208
- return { ok: false, message };
209
- }
210
- const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
211
- /** The newest GitHub Release, from the manifest every release carries. */
212
- async function askGithubReleases(manifestUrl, { timeoutMs, fetchImpl }) {
213
- const attempt = await fetchJson(manifestUrl, { timeoutMs, fetchImpl });
214
- if (!attempt.ok)
215
- return attempt;
216
- const version = attempt.body?.version;
217
- if (typeof version === 'string' && VERSION_PATTERN.test(version)) {
218
- return { ok: true, latest: version };
219
- }
220
- return { ok: false, message: 'release manifest has no version' };
221
- }
222
- /**
223
- * Ask what the current release is.
224
- *
225
- * Never throws: an update check is a convenience, and a machine that is offline
226
- * or behind a proxy should still get a working settings screen.
227
- *
228
- * GitHub Releases and every registry are asked at once and the newest answer
229
- * wins. Taking the first answer let a mirror a few minutes (or hours) behind
230
- * report the release before last as current. `sources` lists the ones that
231
- * already carry the newest version, GitHub first, which is where an install
232
- * should come from; `behind` lists the ones that do not, so the screen can say
233
- * why. GitHub is not reachable from every network, which is what the
234
- * registries are still asked for.
235
- */
236
- async function checkForUpdate({ timeoutMs = 6000, fetchImpl = globalThis.fetch, registries = registryCandidates(), releaseManifest = LATEST_RELEASE_MANIFEST_URL, current = currentVersion(), } = {}) {
237
- const attempts = await Promise.all([
238
- ...(releaseManifest
239
- ? [
240
- askGithubReleases(releaseManifest, { timeoutMs, fetchImpl }).then((answer) => ({
241
- source: GITHUB_RELEASES_URL,
242
- ...answer,
243
- })),
244
- ]
245
- : []),
246
- ...registries.map(async (registry) => ({
247
- source: registry,
248
- ...(await askRegistry(registry, { timeoutMs, fetchImpl })),
249
- })),
250
- ]);
251
- const answers = attempts.filter((attempt) => attempt.ok);
252
- if (answers.length === 0) {
253
- // The reason is carried out with the failure. "Could not reach npm
254
- // registry" with nothing after it leaves the user guessing between DNS, a
255
- // proxy, a firewall and a mirror that does not carry the package.
256
- return {
257
- ok: false,
258
- current,
259
- errorKey: 'update.errorNetwork',
260
- message: attempts
261
- .map((attempt) => `${attempt.source}: ${'message' in attempt ? attempt.message : ''}`)
262
- .join('; '),
263
- };
264
- }
265
- const latest = answers
266
- .map((answer) => answer.latest)
267
- .reduce((best, version) => (compareVersions(version, best) > 0 ? version : best));
268
- const sources = answers
269
- .filter((answer) => compareVersions(answer.latest, latest) === 0)
270
- .map((answer) => answer.source);
271
- const behind = answers
272
- .filter((answer) => compareVersions(answer.latest, latest) < 0)
273
- .map((answer) => ({ source: answer.source, version: answer.latest }));
274
- return {
275
- ok: true,
276
- current,
277
- latest,
278
- source: sources[0],
279
- sources,
280
- behind,
281
- updateAvailable: compareVersions(latest, current) > 0,
282
- };
283
- }
284
- /** Just the lines npm meant for a person: its `npm error` / `npm ERR!` summary. */
285
- function npmErrorSummary(output) {
286
- const lines = String(output || '')
287
- .split(/\r?\n/)
288
- .map((line) => line.trim())
289
- .filter((line) => /^npm (error|ERR!)/.test(line) && !/complete log of this run/i.test(line))
290
- .map((line) => line.replace(/^npm (error|ERR!)\s*/, ''))
291
- .filter(Boolean);
292
- return (lines.length > 0
293
- ? lines.slice(0, 3)
294
- : String(output || '')
295
- .trim()
296
- .split(/\r?\n/)
297
- .slice(-3)).join(' ');
298
- }
299
- /** "No matching version": the registry has not caught up with its own dist-tag. */
300
- function isNotYetPublished(output) {
301
- return /\bETARGET\b|notarget|No matching version found/i.test(String(output || ''));
302
- }
303
- /** What is actually installed now, read from disk rather than the require cache. */
304
- function installedVersionOnDisk() {
305
- try {
306
- return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
307
- }
308
- catch {
309
- return null;
310
- }
311
- }
312
- /**
313
- * Install a release over this one.
314
- *
315
- * Resolves with `{ ok, output }` rather than rejecting, so the caller can show
316
- * npm's own message; npm's diagnostics are more useful than anything that could
317
- * be invented here.
318
- *
319
- * - The exact version the check found is installed, not `@latest`, from the
320
- * sources that reported it: GitHub's release tarball first, then registries,
321
- * asked with `--prefer-online` so npm revalidates the package metadata it
322
- * cached before the release existed.
323
- * - Right after a release, npm's view of the package can still lack the new
324
- * version although its dist-tag already points at it: npm answers ETARGET.
325
- * That is a wait, not a failure, so it is retried a few times, then the next
326
- * source that carries the version is tried.
327
- * - On Windows, when something holds the package directory so npm cannot
328
- * rename it, the release is copied over it instead.
329
- * - Success is what is on disk afterwards, not npm's exit code.
330
- */
331
- async function performUpdate({ spawnImpl = spawn, timeoutMs = 180_000,
332
- // Seam for tests: the suite runs from a checkout, where the guard below is
333
- // correctly the only reachable outcome.
334
- installKindImpl = installKind,
335
- // The sources that reported `version`, preferred first. Empty means GitHub,
336
- // then whatever registry npm is configured with, which is right when no
337
- // check has run.
338
- source = '', sources = [], version = null, attempts = 3, retryDelayMs = 15_000, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), readInstalledVersion = installedVersionOnDisk, onAttempt = () => { }, platform = process.platform, inPlace = installInPlace, } = {}) {
339
- const kind = installKindImpl();
340
- if (kind !== 'npm')
341
- return { ok: false, errorKey: `update.cannot.${kind}` };
342
- const target = typeof version === 'string' && VERSION_PATTERN.test(version) ? version : null;
343
- const candidates = [];
344
- for (const candidate of [source, ...sources]) {
345
- const normalized = normalizeRegistry(candidate);
346
- if (normalized && !candidates.includes(normalized))
347
- candidates.push(normalized);
348
- }
349
- // '' is npm's own configured registry.
350
- if (candidates.length === 0)
351
- candidates.push(GITHUB_RELEASES_URL, '');
352
- let last = { ok: false, output: '' };
353
- let notYetPublished = false;
354
- for (const candidate of candidates) {
355
- const github = candidate === GITHUB_RELEASES_URL;
356
- const args = github
357
- ? ['install', '-g', target ? cliTarballUrl(target) : LATEST_CLI_TARBALL_URL]
358
- : ['install', '-g', `${PACKAGE_NAME}@${target || 'latest'}`, '--prefer-online'];
359
- if (candidate && !github)
360
- args.push('--registry', candidate);
361
- for (let attempt = 1; attempt <= attempts; attempt += 1) {
362
- onAttempt({ source: candidate, attempt });
363
- let result = await runNpm(spawnImpl, args, timeoutMs);
364
- if (!result.ok && platform === 'win32' && heldByAnotherProcess(result.output))
365
- result = await inPlace(args, (stagedArgs) => runNpm(spawnImpl, stagedArgs, timeoutMs));
366
- if (result.ok) {
367
- const installed = readInstalledVersion();
368
- if (target && installed && compareVersions(installed, target) < 0) {
369
- return {
370
- ok: false,
371
- errorKey: 'update.errorNotApplied',
372
- installed,
373
- output: result.output.slice(-2000),
374
- };
375
- }
376
- return { ok: true, installed, output: result.output.slice(-2000) };
377
- }
378
- last = result;
379
- if (result.spawnFailed) {
380
- return {
381
- ok: false,
382
- errorKey: 'update.errorFailed',
383
- output: result.output,
384
- summary: result.output,
385
- };
386
- }
387
- notYetPublished = isNotYetPublished(result.output);
388
- if (!notYetPublished)
389
- break; // Not a wait: this source will not do better.
390
- if (attempt < attempts)
391
- await sleep(retryDelayMs);
392
- }
393
- }
394
- return {
395
- ok: false,
396
- errorKey: notYetPublished ? 'update.errorNotYetPublished' : 'update.errorFailed',
397
- output: String(last.output || '').slice(-2000),
398
- summary: npmErrorSummary(last.output),
399
- };
400
- }
401
- export { DEFAULT_REGISTRY, MIRROR_REGISTRY, canSelfUpdate, checkForUpdate, compareVersions, currentVersion, installKind, installedVersionOnDisk, installedRelayVersion, performUpdate, registryCandidates, updateChecksEnabled, };
@@ -1,76 +0,0 @@
1
- # herdr-remote-relay
2
-
3
- *[English](README.md) · [简体中文](README.zh-CN.md)*
4
-
5
- Standalone relay server and WebUI for [Herdr Remote](https://www.npmjs.com/package/herdr-remote).
6
-
7
- Brokers WebSocket connections between browsers and workstation host connectors. Runtime dependency is `ws` only; requires no compilers or Herdr binaries.
8
-
9
- > **Note**: `herdr-remote` runs a local relay automatically. Install this package only when deploying a dedicated relay server.
10
-
11
- ## Installation
12
-
13
- ```bash
14
- npm install -g https://github.com/dibin666/herdr-remote/releases/latest/download/herdr-remote-relay.tgz
15
- herdr-remote-relay --public-url https://herdr.example.com --password <password>
16
- ```
17
-
18
- From npm instead: `npm install -g herdr-remote-relay` (it can lag a new release by a while).
19
-
20
- Requirements: Node.js 22+, TLS reverse proxy (nginx, Caddy, Cloudflare Tunnel).
21
-
22
- ## Docker
23
-
24
- ```bash
25
- docker run -d --name herdr-relay --restart unless-stopped \
26
- -p 127.0.0.1:8787:8787 \
27
- -v herdr-relay:/data \
28
- -e RELAY_BIND=0.0.0.0 \
29
- -e RELAY_PUBLIC_URL=https://herdr.example.com \
30
- -e RELAY_PASSWORD=your-password \
31
- -e RELAY_ADMIN_TOKEN=your-admin-token \
32
- -e RELAY_TRUST_PROXY=1 \
33
- -e RELAY_AUTH_STATE_FILE=/data/relay-auth.json \
34
- node:22-alpine npx -y herdr-remote-relay
35
- ```
36
-
37
- ## Options
38
-
39
- | Option | Env Var | Default | Description |
40
- |---|---|---|---|
41
- | `--public-url` | `RELAY_PUBLIC_URL` | `http://127.0.0.1:8787` | Public URL for browsers |
42
- | `--password` | `RELAY_PASSWORD` | *(empty)* | Workstation join password (empty = public) |
43
- | `--admin-token` | `RELAY_ADMIN_TOKEN` | *(empty)* | Operator token for `/admin` |
44
- | `--bind` | `RELAY_BIND` | `127.0.0.1` | Listen address |
45
- | `--port` | `RELAY_PORT` | `8787` | Listen port |
46
- | `--trust-proxy` | `RELAY_TRUST_PROXY` | `0` | Trust `X-Forwarded-For` header (`1` behind proxy) |
47
- | `--state-file` | `RELAY_AUTH_STATE_FILE` | `~/.local/state/herdr-remote-relay/relay-auth.json` | Auth state file path |
48
- | `--allowed-origins` | `RELAY_ALLOWED_ORIGINS` | *(same-origin)* | Allowed CORS origins, comma-separated |
49
- | `--max-clients` | `RELAY_MAX_CLIENTS_PER_HOST` | `16` | Maximum browser clients per host |
50
- | `--max-hosts` | `RELAY_MAX_HOSTS` | `1024` | Maximum workstations on this relay |
51
- | `--max-pending-handshakes` | `RELAY_MAX_PENDING_HANDSHAKES` | `1024` | Maximum unauthenticated WebSockets |
52
- | `--max-buffered-bytes` | `RELAY_MAX_BUFFERED_BYTES_PER_CLIENT` | `4194304` | Maximum queued bytes per browser |
53
- | `--host-reconnect-grace-ms` | `RELAY_HOST_RECONNECT_GRACE_MS` | `30000` | Host reconnect grace period |
54
- | `--config` | `HERDR_RELAY_CONFIG` | *(none)* | JSON configuration file path |
55
-
56
- Reverse proxies must pass WebSocket `Upgrade` headers and maintain reasonable idle timeouts. The relay disables WebSocket compression and enables TCP NoDelay for terminal frames. When no browser is attached, business heartbeats stop while low-frequency transport keepalive (minimal idle heartbeat and WebSocket ping/pong watchdog) remains active to prevent proxies from dropping idle connections. Deployment examples for nginx, systemd, and Docker Compose are in `deploy/`.
57
-
58
- ## Multiple workstations and isolation
59
-
60
- A browser can save multiple workstation pairings. The switcher in the lower-left WebUI only shows profiles saved by that browser; it never enumerates other relay hosts. `/api/status` is scoped to the workstation bound to the presented credentials, while only `RELAY_ADMIN_TOKEN` can inspect relay-wide state. `/healthz` does not disclose host or client counts.
61
-
62
- ## Connecting a Workstation
63
-
64
- In `herdr-remote` TUI under **Relay** tab:
65
- - Set mode to **Self-hosted relay**
66
- - Set Relay URL to `wss://herdr.example.com`
67
- - Set Relay Password to matching `RELAY_PASSWORD`
68
-
69
- Check connection status:
70
- ```bash
71
- curl https://herdr.example.com/healthz
72
- ```
73
-
74
- ## License
75
-
76
- MIT
@@ -1,76 +0,0 @@
1
- # herdr-remote-relay
2
-
3
- *[English](README.md) · [简体中文](README.zh-CN.md)*
4
-
5
- [Herdr Remote](https://www.npmjs.com/package/herdr-remote) 的独立 Relay 服务端与 WebUI。
6
-
7
- 负责在浏览器与工作站主机连接器之间转发 WebSocket 连接。运行时仅依赖 `ws`,无需编译环境或 Herdr 二进制文件。
8
-
9
- > **提示**:`herdr-remote` 默认在本地自动运行 relay。仅在需要部署独立中继服务器时安装本包。
10
-
11
- ## 安装
12
-
13
- ```bash
14
- npm install -g https://github.com/dibin666/herdr-remote/releases/latest/download/herdr-remote-relay.tgz
15
- herdr-remote-relay --public-url https://herdr.example.com --password <密码>
16
- ```
17
-
18
- 也可从 npm 安装:`npm install -g herdr-remote-relay`(新版本上线后 npm 可能要过一段时间才同步)。
19
-
20
- 前置要求:Node.js 22+、TLS 反向代理(nginx、Caddy、Cloudflare Tunnel 等)。
21
-
22
- ## Docker 运行
23
-
24
- ```bash
25
- docker run -d --name herdr-relay --restart unless-stopped \
26
- -p 127.0.0.1:8787:8787 \
27
- -v herdr-relay:/data \
28
- -e RELAY_BIND=0.0.0.0 \
29
- -e RELAY_PUBLIC_URL=https://herdr.example.com \
30
- -e RELAY_PASSWORD=你的密码 \
31
- -e RELAY_ADMIN_TOKEN=你的管理令牌 \
32
- -e RELAY_TRUST_PROXY=1 \
33
- -e RELAY_AUTH_STATE_FILE=/data/relay-auth.json \
34
- node:22-alpine npx -y herdr-remote-relay
35
- ```
36
-
37
- ## 配置参数
38
-
39
- | 参数 | 环境变量 | 默认值 | 说明 |
40
- |---|---|---|---|
41
- | `--public-url` | `RELAY_PUBLIC_URL` | `http://127.0.0.1:8787` | 浏览器访问的公开地址 |
42
- | `--password` | `RELAY_PASSWORD` | *(空)* | 工作站接入密码(留空为公用 Relay) |
43
- | `--admin-token` | `RELAY_ADMIN_TOKEN` | *(空)* | `/admin` 管理后台操作员令牌 |
44
- | `--bind` | `RELAY_BIND` | `127.0.0.1` | 监听地址 |
45
- | `--port` | `RELAY_PORT` | `8787` | 监听端口 |
46
- | `--trust-proxy` | `RELAY_TRUST_PROXY` | `0` | 信任 `X-Forwarded-For` 头(反代后设为 `1`) |
47
- | `--state-file` | `RELAY_AUTH_STATE_FILE` | `~/.local/state/herdr-remote-relay/relay-auth.json` | 认证状态文件路径 |
48
- | `--allowed-origins` | `RELAY_ALLOWED_ORIGINS` | *(同源)* | 允许的跨域源,逗号分隔 |
49
- | `--max-clients` | `RELAY_MAX_CLIENTS_PER_HOST` | `16` | 每台工作站最大客户端连接数 |
50
- | `--max-hosts` | `RELAY_MAX_HOSTS` | `1024` | relay 最大工作站数 |
51
- | `--max-pending-handshakes` | `RELAY_MAX_PENDING_HANDSHAKES` | `1024` | 最大未认证 WebSocket 握手数 |
52
- | `--max-buffered-bytes` | `RELAY_MAX_BUFFERED_BYTES_PER_CLIENT` | `4194304` | 单个慢浏览器最大待发送缓冲 |
53
- | `--host-reconnect-grace-ms` | `RELAY_HOST_RECONNECT_GRACE_MS` | `30000` | 工作站断线恢复宽限时间 |
54
- | `--config` | `HERDR_RELAY_CONFIG` | *(无)* | JSON 配置文件路径 |
55
-
56
- 反向代理必须转发 WebSocket `Upgrade` 头,并设置合理的空闲超时时间。relay 会关闭 WebSocket 压缩并启用 TCP NoDelay;没有浏览器连接时,工作站会停止业务 heartbeat,同时保留低频 transport keepalive(最小空闲 heartbeat 与 WebSocket ping/pong 探针)以防止反代断开空闲连接并维持 relay 存活。`deploy/` 目录下提供 nginx、systemd 与 Docker Compose 示例。
57
-
58
- ## 多工作站与隔离
59
-
60
- 同一个浏览器可以保存多个工作站配对,WebUI 左下角的实例切换器只显示本地已保存的实例,不会枚举 relay 上的其他工作站。`/api/status` 按设备令牌绑定的工作站隔离,只有 `RELAY_ADMIN_TOKEN` 才能查看 relay 全局状态;`/healthz` 不公开工作站和客户端数量。
61
-
62
- ## 工作站连接
63
-
64
- 在工作站的 `herdr-remote` TUI 中进入 **Relay** 页面:
65
- - 访问方式选择 **自建 relay**
66
- - Relay 地址填写 `wss://herdr.example.com`
67
- - Relay 密码填写对应的 `RELAY_PASSWORD`
68
-
69
- 验证连接状态:
70
- ```bash
71
- curl https://herdr.example.com/healthz
72
- ```
73
-
74
- ## 开源协议
75
-
76
- MIT
@@ -1,18 +0,0 @@
1
- {
2
- "relay": {
3
- "mode": "remote",
4
- "host": "127.0.0.1",
5
- "port": 8787,
6
- "publicUrl": "https://herdr.example.com",
7
- "trustProxy": true,
8
- "maxHosts": 1024,
9
- "maxPendingHandshakes": 1024,
10
- "maxBufferedBytesPerClient": 4194304,
11
- "hostReconnectGraceMs": 30000
12
- },
13
- "auth": {
14
- "password": "change-me",
15
- "adminToken": "replace-with-a-long-random-operator-token",
16
- "stateFile": "/var/lib/herdr-remote-relay/relay-auth.json"
17
- }
18
- }
@@ -1,9 +0,0 @@
1
- # Copy this file to ~/.cloudflared/config.yml after creating a named tunnel.
2
- # The relay itself remains bound to 127.0.0.1.
3
- tunnel: YOUR_TUNNEL_UUID
4
- credentials-file: /home/YOUR_USER/.cloudflared/YOUR_TUNNEL_UUID.json
5
-
6
- ingress:
7
- - hostname: herdr.example.com
8
- service: http://127.0.0.1:8787
9
- - service: http_status:404
@@ -1,23 +0,0 @@
1
- # Put your own reverse proxy (nginx, Caddy, Traefik, Cloudflare Tunnel…) in
2
- # front of port 8787 and terminate TLS there.
3
-
4
- services:
5
- relay:
6
- image: node:22-alpine
7
- command: npx -y herdr-remote-relay
8
- restart: unless-stopped
9
- ports:
10
- - "127.0.0.1:8787:8787"
11
- environment:
12
- RELAY_BIND: 0.0.0.0
13
- RELAY_PORT: "8787"
14
- RELAY_PUBLIC_URL: https://herdr.example.com
15
- # Leave empty for a public relay.
16
- RELAY_PASSWORD: change-me
17
- RELAY_TRUST_PROXY: "1"
18
- RELAY_AUTH_STATE_FILE: /data/relay-auth.json
19
- volumes:
20
- - relay-state:/data
21
-
22
- volumes:
23
- relay-state:
@@ -1,49 +0,0 @@
1
- # nginx site for a self-hosted relay. Obtain the certificate first, e.g. with
2
- # sudo certbot --nginx -d herdr.example.com
3
-
4
- map $http_upgrade $connection_upgrade {
5
- default upgrade;
6
- '' close;
7
- }
8
-
9
- server {
10
- listen 80;
11
- listen [::]:80;
12
- server_name herdr.example.com;
13
- return 301 https://$host$request_uri;
14
- }
15
-
16
- server {
17
- listen 443 ssl;
18
- listen [::]:443 ssl;
19
- http2 on;
20
- server_name herdr.example.com;
21
-
22
- ssl_certificate /etc/letsencrypt/live/herdr.example.com/fullchain.pem;
23
- ssl_certificate_key /etc/letsencrypt/live/herdr.example.com/privkey.pem;
24
-
25
- location / {
26
- proxy_pass http://127.0.0.1:8787;
27
- proxy_http_version 1.1;
28
-
29
- # Required for /ws/host and /ws/client. Without these the browser falls
30
- # back to nothing: the terminal never opens.
31
- proxy_set_header Upgrade $http_upgrade;
32
- proxy_set_header Connection $connection_upgrade;
33
-
34
- proxy_set_header Host $host;
35
- # The relay checks the Origin header against the request Host, and rate
36
- # limits pairing per client address.
37
- proxy_set_header X-Real-IP $remote_addr;
38
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
39
- proxy_set_header X-Forwarded-Proto $scheme;
40
-
41
- # A terminal can sit idle for a long time between keystrokes; the
42
- # default 60s would drop the session.
43
- proxy_read_timeout 3600s;
44
- proxy_send_timeout 3600s;
45
-
46
- # Terminal output must arrive as it is produced.
47
- proxy_buffering off;
48
- }
49
- }