@artenx/dshb 0.1.31 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/index.js +6 -1
- package/lib/core/index.js.map +1 -1
- package/lib/core/sync/coordinator.d.ts +2 -2
- package/lib/core/sync/coordinator.js +23 -6
- package/lib/core/sync/coordinator.js.map +1 -1
- package/lib/core/sync/internal-routes.d.ts +1 -1
- package/lib/core/sync/internal-routes.js +80 -4
- package/lib/core/sync/internal-routes.js.map +1 -1
- package/lib/core/sync/offline-archive.d.ts +1 -1
- package/lib/core/sync/offline-archive.js +1 -1
- package/lib/core/sync/offline-routes.js +1 -1
- package/lib/core/sync/protocol.d.ts +2 -2
- package/lib/core/sync/protocol.js +4 -1
- package/lib/core/sync/protocol.js.map +1 -1
- package/lib/core/sync/remote-helper.js +107 -76
- package/lib/core/sync/remote-helper.js.map +1 -1
- package/lib/core/sync/routes.d.ts +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 +2 -2
- package/lib/core/sync/session-sync.js +2 -2
- package/lib/core/sync/ssh-transport.d.ts +1 -1
- package/lib/core/sync/ssh-transport.js +100 -2
- package/lib/core/sync/ssh-transport.js.map +1 -1
- package/lib/{protocol-KaWZArY8.d.ts → protocol-DJ0zyVlB.d.ts} +5 -2
- package/lib/{session-sync-Zfo1Q-qT.d.ts → session-sync-BuOJvFxK.d.ts} +54 -2
- package/lib/{session-sync-BQt3HXkp.js → session-sync-CIqjuWkh.js} +455 -48
- package/lib/session-sync-CIqjuWkh.js.map +1 -0
- package/lib/{ssh-transport-Cfb_nwkY.d.ts → ssh-transport-B2oNDs0D.d.ts} +14 -4
- package/package.json +1 -1
- package/lib/session-sync-BQt3HXkp.js.map +0 -1
|
@@ -1 +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, SyncSessionEventPrefix, SyncSessionManifest } from './session-sync.js'\n\nconst SSH_READY_TIMEOUT_MS = 30_000\nconst SSH_READ_IDLE_TIMEOUT_MS = 60_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 class SyncSessionDeltaPrefixError extends Error {\n constructor() {\n super('remote session event prefix changed during transfer')\n this.name = 'SyncSessionDeltaPrefixError'\n }\n}\n\nexport interface SyncRemoteHandshake {\n protocolVersion: number\n instanceId: string\n capabilities: string[]\n}\n\nexport interface SyncRemoteNodeManifest extends SyncNodeManifest {\n capabilities?: string[]\n}\n\nexport class SyncSshSession {\n private readonly decoder = new SyncFrameDecoder()\n private readonly closeSignal = new AbortController()\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 channel.on('close', () => { this.closeSignal.abort() })\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(idleTimeoutMs = SSH_READ_IDLE_TIMEOUT_MS): 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 let idleDeadline: NodeJS.Timeout | undefined\n const armIdleDeadline = (): void => {\n clearTimeout(idleDeadline)\n idleDeadline = setTimeout(() => {\n this.pendingError = new Error(`SSH channel idle for more than ${Math.round(idleTimeoutMs / 1000)}s`)\n // Full teardown: destroying the channel alone may leave the stdout\n // stream open, which would keep a plain for-await suspended forever.\n this.close()\n }, idleTimeoutMs)\n }\n // channel.destroy() does not reliably end ssh2's stdout stream; racing the\n // iterator against the close signal guarantees this generator settles.\n // A channel close resolves with a sentinel instead of rejecting: a normal\n // helper exit closes the channel around the same moment stdout reaches EOF,\n // and only a pendingError (idle timeout, helper crash) makes it an error.\n const CLOSE: unique symbol = Symbol('channel-close')\n const closed: Promise<IteratorResult<Buffer> | typeof CLOSE> = new Promise((resolve) => {\n this.closeSignal.signal.addEventListener('abort', () => resolve(CLOSE), { once: true })\n })\n const iterator = (channel.stdout as AsyncIterable<Buffer>)[Symbol.asyncIterator]()\n try {\n armIdleDeadline()\n for (;;) {\n const next = await Promise.race([iterator.next(), closed])\n if (next === CLOSE || next.done) {\n if (this.pendingError) throw new SyncProtocolError(`SSH channel error: ${this.pendingError.message}`)\n break\n }\n armIdleDeadline()\n for (const frame of decoder.push(Buffer.from(next.value))) yield frame\n }\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 } finally {\n clearTimeout(idleDeadline)\n void iterator.return?.().catch(() => {})\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)) return\n // A frozen connection never drains; bound the wait so writes cannot hang.\n const drainDeadline = setTimeout(() => this.closeSignal.abort(), SSH_READ_IDLE_TIMEOUT_MS)\n try {\n await once(this.channel, 'drain', { signal: this.closeSignal.signal })\n } catch (error) {\n if (this.closeSignal.signal.aborted) throw new SyncProtocolError('SSH channel closed before write could drain')\n throw error\n } finally {\n clearTimeout(drainDeadline)\n }\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: 15_000,\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 clearTimeout(readyDeadline)\n if (error) reject(error)\n else resolve()\n }\n\n // ssh2's readyTimeout can be defeated by stalls behind transparent\n // proxies; this deadline guarantees the connect promise settles.\n const readyDeadline = setTimeout(() => {\n const timeout = new Error('Timed out while waiting for SSH handshake')\n ;(timeout as Error & { level?: string }).level = 'client-timeout'\n finish(timeout)\n client.end()\n }, SSH_READY_TIMEOUT_MS * 2)\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 const execDeadline = setTimeout(() => {\n reject(new Error('Timed out while waiting for the sync helper channel'))\n client.end()\n }, SSH_READY_TIMEOUT_MS)\n client.exec(REMOTE_SYNC_HELPER_COMMAND, (error, opened) => {\n clearTimeout(execDeadline)\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<SyncRemoteNodeManifest> {\n const value = await this.fetchDataOperation(peerId, 'node-manifest')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError('helper')\n const manifest = value.manifest\n if (manifest.protocolVersion !== SYNC_PROTOCOL_VERSION || typeof manifest.instanceId !== 'string' || !Array.isArray(manifest.records)) {\n throw new SyncSshProbeError('protocol')\n }\n const capabilities = value.capabilities === undefined ? undefined : value.capabilities\n if (capabilities !== undefined && (!Array.isArray(capabilities) || !capabilities.every((item) => typeof item === 'string'))) {\n throw new SyncSshProbeError('protocol')\n }\n return { ...manifest, ...(capabilities ? { capabilities } : {}) } as unknown as SyncRemoteNodeManifest\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 value = await this.fetchDataOperation(peerId, 'session-bundle')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (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 }\n return bundle as unknown as SyncSessionBundle\n }\n\n async fetchSessionManifest(peerId: string): Promise<SyncSessionManifest> {\n const value = await this.fetchDataOperation(peerId, 'session-manifest')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError('helper')\n const manifest = value.manifest\n if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== 'string' ||\n !Array.isArray(manifest.entries) || !Array.isArray(manifest.skipped) || typeof manifest.digest !== 'string') {\n throw new SyncSshProbeError('protocol')\n }\n return manifest as unknown as SyncSessionManifest\n }\n\n async fetchSessionEntries(\n peerId: string,\n keys: readonly string[],\n manifestDigest: string,\n prefixes: readonly SyncSessionEventPrefix[] = [],\n ): Promise<SyncSessionBundle> {\n const value = await this.fetchDataOperation(peerId, 'session-entries', { keys, manifestDigest, prefixes })\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (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 }\n return bundle as unknown as SyncSessionBundle\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 === 409 && isRecord(response.body) &&\n response.body.code === 'session-delta-prefix') throw new SyncSessionDeltaPrefixError()\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 SyncSessionDeltaPrefixError) throw 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 fetchDataOperation(\n peerId: string,\n operation: 'node-manifest' | 'session-bundle' | 'session-manifest' | 'session-entries',\n requestBody?: Record<string, unknown>,\n ): Promise<unknown> {\n const body = requestBody === undefined ? undefined : Buffer.from(JSON.stringify(requestBody), 'utf8')\n if (body && body.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,\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n if (body) {\n for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)))\n }\n await session.sendControl({ kind: 'request-end' })\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 ${operation} control frame`)\n }\n if (responseStatus === undefined || bodyMode !== 'data' || responseEnded) {\n throw new SyncProtocolError(`unexpected ${operation} data frame`)\n }\n totalBytes += frame.data.length\n if (totalBytes > MAX_SYNC_TRANSFER_BYTES) throw new SyncProtocolError(`${operation} exceeds transfer limit`)\n chunks.push(frame.data)\n }\n if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError('helper')\n try {\n return JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8')) as unknown\n } catch {\n throw new SyncProtocolError(`${operation} response is invalid JSON`)\n }\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 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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"}
|
|
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_SESSION_OBJECT_STREAM_BYTES,\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, SyncSessionEventPrefix, SyncSessionManifest, SyncSessionObject } from './session-sync.js'\n\nconst SSH_READY_TIMEOUT_MS = 30_000\nconst SSH_READ_IDLE_TIMEOUT_MS = 60_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 class SyncSessionDeltaPrefixError extends Error {\n constructor() {\n super('remote session event prefix changed during transfer')\n this.name = 'SyncSessionDeltaPrefixError'\n }\n}\n\nexport interface SyncRemoteHandshake {\n protocolVersion: number\n instanceId: string\n capabilities: string[]\n}\n\nexport interface SyncRemoteNodeManifest extends SyncNodeManifest {\n capabilities?: string[]\n}\n\nexport class SyncSshSession {\n private readonly decoder = new SyncFrameDecoder()\n private readonly closeSignal = new AbortController()\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 channel.on('close', () => { this.closeSignal.abort() })\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(idleTimeoutMs = SSH_READ_IDLE_TIMEOUT_MS): 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 let idleDeadline: NodeJS.Timeout | undefined\n const armIdleDeadline = (): void => {\n clearTimeout(idleDeadline)\n idleDeadline = setTimeout(() => {\n this.pendingError = new Error(`SSH channel idle for more than ${Math.round(idleTimeoutMs / 1000)}s`)\n // Full teardown: destroying the channel alone may leave the stdout\n // stream open, which would keep a plain for-await suspended forever.\n this.close()\n }, idleTimeoutMs)\n }\n // channel.destroy() does not reliably end ssh2's stdout stream; racing the\n // iterator against the close signal guarantees this generator settles.\n // A channel close resolves with a sentinel instead of rejecting: a normal\n // helper exit closes the channel around the same moment stdout reaches EOF,\n // and only a pendingError (idle timeout, helper crash) makes it an error.\n const CLOSE: unique symbol = Symbol('channel-close')\n const closed: Promise<IteratorResult<Buffer> | typeof CLOSE> = new Promise((resolve) => {\n this.closeSignal.signal.addEventListener('abort', () => resolve(CLOSE), { once: true })\n })\n const iterator = (channel.stdout as AsyncIterable<Buffer>)[Symbol.asyncIterator]()\n try {\n armIdleDeadline()\n for (;;) {\n const next = await Promise.race([iterator.next(), closed])\n if (next === CLOSE || next.done) {\n if (this.pendingError) throw new SyncProtocolError(`SSH channel error: ${this.pendingError.message}`)\n break\n }\n armIdleDeadline()\n for (const frame of decoder.push(Buffer.from(next.value))) yield frame\n }\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 } finally {\n clearTimeout(idleDeadline)\n void iterator.return?.().catch(() => {})\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)) return\n // A frozen connection never drains; bound the wait so writes cannot hang.\n const drainDeadline = setTimeout(() => this.closeSignal.abort(), SSH_READ_IDLE_TIMEOUT_MS)\n try {\n await once(this.channel, 'drain', { signal: this.closeSignal.signal })\n } catch (error) {\n if (this.closeSignal.signal.aborted) throw new SyncProtocolError('SSH channel closed before write could drain')\n throw error\n } finally {\n clearTimeout(drainDeadline)\n }\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: 15_000,\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 clearTimeout(readyDeadline)\n if (error) reject(error)\n else resolve()\n }\n\n // ssh2's readyTimeout can be defeated by stalls behind transparent\n // proxies; this deadline guarantees the connect promise settles.\n const readyDeadline = setTimeout(() => {\n const timeout = new Error('Timed out while waiting for SSH handshake')\n ;(timeout as Error & { level?: string }).level = 'client-timeout'\n finish(timeout)\n client.end()\n }, SSH_READY_TIMEOUT_MS * 2)\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 const execDeadline = setTimeout(() => {\n reject(new Error('Timed out while waiting for the sync helper channel'))\n client.end()\n }, SSH_READY_TIMEOUT_MS)\n client.exec(REMOTE_SYNC_HELPER_COMMAND, (error, opened) => {\n clearTimeout(execDeadline)\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<SyncRemoteNodeManifest> {\n const value = await this.fetchDataOperation(peerId, 'node-manifest')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError('helper')\n const manifest = value.manifest\n if (manifest.protocolVersion !== SYNC_PROTOCOL_VERSION || typeof manifest.instanceId !== 'string' || !Array.isArray(manifest.records)) {\n throw new SyncSshProbeError('protocol')\n }\n const capabilities = value.capabilities === undefined ? undefined : value.capabilities\n if (capabilities !== undefined && (!Array.isArray(capabilities) || !capabilities.every((item) => typeof item === 'string'))) {\n throw new SyncSshProbeError('protocol')\n }\n return { ...manifest, ...(capabilities ? { capabilities } : {}) } as unknown as SyncRemoteNodeManifest\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 value = await this.fetchDataOperation(peerId, 'session-bundle')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (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 }\n return bundle as unknown as SyncSessionBundle\n }\n\n async fetchSessionManifest(peerId: string): Promise<SyncSessionManifest> {\n const value = await this.fetchDataOperation(peerId, 'session-manifest')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError('helper')\n const manifest = value.manifest\n if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== 'string' ||\n !Array.isArray(manifest.entries) || !Array.isArray(manifest.skipped) || typeof manifest.digest !== 'string') {\n throw new SyncSshProbeError('protocol')\n }\n return manifest as unknown as SyncSessionManifest\n }\n\n async fetchSessionEntries(\n peerId: string,\n keys: readonly string[],\n manifestDigest: string,\n prefixes: readonly SyncSessionEventPrefix[] = [],\n deferredObjects = false,\n ): Promise<SyncSessionBundle> {\n const value = await this.fetchDataOperation(peerId, 'session-entries', { keys, manifestDigest, prefixes, deferredObjects })\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (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 }\n return bundle as unknown as SyncSessionBundle\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 === 409 && isRecord(response.body) &&\n response.body.code === 'session-delta-prefix') throw new SyncSessionDeltaPrefixError()\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 SyncSessionDeltaPrefixError) throw 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 /**\n * 拉取带外对象流:响应以帧流形式逐块 yield,调用方(收件箱)边收边落盘,\n * 内存 O(单帧)。会话在流结束/出错时关闭(生成器 finally)。\n */\n async *fetchSessionObjects(\n peerId: string,\n objects: readonly SyncSessionObject[],\n ): AsyncGenerator<Uint8Array> {\n const body = Buffer.from(JSON.stringify({ objects: [...objects] }), 'utf8')\n if (body.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-objects',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let responseStatus: number | undefined\n let bodyMode: string | undefined\n let responseEnded = false\n let totalBytes = 0\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-objects control frame')\n }\n if (responseStatus === undefined || bodyMode !== 'data' || responseEnded) {\n throw new SyncProtocolError('unexpected session-objects data frame')\n }\n totalBytes += frame.data.length\n if (totalBytes > MAX_SESSION_OBJECT_STREAM_BYTES) {\n throw new SyncProtocolError('session-objects exceeds object stream limit')\n }\n yield frame.data\n }\n if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError('helper')\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 /**\n * 推送带外对象流:把帧流按 1MB 分块写入请求体,逐块经 SSH 发送;\n * 对端流式落盘收件箱后返回 JSON 摘要。\n */\n async pushSessionObjects(peerId: string, frames: AsyncIterable<Uint8Array>): Promise<void> {\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-objects-push',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n let sequence = 0\n let totalBytes = 0\n for await (const chunk of frames) {\n if (!(chunk instanceof Uint8Array)) throw new SyncProtocolError('session-objects-push contains a non-byte chunk')\n const data = Buffer.from(chunk)\n totalBytes += data.byteLength\n if (totalBytes > MAX_SESSION_OBJECT_STREAM_BYTES) {\n throw new SyncProtocolError('session-objects-push exceeds object stream limit')\n }\n for (let offset = 0; offset < data.byteLength; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, data.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, data.byteLength)))\n }\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 !== undefined) throw new SyncProtocolError('unexpected session-objects-push frame')\n if (frame.value.kind === 'error') throw new SyncSshProbeError('helper')\n if (frame.value.kind === 'response') response = frame.value\n }\n if (!response || response.status !== 200) throw new SyncSshProbeError('helper')\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 private async fetchDataOperation(\n peerId: string,\n operation: 'node-manifest' | 'session-bundle' | 'session-manifest' | 'session-entries',\n requestBody?: Record<string, unknown>,\n ): Promise<unknown> {\n const body = requestBody === undefined ? undefined : Buffer.from(JSON.stringify(requestBody), 'utf8')\n if (body && body.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,\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n if (body) {\n for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)))\n }\n await session.sendControl({ kind: 'request-end' })\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 ${operation} control frame`)\n }\n if (responseStatus === undefined || bodyMode !== 'data' || responseEnded) {\n throw new SyncProtocolError(`unexpected ${operation} data frame`)\n }\n totalBytes += frame.data.length\n if (totalBytes > MAX_SYNC_TRANSFER_BYTES) throw new SyncProtocolError(`${operation} exceeds transfer limit`)\n chunks.push(frame.data)\n }\n if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError('helper')\n try {\n return JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8')) as unknown\n } catch {\n throw new SyncProtocolError(`${operation} response is invalid JSON`)\n }\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 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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"}
|
|
@@ -3,9 +3,12 @@ declare const SYNC_PROTOCOL_VERSION = 1;
|
|
|
3
3
|
declare const SYNC_SESSION_MANIFEST_CAPABILITY = "session-manifest-v1";
|
|
4
4
|
declare const SYNC_SESSION_EVENT_TAIL_CAPABILITY = "session-event-tail-delta-v1";
|
|
5
5
|
declare const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = "session-bundle-objects-v1";
|
|
6
|
+
declare const SYNC_SESSION_OBJECT_STREAM_CAPABILITY = "session-object-stream-v1";
|
|
6
7
|
declare const MAX_SYNC_CONTROL_FRAME_BYTES: number;
|
|
7
8
|
declare const MAX_SYNC_DATA_CHUNK_BYTES: number;
|
|
8
9
|
declare const MAX_SYNC_TRANSFER_BYTES: number;
|
|
10
|
+
declare const MAX_SESSION_OBJECT_INLINE_BYTES: number;
|
|
11
|
+
declare const MAX_SESSION_OBJECT_STREAM_BYTES: number;
|
|
9
12
|
type SyncFrame = {
|
|
10
13
|
kind: 'control';
|
|
11
14
|
value: Record<string, unknown>;
|
|
@@ -34,5 +37,5 @@ declare class SyncFrameDecoder {
|
|
|
34
37
|
private resetFrame;
|
|
35
38
|
}
|
|
36
39
|
//#endregion
|
|
37
|
-
export {
|
|
38
|
-
//# sourceMappingURL=protocol-
|
|
40
|
+
export { MAX_SYNC_TRANSFER_BYTES as a, SYNC_SESSION_EVENT_TAIL_CAPABILITY as c, SyncFrame as d, SyncFrameDecoder as f, encodeSyncDataFrame as h, MAX_SYNC_DATA_CHUNK_BYTES as i, SYNC_SESSION_MANIFEST_CAPABILITY as l, encodeSyncControlFrame as m, MAX_SESSION_OBJECT_STREAM_BYTES as n, SYNC_PROTOCOL_VERSION as o, SyncProtocolError as p, MAX_SYNC_CONTROL_FRAME_BYTES as r, SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY as s, MAX_SESSION_OBJECT_INLINE_BYTES as t, SYNC_SESSION_OBJECT_STREAM_CAPABILITY as u };
|
|
41
|
+
//# sourceMappingURL=protocol-DJ0zyVlB.d.ts.map
|
|
@@ -1056,6 +1056,7 @@ type SyncSessionObject = {
|
|
|
1056
1056
|
bytes: number;
|
|
1057
1057
|
digest: string;
|
|
1058
1058
|
data: string;
|
|
1059
|
+
deferred?: boolean;
|
|
1059
1060
|
} | {
|
|
1060
1061
|
id: string;
|
|
1061
1062
|
kind: 'file';
|
|
@@ -1063,6 +1064,7 @@ type SyncSessionObject = {
|
|
|
1063
1064
|
bytes: number;
|
|
1064
1065
|
digest: string;
|
|
1065
1066
|
data: string;
|
|
1067
|
+
deferred?: boolean;
|
|
1066
1068
|
} | {
|
|
1067
1069
|
id: string;
|
|
1068
1070
|
kind: 'spill';
|
|
@@ -1071,6 +1073,7 @@ type SyncSessionObject = {
|
|
|
1071
1073
|
digest: string;
|
|
1072
1074
|
data: string;
|
|
1073
1075
|
unavailable?: boolean;
|
|
1076
|
+
deferred?: boolean;
|
|
1074
1077
|
};
|
|
1075
1078
|
interface SyncSessionEntry {
|
|
1076
1079
|
metadata: SyncRecordMetadata;
|
|
@@ -1138,6 +1141,35 @@ interface SyncSessionApplyOptions {
|
|
|
1138
1141
|
interface ExportObjectCacheEntry {
|
|
1139
1142
|
object: SyncSessionObject;
|
|
1140
1143
|
}
|
|
1144
|
+
/**
|
|
1145
|
+
* 带外对象收件箱:对端推送/拉取的大对象(附件/spill)先落盘到
|
|
1146
|
+
* `<home>/dshb/sync-objects/<sha256hex>`,apply 阶段按 digest 流式水合进
|
|
1147
|
+
* 本地存储并删除。落盘全程流式(内存 O(单帧)),对象总体积不受堆约束。
|
|
1148
|
+
* apply 失败时收件箱保留(journal 回放仍可水合),孤儿文件由 TTL 清扫回收。
|
|
1149
|
+
*/
|
|
1150
|
+
interface SyncObjectInboxWriter {
|
|
1151
|
+
write(chunk: Uint8Array): Promise<void>;
|
|
1152
|
+
commit(): Promise<void>;
|
|
1153
|
+
abort(): Promise<void>;
|
|
1154
|
+
}
|
|
1155
|
+
declare class SyncSessionObjectInbox {
|
|
1156
|
+
private readonly root;
|
|
1157
|
+
constructor(home?: string);
|
|
1158
|
+
get path(): string;
|
|
1159
|
+
private fileFor;
|
|
1160
|
+
/**
|
|
1161
|
+
* 打开一个收件箱写入器:逐帧流式写盘 + 增量摘要校验,内存 O(帧)。
|
|
1162
|
+
* commit() 校验字节数与 digest 后原子重命名;abort() 清理临时文件。
|
|
1163
|
+
*/
|
|
1164
|
+
writerFor(digest: string, expectedBytes: number): Promise<SyncObjectInboxWriter>;
|
|
1165
|
+
/** 便捷写入:把对象流整段写进收件箱(测试与小对象用)。 */
|
|
1166
|
+
write(digest: string, bytes: number, stream: AsyncIterable<Uint8Array>): Promise<void>;
|
|
1167
|
+
/** 打开收件箱文件的只读流(apply 水合用);调用方负责消费完毕。 */
|
|
1168
|
+
read(digest: string): Promise<AsyncIterable<Uint8Array>>;
|
|
1169
|
+
delete(digest: string): Promise<void>;
|
|
1170
|
+
/** TTL 清扫:删除超过 maxAgeMs 未修改的孤儿文件。 */
|
|
1171
|
+
sweep(maxAgeMs?: number): Promise<number>;
|
|
1172
|
+
}
|
|
1141
1173
|
declare class ExportObjectCache {
|
|
1142
1174
|
private readonly entries;
|
|
1143
1175
|
private bytes;
|
|
@@ -1240,6 +1272,7 @@ interface SpillStoreLike {
|
|
|
1240
1272
|
declare function sessionEventListDigest(events: readonly SessionEvent[]): string;
|
|
1241
1273
|
declare function estimateJsonBytes(value: unknown, limit: number): number | undefined;
|
|
1242
1274
|
declare function planSessionTransfers(direction: 'pull' | 'bidirectional', localEntries: readonly SyncSessionEntry[], remoteEntries: readonly SyncSessionEntry[]): SyncSessionTransferPlan;
|
|
1275
|
+
declare function validateObjectPayload(object: unknown): SyncSessionObject;
|
|
1243
1276
|
declare function validateSyncSessionBundle(value: unknown): SyncSessionBundle;
|
|
1244
1277
|
declare function makeSyncSessionBundle(instanceId: string, entries: SyncSessionEntry[], skipped?: Array<{
|
|
1245
1278
|
sessionId: string;
|
|
@@ -1268,11 +1301,14 @@ declare class SyncSessionService {
|
|
|
1268
1301
|
private readonly exportCacheStore;
|
|
1269
1302
|
private readonly manifestPayloadCache;
|
|
1270
1303
|
private manifestPayloadCacheBytes;
|
|
1304
|
+
/** 带外对象收件箱:对象流 RPC 的落盘暂存区,apply 水合后删除。 */
|
|
1305
|
+
readonly objectInbox: SyncSessionObjectInbox;
|
|
1271
1306
|
constructor(state: SyncStateStore, nodes: SyncNodeService, registry: NodeRegistry, bindings: WorkspaceBindingsStore, getWorkspaces: () => WorkspaceRegistryLike | undefined, getPersistence: () => SessionPersistenceLike | undefined, getAttachments: () => AttachmentStoreLike | undefined, getSpills: () => SpillStoreLike | undefined, instructions: SessionInstructionSnapshotStore, journal?: SyncSessionJournalStore, now?: () => number, home?: string);
|
|
1272
1307
|
exportBundle(): Promise<SyncSessionBundle>;
|
|
1273
1308
|
exportManifest(): Promise<SyncSessionManifest>;
|
|
1274
1309
|
exportEntries(keys: readonly string[], manifestDigest?: string, prefixes?: readonly SyncSessionEventPrefix[], options?: {
|
|
1275
1310
|
sharedObjects?: boolean;
|
|
1311
|
+
deferredObjects?: boolean;
|
|
1276
1312
|
}): Promise<SyncSessionBundle>;
|
|
1277
1313
|
private rememberManifestPayloads;
|
|
1278
1314
|
applyBundle(value: unknown, sourceName: string, peerId?: string, options?: SyncSessionApplyOptions): Promise<SyncSessionApplyResult>;
|
|
@@ -1307,8 +1343,24 @@ declare class SyncSessionService {
|
|
|
1307
1343
|
private readStableSession;
|
|
1308
1344
|
private exportObjects;
|
|
1309
1345
|
private requirePersistence;
|
|
1346
|
+
/**
|
|
1347
|
+
* 把 deferred 对象编码为带外帧流:每对象一行 JSON 头 + 原始字节。
|
|
1348
|
+
* 内容从本地存储流式读取(spill/文件流式;图片体积有限走缓冲),
|
|
1349
|
+
* 逐块校验字节数与摘要,内存 O(单块)。
|
|
1350
|
+
*/
|
|
1351
|
+
streamObjects(objects: readonly SyncSessionObject[]): AsyncGenerator<Uint8Array>;
|
|
1352
|
+
private openObjectContent;
|
|
1353
|
+
/** 单帧写入上限:超过即认为流失控。 */
|
|
1354
|
+
private static readonly OBJECT_STREAM_HEADER_MAX_BYTES;
|
|
1355
|
+
/**
|
|
1356
|
+
* 解析并落盘对端推送的对象帧流:逐帧写收件箱,边写边校验摘要,
|
|
1357
|
+
* 内存 O(单帧)。返回接收的对象数。
|
|
1358
|
+
*/
|
|
1359
|
+
ingestObjectStream(source: AsyncIterable<Uint8Array>): Promise<number>;
|
|
1360
|
+
/** apply 成功后删除已水合对象的收件箱文件;失败保留供 journal 回放。 */
|
|
1361
|
+
deleteInboxedObjects(digests: Iterable<string>): Promise<void>;
|
|
1310
1362
|
private serializeMutation;
|
|
1311
1363
|
}
|
|
1312
1364
|
//#endregion
|
|
1313
|
-
export {
|
|
1314
|
-
//# sourceMappingURL=session-sync-
|
|
1365
|
+
export { sessionEventListDigest as C, validateSyncSessionManifest as D, validateSyncSessionBundle as E, planSessionTransfers as S, validateObjectPayload as T, SyncSessionWorkspace as _, SyncSessionBundle as a, makeSyncSessionBundle as b, SyncSessionEventDelta as c, SyncSessionManifest as d, SyncSessionManifestEntry as f, SyncSessionTransferPlan as g, SyncSessionService as h, SyncSessionApplyResult as i, SyncSessionEventPrefix as l, SyncSessionObjectInbox as m, SyncObjectInboxWriter as n, SyncSessionEntry as o, SyncSessionObject as p, SyncSessionApplyOptions as r, SyncSessionError as s, ExportObjectCache as t, SyncSessionInstructionSnapshot as u, WorkspaceRegistryLike as v, syncSessionEntryKey as w, makeSyncSessionManifest as x, estimateJsonBytes as y };
|
|
1366
|
+
//# sourceMappingURL=session-sync-BuOJvFxK.d.ts.map
|