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,303 +1,3 @@
1
- import { execFileSync } from 'node:child_process';
2
- import fs from 'node:fs';
3
- import net from 'node:net';
4
- import path from 'node:path';
5
- import { PtySession } from '../pty-session.js';
6
- import { PACKAGE_ROOT, stateDir } from '../paths.js';
7
- import { ADMIN_BROKER_MAX_MESSAGE_BYTES, adminBrokerTaskName, adminBrokerTokenMatches, connectAdminBroker, listenAdminBroker, publishAdminBrokerEndpoint, sendAdminBrokerMessage, } from './adminBrokerEndpoint.js';
8
- const BROKER_PING_TIMEOUT_MS = 1_000;
9
- const BROKER_START_TIMEOUT_MS = 5_000;
10
- const BROKER_START_POLL_MS = 250;
11
- const BROKER_TASK_END_TIMEOUT_MS = 10_000;
12
- /** Time for a stopping broker's goodbye to reach the caller before the process ends. */
13
- const BROKER_EXIT_GRACE_MS = 1_000;
14
- function powershellQuote(value) {
15
- return `'${value.replaceAll("'", "''")}'`;
16
- }
17
- function runPowerShell(script, t) {
18
- try {
19
- execFileSync('powershell.exe', [
20
- '-NoProfile',
21
- '-NonInteractive',
22
- '-EncodedCommand',
23
- Buffer.from(script, 'utf16le').toString('base64'),
24
- ], {
25
- stdio: 'pipe',
26
- windowsHide: true,
27
- timeout: 15_000,
28
- });
29
- }
30
- catch {
31
- throw new Error(t('adminBroker.needsElevation'));
32
- }
33
- }
34
- /** Registers and starts the broker's logon task; run elevated. */
35
- export function adminBrokerTaskScript() {
36
- // Not this module's own URL: a release bundles it into a shared chunk, and
37
- // only this entry point runs the broker when started.
38
- const brokerPath = path.join(PACKAGE_ROOT, 'dist', 'connector', 'admin-broker.js');
39
- // Pinned so the broker publishes its endpoint where this install's callers look.
40
- const brokerCommand = `$env:HERDR_REMOTE_STATE_DIR = ${powershellQuote(stateDir())}; & ${powershellQuote(process.execPath)} ${powershellQuote(brokerPath)}`;
41
- const encodedBrokerCommand = Buffer.from(brokerCommand, 'utf16le').toString('base64');
42
- const powershell = path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
43
- // Not PACKAGE_ROOT: npm updates the package by renaming its directory, and
44
- // Windows refuses while it is the working directory of this long-lived task.
45
- return `
46
- $ErrorActionPreference = 'Stop'
47
- $taskName = ${powershellQuote(adminBrokerTaskName())}
48
- $user = [Security.Principal.WindowsIdentity]::GetCurrent().Name
49
- $action = New-ScheduledTaskAction -Execute ${powershellQuote(powershell)} -Argument ${powershellQuote(`-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ${encodedBrokerCommand}`)} -WorkingDirectory ${powershellQuote(stateDir())}
50
- $trigger = New-ScheduledTaskTrigger -AtLogOn -User $user
51
- $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Highest
52
- $settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Seconds 0)
53
- Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
54
- Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
55
- Start-ScheduledTask -TaskName $taskName
56
- `;
57
- }
58
- export function installAdminBroker(t) {
59
- if (process.platform !== 'win32')
60
- throw new Error(t('adminBroker.windowsOnly'));
61
- // The task cannot start in a working directory that does not exist yet.
62
- fs.mkdirSync(stateDir(), { recursive: true });
63
- runPowerShell(adminBrokerTaskScript(), t);
64
- }
65
- export function uninstallAdminBroker(t) {
66
- if (process.platform !== 'win32')
67
- throw new Error(t('adminBroker.windowsOnly'));
68
- runPowerShell(`
69
- $ErrorActionPreference = 'Stop'
70
- $taskName = ${powershellQuote(adminBrokerTaskName())}
71
- Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
72
- Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
73
- `, t);
74
- }
75
- /** Send the broker one request and read the type of its one reply, or null without one. */
76
- function requestAdminBroker(type) {
77
- let broker;
78
- try {
79
- broker = connectAdminBroker();
80
- }
81
- catch {
82
- return Promise.resolve(null);
83
- }
84
- const { socket, token } = broker;
85
- return new Promise((resolve) => {
86
- let settled = false;
87
- let response = '';
88
- const finish = (reply) => {
89
- if (settled)
90
- return;
91
- settled = true;
92
- socket.destroy();
93
- resolve(reply);
94
- };
95
- socket.setTimeout(BROKER_PING_TIMEOUT_MS, () => finish(null));
96
- socket.setEncoding('utf8');
97
- socket.on('connect', () => sendAdminBrokerMessage(socket, { type, token }));
98
- socket.on('data', (chunk) => {
99
- response += chunk;
100
- const newline = response.indexOf('\n');
101
- if (newline < 0)
102
- return;
103
- try {
104
- finish(String(JSON.parse(response.slice(0, newline)).type));
105
- }
106
- catch {
107
- finish(null);
108
- }
109
- });
110
- socket.on('error', () => finish(null));
111
- });
112
- }
113
- export async function adminBrokerStatus() {
114
- return (await requestAdminBroker('ping')) === 'pong';
115
- }
116
- /**
117
- * Ask the broker to exit so its task can start it on a new release. It stays
118
- * while an administrator terminal is open: that terminal may be the one asking.
119
- * A broker from before this request answers neither way.
120
- */
121
- export async function stopAdminBroker() {
122
- const reply = await requestAdminBroker('shutdown');
123
- return reply === 'bye' ? 'stopped' : reply === 'busy' ? 'busy' : 'unavailable';
124
- }
125
- /** Start the broker's task now, which the account that registered it may do unelevated. */
126
- export function startAdminBrokerTask(t) {
127
- if (process.platform !== 'win32')
128
- throw new Error(t('adminBroker.windowsOnly'));
129
- runPowerShell(`
130
- $ErrorActionPreference = 'Stop'
131
- $taskName = ${powershellQuote(adminBrokerTaskName())}
132
- # A broker that was just stopped may still be ending, and IgnoreNew drops a start until it has.
133
- $deadline = [DateTime]::UtcNow.AddMilliseconds(${BROKER_TASK_END_TIMEOUT_MS})
134
- while ((Get-ScheduledTask -TaskName $taskName).State -eq 'Running' -and [DateTime]::UtcNow -lt $deadline) { Start-Sleep -Milliseconds ${BROKER_START_POLL_MS} }
135
- Start-ScheduledTask -TaskName $taskName
136
- `, t);
137
- }
138
- /** The task starts the broker a moment after it is registered; wait for it to answer. */
139
- export async function waitForAdminBroker() {
140
- const deadline = Date.now() + BROKER_START_TIMEOUT_MS;
141
- while (!(await adminBrokerStatus())) {
142
- if (Date.now() >= deadline)
143
- return false;
144
- await new Promise((resolve) => setTimeout(resolve, BROKER_START_POLL_MS));
145
- }
146
- return true;
147
- }
148
- function writeError(socket, error) {
149
- const message = error instanceof Error ? error.message : String(error);
150
- sendAdminBrokerMessage(socket, { type: 'error', message });
151
- socket.end();
152
- }
153
- function startBrokerPty(socket, message, spawn) {
154
- if (typeof message.command !== 'string' || !message.command || message.command.length > 4096)
155
- throw new Error('Invalid administrator terminal command.');
156
- if (path.basename(message.command).toLowerCase() !== 'powershell.exe')
157
- throw new Error('The administrator broker only starts PowerShell.');
158
- if (!Array.isArray(message.args) ||
159
- message.args.length > 128 ||
160
- !message.args.every((arg) => typeof arg === 'string'))
161
- throw new Error('Invalid administrator terminal arguments.');
162
- if (message.args.length !== 2 ||
163
- message.args[0] !== '-NoLogo' ||
164
- message.args[1] !== '-NoProfile')
165
- throw new Error('The administrator broker only starts an interactive PowerShell prompt.');
166
- if (typeof message.cwd !== 'string' || message.cwd.length > 4096)
167
- throw new Error('Invalid administrator terminal working directory.');
168
- if (message.socketPath !== null && message.socketPath !== undefined)
169
- throw new Error('An administrator PowerShell session does not use a Herdr socket.');
170
- const pty = new PtySession({
171
- command: message.command,
172
- args: message.args,
173
- cwd: message.cwd,
174
- socketPath: message.socketPath,
175
- platform: 'win32',
176
- // A plain PowerShell prompt does not need the Herdr UI's Kitty or mouse modes.
177
- fastWindowsPty: true,
178
- spawn,
179
- });
180
- pty.start({
181
- cols: PtySession.clampDimension(message.cols, PtySession.DEFAULT_COLS),
182
- rows: PtySession.clampDimension(message.rows, PtySession.DEFAULT_ROWS),
183
- onData: (data) => {
184
- if (socket.writable)
185
- sendAdminBrokerMessage(socket, {
186
- type: 'data',
187
- dataBase64: Buffer.from(data).toString('base64'),
188
- });
189
- },
190
- onExit: ({ exitCode, signal }) => {
191
- if (socket.writable)
192
- sendAdminBrokerMessage(socket, { type: 'exit', exitCode, signal });
193
- socket.end();
194
- },
195
- });
196
- sendAdminBrokerMessage(socket, { type: 'started', pid: pty.terminal?.pid ?? 0 });
197
- return pty;
198
- }
199
- /** Listen on loopback and publish the endpoint; every request must carry its token. */
200
- export function serveAdminBroker({ spawn, onShutdown = () => { }, } = {}) {
201
- let token = '';
202
- const sessions = new Set();
203
- const server = net.createServer((socket) => {
204
- socket.setEncoding('utf8');
205
- let pending = '';
206
- let pty = null;
207
- let firstMessage = true;
208
- socket.on('data', (chunk) => {
209
- pending += chunk;
210
- if (Buffer.byteLength(pending, 'utf8') > ADMIN_BROKER_MAX_MESSAGE_BYTES) {
211
- socket.destroy(new Error('Administrator terminal message exceeded the limit.'));
212
- return;
213
- }
214
- while (true) {
215
- const newline = pending.indexOf('\n');
216
- if (newline < 0)
217
- return;
218
- const line = pending.slice(0, newline);
219
- pending = pending.slice(newline + 1);
220
- let message;
221
- try {
222
- message = JSON.parse(line);
223
- }
224
- catch {
225
- writeError(socket, new Error('Invalid administrator terminal message.'));
226
- return;
227
- }
228
- if (firstMessage) {
229
- firstMessage = false;
230
- if (!adminBrokerTokenMatches(token, message.token)) {
231
- writeError(socket, new Error('Administrator terminal request was not authorized.'));
232
- return;
233
- }
234
- if (message.type === 'ping') {
235
- sendAdminBrokerMessage(socket, { type: 'pong' });
236
- socket.end();
237
- return;
238
- }
239
- if (message.type === 'shutdown') {
240
- const busy = sessions.size > 0;
241
- sendAdminBrokerMessage(socket, { type: busy ? 'busy' : 'bye' });
242
- socket.end();
243
- if (!busy) {
244
- server.close();
245
- onShutdown();
246
- }
247
- return;
248
- }
249
- if (message.type !== 'start') {
250
- writeError(socket, new Error('Expected an administrator terminal start request.'));
251
- return;
252
- }
253
- try {
254
- pty = startBrokerPty(socket, message, spawn);
255
- sessions.add(pty);
256
- }
257
- catch (error) {
258
- writeError(socket, error);
259
- return;
260
- }
261
- continue;
262
- }
263
- if (!pty)
264
- return;
265
- if (message.type === 'input' && typeof message.dataBase64 === 'string') {
266
- if (message.dataBase64.length <= ADMIN_BROKER_MAX_MESSAGE_BYTES)
267
- pty.write(Buffer.from(message.dataBase64, 'base64'));
268
- }
269
- else if (message.type === 'resize') {
270
- pty.resize(message.cols, message.rows);
271
- }
272
- else if (message.type === 'kill') {
273
- pty.kill();
274
- socket.end();
275
- return;
276
- }
277
- }
278
- });
279
- const release = () => {
280
- if (!pty)
281
- return;
282
- pty.kill();
283
- sessions.delete(pty);
284
- };
285
- socket.on('close', release);
286
- socket.on('error', release);
287
- });
288
- listenAdminBroker(server, (port) => {
289
- token = publishAdminBrokerEndpoint(port);
290
- });
291
- return server;
292
- }
293
- function runAdminBroker() {
294
- if (process.platform !== 'win32')
295
- throw new Error('Administrator PTYs are only supported on Windows.');
296
- serveAdminBroker({
297
- onShutdown: () => setTimeout(() => process.exit(0), BROKER_EXIT_GRACE_MS).unref(),
298
- }).on('error', (error) => {
299
- process.stderr.write(`herdr-remote administrator broker: ${error.message}\n`);
300
- });
301
- }
302
- if (path.basename(process.argv[1] || '') === 'admin-broker.js')
303
- runAdminBroker();
1
+ import { createRequire as __herdrCreateRequire } from 'node:module';
2
+ const require = __herdrCreateRequire(import.meta.url);
3
+ import{e as a,f as b,g as c,h as d,i as e,j as f,k as g,l as h}from"../chunks/42KARAL4.js";import"../chunks/TNLCNVA2.js";import"../chunks/I6NRCYLI.js";export{d as adminBrokerStatus,a as adminBrokerTaskScript,b as installAdminBroker,h as serveAdminBroker,f as startAdminBrokerTask,e as stopAdminBroker,c as uninstallAdminBroker,g as waitForAdminBroker};
@@ -1,18 +1,20 @@
1
- // Process entry for the host connector; service.ts spawns this file.
2
- import { EXIT_REPLACED } from '../exit-codes.js';
3
- import { HostConnector } from './host-connector.js';
4
- const connector = new HostConnector({ onFatal: (exitCode) => process.exit(exitCode) });
5
- try {
6
- connector.start();
7
- }
8
- catch (error) {
9
- const { code, message } = error;
10
- process.stderr.write(`herdr-remote host connector failed: ${message}\n`);
11
- process.exitCode = code === 'HOST_ALREADY_RUNNING' ? EXIT_REPLACED : 1;
12
- }
13
- const stop = () => {
14
- connector.stop();
15
- process.exit(0);
16
- };
17
- process.once('SIGINT', stop);
18
- process.once('SIGTERM', stop);
1
+ import { createRequire as __herdrCreateRequire } from 'node:module';
2
+ const require = __herdrCreateRequire(import.meta.url);
3
+ import"../chunks/C2UUZY6J.js";import{a as we,b as Te,d as Se}from"../chunks/MEFDV375.js";import"../chunks/3FQAXVGC.js";import{a as g}from"../chunks/42KARAL4.js";import{a as d}from"../chunks/7SMZ43LI.js";import{a as fe,b as me,c as pe,d as k}from"../chunks/JJGEV4EZ.js";import{a as ge,d as M,e as ye,i as _e}from"../chunks/H24ZP5D5.js";import{m as be}from"../chunks/PBRQ3HEG.js";import"../chunks/U6QAYG3E.js";import"../chunks/CVJ4HBMP.js";import{b as ne,e as ie,g as L,h as oe,i as ae,m as ce,n as he,o as le,p as de,q as ue}from"../chunks/KGOQKOHH.js";import{h as Q,i as Z,o as ee}from"../chunks/M52LJEES.js";import{b as re,c as se}from"../chunks/KRVF37OW.js";import{a as p,d as te}from"../chunks/QRGNEKME.js";import{a as G,b as Y,c as W,h as X,j as w,k as q,l as j,m as J}from"../chunks/PBMBFWLQ.js";import{a as K,c as b}from"../chunks/TNLCNVA2.js";import"../chunks/I6NRCYLI.js";import Pt from"node:os";import De from"node:path";import Ke from"node:crypto";import y from"node:fs";import C from"node:path";var Ge=20,Ye=50*1024*1024,We=1440*60*1e3;function ke(){let s=C.join(b(),"pasted");return p(s),s}function I({dir:s=ke(),maxFiles:e=Ge,maxBytes:t=Ye,maxAgeMs:r=We,now:n=Date.now()}={}){let i;try{i=y.readdirSync(s,{withFileTypes:!0})}catch{return}let o=[];for(let h of i){if(!h.isFile())continue;let l=C.join(s,h.name);try{let c=y.statSync(l);if(n-c.mtimeMs>r){try{y.unlinkSync(l)}catch{}continue}o.push({path:l,size:c.size,mtimeMs:c.mtimeMs})}catch{}}o.sort((h,l)=>h.mtimeMs-l.mtimeMs);let a=o.reduce((h,l)=>h+l.size,0);for(;o.length>e||a>t;){let h=o.shift();if(!h)break;try{y.unlinkSync(h.path),a-=h.size}catch{}}}function Ie({mime:s,dataBase64:e,dir:t=ke()}){if(!j(s))throw new Error(`unsupported MIME type: ${s}`);if(typeof e!="string")throw new Error("missing or invalid dataBase64 payload");let r=Buffer.from(e,"base64");if(r.length>3145728)throw new Error(`file size (${r.length} bytes) exceeds maximum limit of 3 MB`);if(r.length===0)throw new Error("file payload is empty");if(!J(s,r))throw new Error(`file magic bytes do not match declared MIME type ${s}`);I({dir:t});let n=`${Ke.randomUUID()}${q[s]}`,i=C.join(t,n);y.writeFileSync(i,r,{mode:384,flag:"wx"});try{y.chmodSync(i,384)}catch{}return I({dir:t}),i}var T=["blocked","done","working","idle","unknown"];function Ee(s,e=48){if(typeof s!="string")return null;let t=s.replace(/[\u0000-\u001F\u007F-\u009F]/g," ").replace(/\s+/g," ").trim();return t?t.length>e?t.slice(0,e):t:null}function Xe(s){return T.includes(s)?s:"unknown"}function qe(){return Object.fromEntries(T.map(s=>[s,0]))}function Ae(s){let e=Array.isArray(s?.agents)?s.agents:[],t=Array.isArray(s?.panes)?s.panes:[],r=typeof s?.focused_pane_id=="string"&&s.focused_pane_id?s.focused_pane_id:null,n=(r?t.find(c=>c?.pane_id===r):t.find(c=>c?.focused===!0))||null,i=(r?e.find(c=>c?.pane_id===r):e.find(c=>c?.focused===!0))||null,o=r||(typeof n?.pane_id=="string"?n.pane_id:null)||(typeof i?.pane_id=="string"?i.pane_id:null),a=typeof n?.agent=="string"?n.agent:typeof i?.agent=="string"?i.agent:null,h=qe(),l=[];for(let c of e){if(!c||typeof c.pane_id!="string")continue;let f=Xe(c.agent_status);h[f]+=1,l.push({paneId:c.pane_id,workspaceId:typeof c.workspace_id=="string"?c.workspace_id:null,agent:Ee(c.display_agent||c.agent,24),title:Ee(c.terminal_title_stripped||c.terminal_title),status:f,focused:c.focused===!0})}return l.sort((c,f)=>T.indexOf(c.status)-T.indexOf(f.status)),{focusedPaneId:o,focusedAgent:a,counts:h,total:l.length,agents:l.slice(0,16)}}function He(s,e){if(!s||!e)return s===e;if(s.focusedPaneId!==e.focusedPaneId||s.focusedAgent!==e.focusedAgent||s.total!==e.total)return!1;for(let t of T)if((s.counts?.[t]||0)!==(e.counts?.[t]||0))return!1;return s.agents.length!==e.agents.length?!1:s.agents.every((t,r)=>{let n=e.agents[r];return n&&t.paneId===n.paneId&&t.status===n.status&&t.agent===n.agent&&t.title===n.title})}var je=5e3,Je=150,Qe=[{type:"pane.focused"},{type:"tab.focused"},{type:"workspace.focused"},{type:"pane.agent_detected"}],E=class{socketPath;request;subscribe;send;pollTimer=null;debounceTimer=null;subscription=null;pending=!1;stale=!1;lastSummary=null;constructor(e,t){this.socketPath=e.socketPath,this.request=e.requestHerdr||we,this.subscribe=e.subscribeHerdr||Te,this.send=t}start(){if(!this.pollTimer){this.pollTimer=setInterval(()=>this.read(),je),this.pollTimer.unref?.();try{this.subscription=this.subscribe(this.socketPath,Qe,()=>this.scheduleRead(),{onStarted:()=>this.scheduleRead()})}catch{}this.read()}}stop(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.subscription&&(this.subscription.close(),this.subscription=null),this.stale=!1,this.lastSummary=null}scheduleRead(){this.pollTimer&&(this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>{this.debounceTimer=null,this.read()},Je),this.debounceTimer.unref?.())}async read(){if(!this.pollTimer)return;if(this.pending){this.stale=!0;return}this.pending=!0,this.stale=!1;let e;try{let r=await this.request(this.socketPath,"session.snapshot",{});e=r?.snapshot||r}catch{return}finally{this.pending=!1,this.stale&&this.scheduleRead()}if(!this.pollTimer)return;let t=Ae(e);He(this.lastSummary,t)||(this.lastSummary=t,this.send({type:"agent_status",...t}))}};import it from"node:crypto";import ot from"node:fs";import{createRequire as Ze}from"node:module";import R from"node:fs";import et from"node:path";var tt=6e4,rt=16384,st=1,N=null;function nt(){if(!N){let s=et.join(K,"dist","harfbuzz-subset.wasm"),e=R.existsSync(s)?s:Ze(import.meta.url).resolve("harfbuzzjs/dist/harfbuzz-subset.wasm");N=new WebAssembly.Module(R.readFileSync(e))}return N}var A=class{idleMs;instance=null;faces=new Map;idleTimer=null;constructor({idleMs:e=tt}={}){this.idleMs=e}exports(){return this.instance||(this.instance=new WebAssembly.Instance(nt(),{}).exports,this.instance._initialize()),this.instance}face(e){let t=`${e.path}\0${e.index||0}`,r=this.faces.get(t);if(r)return r.face;let n=this.exports(),i=R.readFileSync(e.path),o=n.malloc(i.length);if(!o)throw new Error("out of memory loading font");new Uint8Array(n.memory.buffer).set(i,o);let a=n.hb_blob_create(o,i.length,st,0,0),h=n.hb_face_create(a,e.index||0);return n.hb_blob_destroy(a),this.faces.set(t,{pointer:o,face:h}),h}subset(e,t){let r=this.exports(),n=this.face(e),i=r.hb_subset_input_create_or_fail();if(!i)throw new Error("font subsetting is unavailable");let o=0,a=0;try{let h=r.hb_subset_input_unicode_set(i);for(let f of t.slice(0,rt))r.hb_set_add(h,f);if(a=r.hb_subset_or_fail(n,i),!a)throw new Error("the font could not be subset");o=r.hb_face_reference_blob(a);let l=r.hb_blob_get_length(o),c=r.hb_blob_get_data(o,0);return Buffer.from(new Uint8Array(r.memory.buffer,c,l))}finally{o&&r.hb_blob_destroy(o),a&&r.hb_face_destroy(a),r.hb_subset_input_destroy(i),this.scheduleRelease()}}scheduleRelease(){this.idleTimer&&clearTimeout(this.idleTimer),this.idleTimer=setTimeout(()=>this.release(),this.idleMs),this.idleTimer.unref?.()}release(){this.idleTimer&&clearTimeout(this.idleTimer),this.idleTimer=null,this.faces.clear(),this.instance=null}};var at=48*1024*1024,ve="The terminal font changed or is no longer on this workstation",v=class{font;load;subsetter;subsets=new Map;send;constructor(e,t){this.load=e.loadTerminalFont||_e,this.font=e.terminalFont!==void 0?e.terminalFont:this.load(),this.subsetter=e.fontSubsetter||new A,this.send=t}publicFont(){return M(this.font)}pickUp(){if(this.font)return;let e=null;try{e=this.load()}catch{return}e&&(this.font=e,this.send({type:"terminal_font",terminalFont:M(e)}))}refresh(){try{let e=this.load({refresh:!0});e&&(this.font=e)}catch(e){process.stderr.write(`herdr-remote host connector: could not read the terminal font: ${e.message}
4
+ `)}this.send({type:"terminal_font",terminalFont:this.publicFont()})}sendChunk(e,t){let r=this.subsetChunk(t.sha256,t.index)||ye(this.font,t.sha256,t.index);if(!r){this.send({type:"error",clientId:e,code:"host_font_unavailable",message:ve});return}this.send({type:"host_font_chunk",clientId:e,sha256:t.sha256,index:t.index,total:r.total,dataBase64:r.data.toString("base64")})}sendSubset(e,t){let r=(l,c)=>this.send({type:"error",clientId:e,code:l,message:c}),n=this.font?.subsets?.find(l=>l.sha256===t.sha256);if(!n||!this.unchanged(n)){r("host_font_unavailable",ve);return}let i=[...new Set(Array.from(String(t.text||""),l=>l.codePointAt(0)))],o;try{o=this.subsetter.subset(n,i)}catch(l){r("host_font_subset_failed",l.message||"The font could not be subset");return}let a=it.createHash("sha256").update(o).digest("hex"),h={type:"host_font_subset_ready",clientId:e,requestId:t.requestId,sha256:n.sha256,subsetSha:a,bytes:o.length};o.length<=262144?h.dataBase64=o.toString("base64"):this.hold(a,o),this.send(h)}unchanged(e){try{let t=ot.statSync(e.path);return t.size===e.bytes&&t.mtimeMs===e.mtimeMs}catch{return!1}}hold(e,t){this.subsets.delete(e),this.subsets.set(e,t);let r=[...this.subsets.values()].reduce((n,i)=>n+i.length,0);for(let[n,i]of this.subsets){if(r<=at||n===e)break;this.subsets.delete(n),r-=i.length}}subsetChunk(e,t){let r=this.subsets.get(e);if(!r)return null;let n=Math.ceil(r.length/262144);if(!Number.isInteger(t)||t<0||t>=n)return null;let i=t*262144;return{data:r.subarray(i,i+262144),total:n}}};import lt from"node:os";import ct from"node:fs";var Pe=["ctrl+b"];function Fe(s=process.env,e=process.platform){let t;try{t=ct.readFileSync(fe(s,e),"utf8")}catch{return[...Pe]}let r=ge(t)["keys.prefix"];return X(typeof r=="string"?[r]:r)??[...Pe]}function Oe(s){return{type:"host_hello",protocol:1,hostId:s.hostId,token:s.token,password:s.password||null,hostname:lt.hostname(),platform:process.platform,...process.platform==="win32"&&(process.env.MSYSTEM||/(?:^|[\\/])(?:bash|sh)(?:\.exe)?$/i.test(process.env.SHELL||""))?{shellProfile:"git-bash"}:{},arch:process.arch,version:s.version,terminalPalette:s.terminalPalette||null,terminalFont:s.terminalFont,herdrPrefixKeys:Fe(),capabilities:[w.hostHandoff,w.idleHeartbeat,w.binaryFrameV2,...process.platform==="win32"?[w.adminTabs]:[]]}}import _ from"node:fs";import dt from"node:path";function Le(s){try{return JSON.parse(_.readFileSync(s,"utf8"))}catch{return null}}function xe(s,e){let t=new Error(s);return t.code=e,t}function Me(s,e){p(dt.dirname(s));for(let t=0;t<2;t+=1)try{let r=_.openSync(s,"wx",384);return _.writeFileSync(r,`${JSON.stringify({pid:process.pid,processStart:re(process.pid),hostId:e,startedAt:new Date().toISOString()})}
5
+ `),r}catch(r){if(r.code!=="EEXIST")throw r;let n=Le(s);if(n&&se([n]).length>0)throw xe(`another host connector is already running (pid ${n.pid})`,"HOST_ALREADY_RUNNING");try{_.rmSync(s,{force:!0})}catch{}}throw xe("could not acquire host connector lock","HOST_LOCK_FAILED")}function Ce(s,e){try{_.closeSync(e)}catch{}let t=Le(s);if(!(t&&t.pid!==process.pid))try{_.rmSync(s,{force:!0})}catch{}}import P from"node:fs";import ut from"node:net";import ft from"node:path";import{spawn as mt}from"node:child_process";var pt=1e3,gt=2e4,yt=200;function D(s,{connect:e=ut.connect,timeout:t=pt}={}){let r=k(s);return r.ok?new Promise(n=>{let i,o=h=>{clearTimeout(a),i&&(i.removeAllListeners(),i.on("error",()=>{}),i.destroy()),n(h)},a=setTimeout(()=>o({state:"running"}),t);typeof a.unref=="function"&&a.unref(),i=e(me(s)),i.once("connect",()=>o({state:"running"})),i.once("error",h=>o(h.code==="ECONNREFUSED"||h.code==="ENOENT"?{state:"stopped",stale:!0}:{state:"unavailable",reason:h.message}))}):Promise.resolve(r.missing?{state:"stopped"}:{state:"unavailable",reason:r.reason})}function _t({platform:s=process.platform,readCgroup:e=()=>P.readFileSync("/proc/self/cgroup","utf8")}={}){if(s!=="linux")return!1;try{let t=e().split(`
6
+ `).find(r=>r.startsWith("0::"));return t!==void 0&&/\.service$/.test(t.trim())}catch{return!1}}function bt(s,e=process.env){let t={...e};for(let r of Object.keys(t))r.startsWith("HERDR_")&&delete t[r];return s&&(t.HERDR_SOCKET_PATH=s),t}function wt({command:s,args:e=[],inService:t=_t()}){let r=[s,...e,"server"];return t?{file:"systemd-run",args:["--user","--scope","--quiet","--collect","--description=Herdr server started by herdr-remote","--",...r]}:{file:r[0],args:r.slice(1)}}function Tt({command:s,args:e=[],socketPath:t,cwd:r,logPath:n,spawnProcess:i=mt,inService:o}){let{file:a,args:h}=wt({command:s,args:e,inService:o});p(ft.dirname(n));let l=P.openSync(n,"a"),c;try{c=i(a,h,{cwd:r,env:bt(t),detached:!0,windowsHide:!0,stdio:["ignore",l,l]})}finally{P.closeSync(l)}return c.on("error",()=>{}),c.unref(),c}function St(s,e=2e3){try{return P.readFileSync(s,"utf8").slice(-e).trim()}catch{return""}}async function Ne({command:s,args:e=[],socketPath:t,cwd:r,logPath:n,probe:i=D,launch:o=Tt,timeout:a=gt,interval:h=yt}){let l=await i(t);if(l.state==="running")return{started:!1};if(l.state==="unavailable")throw new Error(`Herdr socket at ${t} cannot be used: ${l.reason}`);let c=o({command:s,args:e,socketPath:t,cwd:r,logPath:n}),f=null;c.once("exit",(B,S)=>{f={code:B,signal:S}});let Ve=Date.now()+a;for(;;){if(await new Promise(Be=>setTimeout(Be,h)),(await i(t)).state==="running")return{started:!0};let S=St(n),z=S?`
7
+ ${S}`:"";if(f)throw new Error(`Herdr server exited (${f.signal||`code ${f.code}`}) before opening ${t}.${z}`);if(Date.now()>=Ve)throw new Error(`Herdr server did not open ${t} within ${Math.round(a/1e3)}s.${z}`)}}function m(s,e){s&&s.readyState===d.default.OPEN&&s.send(JSON.stringify(e))}function Re(s,e="host connector stopping"){if(!(!s||s.readyState===d.default.CLOSED||s.readyState===d.default.CLOSING))try{s.close(1e3,e)}catch{}}function U(s){if(!s||s.readyState===d.default.CLOSED)return;let e=s;try{typeof e.terminate=="function"?e.terminate():typeof e.destroy=="function"?e.destroy():e.close?.()}catch{}}function u(s){let e=s.clientId||s.streamId;return typeof e=="string"&&e?e:null}var It=1500,Et=4,At=3,F=class{byStream=new Map;streamIndexToId=new Map;waitingForHerdr=new Map;probingStarts=new Map;herdrLaunch=null;herdrVersionWarned=!1;fastFailures=0;herdrCommand;herdrArgs;socketPath;cwd;herdrLookup;herdrLogPath;readHerdrVersion;Pty;probeHerdr;ensureHerdr;link;constructor(e,t){this.socketPath=e.socketPath,this.herdrLookup=e.herdrLookup||{},this.herdrCommand=e.herdrCommand||ce(this.herdrLookup),this.herdrArgs=e.herdrArgs,this.cwd=e.cwd,this.herdrLogPath=e.herdrLogPath,this.readHerdrVersion=e.readHerdrVersion||le,this.Pty=e.PtySession||g,this.probeHerdr=e.probeHerdr||D,this.ensureHerdr=e.ensureHerdr||Ne,this.link=t}sendError(e,t,r){this.link.send({type:"error",clientId:e,code:t,message:r})}ensureHerdrCommand(){let e=he(this.herdrCommand,this.herdrLookup);return e.found?(this.herdrCommand=e.command,this.warnIfHerdrOutdated(),null):ue(this.herdrLookup)}warnIfHerdrOutdated(){if(this.herdrVersionWarned)return;let e=this.readHerdrVersion({command:this.herdrCommand});if(e.supported){e.version&&(this.herdrVersionWarned=!0);return}this.herdrVersionWarned=!0,process.stderr.write(`herdr-remote host connector: ${de(e.version)}
8
+ `)}start(e){let t=u(e);if(!t)return;this.stop(t);let r=this.ensureHerdrCommand();if(r){process.stderr.write(`herdr-remote host connector: ${r}
9
+ `),this.sendError(t,"herdr_not_found",r);return}let n=k(this.socketPath);if(!n.ok&&!n.missing){this.sendError(t,"herdr_socket_unavailable",n.reason);return}if(!n.ok){this.waitForHerdr(t,e);return}let i={message:e};return this.probingStarts.set(t,i),this.probeHerdr(this.socketPath).then(o=>{this.probingStarts.get(t)===i&&(this.probingStarts.delete(t),o.state==="running"?this.spawn(i.message):o.state==="stopped"?this.waitForHerdr(t,i.message):this.sendError(t,"herdr_socket_unavailable",o.reason))})}waitForHerdr(e,t){this.waitingForHerdr.set(e,t),this.sendError(e,"herdr_not_running",`Herdr is not running on this workstation (${this.socketPath}).`)}startHerdr(){return this.herdrLaunch||(this.herdrLaunch=Promise.resolve().then(()=>{let e=this.ensureHerdrCommand();if(e)throw new Error(e);return this.ensureHerdr({command:this.herdrCommand,args:this.herdrArgs,socketPath:this.socketPath,cwd:this.cwd,logPath:this.herdrLogPath})}).finally(()=>{this.herdrLaunch=null})),this.herdrLaunch}async startHerdrFor(e){try{(await this.startHerdr()).started&&process.stderr.write(`herdr-remote host connector: started Herdr at a browser's request
10
+ `)}catch(r){let{message:n}=r;process.stderr.write(`herdr-remote host connector: could not start Herdr: ${n}
11
+ `),e&&this.sendError(e,"herdr_start_failed",n);return}let t=[...this.waitingForHerdr.values()];this.waitingForHerdr.clear();for(let r of t)this.spawn(r)}spawn(e){let t=u(e),r=typeof e.streamIndex=="number"?e.streamIndex:null,n=new this.Pty({command:this.herdrCommand,args:this.herdrArgs,cwd:this.cwd,socketPath:this.socketPath}),i={id:t,streamIndex:r,pty:n,cols:Number(e.cols)||g.DEFAULT_COLS,rows:Number(e.rows)||g.DEFAULT_ROWS,createdAt:new Date().toISOString(),startedAtMs:Date.now(),pendingOutput:[],flushImmediate:null,flushTimeout:null},o=()=>{if(i.flushImmediate&&(clearImmediate(i.flushImmediate),i.flushImmediate=null),i.flushTimeout&&(clearTimeout(i.flushTimeout),i.flushTimeout=null),i.pendingOutput.length===0)return;let a=Buffer.concat(i.pendingOutput);i.pendingOutput=[],this.link.sendFrame(i.streamIndex!==null?Y(0,i.streamIndex,a):G("output",t,a))};try{n.start({cols:i.cols,rows:i.rows,onData:a=>{let h=Buffer.isBuffer(a)?a:Buffer.from(a,"utf8");h.length!==0&&(i.pendingOutput.push(h),!i.flushImmediate&&!i.flushTimeout&&(process.platform==="win32"?i.flushTimeout=setTimeout(o,Et):i.flushImmediate=setImmediate(o)))},onExit:({exitCode:a})=>{this.byStream.get(t)===i&&(this.forget(i),this.reportExit(i,a),this.link.changed())}})}catch(a){$(i),this.sendError(t,"pty_start_failed",a.message),n.kill();return}this.byStream.set(t,i),r!==null&&this.streamIndexToId.set(r,t),this.link.send({type:"session_ready",clientId:t}),this.link.changed()}reportExit(e,t){let r=e.id,n=Date.now()-e.startedAtMs,i=t!==0&&n<It;if(this.fastFailures=i?this.fastFailures+1:0,this.fastFailures<At){this.link.send({type:"session_exit",clientId:r,code:t});return}let o=`"${this.herdrCommand}" exited immediately with code ${t} on ${this.fastFailures} attempts in a row. Check that it runs from a terminal, and see the host connector log for what it printed.`;process.stderr.write(`herdr-remote host connector: ${o}
12
+ `),this.sendError(r,"herdr_start_failed",o)}write(e,t){let r=e.streamIndex!==void 0?this.streamIndexToId.get(e.streamIndex):e.streamId;r&&this.byStream.get(r)?.pty.write(t)}resize(e){let t=u(e);if(!t)return;let r=this.waitingForHerdr.get(t)||this.probingStarts.get(t)?.message;r&&(Number.isInteger(e.cols)&&(r.cols=e.cols),Number.isInteger(e.rows)&&(r.rows=e.rows));let n=this.byStream.get(t);n&&(Number.isInteger(e.cols)&&(n.cols=g.clampDimension(e.cols,0)),Number.isInteger(e.rows)&&(n.rows=g.clampDimension(e.rows,0)),n.pty.resize(n.cols,n.rows))}stop(e){this.waitingForHerdr.delete(e),this.probingStarts.delete(e);let t=this.byStream.get(e);t&&(this.forget(t),t.pty.kill(),this.link.changed())}stopAll(){for(let e of this.byStream.values())$(e),e.pty.kill();this.byStream.clear(),this.streamIndexToId.clear(),this.waitingForHerdr.clear(),this.probingStarts.clear()}summaries(){return[...this.byStream.values()].map(e=>({id:e.id,pid:e.pty.terminal?.pid||null,command:e.pty.command,cols:e.cols,rows:e.rows,cwd:e.pty.cwd,createdAt:e.createdAt,activeClients:1}))}forget(e){this.byStream.delete(e.id),e.streamIndex!==null&&this.streamIndexToId.delete(e.streamIndex),$(e)}};function $(s){s.flushImmediate&&(clearImmediate(s.flushImmediate),s.flushImmediate=null),s.flushTimeout&&(clearTimeout(s.flushTimeout),s.flushTimeout=null),s.pendingOutput=[]}var Ht=6e4,O=class{checkUpdate;readInstalledVersion;runningVersion;send;checkedAt=0;pending=null;constructor(e,t){this.checkUpdate=e.checkUpdate!==void 0?e.checkUpdate:ie()?oe:null,this.readInstalledVersion=e.readInstalledVersion||ae,this.runningVersion=e.runningVersion||ne(),this.send=t}report({now:e=Date.now()}={}){let t=this.checkUpdate;return!t||this.pending?this.pending:e-this.checkedAt<Ht?null:(this.checkedAt=e,this.pending=Promise.resolve().then(()=>t()).then(r=>{if(!r?.ok||!r.latest)return;let n=this.runningVersion,i=this.readInstalledVersion()||n;this.send({type:"update_status",current:n,installed:i,latest:r.latest,updateAvailable:L(r.latest,n)>0,restartPending:L(i,n)>0,updateCommand:!0})}).catch(()=>{}).finally(()=>{this.pending=null}),this.pending)}};var Ft=["relay_password_required","host_auth_failed","invalid_host_credentials"],x=class{config;relayUrl;hostId;hostToken;relayPassword;socketPath;terminalPalette;sessions;font;agents;updates;ws=null;reconnectTimer=null;heartbeatTimer=null;keepaliveTimer=null;reconnectAttempts=0;clientCount=0;legacyHeartbeat=!1;authFailure=!1;stopping=!1;lockPath;lockFd=null;onFatal;constructor(e={}){let t=e.config||ee();this.config=t,this.relayUrl=e.relayUrl||(process.env.RELAY_URL?Z(process.env.RELAY_URL):Q(t)),this.hostId=e.hostId||process.env.RELAY_HOST_ID||`host-${te(9)}`,this.hostToken=e.hostToken||process.env.RELAY_HOST_TOKEN||"",this.relayPassword=e.relayPassword??process.env.RELAY_PASSWORD??"",this.socketPath=e.socketPath||pe(t.herdr.socketPath),this.terminalPalette=e.terminalPalette!==void 0?e.terminalPalette:be();let r=n=>m(this.ws,n);this.sessions=new F({...e,socketPath:this.socketPath,herdrArgs:e.herdrArgs||t.herdr.args,cwd:e.cwd||t.herdr.cwd,herdrLogPath:e.herdrLogPath||De.join(b(),"herdr-server.log")},{send:r,sendFrame:n=>{this.ws?.readyState===d.default.OPEN&&this.ws.send(n)},changed:()=>this.sendHeartbeat()}),this.font=new v(e,r),this.agents=new E({...e,socketPath:this.socketPath},r),this.updates=new O(e,r),this.lockPath=e.lockPath||process.env.HERDR_REMOTE_HOST_LOCK||De.join(b(),"host-connector.lock"),this.onFatal=e.onFatal||(n=>process.exit(n));try{I()}catch{}}acquireLock(){this.lockFd===null&&(this.lockFd=Me(this.lockPath,this.hostId))}releaseLock(){this.lockFd!==null&&(Ce(this.lockPath,this.lockFd),this.lockFd=null)}start(){if(!this.hostToken)throw new Error("RELAY_HOST_TOKEN is required");this.acquireLock(),this.stopping=!1,this.connect(),this.config.herdr?.autoStart&&this.sessions.startHerdr().catch(e=>{process.stderr.write(`herdr-remote host connector: could not start Herdr: ${e.message}
13
+ `)})}stop(){this.stopping=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null,m(this.ws,{type:"host_shutdown"}),this.disconnected(),Re(this.ws,"host_shutdown"),this.ws=null,this.releaseLock()}disconnected(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null,this.stopKeepalive(),this.agents.stop(),this.clientCount=0,this.legacyHeartbeat=!1,this.sessions.stopAll()}connect(){if(this.stopping||this.ws?.readyState===d.default.OPEN||this.ws?.readyState===d.default.CONNECTING)return;this.stopKeepalive();let e;try{e=new d.default(this.relayUrl)}catch(t){this.scheduleReconnect(t);return}this.ws=e,e.isAlive=!0,e.on("open",()=>{try{e._socket?.setNoDelay(!0)}catch{}e.isAlive=!0,this.authFailure=!1,m(e,Oe({hostId:this.hostId,token:this.hostToken,password:this.relayPassword,version:this.updates.runningVersion,terminalPalette:this.terminalPalette,terminalFont:this.font.publicFont()}))}),e.on("pong",()=>{e.isAlive=!0}),e.on("message",(t,r)=>this.handleMessage(t,r)),e.on("close",(t,r)=>{if(this.ws!==e)return;if(this.ws=null,this.disconnected(),(r?r.toString():"")==="host_replaced"){process.stderr.write(`herdr-remote host connector: another instance took over this workstation; exiting
14
+ `),this.stop(),this.onFatal(12);return}if(this.authFailure){this.stopping=!0,process.stderr.write(`herdr-remote host connector: authentication failed; update relay credentials and restart the service
15
+ `),this.releaseLock(),this.onFatal(13);return}this.scheduleReconnect()}),e.on("error",t=>{process.stderr.write(`herdr-remote host connector: ${t.message}
16
+ `)})}scheduleReconnect(e=null){if(this.stopping||this.reconnectTimer)return;e&&process.stderr.write(`herdr-remote host connector: ${e.message}
17
+ `);let t=Math.min(3e4,500*1.5**this.reconnectAttempts+Math.floor(Math.random()*250));this.reconnectAttempts+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},t)}handleMessage(e,t=!1){if(t){this.handleFrame(e);return}let r;try{r=JSON.parse(typeof e=="string"?e:e.toString("utf8"))}catch{return}switch(r.type){case"error":if(r.clientId)return;this.authFailure=Ft.includes(r.code),process.stderr.write(`herdr-remote host connector: relay rejected the connection: ${r.message||r.code}
18
+ `);return;case"host_ready":this.hostReady(r);return;case"client_count":this.setClientCount(r.clientCount);return;case"session_start":return this.startSession(r);case"herdr_start":return this.sessions.startHerdrFor(u(r));case"admin_tab_open":return Se(u(r),{socketPath:this.socketPath,language:this.config.ui?.language,send:n=>m(this.ws,n)});case"session_stop":{let n=u(r);n&&this.sessions.stop(n);return}case"resize":this.sessions.resize(r);return;case"paste_file":this.savePaste(u(r),r);return;case"host_font_chunk_request":this.font.sendChunk(u(r),r);return;case"host_font_subset_request":this.font.sendSubset(u(r),r);return;case"host_font_refresh":this.font.refresh();return;default:return}}handleFrame(e){let t;try{t=W(e)}catch(r){process.stderr.write(`herdr-remote host connector: invalid relay frame: ${r.message}
19
+ `);return}t.type==="input"&&this.sessions.write(t.version===2?{streamIndex:t.streamIndex}:{streamId:t.streamId},t.payload)}hostReady(e){if(this.reconnectAttempts=0,this.startKeepalive(),Object.hasOwn(e,"clientCount")){this.legacyHeartbeat=!1,this.setClientCount(e.clientCount);return}this.legacyHeartbeat=!0,this.sendHeartbeat(!0),this.heartbeatTimer=setInterval(()=>this.sendHeartbeat(),this.config.cleanup.heartbeatIntervalMs)}startSession(e){if(u(e))return this.updates.report(),this.font.pickUp(),this.sessions.start(e)}savePaste(e,t){try{let r=Ie({mime:t.mime,dataBase64:t.dataBase64});m(this.ws,{type:"paste_file_ready",clientId:e,path:r})}catch(r){m(this.ws,{type:"error",clientId:e,code:"paste_file_write_failed",message:r.message||"Failed to save pasted file"})}}startKeepalive(){if(this.stopKeepalive(),!this.ws||this.stopping)return;this.ws.isAlive=!0;let e=this.config?.cleanup?.heartbeatIntervalMs||3e4;this.keepaliveTimer=setInterval(()=>this.tickKeepalive(),e)}stopKeepalive(){this.keepaliveTimer&&(clearInterval(this.keepaliveTimer),this.keepaliveTimer=null)}tickKeepalive(){let e=this.ws;if(!(!e||this.stopping)){if(e.readyState===d.default.CLOSING){U(e);return}if(e.readyState===d.default.OPEN){if(!e.isAlive){U(e);return}e.isAlive=!1;try{typeof e.ping=="function"&&e.ping()}catch{}this.clientCount<=0&&!this.legacyHeartbeat&&m(e,{type:"heartbeat",load:{},ptys:[]})}}}setClientCount(e){let t=Number.isInteger(e)?Math.max(0,e):0;if(t===this.clientCount&&(t===0||this.heartbeatTimer))return;let r=this.clientCount>0;this.clientCount=t,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null,t>0?(this.sendHeartbeat(!0),this.heartbeatTimer=setInterval(()=>this.sendHeartbeat(),this.config.cleanup.heartbeatIntervalMs)):r&&this.sendHeartbeat(!0),t>0&&!this.stopping?this.agents.start():this.agents.stop()}sendHeartbeat(e=!1){if(!e&&this.clientCount<=0&&!this.legacyHeartbeat)return;let t=process.memoryUsage(),r=Pt.loadavg();m(this.ws,{type:"heartbeat",load:{load1m:r[0]||0,load5m:r[1]||0,load15m:r[2]||0,rssBytes:t.rss,heapUsedBytes:t.heapUsed},ptys:this.sessions.summaries()})}};var Ue=new x({onFatal:s=>process.exit(s)});try{Ue.start()}catch(s){let{code:e,message:t}=s;process.stderr.write(`herdr-remote host connector failed: ${t}
20
+ `),process.exitCode=e==="HOST_ALREADY_RUNNING"?12:1}var $e=()=>{Ue.stop(),process.exit(0)};process.once("SIGINT",$e);process.once("SIGTERM",$e);
Binary file
@@ -0,0 +1,17 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ try {
5
+ const root = path.dirname(createRequire(import.meta.url).resolve('node-pty/package.json'));
6
+ const prebuilds = path.join(root, 'prebuilds');
7
+ const own = process.platform + '-' + process.arch;
8
+ for (const name of fs.existsSync(prebuilds) ? fs.readdirSync(prebuilds) : []) {
9
+ const directory = path.join(prebuilds, name);
10
+ if (name !== own) fs.rmSync(directory, { recursive: true, force: true });
11
+ else
12
+ for (const file of fs.readdirSync(directory, { recursive: true }))
13
+ if (String(file).endsWith('.pdb')) fs.rmSync(path.join(directory, String(file)), { force: true });
14
+ }
15
+ } catch (error) {
16
+ process.stderr.write('herdr-remote: could not prune node-pty prebuilds: ' + error.message + '\n');
17
+ }
package/herdr-plugin.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  id = "herdr.remote.web"
2
2
  name = "Herdr Remote Web"
3
- version = "0.2.39"
3
+ version = "0.3.32"
4
4
  min_herdr_version = "0.9.3"
5
5
  description = "Browser access to Herdr workspaces via local or self-hosted relay"
6
6
  platforms = ["linux", "macos", "windows"]