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.
- package/README.md +4 -2
- package/README.zh-CN.md +2 -2
- package/dist/chunks/3FQAXVGC.js +3 -0
- package/dist/chunks/42KARAL4.js +31 -0
- package/dist/chunks/44IGVPMA.js +5 -0
- package/dist/chunks/5LZFJ6UZ.js +3 -0
- package/dist/chunks/7SMZ43LI.js +9 -0
- package/dist/chunks/AX4EPJCP.js +4 -0
- package/dist/chunks/B76DFI4H.js +16 -0
- package/dist/chunks/BYGDPZZD.js +3 -0
- package/dist/chunks/C2UUZY6J.js +2 -0
- package/dist/chunks/CVJ4HBMP.js +3 -0
- package/dist/chunks/DGUZCGQN.js +3 -0
- package/dist/chunks/FONUO5O5.js +4 -0
- package/dist/chunks/H24ZP5D5.js +16 -0
- package/dist/chunks/I6NRCYLI.js +3 -0
- package/dist/chunks/JJGEV4EZ.js +3 -0
- package/dist/chunks/K5RFPHSF.js +5 -0
- package/dist/chunks/KGOQKOHH.js +5 -0
- package/dist/chunks/KRVF37OW.js +3 -0
- package/dist/chunks/KUDCC7ZO.js +6 -0
- package/dist/chunks/LPR662JH.js +77 -0
- package/dist/chunks/LY2ZGCXN.js +4 -0
- package/dist/chunks/M52LJEES.js +6 -0
- package/dist/chunks/MCUVFSSK.js +3 -0
- package/dist/chunks/MEFDV375.js +10 -0
- package/dist/chunks/MKAEXKXG.js +10 -0
- package/dist/chunks/O2LNVFUT.js +3 -0
- package/dist/chunks/PBMBFWLQ.js +3 -0
- package/dist/chunks/PBRQ3HEG.js +3 -0
- package/dist/chunks/QCSILMFB.js +3 -0
- package/dist/chunks/QRGNEKME.js +5 -0
- package/dist/chunks/RMHH3IAX.js +63 -0
- package/dist/chunks/TNLCNVA2.js +3 -0
- package/dist/chunks/U6QAYG3E.js +3 -0
- package/dist/chunks/VD54DF7R.js +45 -0
- package/dist/chunks/WY7WHEJ4.js +3 -0
- package/dist/cli.js +21 -294
- package/dist/connector/admin-broker.js +3 -303
- package/dist/connector/main.js +20 -18
- package/dist/harfbuzz-subset.wasm +0 -0
- package/dist/postinstall.js +17 -0
- package/herdr-plugin.toml +1 -1
- package/node_modules/herdr-remote-relay/bin/herdr-remote-relay.js +33 -92
- package/node_modules/herdr-remote-relay/package.json +3 -46
- package/package.json +8 -27
- package/dist/agent-status.js +0 -130
- package/dist/binary-plist.js +0 -157
- package/dist/config.js +0 -278
- package/dist/connector/admin-broker-setup.js +0 -40
- package/dist/connector/admin-shell.js +0 -67
- package/dist/connector/admin-tab.js +0 -90
- package/dist/connector/adminBrokerEndpoint.js +0 -68
- package/dist/connector/agent-watch.js +0 -123
- package/dist/connector/elevatedPty.js +0 -141
- package/dist/connector/font-server.js +0 -165
- package/dist/connector/herdr-prefix-keys.js +0 -28
- package/dist/connector/host-connector.js +0 -433
- package/dist/connector/host-hello.js +0 -32
- package/dist/connector/lock.js +0 -73
- package/dist/connector/sessions.js +0 -381
- package/dist/connector/update-report.js +0 -70
- package/dist/connector/wire.js +0 -42
- package/dist/exit-codes.js +0 -13
- package/dist/font-subset.js +0 -116
- package/dist/herdr-api.js +0 -254
- package/dist/herdr-command.js +0 -245
- package/dist/herdr-plugin.js +0 -120
- package/dist/herdr-server.js +0 -190
- package/dist/i18n/en.js +0 -257
- package/dist/i18n/index.js +0 -48
- package/dist/i18n/zh.js +0 -256
- package/dist/keepalive/detached.js +0 -97
- package/dist/keepalive/environment.js +0 -44
- package/dist/keepalive/index.js +0 -129
- package/dist/keepalive/launchd.js +0 -117
- package/dist/keepalive/systemd.js +0 -112
- package/dist/keepalive/types.js +0 -1
- package/dist/keepalive/windows-service-setup.js +0 -138
- package/dist/keepalive/windows-service.js +0 -272
- package/dist/keepalive/windows-task.js +0 -191
- package/dist/lib/process.js +0 -132
- package/dist/lifecycle.js +0 -79
- package/dist/net-interfaces.js +0 -82
- package/dist/node-pty-loader.js +0 -80
- package/dist/pasted-files.js +0 -115
- package/dist/paths.js +0 -74
- package/dist/pty-session.js +0 -133
- package/dist/relay-client.js +0 -106
- package/dist/relay-urls.js +0 -99
- package/dist/runtime.js +0 -107
- package/dist/service.js +0 -267
- package/dist/settings-model.js +0 -298
- package/dist/socket-discovery.js +0 -70
- package/dist/supervisor.js +0 -323
- package/dist/terminal-font/deps.js +0 -83
- package/dist/terminal-font/files.js +0 -357
- package/dist/terminal-font/formats.js +0 -355
- package/dist/terminal-font/index.js +0 -187
- package/dist/terminal-font/readers.js +0 -373
- package/dist/terminal-font/terminals.js +0 -147
- package/dist/terminal-palette.js +0 -399
- package/dist/tui/App.js +0 -199
- package/dist/tui/api.js +0 -91
- package/dist/tui/components/TextField.js +0 -50
- package/dist/tui/components/common.js +0 -80
- package/dist/tui/index.js +0 -36
- package/dist/tui/mouse/index.js +0 -164
- package/dist/tui/mouse/protocol.js +0 -96
- package/dist/tui/mouse/source.js +0 -79
- package/dist/tui/screens/About.js +0 -172
- package/dist/tui/screens/Herdr.js +0 -75
- package/dist/tui/screens/Keepalive.js +0 -141
- package/dist/tui/screens/Overview.js +0 -36
- package/dist/tui/screens/Pair.js +0 -74
- package/dist/tui/screens/Relay.js +0 -243
- package/dist/tui/screens/Services.js +0 -63
- package/dist/tui/screens/Wizard.js +0 -241
- package/dist/tui/terminal.js +0 -39
- package/dist/tui/theme.js +0 -33
- package/dist/update-command.js +0 -118
- package/dist/updater/in-place.js +0 -83
- package/dist/updater/npm.js +0 -62
- package/dist/updater.js +0 -401
- package/node_modules/herdr-remote-relay/README.md +0 -76
- package/node_modules/herdr-remote-relay/README.zh-CN.md +0 -76
- package/node_modules/herdr-remote-relay/config.example.json +0 -18
- package/node_modules/herdr-remote-relay/deploy/cloudflared/config.yml.example +0 -9
- package/node_modules/herdr-remote-relay/deploy/docker/compose.yaml +0 -23
- package/node_modules/herdr-remote-relay/deploy/nginx/herdr-remote-relay.conf.example +0 -49
- package/node_modules/herdr-remote-relay/deploy/systemd/herdr-remote-relay.service +0 -41
- package/node_modules/herdr-remote-relay/deploy/systemd/relay.env.example +0 -25
- package/node_modules/herdr-remote-relay/dist/auth-store.d.ts +0 -114
- package/node_modules/herdr-remote-relay/dist/auth-store.js +0 -289
- package/node_modules/herdr-remote-relay/dist/metrics.d.ts +0 -76
- package/node_modules/herdr-remote-relay/dist/metrics.js +0 -249
- package/node_modules/herdr-remote-relay/dist/protocol/frames.d.ts +0 -27
- package/node_modules/herdr-remote-relay/dist/protocol/frames.js +0 -110
- package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.d.ts +0 -8
- package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.js +0 -34
- package/node_modules/herdr-remote-relay/dist/protocol/http.d.ts +0 -165
- package/node_modules/herdr-remote-relay/dist/protocol/http.js +0 -3
- package/node_modules/herdr-remote-relay/dist/protocol/index.d.ts +0 -7
- package/node_modules/herdr-remote-relay/dist/protocol/index.js +0 -9
- package/node_modules/herdr-remote-relay/dist/protocol/messages.d.ts +0 -362
- package/node_modules/herdr-remote-relay/dist/protocol/messages.js +0 -20
- package/node_modules/herdr-remote-relay/dist/protocol/paste.d.ts +0 -14
- package/node_modules/herdr-remote-relay/dist/protocol/paste.js +0 -50
- package/node_modules/herdr-remote-relay/dist/protocol/release.d.ts +0 -8
- package/node_modules/herdr-remote-relay/dist/protocol/release.js +0 -17
- package/node_modules/herdr-remote-relay/dist/protocol/terminal.d.ts +0 -79
- package/node_modules/herdr-remote-relay/dist/protocol/terminal.js +0 -150
- package/node_modules/herdr-remote-relay/dist/relay-config.d.ts +0 -74
- package/node_modules/herdr-remote-relay/dist/relay-config.js +0 -316
- package/node_modules/herdr-remote-relay/dist/relay-server.d.ts +0 -67
- package/node_modules/herdr-remote-relay/dist/relay-server.js +0 -196
- package/node_modules/herdr-remote-relay/dist/scroll-input.d.ts +0 -17
- package/node_modules/herdr-remote-relay/dist/scroll-input.js +0 -124
- package/node_modules/herdr-remote-relay/dist/server/client-channel.d.ts +0 -3
- package/node_modules/herdr-remote-relay/dist/server/client-channel.js +0 -396
- package/node_modules/herdr-remote-relay/dist/server/font-proxy.d.ts +0 -19
- package/node_modules/herdr-remote-relay/dist/server/font-proxy.js +0 -155
- package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.d.ts +0 -27
- package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.js +0 -83
- package/node_modules/herdr-remote-relay/dist/server/host-channel.d.ts +0 -3
- package/node_modules/herdr-remote-relay/dist/server/host-channel.js +0 -271
- package/node_modules/herdr-remote-relay/dist/server/http-api.d.ts +0 -4
- package/node_modules/herdr-remote-relay/dist/server/http-api.js +0 -219
- package/node_modules/herdr-remote-relay/dist/server/maintenance.d.ts +0 -5
- package/node_modules/herdr-remote-relay/dist/server/maintenance.js +0 -64
- package/node_modules/herdr-remote-relay/dist/server/requests.d.ts +0 -34
- package/node_modules/herdr-remote-relay/dist/server/requests.js +0 -142
- package/node_modules/herdr-remote-relay/dist/server/sessions.d.ts +0 -36
- package/node_modules/herdr-remote-relay/dist/server/sessions.js +0 -230
- package/node_modules/herdr-remote-relay/dist/server/sockets.d.ts +0 -12
- package/node_modules/herdr-remote-relay/dist/server/sockets.js +0 -64
- package/node_modules/herdr-remote-relay/dist/server/status.d.ts +0 -11
- package/node_modules/herdr-remote-relay/dist/server/status.js +0 -99
- package/node_modules/herdr-remote-relay/dist/server/transport.d.ts +0 -16
- package/node_modules/herdr-remote-relay/dist/server/transport.js +0 -110
- package/node_modules/herdr-remote-relay/dist/server/types.d.ts +0 -133
- package/node_modules/herdr-remote-relay/dist/server/types.js +0 -3
- package/node_modules/herdr-remote-relay/dist/server/version.d.ts +0 -2
- package/node_modules/herdr-remote-relay/dist/server/version.js +0 -5
- package/node_modules/herdr-remote-relay/dist/state.d.ts +0 -6
- package/node_modules/herdr-remote-relay/dist/state.js +0 -47
- package/node_modules/ws/LICENSE +0 -20
- package/node_modules/ws/README.md +0 -548
- package/node_modules/ws/browser.js +0 -8
- package/node_modules/ws/index.js +0 -22
- package/node_modules/ws/lib/buffer-util.js +0 -131
- package/node_modules/ws/lib/constants.js +0 -19
- package/node_modules/ws/lib/event-target.js +0 -292
- package/node_modules/ws/lib/extension.js +0 -203
- package/node_modules/ws/lib/limiter.js +0 -55
- package/node_modules/ws/lib/permessage-deflate.js +0 -530
- package/node_modules/ws/lib/receiver.js +0 -743
- package/node_modules/ws/lib/sender.js +0 -607
- package/node_modules/ws/lib/stream.js +0 -161
- package/node_modules/ws/lib/subprotocol.js +0 -62
- package/node_modules/ws/lib/validation.js +0 -152
- package/node_modules/ws/lib/websocket-server.js +0 -562
- package/node_modules/ws/lib/websocket.js +0 -1407
- package/node_modules/ws/package.json +0 -74
- package/node_modules/ws/wrapper.mjs +0 -21
|
@@ -1,303 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import
|
|
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};
|
package/dist/connector/main.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
try {
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
|
|
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