@kdonev/termscape 0.1.0
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/LICENSE +21 -0
- package/README.md +288 -0
- package/dist/agents/env.d.ts +36 -0
- package/dist/agents/env.d.ts.map +1 -0
- package/dist/agents/env.js +61 -0
- package/dist/agents/env.js.map +1 -0
- package/dist/agents/profiles.d.ts +45 -0
- package/dist/agents/profiles.d.ts.map +1 -0
- package/dist/agents/profiles.js +123 -0
- package/dist/agents/profiles.js.map +1 -0
- package/dist/agents/resolve.d.ts +16 -0
- package/dist/agents/resolve.d.ts.map +1 -0
- package/dist/agents/resolve.js +71 -0
- package/dist/agents/resolve.js.map +1 -0
- package/dist/agents/router.d.ts +29 -0
- package/dist/agents/router.d.ts.map +1 -0
- package/dist/agents/router.js +83 -0
- package/dist/agents/router.js.map +1 -0
- package/dist/agents/tokens.d.ts +21 -0
- package/dist/agents/tokens.d.ts.map +1 -0
- package/dist/agents/tokens.js +39 -0
- package/dist/agents/tokens.js.map +1 -0
- package/dist/agents/wiring.d.ts +30 -0
- package/dist/agents/wiring.d.ts.map +1 -0
- package/dist/agents/wiring.js +110 -0
- package/dist/agents/wiring.js.map +1 -0
- package/dist/browser.d.ts +13 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +34 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +200 -0
- package/dist/cli.js.map +1 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +24 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +15 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +176 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/store.d.ts +71 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +341 -0
- package/dist/db/store.js.map +1 -0
- package/dist/hub.d.ts +188 -0
- package/dist/hub.d.ts.map +1 -0
- package/dist/hub.js +636 -0
- package/dist/hub.js.map +1 -0
- package/dist/hub.tgz +0 -0
- package/dist/mcp/server.d.ts +31 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +66 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/paths.d.ts +27 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +31 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/address.d.ts +15 -0
- package/dist/protocol/address.d.ts.map +1 -0
- package/dist/protocol/address.js +39 -0
- package/dist/protocol/address.js.map +1 -0
- package/dist/protocol/domain.d.ts +151 -0
- package/dist/protocol/domain.d.ts.map +1 -0
- package/dist/protocol/domain.js +104 -0
- package/dist/protocol/domain.js.map +1 -0
- package/dist/protocol/index.d.ts +7 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +7 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/inject.d.ts +29 -0
- package/dist/protocol/inject.d.ts.map +1 -0
- package/dist/protocol/inject.js +40 -0
- package/dist/protocol/inject.js.map +1 -0
- package/dist/protocol/mcp-tools.d.ts +45 -0
- package/dist/protocol/mcp-tools.d.ts.map +1 -0
- package/dist/protocol/mcp-tools.js +46 -0
- package/dist/protocol/mcp-tools.js.map +1 -0
- package/dist/protocol/peer.d.ts +304 -0
- package/dist/protocol/peer.d.ts.map +1 -0
- package/dist/protocol/peer.js +183 -0
- package/dist/protocol/peer.js.map +1 -0
- package/dist/protocol/ws.d.ts +416 -0
- package/dist/protocol/ws.d.ts.map +1 -0
- package/dist/protocol/ws.js +119 -0
- package/dist/protocol/ws.js.map +1 -0
- package/dist/remote/deployer.d.ts +78 -0
- package/dist/remote/deployer.d.ts.map +1 -0
- package/dist/remote/deployer.js +216 -0
- package/dist/remote/deployer.js.map +1 -0
- package/dist/remote/enroll.d.ts +14 -0
- package/dist/remote/enroll.d.ts.map +1 -0
- package/dist/remote/enroll.js +244 -0
- package/dist/remote/enroll.js.map +1 -0
- package/dist/remote/join-script.d.ts +29 -0
- package/dist/remote/join-script.d.ts.map +1 -0
- package/dist/remote/join-script.js +709 -0
- package/dist/remote/join-script.js.map +1 -0
- package/dist/remote/join.d.ts +19 -0
- package/dist/remote/join.d.ts.map +1 -0
- package/dist/remote/join.js +195 -0
- package/dist/remote/join.js.map +1 -0
- package/dist/remote/lan.d.ts +45 -0
- package/dist/remote/lan.d.ts.map +1 -0
- package/dist/remote/lan.js +105 -0
- package/dist/remote/lan.js.map +1 -0
- package/dist/remote/peer-serve.d.ts +27 -0
- package/dist/remote/peer-serve.d.ts.map +1 -0
- package/dist/remote/peer-serve.js +274 -0
- package/dist/remote/peer-serve.js.map +1 -0
- package/dist/remote/peer.d.ts +62 -0
- package/dist/remote/peer.d.ts.map +1 -0
- package/dist/remote/peer.js +250 -0
- package/dist/remote/peer.js.map +1 -0
- package/dist/remote/registry.d.ts +104 -0
- package/dist/remote/registry.d.ts.map +1 -0
- package/dist/remote/registry.js +359 -0
- package/dist/remote/registry.js.map +1 -0
- package/dist/remote/tarball.d.ts +9 -0
- package/dist/remote/tarball.d.ts.map +1 -0
- package/dist/remote/tarball.js +22 -0
- package/dist/remote/tarball.js.map +1 -0
- package/dist/server.d.ts +54 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +438 -0
- package/dist/server.js.map +1 -0
- package/dist/session/manager.d.ts +104 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +421 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/pty.d.ts +114 -0
- package/dist/session/pty.d.ts.map +1 -0
- package/dist/session/pty.js +300 -0
- package/dist/session/pty.js.map +1 -0
- package/package.json +49 -0
- package/web/assets/index-B2JtCtHa.css +1 -0
- package/web/assets/index-BOqZOoep.js +107 -0
- package/web/index.html +13 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { PeerRequest, PEER_SCHEMA_VERSION, } from '../protocol/index.js';
|
|
3
|
+
import { HUB_VERSION } from '../hub.js';
|
|
4
|
+
/**
|
|
5
|
+
* The answering half of the hub-to-hub link: it serves requests about *this*
|
|
6
|
+
* hub's sessions and pushes its state changes.
|
|
7
|
+
*
|
|
8
|
+
* It is deliberately independent of who opened the socket. An SSH-deployed hub
|
|
9
|
+
* accepts a socket on `/peer` and runs this over it; a hub that ran the join
|
|
10
|
+
* installer dials out and runs the very same thing over the socket it opened.
|
|
11
|
+
* That split is the whole reason enrollment can invert the direction of the
|
|
12
|
+
* connection without the canvas-owning hub noticing.
|
|
13
|
+
*
|
|
14
|
+
* It is also this hub's *uplink*: the one link back to the canvas it was
|
|
15
|
+
* attached to. Answering is most of what it does, but not all — it holds the
|
|
16
|
+
* directory the canvas sends of who else is out there, and it can ask the
|
|
17
|
+
* canvas to deliver a message to any of them. Without those an agent here can
|
|
18
|
+
* neither see nor reach anything beyond this machine.
|
|
19
|
+
*/
|
|
20
|
+
/** How long a relayed ask waits for the canvas to report what happened. */
|
|
21
|
+
const RELAY_TIMEOUT_MS = 15_000;
|
|
22
|
+
export function createPeerServer(hub, clientToken) {
|
|
23
|
+
const sockets = new Set();
|
|
24
|
+
/** Which addresses each peer asked to stream output for. */
|
|
25
|
+
const attached = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* Who else is on the canvas, as the canvas last described it. Replaced
|
|
28
|
+
* wholesale on every `directory`, so an agent that went away over there
|
|
29
|
+
* cannot linger here.
|
|
30
|
+
*/
|
|
31
|
+
let directory = [];
|
|
32
|
+
/** Asks sent to the canvas and still waiting for their answer. */
|
|
33
|
+
const relays = new Map();
|
|
34
|
+
const send = (ws, msg) => {
|
|
35
|
+
if (ws.readyState === ws.OPEN)
|
|
36
|
+
ws.send(JSON.stringify(msg));
|
|
37
|
+
};
|
|
38
|
+
hub.on('session', (s) => {
|
|
39
|
+
for (const ws of sockets)
|
|
40
|
+
send(ws, { t: 'sessionUpserted', session: s });
|
|
41
|
+
});
|
|
42
|
+
hub.on('removed', (_id, address) => {
|
|
43
|
+
// Peers address sessions by name, not by this hub's internal id, which is
|
|
44
|
+
// why the address travels with the event rather than being looked up
|
|
45
|
+
// after the row is already gone.
|
|
46
|
+
if (!address)
|
|
47
|
+
return;
|
|
48
|
+
for (const ws of sockets)
|
|
49
|
+
send(ws, { t: 'sessionRemoved', address });
|
|
50
|
+
});
|
|
51
|
+
hub.on('data', (sessionId, chunk) => {
|
|
52
|
+
if (sockets.size === 0)
|
|
53
|
+
return;
|
|
54
|
+
const s = hub.sessions.get(sessionId);
|
|
55
|
+
if (!s)
|
|
56
|
+
return;
|
|
57
|
+
for (const ws of sockets) {
|
|
58
|
+
if (attached.get(ws)?.has(s.address)) {
|
|
59
|
+
send(ws, { t: 'output', address: s.address, data: chunk });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
function serve(socket, opts = {}) {
|
|
64
|
+
// A dialled-out link authenticated itself in its own hello, so there is no
|
|
65
|
+
// second token to check here; an accepted one must present ours first.
|
|
66
|
+
let authed = opts.preAuthed ?? false;
|
|
67
|
+
if (authed) {
|
|
68
|
+
sockets.add(socket);
|
|
69
|
+
attached.set(socket, new Set());
|
|
70
|
+
}
|
|
71
|
+
socket.on('message', async (raw) => {
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = PeerRequest.safeParse(JSON.parse(raw.toString('utf8')));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!parsed.success)
|
|
80
|
+
return;
|
|
81
|
+
const req = parsed.data;
|
|
82
|
+
if (!authed) {
|
|
83
|
+
if (req.t !== 'hello' || req.token !== clientToken) {
|
|
84
|
+
socket.close();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
authed = true;
|
|
88
|
+
sockets.add(socket);
|
|
89
|
+
attached.set(socket, new Set());
|
|
90
|
+
send(socket, {
|
|
91
|
+
t: 'welcome',
|
|
92
|
+
hubVersion: HUB_VERSION,
|
|
93
|
+
schemaVersion: PEER_SCHEMA_VERSION,
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (req.t === 'hello')
|
|
98
|
+
return;
|
|
99
|
+
const ok = (result) => send(socket, { t: 'ok', id: req.id, result });
|
|
100
|
+
const err = (message) => send(socket, { t: 'err', id: req.id, message });
|
|
101
|
+
try {
|
|
102
|
+
switch (req.t) {
|
|
103
|
+
case 'listSessions':
|
|
104
|
+
return ok(hub.sessions.list());
|
|
105
|
+
case 'directory':
|
|
106
|
+
// Replaced wholesale: this is the canvas's whole view, minus our
|
|
107
|
+
// own agents, and a merge would keep agents it has dropped.
|
|
108
|
+
directory = req.agents;
|
|
109
|
+
return ok({ agents: req.agents.length });
|
|
110
|
+
case 'relayResult': {
|
|
111
|
+
const p = relays.get(req.relayId);
|
|
112
|
+
if (!p)
|
|
113
|
+
return ok({ unknown: req.relayId });
|
|
114
|
+
relays.delete(req.relayId);
|
|
115
|
+
clearTimeout(p.timer);
|
|
116
|
+
if (req.ok)
|
|
117
|
+
p.resolve(req.result);
|
|
118
|
+
else
|
|
119
|
+
p.reject(new Error(req.error ?? 'the canvas could not do that'));
|
|
120
|
+
return ok({ ok: true });
|
|
121
|
+
}
|
|
122
|
+
case 'deliver': {
|
|
123
|
+
// The originating hub already authenticated the sender, so its
|
|
124
|
+
// reported `from` is trusted. Attribution is still applied here,
|
|
125
|
+
// so the receiving agent sees the true origin address.
|
|
126
|
+
const r = hub.router.send(req.from, req.to, req.body);
|
|
127
|
+
return r.delivered ? ok({ delivered: true }) : err(r.error ?? 'delivery failed');
|
|
128
|
+
}
|
|
129
|
+
case 'readScreen': {
|
|
130
|
+
const target = hub.sessions.getByAddress(req.address);
|
|
131
|
+
if (!target)
|
|
132
|
+
return err(`no agent at address "${req.address}"`);
|
|
133
|
+
const pty = hub.sessions.pty(target.id);
|
|
134
|
+
return ok({
|
|
135
|
+
address: req.address,
|
|
136
|
+
running: pty?.running ?? false,
|
|
137
|
+
screen: pty ? pty.tailLines(req.lines ?? 40) : '(not running)',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
case 'startSession': {
|
|
141
|
+
let ws = hub.store.getWorkspaceByName(req.workspaceName);
|
|
142
|
+
if (!ws)
|
|
143
|
+
ws = hub.createWorkspace(req.workspaceName, req.rootPath, null);
|
|
144
|
+
const s = await hub.startSession({
|
|
145
|
+
workspaceId: ws.id,
|
|
146
|
+
profile: req.profile,
|
|
147
|
+
name: req.name,
|
|
148
|
+
});
|
|
149
|
+
return ok(s);
|
|
150
|
+
}
|
|
151
|
+
case 'shutdown':
|
|
152
|
+
// Answer first: the canvas hub is waiting, and a moment later
|
|
153
|
+
// this process is gone. Whoever owns the process lifecycle
|
|
154
|
+
// listens for this; nothing here calls process.exit, so an
|
|
155
|
+
// in-process test hub is not taken down with it.
|
|
156
|
+
ok({ stopping: true });
|
|
157
|
+
setTimeout(() => hub.emit('peerShutdown'), 50);
|
|
158
|
+
return;
|
|
159
|
+
case 'stopSession': {
|
|
160
|
+
const t = hub.sessions.getByAddress(req.address);
|
|
161
|
+
if (!t)
|
|
162
|
+
return err(`no agent at address "${req.address}"`);
|
|
163
|
+
hub.sessions.stop(t.id);
|
|
164
|
+
return ok({ stopped: req.address });
|
|
165
|
+
}
|
|
166
|
+
case 'removeSession': {
|
|
167
|
+
const t = hub.sessions.getByAddress(req.address);
|
|
168
|
+
// Already gone is the outcome that was asked for, not an error:
|
|
169
|
+
// the canvas retries deferred removals and must not stall on one
|
|
170
|
+
// that a previous attempt already applied.
|
|
171
|
+
if (!t)
|
|
172
|
+
return ok({ removed: req.address });
|
|
173
|
+
hub.sessions.remove(t.id);
|
|
174
|
+
return ok({ removed: req.address });
|
|
175
|
+
}
|
|
176
|
+
case 'resumeSession': {
|
|
177
|
+
const t = hub.sessions.getByAddress(req.address);
|
|
178
|
+
if (!t)
|
|
179
|
+
return err(`no agent at address "${req.address}"`);
|
|
180
|
+
return ok(await hub.sessions.resume(t.id));
|
|
181
|
+
}
|
|
182
|
+
case 'attach': {
|
|
183
|
+
attached.get(socket)?.add(req.address);
|
|
184
|
+
const t = hub.sessions.getByAddress(req.address);
|
|
185
|
+
if (t) {
|
|
186
|
+
const snap = hub.sessions.snapshotForAttach(t.id);
|
|
187
|
+
if (snap)
|
|
188
|
+
send(socket, { t: 'output', address: req.address, data: snap.serialized });
|
|
189
|
+
}
|
|
190
|
+
return ok({ attached: req.address });
|
|
191
|
+
}
|
|
192
|
+
case 'detach':
|
|
193
|
+
attached.get(socket)?.delete(req.address);
|
|
194
|
+
return ok({ detached: req.address });
|
|
195
|
+
case 'input': {
|
|
196
|
+
const t = hub.sessions.getByAddress(req.address);
|
|
197
|
+
if (!t)
|
|
198
|
+
return err(`no agent at address "${req.address}"`);
|
|
199
|
+
hub.sessions.write(t.id, req.data);
|
|
200
|
+
return ok({ ok: true });
|
|
201
|
+
}
|
|
202
|
+
case 'resize': {
|
|
203
|
+
const t = hub.sessions.getByAddress(req.address);
|
|
204
|
+
if (!t)
|
|
205
|
+
return err(`no agent at address "${req.address}"`);
|
|
206
|
+
hub.sessions.resize(t.id, req.cols, req.rows);
|
|
207
|
+
return ok({ ok: true });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
err(e.message);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
socket.on('close', () => {
|
|
216
|
+
sockets.delete(socket);
|
|
217
|
+
attached.delete(socket);
|
|
218
|
+
// Nothing left to answer a relay, and a caller waiting on one would
|
|
219
|
+
// otherwise sit there until the timeout for no reason.
|
|
220
|
+
if (sockets.size === 0) {
|
|
221
|
+
directory = [];
|
|
222
|
+
for (const [, p] of relays) {
|
|
223
|
+
clearTimeout(p.timer);
|
|
224
|
+
p.reject(new Error('the link to the canvas closed'));
|
|
225
|
+
}
|
|
226
|
+
relays.clear();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
serve,
|
|
232
|
+
get size() {
|
|
233
|
+
return sockets.size;
|
|
234
|
+
},
|
|
235
|
+
get attached() {
|
|
236
|
+
return sockets.size > 0;
|
|
237
|
+
},
|
|
238
|
+
agents() {
|
|
239
|
+
return sockets.size > 0 ? directory : [];
|
|
240
|
+
},
|
|
241
|
+
/**
|
|
242
|
+
* Hand something to the canvas to do. Only the canvas knows where every
|
|
243
|
+
* address on it lives, so this is the only way an agent here reaches one
|
|
244
|
+
* anywhere else — including on the machine the canvas itself runs on.
|
|
245
|
+
*/
|
|
246
|
+
ask(ask) {
|
|
247
|
+
const socket = [...sockets].find((ws) => ws.readyState === ws.OPEN);
|
|
248
|
+
if (!socket)
|
|
249
|
+
return Promise.reject(new Error('not attached to a canvas'));
|
|
250
|
+
const relayId = randomUUID();
|
|
251
|
+
return new Promise((resolve, reject) => {
|
|
252
|
+
const timer = setTimeout(() => {
|
|
253
|
+
relays.delete(relayId);
|
|
254
|
+
reject(new Error('the canvas did not answer'));
|
|
255
|
+
}, RELAY_TIMEOUT_MS);
|
|
256
|
+
relays.set(relayId, { resolve: (v) => resolve(v), reject, timer });
|
|
257
|
+
send(socket, { t: 'relay', relayId, ask });
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
closeAll() {
|
|
261
|
+
for (const ws of sockets) {
|
|
262
|
+
try {
|
|
263
|
+
ws.terminate();
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Already gone.
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
sockets.clear();
|
|
270
|
+
attached.clear();
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=peer-serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-serve.js","sourceRoot":"","sources":["../../src/remote/peer-serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { WebSocket } from 'ws';
|
|
3
|
+
import { type PeerRequest } from '../protocol/index.js';
|
|
4
|
+
export interface PeerOptions {
|
|
5
|
+
hostId: string;
|
|
6
|
+
/**
|
|
7
|
+
* Loopback URL of the tunnelled remote hub, e.g. ws://127.0.0.1:51234/peer.
|
|
8
|
+
* Absent for an enrolled host: it dialled us, and we have no way to dial it.
|
|
9
|
+
*/
|
|
10
|
+
url?: string;
|
|
11
|
+
token?: string;
|
|
12
|
+
hubVersion: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Requesting half of the hub-to-hub link — the side that owns the canvas.
|
|
16
|
+
*
|
|
17
|
+
* It gets its socket one of two ways. `connect()` dials an SSH-deployed hub
|
|
18
|
+
* through its tunnel and reconnects with backoff, because the whole point of a
|
|
19
|
+
* daemon rather than an ssh subprocess is that sessions survive the link
|
|
20
|
+
* dropping. `adopt()` takes a socket an enrolled host opened towards us; there
|
|
21
|
+
* is nothing to dial on a drop, so we wait for it to come back and adopt the
|
|
22
|
+
* new socket instead.
|
|
23
|
+
*
|
|
24
|
+
* Either way, once the socket is live this class behaves identically: it
|
|
25
|
+
* re-subscribes and re-fetches the peer's session list, so the canvas
|
|
26
|
+
* repopulates on its own.
|
|
27
|
+
*/
|
|
28
|
+
export declare class PeerConnection extends EventEmitter {
|
|
29
|
+
private readonly opts;
|
|
30
|
+
private ws;
|
|
31
|
+
private readonly pending;
|
|
32
|
+
private readonly attached;
|
|
33
|
+
private closed;
|
|
34
|
+
private backoff;
|
|
35
|
+
private _connected;
|
|
36
|
+
private _remoteVersion;
|
|
37
|
+
constructor(opts: PeerOptions);
|
|
38
|
+
get connected(): boolean;
|
|
39
|
+
get remoteVersion(): string | null;
|
|
40
|
+
get hostId(): string;
|
|
41
|
+
/** True when this peer dialled us and we cannot dial it back. */
|
|
42
|
+
get inbound(): boolean;
|
|
43
|
+
connect(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Take over a socket an enrolling host opened towards us. Its hello was
|
|
46
|
+
* already validated by the join endpoint, and we already answered `welcome`,
|
|
47
|
+
* so there is no handshake left to run here — go straight to the connected
|
|
48
|
+
* state the welcome path would have reached.
|
|
49
|
+
*/
|
|
50
|
+
adopt(socket: WebSocket, remoteVersion: string): void;
|
|
51
|
+
/** Everything that must happen once a socket is usable, either way in. */
|
|
52
|
+
private onLive;
|
|
53
|
+
private onMessage;
|
|
54
|
+
private failAllPending;
|
|
55
|
+
/** Send a request and await its reply. */
|
|
56
|
+
request<T = unknown>(req: PeerRequest): Promise<T>;
|
|
57
|
+
refresh(): Promise<void>;
|
|
58
|
+
attach(address: string): Promise<void>;
|
|
59
|
+
detach(address: string): Promise<void>;
|
|
60
|
+
close(): void;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=peer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer.d.ts","sourceRoot":"","sources":["../../src/remote/peer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAUD;;;;;;;;;;;;;GAaG;AACH,qBAAa,cAAe,SAAQ,YAAY;IASlC,OAAO,CAAC,QAAQ,CAAC,IAAI;IARjC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAO;IACtB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAuB;gBAEhB,IAAI,EAAE,WAAW;IAI9C,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,aAAa,IAAI,MAAM,GAAG,IAAI,CAEjC;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,iEAAiE;IACjE,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,OAAO,IAAI,IAAI;IAsCf;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAiCrD,0EAA0E;IAC1E,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,SAAS;IAmEjB,OAAO,CAAC,cAAc;IAQtB,0CAA0C;IAC1C,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAoB5C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5C,KAAK,IAAI,IAAI;CAYd"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { WebSocket } from 'ws';
|
|
4
|
+
import { PEER_SCHEMA_VERSION, PeerResponse, } from '../protocol/index.js';
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 20_000;
|
|
6
|
+
/**
|
|
7
|
+
* Requesting half of the hub-to-hub link — the side that owns the canvas.
|
|
8
|
+
*
|
|
9
|
+
* It gets its socket one of two ways. `connect()` dials an SSH-deployed hub
|
|
10
|
+
* through its tunnel and reconnects with backoff, because the whole point of a
|
|
11
|
+
* daemon rather than an ssh subprocess is that sessions survive the link
|
|
12
|
+
* dropping. `adopt()` takes a socket an enrolled host opened towards us; there
|
|
13
|
+
* is nothing to dial on a drop, so we wait for it to come back and adopt the
|
|
14
|
+
* new socket instead.
|
|
15
|
+
*
|
|
16
|
+
* Either way, once the socket is live this class behaves identically: it
|
|
17
|
+
* re-subscribes and re-fetches the peer's session list, so the canvas
|
|
18
|
+
* repopulates on its own.
|
|
19
|
+
*/
|
|
20
|
+
export class PeerConnection extends EventEmitter {
|
|
21
|
+
opts;
|
|
22
|
+
ws = null;
|
|
23
|
+
pending = new Map();
|
|
24
|
+
attached = new Set();
|
|
25
|
+
closed = false;
|
|
26
|
+
backoff = 500;
|
|
27
|
+
_connected = false;
|
|
28
|
+
_remoteVersion = null;
|
|
29
|
+
constructor(opts) {
|
|
30
|
+
super();
|
|
31
|
+
this.opts = opts;
|
|
32
|
+
}
|
|
33
|
+
get connected() {
|
|
34
|
+
return this._connected;
|
|
35
|
+
}
|
|
36
|
+
get remoteVersion() {
|
|
37
|
+
return this._remoteVersion;
|
|
38
|
+
}
|
|
39
|
+
get hostId() {
|
|
40
|
+
return this.opts.hostId;
|
|
41
|
+
}
|
|
42
|
+
/** True when this peer dialled us and we cannot dial it back. */
|
|
43
|
+
get inbound() {
|
|
44
|
+
return !this.opts.url;
|
|
45
|
+
}
|
|
46
|
+
connect() {
|
|
47
|
+
if (this.closed)
|
|
48
|
+
return;
|
|
49
|
+
if (!this.opts.url) {
|
|
50
|
+
throw new Error(`host ${this.opts.hostId} enrolled itself; it dials us`);
|
|
51
|
+
}
|
|
52
|
+
const ws = new WebSocket(this.opts.url);
|
|
53
|
+
this.ws = ws;
|
|
54
|
+
ws.on('open', () => {
|
|
55
|
+
ws.send(JSON.stringify({
|
|
56
|
+
t: 'hello',
|
|
57
|
+
token: this.opts.token ?? '',
|
|
58
|
+
hubVersion: this.opts.hubVersion,
|
|
59
|
+
schemaVersion: PEER_SCHEMA_VERSION,
|
|
60
|
+
}));
|
|
61
|
+
});
|
|
62
|
+
ws.on('message', (raw) => this.onMessage(raw));
|
|
63
|
+
ws.on('close', () => {
|
|
64
|
+
this.ws = null;
|
|
65
|
+
const wasConnected = this._connected;
|
|
66
|
+
this._connected = false;
|
|
67
|
+
this.failAllPending('peer connection closed');
|
|
68
|
+
if (wasConnected)
|
|
69
|
+
this.emit('disconnected');
|
|
70
|
+
if (this.closed)
|
|
71
|
+
return;
|
|
72
|
+
setTimeout(() => this.connect(), this.backoff);
|
|
73
|
+
this.backoff = Math.min(this.backoff * 2, 15_000);
|
|
74
|
+
});
|
|
75
|
+
ws.on('error', (err) => {
|
|
76
|
+
this.emit('error', err);
|
|
77
|
+
ws.close();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Take over a socket an enrolling host opened towards us. Its hello was
|
|
82
|
+
* already validated by the join endpoint, and we already answered `welcome`,
|
|
83
|
+
* so there is no handshake left to run here — go straight to the connected
|
|
84
|
+
* state the welcome path would have reached.
|
|
85
|
+
*/
|
|
86
|
+
adopt(socket, remoteVersion) {
|
|
87
|
+
if (this.closed) {
|
|
88
|
+
socket.close();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.ws = socket;
|
|
92
|
+
socket.on('message', (raw) => this.onMessage(raw));
|
|
93
|
+
socket.on('close', () => {
|
|
94
|
+
// Ignore a socket we already replaced with a newer one. A null `ws`
|
|
95
|
+
// means close() ran, which is ours to finish, not to skip.
|
|
96
|
+
if (this.ws && this.ws !== socket)
|
|
97
|
+
return;
|
|
98
|
+
this.ws = null;
|
|
99
|
+
const wasConnected = this._connected;
|
|
100
|
+
this._connected = false;
|
|
101
|
+
this.failAllPending('peer connection closed');
|
|
102
|
+
if (wasConnected)
|
|
103
|
+
this.emit('disconnected');
|
|
104
|
+
// No redial: the host reaches us, not the other way round. It will come
|
|
105
|
+
// back on its own backoff and be adopted again.
|
|
106
|
+
});
|
|
107
|
+
socket.on('error', (err) => {
|
|
108
|
+
this.emit('error', err);
|
|
109
|
+
socket.close();
|
|
110
|
+
});
|
|
111
|
+
this._connected = true;
|
|
112
|
+
this._remoteVersion = remoteVersion;
|
|
113
|
+
this.emit('connected', remoteVersion);
|
|
114
|
+
this.onLive();
|
|
115
|
+
}
|
|
116
|
+
/** Everything that must happen once a socket is usable, either way in. */
|
|
117
|
+
onLive() {
|
|
118
|
+
for (const address of this.attached) {
|
|
119
|
+
// A session that vanished while the link was down cannot be re-attached,
|
|
120
|
+
// and that is not a reason to fail anything: drop it and carry on.
|
|
121
|
+
void this.request({ t: 'attach', id: randomUUID(), address }).catch(() => {
|
|
122
|
+
this.attached.delete(address);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
void this.refresh();
|
|
126
|
+
}
|
|
127
|
+
onMessage(raw) {
|
|
128
|
+
let parsed;
|
|
129
|
+
try {
|
|
130
|
+
parsed = PeerResponse.safeParse(JSON.parse(raw.toString('utf8')));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!parsed.success)
|
|
136
|
+
return;
|
|
137
|
+
const msg = parsed.data;
|
|
138
|
+
switch (msg.t) {
|
|
139
|
+
case 'welcome': {
|
|
140
|
+
// Refuse a peer we cannot speak to correctly rather than half-working.
|
|
141
|
+
if (msg.schemaVersion !== PEER_SCHEMA_VERSION) {
|
|
142
|
+
this.emit('error', new Error(`peer schema mismatch: remote hub ${msg.hubVersion} speaks v${msg.schemaVersion}, ` +
|
|
143
|
+
`this hub speaks v${PEER_SCHEMA_VERSION}. Redeploy the remote hub.`));
|
|
144
|
+
this.close();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this._connected = true;
|
|
148
|
+
this._remoteVersion = msg.hubVersion;
|
|
149
|
+
this.backoff = 500;
|
|
150
|
+
this.emit('connected', msg.hubVersion);
|
|
151
|
+
// Re-establish attachments that predate this connection.
|
|
152
|
+
this.onLive();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
case 'ok':
|
|
156
|
+
case 'err': {
|
|
157
|
+
const p = this.pending.get(msg.id);
|
|
158
|
+
if (!p)
|
|
159
|
+
return;
|
|
160
|
+
this.pending.delete(msg.id);
|
|
161
|
+
clearTimeout(p.timer);
|
|
162
|
+
if (msg.t === 'ok')
|
|
163
|
+
p.resolve(msg.result);
|
|
164
|
+
else
|
|
165
|
+
p.reject(new Error(msg.message));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
case 'sessions':
|
|
169
|
+
this.emit('sessions', msg.sessions);
|
|
170
|
+
return;
|
|
171
|
+
case 'sessionUpserted':
|
|
172
|
+
this.emit('sessionUpserted', msg.session);
|
|
173
|
+
return;
|
|
174
|
+
case 'sessionRemoved':
|
|
175
|
+
this.emit('sessionRemoved', msg.address);
|
|
176
|
+
return;
|
|
177
|
+
case 'output':
|
|
178
|
+
this.emit('output', msg.address, msg.data);
|
|
179
|
+
return;
|
|
180
|
+
// This peer asking us to act on an address it cannot resolve itself.
|
|
181
|
+
// Answered with a `relayResult` request rather than an `ok`, because the
|
|
182
|
+
// request channel runs the other way: it is not replying to us here, it
|
|
183
|
+
// is asking, and we answer.
|
|
184
|
+
case 'relay':
|
|
185
|
+
this.emit('relay', msg.relayId, msg.ask);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
failAllPending(reason) {
|
|
190
|
+
for (const [, p] of this.pending) {
|
|
191
|
+
clearTimeout(p.timer);
|
|
192
|
+
p.reject(new Error(reason));
|
|
193
|
+
}
|
|
194
|
+
this.pending.clear();
|
|
195
|
+
}
|
|
196
|
+
/** Send a request and await its reply. */
|
|
197
|
+
request(req) {
|
|
198
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
199
|
+
return Promise.reject(new Error(`host ${this.opts.hostId} is not connected`));
|
|
200
|
+
}
|
|
201
|
+
const id = 'id' in req ? req.id : randomUUID();
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
const timer = setTimeout(() => {
|
|
204
|
+
this.pending.delete(id);
|
|
205
|
+
reject(new Error(`peer request "${req.t}" timed out`));
|
|
206
|
+
}, REQUEST_TIMEOUT_MS);
|
|
207
|
+
timer.unref?.();
|
|
208
|
+
this.pending.set(id, {
|
|
209
|
+
resolve: resolve,
|
|
210
|
+
reject,
|
|
211
|
+
timer,
|
|
212
|
+
});
|
|
213
|
+
this.ws.send(JSON.stringify(req));
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async refresh() {
|
|
217
|
+
try {
|
|
218
|
+
const sessions = await this.request({
|
|
219
|
+
t: 'listSessions',
|
|
220
|
+
id: randomUUID(),
|
|
221
|
+
});
|
|
222
|
+
this.emit('sessions', sessions);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
this.emit('error', err);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async attach(address) {
|
|
229
|
+
this.attached.add(address);
|
|
230
|
+
await this.request({ t: 'attach', id: randomUUID(), address });
|
|
231
|
+
}
|
|
232
|
+
async detach(address) {
|
|
233
|
+
this.attached.delete(address);
|
|
234
|
+
await this.request({ t: 'detach', id: randomUUID(), address });
|
|
235
|
+
}
|
|
236
|
+
close() {
|
|
237
|
+
this.closed = true;
|
|
238
|
+
this.failAllPending('peer closed');
|
|
239
|
+
const wasConnected = this._connected;
|
|
240
|
+
this._connected = false;
|
|
241
|
+
this.ws?.close();
|
|
242
|
+
this.ws = null;
|
|
243
|
+
// Closing a peer is a disconnection like any other. Without this the
|
|
244
|
+
// socket's own close handler sees _connected already false and stays
|
|
245
|
+
// quiet, leaving the host row claiming to be connected.
|
|
246
|
+
if (wasConnected)
|
|
247
|
+
this.emit('disconnected');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=peer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer.js","sourceRoot":"","sources":["../../src/remote/peer.ts"],"names":[],"mappings":"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"}
|