linkshell-cli 0.9.0 → 0.10.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.
Files changed (153) hide show
  1. package/README.md +11 -6
  2. package/dist/cli/src/commands/gateway.d.ts +2 -0
  3. package/dist/cli/src/commands/gateway.js +29 -0
  4. package/dist/cli/src/commands/gateway.js.map +1 -0
  5. package/dist/cli/src/commands/host.d.ts +2 -0
  6. package/dist/cli/src/commands/host.js +12 -2
  7. package/dist/cli/src/commands/host.js.map +1 -1
  8. package/dist/cli/src/commands/setup.js +4 -2
  9. package/dist/cli/src/commands/setup.js.map +1 -1
  10. package/dist/cli/src/index.js +12 -334
  11. package/dist/cli/src/index.js.map +1 -1
  12. package/dist/cli/src/utils/daemon.d.ts +0 -7
  13. package/dist/cli/src/utils/daemon.js +1 -18
  14. package/dist/cli/src/utils/daemon.js.map +1 -1
  15. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  16. package/dist/gateway/src/accounts.d.ts +6 -0
  17. package/dist/gateway/src/accounts.js +32 -0
  18. package/dist/gateway/src/accounts.js.map +1 -0
  19. package/dist/gateway/src/index.d.ts +4 -0
  20. package/dist/gateway/src/index.js +5 -0
  21. package/dist/gateway/src/index.js.map +1 -0
  22. package/dist/gateway/src/main.d.ts +1 -0
  23. package/dist/gateway/src/main.js +57 -0
  24. package/dist/gateway/src/main.js.map +1 -0
  25. package/dist/gateway/src/rate-limit.d.ts +22 -0
  26. package/dist/gateway/src/rate-limit.js +66 -0
  27. package/dist/gateway/src/rate-limit.js.map +1 -0
  28. package/dist/gateway/src/relay.d.ts +84 -0
  29. package/dist/gateway/src/relay.js +434 -0
  30. package/dist/gateway/src/relay.js.map +1 -0
  31. package/dist/gateway/src/serve.d.ts +34 -0
  32. package/dist/gateway/src/serve.js +102 -0
  33. package/dist/gateway/src/serve.js.map +1 -0
  34. package/dist/gateway/src/store.d.ts +35 -0
  35. package/dist/gateway/src/store.js +109 -0
  36. package/dist/gateway/src/store.js.map +1 -0
  37. package/dist/gateway/src/subscription.d.ts +18 -0
  38. package/dist/gateway/src/subscription.js +46 -0
  39. package/dist/gateway/src/subscription.js.map +1 -0
  40. package/dist/host/src/drivers/claude/transcript.d.ts +23 -23
  41. package/dist/host/src/fs.d.ts +3 -3
  42. package/dist/host/src/input.d.ts +4 -4
  43. package/dist/host/src/terminals.d.ts +1 -1
  44. package/dist/wire/src/model.d.ts +6 -6
  45. package/dist/wire/src/relay.d.ts +8 -8
  46. package/dist/wire/src/rpc.d.ts +818 -818
  47. package/dist/wire/src/updates.d.ts +18 -18
  48. package/package.json +4 -13
  49. package/src/commands/gateway.ts +31 -0
  50. package/src/commands/host.ts +13 -2
  51. package/src/commands/setup.ts +4 -2
  52. package/src/index.ts +14 -429
  53. package/src/utils/daemon.ts +2 -27
  54. package/dist/cli/src/config.d.ts +0 -14
  55. package/dist/cli/src/config.js +0 -23
  56. package/dist/cli/src/config.js.map +0 -1
  57. package/dist/cli/src/machine-id.d.ts +0 -6
  58. package/dist/cli/src/machine-id.js +0 -50
  59. package/dist/cli/src/machine-id.js.map +0 -1
  60. package/dist/cli/src/providers.d.ts +0 -13
  61. package/dist/cli/src/providers.js +0 -77
  62. package/dist/cli/src/providers.js.map +0 -1
  63. package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -128
  64. package/dist/cli/src/runtime/acp/acp-client.js +0 -430
  65. package/dist/cli/src/runtime/acp/acp-client.js.map +0 -1
  66. package/dist/cli/src/runtime/acp/agent-process-discovery.d.ts +0 -48
  67. package/dist/cli/src/runtime/acp/agent-process-discovery.js +0 -278
  68. package/dist/cli/src/runtime/acp/agent-process-discovery.js.map +0 -1
  69. package/dist/cli/src/runtime/acp/agent-session.d.ts +0 -62
  70. package/dist/cli/src/runtime/acp/agent-session.js +0 -1081
  71. package/dist/cli/src/runtime/acp/agent-session.js.map +0 -1
  72. package/dist/cli/src/runtime/acp/agent-workspace.d.ts +0 -311
  73. package/dist/cli/src/runtime/acp/agent-workspace.js +0 -6083
  74. package/dist/cli/src/runtime/acp/agent-workspace.js.map +0 -1
  75. package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +0 -121
  76. package/dist/cli/src/runtime/acp/claude-sdk-client.js +0 -1147
  77. package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +0 -1
  78. package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +0 -53
  79. package/dist/cli/src/runtime/acp/claude-stream-json-client.js +0 -552
  80. package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +0 -1
  81. package/dist/cli/src/runtime/acp/json-rpc.d.ts +0 -22
  82. package/dist/cli/src/runtime/acp/json-rpc.js +0 -160
  83. package/dist/cli/src/runtime/acp/json-rpc.js.map +0 -1
  84. package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -16
  85. package/dist/cli/src/runtime/acp/provider-resolver.js +0 -132
  86. package/dist/cli/src/runtime/acp/provider-resolver.js.map +0 -1
  87. package/dist/cli/src/runtime/bridge-session.d.ts +0 -97
  88. package/dist/cli/src/runtime/bridge-session.js +0 -1364
  89. package/dist/cli/src/runtime/bridge-session.js.map +0 -1
  90. package/dist/cli/src/runtime/hook-command.d.ts +0 -9
  91. package/dist/cli/src/runtime/hook-command.js +0 -80
  92. package/dist/cli/src/runtime/hook-command.js.map +0 -1
  93. package/dist/cli/src/runtime/screen-fallback.d.ts +0 -22
  94. package/dist/cli/src/runtime/screen-fallback.js +0 -202
  95. package/dist/cli/src/runtime/screen-fallback.js.map +0 -1
  96. package/dist/cli/src/runtime/screen-share.d.ts +0 -34
  97. package/dist/cli/src/runtime/screen-share.js +0 -376
  98. package/dist/cli/src/runtime/screen-share.js.map +0 -1
  99. package/dist/cli/src/runtime/scrollback.d.ts +0 -11
  100. package/dist/cli/src/runtime/scrollback.js +0 -33
  101. package/dist/cli/src/runtime/scrollback.js.map +0 -1
  102. package/dist/cli/src/runtime/usage-report.d.ts +0 -34
  103. package/dist/cli/src/runtime/usage-report.js +0 -332
  104. package/dist/cli/src/runtime/usage-report.js.map +0 -1
  105. package/dist/cli/src/utils/keep-awake.d.ts +0 -6
  106. package/dist/cli/src/utils/keep-awake.js +0 -48
  107. package/dist/cli/src/utils/keep-awake.js.map +0 -1
  108. package/dist/cli/src/utils/lan-ip.d.ts +0 -9
  109. package/dist/cli/src/utils/lan-ip.js +0 -61
  110. package/dist/cli/src/utils/lan-ip.js.map +0 -1
  111. package/dist/host/src/input-helper.d.ts +0 -1
  112. package/dist/host/src/input-helper.js +0 -564
  113. package/dist/host/src/input-helper.js.map +0 -1
  114. package/dist/shared-protocol/src/index.d.ts +0 -17029
  115. package/dist/shared-protocol/src/index.js +0 -1064
  116. package/dist/shared-protocol/src/index.js.map +0 -1
  117. package/scripts/copy-web.mjs +0 -34
  118. package/src/config.ts +0 -36
  119. package/src/machine-id.ts +0 -61
  120. package/src/providers.ts +0 -97
  121. package/src/runtime/acp/acp-client.ts +0 -521
  122. package/src/runtime/acp/agent-process-discovery.ts +0 -308
  123. package/src/runtime/acp/agent-session.ts +0 -1186
  124. package/src/runtime/acp/agent-workspace.ts +0 -6590
  125. package/src/runtime/acp/claude-sdk-client.ts +0 -1221
  126. package/src/runtime/acp/claude-stream-json-client.ts +0 -633
  127. package/src/runtime/acp/json-rpc.ts +0 -184
  128. package/src/runtime/acp/provider-resolver.ts +0 -154
  129. package/src/runtime/bridge-session.ts +0 -1549
  130. package/src/runtime/hook-command.ts +0 -73
  131. package/src/runtime/screen-fallback.ts +0 -235
  132. package/src/runtime/screen-share.ts +0 -426
  133. package/src/runtime/scrollback.ts +0 -43
  134. package/src/runtime/usage-report.ts +0 -342
  135. package/src/utils/keep-awake.ts +0 -61
  136. package/src/utils/lan-ip.ts +0 -61
  137. package/web/assets/FileBrowser-wOAfY-xj.js +0 -2
  138. package/web/assets/PortPreview-Cbvv7yzA.js +0 -2
  139. package/web/assets/TerminalPanel-CTSklkct.js +0 -1
  140. package/web/assets/UsageDashboard-CJ1AsNmd.js +0 -1
  141. package/web/assets/index-BsnDaeCt.css +0 -33
  142. package/web/assets/index-q85psYZ5.js +0 -36
  143. package/web/assets/vendor-highlight-NXWAAyIv.js +0 -5
  144. package/web/assets/vendor-react-M7xG3qiw.js +0 -49
  145. package/web/assets/vendor-xterm-D15THcI4.js +0 -9
  146. package/web/index.html +0 -37
  147. package/web/logos/claude.png +0 -0
  148. package/web/logos/codex.png +0 -0
  149. package/web/logos/copilot.png +0 -0
  150. package/web/logos/gemini.png +0 -0
  151. package/web/logos/linkshell-1024.png +0 -0
  152. package/web/logos/linkshell-splash.png +0 -0
  153. package/web/logos/linkshell.png +0 -0
@@ -0,0 +1,434 @@
1
+ import { createServer } from "node:http";
2
+ import { randomBytes, randomInt, randomUUID } from "node:crypto";
3
+ import WebSocket, { WebSocketServer } from "ws";
4
+ import { clientFrameSchema, gatewayChallengeMessage, idOf, RELAY_PATH, verifySignature, } from "@linkshell/wire";
5
+ import { GatewayStore } from "./store.js";
6
+ class RelayError extends Error {
7
+ code;
8
+ constructor(code, message) {
9
+ super(message);
10
+ this.code = code;
11
+ }
12
+ }
13
+ const AUTH_TIMEOUT_MS = 10_000;
14
+ const PAIR_DECISION_MS = 30_000;
15
+ const MAX_CLAIM_ATTEMPTS = 8;
16
+ /**
17
+ * Gateway v2: authenticates peers by key, routes their end-to-end encrypted
18
+ * frames to peers they may reach (paired, or same account), and brokers
19
+ * pairing. It has no way to read what it routes.
20
+ */
21
+ export class Gateway {
22
+ options;
23
+ store;
24
+ server;
25
+ peers = new Map();
26
+ offers = new Map();
27
+ decisions = new Map();
28
+ claimTimes = new Map();
29
+ heartbeat;
30
+ /** Sockets that answered the last ping. */
31
+ alive = new WeakSet();
32
+ stopped = false;
33
+ log;
34
+ constructor(options) {
35
+ this.options = options;
36
+ this.store = new GatewayStore(options.databasePath);
37
+ this.log = options.log ?? ((message) => process.stderr.write(`${new Date().toISOString()} [gateway] ${message}\n`));
38
+ }
39
+ wss = new WebSocketServer({ noServer: true, perMessageDeflate: false, maxPayload: 48 * 1024 * 1024 });
40
+ /** Runs on its own port. */
41
+ async start() {
42
+ const server = createServer((request, response) => {
43
+ if (request.url === "/healthz") {
44
+ response.writeHead(200, { "content-type": "application/json" });
45
+ response.end(JSON.stringify({ ok: true, peers: this.peers.size }));
46
+ return;
47
+ }
48
+ response.writeHead(404);
49
+ response.end();
50
+ });
51
+ server.on("upgrade", (request, socket, head) => {
52
+ if (!request.url?.startsWith(RELAY_PATH)) {
53
+ socket.destroy();
54
+ return;
55
+ }
56
+ this.handleUpgrade(request, socket, head);
57
+ });
58
+ this.server = server;
59
+ await new Promise((resolve) => server.listen(this.options.port, this.options.host ?? "0.0.0.0", resolve));
60
+ this.attach();
61
+ const address = server.address();
62
+ return typeof address === "object" && address ? address.port : this.options.port;
63
+ }
64
+ /**
65
+ * Runs inside another HTTP server, which passes it upgrades for
66
+ * `RELAY_PATH` through `handleUpgrade`.
67
+ */
68
+ attach() {
69
+ if (this.heartbeat)
70
+ return;
71
+ this.heartbeat = setInterval(() => this.sweep(), this.options.heartbeatMs ?? 30_000);
72
+ this.heartbeat.unref();
73
+ }
74
+ handleUpgrade(request, socket, head) {
75
+ this.wss.handleUpgrade(request, socket, head, (ws) => this.accept(ws));
76
+ }
77
+ get connected() {
78
+ return this.peers.size;
79
+ }
80
+ /** Bytes accepted from senders and not yet written to their receivers. */
81
+ get bufferedBytes() {
82
+ let total = 0;
83
+ for (const peer of this.peers.values())
84
+ total += peer.socket.bufferedAmount;
85
+ return total;
86
+ }
87
+ /** Drops connections that didn't answer the previous ping, pings the rest. */
88
+ sweep() {
89
+ for (const peer of this.peers.values()) {
90
+ // A held peer isn't read, so its pong can't arrive; it is checked again once released.
91
+ if (peer.held)
92
+ continue;
93
+ if (!this.alive.has(peer.socket)) {
94
+ peer.socket.terminate();
95
+ continue;
96
+ }
97
+ this.alive.delete(peer.socket);
98
+ peer.socket.ping();
99
+ }
100
+ }
101
+ async stop() {
102
+ this.stopped = true;
103
+ clearInterval(this.heartbeat);
104
+ for (const peer of this.peers.values())
105
+ peer.socket.terminate();
106
+ await new Promise((resolve) => (this.server ? this.server.close(() => resolve()) : resolve()));
107
+ this.store.close();
108
+ }
109
+ // ── connection ────────────────────────────────────────────────────
110
+ accept(socket) {
111
+ this.alive.add(socket);
112
+ socket.on("pong", () => this.alive.add(socket));
113
+ const nonce = randomBytes(32).toString("base64url");
114
+ let peer;
115
+ const send = (frame) => {
116
+ if (socket.readyState === WebSocket.OPEN)
117
+ socket.send(JSON.stringify(frame));
118
+ };
119
+ const timer = setTimeout(() => {
120
+ if (!peer)
121
+ socket.close(4001, "auth timeout");
122
+ }, AUTH_TIMEOUT_MS);
123
+ send({ t: "challenge", nonce });
124
+ socket.on("message", async (data) => {
125
+ let parsed;
126
+ try {
127
+ parsed = clientFrameSchema.safeParse(JSON.parse(data.toString()));
128
+ }
129
+ catch {
130
+ parsed = undefined;
131
+ }
132
+ if (!parsed?.success) {
133
+ send({ t: "error", code: "bad_frame", message: "unreadable frame" });
134
+ return;
135
+ }
136
+ const frame = parsed.data;
137
+ if (!peer) {
138
+ if (frame.t !== "auth") {
139
+ socket.close(4001, "auth first");
140
+ return;
141
+ }
142
+ try {
143
+ peer = await this.authenticate(frame, nonce, socket);
144
+ }
145
+ catch (error) {
146
+ const code = error instanceof RelayError ? error.code : "auth_failed";
147
+ send({ t: "error", code, message: error instanceof Error ? error.message : String(error) });
148
+ socket.close(4001, code);
149
+ return;
150
+ }
151
+ clearTimeout(timer);
152
+ send({ t: "ready", userId: peer.record.userId, email: peer.email });
153
+ this.announce(peer.record, true);
154
+ return;
155
+ }
156
+ switch (frame.t) {
157
+ case "ping":
158
+ send({ t: "pong" });
159
+ return;
160
+ case "to":
161
+ this.route(peer, frame.to, frame.d);
162
+ return;
163
+ case "req":
164
+ try {
165
+ const result = await this.handle(peer, frame.method, (frame.params ?? {}));
166
+ send({ t: "res", id: frame.id, result });
167
+ }
168
+ catch (error) {
169
+ const code = error instanceof RelayError ? error.code : "internal";
170
+ send({ t: "res", id: frame.id, error: { code, message: error instanceof Error ? error.message : String(error) } });
171
+ }
172
+ return;
173
+ case "auth":
174
+ return;
175
+ }
176
+ });
177
+ socket.on("close", () => {
178
+ clearTimeout(timer);
179
+ if (this.stopped || !peer || this.peers.get(peer.record.id)?.socket !== socket)
180
+ return;
181
+ this.peers.delete(peer.record.id);
182
+ this.store.touch(peer.record.id);
183
+ this.announce(peer.record, false);
184
+ });
185
+ }
186
+ async authenticate(frame, nonce, socket) {
187
+ const { identity, role } = frame;
188
+ if (idOf(identity.signKey) !== identity.id)
189
+ throw new RelayError("bad_identity", "id doesn't match key");
190
+ if (!verifySignature(identity.signKey, gatewayChallengeMessage(nonce), frame.signature)) {
191
+ throw new RelayError("bad_signature", "signature check failed");
192
+ }
193
+ const existing = this.store.peer(identity.id);
194
+ if (existing && existing.role !== role)
195
+ throw new RelayError("bad_identity", "this key is registered with another role");
196
+ let userId;
197
+ let email;
198
+ if (frame.token) {
199
+ if (!this.options.verifyToken)
200
+ throw new RelayError("accounts_disabled", "this gateway doesn't support accounts");
201
+ const account = await this.options.verifyToken(frame.token);
202
+ if (!account)
203
+ throw new RelayError("token_invalid", "account token is invalid or expired");
204
+ userId = account.userId;
205
+ email = account.email;
206
+ }
207
+ const refusal = await this.options.admit?.({ role: role, userId });
208
+ if (refusal)
209
+ throw new RelayError("not_admitted", refusal);
210
+ const record = {
211
+ id: identity.id,
212
+ role: role,
213
+ signKey: identity.signKey,
214
+ boxKey: identity.boxKey,
215
+ name: frame.name,
216
+ platform: frame.platform,
217
+ userId,
218
+ lastSeen: Date.now(),
219
+ };
220
+ this.store.upsertPeer(record);
221
+ const previous = this.peers.get(record.id);
222
+ const peer = { record, socket, email };
223
+ this.peers.set(record.id, peer);
224
+ // One live connection per identity: the newest wins.
225
+ previous?.socket.close(4000, "replaced by a newer connection");
226
+ this.log(`${role} ${record.id.slice(0, 8)} "${record.name}" online${userId ? " (account)" : ""}`);
227
+ return peer;
228
+ }
229
+ // ── routing ───────────────────────────────────────────────────────
230
+ route(from, to, data) {
231
+ const via = this.store.route(from.record.id, to);
232
+ const target = this.peers.get(to);
233
+ const undeliverable = (reason) => from.socket.send(JSON.stringify({ t: "undeliverable", to, reason }));
234
+ if (!via)
235
+ return undeliverable("not_allowed");
236
+ if (!target)
237
+ return undeliverable("offline");
238
+ const frame = JSON.stringify({
239
+ t: "from",
240
+ from: { id: from.record.id, signKey: from.record.signKey, boxKey: from.record.boxKey },
241
+ via,
242
+ d: data,
243
+ });
244
+ this.options.onRouted?.(frame);
245
+ target.socket.send(frame);
246
+ this.holdWhileBehind(from, target);
247
+ }
248
+ /**
249
+ * Backpressure. What a peer can't take yet would otherwise pile up here, in
250
+ * memory every user of this gateway shares: stop reading the sender instead,
251
+ * so the data waits on its side until the receiver catches up.
252
+ */
253
+ holdWhileBehind(from, target) {
254
+ const limit = this.options.maxBufferedBytes ?? 4 * 1024 * 1024;
255
+ if (from.held || target.socket.bufferedAmount <= limit)
256
+ return;
257
+ from.held = true;
258
+ from.socket.pause();
259
+ const since = Date.now();
260
+ const timer = setInterval(() => {
261
+ const open = target.socket.readyState === WebSocket.OPEN;
262
+ if (open && target.socket.bufferedAmount > limit / 4) {
263
+ if (Date.now() - since < (this.options.stalledPeerMs ?? 60_000))
264
+ return;
265
+ this.log(`${target.record.role} ${target.record.id.slice(0, 8)} can't keep up; dropping it`);
266
+ target.socket.terminate();
267
+ }
268
+ clearInterval(timer);
269
+ from.held = false;
270
+ this.alive.add(from.socket);
271
+ if (from.socket.readyState === WebSocket.OPEN)
272
+ from.socket.resume();
273
+ }, 20);
274
+ timer.unref();
275
+ }
276
+ event(peerId, name, data) {
277
+ const peer = this.peers.get(peerId);
278
+ if (peer?.socket.readyState === WebSocket.OPEN)
279
+ peer.socket.send(JSON.stringify({ t: "event", name, data }));
280
+ }
281
+ /** Tells everyone who can reach `record` that it came or went. */
282
+ announce(record, online) {
283
+ for (const { peer } of this.store.reachable(record.id))
284
+ this.event(peer.id, "presence", { id: record.id, online });
285
+ }
286
+ machineEntry(peer, via) {
287
+ return {
288
+ id: peer.id,
289
+ signKey: peer.signKey,
290
+ boxKey: peer.boxKey,
291
+ name: peer.name,
292
+ platform: peer.platform,
293
+ online: this.peers.has(peer.id),
294
+ via,
295
+ };
296
+ }
297
+ deviceEntry(peer, pairedAt) {
298
+ return { id: peer.id, signKey: peer.signKey, boxKey: peer.boxKey, name: peer.name, online: this.peers.has(peer.id), pairedAt };
299
+ }
300
+ // ── control methods ───────────────────────────────────────────────
301
+ async handle(peer, method, params) {
302
+ const role = peer.record.role;
303
+ const need = (wanted) => {
304
+ if (role !== wanted)
305
+ throw new RelayError("forbidden", `${method} is for ${wanted}s`);
306
+ };
307
+ switch (method) {
308
+ case "pair.offer": {
309
+ need("machine");
310
+ for (const [code, offer] of this.offers)
311
+ if (offer.machineId === peer.record.id)
312
+ this.offers.delete(code);
313
+ let code;
314
+ do
315
+ code = String(randomInt(0, 1_000_000)).padStart(6, "0");
316
+ while (this.offers.has(code));
317
+ const expiresAt = Date.now() + (this.options.pairingTtlMs ?? 10 * 60_000);
318
+ this.offers.set(code, { code, machineId: peer.record.id, expiresAt, attempts: 0 });
319
+ return { code, expiresAt };
320
+ }
321
+ case "pair.decide": {
322
+ need("machine");
323
+ const pending = this.decisions.get(String(params.requestId));
324
+ if (!pending || pending.machineId !== peer.record.id)
325
+ throw new RelayError("not_found", "no such pairing request");
326
+ pending.resolve(params.accept === true);
327
+ return {};
328
+ }
329
+ case "pair.claim":
330
+ need("device");
331
+ return this.claim(peer, params);
332
+ case "machines.list":
333
+ need("device");
334
+ return {
335
+ machines: this.store
336
+ .reachable(peer.record.id)
337
+ .map(({ peer: machine, via }) => this.machineEntry(machine, via))
338
+ .sort((a, b) => Number(b.online) - Number(a.online) || a.name.localeCompare(b.name)),
339
+ };
340
+ case "machines.forget": {
341
+ need("device");
342
+ const machineId = String(params.machineId ?? "");
343
+ const unpaired = this.store.unlink(machineId, peer.record.id);
344
+ if (unpaired)
345
+ this.event(machineId, "presence", { id: peer.record.id, online: false });
346
+ // A computer that is listed because it signed in to this account, and is gone (reinstalled,
347
+ // given away): taken off the account, so the list stops showing it. One that is connected
348
+ // stays: it is signed in right now, and would only be back at once.
349
+ const machine = this.store.peer(machineId);
350
+ const ours = machine?.role === "machine" && peer.record.userId !== undefined && machine.userId === peer.record.userId;
351
+ if (ours && !this.peers.has(machineId))
352
+ this.store.disown(machineId);
353
+ return {};
354
+ }
355
+ case "devices.list":
356
+ need("machine");
357
+ return {
358
+ devices: this.store.reachable(peer.record.id).map(({ peer: device, pairedAt }) => this.deviceEntry(device, pairedAt)),
359
+ };
360
+ case "devices.revoke": {
361
+ need("machine");
362
+ const deviceId = String(params.deviceId ?? "");
363
+ if (this.store.unlink(peer.record.id, deviceId)) {
364
+ this.event(deviceId, "machines.changed", {});
365
+ this.peers.get(deviceId)?.socket.close(4003, "unpaired");
366
+ }
367
+ return {};
368
+ }
369
+ default:
370
+ throw new RelayError("unknown_method", `unknown method ${method}`);
371
+ }
372
+ }
373
+ async claim(device, params) {
374
+ // Throttle guessing: a handful of claims per device per minute.
375
+ const now = Date.now();
376
+ const recent = (this.claimTimes.get(device.record.id) ?? []).filter((t) => now - t < 60_000);
377
+ if (recent.length >= 6)
378
+ throw new RelayError("rate_limited", "too many pairing attempts, wait a minute");
379
+ this.claimTimes.set(device.record.id, [...recent, now]);
380
+ const code = typeof params.code === "string" ? params.code.replace(/\D/g, "") : undefined;
381
+ const machineId = typeof params.machineId === "string" ? params.machineId : undefined;
382
+ const offer = code
383
+ ? this.offers.get(code)
384
+ : [...this.offers.values()].find((candidate) => candidate.machineId === machineId);
385
+ if (!offer || offer.expiresAt < now) {
386
+ if (offer)
387
+ this.offers.delete(offer.code);
388
+ throw new RelayError("pairing_expired", "this pairing code is wrong or has expired");
389
+ }
390
+ if (++offer.attempts > MAX_CLAIM_ATTEMPTS) {
391
+ this.offers.delete(offer.code);
392
+ throw new RelayError("pairing_expired", "too many attempts on this code; start pairing again");
393
+ }
394
+ const machine = this.peers.get(offer.machineId);
395
+ if (!machine)
396
+ throw new RelayError("machine_offline", "the computer is offline");
397
+ // The machine checks the proof (it holds the QR secret) and decides.
398
+ const requestId = randomUUID();
399
+ const accepted = await new Promise((resolve) => {
400
+ const timer = setTimeout(() => {
401
+ this.decisions.delete(requestId);
402
+ resolve(false);
403
+ }, PAIR_DECISION_MS);
404
+ this.decisions.set(requestId, {
405
+ machineId: machine.record.id,
406
+ resolve: (accept) => {
407
+ clearTimeout(timer);
408
+ this.decisions.delete(requestId);
409
+ resolve(accept);
410
+ },
411
+ });
412
+ this.event(machine.record.id, "pair.request", {
413
+ requestId,
414
+ device: { id: device.record.id, signKey: device.record.signKey, boxKey: device.record.boxKey, name: device.record.name },
415
+ proof: String(params.proof ?? ""),
416
+ code: offer.code,
417
+ });
418
+ });
419
+ if (!accepted) {
420
+ this.event(machine.record.id, "pair.done", { requestId, accepted: false });
421
+ throw new RelayError("pairing_refused", "the computer refused this pairing");
422
+ }
423
+ this.offers.delete(offer.code);
424
+ this.store.link(machine.record.id, device.record.id);
425
+ this.event(machine.record.id, "pair.done", {
426
+ requestId,
427
+ accepted: true,
428
+ device: this.deviceEntry(device.record, Date.now()),
429
+ });
430
+ this.log(`paired device ${device.record.id.slice(0, 8)} with machine ${machine.record.id.slice(0, 8)}`);
431
+ return { machine: this.machineEntry(machine.record, "paired") };
432
+ }
433
+ }
434
+ //# sourceMappingURL=relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.js","sourceRoot":"","sources":["../../../../gateway/src/relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { VerifyToken } from "./accounts.js";
2
+ import { Gateway, type GatewayOptions } from "./relay.js";
3
+ export interface StartGatewayOptions {
4
+ port: number;
5
+ /** The interface to listen on; all of them when omitted. */
6
+ host?: string;
7
+ /** SQLite file holding pairings and peers' public keys. Losing it unpairs every phone. */
8
+ databasePath: string;
9
+ /** What `/healthz` reports; this package's version when omitted. */
10
+ version?: string;
11
+ log?: (message: string) => void;
12
+ /** Validates account tokens; without it, accounts are off and only pairing works. */
13
+ verifyToken?: VerifyToken;
14
+ admit?: GatewayOptions["admit"];
15
+ /** Addresses of the reverse proxies in front, whose X-Forwarded-For is believed. */
16
+ trustedProxies?: readonly string[];
17
+ /** Connections one address may open per window (default 20 a minute). */
18
+ wsConnectLimit?: {
19
+ max: number;
20
+ windowMs: number;
21
+ };
22
+ }
23
+ export interface RunningGateway {
24
+ port: number;
25
+ relay: Gateway;
26
+ close(): Promise<void>;
27
+ }
28
+ /**
29
+ * The gateway as it is deployed: one HTTP server with the relay on
30
+ * `RELAY_PATH` and a health check on `/healthz`. Nothing else is served.
31
+ */
32
+ export declare function startGateway(options: StartGatewayOptions): Promise<RunningGateway>;
33
+ /** This package's version, so `/healthz` says which build is deployed. */
34
+ export declare function packageVersion(): string;
@@ -0,0 +1,102 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { createServer } from "node:http";
3
+ import { createRequire } from "node:module";
4
+ import { dirname } from "node:path";
5
+ import { RELAY_PATH } from "@linkshell/wire";
6
+ import { clientIp, isLoopback, RateLimiter, trustedProxySet } from "./rate-limit.js";
7
+ import { Gateway } from "./relay.js";
8
+ /**
9
+ * The gateway as it is deployed: one HTTP server with the relay on
10
+ * `RELAY_PATH` and a health check on `/healthz`. Nothing else is served.
11
+ */
12
+ export async function startGateway(options) {
13
+ mkdirSync(dirname(options.databasePath), { recursive: true });
14
+ const relay = new Gateway({
15
+ port: 0,
16
+ databasePath: options.databasePath,
17
+ verifyToken: options.verifyToken,
18
+ admit: options.admit,
19
+ log: options.log,
20
+ });
21
+ const version = options.version ?? packageVersion();
22
+ const trustedProxies = trustedProxySet(options.trustedProxies ?? []);
23
+ const limit = options.wsConnectLimit ?? { max: 20, windowMs: 60_000 };
24
+ const connectLimiter = new RateLimiter(limit.max, limit.windowMs);
25
+ const server = createServer((request, response) => {
26
+ if (request.method === "GET" && pathOf(request) === "/healthz") {
27
+ // memoryMb: what the process holds, to watch against the container's limit.
28
+ json(response, 200, { ok: true, version, relay: relay.connected, memoryMb: Math.round(process.memoryUsage.rss() / 1048576) });
29
+ return;
30
+ }
31
+ json(response, 404, { error: "not_found" });
32
+ });
33
+ server.on("upgrade", (request, socket, head) => {
34
+ if (pathOf(request) !== RELAY_PATH) {
35
+ socket.destroy();
36
+ return;
37
+ }
38
+ const ip = clientIp(request, trustedProxies);
39
+ if (!isLoopback(ip) && !connectLimiter.allow(ip)) {
40
+ socket.write("HTTP/1.1 429 Too Many Requests\r\n\r\n");
41
+ socket.destroy();
42
+ return;
43
+ }
44
+ relay.handleUpgrade(request, socket, head);
45
+ });
46
+ try {
47
+ await new Promise((resolve, reject) => {
48
+ server.once("error", reject);
49
+ server.listen(options.port, options.host, () => {
50
+ server.off("error", reject);
51
+ resolve();
52
+ });
53
+ });
54
+ }
55
+ catch (error) {
56
+ connectLimiter.destroy();
57
+ await relay.stop();
58
+ throw error;
59
+ }
60
+ relay.attach();
61
+ const address = server.address();
62
+ return {
63
+ port: typeof address === "object" && address ? address.port : options.port,
64
+ relay,
65
+ close: async () => {
66
+ connectLimiter.destroy();
67
+ // The relay drops its peers first: the server only finishes closing once their sockets are gone.
68
+ await relay.stop();
69
+ await new Promise((resolve) => server.close(() => resolve()));
70
+ },
71
+ };
72
+ }
73
+ /** The request's path; undefined for a target that doesn't parse, which then matches nothing. */
74
+ function pathOf(request) {
75
+ try {
76
+ return new URL(request.url ?? "/", "http://gateway").pathname;
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
82
+ function json(response, status, body) {
83
+ response.writeHead(status, { "content-type": "application/json" });
84
+ response.end(JSON.stringify(body));
85
+ }
86
+ /** This package's version, so `/healthz` says which build is deployed. */
87
+ export function packageVersion() {
88
+ const require = createRequire(import.meta.url);
89
+ // package.json is one level up from src/ and three up from dist/gateway/src/.
90
+ for (const path of ["../package.json", "../../../package.json"]) {
91
+ try {
92
+ const manifest = require(path);
93
+ if (manifest.name === "@linkshell/gateway" && manifest.version)
94
+ return manifest.version;
95
+ }
96
+ catch {
97
+ // Not at this depth.
98
+ }
99
+ }
100
+ return "unknown";
101
+ }
102
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../../../gateway/src/serve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { PeerRole } from "@linkshell/wire";
2
+ export interface PeerRecord {
3
+ id: string;
4
+ role: PeerRole;
5
+ signKey: string;
6
+ boxKey: string;
7
+ name: string;
8
+ platform?: string;
9
+ userId?: string;
10
+ lastSeen: number;
11
+ }
12
+ export declare class GatewayStore {
13
+ private readonly db;
14
+ constructor(path: string);
15
+ close(): void;
16
+ peer(id: string): PeerRecord | undefined;
17
+ upsertPeer(peer: PeerRecord): void;
18
+ touch(id: string, at?: number): void;
19
+ link(machineId: string, deviceId: string, at?: number): void;
20
+ unlink(machineId: string, deviceId: string): boolean;
21
+ /**
22
+ * Takes a peer off its account: the account's other peers no longer reach it, or list it. Its
23
+ * keys and pairings stay, and signing in again puts it back.
24
+ */
25
+ disown(id: string): boolean;
26
+ linkedAt(machineId: string, deviceId: string): number | undefined;
27
+ /** Peers of the other role that `id` may reach: linked, or owned by the same account. */
28
+ reachable(id: string): {
29
+ peer: PeerRecord;
30
+ via: "paired" | "account";
31
+ pairedAt?: number;
32
+ }[];
33
+ /** How `from` may reach `to`, or undefined when it may not. */
34
+ route(from: string, to: string): "paired" | "account" | undefined;
35
+ }