herdr-remote 0.2.39 → 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 -303
  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 -116
  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
@@ -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 };