@artenx/dshb 0.1.18 → 0.1.19

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 (101) hide show
  1. package/lib/client.js +2157 -567
  2. package/lib/client.js.map +1 -1
  3. package/lib/core/index.js +72 -4
  4. package/lib/core/index.js.map +1 -1
  5. package/lib/core/node-registry.d.ts +2 -108
  6. package/lib/core/node-registry.js +43 -2
  7. package/lib/core/node-registry.js.map +1 -1
  8. package/lib/core/routes.d.ts +4 -3
  9. package/lib/core/routes.js +67 -23
  10. package/lib/core/routes.js.map +1 -1
  11. package/lib/core/sync/coordinator.d.ts +48 -0
  12. package/lib/core/sync/coordinator.js +247 -0
  13. package/lib/core/sync/coordinator.js.map +1 -0
  14. package/lib/core/sync/host-key.d.ts +2 -0
  15. package/lib/core/sync/host-key.js +71 -0
  16. package/lib/core/sync/host-key.js.map +1 -0
  17. package/lib/core/sync/internal-routes.d.ts +9 -0
  18. package/lib/core/sync/internal-routes.js +236 -0
  19. package/lib/core/sync/internal-routes.js.map +1 -0
  20. package/lib/core/sync/model.d.ts +2 -0
  21. package/lib/core/sync/model.js +106 -0
  22. package/lib/core/sync/model.js.map +1 -0
  23. package/lib/core/sync/node-sync.d.ts +2 -0
  24. package/lib/core/sync/node-sync.js +660 -0
  25. package/lib/core/sync/node-sync.js.map +1 -0
  26. package/lib/core/sync/offline-archive.d.ts +55 -0
  27. package/lib/core/sync/offline-archive.js +673 -0
  28. package/lib/core/sync/offline-archive.js.map +1 -0
  29. package/lib/core/sync/offline-journal.d.ts +22 -0
  30. package/lib/core/sync/offline-journal.js +74 -0
  31. package/lib/core/sync/offline-journal.js.map +1 -0
  32. package/lib/core/sync/offline-routes.d.ts +7 -0
  33. package/lib/core/sync/offline-routes.js +192 -0
  34. package/lib/core/sync/offline-routes.js.map +1 -0
  35. package/lib/core/sync/peer-manager.d.ts +56 -0
  36. package/lib/core/sync/peer-manager.js +258 -0
  37. package/lib/core/sync/peer-manager.js.map +1 -0
  38. package/lib/core/sync/protocol.d.ts +2 -0
  39. package/lib/core/sync/protocol.js +124 -0
  40. package/lib/core/sync/protocol.js.map +1 -0
  41. package/lib/core/sync/remote-helper.d.ts +5 -0
  42. package/lib/core/sync/remote-helper.js +160 -0
  43. package/lib/core/sync/remote-helper.js.map +1 -0
  44. package/lib/core/sync/revision.d.ts +2 -0
  45. package/lib/core/sync/revision.js +77 -0
  46. package/lib/core/sync/revision.js.map +1 -0
  47. package/lib/core/sync/routes.d.ts +9 -0
  48. package/lib/core/sync/routes.js +339 -0
  49. package/lib/core/sync/routes.js.map +1 -0
  50. package/lib/core/sync/scheduler.d.ts +14 -0
  51. package/lib/core/sync/scheduler.js +81 -0
  52. package/lib/core/sync/scheduler.js.map +1 -0
  53. package/lib/core/sync/session-journal.d.ts +2 -0
  54. package/lib/core/sync/session-journal.js +191 -0
  55. package/lib/core/sync/session-journal.js.map +1 -0
  56. package/lib/core/sync/session-routes.d.ts +7 -0
  57. package/lib/core/sync/session-routes.js +67 -0
  58. package/lib/core/sync/session-routes.js.map +1 -0
  59. package/lib/core/sync/session-sync.d.ts +2 -0
  60. package/lib/core/sync/session-sync.js +2 -0
  61. package/lib/core/sync/ssh-transport.d.ts +2 -0
  62. package/lib/core/sync/ssh-transport.js +408 -0
  63. package/lib/core/sync/ssh-transport.js.map +1 -0
  64. package/lib/core/sync/stores.d.ts +3 -0
  65. package/lib/core/sync/stores.js +385 -0
  66. package/lib/core/sync/stores.js.map +1 -0
  67. package/lib/core/test.d.ts +1 -1
  68. package/lib/core/workspace-bindings.d.ts +2 -28
  69. package/lib/core/workspace-bindings.js +14 -14
  70. package/lib/core/workspace-bindings.js.map +1 -1
  71. package/lib/core/workspace-instruction-context.d.ts +3 -2260
  72. package/lib/core/workspace-instruction-context.js +104 -2
  73. package/lib/core/workspace-instruction-context.js.map +1 -0
  74. package/lib/core/workspace-instruction-routes.d.ts +1 -1
  75. package/lib/core/workspace-instructions.d.ts +2 -68
  76. package/lib/core/workspace-instructions.js +13 -0
  77. package/lib/core/workspace-instructions.js.map +1 -1
  78. package/lib/core/workspaces.d.ts +4 -3
  79. package/lib/core/workspaces.js +17 -1
  80. package/lib/core/workspaces.js.map +1 -1
  81. package/lib/host-key-C5afhAUd.d.ts +30 -0
  82. package/lib/index-VF8bcBeX.d.ts +2293 -0
  83. package/lib/{workspace-instruction-context-DytAmaPY.js → lib-gdtU_WKT.js} +159 -102
  84. package/lib/lib-gdtU_WKT.js.map +1 -0
  85. package/lib/model-BP7Gw1yk.d.ts +104 -0
  86. package/lib/node-registry-BTnnM5-k.d.ts +112 -0
  87. package/lib/node-sync-3Z9QLz2n.d.ts +89 -0
  88. package/lib/protocol-CV7LGE44.d.ts +35 -0
  89. package/lib/revision-C9sOlDZ6.d.ts +25 -0
  90. package/lib/router/router-fs.d.ts +12 -3
  91. package/lib/router/router-fs.js.map +1 -1
  92. package/lib/session-journal-pgPqcWn9.d.ts +40 -0
  93. package/lib/session-sync-EdfwUON5.js +2177 -0
  94. package/lib/session-sync-EdfwUON5.js.map +1 -0
  95. package/lib/session-sync-g6qKZzjV.d.ts +1252 -0
  96. package/lib/ssh-transport-BcIiLxsX.d.ts +55 -0
  97. package/lib/stores-CKyEt0yk.d.ts +31 -0
  98. package/lib/workspace-bindings-CsB0TIJB.d.ts +28 -0
  99. package/lib/workspace-instructions-DirYYiku.d.ts +69 -0
  100. package/package.json +20 -1
  101. package/lib/workspace-instruction-context-DytAmaPY.js.map +0 -1
@@ -0,0 +1,408 @@
1
+ import { isRecord } from "./model.js";
2
+ import { SyncPeerNotFoundError } from "./peer-manager.js";
3
+ import { MAX_SYNC_DATA_CHUNK_BYTES, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame } from "./protocol.js";
4
+ import { SyncHostKeyChallengeError, SyncHostKeyChallenges, inspectSyncHostKey } from "./host-key.js";
5
+ import { classifySshError } from "../../exec-ssh/connection.js";
6
+ import { REMOTE_SYNC_HELPER_COMMAND } from "./remote-helper.js";
7
+ import { Client } from "ssh2";
8
+ import { once } from "node:events";
9
+ //#region src/core/sync/ssh-transport.ts
10
+ const SSH_READY_TIMEOUT_MS = 3e4;
11
+ const DEFAULT_REMOTE_WEB_PORT = 3080;
12
+ var SyncSshProbeError = class extends Error {
13
+ category;
14
+ constructor(category) {
15
+ super(`SSH sync probe failed: ${category}`);
16
+ this.category = category;
17
+ this.name = "SyncSshProbeError";
18
+ }
19
+ };
20
+ var SyncSshSession = class {
21
+ client;
22
+ channel;
23
+ decoder = new SyncFrameDecoder();
24
+ reading = false;
25
+ closed = false;
26
+ inputEnded = false;
27
+ nextDataSequence = 0;
28
+ pendingError;
29
+ constructor(client, channel) {
30
+ this.client = client;
31
+ this.channel = channel;
32
+ channel.on("error", (error) => {
33
+ this.pendingError = error;
34
+ });
35
+ channel.on("exit", (code) => {
36
+ if (code !== null && code !== 0) this.pendingError = /* @__PURE__ */ new Error(`remote helper exited with code ${code}`);
37
+ });
38
+ }
39
+ async sendControl(value) {
40
+ await this.write(encodeSyncControlFrame(value));
41
+ }
42
+ async sendData(sequence, data) {
43
+ if (sequence !== this.nextDataSequence) throw new SyncProtocolError("outgoing data sequence is out of order");
44
+ await this.write(encodeSyncDataFrame(sequence, data));
45
+ this.nextDataSequence += 1;
46
+ }
47
+ endInput() {
48
+ if (this.closed || this.inputEnded) return;
49
+ this.inputEnded = true;
50
+ this.channel.end();
51
+ }
52
+ async *readFrames() {
53
+ if (this.reading) throw new SyncProtocolError("SSH sync channel can only be read once");
54
+ this.reading = true;
55
+ const channel = this.channel;
56
+ const decoder = this.decoder;
57
+ try {
58
+ for await (const chunk of channel.stdout) for (const frame of decoder.push(Buffer.from(chunk))) yield frame;
59
+ if (this.pendingError) throw new SyncProtocolError(`SSH channel error: ${this.pendingError.message}`);
60
+ decoder.finish();
61
+ } catch (error) {
62
+ if (error instanceof SyncProtocolError) throw error;
63
+ const detail = this.pendingError ? this.pendingError.message : error instanceof Error ? error.message : "unknown error";
64
+ throw new SyncProtocolError(`SSH channel read failed: ${detail}`);
65
+ }
66
+ }
67
+ close() {
68
+ if (this.closed) return;
69
+ this.closed = true;
70
+ this.channel.destroy();
71
+ this.client.end();
72
+ }
73
+ async write(frame) {
74
+ if (this.closed || this.inputEnded) throw new SyncProtocolError("SSH sync channel input is closed");
75
+ if (!this.channel.write(frame)) await once(this.channel, "drain");
76
+ }
77
+ };
78
+ function buildSyncSshConnectConfig(peer, secrets, challenges, onChallenge) {
79
+ return {
80
+ host: peer.host,
81
+ port: peer.port,
82
+ username: peer.username,
83
+ hostHash: "sha256",
84
+ readyTimeout: SSH_READY_TIMEOUT_MS,
85
+ keepaliveInterval: 0,
86
+ hostVerifier: (sha256Hex) => {
87
+ try {
88
+ const decision = inspectSyncHostKey(peer, sha256Hex, challenges);
89
+ if (!decision.trusted) onChallenge(decision.challenge);
90
+ return decision.trusted;
91
+ } catch (error) {
92
+ if (error instanceof SyncHostKeyChallengeError) onChallenge(error);
93
+ return false;
94
+ }
95
+ },
96
+ ...peer.authKind === "password" ? { password: secrets.password } : {
97
+ privateKey: secrets.privateKey,
98
+ ...secrets.passphrase ? { passphrase: secrets.passphrase } : {}
99
+ }
100
+ };
101
+ }
102
+ var SyncSshTransport = class {
103
+ peerManager;
104
+ hostKeyChallenges = new SyncHostKeyChallenges();
105
+ constructor(peerManager) {
106
+ this.peerManager = peerManager;
107
+ }
108
+ async probe(peerId) {
109
+ const { peer, session } = await this.openSession(peerId);
110
+ try {
111
+ const handshake = await this.requestHandshake(session, peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT);
112
+ if (handshake.protocolVersion !== 1) throw new SyncSshProbeError("protocol");
113
+ return handshake;
114
+ } catch (error) {
115
+ if (error instanceof SyncHostKeyChallengeError || error instanceof SyncPeerNotFoundError || error instanceof SyncSshProbeError) throw error;
116
+ if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
117
+ throw new SyncSshProbeError("helper");
118
+ } finally {
119
+ session.close();
120
+ }
121
+ }
122
+ async openSession(peerId) {
123
+ const { peer, secrets } = await this.peerManager.connectionData(peerId);
124
+ if (peer.authKind === "password" && !secrets.password) throw new SyncSshProbeError("auth");
125
+ if (peer.authKind === "key" && !secrets.privateKey) throw new SyncSshProbeError("auth");
126
+ const client = new Client();
127
+ let challenge;
128
+ let sessionOpened = false;
129
+ try {
130
+ await new Promise((resolve, reject) => {
131
+ let settled = false;
132
+ const finish = (error) => {
133
+ if (settled) return;
134
+ settled = true;
135
+ if (error) reject(error);
136
+ else resolve();
137
+ };
138
+ client.once("ready", () => finish());
139
+ client.on("error", (error) => finish(challenge ?? error));
140
+ const config = buildSyncSshConnectConfig(peer, secrets, this.hostKeyChallenges, (error) => {
141
+ challenge = error;
142
+ });
143
+ try {
144
+ client.connect(config);
145
+ } catch (error) {
146
+ finish(error);
147
+ }
148
+ });
149
+ const session = new SyncSshSession(client, await new Promise((resolve, reject) => {
150
+ client.exec(REMOTE_SYNC_HELPER_COMMAND, (error, opened) => {
151
+ if (error) reject(error);
152
+ else resolve(opened);
153
+ });
154
+ }));
155
+ sessionOpened = true;
156
+ return {
157
+ peer,
158
+ session
159
+ };
160
+ } catch (error) {
161
+ if (error instanceof SyncHostKeyChallengeError || error instanceof SyncPeerNotFoundError || error instanceof SyncSshProbeError) throw error;
162
+ if (challenge) throw challenge;
163
+ const category = classifySshError(error);
164
+ if (category === "auth" || category === "network" || category === "timeout") throw new SyncSshProbeError(category);
165
+ if (category === "hostkey") throw new SyncSshProbeError("hostkey");
166
+ if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
167
+ throw new SyncSshProbeError("helper");
168
+ } finally {
169
+ if (!sessionOpened) try {
170
+ client.end();
171
+ } catch {}
172
+ }
173
+ }
174
+ async fetchNodeManifest(peerId) {
175
+ const { peer, session } = await this.openSession(peerId);
176
+ try {
177
+ await session.sendControl({
178
+ kind: "request",
179
+ protocolVersion: 1,
180
+ operation: "node-manifest",
181
+ port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
182
+ });
183
+ session.endInput();
184
+ let responseStatus;
185
+ let bodyMode;
186
+ let responseEnded = false;
187
+ let totalBytes = 0;
188
+ const chunks = [];
189
+ for await (const frame of session.readFrames()) {
190
+ if (frame.kind === "control") {
191
+ if (frame.value.kind === "error") throw new SyncSshProbeError("helper");
192
+ if (frame.value.kind === "response" && responseStatus === void 0) {
193
+ responseStatus = typeof frame.value.status === "number" ? frame.value.status : void 0;
194
+ bodyMode = typeof frame.value.bodyMode === "string" ? frame.value.bodyMode : void 0;
195
+ continue;
196
+ }
197
+ if (frame.value.kind === "response-end" && responseStatus !== void 0 && !responseEnded) {
198
+ responseEnded = true;
199
+ continue;
200
+ }
201
+ throw new SyncProtocolError("unexpected node manifest control frame");
202
+ }
203
+ if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError("unexpected node manifest data frame");
204
+ totalBytes += frame.data.length;
205
+ if (totalBytes > 107374182400) throw new SyncProtocolError("node manifest exceeds transfer limit");
206
+ chunks.push(frame.data);
207
+ }
208
+ if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError("helper");
209
+ let value;
210
+ try {
211
+ value = JSON.parse(Buffer.concat(chunks, totalBytes).toString("utf8"));
212
+ } catch {
213
+ throw new SyncProtocolError("node manifest response is invalid JSON");
214
+ }
215
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value.ok !== true) throw new SyncSshProbeError("helper");
216
+ const manifest = value.manifest;
217
+ if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest) || manifest.protocolVersion !== 1 || typeof manifest.instanceId !== "string" || !Array.isArray(manifest.records)) throw new SyncSshProbeError("protocol");
218
+ return manifest;
219
+ } catch (error) {
220
+ if (error instanceof SyncSshProbeError) throw error;
221
+ if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
222
+ throw new SyncSshProbeError("helper");
223
+ } finally {
224
+ session.close();
225
+ }
226
+ }
227
+ async applyNodeManifest(peerId, records, sourceName) {
228
+ const requestBody = Buffer.from(JSON.stringify({
229
+ records,
230
+ sourceName
231
+ }), "utf8");
232
+ if (requestBody.length > 107374182400) throw new SyncSshProbeError("protocol");
233
+ const { peer, session } = await this.openSession(peerId);
234
+ try {
235
+ await session.sendControl({
236
+ kind: "request",
237
+ protocolVersion: 1,
238
+ operation: "node-apply",
239
+ port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
240
+ });
241
+ for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)));
242
+ await session.sendControl({ kind: "request-end" });
243
+ session.endInput();
244
+ let response;
245
+ for await (const frame of session.readFrames()) {
246
+ if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected node apply response frame");
247
+ response = frame.value;
248
+ }
249
+ if (!response || response.kind === "error") throw new SyncSshProbeError("helper");
250
+ if (response.kind !== "response" || response.status !== 200 || response.body === null || typeof response.body !== "object") throw new SyncSshProbeError("helper");
251
+ const body = response.body;
252
+ if (body.ok !== true || body.result === null || typeof body.result !== "object") throw new SyncSshProbeError("helper");
253
+ const result = body.result;
254
+ for (const key of [
255
+ "created",
256
+ "updated",
257
+ "deleted",
258
+ "pendingRemoval",
259
+ "skipped"
260
+ ]) if (!Number.isSafeInteger(result[key]) || result[key] < 0) throw new SyncSshProbeError("protocol");
261
+ return result;
262
+ } catch (error) {
263
+ if (error instanceof SyncSshProbeError) throw error;
264
+ if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
265
+ throw new SyncSshProbeError("helper");
266
+ } finally {
267
+ session.close();
268
+ }
269
+ }
270
+ async fetchSessionBundle(peerId) {
271
+ const { peer, session } = await this.openSession(peerId);
272
+ try {
273
+ await session.sendControl({
274
+ kind: "request",
275
+ protocolVersion: 1,
276
+ operation: "session-bundle",
277
+ port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
278
+ });
279
+ session.endInput();
280
+ let responseStatus;
281
+ let bodyMode;
282
+ let responseEnded = false;
283
+ let totalBytes = 0;
284
+ const chunks = [];
285
+ for await (const frame of session.readFrames()) {
286
+ if (frame.kind === "control") {
287
+ if (frame.value.kind === "error") throw new SyncSshProbeError("helper");
288
+ if (frame.value.kind === "response" && responseStatus === void 0) {
289
+ responseStatus = typeof frame.value.status === "number" ? frame.value.status : void 0;
290
+ bodyMode = typeof frame.value.bodyMode === "string" ? frame.value.bodyMode : void 0;
291
+ continue;
292
+ }
293
+ if (frame.value.kind === "response-end" && responseStatus !== void 0 && !responseEnded) {
294
+ responseEnded = true;
295
+ continue;
296
+ }
297
+ throw new SyncProtocolError("unexpected session bundle control frame");
298
+ }
299
+ if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError("unexpected session bundle data frame");
300
+ totalBytes += frame.data.length;
301
+ if (totalBytes > 107374182400) throw new SyncProtocolError("session bundle exceeds transfer limit");
302
+ chunks.push(frame.data);
303
+ }
304
+ if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError("helper");
305
+ let value;
306
+ try {
307
+ value = JSON.parse(Buffer.concat(chunks, totalBytes).toString("utf8"));
308
+ } catch {
309
+ throw new SyncProtocolError("session bundle response is invalid JSON");
310
+ }
311
+ if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError("helper");
312
+ const bundle = value.bundle;
313
+ if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== "string" || !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== "string") throw new SyncSshProbeError("protocol");
314
+ return bundle;
315
+ } catch (error) {
316
+ if (error instanceof SyncSshProbeError) throw error;
317
+ if (error instanceof SyncProtocolError) {
318
+ const probe = new SyncSshProbeError("protocol");
319
+ probe.detail = error.message;
320
+ throw probe;
321
+ }
322
+ throw new SyncSshProbeError("helper");
323
+ } finally {
324
+ session.close();
325
+ }
326
+ }
327
+ async applySessionBundle(peerId, bundle, sourceName) {
328
+ const requestBody = Buffer.from(JSON.stringify({
329
+ bundle,
330
+ sourceName,
331
+ peerId
332
+ }), "utf8");
333
+ if (requestBody.length > 107374182400) throw new SyncSshProbeError("protocol");
334
+ const { peer, session } = await this.openSession(peerId);
335
+ try {
336
+ await session.sendControl({
337
+ kind: "request",
338
+ protocolVersion: 1,
339
+ operation: "session-apply",
340
+ port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
341
+ });
342
+ for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)));
343
+ await session.sendControl({ kind: "request-end" });
344
+ session.endInput();
345
+ let response;
346
+ for await (const frame of session.readFrames()) {
347
+ if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected session apply response frame");
348
+ response = frame.value;
349
+ }
350
+ if (!response || response.kind === "error") throw new SyncSshProbeError("helper");
351
+ if (response.kind !== "response" || response.status !== 200 || response.body === null || typeof response.body !== "object") throw new SyncSshProbeError("helper");
352
+ const body = response.body;
353
+ if (body.ok !== true || body.result === null || typeof body.result !== "object") throw new SyncSshProbeError("helper");
354
+ const result = body.result;
355
+ for (const key of [
356
+ "created",
357
+ "updated",
358
+ "archived",
359
+ "deleted",
360
+ "skipped",
361
+ "unmapped"
362
+ ]) if (!Number.isSafeInteger(result[key]) || result[key] < 0) throw new SyncSshProbeError("protocol");
363
+ return result;
364
+ } catch (error) {
365
+ if (error instanceof SyncSshProbeError) throw error;
366
+ if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
367
+ throw new SyncSshProbeError("helper");
368
+ } finally {
369
+ session.close();
370
+ }
371
+ }
372
+ async trustObservedHostKey(peerId) {
373
+ const { peer } = await this.peerManager.connectionData(peerId);
374
+ const fingerprint = this.hostKeyChallenges.acceptObserved(peer);
375
+ await this.peerManager.pinHostKey(peerId, fingerprint);
376
+ }
377
+ async requestHandshake(session, port) {
378
+ await session.sendControl({
379
+ kind: "request",
380
+ protocolVersion: 1,
381
+ operation: "handshake",
382
+ port
383
+ });
384
+ session.endInput();
385
+ let response;
386
+ for await (const frame of session.readFrames()) {
387
+ if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected helper frame");
388
+ response = frame.value;
389
+ }
390
+ if (!response) throw new SyncProtocolError("helper returned no handshake response");
391
+ return validateHandshakeResponse(response);
392
+ }
393
+ };
394
+ function validateHandshakeResponse(value) {
395
+ if (value.kind === "error") throw new SyncSshProbeError("helper");
396
+ if (value.kind !== "response" || value.status !== 200 || value.body === null || typeof value.body !== "object") throw new SyncSshProbeError("helper");
397
+ const body = value.body;
398
+ if (typeof body.protocolVersion !== "number" || typeof body.instanceId !== "string" || !Array.isArray(body.capabilities) || !body.capabilities.every((capability) => typeof capability === "string")) throw new SyncSshProbeError("protocol");
399
+ return {
400
+ protocolVersion: body.protocolVersion,
401
+ instanceId: body.instanceId,
402
+ capabilities: body.capabilities
403
+ };
404
+ }
405
+ //#endregion
406
+ export { SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
407
+
408
+ //# sourceMappingURL=ssh-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-transport.js","names":[],"sources":["../../../src/core/sync/ssh-transport.ts"],"sourcesContent":["import { Client, type ConnectConfig } from 'ssh2'\nimport type { ClientChannel } from 'ssh2'\nimport { once } from 'node:events'\nimport { classifySshError } from '../../exec-ssh/connection.js'\nimport { SyncPeerManager, SyncPeerNotFoundError, type SyncPeerSecrets } from './peer-manager.js'\nimport { isRecord, type SyncPeer } from './model.js'\nimport { SyncHostKeyChallengeError, SyncHostKeyChallenges, inspectSyncHostKey } from './host-key.js'\nimport type { SyncNodeApplyResult, SyncNodeManifest } from './node-sync.js'\nimport {\n MAX_SYNC_DATA_CHUNK_BYTES,\n MAX_SYNC_TRANSFER_BYTES,\n SYNC_PROTOCOL_VERSION,\n SyncFrameDecoder,\n SyncProtocolError,\n encodeSyncControlFrame,\n encodeSyncDataFrame,\n type SyncFrame,\n} from './protocol.js'\nimport { REMOTE_SYNC_HELPER_COMMAND } from './remote-helper.js'\nimport type { SyncSessionApplyResult, SyncSessionBundle } from './session-sync.js'\n\nconst SSH_READY_TIMEOUT_MS = 30_000\nconst DEFAULT_REMOTE_WEB_PORT = 3080\n\nexport type SyncSshErrorCategory = 'auth' | 'network' | 'hostkey' | 'timeout' | 'helper' | 'protocol'\n\nexport class SyncSshProbeError extends Error {\n constructor(readonly category: SyncSshErrorCategory) {\n super(`SSH sync probe failed: ${category}`)\n this.name = 'SyncSshProbeError'\n }\n}\n\nexport interface SyncRemoteHandshake {\n protocolVersion: number\n instanceId: string\n capabilities: string[]\n}\n\nexport class SyncSshSession {\n private readonly decoder = new SyncFrameDecoder()\n private reading = false\n private closed = false\n private inputEnded = false\n private nextDataSequence = 0\n private pendingError: Error | undefined\n\n constructor(\n private readonly client: Client,\n private readonly channel: ClientChannel,\n ) {\n channel.on('error', (error: Error) => { this.pendingError = error })\n channel.on('exit', (code: number | null) => {\n if (code !== null && code !== 0) this.pendingError = new Error(`remote helper exited with code ${code}`)\n })\n }\n\n async sendControl(value: Record<string, unknown>): Promise<void> {\n await this.write(encodeSyncControlFrame(value))\n }\n\n async sendData(sequence: number, data: Buffer): Promise<void> {\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('outgoing data sequence is out of order')\n await this.write(encodeSyncDataFrame(sequence, data))\n this.nextDataSequence += 1\n }\n\n endInput(): void {\n if (this.closed || this.inputEnded) return\n this.inputEnded = true\n this.channel.end()\n }\n\n async *readFrames(): AsyncGenerator<SyncFrame> {\n if (this.reading) throw new SyncProtocolError('SSH sync channel can only be read once')\n this.reading = true\n const channel = this.channel\n const decoder = this.decoder\n try {\n for await (const chunk of channel.stdout as AsyncIterable<Buffer>) {\n for (const frame of decoder.push(Buffer.from(chunk))) yield frame\n }\n if (this.pendingError) throw new SyncProtocolError(`SSH channel error: ${this.pendingError.message}`)\n decoder.finish()\n } catch (error) {\n if (error instanceof SyncProtocolError) throw error\n const detail = this.pendingError ? this.pendingError.message : error instanceof Error ? error.message : 'unknown error'\n throw new SyncProtocolError(`SSH channel read failed: ${detail}`)\n }\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.channel.destroy()\n this.client.end()\n }\n\n private async write(frame: Buffer): Promise<void> {\n if (this.closed || this.inputEnded) throw new SyncProtocolError('SSH sync channel input is closed')\n if (!this.channel.write(frame)) await once(this.channel, 'drain')\n }\n}\n\nexport function buildSyncSshConnectConfig(\n peer: SyncPeer,\n secrets: SyncPeerSecrets,\n challenges: SyncHostKeyChallenges,\n onChallenge: (error: SyncHostKeyChallengeError) => void,\n): ConnectConfig {\n return {\n host: peer.host,\n port: peer.port,\n username: peer.username,\n hostHash: 'sha256',\n readyTimeout: SSH_READY_TIMEOUT_MS,\n keepaliveInterval: 0,\n hostVerifier: (sha256Hex: string): boolean => {\n try {\n const decision = inspectSyncHostKey(peer, sha256Hex, challenges)\n if (!decision.trusted) onChallenge(decision.challenge)\n return decision.trusted\n } catch (error) {\n if (error instanceof SyncHostKeyChallengeError) onChallenge(error)\n return false\n }\n },\n ...(peer.authKind === 'password'\n ? { password: secrets.password }\n : { privateKey: secrets.privateKey, ...(secrets.passphrase ? { passphrase: secrets.passphrase } : {}) }),\n }\n}\n\nexport class SyncSshTransport {\n private readonly hostKeyChallenges = new SyncHostKeyChallenges()\n\n constructor(private readonly peerManager: SyncPeerManager) {}\n\n async probe(peerId: string): Promise<SyncRemoteHandshake> {\n const { peer, session } = await this.openSession(peerId)\n try {\n const handshake = await this.requestHandshake(session, peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT)\n if (handshake.protocolVersion !== SYNC_PROTOCOL_VERSION) throw new SyncSshProbeError('protocol')\n return handshake\n } catch (error) {\n if (\n error instanceof SyncHostKeyChallengeError ||\n error instanceof SyncPeerNotFoundError ||\n error instanceof SyncSshProbeError\n ) {\n throw error\n }\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async openSession(peerId: string): Promise<{ peer: SyncPeer; session: SyncSshSession }> {\n const { peer, secrets } = await this.peerManager.connectionData(peerId)\n if (peer.authKind === 'password' && !secrets.password) throw new SyncSshProbeError('auth')\n if (peer.authKind === 'key' && !secrets.privateKey) throw new SyncSshProbeError('auth')\n\n const client = new Client()\n let challenge: SyncHostKeyChallengeError | undefined\n let sessionOpened = false\n try {\n await new Promise<void>((resolve, reject) => {\n let settled = false\n const finish = (error?: unknown): void => {\n if (settled) return\n settled = true\n if (error) reject(error)\n else resolve()\n }\n\n client.once('ready', () => finish())\n client.on('error', (error: Error) => finish(challenge ?? error))\n const config = buildSyncSshConnectConfig(peer, secrets, this.hostKeyChallenges, (error) => {\n challenge = error\n })\n\n try {\n client.connect(config)\n } catch (error) {\n finish(error)\n }\n })\n\n const channel = await new Promise<ClientChannel>((resolve, reject) => {\n client.exec(REMOTE_SYNC_HELPER_COMMAND, (error, opened) => {\n if (error) reject(error)\n else resolve(opened)\n })\n })\n const session = new SyncSshSession(client, channel)\n sessionOpened = true\n return { peer, session }\n } catch (error) {\n if (error instanceof SyncHostKeyChallengeError || error instanceof SyncPeerNotFoundError || error instanceof SyncSshProbeError) {\n throw error\n }\n if (challenge) throw challenge\n const category = classifySshError(error)\n if (category === 'auth' || category === 'network' || category === 'timeout') {\n throw new SyncSshProbeError(category)\n }\n if (category === 'hostkey') throw new SyncSshProbeError('hostkey')\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n if (!sessionOpened) {\n try {\n client.end()\n } catch {}\n }\n }\n }\n\n async fetchNodeManifest(peerId: string): Promise<SyncNodeManifest> {\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'node-manifest',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n session.endInput()\n\n let responseStatus: number | undefined\n let bodyMode: string | undefined\n let responseEnded = false\n let totalBytes = 0\n const chunks: Buffer[] = []\n for await (const frame of session.readFrames()) {\n if (frame.kind === 'control') {\n if (frame.value.kind === 'error') throw new SyncSshProbeError('helper')\n if (frame.value.kind === 'response' && responseStatus === undefined) {\n responseStatus = typeof frame.value.status === 'number' ? frame.value.status : undefined\n bodyMode = typeof frame.value.bodyMode === 'string' ? frame.value.bodyMode : undefined\n continue\n }\n if (frame.value.kind === 'response-end' && responseStatus !== undefined && !responseEnded) {\n responseEnded = true\n continue\n }\n throw new SyncProtocolError('unexpected node manifest control frame')\n }\n if (responseStatus === undefined || bodyMode !== 'data' || responseEnded) {\n throw new SyncProtocolError('unexpected node manifest data frame')\n }\n totalBytes += frame.data.length\n if (totalBytes > MAX_SYNC_TRANSFER_BYTES) throw new SyncProtocolError('node manifest exceeds transfer limit')\n chunks.push(frame.data)\n }\n if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError('helper')\n\n let value: unknown\n try {\n value = JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8'))\n } catch {\n throw new SyncProtocolError('node manifest response is invalid JSON')\n }\n if (value === null || typeof value !== 'object' || Array.isArray(value) || (value as Record<string, unknown>).ok !== true) {\n throw new SyncSshProbeError('helper')\n }\n const manifest = (value as { manifest?: unknown }).manifest\n if (\n manifest === null || typeof manifest !== 'object' || Array.isArray(manifest) ||\n (manifest as Record<string, unknown>).protocolVersion !== SYNC_PROTOCOL_VERSION ||\n typeof (manifest as Record<string, unknown>).instanceId !== 'string' ||\n !Array.isArray((manifest as Record<string, unknown>).records)\n ) {\n throw new SyncSshProbeError('protocol')\n }\n return manifest as SyncNodeManifest\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async applyNodeManifest(\n peerId: string,\n records: SyncNodeManifest['records'],\n sourceName: string,\n ): Promise<SyncNodeApplyResult> {\n const requestBody = Buffer.from(JSON.stringify({ records, sourceName }), 'utf8')\n if (requestBody.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'node-apply',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected node apply response frame')\n response = frame.value\n }\n if (!response || response.kind === 'error') throw new SyncSshProbeError('helper')\n if (response.kind !== 'response' || response.status !== 200 || response.body === null || typeof response.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = response.body as Record<string, unknown>\n if (body.ok !== true || body.result === null || typeof body.result !== 'object') throw new SyncSshProbeError('helper')\n const result = body.result as Record<string, unknown>\n for (const key of ['created', 'updated', 'deleted', 'pendingRemoval', 'skipped'] as const) {\n if (!Number.isSafeInteger(result[key]) || (result[key] as number) < 0) throw new SyncSshProbeError('protocol')\n }\n return result as unknown as SyncNodeApplyResult\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async fetchSessionBundle(peerId: string): Promise<SyncSessionBundle> {\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'session-bundle',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n session.endInput()\n\n let responseStatus: number | undefined\n let bodyMode: string | undefined\n let responseEnded = false\n let totalBytes = 0\n const chunks: Buffer[] = []\n for await (const frame of session.readFrames()) {\n if (frame.kind === 'control') {\n if (frame.value.kind === 'error') throw new SyncSshProbeError('helper')\n if (frame.value.kind === 'response' && responseStatus === undefined) {\n responseStatus = typeof frame.value.status === 'number' ? frame.value.status : undefined\n bodyMode = typeof frame.value.bodyMode === 'string' ? frame.value.bodyMode : undefined\n continue\n }\n if (frame.value.kind === 'response-end' && responseStatus !== undefined && !responseEnded) {\n responseEnded = true\n continue\n }\n throw new SyncProtocolError('unexpected session bundle control frame')\n }\n if (responseStatus === undefined || bodyMode !== 'data' || responseEnded) {\n throw new SyncProtocolError('unexpected session bundle data frame')\n }\n totalBytes += frame.data.length\n if (totalBytes > MAX_SYNC_TRANSFER_BYTES) throw new SyncProtocolError('session bundle exceeds transfer limit')\n chunks.push(frame.data)\n }\n if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError('helper')\n let value: unknown\n try {\n value = JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8'))\n } catch {\n throw new SyncProtocolError('session bundle response is invalid JSON')\n }\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (\n bundle.protocolVersion !== 1 || typeof bundle.instanceId !== 'string' ||\n !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== 'string'\n ) throw new SyncSshProbeError('protocol')\n return bundle as unknown as SyncSessionBundle\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) {\n const probe = new SyncSshProbeError('protocol')\n ;(probe as Error & { detail?: string }).detail = error.message\n throw probe\n }\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async applySessionBundle(peerId: string, bundle: SyncSessionBundle, sourceName: string): Promise<SyncSessionApplyResult> {\n const requestBody = Buffer.from(JSON.stringify({ bundle, sourceName, peerId }), 'utf8')\n if (requestBody.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'session-apply',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected session apply response frame')\n response = frame.value\n }\n if (!response || response.kind === 'error') throw new SyncSshProbeError('helper')\n if (response.kind !== 'response' || response.status !== 200 || response.body === null || typeof response.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = response.body as Record<string, unknown>\n if (body.ok !== true || body.result === null || typeof body.result !== 'object') throw new SyncSshProbeError('helper')\n const result = body.result as Record<string, unknown>\n for (const key of ['created', 'updated', 'archived', 'deleted', 'skipped', 'unmapped'] as const) {\n if (!Number.isSafeInteger(result[key]) || (result[key] as number) < 0) throw new SyncSshProbeError('protocol')\n }\n return result as unknown as SyncSessionApplyResult\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async trustObservedHostKey(peerId: string): Promise<void> {\n const { peer } = await this.peerManager.connectionData(peerId)\n const fingerprint = this.hostKeyChallenges.acceptObserved(peer)\n await this.peerManager.pinHostKey(peerId, fingerprint)\n }\n\n private async requestHandshake(session: SyncSshSession, port: number): Promise<SyncRemoteHandshake> {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'handshake',\n port,\n })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected helper frame')\n response = frame.value\n }\n if (!response) throw new SyncProtocolError('helper returned no handshake response')\n return validateHandshakeResponse(response)\n }\n}\n\nfunction validateHandshakeResponse(value: Record<string, unknown>): SyncRemoteHandshake {\n if (value.kind === 'error') throw new SyncSshProbeError('helper')\n if (value.kind !== 'response' || value.status !== 200 || value.body === null || typeof value.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = value.body as Record<string, unknown>\n if (\n typeof body.protocolVersion !== 'number' ||\n typeof body.instanceId !== 'string' ||\n !Array.isArray(body.capabilities) ||\n !body.capabilities.every((capability) => typeof capability === 'string')\n ) {\n throw new SyncSshProbeError('protocol')\n }\n return {\n protocolVersion: body.protocolVersion,\n instanceId: body.instanceId,\n capabilities: body.capabilities,\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { t as HlcSnapshot } from "../../revision-C9sOlDZ6.js";
2
+ import { n as SyncStateStore, r as getOrCreateSyncInstanceId, t as SyncPeerStore } from "../../stores-CKyEt0yk.js";
3
+ export { type HlcSnapshot, SyncPeerStore, SyncStateStore, getOrCreateSyncInstanceId };