herdr-remote 0.2.39 → 0.3.32

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 (205) hide show
  1. package/README.md +4 -2
  2. package/README.zh-CN.md +2 -2
  3. package/dist/chunks/3FQAXVGC.js +3 -0
  4. package/dist/chunks/42KARAL4.js +31 -0
  5. package/dist/chunks/44IGVPMA.js +5 -0
  6. package/dist/chunks/5LZFJ6UZ.js +3 -0
  7. package/dist/chunks/7SMZ43LI.js +9 -0
  8. package/dist/chunks/AX4EPJCP.js +4 -0
  9. package/dist/chunks/B76DFI4H.js +16 -0
  10. package/dist/chunks/BYGDPZZD.js +3 -0
  11. package/dist/chunks/C2UUZY6J.js +2 -0
  12. package/dist/chunks/CVJ4HBMP.js +3 -0
  13. package/dist/chunks/DGUZCGQN.js +3 -0
  14. package/dist/chunks/FONUO5O5.js +4 -0
  15. package/dist/chunks/H24ZP5D5.js +16 -0
  16. package/dist/chunks/I6NRCYLI.js +3 -0
  17. package/dist/chunks/JJGEV4EZ.js +3 -0
  18. package/dist/chunks/K5RFPHSF.js +5 -0
  19. package/dist/chunks/KGOQKOHH.js +5 -0
  20. package/dist/chunks/KRVF37OW.js +3 -0
  21. package/dist/chunks/KUDCC7ZO.js +6 -0
  22. package/dist/chunks/LPR662JH.js +77 -0
  23. package/dist/chunks/LY2ZGCXN.js +4 -0
  24. package/dist/chunks/M52LJEES.js +6 -0
  25. package/dist/chunks/MCUVFSSK.js +3 -0
  26. package/dist/chunks/MEFDV375.js +10 -0
  27. package/dist/chunks/MKAEXKXG.js +10 -0
  28. package/dist/chunks/O2LNVFUT.js +3 -0
  29. package/dist/chunks/PBMBFWLQ.js +3 -0
  30. package/dist/chunks/PBRQ3HEG.js +3 -0
  31. package/dist/chunks/QCSILMFB.js +3 -0
  32. package/dist/chunks/QRGNEKME.js +5 -0
  33. package/dist/chunks/RMHH3IAX.js +63 -0
  34. package/dist/chunks/TNLCNVA2.js +3 -0
  35. package/dist/chunks/U6QAYG3E.js +3 -0
  36. package/dist/chunks/VD54DF7R.js +45 -0
  37. package/dist/chunks/WY7WHEJ4.js +3 -0
  38. package/dist/cli.js +21 -294
  39. package/dist/connector/admin-broker.js +3 -303
  40. package/dist/connector/main.js +20 -18
  41. package/dist/harfbuzz-subset.wasm +0 -0
  42. package/dist/postinstall.js +17 -0
  43. package/herdr-plugin.toml +1 -1
  44. package/node_modules/herdr-remote-relay/bin/herdr-remote-relay.js +33 -92
  45. package/node_modules/herdr-remote-relay/package.json +3 -46
  46. package/package.json +8 -27
  47. package/dist/agent-status.js +0 -130
  48. package/dist/binary-plist.js +0 -157
  49. package/dist/config.js +0 -278
  50. package/dist/connector/admin-broker-setup.js +0 -40
  51. package/dist/connector/admin-shell.js +0 -67
  52. package/dist/connector/admin-tab.js +0 -90
  53. package/dist/connector/adminBrokerEndpoint.js +0 -68
  54. package/dist/connector/agent-watch.js +0 -123
  55. package/dist/connector/elevatedPty.js +0 -141
  56. package/dist/connector/font-server.js +0 -165
  57. package/dist/connector/herdr-prefix-keys.js +0 -28
  58. package/dist/connector/host-connector.js +0 -433
  59. package/dist/connector/host-hello.js +0 -32
  60. package/dist/connector/lock.js +0 -73
  61. package/dist/connector/sessions.js +0 -381
  62. package/dist/connector/update-report.js +0 -70
  63. package/dist/connector/wire.js +0 -42
  64. package/dist/exit-codes.js +0 -13
  65. package/dist/font-subset.js +0 -116
  66. package/dist/herdr-api.js +0 -254
  67. package/dist/herdr-command.js +0 -245
  68. package/dist/herdr-plugin.js +0 -120
  69. package/dist/herdr-server.js +0 -190
  70. package/dist/i18n/en.js +0 -257
  71. package/dist/i18n/index.js +0 -48
  72. package/dist/i18n/zh.js +0 -256
  73. package/dist/keepalive/detached.js +0 -97
  74. package/dist/keepalive/environment.js +0 -44
  75. package/dist/keepalive/index.js +0 -129
  76. package/dist/keepalive/launchd.js +0 -117
  77. package/dist/keepalive/systemd.js +0 -112
  78. package/dist/keepalive/types.js +0 -1
  79. package/dist/keepalive/windows-service-setup.js +0 -138
  80. package/dist/keepalive/windows-service.js +0 -272
  81. package/dist/keepalive/windows-task.js +0 -191
  82. package/dist/lib/process.js +0 -132
  83. package/dist/lifecycle.js +0 -79
  84. package/dist/net-interfaces.js +0 -82
  85. package/dist/node-pty-loader.js +0 -80
  86. package/dist/pasted-files.js +0 -115
  87. package/dist/paths.js +0 -74
  88. package/dist/pty-session.js +0 -133
  89. package/dist/relay-client.js +0 -106
  90. package/dist/relay-urls.js +0 -99
  91. package/dist/runtime.js +0 -107
  92. package/dist/service.js +0 -267
  93. package/dist/settings-model.js +0 -298
  94. package/dist/socket-discovery.js +0 -70
  95. package/dist/supervisor.js +0 -323
  96. package/dist/terminal-font/deps.js +0 -83
  97. package/dist/terminal-font/files.js +0 -357
  98. package/dist/terminal-font/formats.js +0 -355
  99. package/dist/terminal-font/index.js +0 -187
  100. package/dist/terminal-font/readers.js +0 -373
  101. package/dist/terminal-font/terminals.js +0 -147
  102. package/dist/terminal-palette.js +0 -399
  103. package/dist/tui/App.js +0 -199
  104. package/dist/tui/api.js +0 -91
  105. package/dist/tui/components/TextField.js +0 -50
  106. package/dist/tui/components/common.js +0 -80
  107. package/dist/tui/index.js +0 -36
  108. package/dist/tui/mouse/index.js +0 -164
  109. package/dist/tui/mouse/protocol.js +0 -96
  110. package/dist/tui/mouse/source.js +0 -79
  111. package/dist/tui/screens/About.js +0 -172
  112. package/dist/tui/screens/Herdr.js +0 -75
  113. package/dist/tui/screens/Keepalive.js +0 -141
  114. package/dist/tui/screens/Overview.js +0 -36
  115. package/dist/tui/screens/Pair.js +0 -74
  116. package/dist/tui/screens/Relay.js +0 -243
  117. package/dist/tui/screens/Services.js +0 -63
  118. package/dist/tui/screens/Wizard.js +0 -241
  119. package/dist/tui/terminal.js +0 -39
  120. package/dist/tui/theme.js +0 -33
  121. package/dist/update-command.js +0 -118
  122. package/dist/updater/in-place.js +0 -83
  123. package/dist/updater/npm.js +0 -62
  124. package/dist/updater.js +0 -401
  125. package/node_modules/herdr-remote-relay/README.md +0 -76
  126. package/node_modules/herdr-remote-relay/README.zh-CN.md +0 -76
  127. package/node_modules/herdr-remote-relay/config.example.json +0 -18
  128. package/node_modules/herdr-remote-relay/deploy/cloudflared/config.yml.example +0 -9
  129. package/node_modules/herdr-remote-relay/deploy/docker/compose.yaml +0 -23
  130. package/node_modules/herdr-remote-relay/deploy/nginx/herdr-remote-relay.conf.example +0 -49
  131. package/node_modules/herdr-remote-relay/deploy/systemd/herdr-remote-relay.service +0 -41
  132. package/node_modules/herdr-remote-relay/deploy/systemd/relay.env.example +0 -25
  133. package/node_modules/herdr-remote-relay/dist/auth-store.d.ts +0 -114
  134. package/node_modules/herdr-remote-relay/dist/auth-store.js +0 -289
  135. package/node_modules/herdr-remote-relay/dist/metrics.d.ts +0 -76
  136. package/node_modules/herdr-remote-relay/dist/metrics.js +0 -249
  137. package/node_modules/herdr-remote-relay/dist/protocol/frames.d.ts +0 -27
  138. package/node_modules/herdr-remote-relay/dist/protocol/frames.js +0 -110
  139. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.d.ts +0 -8
  140. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.js +0 -34
  141. package/node_modules/herdr-remote-relay/dist/protocol/http.d.ts +0 -165
  142. package/node_modules/herdr-remote-relay/dist/protocol/http.js +0 -3
  143. package/node_modules/herdr-remote-relay/dist/protocol/index.d.ts +0 -7
  144. package/node_modules/herdr-remote-relay/dist/protocol/index.js +0 -9
  145. package/node_modules/herdr-remote-relay/dist/protocol/messages.d.ts +0 -362
  146. package/node_modules/herdr-remote-relay/dist/protocol/messages.js +0 -20
  147. package/node_modules/herdr-remote-relay/dist/protocol/paste.d.ts +0 -14
  148. package/node_modules/herdr-remote-relay/dist/protocol/paste.js +0 -50
  149. package/node_modules/herdr-remote-relay/dist/protocol/release.d.ts +0 -8
  150. package/node_modules/herdr-remote-relay/dist/protocol/release.js +0 -17
  151. package/node_modules/herdr-remote-relay/dist/protocol/terminal.d.ts +0 -79
  152. package/node_modules/herdr-remote-relay/dist/protocol/terminal.js +0 -150
  153. package/node_modules/herdr-remote-relay/dist/relay-config.d.ts +0 -74
  154. package/node_modules/herdr-remote-relay/dist/relay-config.js +0 -316
  155. package/node_modules/herdr-remote-relay/dist/relay-server.d.ts +0 -67
  156. package/node_modules/herdr-remote-relay/dist/relay-server.js +0 -196
  157. package/node_modules/herdr-remote-relay/dist/scroll-input.d.ts +0 -17
  158. package/node_modules/herdr-remote-relay/dist/scroll-input.js +0 -124
  159. package/node_modules/herdr-remote-relay/dist/server/client-channel.d.ts +0 -3
  160. package/node_modules/herdr-remote-relay/dist/server/client-channel.js +0 -396
  161. package/node_modules/herdr-remote-relay/dist/server/font-proxy.d.ts +0 -19
  162. package/node_modules/herdr-remote-relay/dist/server/font-proxy.js +0 -155
  163. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.d.ts +0 -27
  164. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.js +0 -83
  165. package/node_modules/herdr-remote-relay/dist/server/host-channel.d.ts +0 -3
  166. package/node_modules/herdr-remote-relay/dist/server/host-channel.js +0 -271
  167. package/node_modules/herdr-remote-relay/dist/server/http-api.d.ts +0 -4
  168. package/node_modules/herdr-remote-relay/dist/server/http-api.js +0 -219
  169. package/node_modules/herdr-remote-relay/dist/server/maintenance.d.ts +0 -5
  170. package/node_modules/herdr-remote-relay/dist/server/maintenance.js +0 -64
  171. package/node_modules/herdr-remote-relay/dist/server/requests.d.ts +0 -34
  172. package/node_modules/herdr-remote-relay/dist/server/requests.js +0 -142
  173. package/node_modules/herdr-remote-relay/dist/server/sessions.d.ts +0 -36
  174. package/node_modules/herdr-remote-relay/dist/server/sessions.js +0 -230
  175. package/node_modules/herdr-remote-relay/dist/server/sockets.d.ts +0 -12
  176. package/node_modules/herdr-remote-relay/dist/server/sockets.js +0 -64
  177. package/node_modules/herdr-remote-relay/dist/server/status.d.ts +0 -11
  178. package/node_modules/herdr-remote-relay/dist/server/status.js +0 -99
  179. package/node_modules/herdr-remote-relay/dist/server/transport.d.ts +0 -16
  180. package/node_modules/herdr-remote-relay/dist/server/transport.js +0 -110
  181. package/node_modules/herdr-remote-relay/dist/server/types.d.ts +0 -133
  182. package/node_modules/herdr-remote-relay/dist/server/types.js +0 -3
  183. package/node_modules/herdr-remote-relay/dist/server/version.d.ts +0 -2
  184. package/node_modules/herdr-remote-relay/dist/server/version.js +0 -5
  185. package/node_modules/herdr-remote-relay/dist/state.d.ts +0 -6
  186. package/node_modules/herdr-remote-relay/dist/state.js +0 -47
  187. package/node_modules/ws/LICENSE +0 -20
  188. package/node_modules/ws/README.md +0 -548
  189. package/node_modules/ws/browser.js +0 -8
  190. package/node_modules/ws/index.js +0 -22
  191. package/node_modules/ws/lib/buffer-util.js +0 -131
  192. package/node_modules/ws/lib/constants.js +0 -19
  193. package/node_modules/ws/lib/event-target.js +0 -292
  194. package/node_modules/ws/lib/extension.js +0 -203
  195. package/node_modules/ws/lib/limiter.js +0 -55
  196. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  197. package/node_modules/ws/lib/receiver.js +0 -743
  198. package/node_modules/ws/lib/sender.js +0 -607
  199. package/node_modules/ws/lib/stream.js +0 -161
  200. package/node_modules/ws/lib/subprotocol.js +0 -62
  201. package/node_modules/ws/lib/validation.js +0 -152
  202. package/node_modules/ws/lib/websocket-server.js +0 -562
  203. package/node_modules/ws/lib/websocket.js +0 -1407
  204. package/node_modules/ws/package.json +0 -74
  205. package/node_modules/ws/wrapper.mjs +0 -21
@@ -1,381 +0,0 @@
1
- // One Herdr client in a PTY per browser window, and starting Herdr's server
2
- // when a window finds it not running.
3
- import { FRAME_TYPE_OUTPUT, packStreamFrame, packStreamFrameV2, } from 'herdr-remote-relay/protocol';
4
- import { herdrNotFoundMessage, herdrOutdatedMessage, herdrVersion, resolveHerdrCommand, verifyHerdrCommand, } from '../herdr-command.js';
5
- import { ensureHerdrServer, probeHerdrServer, } from '../herdr-server.js';
6
- import { PtySession } from '../pty-session.js';
7
- import { inspectSocket } from '../socket-discovery.js';
8
- import { streamOf } from './wire.js';
9
- /**
10
- * A session that dies this fast never really started. Anything longer is a
11
- * session the user actually used and then left.
12
- */
13
- const FAST_FAILURE_MS = 1500;
14
- /** Coalesce Windows ConPTY's many small output chunks before they reach the relay. */
15
- const WINDOWS_OUTPUT_BATCH_MS = 4;
16
- /** How many broken starts in a row before we stop calling them exits. */
17
- export const FAST_FAILURE_LIMIT = 3;
18
- export class Sessions {
19
- /** Running sessions by stream id. */
20
- byStream = new Map();
21
- /** v2 frame index → stream id. */
22
- streamIndexToId = new Map();
23
- /** Windows whose session waits on a Herdr that is not running, by stream. */
24
- waitingForHerdr = new Map();
25
- /** Session starts between their liveness check and their PTY, by stream. */
26
- probingStarts = new Map();
27
- /** One start at a time, however many windows asked for it. */
28
- herdrLaunch = null;
29
- herdrVersionWarned = false;
30
- fastFailures = 0;
31
- herdrCommand;
32
- herdrArgs;
33
- socketPath;
34
- cwd;
35
- herdrLookup;
36
- herdrLogPath;
37
- readHerdrVersion;
38
- Pty;
39
- probeHerdr;
40
- ensureHerdr;
41
- link;
42
- constructor(options, link) {
43
- this.socketPath = options.socketPath;
44
- this.herdrLookup = options.herdrLookup || {};
45
- this.herdrCommand = options.herdrCommand || resolveHerdrCommand(this.herdrLookup);
46
- this.herdrArgs = options.herdrArgs;
47
- this.cwd = options.cwd;
48
- this.herdrLogPath = options.herdrLogPath;
49
- this.readHerdrVersion = options.readHerdrVersion || herdrVersion;
50
- this.Pty = options.PtySession || PtySession;
51
- this.probeHerdr = options.probeHerdr || probeHerdrServer;
52
- this.ensureHerdr = options.ensureHerdr || ensureHerdrServer;
53
- this.link = link;
54
- }
55
- sendError(streamId, code, message) {
56
- this.link.send({ type: 'error', clientId: streamId, code, message });
57
- }
58
- /**
59
- * Confirm Herdr is really there before the name reaches `pty.spawn`.
60
- *
61
- * node-pty resolves the command with `execvp(3)` inside the forked child, so
62
- * a missing binary is not a spawn error anyone can catch: the child writes
63
- * "execvp(3) failed.: No such file or directory" into the PTY and exits. The
64
- * `session_exit` that follows makes the relay drop the browser's socket, the
65
- * browser reconnects into the same broken start, and that is the reconnect
66
- * loop of issue #1. Refusing to start keeps the socket up and puts the actual
67
- * reason in front of the user.
68
- *
69
- * Re-resolved per session rather than cached from the constructor: a service
70
- * that started before Herdr was installed should pick it up without a restart.
71
- */
72
- ensureHerdrCommand() {
73
- const resolved = verifyHerdrCommand(this.herdrCommand, this.herdrLookup);
74
- if (resolved.found) {
75
- this.herdrCommand = resolved.command;
76
- this.warnIfHerdrOutdated();
77
- return null;
78
- }
79
- return herdrNotFoundMessage(this.herdrLookup);
80
- }
81
- /**
82
- * Say once that the installed Herdr is older than this release expects.
83
- *
84
- * Never a refusal to start. An older Herdr still runs — it just misbehaves in
85
- * ways a browser window notices — and the comment on `start` explains what a
86
- * refused start does to a reconnecting browser.
87
- */
88
- warnIfHerdrOutdated() {
89
- if (this.herdrVersionWarned)
90
- return;
91
- const installed = this.readHerdrVersion({ command: this.herdrCommand });
92
- if (installed.supported) {
93
- // A version we could not read is checked again on the next session: the
94
- // install may simply not have been finished writing.
95
- if (installed.version)
96
- this.herdrVersionWarned = true;
97
- return;
98
- }
99
- this.herdrVersionWarned = true;
100
- process.stderr.write(`herdr-remote host connector: ${herdrOutdatedMessage(installed.version)}\n`);
101
- }
102
- /** Start a window's session, or hold it until Herdr runs. */
103
- start(message) {
104
- const streamId = streamOf(message);
105
- if (!streamId)
106
- return undefined;
107
- this.stop(streamId);
108
- const missingHerdr = this.ensureHerdrCommand();
109
- if (missingHerdr) {
110
- process.stderr.write(`herdr-remote host connector: ${missingHerdr}\n`);
111
- this.sendError(streamId, 'herdr_not_found', missingHerdr);
112
- return undefined;
113
- }
114
- const socketInfo = inspectSocket(this.socketPath);
115
- if (!socketInfo.ok && !socketInfo.missing) {
116
- this.sendError(streamId, 'herdr_socket_unavailable', socketInfo.reason);
117
- return undefined;
118
- }
119
- if (!socketInfo.ok) {
120
- this.waitForHerdr(streamId, message);
121
- return undefined;
122
- }
123
- // The socket file alone proves nothing: a `herdr` client pointed at a dead
124
- // one starts a server of its own, inside this process's service, where the
125
- // next restart of herdr-remote would kill it. Only a server that accepts a
126
- // connection gets a client.
127
- const pending = { message };
128
- this.probingStarts.set(streamId, pending);
129
- return this.probeHerdr(this.socketPath).then((probe) => {
130
- // Stopped, or started again, while the answer was in flight.
131
- if (this.probingStarts.get(streamId) !== pending)
132
- return;
133
- this.probingStarts.delete(streamId);
134
- if (probe.state === 'running')
135
- this.spawn(pending.message);
136
- else if (probe.state === 'stopped')
137
- this.waitForHerdr(streamId, pending.message);
138
- else
139
- this.sendError(streamId, 'herdr_socket_unavailable', probe.reason);
140
- });
141
- }
142
- /**
143
- * Hold a window's session until Herdr runs, and ask the browser whether it
144
- * should be started. Nothing starts until somebody says yes.
145
- */
146
- waitForHerdr(streamId, message) {
147
- this.waitingForHerdr.set(streamId, message);
148
- this.sendError(streamId, 'herdr_not_running', `Herdr is not running on this workstation (${this.socketPath}).`);
149
- }
150
- /**
151
- * Start this workstation's Herdr server, once, however many windows or
152
- * callers ask at the same time.
153
- */
154
- startHerdr() {
155
- if (!this.herdrLaunch) {
156
- this.herdrLaunch = Promise.resolve()
157
- .then(() => {
158
- const missing = this.ensureHerdrCommand();
159
- if (missing)
160
- throw new Error(missing);
161
- return this.ensureHerdr({
162
- command: this.herdrCommand,
163
- args: this.herdrArgs,
164
- socketPath: this.socketPath,
165
- cwd: this.cwd,
166
- logPath: this.herdrLogPath,
167
- });
168
- })
169
- .finally(() => {
170
- this.herdrLaunch = null;
171
- });
172
- }
173
- return this.herdrLaunch;
174
- }
175
- /**
176
- * A browser said yes. The relay only ever routes this from a window paired
177
- * to *this* workstation, so what starts is this user's own Herdr, at the
178
- * socket this connector is configured for — never one named by the message.
179
- * Once it runs, every window waiting on it gets its session.
180
- */
181
- async startHerdrFor(streamId) {
182
- try {
183
- const result = await this.startHerdr();
184
- if (result.started)
185
- process.stderr.write("herdr-remote host connector: started Herdr at a browser's request\n");
186
- }
187
- catch (error) {
188
- const { message } = error;
189
- process.stderr.write(`herdr-remote host connector: could not start Herdr: ${message}\n`);
190
- if (streamId)
191
- this.sendError(streamId, 'herdr_start_failed', message);
192
- return;
193
- }
194
- const waiting = [...this.waitingForHerdr.values()];
195
- this.waitingForHerdr.clear();
196
- for (const held of waiting)
197
- this.spawn(held);
198
- }
199
- /** Start the PTY for a window once Herdr is known to be running. */
200
- spawn(message) {
201
- const streamId = streamOf(message);
202
- const streamIndex = typeof message.streamIndex === 'number' ? message.streamIndex : null;
203
- const pty = new this.Pty({
204
- command: this.herdrCommand,
205
- args: this.herdrArgs,
206
- cwd: this.cwd,
207
- socketPath: this.socketPath,
208
- });
209
- const session = {
210
- id: streamId,
211
- streamIndex,
212
- pty,
213
- cols: Number(message.cols) || PtySession.DEFAULT_COLS,
214
- rows: Number(message.rows) || PtySession.DEFAULT_ROWS,
215
- createdAt: new Date().toISOString(),
216
- startedAtMs: Date.now(),
217
- pendingOutput: [],
218
- flushImmediate: null,
219
- flushTimeout: null,
220
- };
221
- const flushOutput = () => {
222
- if (session.flushImmediate) {
223
- clearImmediate(session.flushImmediate);
224
- session.flushImmediate = null;
225
- }
226
- if (session.flushTimeout) {
227
- clearTimeout(session.flushTimeout);
228
- session.flushTimeout = null;
229
- }
230
- if (session.pendingOutput.length === 0)
231
- return;
232
- const payload = Buffer.concat(session.pendingOutput);
233
- session.pendingOutput = [];
234
- this.link.sendFrame(session.streamIndex !== null
235
- ? packStreamFrameV2(FRAME_TYPE_OUTPUT, session.streamIndex, payload)
236
- : packStreamFrame('output', streamId, payload));
237
- };
238
- try {
239
- pty.start({
240
- cols: session.cols,
241
- rows: session.rows,
242
- onData: (data) => {
243
- const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
244
- if (chunk.length === 0)
245
- return;
246
- session.pendingOutput.push(chunk);
247
- if (!session.flushImmediate && !session.flushTimeout) {
248
- if (process.platform === 'win32') {
249
- session.flushTimeout = setTimeout(flushOutput, WINDOWS_OUTPUT_BATCH_MS);
250
- }
251
- else {
252
- session.flushImmediate = setImmediate(flushOutput);
253
- }
254
- }
255
- },
256
- onExit: ({ exitCode }) => {
257
- if (this.byStream.get(streamId) !== session)
258
- return;
259
- this.forget(session);
260
- this.reportExit(session, exitCode);
261
- this.link.changed();
262
- },
263
- });
264
- }
265
- catch (error) {
266
- discardOutput(session);
267
- this.sendError(streamId, 'pty_start_failed', error.message);
268
- pty.kill();
269
- return;
270
- }
271
- this.byStream.set(streamId, session);
272
- if (streamIndex !== null)
273
- this.streamIndexToId.set(streamIndex, streamId);
274
- this.link.send({ type: 'session_ready', clientId: streamId });
275
- this.link.changed();
276
- }
277
- /**
278
- * Tell the relay how a session ended.
279
- *
280
- * `session_exit` is the honest answer for a session that ran, and the relay
281
- * closes the browser's socket on it so the window can start a fresh one. That
282
- * is also what turns a start that keeps failing into a reconnect loop: exit,
283
- * close, reconnect, exit. Once a few starts in a row have died immediately
284
- * with a non-zero code the failure is systemic, not a session ending, so it is
285
- * reported as an error — which the relay forwards without dropping the socket,
286
- * leaving the user with a message instead of a spinner.
287
- */
288
- reportExit(session, exitCode) {
289
- const streamId = session.id;
290
- const lifetimeMs = Date.now() - session.startedAtMs;
291
- const failedFast = exitCode !== 0 && lifetimeMs < FAST_FAILURE_MS;
292
- this.fastFailures = failedFast ? this.fastFailures + 1 : 0;
293
- if (this.fastFailures < FAST_FAILURE_LIMIT) {
294
- this.link.send({ type: 'session_exit', clientId: streamId, code: exitCode });
295
- return;
296
- }
297
- const message = `"${this.herdrCommand}" exited immediately with code ${exitCode} on ${this.fastFailures} attempts in a row. ` +
298
- 'Check that it runs from a terminal, and see the host connector log for what it printed.';
299
- process.stderr.write(`herdr-remote host connector: ${message}\n`);
300
- this.sendError(streamId, 'herdr_start_failed', message);
301
- }
302
- /** Keyboard input for the session behind a stream id or v2 index. */
303
- write(stream, payload) {
304
- const streamId = stream.streamIndex !== undefined
305
- ? this.streamIndexToId.get(stream.streamIndex)
306
- : stream.streamId;
307
- if (streamId)
308
- this.byStream.get(streamId)?.pty.write(payload);
309
- }
310
- resize(message) {
311
- const id = streamOf(message);
312
- if (!id)
313
- return;
314
- // A window still waiting on Herdr starts at the size it has by then.
315
- const held = this.waitingForHerdr.get(id) || this.probingStarts.get(id)?.message;
316
- if (held) {
317
- if (Number.isInteger(message.cols))
318
- held.cols = message.cols;
319
- if (Number.isInteger(message.rows))
320
- held.rows = message.rows;
321
- }
322
- const session = this.byStream.get(id);
323
- if (!session)
324
- return;
325
- if (Number.isInteger(message.cols))
326
- session.cols = PtySession.clampDimension(message.cols, 0);
327
- if (Number.isInteger(message.rows))
328
- session.rows = PtySession.clampDimension(message.rows, 0);
329
- session.pty.resize(session.cols, session.rows);
330
- }
331
- stop(streamId) {
332
- this.waitingForHerdr.delete(streamId);
333
- this.probingStarts.delete(streamId);
334
- const session = this.byStream.get(streamId);
335
- if (!session)
336
- return;
337
- this.forget(session);
338
- session.pty.kill();
339
- this.link.changed();
340
- }
341
- stopAll() {
342
- for (const session of this.byStream.values()) {
343
- discardOutput(session);
344
- session.pty.kill();
345
- }
346
- this.byStream.clear();
347
- this.streamIndexToId.clear();
348
- this.waitingForHerdr.clear();
349
- this.probingStarts.clear();
350
- }
351
- /** The running PTYs, as the relay's status page lists them. */
352
- summaries() {
353
- return [...this.byStream.values()].map((session) => ({
354
- id: session.id,
355
- pid: session.pty.terminal?.pid || null,
356
- command: session.pty.command,
357
- cols: session.cols,
358
- rows: session.rows,
359
- cwd: session.pty.cwd,
360
- createdAt: session.createdAt,
361
- activeClients: 1,
362
- }));
363
- }
364
- forget(session) {
365
- this.byStream.delete(session.id);
366
- if (session.streamIndex !== null)
367
- this.streamIndexToId.delete(session.streamIndex);
368
- discardOutput(session);
369
- }
370
- }
371
- function discardOutput(session) {
372
- if (session.flushImmediate) {
373
- clearImmediate(session.flushImmediate);
374
- session.flushImmediate = null;
375
- }
376
- if (session.flushTimeout) {
377
- clearTimeout(session.flushTimeout);
378
- session.flushTimeout = null;
379
- }
380
- session.pendingOutput = [];
381
- }
@@ -1,70 +0,0 @@
1
- // Telling a workstation's windows whether its herdr-remote is behind.
2
- import { checkForUpdate, compareVersions, currentVersion, installedVersionOnDisk, updateChecksEnabled, } from '../updater.js';
3
- /** Windows opening within this long of a check reuse its answer. */
4
- const UPDATE_RECHECK_MS = 60_000;
5
- /**
6
- * Whether a newer herdr-remote is out, asked when a window opens. The version
7
- * this process runs is fixed at start; the one on disk moves when somebody
8
- * updates without restarting.
9
- */
10
- export class UpdateReport {
11
- checkUpdate;
12
- readInstalledVersion;
13
- /** The release this process runs, fixed at start. */
14
- runningVersion;
15
- send;
16
- checkedAt = 0;
17
- /** The check in flight, if any. */
18
- pending = null;
19
- constructor(options, send) {
20
- this.checkUpdate =
21
- options.checkUpdate !== undefined
22
- ? options.checkUpdate
23
- : updateChecksEnabled()
24
- ? checkForUpdate
25
- : null;
26
- this.readInstalledVersion = options.readInstalledVersion || installedVersionOnDisk;
27
- this.runningVersion = options.runningVersion || currentVersion();
28
- this.send = send;
29
- }
30
- /**
31
- * Tell every window whether this workstation's herdr-remote is behind the
32
- * newest release. Asked at most once a minute however many windows open;
33
- * the relay keeps the last answer for windows that open in between. A failed
34
- * check says nothing: offline is not news.
35
- */
36
- report({ now = Date.now() } = {}) {
37
- const checkUpdate = this.checkUpdate;
38
- if (!checkUpdate || this.pending)
39
- return this.pending;
40
- if (now - this.checkedAt < UPDATE_RECHECK_MS)
41
- return null;
42
- this.checkedAt = now;
43
- this.pending = Promise.resolve()
44
- .then(() => checkUpdate())
45
- .then((result) => {
46
- if (!result?.ok || !result.latest)
47
- return;
48
- const running = this.runningVersion;
49
- const installed = this.readInstalledVersion() || running;
50
- this.send({
51
- type: 'update_status',
52
- current: running,
53
- installed,
54
- latest: result.latest,
55
- updateAvailable: compareVersions(result.latest, running) > 0,
56
- // Updated on disk, still running the old code: a restart finishes it.
57
- restartPending: compareVersions(installed, running) > 0,
58
- // Older connectors leave this out, and their windows keep the npm steps.
59
- updateCommand: true,
60
- });
61
- })
62
- .catch(() => {
63
- // Offline, or the registry is down: nothing worth telling a window.
64
- })
65
- .finally(() => {
66
- this.pending = null;
67
- });
68
- return this.pending;
69
- }
70
- }
@@ -1,42 +0,0 @@
1
- // The host connector's side of its WebSocket to the relay.
2
- import { WebSocket } from 'ws';
3
- export function sendJson(ws, payload) {
4
- if (ws && ws.readyState === WebSocket.OPEN)
5
- ws.send(JSON.stringify(payload));
6
- }
7
- export function closeSocket(ws, reason = 'host connector stopping') {
8
- if (!ws || ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING)
9
- return;
10
- try {
11
- ws.close(1000, reason);
12
- }
13
- catch {
14
- // Closing a socket that is already failing needs no further handling.
15
- }
16
- }
17
- export function terminateSocket(ws) {
18
- if (!ws || ws.readyState === WebSocket.CLOSED)
19
- return;
20
- // Tests stand in a plain object for the socket; end it however it allows.
21
- const socket = ws;
22
- try {
23
- if (typeof socket.terminate === 'function')
24
- socket.terminate();
25
- else if (typeof socket.destroy === 'function')
26
- socket.destroy();
27
- else
28
- socket.close?.();
29
- }
30
- catch {
31
- // Tearing down a dead socket; there is nothing left to recover.
32
- }
33
- }
34
- /**
35
- * The stream a relay message is about. The relay sends `clientId` and
36
- * `streamId` with the same value; `clientId` is read first, as the protocol
37
- * says.
38
- */
39
- export function streamOf(message) {
40
- const id = message.clientId || message.streamId;
41
- return typeof id === 'string' && id ? id : null;
42
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * Exit codes the supervisor interprets.
3
- *
4
- * `REPLACED` means another host connector has taken over this workstation on
5
- * the relay. Restarting after that would make the two instances kick each other
6
- * off in turn, and every browser attached to the relay is disconnected on each
7
- * swap — the endless "Connection closed (1012). Retrying…" loop. So it is the
8
- * one exit the supervisor must respect rather than recover from.
9
- */
10
- const EXIT_REPLACED = 12;
11
- /** Credentials need operator intervention; a supervisor must not loop. */
12
- const EXIT_AUTH_FAILED = 13;
13
- export { EXIT_REPLACED, EXIT_AUTH_FAILED };
@@ -1,116 +0,0 @@
1
- /**
2
- * Cuts the characters a browser needs out of a large font.
3
- *
4
- * A CJK face is 16–20 MB — too much to send a phone whole — but a terminal
5
- * only ever shows a few thousand distinct characters. HarfBuzz's subsetter
6
- * (the same code Google Fonts slices its CJK families with) produces a small,
7
- * valid font holding exactly the requested characters: 3,755 common Hanzi in
8
- * about 1.7 MB and 50 ms, a handful of rare ones in a few KB and milliseconds.
9
- *
10
- * The WebAssembly build has no imports and runs synchronously. A loaded face
11
- * costs its file size twice over (the file and the wasm heap), so the whole
12
- * instance is dropped after a quiet minute; wasm memory never shrinks, only
13
- * a new instance gives it back.
14
- */
15
- import { createRequire } from 'node:module';
16
- import fs from 'node:fs';
17
- import path from 'node:path';
18
- import { PACKAGE_ROOT } from './paths.js';
19
- /** How long a loaded font may sit unused before its memory is released. */
20
- const IDLE_RELEASE_MS = 60_000;
21
- /** Never cut more than this many characters in one go. */
22
- const MAX_CODEPOINTS = 16_384;
23
- const HB_MEMORY_MODE_READONLY = 1;
24
- let wasmModule = null;
25
- function loadModule() {
26
- if (!wasmModule) {
27
- // A release bundles harfbuzzjs away and ships only this file beside the bundle.
28
- const bundled = path.join(PACKAGE_ROOT, 'dist', 'harfbuzz-subset.wasm');
29
- const file = fs.existsSync(bundled)
30
- ? bundled
31
- : createRequire(import.meta.url).resolve('harfbuzzjs/dist/harfbuzz-subset.wasm');
32
- wasmModule = new WebAssembly.Module(fs.readFileSync(file));
33
- }
34
- return wasmModule;
35
- }
36
- class FontSubsetter {
37
- idleMs;
38
- instance = null;
39
- /** Loaded faces by source key, in the current instance. */
40
- faces = new Map();
41
- idleTimer = null;
42
- constructor({ idleMs = IDLE_RELEASE_MS } = {}) {
43
- this.idleMs = idleMs;
44
- }
45
- exports() {
46
- if (!this.instance) {
47
- this.instance = new WebAssembly.Instance(loadModule(), {}).exports;
48
- // An Emscripten reactor: static constructors run here, before any call.
49
- this.instance._initialize();
50
- }
51
- return this.instance;
52
- }
53
- face(source) {
54
- const key = `${source.path}\0${source.index || 0}`;
55
- const loaded = this.faces.get(key);
56
- if (loaded)
57
- return loaded.face;
58
- const hb = this.exports();
59
- const data = fs.readFileSync(source.path);
60
- const pointer = hb.malloc(data.length);
61
- if (!pointer)
62
- throw new Error('out of memory loading font');
63
- new Uint8Array(hb.memory.buffer).set(data, pointer);
64
- const blob = hb.hb_blob_create(pointer, data.length, HB_MEMORY_MODE_READONLY, 0, 0);
65
- const face = hb.hb_face_create(blob, source.index || 0);
66
- hb.hb_blob_destroy(blob);
67
- this.faces.set(key, { pointer, face });
68
- return face;
69
- }
70
- /** A standalone font with just `codepoints` from `source` (`{ path, index }`). */
71
- subset(source, codepoints) {
72
- const hb = this.exports();
73
- const face = this.face(source);
74
- const input = hb.hb_subset_input_create_or_fail();
75
- if (!input)
76
- throw new Error('font subsetting is unavailable');
77
- let result = 0;
78
- let subsetFace = 0;
79
- try {
80
- const set = hb.hb_subset_input_unicode_set(input);
81
- for (const codepoint of codepoints.slice(0, MAX_CODEPOINTS))
82
- hb.hb_set_add(set, codepoint);
83
- subsetFace = hb.hb_subset_or_fail(face, input);
84
- if (!subsetFace)
85
- throw new Error('the font could not be subset');
86
- result = hb.hb_face_reference_blob(subsetFace);
87
- const length = hb.hb_blob_get_length(result);
88
- const pointer = hb.hb_blob_get_data(result, 0);
89
- // Copied out: the heap may grow (and move) on the next call.
90
- return Buffer.from(new Uint8Array(hb.memory.buffer, pointer, length));
91
- }
92
- finally {
93
- if (result)
94
- hb.hb_blob_destroy(result);
95
- if (subsetFace)
96
- hb.hb_face_destroy(subsetFace);
97
- hb.hb_subset_input_destroy(input);
98
- this.scheduleRelease();
99
- }
100
- }
101
- scheduleRelease() {
102
- if (this.idleTimer)
103
- clearTimeout(this.idleTimer);
104
- this.idleTimer = setTimeout(() => this.release(), this.idleMs);
105
- this.idleTimer.unref?.();
106
- }
107
- /** Drop every loaded face and the instance holding them. */
108
- release() {
109
- if (this.idleTimer)
110
- clearTimeout(this.idleTimer);
111
- this.idleTimer = null;
112
- this.faces.clear();
113
- this.instance = null;
114
- }
115
- }
116
- export { FontSubsetter };