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
package/dist/paths.js DELETED
@@ -1,74 +0,0 @@
1
- // Where herdr-remote keeps its files: the package itself, the configuration
2
- // directory and the state directory.
3
- import fs from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- /**
7
- * Locate the package directory by walking up to our own package.json.
8
- *
9
- * This module runs from src/ under the tests and from dist/ once built.
10
- * Everything that matters — the plugin manifest, the host connector entry
11
- * point, the systemd unit's ExecStart — is resolved from this value, so it has
12
- * to be independent of where the code happens to be loaded from.
13
- */
14
- function findPackageRoot(start) {
15
- let directory = start;
16
- for (let depth = 0; depth < 8; depth += 1) {
17
- const candidate = path.join(directory, 'package.json');
18
- try {
19
- if (JSON.parse(fs.readFileSync(candidate, 'utf8')).name === 'herdr-remote')
20
- return directory;
21
- }
22
- catch {
23
- // Keep climbing: a missing or unrelated package.json is expected.
24
- }
25
- const parent = path.dirname(directory);
26
- if (parent === directory)
27
- break;
28
- directory = parent;
29
- }
30
- return path.resolve(start, '..');
31
- }
32
- const PACKAGE_ROOT = findPackageRoot(import.meta.dirname);
33
- /**
34
- * The canonical configuration directory.
35
- *
36
- * Deliberately NOT $HERDR_PLUGIN_CONFIG_DIR: Herdr sets that variable only when
37
- * it launches the plugin itself, so honouring it would give "herdr-remote" run
38
- * from a shell and the same tool run from a Herdr pane two different config
39
- * files. One path, one config; `migrateLegacyConfig()` imports the old one.
40
- * On Windows, configuration roams under `APPDATA` while state and logs stay
41
- * local under `LOCALAPPDATA`, following Windows and Herdr's directory layout.
42
- */
43
- function configDir(env = process.env, platform = process.platform) {
44
- if (env.HERDR_REMOTE_CONFIG_DIR)
45
- return env.HERDR_REMOTE_CONFIG_DIR;
46
- if (platform === 'win32') {
47
- return path.join(env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'herdr-remote');
48
- }
49
- return path.join(os.homedir(), '.config', 'herdr-remote');
50
- }
51
- function stateDir(env = process.env, platform = process.platform) {
52
- if (env.HERDR_REMOTE_STATE_DIR)
53
- return env.HERDR_REMOTE_STATE_DIR;
54
- if (platform === 'win32') {
55
- return path.join(env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'herdr-remote');
56
- }
57
- return path.join(os.homedir(), '.local', 'state', 'herdr-remote');
58
- }
59
- function configPath() {
60
- return path.join(configDir(), 'config.json');
61
- }
62
- function runtimeStatePath() {
63
- return path.join(stateDir(), 'runtime.json');
64
- }
65
- /** Where a service (relay, host, supervisor) writes its output. */
66
- function logPath(name) {
67
- return path.join(stateDir(), `${name}.log`);
68
- }
69
- function legacyConfigPath() {
70
- return process.env.HERDR_PLUGIN_CONFIG_DIR
71
- ? path.join(process.env.HERDR_PLUGIN_CONFIG_DIR, 'config.json')
72
- : null;
73
- }
74
- export { PACKAGE_ROOT, configDir, stateDir, configPath, runtimeStatePath, logPath, legacyConfigPath, };
@@ -1,133 +0,0 @@
1
- import os from 'node:os';
2
- import { nodePty } from './node-pty-loader.js';
3
- /**
4
- * Variables that name the terminal this process was started from. The Herdr
5
- * client's outer terminal is the browser's xterm.js, and from Herdr 0.9.2 the
6
- * client reads these to pick image transports: one that believes it runs in
7
- * Ghostty, kitty or WezTerm hands images over as local file paths no browser
8
- * can open. Herdr strips the same list from its own panes.
9
- */
10
- const OUTER_TERMINAL_ENV = [
11
- 'TERM_PROGRAM',
12
- 'TERM_PROGRAM_VERSION',
13
- 'ITERM_SESSION_ID',
14
- 'LC_TERMINAL',
15
- 'LC_TERMINAL_VERSION',
16
- 'WEZTERM_PANE',
17
- 'KITTY_WINDOW_ID',
18
- 'WT_SESSION',
19
- 'TMUX',
20
- 'TMUX_PANE',
21
- 'STY',
22
- 'ZELLIJ',
23
- 'ZELLIJ_SESSION_NAME',
24
- 'ZELLIJ_PANE_ID',
25
- ];
26
- class PtySession {
27
- static DEFAULT_COLS = 100;
28
- static DEFAULT_ROWS = 30;
29
- static MIN_DIMENSION = 2;
30
- static MAX_DIMENSION = 500;
31
- command;
32
- args;
33
- cwd;
34
- socketPath;
35
- platform;
36
- fastWindowsPty;
37
- spawnPty;
38
- terminal;
39
- startedAt;
40
- constructor({ command, args = [], cwd = os.homedir(), socketPath, platform = process.platform, spawn: spawnPty = nodePty().spawn, fastWindowsPty = false, } = {}) {
41
- if (typeof command !== 'string' || command.length === 0)
42
- throw new TypeError('command must be a non-empty string');
43
- if (!Array.isArray(args) || !args.every((arg) => typeof arg === 'string'))
44
- throw new TypeError('args must be strings');
45
- this.command = command;
46
- this.args = args;
47
- this.cwd = cwd;
48
- this.socketPath = socketPath;
49
- this.platform = platform;
50
- this.fastWindowsPty = fastWindowsPty;
51
- this.spawnPty = spawnPty;
52
- this.terminal = null;
53
- this.startedAt = null;
54
- }
55
- static childEnv(socketPath, source = process.env) {
56
- const env = { ...source };
57
- for (const key of Object.keys(env)) {
58
- if (key.startsWith('HERDR_'))
59
- delete env[key];
60
- }
61
- for (const key of OUTER_TERMINAL_ENV)
62
- delete env[key];
63
- if (socketPath)
64
- env.HERDR_SOCKET_PATH = socketPath;
65
- env.TERM = 'xterm-256color';
66
- env.COLORTERM = 'truecolor';
67
- return env;
68
- }
69
- static clampDimension(value, fallback) {
70
- const numeric = Number(value);
71
- if (!Number.isInteger(numeric))
72
- return fallback;
73
- return Math.min(PtySession.MAX_DIMENSION, Math.max(PtySession.MIN_DIMENSION, numeric));
74
- }
75
- start({ cols, rows, onData, onExit }) {
76
- if (this.terminal)
77
- throw new Error('PTY session already started');
78
- // The Herdr UI needs the bundled ConPTY's Kitty and mouse reporting.
79
- this.terminal = this.spawnPty(this.command, this.args, {
80
- name: 'xterm-256color',
81
- cols: PtySession.clampDimension(cols, PtySession.DEFAULT_COLS),
82
- rows: PtySession.clampDimension(rows, PtySession.DEFAULT_ROWS),
83
- cwd: this.cwd,
84
- env: PtySession.childEnv(this.socketPath),
85
- ...(this.platform === 'win32'
86
- ? this.fastWindowsPty
87
- ? { useConpty: false }
88
- : { useConptyDll: true }
89
- : {}),
90
- });
91
- this.startedAt = new Date().toISOString();
92
- const terminal = this.terminal;
93
- if (onData)
94
- terminal.onData(onData);
95
- if (onExit) {
96
- terminal.onExit((event) => {
97
- this.terminal = null;
98
- onExit(event);
99
- });
100
- }
101
- return this;
102
- }
103
- write(data) {
104
- if (!this.terminal)
105
- return;
106
- this.terminal.write(Buffer.isBuffer(data) ? data.toString('utf8') : String(data));
107
- }
108
- resize(cols, rows) {
109
- if (!this.terminal)
110
- return;
111
- this.terminal.resize(PtySession.clampDimension(cols, PtySession.DEFAULT_COLS), PtySession.clampDimension(rows, PtySession.DEFAULT_ROWS));
112
- }
113
- info() {
114
- return {
115
- pid: this.terminal?.pid || null,
116
- command: this.command,
117
- cols: this.terminal?.cols || null,
118
- rows: this.terminal?.rows || null,
119
- cwd: this.cwd,
120
- createdAt: this.startedAt,
121
- };
122
- }
123
- kill() {
124
- try {
125
- this.terminal?.kill();
126
- }
127
- catch {
128
- // The process has already exited.
129
- }
130
- this.terminal = null;
131
- }
132
- }
133
- export { PtySession };
@@ -1,106 +0,0 @@
1
- // Talking to a relay's HTTP API from this workstation: health, status, pairing.
2
- import http from 'node:http';
3
- import https from 'node:https';
4
- import { resolveAdminOrigin } from './relay-urls.js';
5
- import { ensureRuntime } from './runtime.js';
6
- /**
7
- * Minimal JSON client. Picks http or https from the URL: a self-hosted relay is
8
- * reached over https, while the local relay is plain http on loopback.
9
- */
10
- export function requestJson(urlString, options = {}) {
11
- return new Promise((resolve, reject) => {
12
- let url;
13
- try {
14
- url = new URL(urlString);
15
- }
16
- catch (error) {
17
- reject(error);
18
- return;
19
- }
20
- const transport = url.protocol === 'https:' ? https : http;
21
- const request = transport.request(url, {
22
- method: options.method || 'GET',
23
- headers: options.headers || {},
24
- timeout: options.timeout || 1500,
25
- }, (response) => {
26
- const chunks = [];
27
- response.on('data', (chunk) => chunks.push(chunk));
28
- response.on('end', () => {
29
- const text = Buffer.concat(chunks).toString('utf8');
30
- let body;
31
- try {
32
- body = JSON.parse(text);
33
- }
34
- catch {
35
- body = { raw: text };
36
- }
37
- const status = response.statusCode || 0;
38
- if (status >= 400) {
39
- const error = new Error(typeof body.message === 'string' ? body.message : `HTTP ${status}`);
40
- error.statusCode = status;
41
- error.body = body;
42
- reject(error);
43
- }
44
- else
45
- resolve(body);
46
- });
47
- });
48
- request.on('timeout', () => request.destroy(new Error('request timed out')));
49
- request.on('error', reject);
50
- if (options.body)
51
- request.write(options.body);
52
- request.end();
53
- });
54
- }
55
- export function healthUrl(config) {
56
- return `${resolveAdminOrigin(config)}/healthz`;
57
- }
58
- /** This workstation's own credentials, which scope a relay's answers to it. */
59
- export function hostHeaders() {
60
- const state = ensureRuntime();
61
- return { 'X-Herdr-Host-Id': state.hostId, 'X-Herdr-Host-Token': state.hostToken };
62
- }
63
- export async function waitForRelay(config, { attempts = 20, delayMs = 100 } = {}) {
64
- let lastError;
65
- for (let attempt = 0; attempt < attempts; attempt += 1) {
66
- try {
67
- return await requestJson(healthUrl(config), { timeout: 800 });
68
- }
69
- catch (error) {
70
- lastError = error;
71
- }
72
- await new Promise((resolve) => setTimeout(resolve, delayMs));
73
- }
74
- throw lastError || new Error('relay did not become ready');
75
- }
76
- export async function waitForHost(config, { attempts = 30, delayMs = 100 } = {}) {
77
- const headers = hostHeaders();
78
- const hostId = headers['X-Herdr-Host-Id'];
79
- const statusEndpoint = `${resolveAdminOrigin(config)}/api/status`;
80
- let lastStatus = null;
81
- for (let attempt = 0; attempt < attempts; attempt += 1) {
82
- try {
83
- lastStatus = await requestJson(statusEndpoint, {
84
- timeout: 800,
85
- headers,
86
- });
87
- if (lastStatus.hosts?.some((host) => host.id === hostId))
88
- return lastStatus;
89
- }
90
- catch (error) {
91
- lastStatus = { ok: false, message: error.message };
92
- }
93
- await new Promise((resolve) => setTimeout(resolve, delayMs));
94
- }
95
- const error = new Error(lastStatus?.message || 'the Herdr host connector did not register with the relay');
96
- error.health = lastStatus;
97
- throw error;
98
- }
99
- export function extractPairingCode(response) {
100
- const answer = response;
101
- const code = answer?.code || answer?.pairCode;
102
- if (typeof code !== 'string' || code.length === 0) {
103
- throw new Error('the relay response did not contain a valid pairing code');
104
- }
105
- return code;
106
- }
@@ -1,99 +0,0 @@
1
- // The addresses a configuration implies: where the local relay binds, the URL
2
- // a browser opens, and the relay URLs the host connector and the CLI dial.
3
- import { WS_HOST_PATH } from 'herdr-remote-relay/protocol';
4
- function isLoopbackHost(value) {
5
- return value === '127.0.0.1' || value === 'localhost' || value === '::1' || value === '[::1]';
6
- }
7
- function isUnspecifiedAddress(value) {
8
- const address = typeof value === 'string' ? value.trim().toLowerCase() : '';
9
- return address === '0.0.0.0' || address === '::' || address === '[::]';
10
- }
11
- /** An unspecified host is valid for a server bind, but not for a browser URL. */
12
- function isUnspecifiedHost(value) {
13
- try {
14
- const hostname = new URL(value).hostname.toLowerCase();
15
- return hostname === '0.0.0.0' || hostname === '::' || hostname === '[::]';
16
- }
17
- catch {
18
- return false;
19
- }
20
- }
21
- function normalizeUrl(value) {
22
- return typeof value === 'string' ? value.trim().replace(/\/+$/, '') : '';
23
- }
24
- /** Whether this machine runs its own relay process. */
25
- function runsLocalRelay(config) {
26
- return config.relay.mode !== 'remote';
27
- }
28
- /** Address the local relay binds to for the configured mode. */
29
- function bindAddress(config) {
30
- return config.relay.mode === 'lan' ? '0.0.0.0' : '127.0.0.1';
31
- }
32
- /**
33
- * Host name to put into pairing URLs. For "lan" this is the interface address a
34
- * phone can actually reach; `lanHost` is the user's pick from the TUI, and the
35
- * fallback keeps things working if that interface disappeared.
36
- */
37
- function advertisedHost(config, fallbackLanHost = null) {
38
- if (config.relay.mode === 'lan') {
39
- const configured = config.relay.lanHost;
40
- if (configured && !isLoopbackHost(configured) && !isUnspecifiedAddress(configured))
41
- return configured;
42
- if (fallbackLanHost &&
43
- !isLoopbackHost(fallbackLanHost) &&
44
- !isUnspecifiedAddress(fallbackLanHost)) {
45
- return fallbackLanHost;
46
- }
47
- return '127.0.0.1';
48
- }
49
- return '127.0.0.1';
50
- }
51
- /** The URL a browser opens. */
52
- function resolvePublicUrl(config, fallbackLanHost = null) {
53
- if (config.relay.publicUrl)
54
- return config.relay.publicUrl;
55
- if (config.relay.mode === 'remote')
56
- return httpOrigin(config.relay.remoteUrl);
57
- return `http://${advertisedHost(config, fallbackLanHost)}:${config.relay.port}`;
58
- }
59
- /** Base HTTP origin used for relay admin calls (pairing, health). */
60
- function resolveAdminOrigin(config) {
61
- if (config.relay.mode === 'remote')
62
- return httpOrigin(config.relay.remoteUrl);
63
- return `http://127.0.0.1:${config.relay.port}`;
64
- }
65
- /** WebSocket URL the host connector dials. */
66
- function resolveHostRelayUrl(config) {
67
- const base = config.relay.mode === 'remote' ? config.relay.remoteUrl : `ws://127.0.0.1:${config.relay.port}`;
68
- return hostWebSocketUrl(base);
69
- }
70
- function httpOrigin(value) {
71
- try {
72
- const url = new URL(value);
73
- if (url.protocol === 'ws:')
74
- url.protocol = 'http:';
75
- if (url.protocol === 'wss:')
76
- url.protocol = 'https:';
77
- const pathname = url.pathname.replace(/\/+$/, '');
78
- return `${url.origin}${pathname}`;
79
- }
80
- catch {
81
- return normalizeUrl(value);
82
- }
83
- }
84
- function hostWebSocketUrl(base) {
85
- const url = new URL(base);
86
- if (url.protocol === 'http:')
87
- url.protocol = 'ws:';
88
- if (url.protocol === 'https:')
89
- url.protocol = 'wss:';
90
- const pathname = url.pathname.replace(/\/+$/, '');
91
- if (!pathname || pathname === '/') {
92
- url.pathname = WS_HOST_PATH;
93
- }
94
- else if (!pathname.endsWith(WS_HOST_PATH)) {
95
- url.pathname = `${pathname}${WS_HOST_PATH}`;
96
- }
97
- return url.toString();
98
- }
99
- export { isLoopbackHost, isUnspecifiedAddress, isUnspecifiedHost, normalizeUrl, runsLocalRelay, bindAddress, advertisedHost, resolvePublicUrl, resolveAdminOrigin, resolveHostRelayUrl, hostWebSocketUrl, };
package/dist/runtime.js DELETED
@@ -1,107 +0,0 @@
1
- // runtime.json: this workstation's relay credentials, the processes started
2
- // for it, and facts other modules remember there (terminal palette and font).
3
- // Every read and write of that file goes through here.
4
- import { ensureDir, randomToken, readJson, writeJsonAtomic } from 'herdr-remote-relay/state';
5
- import { processStart, stillRunning } from './lib/process.js';
6
- import { configDir, runtimeStatePath, stateDir } from './paths.js';
7
- const RUNTIME_VERSION = 2;
8
- export function readRuntime() {
9
- const state = readJson(runtimeStatePath(), {});
10
- return state && typeof state === 'object' ? state : {};
11
- }
12
- export function writeRuntime(state) {
13
- writeJsonAtomic(runtimeStatePath(), state);
14
- }
15
- /** Read, change and write back in one step, so no field another writer set is lost. */
16
- export function updateRuntime(change) {
17
- const state = readRuntime();
18
- change(state);
19
- writeRuntime(state);
20
- return state;
21
- }
22
- /**
23
- * Load (creating if needed) the long-lived identifiers and secrets this
24
- * workstation uses. These live in the state directory rather than config.json
25
- * because they are credentials: writeJsonAtomic stores them mode 0600.
26
- */
27
- export function ensureRuntime() {
28
- ensureDir(configDir());
29
- ensureDir(stateDir());
30
- return updateRuntime((state) => {
31
- if (!state.version)
32
- state.version = RUNTIME_VERSION;
33
- if (!state.hostId)
34
- state.hostId = `host-${randomToken(9)}`;
35
- if (!state.hostToken)
36
- state.hostToken = randomToken(32);
37
- });
38
- }
39
- /**
40
- * Set the password used to join a relay. An empty value means "no password",
41
- * which is what a public relay expects.
42
- *
43
- * It lives in the state file rather than config.json because it is a secret:
44
- * writeJsonAtomic stores that file mode 0600.
45
- */
46
- export function setRelayPassword(password) {
47
- const state = ensureRuntime();
48
- state.relayPassword = typeof password === 'string' ? password.trim() : '';
49
- writeRuntime(state);
50
- return state;
51
- }
52
- /**
53
- * Issue this workstation a new identity on the relay.
54
- *
55
- * The host token is what proves ownership of this workstation, so replacing it
56
- * means the relay no longer recognises the old one — useful if it leaked, but
57
- * it also orphans the previous record on a relay that already enrolled it.
58
- */
59
- export function regenerateHostIdentity() {
60
- const state = ensureRuntime();
61
- state.hostId = `host-${randomToken(9)}`;
62
- state.hostToken = randomToken(32);
63
- writeRuntime(state);
64
- return state;
65
- }
66
- /**
67
- * Remember a process we started, in a list that survives being overwritten.
68
- *
69
- * `relayPid`/`hostPid` alone were not enough: the supervisor rewrote them with
70
- * its own children, which orphaned anything an earlier detached start had
71
- * spawned. Those orphans kept the port and the host session, so the relay could
72
- * never bind and "stop" had nothing left to kill. The ledger is append-only
73
- * (minus dead entries) precisely so no writer can lose another writer's pids.
74
- */
75
- export function recordManagedPid(state, name, pid) {
76
- const existing = Array.isArray(state.managedPids) ? state.managedPids.filter(Boolean) : [];
77
- const kept = stillRunning(existing.filter((entry) => entry.pid !== pid));
78
- // Only track something that is actually running, and together with its start
79
- // token: the ledger outlives a reboot, and a bare pid it kept would then name
80
- // whichever unrelated process got that number next — which we would signal.
81
- const start = Number.isInteger(pid) ? processStart(pid) : null;
82
- if (start) {
83
- kept.push({
84
- name,
85
- pid: pid,
86
- processStart: start,
87
- startedAt: new Date().toISOString(),
88
- });
89
- }
90
- state.managedPids = kept;
91
- return state;
92
- }
93
- /**
94
- * Every process we started that is still running, and still that process.
95
- *
96
- * Only the ledger counts. `supervisorPid`, `relayPid` and `hostPid` are bare
97
- * numbers that survive a reboot, and trusting them would signal whichever
98
- * unrelated process inherited one.
99
- */
100
- export function managedPids(state = readRuntime()) {
101
- const unique = new Map();
102
- for (const entry of state.managedPids || []) {
103
- if (entry && !unique.has(entry.pid))
104
- unique.set(entry.pid, entry);
105
- }
106
- return stillRunning([...unique.values()]);
107
- }