@artenx/dshb 0.1.33 → 0.1.35
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/cordis.patch.yml +8 -4
- package/lib/auth/reset-cli.d.ts +15 -0
- package/lib/auth/reset-cli.js +47 -0
- package/lib/auth/reset-cli.js.map +1 -0
- package/lib/client.js +34 -3
- package/lib/client.js.map +1 -1
- package/lib/core/index.js +1 -1
- package/lib/core/routes.d.ts +1 -1
- package/lib/core/sync/coordinator.d.ts +14 -5
- package/lib/core/sync/coordinator.js +83 -18
- package/lib/core/sync/coordinator.js.map +1 -1
- package/lib/core/sync/host-key.d.ts +1 -1
- package/lib/core/sync/internal-routes.d.ts +3 -3
- package/lib/core/sync/internal-routes.js +1 -1
- package/lib/core/sync/model.d.ts +1 -1
- package/lib/core/sync/model.js.map +1 -1
- package/lib/core/sync/node-sync.d.ts +1 -1
- package/lib/core/sync/offline-archive.d.ts +3 -3
- package/lib/core/sync/offline-archive.js +1 -1
- package/lib/core/sync/offline-routes.js +1 -1
- package/lib/core/sync/peer-manager.d.ts +2 -2
- package/lib/core/sync/routes.d.ts +1 -1
- package/lib/core/sync/routes.js +19 -0
- package/lib/core/sync/routes.js.map +1 -1
- package/lib/core/sync/session-journal.d.ts +1 -1
- package/lib/core/sync/session-journal.js +16 -0
- package/lib/core/sync/session-journal.js.map +1 -1
- package/lib/core/sync/session-routes.d.ts +1 -1
- package/lib/core/sync/session-routes.js +1 -1
- package/lib/core/sync/session-sync.d.ts +1 -1
- package/lib/core/sync/session-sync.js +1 -1
- package/lib/core/sync/ssh-transport.d.ts +1 -1
- package/lib/core/sync/ssh-transport.js +29 -2
- package/lib/core/sync/ssh-transport.js.map +1 -1
- package/lib/core/sync/stores.d.ts +1 -1
- package/lib/core/sync/stores.js +1 -1
- package/lib/core/sync/stores.js.map +1 -1
- package/lib/core/workspaces.d.ts +1 -1
- package/lib/{host-key-C5afhAUd.d.ts → host-key-CSnpIrjq.d.ts} +2 -2
- package/lib/{model-BP7Gw1yk.d.ts → model-CKTBf-Pl.d.ts} +2 -2
- package/lib/{node-sync-3Z9QLz2n.d.ts → node-sync-C3Ym-HDu.d.ts} +3 -3
- package/lib/{session-journal-pgPqcWn9.d.ts → session-journal-VE_1tA2-.d.ts} +8 -2
- package/lib/{session-sync-CIqjuWkh.js → session-sync-DLr_BdvN.js} +15 -1
- package/lib/{session-sync-CIqjuWkh.js.map → session-sync-DLr_BdvN.js.map} +1 -1
- package/lib/{session-sync-BuOJvFxK.d.ts → session-sync-DrKShc5c.d.ts} +11 -5
- package/lib/{ssh-transport-C7povM0s.d.ts → ssh-transport-BxPWfu9X.d.ts} +13 -6
- package/lib/{stores-CKyEt0yk.d.ts → stores-puyTC075.d.ts} +2 -2
- package/package.json +6 -4
- package/lib/auth/startup.d.ts +0 -15
- package/lib/auth/startup.js +0 -68
- package/lib/auth/startup.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as SyncPeer, s as SyncPeerProgress } from "../../model-
|
|
2
|
-
import { n as SyncStateStore, t as SyncPeerStore } from "../../stores-
|
|
1
|
+
import { a as SyncPeer, s as SyncPeerProgress } from "../../model-CKTBf-Pl.js";
|
|
2
|
+
import { n as SyncStateStore, t as SyncPeerStore } from "../../stores-puyTC075.js";
|
|
3
3
|
import { CredentialKey } from "@deepseek-ai/dsh-credentials";
|
|
4
4
|
//#region src/core/sync/peer-manager.d.ts
|
|
5
5
|
interface SyncPeerSecrets {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SyncPeerManager } from "./peer-manager.js";
|
|
2
|
-
import { m as SyncSshTransport } from "../../ssh-transport-
|
|
2
|
+
import { m as SyncSshTransport } from "../../ssh-transport-BxPWfu9X.js";
|
|
3
3
|
import { SyncCoordinator } from "./coordinator.js";
|
|
4
4
|
import { Context } from "@deepseek-ai/cordis";
|
|
5
5
|
//#region src/core/sync/routes.d.ts
|
package/lib/core/sync/routes.js
CHANGED
|
@@ -320,6 +320,25 @@ function registerSyncPeerRoutes(ctx, manager, transport, coordinator) {
|
|
|
320
320
|
}
|
|
321
321
|
return;
|
|
322
322
|
}
|
|
323
|
+
if (action === "stop") {
|
|
324
|
+
if (!coordinator) {
|
|
325
|
+
sendJson(res, 503, {
|
|
326
|
+
ok: false,
|
|
327
|
+
error: "sync coordinator unavailable"
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
await manager.get(peerId);
|
|
333
|
+
sendJson(res, 200, {
|
|
334
|
+
ok: true,
|
|
335
|
+
stopped: coordinator.stop(peerId)
|
|
336
|
+
});
|
|
337
|
+
} catch (error) {
|
|
338
|
+
sendRunFailure(res, error);
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
323
342
|
sendJson(res, 404, {
|
|
324
343
|
ok: false,
|
|
325
344
|
error: "not found"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","names":[],"sources":["../../../src/core/sync/routes.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport { SyncHostKeyChallengeError, SyncHostKeyChallengeExpiredError } from './host-key.js'\nimport { SyncPeerInputError, SyncPeerManager, SyncPeerNotFoundError } from './peer-manager.js'\nimport { SyncRemoteProbe, SyncSshProbeError, SyncSshTransport } from './ssh-transport.js'\nimport { SyncCoordinator, SyncCoordinatorError } from './coordinator.js'\n\nconst MAX_BODY_BYTES = 128 * 1024\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve) => {\n const contentType = req.headers['content-type']?.split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n resolve(null)\n return\n }\n\n const chunks: Buffer[] = []\n let size = 0\n let settled = false\n const fail = (): void => {\n if (settled) return\n settled = true\n resolve(null)\n }\n req.on('data', (chunk: Buffer) => {\n if (settled) return\n size += chunk.length\n if (size > MAX_BODY_BYTES) {\n fail()\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n if (settled) return\n try {\n const value: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n fail()\n return\n }\n settled = true\n resolve(value as Record<string, unknown>)\n } catch {\n fail()\n }\n })\n req.on('error', fail)\n })\n}\n\nfunction pathSegments(req: IncomingMessage): string[] {\n return new URL(req.url ?? '/', 'http://x').pathname.split('/').filter(Boolean)\n}\n\nfunction splitBody(body: Record<string, unknown>): { fields: Record<string, unknown>; secrets: unknown } {\n const { secrets, ...fields } = body\n return { fields, secrets }\n}\n\nfunction sendFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncPeerInputError) {\n sendJson(res, 400, { ok: false, error: error.message })\n return\n }\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync peer operation failed' })\n}\n\nfunction sendProbeFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncHostKeyChallengeError) {\n sendJson(res, 409, {\n ok: false,\n code: `host-key-${error.decision}`,\n fingerprint: error.fingerprint,\n ...(error.expected ? { expected: error.expected } : {}),\n })\n return\n }\n if (error instanceof SyncHostKeyChallengeExpiredError) {\n sendJson(res, 409, { ok: false, code: 'host-key-challenge-expired', error: error.message })\n return\n }\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n if (error instanceof SyncSshProbeError) {\n const status = error.category === 'auth' ? 502 : error.category === 'protocol' || error.category === 'hostkey' ? 409 : 503\n sendJson(res, status, { ok: false, code: `ssh-${error.category}`, error: error.message })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync peer probe failed' })\n}\n\nfunction sendRunFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncCoordinatorError) {\n const status = error.code === 'peer-not-found' ? 404\n : error.code === 'peer-disabled' || error.code === 'sessions-unavailable' || error.code === 'revision-conflict' ? 409\n : error.code === 'session-bundle-too-large' || error.code === 'session-too-large' ? 413\n : error.code === 'session-workspace-unmapped' || error.code === 'session-format' || error.code === 'session-revision-conflict' || error.code === 'session-same-instance' ? 409\n : error.code.startsWith('ssh-') ? 502 : 500\n sendJson(res, status, { ok: false, code: error.code, error: error.message })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync run failed' })\n}\n\nexport function registerSyncPeerRoutes(\n ctx: Context,\n manager: SyncPeerManager,\n transport?: SyncSshTransport,\n coordinator?: SyncCoordinator,\n): void {\n ctx.effect(() =>\n ctx.webServer.register({\n kind: 'prefix',\n path: '/api/dshb/sync/peers',\n handler: async (req, res) => {\n const segments = pathSegments(req)\n const peerId = segments[4]\n const action = segments[5]\n if (segments.length > 6) {\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n if (req.method === 'GET' && peerId === undefined) {\n try {\n sendJson(res, 200, { ok: true, peers: await manager.list() })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer list unavailable' })\n }\n return\n }\n\n if (req.method === 'POST' && peerId === 'probe-remote') {\n // 远端配置探测:① peerId 模式——用已保存对端的存储凭据;② 显式模式——\n // 用表单填写的 SSH 连接信息(新建对端场景)。\n const body = await readBody(req)\n if (!body || (typeof body.peerId !== 'string'\n && (typeof body.host !== 'string' || !body.host.trim()\n || typeof body.username !== 'string' || !body.username.trim()\n || !Number.isInteger(body.port) || (body.port as number) < 1 || (body.port as number) > 65535\n || (body.authKind !== 'password' && body.authKind !== 'key')))) {\n sendJson(res, 400, { ok: false, error: 'either peerId or host/port/username/authKind are required' })\n return\n }\n try {\n const probe = new SyncRemoteProbe()\n let result\n if (typeof body.peerId === 'string' && body.peerId !== 'new') {\n const { peer, secrets } = await manager.connectionData(body.peerId)\n result = await probe.probeRemote(\n { host: peer.host, port: peer.port, username: peer.username, authKind: peer.authKind },\n secrets,\n peer.remoteWebPort,\n )\n } else {\n const secrets = (body.secrets ?? {}) as Record<string, unknown>\n if (body.authKind === 'password' && typeof secrets.password !== 'string') {\n sendJson(res, 400, { ok: false, error: 'SSH password is required for probing' })\n return\n }\n if (body.authKind === 'key' && typeof secrets.privateKey !== 'string') {\n sendJson(res, 400, { ok: false, error: 'SSH private key is required for probing' })\n return\n }\n const authKind = body.authKind === 'key' ? 'key' : 'password'\n result = await probe.probeRemote(\n { host: (body.host as string).trim(), port: body.port as number, username: (body.username as string).trim(), authKind },\n secrets as { password?: string; privateKey?: string; passphrase?: string },\n )\n }\n sendJson(res, 200, { ok: true, probe: result })\n } catch (error) {\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: '同步节点已不存在' })\n return\n }\n sendProbeFailure(res, error)\n }\n return\n }\n if (req.method === 'POST' && peerId === undefined) {\n const body = await readBody(req)\n if (!body) {\n sendJson(res, 400, { ok: false, error: 'request body must be JSON' })\n return\n }\n const { fields, secrets } = splitBody(body)\n try {\n sendJson(res, 201, { ok: true, peer: await manager.create(fields, secrets) })\n } catch (error) {\n sendFailure(res, error)\n }\n return\n }\n\n if (!peerId) {\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n\n if (action !== undefined) {\n if (segments.length !== 6 || req.method !== 'POST') {\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n if (!transport) {\n sendJson(res, 503, { ok: false, error: 'SSH sync transport unavailable' })\n return\n }\n if (action === 'probe') {\n try {\n const handshake = await transport.probe(peerId)\n sendJson(res, 200, { ok: true, handshake })\n } catch (error) {\n sendProbeFailure(res, error)\n }\n return\n }\n if (action === 'trust-host-key') {\n try {\n await transport.trustObservedHostKey(peerId)\n const peer = await manager.get(peerId)\n sendJson(res, 200, { ok: true, peer })\n } catch (error) {\n sendProbeFailure(res, error)\n }\n return\n }\n if (action === 'run') {\n if (!coordinator) {\n sendJson(res, 503, { ok: false, error: 'sync coordinator unavailable' })\n return\n }\n try {\n const peer = await manager.get(peerId)\n if (!peer) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n if (!peer.enabled) {\n sendJson(res, 409, { ok: false, code: 'peer-disabled', error: '此同步节点已停用' })\n return\n }\n void coordinator.run(peerId).catch(() => undefined)\n sendJson(res, 202, { ok: true, accepted: true })\n } catch (error) {\n sendRunFailure(res, error)\n }\n return\n }\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n if (req.method === 'GET') {\n try {\n const peer = await manager.get(peerId)\n if (!peer) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 200, { ok: true, peer })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer lookup failed' })\n }\n return\n }\n\n if (req.method === 'PATCH') {\n const body = await readBody(req)\n if (!body) {\n sendJson(res, 400, { ok: false, error: 'request body must be JSON' })\n return\n }\n const { fields, secrets } = splitBody(body)\n try {\n sendJson(res, 200, { ok: true, peer: await manager.update(peerId, fields, secrets) })\n } catch (error) {\n sendFailure(res, error)\n }\n return\n }\n\n if (req.method === 'DELETE') {\n try {\n if (!(await manager.remove(peerId))) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 200, { ok: true })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer removal failed' })\n }\n return\n }\n\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n },\n }),\n )\n}\n"],"mappings":";;;;;AAQA,MAAM,iBAAiB;AAEvB,SAAS,SAAS,KAAqB,QAAgB,MAAqB;CAC1E,IAAI,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;CAC5D,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAC9B;AAEA,SAAS,SAAS,KAA+D;CAC/E,OAAO,IAAI,SAAS,YAAY;EAE9B,IADoB,IAAI,QAAQ,eAAe,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,YAAY,MAClE,oBAAoB;GACtC,QAAQ,IAAI;GACZ;EACF;EAEA,MAAM,SAAmB,CAAC;EAC1B,IAAI,OAAO;EACX,IAAI,UAAU;EACd,MAAM,aAAmB;GACvB,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,IAAI;EACd;EACA,IAAI,GAAG,SAAS,UAAkB;GAChC,IAAI,SAAS;GACb,QAAQ,MAAM;GACd,IAAI,OAAO,gBAAgB;IACzB,KAAK;IACL,IAAI,QAAQ;IACZ;GACF;GACA,OAAO,KAAK,KAAK;EACnB,CAAC;EACD,IAAI,GAAG,aAAa;GAClB,IAAI,SAAS;GACb,IAAI;IACF,MAAM,QAAiB,KAAK,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC;IACxE,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;KACvE,KAAK;KACL;IACF;IACA,UAAU;IACV,QAAQ,KAAgC;GAC1C,QAAQ;IACN,KAAK;GACP;EACF,CAAC;EACD,IAAI,GAAG,SAAS,IAAI;CACtB,CAAC;AACH;AAEA,SAAS,aAAa,KAAgC;CACpD,OAAO,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;AAC/E;AAEA,SAAS,UAAU,MAAsF;CACvG,MAAM,EAAE,SAAS,GAAG,WAAW;CAC/B,OAAO;EAAE;EAAQ;CAAQ;AAC3B;AAEA,SAAS,YAAY,KAAqB,OAAsB;CAC9D,IAAI,iBAAiB,oBAAoB;EACvC,SAAS,KAAK,KAAK;GAAE,IAAI;GAAO,OAAO,MAAM;EAAQ,CAAC;EACtD;CACF;CACA,IAAI,iBAAiB,uBAAuB;EAC1C,SAAS,KAAK,KAAK;GAAE,IAAI;GAAO,OAAO;EAAsB,CAAC;EAC9D;CACF;CACA,SAAS,KAAK,KAAK;EAAE,IAAI;EAAO,OAAO;CAA6B,CAAC;AACvE;AAEA,SAAS,iBAAiB,KAAqB,OAAsB;CACnE,IAAI,iBAAiB,2BAA2B;EAC9C,SAAS,KAAK,KAAK;GACjB,IAAI;GACJ,MAAM,YAAY,MAAM;GACxB,aAAa,MAAM;GACnB,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;EACvD,CAAC;EACD;CACF;CACA,IAAI,iBAAiB,kCAAkC;EACrD,SAAS,KAAK,KAAK;GAAE,IAAI;GAAO,MAAM;GAA8B,OAAO,MAAM;EAAQ,CAAC;EAC1F;CACF;CACA,IAAI,iBAAiB,uBAAuB;EAC1C,SAAS,KAAK,KAAK;GAAE,IAAI;GAAO,OAAO;EAAsB,CAAC;EAC9D;CACF;CACA,IAAI,iBAAiB,mBAAmB;EAEtC,SAAS,KADM,MAAM,aAAa,SAAS,MAAM,MAAM,aAAa,cAAc,MAAM,aAAa,YAAY,MAAM,KACjG;GAAE,IAAI;GAAO,MAAM,OAAO,MAAM;GAAY,OAAO,MAAM;EAAQ,CAAC;EACxF;CACF;CACA,SAAS,KAAK,KAAK;EAAE,IAAI;EAAO,OAAO;CAAyB,CAAC;AACnE;AAEA,SAAS,eAAe,KAAqB,OAAsB;CACjE,IAAI,iBAAiB,sBAAsB;EAMzC,SAAS,KALM,MAAM,SAAS,mBAAmB,MAC7C,MAAM,SAAS,mBAAmB,MAAM,SAAS,0BAA0B,MAAM,SAAS,sBAAsB,MAC9G,MAAM,SAAS,8BAA8B,MAAM,SAAS,sBAAsB,MAChF,MAAM,SAAS,gCAAgC,MAAM,SAAS,oBAAoB,MAAM,SAAS,+BAA+B,MAAM,SAAS,0BAA0B,MACvK,MAAM,KAAK,WAAW,MAAM,IAAI,MAAM,KAC1B;GAAE,IAAI;GAAO,MAAM,MAAM;GAAM,OAAO,MAAM;EAAQ,CAAC;EAC3E;CACF;CACA,SAAS,KAAK,KAAK;EAAE,IAAI;EAAO,OAAO;CAAkB,CAAC;AAC5D;AAEA,SAAgB,uBACd,KACA,SACA,WACA,aACM;CACN,IAAI,aACF,IAAI,UAAU,SAAS;EACrB,MAAM;EACN,MAAM;EACN,SAAS,OAAO,KAAK,QAAQ;GAC3B,MAAM,WAAW,aAAa,GAAG;GACjC,MAAM,SAAS,SAAS;GACxB,MAAM,SAAS,SAAS;GACxB,IAAI,SAAS,SAAS,GAAG;IACvB,SAAS,KAAK,KAAK;KAAE,IAAI;KAAO,OAAO;IAAY,CAAC;IACpD;GACF;GAEA,IAAI,IAAI,WAAW,SAAS,WAAW,KAAA,GAAW;IAChD,IAAI;KACF,SAAS,KAAK,KAAK;MAAE,IAAI;MAAM,OAAO,MAAM,QAAQ,KAAK;KAAE,CAAC;IAC9D,QAAQ;KACN,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;KAA6B,CAAC;IACvE;IACA;GACF;GAEA,IAAI,IAAI,WAAW,UAAU,WAAW,gBAAgB;IAGtD,MAAM,OAAO,MAAM,SAAS,GAAG;IAC/B,IAAI,CAAC,QAAS,OAAO,KAAK,WAAW,aAC/B,OAAO,KAAK,SAAS,YAAY,CAAC,KAAK,KAAK,KAAK,KAChD,OAAO,KAAK,aAAa,YAAY,CAAC,KAAK,SAAS,KAAK,KACzD,CAAC,OAAO,UAAU,KAAK,IAAI,KAAM,KAAK,OAAkB,KAAM,KAAK,OAAkB,SACpF,KAAK,aAAa,cAAc,KAAK,aAAa,QAAU;KAClE,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;KAA4D,CAAC;KACpG;IACF;IACA,IAAI;KACF,MAAM,QAAQ,IAAI,gBAAgB;KAClC,IAAI;KACJ,IAAI,OAAO,KAAK,WAAW,YAAY,KAAK,WAAW,OAAO;MAC5D,MAAM,EAAE,MAAM,YAAY,MAAM,QAAQ,eAAe,KAAK,MAAM;MAClE,SAAS,MAAM,MAAM,YACnB;OAAE,MAAM,KAAK;OAAM,MAAM,KAAK;OAAM,UAAU,KAAK;OAAU,UAAU,KAAK;MAAS,GACrF,SACA,KAAK,aACP;KACF,OAAO;MACL,MAAM,UAAW,KAAK,WAAW,CAAC;MAClC,IAAI,KAAK,aAAa,cAAc,OAAO,QAAQ,aAAa,UAAU;OACxE,SAAS,KAAK,KAAK;QAAE,IAAI;QAAO,OAAO;OAAuC,CAAC;OAC/E;MACF;MACA,IAAI,KAAK,aAAa,SAAS,OAAO,QAAQ,eAAe,UAAU;OACrE,SAAS,KAAK,KAAK;QAAE,IAAI;QAAO,OAAO;OAA0C,CAAC;OAClF;MACF;MACA,MAAM,WAAW,KAAK,aAAa,QAAQ,QAAQ;MACnD,SAAS,MAAM,MAAM,YACnB;OAAE,MAAO,KAAK,KAAgB,KAAK;OAAG,MAAM,KAAK;OAAgB,UAAW,KAAK,SAAoB,KAAK;OAAG;MAAS,GACtH,OACF;KACF;KACA,SAAS,KAAK,KAAK;MAAE,IAAI;MAAM,OAAO;KAAO,CAAC;IAChD,SAAS,OAAO;KACd,IAAI,iBAAiB,uBAAuB;MAC1C,SAAS,KAAK,KAAK;OAAE,IAAI;OAAO,OAAO;MAAW,CAAC;MACnD;KACF;KACA,iBAAiB,KAAK,KAAK;IAC7B;IACA;GACF;GACA,IAAI,IAAI,WAAW,UAAU,WAAW,KAAA,GAAW;IACjD,MAAM,OAAO,MAAM,SAAS,GAAG;IAC/B,IAAI,CAAC,MAAM;KACT,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;KAA4B,CAAC;KACpE;IACF;IACA,MAAM,EAAE,QAAQ,YAAY,UAAU,IAAI;IAC1C,IAAI;KACF,SAAS,KAAK,KAAK;MAAE,IAAI;MAAM,MAAM,MAAM,QAAQ,OAAO,QAAQ,OAAO;KAAE,CAAC;IAC9E,SAAS,OAAO;KACd,YAAY,KAAK,KAAK;IACxB;IACA;GACF;GAEA,IAAI,CAAC,QAAQ;IACX,SAAS,KAAK,KAAK;KAAE,IAAI;KAAO,OAAO;IAAqB,CAAC;IAC7D;GACF;GAEA,IAAI,WAAW,KAAA,GAAW;IACxB,IAAI,SAAS,WAAW,KAAK,IAAI,WAAW,QAAQ;KAClD,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;KAAqB,CAAC;KAC7D;IACF;IACA,IAAI,CAAC,WAAW;KACd,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;KAAiC,CAAC;KACzE;IACF;IACA,IAAI,WAAW,SAAS;KACtB,IAAI;MAEF,SAAS,KAAK,KAAK;OAAE,IAAI;OAAM,WAAA,MADP,UAAU,MAAM,MAAM;MACL,CAAC;KAC5C,SAAS,OAAO;MACd,iBAAiB,KAAK,KAAK;KAC7B;KACA;IACF;IACA,IAAI,WAAW,kBAAkB;KAC/B,IAAI;MACF,MAAM,UAAU,qBAAqB,MAAM;MAE3C,SAAS,KAAK,KAAK;OAAE,IAAI;OAAM,MAAA,MADZ,QAAQ,IAAI,MAAM;MACD,CAAC;KACvC,SAAS,OAAO;MACd,iBAAiB,KAAK,KAAK;KAC7B;KACA;IACF;IACA,IAAI,WAAW,OAAO;KACpB,IAAI,CAAC,aAAa;MAChB,SAAS,KAAK,KAAK;OAAE,IAAI;OAAO,OAAO;MAA+B,CAAC;MACvE;KACF;KACA,IAAI;MACF,MAAM,OAAO,MAAM,QAAQ,IAAI,MAAM;MACrC,IAAI,CAAC,MAAM;OACT,SAAS,KAAK,KAAK;QAAE,IAAI;QAAO,OAAO;OAAsB,CAAC;OAC9D;MACF;MACA,IAAI,CAAC,KAAK,SAAS;OACjB,SAAS,KAAK,KAAK;QAAE,IAAI;QAAO,MAAM;QAAiB,OAAO;OAAW,CAAC;OAC1E;MACF;MACA,YAAiB,IAAI,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;MAClD,SAAS,KAAK,KAAK;OAAE,IAAI;OAAM,UAAU;MAAK,CAAC;KACjD,SAAS,OAAO;MACd,eAAe,KAAK,KAAK;KAC3B;KACA;IACF;IACA,SAAS,KAAK,KAAK;KAAE,IAAI;KAAO,OAAO;IAAY,CAAC;IACpD;GACF;GAEA,IAAI,IAAI,WAAW,OAAO;IACxB,IAAI;KACF,MAAM,OAAO,MAAM,QAAQ,IAAI,MAAM;KACrC,IAAI,CAAC,MAAM;MACT,SAAS,KAAK,KAAK;OAAE,IAAI;OAAO,OAAO;MAAsB,CAAC;MAC9D;KACF;KACA,SAAS,KAAK,KAAK;MAAE,IAAI;MAAM;KAAK,CAAC;IACvC,QAAQ;KACN,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;KAA0B,CAAC;IACpE;IACA;GACF;GAEA,IAAI,IAAI,WAAW,SAAS;IAC1B,MAAM,OAAO,MAAM,SAAS,GAAG;IAC/B,IAAI,CAAC,MAAM;KACT,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;KAA4B,CAAC;KACpE;IACF;IACA,MAAM,EAAE,QAAQ,YAAY,UAAU,IAAI;IAC1C,IAAI;KACF,SAAS,KAAK,KAAK;MAAE,IAAI;MAAM,MAAM,MAAM,QAAQ,OAAO,QAAQ,QAAQ,OAAO;KAAE,CAAC;IACtF,SAAS,OAAO;KACd,YAAY,KAAK,KAAK;IACxB;IACA;GACF;GAEA,IAAI,IAAI,WAAW,UAAU;IAC3B,IAAI;KACF,IAAI,CAAE,MAAM,QAAQ,OAAO,MAAM,GAAI;MACnC,SAAS,KAAK,KAAK;OAAE,IAAI;OAAO,OAAO;MAAsB,CAAC;MAC9D;KACF;KACA,SAAS,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC;IACjC,QAAQ;KACN,SAAS,KAAK,KAAK;MAAE,IAAI;MAAO,OAAO;KAA2B,CAAC;IACrE;IACA;GACF;GAEA,SAAS,KAAK,KAAK;IAAE,IAAI;IAAO,OAAO;GAAqB,CAAC;EAC/D;CACF,CAAC,CACH;AACF"}
|
|
1
|
+
{"version":3,"file":"routes.js","names":[],"sources":["../../../src/core/sync/routes.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport { SyncHostKeyChallengeError, SyncHostKeyChallengeExpiredError } from './host-key.js'\nimport { SyncPeerInputError, SyncPeerManager, SyncPeerNotFoundError } from './peer-manager.js'\nimport { SyncRemoteProbe, SyncSshProbeError, SyncSshTransport } from './ssh-transport.js'\nimport { SyncCoordinator, SyncCoordinatorError } from './coordinator.js'\n\nconst MAX_BODY_BYTES = 128 * 1024\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve) => {\n const contentType = req.headers['content-type']?.split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n resolve(null)\n return\n }\n\n const chunks: Buffer[] = []\n let size = 0\n let settled = false\n const fail = (): void => {\n if (settled) return\n settled = true\n resolve(null)\n }\n req.on('data', (chunk: Buffer) => {\n if (settled) return\n size += chunk.length\n if (size > MAX_BODY_BYTES) {\n fail()\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n if (settled) return\n try {\n const value: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n fail()\n return\n }\n settled = true\n resolve(value as Record<string, unknown>)\n } catch {\n fail()\n }\n })\n req.on('error', fail)\n })\n}\n\nfunction pathSegments(req: IncomingMessage): string[] {\n return new URL(req.url ?? '/', 'http://x').pathname.split('/').filter(Boolean)\n}\n\nfunction splitBody(body: Record<string, unknown>): { fields: Record<string, unknown>; secrets: unknown } {\n const { secrets, ...fields } = body\n return { fields, secrets }\n}\n\nfunction sendFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncPeerInputError) {\n sendJson(res, 400, { ok: false, error: error.message })\n return\n }\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync peer operation failed' })\n}\n\nfunction sendProbeFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncHostKeyChallengeError) {\n sendJson(res, 409, {\n ok: false,\n code: `host-key-${error.decision}`,\n fingerprint: error.fingerprint,\n ...(error.expected ? { expected: error.expected } : {}),\n })\n return\n }\n if (error instanceof SyncHostKeyChallengeExpiredError) {\n sendJson(res, 409, { ok: false, code: 'host-key-challenge-expired', error: error.message })\n return\n }\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n if (error instanceof SyncSshProbeError) {\n const status = error.category === 'auth' ? 502 : error.category === 'protocol' || error.category === 'hostkey' ? 409 : 503\n sendJson(res, status, { ok: false, code: `ssh-${error.category}`, error: error.message })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync peer probe failed' })\n}\n\nfunction sendRunFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncCoordinatorError) {\n const status = error.code === 'peer-not-found' ? 404\n : error.code === 'peer-disabled' || error.code === 'sessions-unavailable' || error.code === 'revision-conflict' ? 409\n : error.code === 'session-bundle-too-large' || error.code === 'session-too-large' ? 413\n : error.code === 'session-workspace-unmapped' || error.code === 'session-format' || error.code === 'session-revision-conflict' || error.code === 'session-same-instance' ? 409\n : error.code.startsWith('ssh-') ? 502 : 500\n sendJson(res, status, { ok: false, code: error.code, error: error.message })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync run failed' })\n}\n\nexport function registerSyncPeerRoutes(\n ctx: Context,\n manager: SyncPeerManager,\n transport?: SyncSshTransport,\n coordinator?: SyncCoordinator,\n): void {\n ctx.effect(() =>\n ctx.webServer.register({\n kind: 'prefix',\n path: '/api/dshb/sync/peers',\n handler: async (req, res) => {\n const segments = pathSegments(req)\n const peerId = segments[4]\n const action = segments[5]\n if (segments.length > 6) {\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n if (req.method === 'GET' && peerId === undefined) {\n try {\n sendJson(res, 200, { ok: true, peers: await manager.list() })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer list unavailable' })\n }\n return\n }\n\n if (req.method === 'POST' && peerId === 'probe-remote') {\n // 远端配置探测:① peerId 模式——用已保存对端的存储凭据;② 显式模式——\n // 用表单填写的 SSH 连接信息(新建对端场景)。\n const body = await readBody(req)\n if (!body || (typeof body.peerId !== 'string'\n && (typeof body.host !== 'string' || !body.host.trim()\n || typeof body.username !== 'string' || !body.username.trim()\n || !Number.isInteger(body.port) || (body.port as number) < 1 || (body.port as number) > 65535\n || (body.authKind !== 'password' && body.authKind !== 'key')))) {\n sendJson(res, 400, { ok: false, error: 'either peerId or host/port/username/authKind are required' })\n return\n }\n try {\n const probe = new SyncRemoteProbe()\n let result\n if (typeof body.peerId === 'string' && body.peerId !== 'new') {\n const { peer, secrets } = await manager.connectionData(body.peerId)\n result = await probe.probeRemote(\n { host: peer.host, port: peer.port, username: peer.username, authKind: peer.authKind },\n secrets,\n peer.remoteWebPort,\n )\n } else {\n const secrets = (body.secrets ?? {}) as Record<string, unknown>\n if (body.authKind === 'password' && typeof secrets.password !== 'string') {\n sendJson(res, 400, { ok: false, error: 'SSH password is required for probing' })\n return\n }\n if (body.authKind === 'key' && typeof secrets.privateKey !== 'string') {\n sendJson(res, 400, { ok: false, error: 'SSH private key is required for probing' })\n return\n }\n const authKind = body.authKind === 'key' ? 'key' : 'password'\n result = await probe.probeRemote(\n { host: (body.host as string).trim(), port: body.port as number, username: (body.username as string).trim(), authKind },\n secrets as { password?: string; privateKey?: string; passphrase?: string },\n )\n }\n sendJson(res, 200, { ok: true, probe: result })\n } catch (error) {\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: '同步节点已不存在' })\n return\n }\n sendProbeFailure(res, error)\n }\n return\n }\n if (req.method === 'POST' && peerId === undefined) {\n const body = await readBody(req)\n if (!body) {\n sendJson(res, 400, { ok: false, error: 'request body must be JSON' })\n return\n }\n const { fields, secrets } = splitBody(body)\n try {\n sendJson(res, 201, { ok: true, peer: await manager.create(fields, secrets) })\n } catch (error) {\n sendFailure(res, error)\n }\n return\n }\n\n if (!peerId) {\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n\n if (action !== undefined) {\n if (segments.length !== 6 || req.method !== 'POST') {\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n if (!transport) {\n sendJson(res, 503, { ok: false, error: 'SSH sync transport unavailable' })\n return\n }\n if (action === 'probe') {\n try {\n const handshake = await transport.probe(peerId)\n sendJson(res, 200, { ok: true, handshake })\n } catch (error) {\n sendProbeFailure(res, error)\n }\n return\n }\n if (action === 'trust-host-key') {\n try {\n await transport.trustObservedHostKey(peerId)\n const peer = await manager.get(peerId)\n sendJson(res, 200, { ok: true, peer })\n } catch (error) {\n sendProbeFailure(res, error)\n }\n return\n }\n if (action === 'run') {\n if (!coordinator) {\n sendJson(res, 503, { ok: false, error: 'sync coordinator unavailable' })\n return\n }\n try {\n const peer = await manager.get(peerId)\n if (!peer) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n if (!peer.enabled) {\n sendJson(res, 409, { ok: false, code: 'peer-disabled', error: '此同步节点已停用' })\n return\n }\n void coordinator.run(peerId).catch(() => undefined)\n sendJson(res, 202, { ok: true, accepted: true })\n } catch (error) {\n sendRunFailure(res, error)\n }\n return\n }\n if (action === 'stop') {\n // 停止运行中的同步:在最近的安全边界中止,丢弃未完成批次,水位不变。\n if (!coordinator) {\n sendJson(res, 503, { ok: false, error: 'sync coordinator unavailable' })\n return\n }\n try {\n await manager.get(peerId)\n sendJson(res, 200, { ok: true, stopped: coordinator.stop(peerId) })\n } catch (error) {\n sendRunFailure(res, error)\n }\n return\n }\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n if (req.method === 'GET') {\n try {\n const peer = await manager.get(peerId)\n if (!peer) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 200, { ok: true, peer })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer lookup failed' })\n }\n return\n }\n\n if (req.method === 'PATCH') {\n const body = await readBody(req)\n if (!body) {\n sendJson(res, 400, { ok: false, error: 'request body must be JSON' })\n return\n }\n const { fields, secrets } = splitBody(body)\n try {\n sendJson(res, 200, { ok: true, peer: await manager.update(peerId, fields, secrets) })\n } catch (error) {\n sendFailure(res, error)\n }\n return\n }\n\n if (req.method === 'DELETE') {\n try {\n if (!(await manager.remove(peerId))) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 200, { ok: true })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer removal failed' })\n }\n return\n }\n\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n },\n }),\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as SyncSessionJournalStore } from "../../session-journal-
|
|
1
|
+
import { t as SyncSessionJournalStore } from "../../session-journal-VE_1tA2-.js";
|
|
2
2
|
export { SyncSessionJournalStore };
|
|
@@ -146,6 +146,22 @@ var SyncSessionJournalStore = class {
|
|
|
146
146
|
force: true
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* 丢弃一个未完成批次:删除批次记录与暂存目录。用于手动停止同步——
|
|
151
|
+
* 该批未应用的条目不再落地,重启后也不会被 recoverPending 回放。
|
|
152
|
+
* 已应用完成的条目不受影响(它们是完整会话,不是半文件)。
|
|
153
|
+
*/
|
|
154
|
+
discard(batchId) {
|
|
155
|
+
if (!/^b-[a-f0-9]{32}$/.test(batchId)) throw new Error("Invalid sync session batch ID");
|
|
156
|
+
const data = this.load();
|
|
157
|
+
if (data.batches[batchId] === void 0) return;
|
|
158
|
+
delete data.batches[batchId];
|
|
159
|
+
this.save();
|
|
160
|
+
rmSync(join(this.home, "dshb", "sync-session-staging", batchId), {
|
|
161
|
+
recursive: true,
|
|
162
|
+
force: true
|
|
163
|
+
});
|
|
164
|
+
}
|
|
149
165
|
cleanupOrphanedStaging() {
|
|
150
166
|
const root = join(this.home, "dshb", "sync-session-staging");
|
|
151
167
|
let entries;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-journal.js","names":[],"sources":["../../../src/core/sync/session-journal.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto'\nimport { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { Dirent } from 'node:fs'\nimport type { SyncImportMethod } from './model.js'\n\ninterface JournalBatch {\n peerId: string\n sourceName: string\n bundleDigest: string\n startedAt: string\n recordKeys: string[]\n importedVia?: SyncImportMethod\n allowSameInstance?: boolean\n}\n\ninterface JournalData {\n version: 1\n batches: Record<string, JournalBatch>\n}\n\nfunction sessionHome(home?: string): string {\n return home ?? process.env.DSH_HOME ?? join(homedir(), '.dsh')\n}\n\nfunction validBatch(value: unknown): value is JournalBatch {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n const batch = value as Record<string, unknown>\n return typeof batch.peerId === 'string' && /^p-[a-f0-9]{24}$/.test(batch.peerId) &&\n typeof batch.sourceName === 'string' && batch.sourceName.length <= 128 && !/[\\u0000-\\u001f\\u007f]/.test(batch.sourceName) &&\n typeof batch.bundleDigest === 'string' && /^sha256:[a-f0-9]{64}$/.test(batch.bundleDigest) &&\n typeof batch.startedAt === 'string' && batch.startedAt.length <= 64 &&\n Array.isArray(batch.recordKeys) && batch.recordKeys.every((key) => typeof key === 'string' && key.length <= 8192) &&\n (batch.importedVia === undefined || batch.importedVia === 'peer' || batch.importedVia === 'offline') &&\n (batch.allowSameInstance === undefined || typeof batch.allowSameInstance === 'boolean')\n}\n\nfunction validateJournal(value: unknown): JournalData {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid sync session journal')\n const journal = value as Record<string, unknown>\n if (journal.version !== 1 || journal.batches === null || typeof journal.batches !== 'object' || Array.isArray(journal.batches)) {\n throw new Error('Invalid sync session journal')\n }\n const batches = Object.create(null) as Record<string, JournalBatch>\n for (const [id, batch] of Object.entries(journal.batches)) {\n if (!/^b-[a-f0-9]{32}$/.test(id) || !validBatch(batch)) throw new Error('Invalid sync session journal batch')\n batches[id] = {\n ...batch,\n importedVia: batch.importedVia ?? 'peer',\n allowSameInstance: batch.allowSameInstance ?? false,\n recordKeys: [...batch.recordKeys],\n }\n }\n return { version: 1, batches }\n}\n\nfunction atomicWrite(path: string, value: unknown): void {\n mkdirSync(join(path, '..'), { recursive: true, mode: 0o700 })\n const temporary = `${path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`\n let descriptor: number | undefined\n try {\n descriptor = openSync(temporary, 'wx', 0o600)\n writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\\n`, 'utf8')\n fsyncSync(descriptor)\n closeSync(descriptor)\n descriptor = undefined\n renameSync(temporary, path)\n chmodSync(path, 0o600)\n } catch (error) {\n if (descriptor !== undefined) closeSync(descriptor)\n try {\n unlinkSync(temporary)\n } catch (cleanupError) {\n if (!(cleanupError instanceof Error) || !('code' in cleanupError) || cleanupError.code !== 'ENOENT') {\n throw new AggregateError([error, cleanupError], 'session journal write and cleanup failed')\n }\n }\n throw error\n }\n}\n\nexport class SyncSessionJournalStore {\n private readonly home: string\n private readonly filePath: string\n private data: JournalData | undefined\n\n constructor(home?: string) {\n this.home = sessionHome(home)\n this.filePath = join(this.home, 'dshb', 'sync-session-journal.json')\n }\n\n begin(\n peerId: string,\n sourceName: string,\n bundleDigest: string,\n recordKeys: readonly string[],\n startedAt = new Date().toISOString(),\n importedVia: SyncImportMethod = 'peer',\n allowSameInstance = false,\n ): string {\n const batchId = `b-${createHash('sha256').update(JSON.stringify([peerId, bundleDigest])).digest('hex').slice(0, 32)}`\n const data = this.load()\n const existing = data.batches[batchId]\n const batch: JournalBatch = {\n peerId,\n sourceName,\n bundleDigest,\n startedAt: existing?.startedAt ?? startedAt,\n recordKeys: [...new Set([...(existing?.recordKeys ?? []), ...recordKeys])],\n importedVia,\n allowSameInstance,\n }\n data.batches[batchId] = batch\n this.save()\n return batchId\n }\n\n beginWithBundle(\n peerId: string,\n sourceName: string,\n bundleDigest: string,\n recordKeys: readonly string[],\n bundle: unknown,\n startedAt = new Date().toISOString(),\n importedVia: SyncImportMethod = 'peer',\n allowSameInstance = false,\n ): string {\n const batchId = `b-${createHash('sha256').update(JSON.stringify([peerId, bundleDigest])).digest('hex').slice(0, 32)}`\n this.writeBundle(batchId, bundle)\n const registered = this.begin(\n peerId,\n sourceName,\n bundleDigest,\n recordKeys,\n startedAt,\n importedVia,\n allowSameInstance,\n )\n if (registered !== batchId) throw new Error('Session journal batch identity changed')\n return batchId\n }\n\n pendingPeerIds(): string[] {\n return [...new Set(Object.values(this.load().batches).map((batch) => batch.peerId))]\n }\n\n pendingBatches(): Array<{ batchId: string; peerId: string; sourceName: string; bundleDigest: string; importedVia: SyncImportMethod; allowSameInstance: boolean }> {\n return Object.entries(this.load().batches).map(([batchId, batch]) => ({\n batchId,\n peerId: batch.peerId,\n sourceName: batch.sourceName,\n bundleDigest: batch.bundleDigest,\n importedVia: batch.importedVia ?? 'peer',\n allowSameInstance: batch.allowSameInstance ?? false,\n }))\n }\n\n findBatch(peerId: string, bundleDigest: string): {\n batchId: string\n peerId: string\n sourceName: string\n bundleDigest: string\n importedVia: SyncImportMethod\n allowSameInstance: boolean\n } | undefined {\n const match = Object.entries(this.load().batches).find(([, batch]) =>\n batch.peerId === peerId && batch.bundleDigest === bundleDigest,\n )\n if (!match) return undefined\n const [batchId, batch] = match\n return {\n batchId,\n peerId: batch.peerId,\n sourceName: batch.sourceName,\n bundleDigest: batch.bundleDigest,\n importedVia: batch.importedVia ?? 'peer',\n allowSameInstance: batch.allowSameInstance ?? false,\n }\n }\n\n writeBundle(batchId: string, value: unknown): void {\n if (!/^b-[a-f0-9]{32}$/.test(batchId)) throw new Error('Invalid sync session batch ID')\n atomicWrite(this.bundlePath(batchId), value)\n }\n\n readBundle(batchId: string): unknown | undefined {\n try {\n return JSON.parse(readFileSync(this.bundlePath(batchId), 'utf8')) as unknown\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return undefined\n throw error\n }\n }\n\n writePrepared(batchId: string, recordKey: string, value: unknown): void {\n if (!/^b-[a-f0-9]{32}$/.test(batchId)) throw new Error('Invalid sync session batch ID')\n const file = this.preparedPath(batchId, recordKey)\n atomicWrite(file, value)\n }\n\n readPrepared(batchId: string, recordKey: string): unknown | undefined {\n const file = this.preparedPath(batchId, recordKey)\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as unknown\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return undefined\n throw error\n }\n }\n\n complete(batchId: string): void {\n const data = this.load()\n delete data.batches[batchId]\n this.save()\n rmSync(join(this.home, 'dshb', 'sync-session-staging', batchId), { recursive: true, force: true })\n }\n\n cleanupOrphanedStaging(): void {\n const root = join(this.home, 'dshb', 'sync-session-staging')\n let entries: Dirent[]\n try {\n entries = readdirSync(root, { withFileTypes: true })\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return\n throw error\n }\n const activeBatches = new Set(Object.keys(this.load().batches))\n for (const entry of entries) {\n if (entry.isDirectory() && /^b-[a-f0-9]{32}$/.test(entry.name) && !activeBatches.has(entry.name)) {\n rmSync(join(root, entry.name), { recursive: true, force: true })\n }\n }\n }\n\n private preparedPath(batchId: string, recordKey: string): string {\n const digest = createHash('sha256').update(recordKey).digest('hex')\n return join(this.home, 'dshb', 'sync-session-staging', batchId, `${digest}.json`)\n }\n\n private bundlePath(batchId: string): string {\n return join(this.home, 'dshb', 'sync-session-staging', batchId, 'bundle.json')\n }\n\n private load(): JournalData {\n if (this.data) return this.data\n try {\n this.data = validateJournal(JSON.parse(readFileSync(this.filePath, 'utf8')) as unknown)\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {\n this.data = { version: 1, batches: {} }\n } else {\n throw error\n }\n }\n return this.data\n }\n\n private save(): void {\n atomicWrite(this.filePath, this.load())\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-journal.js","names":[],"sources":["../../../src/core/sync/session-journal.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto'\nimport { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { Dirent } from 'node:fs'\nimport type { SyncImportMethod } from './model.js'\n\ninterface JournalBatch {\n peerId: string\n sourceName: string\n bundleDigest: string\n startedAt: string\n recordKeys: string[]\n importedVia?: SyncImportMethod\n allowSameInstance?: boolean\n}\n\ninterface JournalData {\n version: 1\n batches: Record<string, JournalBatch>\n}\n\nfunction sessionHome(home?: string): string {\n return home ?? process.env.DSH_HOME ?? join(homedir(), '.dsh')\n}\n\nfunction validBatch(value: unknown): value is JournalBatch {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n const batch = value as Record<string, unknown>\n return typeof batch.peerId === 'string' && /^p-[a-f0-9]{24}$/.test(batch.peerId) &&\n typeof batch.sourceName === 'string' && batch.sourceName.length <= 128 && !/[\\u0000-\\u001f\\u007f]/.test(batch.sourceName) &&\n typeof batch.bundleDigest === 'string' && /^sha256:[a-f0-9]{64}$/.test(batch.bundleDigest) &&\n typeof batch.startedAt === 'string' && batch.startedAt.length <= 64 &&\n Array.isArray(batch.recordKeys) && batch.recordKeys.every((key) => typeof key === 'string' && key.length <= 8192) &&\n (batch.importedVia === undefined || batch.importedVia === 'peer' || batch.importedVia === 'offline') &&\n (batch.allowSameInstance === undefined || typeof batch.allowSameInstance === 'boolean')\n}\n\nfunction validateJournal(value: unknown): JournalData {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid sync session journal')\n const journal = value as Record<string, unknown>\n if (journal.version !== 1 || journal.batches === null || typeof journal.batches !== 'object' || Array.isArray(journal.batches)) {\n throw new Error('Invalid sync session journal')\n }\n const batches = Object.create(null) as Record<string, JournalBatch>\n for (const [id, batch] of Object.entries(journal.batches)) {\n if (!/^b-[a-f0-9]{32}$/.test(id) || !validBatch(batch)) throw new Error('Invalid sync session journal batch')\n batches[id] = {\n ...batch,\n importedVia: batch.importedVia ?? 'peer',\n allowSameInstance: batch.allowSameInstance ?? false,\n recordKeys: [...batch.recordKeys],\n }\n }\n return { version: 1, batches }\n}\n\nfunction atomicWrite(path: string, value: unknown): void {\n mkdirSync(join(path, '..'), { recursive: true, mode: 0o700 })\n const temporary = `${path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`\n let descriptor: number | undefined\n try {\n descriptor = openSync(temporary, 'wx', 0o600)\n writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\\n`, 'utf8')\n fsyncSync(descriptor)\n closeSync(descriptor)\n descriptor = undefined\n renameSync(temporary, path)\n chmodSync(path, 0o600)\n } catch (error) {\n if (descriptor !== undefined) closeSync(descriptor)\n try {\n unlinkSync(temporary)\n } catch (cleanupError) {\n if (!(cleanupError instanceof Error) || !('code' in cleanupError) || cleanupError.code !== 'ENOENT') {\n throw new AggregateError([error, cleanupError], 'session journal write and cleanup failed')\n }\n }\n throw error\n }\n}\n\nexport class SyncSessionJournalStore {\n private readonly home: string\n private readonly filePath: string\n private data: JournalData | undefined\n\n constructor(home?: string) {\n this.home = sessionHome(home)\n this.filePath = join(this.home, 'dshb', 'sync-session-journal.json')\n }\n\n begin(\n peerId: string,\n sourceName: string,\n bundleDigest: string,\n recordKeys: readonly string[],\n startedAt = new Date().toISOString(),\n importedVia: SyncImportMethod = 'peer',\n allowSameInstance = false,\n ): string {\n const batchId = `b-${createHash('sha256').update(JSON.stringify([peerId, bundleDigest])).digest('hex').slice(0, 32)}`\n const data = this.load()\n const existing = data.batches[batchId]\n const batch: JournalBatch = {\n peerId,\n sourceName,\n bundleDigest,\n startedAt: existing?.startedAt ?? startedAt,\n recordKeys: [...new Set([...(existing?.recordKeys ?? []), ...recordKeys])],\n importedVia,\n allowSameInstance,\n }\n data.batches[batchId] = batch\n this.save()\n return batchId\n }\n\n beginWithBundle(\n peerId: string,\n sourceName: string,\n bundleDigest: string,\n recordKeys: readonly string[],\n bundle: unknown,\n startedAt = new Date().toISOString(),\n importedVia: SyncImportMethod = 'peer',\n allowSameInstance = false,\n ): string {\n const batchId = `b-${createHash('sha256').update(JSON.stringify([peerId, bundleDigest])).digest('hex').slice(0, 32)}`\n this.writeBundle(batchId, bundle)\n const registered = this.begin(\n peerId,\n sourceName,\n bundleDigest,\n recordKeys,\n startedAt,\n importedVia,\n allowSameInstance,\n )\n if (registered !== batchId) throw new Error('Session journal batch identity changed')\n return batchId\n }\n\n pendingPeerIds(): string[] {\n return [...new Set(Object.values(this.load().batches).map((batch) => batch.peerId))]\n }\n\n pendingBatches(): Array<{ batchId: string; peerId: string; sourceName: string; bundleDigest: string; importedVia: SyncImportMethod; allowSameInstance: boolean }> {\n return Object.entries(this.load().batches).map(([batchId, batch]) => ({\n batchId,\n peerId: batch.peerId,\n sourceName: batch.sourceName,\n bundleDigest: batch.bundleDigest,\n importedVia: batch.importedVia ?? 'peer',\n allowSameInstance: batch.allowSameInstance ?? false,\n }))\n }\n\n findBatch(peerId: string, bundleDigest: string): {\n batchId: string\n peerId: string\n sourceName: string\n bundleDigest: string\n importedVia: SyncImportMethod\n allowSameInstance: boolean\n } | undefined {\n const match = Object.entries(this.load().batches).find(([, batch]) =>\n batch.peerId === peerId && batch.bundleDigest === bundleDigest,\n )\n if (!match) return undefined\n const [batchId, batch] = match\n return {\n batchId,\n peerId: batch.peerId,\n sourceName: batch.sourceName,\n bundleDigest: batch.bundleDigest,\n importedVia: batch.importedVia ?? 'peer',\n allowSameInstance: batch.allowSameInstance ?? false,\n }\n }\n\n writeBundle(batchId: string, value: unknown): void {\n if (!/^b-[a-f0-9]{32}$/.test(batchId)) throw new Error('Invalid sync session batch ID')\n atomicWrite(this.bundlePath(batchId), value)\n }\n\n readBundle(batchId: string): unknown | undefined {\n try {\n return JSON.parse(readFileSync(this.bundlePath(batchId), 'utf8')) as unknown\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return undefined\n throw error\n }\n }\n\n writePrepared(batchId: string, recordKey: string, value: unknown): void {\n if (!/^b-[a-f0-9]{32}$/.test(batchId)) throw new Error('Invalid sync session batch ID')\n const file = this.preparedPath(batchId, recordKey)\n atomicWrite(file, value)\n }\n\n readPrepared(batchId: string, recordKey: string): unknown | undefined {\n const file = this.preparedPath(batchId, recordKey)\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as unknown\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return undefined\n throw error\n }\n }\n\n complete(batchId: string): void {\n const data = this.load()\n delete data.batches[batchId]\n this.save()\n rmSync(join(this.home, 'dshb', 'sync-session-staging', batchId), { recursive: true, force: true })\n }\n\n /**\n * 丢弃一个未完成批次:删除批次记录与暂存目录。用于手动停止同步——\n * 该批未应用的条目不再落地,重启后也不会被 recoverPending 回放。\n * 已应用完成的条目不受影响(它们是完整会话,不是半文件)。\n */\n discard(batchId: string): void {\n if (!/^b-[a-f0-9]{32}$/.test(batchId)) throw new Error('Invalid sync session batch ID')\n const data = this.load()\n if (data.batches[batchId] === undefined) return\n delete data.batches[batchId]\n this.save()\n rmSync(join(this.home, 'dshb', 'sync-session-staging', batchId), { recursive: true, force: true })\n }\n\n cleanupOrphanedStaging(): void {\n const root = join(this.home, 'dshb', 'sync-session-staging')\n let entries: Dirent[]\n try {\n entries = readdirSync(root, { withFileTypes: true })\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return\n throw error\n }\n const activeBatches = new Set(Object.keys(this.load().batches))\n for (const entry of entries) {\n if (entry.isDirectory() && /^b-[a-f0-9]{32}$/.test(entry.name) && !activeBatches.has(entry.name)) {\n rmSync(join(root, entry.name), { recursive: true, force: true })\n }\n }\n }\n\n private preparedPath(batchId: string, recordKey: string): string {\n const digest = createHash('sha256').update(recordKey).digest('hex')\n return join(this.home, 'dshb', 'sync-session-staging', batchId, `${digest}.json`)\n }\n\n private bundlePath(batchId: string): string {\n return join(this.home, 'dshb', 'sync-session-staging', batchId, 'bundle.json')\n }\n\n private load(): JournalData {\n if (this.data) return this.data\n try {\n this.data = validateJournal(JSON.parse(readFileSync(this.filePath, 'utf8')) as unknown)\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {\n this.data = { version: 1, batches: {} }\n } else {\n throw error\n }\n }\n return this.data\n }\n\n private save(): void {\n atomicWrite(this.filePath, this.load())\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as SyncSessionService } from "../../session-sync-
|
|
1
|
+
import { h as SyncSessionService } from "../../session-sync-DrKShc5c.js";
|
|
2
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
3
3
|
//#region src/core/sync/session-routes.d.ts
|
|
4
4
|
declare function registerSyncSessionRoutes(ctx: Context, sessions: SyncSessionService): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as sessionEventListDigest, D as validateSyncSessionManifest, E as validateSyncSessionBundle, S as planSessionTransfers, T as validateObjectPayload, _ as SyncSessionWorkspace, a as SyncSessionBundle, b as makeSyncSessionBundle, c as SyncSessionEventDelta, d as SyncSessionManifest, f as SyncSessionManifestEntry, g as SyncSessionTransferPlan, h as SyncSessionService, i as SyncSessionApplyResult, l as SyncSessionEventPrefix, m as SyncSessionObjectInbox, n as SyncObjectInboxWriter, o as SyncSessionEntry, p as SyncSessionObject, r as SyncSessionApplyOptions, s as SyncSessionError, t as ExportObjectCache, u as SyncSessionInstructionSnapshot, v as WorkspaceRegistryLike, w as syncSessionEntryKey, x as makeSyncSessionManifest, y as estimateJsonBytes } from "../../session-sync-
|
|
1
|
+
import { C as sessionEventListDigest, D as validateSyncSessionManifest, E as validateSyncSessionBundle, S as planSessionTransfers, T as validateObjectPayload, _ as SyncSessionWorkspace, a as SyncSessionBundle, b as makeSyncSessionBundle, c as SyncSessionEventDelta, d as SyncSessionManifest, f as SyncSessionManifestEntry, g as SyncSessionTransferPlan, h as SyncSessionService, i as SyncSessionApplyResult, l as SyncSessionEventPrefix, m as SyncSessionObjectInbox, n as SyncObjectInboxWriter, o as SyncSessionEntry, p as SyncSessionObject, r as SyncSessionApplyOptions, s as SyncSessionError, t as ExportObjectCache, u as SyncSessionInstructionSnapshot, v as WorkspaceRegistryLike, w as syncSessionEntryKey, x as makeSyncSessionManifest, y as estimateJsonBytes } from "../../session-sync-DrKShc5c.js";
|
|
2
2
|
export { ExportObjectCache, SyncObjectInboxWriter, SyncSessionApplyOptions, SyncSessionApplyResult, SyncSessionBundle, SyncSessionEntry, SyncSessionError, SyncSessionEventDelta, SyncSessionEventPrefix, SyncSessionInstructionSnapshot, SyncSessionManifest, SyncSessionManifestEntry, SyncSessionObject, SyncSessionObjectInbox, SyncSessionService, SyncSessionTransferPlan, SyncSessionWorkspace, WorkspaceRegistryLike, estimateJsonBytes, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateObjectPayload, validateSyncSessionBundle, validateSyncSessionManifest };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as estimateJsonBytes, c as planSessionTransfers, d as validateObjectPayload, f as validateSyncSessionBundle, i as SyncSessionService, l as sessionEventListDigest, n as SyncSessionError, o as makeSyncSessionBundle, p as validateSyncSessionManifest, r as SyncSessionObjectInbox, s as makeSyncSessionManifest, t as ExportObjectCache, u as syncSessionEntryKey } from "../../session-sync-
|
|
1
|
+
import { a as estimateJsonBytes, c as planSessionTransfers, d as validateObjectPayload, f as validateSyncSessionBundle, i as SyncSessionService, l as sessionEventListDigest, n as SyncSessionError, o as makeSyncSessionBundle, p as validateSyncSessionManifest, r as SyncSessionObjectInbox, s as makeSyncSessionManifest, t as ExportObjectCache, u as syncSessionEntryKey } from "../../session-sync-DLr_BdvN.js";
|
|
2
2
|
export { ExportObjectCache, SyncSessionError, SyncSessionObjectInbox, SyncSessionService, estimateJsonBytes, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateObjectPayload, validateSyncSessionBundle, validateSyncSessionManifest };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as parseRemoteProbeOutput, a as SyncRemoteProbe, c as SyncRemoteProbeProfile, d as SyncSshErrorCategory, f as SyncSshProbeError, g as buildSyncSshConnectConfig, h as buildRemoteProbeScript, i as SyncRemoteNodeManifest, l as SyncRemoteProbeResult, m as SyncSshTransport, n as REMOTE_PROBE_END_MARKER, o as SyncRemoteProbeDraft, p as SyncSshSession, r as SyncRemoteHandshake, s as SyncRemoteProbeHandshake, t as REMOTE_PROBE_BEGIN_MARKER, u as SyncSessionDeltaPrefixError } from "../../ssh-transport-
|
|
1
|
+
import { _ as parseRemoteProbeOutput, a as SyncRemoteProbe, c as SyncRemoteProbeProfile, d as SyncSshErrorCategory, f as SyncSshProbeError, g as buildSyncSshConnectConfig, h as buildRemoteProbeScript, i as SyncRemoteNodeManifest, l as SyncRemoteProbeResult, m as SyncSshTransport, n as REMOTE_PROBE_END_MARKER, o as SyncRemoteProbeDraft, p as SyncSshSession, r as SyncRemoteHandshake, s as SyncRemoteProbeHandshake, t as REMOTE_PROBE_BEGIN_MARKER, u as SyncSessionDeltaPrefixError } from "../../ssh-transport-BxPWfu9X.js";
|
|
2
2
|
export { REMOTE_PROBE_BEGIN_MARKER, REMOTE_PROBE_END_MARKER, SyncRemoteHandshake, SyncRemoteNodeManifest, SyncRemoteProbe, SyncRemoteProbeDraft, SyncRemoteProbeHandshake, SyncRemoteProbeProfile, SyncRemoteProbeResult, SyncSessionDeltaPrefixError, SyncSshErrorCategory, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildRemoteProbeScript, buildSyncSshConnectConfig, parseRemoteProbeOutput };
|
|
@@ -27,6 +27,7 @@ var SyncSessionDeltaPrefixError = class extends Error {
|
|
|
27
27
|
var SyncSshSession = class {
|
|
28
28
|
client;
|
|
29
29
|
channel;
|
|
30
|
+
onClosed;
|
|
30
31
|
decoder = new SyncFrameDecoder();
|
|
31
32
|
closeSignal = new AbortController();
|
|
32
33
|
reading = false;
|
|
@@ -34,9 +35,10 @@ var SyncSshSession = class {
|
|
|
34
35
|
inputEnded = false;
|
|
35
36
|
nextDataSequence = 0;
|
|
36
37
|
pendingError;
|
|
37
|
-
constructor(client, channel) {
|
|
38
|
+
constructor(client, channel, onClosed) {
|
|
38
39
|
this.client = client;
|
|
39
40
|
this.channel = channel;
|
|
41
|
+
this.onClosed = onClosed;
|
|
40
42
|
channel.on("error", (error) => {
|
|
41
43
|
this.pendingError = error;
|
|
42
44
|
});
|
|
@@ -104,6 +106,9 @@ var SyncSshSession = class {
|
|
|
104
106
|
this.closed = true;
|
|
105
107
|
this.channel.destroy();
|
|
106
108
|
this.client.end();
|
|
109
|
+
try {
|
|
110
|
+
this.onClosed?.();
|
|
111
|
+
} catch {}
|
|
107
112
|
}
|
|
108
113
|
async write(frame) {
|
|
109
114
|
if (this.closed || this.inputEnded) throw new SyncProtocolError("SSH sync channel input is closed");
|
|
@@ -146,9 +151,30 @@ function buildSyncSshConnectConfig(peer, secrets, challenges, onChallenge) {
|
|
|
146
151
|
var SyncSshTransport = class {
|
|
147
152
|
peerManager;
|
|
148
153
|
hostKeyChallenges = new SyncHostKeyChallenges();
|
|
154
|
+
/** 每对端当前打开的 SSH 会话:手动停止同步时立即关闭通道,等待中的 readFrames 随即返回。 */
|
|
155
|
+
openSessions = /* @__PURE__ */ new Map();
|
|
149
156
|
constructor(peerManager) {
|
|
150
157
|
this.peerManager = peerManager;
|
|
151
158
|
}
|
|
159
|
+
trackSession(peerId, session) {
|
|
160
|
+
const sessions = this.openSessions.get(peerId) ?? /* @__PURE__ */ new Set();
|
|
161
|
+
sessions.add(session);
|
|
162
|
+
this.openSessions.set(peerId, sessions);
|
|
163
|
+
}
|
|
164
|
+
untrackSession(peerId, session) {
|
|
165
|
+
const sessions = this.openSessions.get(peerId);
|
|
166
|
+
sessions?.delete(session);
|
|
167
|
+
if (sessions !== void 0 && sessions.size === 0) this.openSessions.delete(peerId);
|
|
168
|
+
}
|
|
169
|
+
/** 立即关闭该对端的全部活跃 SSH 会话(幂等;用于手动停止同步)。 */
|
|
170
|
+
stopActiveSessions(peerId) {
|
|
171
|
+
const sessions = this.openSessions.get(peerId);
|
|
172
|
+
if (sessions === void 0) return 0;
|
|
173
|
+
for (const session of [...sessions]) try {
|
|
174
|
+
session.close();
|
|
175
|
+
} catch {}
|
|
176
|
+
return sessions.size;
|
|
177
|
+
}
|
|
152
178
|
async probe(peerId) {
|
|
153
179
|
const { peer, session } = await this.openSession(peerId);
|
|
154
180
|
try {
|
|
@@ -207,8 +233,9 @@ var SyncSshTransport = class {
|
|
|
207
233
|
if (error) reject(error);
|
|
208
234
|
else resolve(opened);
|
|
209
235
|
});
|
|
210
|
-
}));
|
|
236
|
+
}), () => this.untrackSession(peer.id, session));
|
|
211
237
|
sessionOpened = true;
|
|
238
|
+
this.trackSession(peer.id, session);
|
|
212
239
|
return {
|
|
213
240
|
peer,
|
|
214
241
|
session
|