@artenx/dshb 0.1.21 → 0.1.23
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/README.md +1 -1
- package/lib/auth/index.js +22 -11
- package/lib/auth/index.js.map +1 -1
- package/lib/client.js +1 -1
- package/lib/client.js.map +1 -1
- package/lib/core/index.js +2 -2
- package/lib/core/index.js.map +1 -1
- package/lib/core/sync/coordinator.d.ts +2 -2
- package/lib/core/sync/coordinator.js +121 -15
- 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 +58 -9
- 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 +10 -4
- 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 +2 -2
- package/lib/core/sync/ssh-transport.js +97 -104
- package/lib/core/sync/ssh-transport.js.map +1 -1
- package/lib/{protocol-CV7LGE44.d.ts → protocol-KaWZArY8.d.ts} +5 -2
- package/lib/{session-sync-EdfwUON5.js → session-sync-C0uoizyB.js} +834 -98
- package/lib/session-sync-C0uoizyB.js.map +1 -0
- package/lib/{session-sync-g6qKZzjV.d.ts → session-sync-C2j2rk9K.d.ts} +56 -4
- package/lib/{ssh-transport-BcIiLxsX.d.ts → ssh-transport-DGkQh-HA.d.ts} +14 -5
- package/package.json +1 -1
- package/lib/session-sync-EdfwUON5.js.map +0 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
2
|
//#region src/core/sync/protocol.ts
|
|
3
3
|
const SYNC_PROTOCOL_VERSION = 1;
|
|
4
|
+
const SYNC_SESSION_MANIFEST_CAPABILITY = "session-manifest-v1";
|
|
5
|
+
const SYNC_SESSION_EVENT_TAIL_CAPABILITY = "session-event-tail-delta-v1";
|
|
6
|
+
const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = "session-bundle-objects-v1";
|
|
4
7
|
const MAX_SYNC_CONTROL_FRAME_BYTES = 65536;
|
|
5
8
|
const MAX_SYNC_DATA_CHUNK_BYTES = 1048576;
|
|
6
9
|
const MAX_SYNC_TRANSFER_BYTES = 107374182400;
|
|
@@ -119,6 +122,6 @@ var SyncFrameDecoder = class {
|
|
|
119
122
|
}
|
|
120
123
|
};
|
|
121
124
|
//#endregion
|
|
122
|
-
export { MAX_SYNC_CONTROL_FRAME_BYTES, MAX_SYNC_DATA_CHUNK_BYTES, MAX_SYNC_TRANSFER_BYTES, SYNC_PROTOCOL_VERSION, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame };
|
|
125
|
+
export { MAX_SYNC_CONTROL_FRAME_BYTES, MAX_SYNC_DATA_CHUNK_BYTES, MAX_SYNC_TRANSFER_BYTES, SYNC_PROTOCOL_VERSION, SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY, SYNC_SESSION_EVENT_TAIL_CAPABILITY, SYNC_SESSION_MANIFEST_CAPABILITY, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame };
|
|
123
126
|
|
|
124
127
|
//# sourceMappingURL=protocol.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","names":[],"sources":["../../../src/core/sync/protocol.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto'\n\nexport const SYNC_PROTOCOL_VERSION = 1\nexport const MAX_SYNC_CONTROL_FRAME_BYTES = 64 * 1024\nexport const MAX_SYNC_DATA_CHUNK_BYTES = 1024 * 1024\nexport const MAX_SYNC_TRANSFER_BYTES = 100 * 1024 * 1024 * 1024\n\nconst FRAME_HEADER_BYTES = 5\nconst CONTROL_FRAME = 0\nconst DATA_FRAME = 1\nconst DATA_METADATA_BYTES = 36\n\nexport type SyncFrame =\n | { kind: 'control'; value: Record<string, unknown> }\n | { kind: 'data'; sequence: number; digest: string; data: Buffer }\n\nexport class SyncProtocolError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SyncProtocolError'\n }\n}\n\nfunction makeHeader(type: number, payloadLength: number): Buffer {\n const header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n header[0] = type\n header.writeUInt32BE(payloadLength, 1)\n return header\n}\n\nexport function encodeSyncControlFrame(value: Record<string, unknown>): Buffer {\n const payload = Buffer.from(`${JSON.stringify(value)}\\n`, 'utf8')\n if (payload.length > MAX_SYNC_CONTROL_FRAME_BYTES) throw new SyncProtocolError('control frame exceeds size limit')\n return Buffer.concat([makeHeader(CONTROL_FRAME, payload.length), payload])\n}\n\nexport function encodeSyncDataFrame(sequence: number, data: Buffer): Buffer {\n if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence > 0xffffffff) {\n throw new SyncProtocolError('data sequence is invalid')\n }\n if (data.length > MAX_SYNC_DATA_CHUNK_BYTES) throw new SyncProtocolError('data chunk exceeds size limit')\n const metadata = Buffer.allocUnsafe(DATA_METADATA_BYTES)\n metadata.writeUInt32BE(sequence, 0)\n createHash('sha256').update(data).digest().copy(metadata, 4)\n return Buffer.concat([makeHeader(DATA_FRAME, metadata.length + data.length), metadata, data])\n}\n\nexport class SyncFrameDecoder {\n private readonly header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n private headerOffset = 0\n private frameType = -1\n private payload: Buffer | undefined\n private payloadOffset = 0\n private nextDataSequence = 0\n private ended = false\n\n push(chunk: Buffer): SyncFrame[] {\n if (this.ended) throw new SyncProtocolError('frame decoder is already finished')\n const frames: SyncFrame[] = []\n let inputOffset = 0\n\n while (inputOffset < chunk.length) {\n if (!this.payload) {\n const copied = Math.min(FRAME_HEADER_BYTES - this.headerOffset, chunk.length - inputOffset)\n chunk.copy(this.header, this.headerOffset, inputOffset, inputOffset + copied)\n this.headerOffset += copied\n inputOffset += copied\n if (this.headerOffset < FRAME_HEADER_BYTES) continue\n\n this.frameType = this.header[0]\n const payloadLength = this.header.readUInt32BE(1)\n if (this.frameType === CONTROL_FRAME) {\n if (payloadLength < 2 || payloadLength > MAX_SYNC_CONTROL_FRAME_BYTES) {\n throw new SyncProtocolError('control frame length is invalid')\n }\n } else if (this.frameType === DATA_FRAME) {\n if (payloadLength < DATA_METADATA_BYTES || payloadLength > DATA_METADATA_BYTES + MAX_SYNC_DATA_CHUNK_BYTES) {\n throw new SyncProtocolError('data frame length is invalid')\n }\n } else {\n throw new SyncProtocolError('unknown frame type')\n }\n this.payload = Buffer.allocUnsafe(payloadLength)\n this.payloadOffset = 0\n }\n\n const payload = this.payload\n const copied = Math.min(payload.length - this.payloadOffset, chunk.length - inputOffset)\n chunk.copy(payload, this.payloadOffset, inputOffset, inputOffset + copied)\n this.payloadOffset += copied\n inputOffset += copied\n\n if (this.payloadOffset === payload.length) {\n frames.push(this.decodeFrame(this.frameType, payload))\n this.resetFrame()\n }\n }\n return frames\n }\n\n finish(): void {\n if (this.headerOffset !== 0 || this.payload !== undefined) throw new SyncProtocolError('truncated frame stream')\n this.ended = true\n }\n\n private decodeFrame(type: number, payload: Buffer): SyncFrame {\n if (type === CONTROL_FRAME) {\n if (payload[payload.length - 1] !== 0x0a) throw new SyncProtocolError('control frame must end with a newline')\n let value: unknown\n try {\n value = JSON.parse(payload.subarray(0, -1).toString('utf8'))\n } catch {\n throw new SyncProtocolError('control frame contains invalid JSON')\n }\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new SyncProtocolError('control frame must contain an object')\n }\n return { kind: 'control', value: value as Record<string, unknown> }\n }\n\n const sequence = payload.readUInt32BE(0)\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('data sequence is out of order')\n const expectedDigest = payload.subarray(4, DATA_METADATA_BYTES)\n const data = payload.subarray(DATA_METADATA_BYTES)\n const actualDigest = createHash('sha256').update(data).digest()\n if (!timingSafeEqual(expectedDigest, actualDigest)) throw new SyncProtocolError('data chunk digest mismatch')\n this.nextDataSequence += 1\n return { kind: 'data', sequence, digest: actualDigest.toString('hex'), data: Buffer.from(data) }\n }\n\n private resetFrame(): void {\n this.headerOffset = 0\n this.frameType = -1\n this.payload = undefined\n this.payloadOffset = 0\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"protocol.js","names":[],"sources":["../../../src/core/sync/protocol.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto'\n\nexport const SYNC_PROTOCOL_VERSION = 1\nexport const SYNC_SESSION_MANIFEST_CAPABILITY = 'session-manifest-v1'\nexport const SYNC_SESSION_EVENT_TAIL_CAPABILITY = 'session-event-tail-delta-v1'\nexport const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = 'session-bundle-objects-v1'\nexport const MAX_SYNC_CONTROL_FRAME_BYTES = 64 * 1024\nexport const MAX_SYNC_DATA_CHUNK_BYTES = 1024 * 1024\nexport const MAX_SYNC_TRANSFER_BYTES = 100 * 1024 * 1024 * 1024\n\nconst FRAME_HEADER_BYTES = 5\nconst CONTROL_FRAME = 0\nconst DATA_FRAME = 1\nconst DATA_METADATA_BYTES = 36\n\nexport type SyncFrame =\n | { kind: 'control'; value: Record<string, unknown> }\n | { kind: 'data'; sequence: number; digest: string; data: Buffer }\n\nexport class SyncProtocolError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SyncProtocolError'\n }\n}\n\nfunction makeHeader(type: number, payloadLength: number): Buffer {\n const header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n header[0] = type\n header.writeUInt32BE(payloadLength, 1)\n return header\n}\n\nexport function encodeSyncControlFrame(value: Record<string, unknown>): Buffer {\n const payload = Buffer.from(`${JSON.stringify(value)}\\n`, 'utf8')\n if (payload.length > MAX_SYNC_CONTROL_FRAME_BYTES) throw new SyncProtocolError('control frame exceeds size limit')\n return Buffer.concat([makeHeader(CONTROL_FRAME, payload.length), payload])\n}\n\nexport function encodeSyncDataFrame(sequence: number, data: Buffer): Buffer {\n if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence > 0xffffffff) {\n throw new SyncProtocolError('data sequence is invalid')\n }\n if (data.length > MAX_SYNC_DATA_CHUNK_BYTES) throw new SyncProtocolError('data chunk exceeds size limit')\n const metadata = Buffer.allocUnsafe(DATA_METADATA_BYTES)\n metadata.writeUInt32BE(sequence, 0)\n createHash('sha256').update(data).digest().copy(metadata, 4)\n return Buffer.concat([makeHeader(DATA_FRAME, metadata.length + data.length), metadata, data])\n}\n\nexport class SyncFrameDecoder {\n private readonly header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n private headerOffset = 0\n private frameType = -1\n private payload: Buffer | undefined\n private payloadOffset = 0\n private nextDataSequence = 0\n private ended = false\n\n push(chunk: Buffer): SyncFrame[] {\n if (this.ended) throw new SyncProtocolError('frame decoder is already finished')\n const frames: SyncFrame[] = []\n let inputOffset = 0\n\n while (inputOffset < chunk.length) {\n if (!this.payload) {\n const copied = Math.min(FRAME_HEADER_BYTES - this.headerOffset, chunk.length - inputOffset)\n chunk.copy(this.header, this.headerOffset, inputOffset, inputOffset + copied)\n this.headerOffset += copied\n inputOffset += copied\n if (this.headerOffset < FRAME_HEADER_BYTES) continue\n\n this.frameType = this.header[0]\n const payloadLength = this.header.readUInt32BE(1)\n if (this.frameType === CONTROL_FRAME) {\n if (payloadLength < 2 || payloadLength > MAX_SYNC_CONTROL_FRAME_BYTES) {\n throw new SyncProtocolError('control frame length is invalid')\n }\n } else if (this.frameType === DATA_FRAME) {\n if (payloadLength < DATA_METADATA_BYTES || payloadLength > DATA_METADATA_BYTES + MAX_SYNC_DATA_CHUNK_BYTES) {\n throw new SyncProtocolError('data frame length is invalid')\n }\n } else {\n throw new SyncProtocolError('unknown frame type')\n }\n this.payload = Buffer.allocUnsafe(payloadLength)\n this.payloadOffset = 0\n }\n\n const payload = this.payload\n const copied = Math.min(payload.length - this.payloadOffset, chunk.length - inputOffset)\n chunk.copy(payload, this.payloadOffset, inputOffset, inputOffset + copied)\n this.payloadOffset += copied\n inputOffset += copied\n\n if (this.payloadOffset === payload.length) {\n frames.push(this.decodeFrame(this.frameType, payload))\n this.resetFrame()\n }\n }\n return frames\n }\n\n finish(): void {\n if (this.headerOffset !== 0 || this.payload !== undefined) throw new SyncProtocolError('truncated frame stream')\n this.ended = true\n }\n\n private decodeFrame(type: number, payload: Buffer): SyncFrame {\n if (type === CONTROL_FRAME) {\n if (payload[payload.length - 1] !== 0x0a) throw new SyncProtocolError('control frame must end with a newline')\n let value: unknown\n try {\n value = JSON.parse(payload.subarray(0, -1).toString('utf8'))\n } catch {\n throw new SyncProtocolError('control frame contains invalid JSON')\n }\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new SyncProtocolError('control frame must contain an object')\n }\n return { kind: 'control', value: value as Record<string, unknown> }\n }\n\n const sequence = payload.readUInt32BE(0)\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('data sequence is out of order')\n const expectedDigest = payload.subarray(4, DATA_METADATA_BYTES)\n const data = payload.subarray(DATA_METADATA_BYTES)\n const actualDigest = createHash('sha256').update(data).digest()\n if (!timingSafeEqual(expectedDigest, actualDigest)) throw new SyncProtocolError('data chunk digest mismatch')\n this.nextDataSequence += 1\n return { kind: 'data', sequence, digest: actualDigest.toString('hex'), data: Buffer.from(data) }\n }\n\n private resetFrame(): void {\n this.headerOffset = 0\n this.frameType = -1\n this.payload = undefined\n this.payloadOffset = 0\n }\n}\n"],"mappings":"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"}
|
|
@@ -10,6 +10,8 @@ const PATHS = {
|
|
|
10
10
|
'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },
|
|
11
11
|
'node-apply': { path: '/api/dshb/sync/internal/nodes/apply', method: 'POST' },
|
|
12
12
|
'session-bundle': { path: '/api/dshb/sync/internal/sessions/bundle', method: 'GET' },
|
|
13
|
+
'session-manifest': { path: '/api/dshb/sync/internal/sessions/manifest', method: 'GET' },
|
|
14
|
+
'session-entries': { path: '/api/dshb/sync/internal/sessions/entries', method: 'POST' },
|
|
13
15
|
'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },
|
|
14
16
|
}
|
|
15
17
|
const input = []
|
|
@@ -84,7 +86,7 @@ function parseRequest(buffer) {
|
|
|
84
86
|
}
|
|
85
87
|
if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')
|
|
86
88
|
const operation = PATHS[request.operation]
|
|
87
|
-
if (request.operation === 'node-apply' || request.operation === 'session-apply') {
|
|
89
|
+
if (request.operation === 'node-apply' || request.operation === 'session-entries' || request.operation === 'session-apply') {
|
|
88
90
|
const last = frames[frames.length - 1]
|
|
89
91
|
if (!last || last.kind !== 'control' || last.value.kind !== 'request-end') throw new Error('apply request terminator required')
|
|
90
92
|
if (frames.slice(1, -1).some((frame) => frame.kind !== 'data')) throw new Error('invalid apply request body')
|
|
@@ -113,7 +115,8 @@ async function run() {
|
|
|
113
115
|
})
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
if (request.operation === 'node-manifest' || request.operation === 'session-bundle'
|
|
118
|
+
if (request.operation === 'node-manifest' || request.operation === 'session-bundle' ||
|
|
119
|
+
request.operation === 'session-manifest' || request.operation === 'session-entries') {
|
|
117
120
|
await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))
|
|
118
121
|
if (!response.body) throw new Error('manifest response body missing')
|
|
119
122
|
var sequence = 0
|
|
@@ -133,8 +136,11 @@ async function run() {
|
|
|
133
136
|
|
|
134
137
|
var text = await response.text()
|
|
135
138
|
if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
var responseBody = JSON.parse(text)
|
|
140
|
+
await writeStdout(encodeControl({ kind: 'response', status: response.status, body: responseBody }))
|
|
141
|
+
var recoverableDeltaMismatch = request.operation === 'session-apply' && response.status === 409 &&
|
|
142
|
+
responseBody && responseBody.code === 'session-delta-prefix'
|
|
143
|
+
if (!response.ok && !recoverableDeltaMismatch) process.exitCode = 1
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
process.stdin.on('data', (chunk) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-helper.js","names":[],"sources":["../../../src/core/sync/remote-helper.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\n\nconst REMOTE_HELPER_SOURCE = String.raw`'use strict'\nconst { createHash, timingSafeEqual } = require('node:crypto')\nconst MAX_CONTROL = 65536\nconst MAX_CHUNK = 1048576\nconst MAX_TRANSFER = 107374182400\nconst PATHS = {\n handshake: { path: '/api/dshb/sync/internal/handshake', method: 'GET' },\n 'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },\n 'node-apply': { path: '/api/dshb/sync/internal/nodes/apply', method: 'POST' },\n 'session-bundle': { path: '/api/dshb/sync/internal/sessions/bundle', method: 'GET' },\n 'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },\n}\nconst input = []\nlet inputBytes = 0\n\nfunction encodeControl(value) {\n const body = Buffer.from(JSON.stringify(value) + '\\n', 'utf8')\n if (body.length > MAX_CONTROL) throw new Error('control frame too large')\n const header = Buffer.alloc(5)\n header[0] = 0\n header.writeUInt32BE(body.length, 1)\n return Buffer.concat([header, body])\n}\n\nfunction encodeData(sequence, data) {\n const digest = createHash('sha256').update(data).digest()\n const metadata = Buffer.alloc(36)\n metadata.writeUInt32BE(sequence, 0)\n digest.copy(metadata, 4)\n const payload = Buffer.concat([metadata, data])\n const header = Buffer.alloc(5)\n header[0] = 1\n header.writeUInt32BE(payload.length, 1)\n return Buffer.concat([header, payload])\n}\n\nfunction decodeFrames(buffer) {\n const frames = []\n let offset = 0\n let sequence = 0\n let totalData = 0\n while (offset < buffer.length) {\n if (buffer.length - offset < 5) throw new Error('truncated frame header')\n const kind = buffer[offset]\n const length = buffer.readUInt32BE(offset + 1)\n offset += 5\n const maxLength = kind === 0 ? MAX_CONTROL : 36 + MAX_CHUNK\n if ((kind !== 0 && kind !== 1) || length > maxLength || length > buffer.length - offset) {\n throw new Error('invalid frame length')\n }\n const payload = buffer.subarray(offset, offset + length)\n offset += length\n if (kind === 0) {\n if (length < 2 || payload[length - 1] !== 10) throw new Error('invalid control frame')\n frames.push({ kind: 'control', value: JSON.parse(payload.subarray(0, -1).toString('utf8')) })\n } else {\n if (length < 36) throw new Error('invalid data frame')\n const currentSequence = payload.readUInt32BE(0)\n const expected = payload.subarray(4, 36)\n const data = payload.subarray(36)\n const actual = createHash('sha256').update(data).digest()\n if (currentSequence !== sequence || !timingSafeEqual(expected, actual)) throw new Error('invalid data chunk')\n sequence += 1\n totalData += data.length\n if (totalData > MAX_TRANSFER) throw new Error('transfer exceeds size limit')\n frames.push({ kind: 'data', data })\n }\n }\n return frames\n}\n\nfunction parseRequest(buffer) {\n const frames = decodeFrames(buffer)\n const first = frames[0]\n if (!first || first.kind !== 'control') throw new Error('request control frame required')\n const request = first.value\n if (!request || request.kind !== 'request' || request.protocolVersion !== 1 || !Object.prototype.hasOwnProperty.call(PATHS, request.operation)) {\n throw new Error('unsupported helper request')\n }\n if (Object.keys(request).some((key) => !['kind', 'protocolVersion', 'operation', 'port'].includes(key))) {\n throw new Error('unsupported helper request field')\n }\n if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')\n const operation = PATHS[request.operation]\n if (request.operation === 'node-apply' || request.operation === 'session-apply') {\n const last = frames[frames.length - 1]\n if (!last || last.kind !== 'control' || last.value.kind !== 'request-end') throw new Error('apply request terminator required')\n if (frames.slice(1, -1).some((frame) => frame.kind !== 'data')) throw new Error('invalid apply request body')\n const chunks = frames.slice(1, -1).map((frame) => frame.data)\n return { request, operation, body: Buffer.concat(chunks) }\n }\n if (frames.length !== 1) throw new Error('unexpected helper request frames')\n return { request, operation }\n}\n\nasync function run() {\n const buffer = Buffer.concat(input, inputBytes)\n const { request, operation, body } = parseRequest(buffer)\n const url = 'http://127.0.0.1:' + request.port + operation.path\n const response = await fetch(url, {\n method: operation.method,\n redirect: 'error',\n headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) },\n ...(body ? { body } : {}),\n })\n\n function writeStdout(data) {\n return new Promise(function(resolve, reject) {\n if (process.stdout.write(data)) resolve()\n else process.stdout.once('drain', resolve)\n })\n }\n\n if (request.operation === 'node-manifest' || request.operation === 'session-bundle') {\n await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))\n if (!response.body) throw new Error('manifest response body missing')\n var sequence = 0\n var total = 0\n for await (var chunk of response.body) {\n var data = Buffer.from(chunk)\n total += data.length\n if (total > MAX_TRANSFER) throw new Error('manifest exceeds size limit')\n for (var offset = 0; offset < data.length; offset += MAX_CHUNK) {\n await writeStdout(encodeData(sequence++, data.subarray(offset, Math.min(offset + MAX_CHUNK, data.length))))\n }\n }\n await writeStdout(encodeControl({ kind: 'response-end' }))\n if (!response.ok) process.exitCode = 1\n return\n }\n\n var text = await response.text()\n if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')\n await writeStdout(encodeControl({ kind: 'response', status: response.status, body:
|
|
1
|
+
{"version":3,"file":"remote-helper.js","names":[],"sources":["../../../src/core/sync/remote-helper.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\n\nconst REMOTE_HELPER_SOURCE = String.raw`'use strict'\nconst { createHash, timingSafeEqual } = require('node:crypto')\nconst MAX_CONTROL = 65536\nconst MAX_CHUNK = 1048576\nconst MAX_TRANSFER = 107374182400\nconst PATHS = {\n handshake: { path: '/api/dshb/sync/internal/handshake', method: 'GET' },\n 'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },\n 'node-apply': { path: '/api/dshb/sync/internal/nodes/apply', method: 'POST' },\n 'session-bundle': { path: '/api/dshb/sync/internal/sessions/bundle', method: 'GET' },\n 'session-manifest': { path: '/api/dshb/sync/internal/sessions/manifest', method: 'GET' },\n 'session-entries': { path: '/api/dshb/sync/internal/sessions/entries', method: 'POST' },\n 'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },\n}\nconst input = []\nlet inputBytes = 0\n\nfunction encodeControl(value) {\n const body = Buffer.from(JSON.stringify(value) + '\\n', 'utf8')\n if (body.length > MAX_CONTROL) throw new Error('control frame too large')\n const header = Buffer.alloc(5)\n header[0] = 0\n header.writeUInt32BE(body.length, 1)\n return Buffer.concat([header, body])\n}\n\nfunction encodeData(sequence, data) {\n const digest = createHash('sha256').update(data).digest()\n const metadata = Buffer.alloc(36)\n metadata.writeUInt32BE(sequence, 0)\n digest.copy(metadata, 4)\n const payload = Buffer.concat([metadata, data])\n const header = Buffer.alloc(5)\n header[0] = 1\n header.writeUInt32BE(payload.length, 1)\n return Buffer.concat([header, payload])\n}\n\nfunction decodeFrames(buffer) {\n const frames = []\n let offset = 0\n let sequence = 0\n let totalData = 0\n while (offset < buffer.length) {\n if (buffer.length - offset < 5) throw new Error('truncated frame header')\n const kind = buffer[offset]\n const length = buffer.readUInt32BE(offset + 1)\n offset += 5\n const maxLength = kind === 0 ? MAX_CONTROL : 36 + MAX_CHUNK\n if ((kind !== 0 && kind !== 1) || length > maxLength || length > buffer.length - offset) {\n throw new Error('invalid frame length')\n }\n const payload = buffer.subarray(offset, offset + length)\n offset += length\n if (kind === 0) {\n if (length < 2 || payload[length - 1] !== 10) throw new Error('invalid control frame')\n frames.push({ kind: 'control', value: JSON.parse(payload.subarray(0, -1).toString('utf8')) })\n } else {\n if (length < 36) throw new Error('invalid data frame')\n const currentSequence = payload.readUInt32BE(0)\n const expected = payload.subarray(4, 36)\n const data = payload.subarray(36)\n const actual = createHash('sha256').update(data).digest()\n if (currentSequence !== sequence || !timingSafeEqual(expected, actual)) throw new Error('invalid data chunk')\n sequence += 1\n totalData += data.length\n if (totalData > MAX_TRANSFER) throw new Error('transfer exceeds size limit')\n frames.push({ kind: 'data', data })\n }\n }\n return frames\n}\n\nfunction parseRequest(buffer) {\n const frames = decodeFrames(buffer)\n const first = frames[0]\n if (!first || first.kind !== 'control') throw new Error('request control frame required')\n const request = first.value\n if (!request || request.kind !== 'request' || request.protocolVersion !== 1 || !Object.prototype.hasOwnProperty.call(PATHS, request.operation)) {\n throw new Error('unsupported helper request')\n }\n if (Object.keys(request).some((key) => !['kind', 'protocolVersion', 'operation', 'port'].includes(key))) {\n throw new Error('unsupported helper request field')\n }\n if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')\n const operation = PATHS[request.operation]\n if (request.operation === 'node-apply' || request.operation === 'session-entries' || request.operation === 'session-apply') {\n const last = frames[frames.length - 1]\n if (!last || last.kind !== 'control' || last.value.kind !== 'request-end') throw new Error('apply request terminator required')\n if (frames.slice(1, -1).some((frame) => frame.kind !== 'data')) throw new Error('invalid apply request body')\n const chunks = frames.slice(1, -1).map((frame) => frame.data)\n return { request, operation, body: Buffer.concat(chunks) }\n }\n if (frames.length !== 1) throw new Error('unexpected helper request frames')\n return { request, operation }\n}\n\nasync function run() {\n const buffer = Buffer.concat(input, inputBytes)\n const { request, operation, body } = parseRequest(buffer)\n const url = 'http://127.0.0.1:' + request.port + operation.path\n const response = await fetch(url, {\n method: operation.method,\n redirect: 'error',\n headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) },\n ...(body ? { body } : {}),\n })\n\n function writeStdout(data) {\n return new Promise(function(resolve, reject) {\n if (process.stdout.write(data)) resolve()\n else process.stdout.once('drain', resolve)\n })\n }\n\n if (request.operation === 'node-manifest' || request.operation === 'session-bundle' ||\n request.operation === 'session-manifest' || request.operation === 'session-entries') {\n await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))\n if (!response.body) throw new Error('manifest response body missing')\n var sequence = 0\n var total = 0\n for await (var chunk of response.body) {\n var data = Buffer.from(chunk)\n total += data.length\n if (total > MAX_TRANSFER) throw new Error('manifest exceeds size limit')\n for (var offset = 0; offset < data.length; offset += MAX_CHUNK) {\n await writeStdout(encodeData(sequence++, data.subarray(offset, Math.min(offset + MAX_CHUNK, data.length))))\n }\n }\n await writeStdout(encodeControl({ kind: 'response-end' }))\n if (!response.ok) process.exitCode = 1\n return\n }\n\n var text = await response.text()\n if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')\n var responseBody = JSON.parse(text)\n await writeStdout(encodeControl({ kind: 'response', status: response.status, body: responseBody }))\n var recoverableDeltaMismatch = request.operation === 'session-apply' && response.status === 409 &&\n responseBody && responseBody.code === 'session-delta-prefix'\n if (!response.ok && !recoverableDeltaMismatch) process.exitCode = 1\n}\n\nprocess.stdin.on('data', (chunk) => {\n inputBytes += chunk.length\n if (inputBytes > MAX_TRANSFER + 2 * MAX_CONTROL) {\n process.exitCode = 1\n process.stdin.destroy()\n return\n }\n input.push(Buffer.from(chunk))\n})\nprocess.stdin.on('end', () => {\n run().catch(() => {\n try { process.stdout.write(encodeControl({ kind: 'error', code: 'helper-failed' })) } catch {}\n process.exitCode = 1\n })\n})\n`\n\nconst REMOTE_HELPER_BASE64 = Buffer.from(REMOTE_HELPER_SOURCE, 'utf8').toString('base64')\n\nexport const REMOTE_SYNC_HELPER_COMMAND =\n `node -e 'eval(Buffer.from(\"${REMOTE_HELPER_BASE64}\",\"base64\").toString(\"utf8\"))'`\n"],"mappings":";;AAEA,MAAM,uBAAuB,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkKvC,MAAa,6BACX,8BAH2B,OAAO,KAAK,sBAAsB,MAAM,CAAC,CAAC,SAAS,QAGhD,EAAqB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SyncPeerManager } from "./peer-manager.js";
|
|
2
|
-
import {
|
|
2
|
+
import { s as SyncSshTransport } from "../../ssh-transport-DGkQh-HA.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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as SyncSessionService } from "../../session-sync-C2j2rk9K.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 { a as SyncSessionError, c as
|
|
2
|
-
export { SyncSessionApplyOptions, SyncSessionApplyResult, SyncSessionBundle, SyncSessionEntry, SyncSessionError, SyncSessionInstructionSnapshot, SyncSessionObject, SyncSessionService, SyncSessionTransferPlan, SyncSessionWorkspace, WorkspaceRegistryLike, makeSyncSessionBundle, planSessionTransfers, validateSyncSessionBundle };
|
|
1
|
+
import { S as validateSyncSessionManifest, _ as makeSyncSessionManifest, a as SyncSessionError, b as syncSessionEntryKey, c as SyncSessionInstructionSnapshot, d as SyncSessionObject, f as SyncSessionService, g as makeSyncSessionBundle, h as WorkspaceRegistryLike, i as SyncSessionEntry, l as SyncSessionManifest, m as SyncSessionWorkspace, n as SyncSessionApplyResult, o as SyncSessionEventDelta, p as SyncSessionTransferPlan, r as SyncSessionBundle, s as SyncSessionEventPrefix, t as SyncSessionApplyOptions, u as SyncSessionManifestEntry, v as planSessionTransfers, x as validateSyncSessionBundle, y as sessionEventListDigest } from "../../session-sync-C2j2rk9K.js";
|
|
2
|
+
export { SyncSessionApplyOptions, SyncSessionApplyResult, SyncSessionBundle, SyncSessionEntry, SyncSessionError, SyncSessionEventDelta, SyncSessionEventPrefix, SyncSessionInstructionSnapshot, SyncSessionManifest, SyncSessionManifestEntry, SyncSessionObject, SyncSessionService, SyncSessionTransferPlan, SyncSessionWorkspace, WorkspaceRegistryLike, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateSyncSessionBundle, validateSyncSessionManifest };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as validateSyncSessionBundle, i as
|
|
2
|
-
export { SyncSessionError, SyncSessionService, makeSyncSessionBundle, planSessionTransfers, validateSyncSessionBundle };
|
|
1
|
+
import { a as planSessionTransfers, c as validateSyncSessionBundle, i as makeSyncSessionManifest, l as validateSyncSessionManifest, n as SyncSessionService, o as sessionEventListDigest, r as makeSyncSessionBundle, s as syncSessionEntryKey, t as SyncSessionError } from "../../session-sync-C0uoizyB.js";
|
|
2
|
+
export { SyncSessionError, SyncSessionService, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateSyncSessionBundle, validateSyncSessionManifest };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { SyncRemoteHandshake, SyncSshErrorCategory, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
|
|
1
|
+
import { a as SyncSshProbeError, c as buildSyncSshConnectConfig, i as SyncSshErrorCategory, n as SyncRemoteNodeManifest, o as SyncSshSession, r as SyncSessionDeltaPrefixError, s as SyncSshTransport, t as SyncRemoteHandshake } from "../../ssh-transport-DGkQh-HA.js";
|
|
2
|
+
export { SyncRemoteHandshake, SyncRemoteNodeManifest, SyncSessionDeltaPrefixError, SyncSshErrorCategory, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
|
|
@@ -17,6 +17,12 @@ var SyncSshProbeError = class extends Error {
|
|
|
17
17
|
this.name = "SyncSshProbeError";
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
+
var SyncSessionDeltaPrefixError = class extends Error {
|
|
21
|
+
constructor() {
|
|
22
|
+
super("remote session event prefix changed during transfer");
|
|
23
|
+
this.name = "SyncSessionDeltaPrefixError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
20
26
|
var SyncSshSession = class {
|
|
21
27
|
client;
|
|
22
28
|
channel;
|
|
@@ -172,50 +178,52 @@ var SyncSshTransport = class {
|
|
|
172
178
|
}
|
|
173
179
|
}
|
|
174
180
|
async fetchNodeManifest(peerId) {
|
|
181
|
+
const value = await this.fetchDataOperation(peerId, "node-manifest");
|
|
182
|
+
if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError("helper");
|
|
183
|
+
const manifest = value.manifest;
|
|
184
|
+
if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== "string" || !Array.isArray(manifest.records)) throw new SyncSshProbeError("protocol");
|
|
185
|
+
const capabilities = value.capabilities === void 0 ? void 0 : value.capabilities;
|
|
186
|
+
if (capabilities !== void 0 && (!Array.isArray(capabilities) || !capabilities.every((item) => typeof item === "string"))) throw new SyncSshProbeError("protocol");
|
|
187
|
+
return {
|
|
188
|
+
...manifest,
|
|
189
|
+
...capabilities ? { capabilities } : {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async applyNodeManifest(peerId, records, sourceName) {
|
|
193
|
+
const requestBody = Buffer.from(JSON.stringify({
|
|
194
|
+
records,
|
|
195
|
+
sourceName
|
|
196
|
+
}), "utf8");
|
|
197
|
+
if (requestBody.length > 107374182400) throw new SyncSshProbeError("protocol");
|
|
175
198
|
const { peer, session } = await this.openSession(peerId);
|
|
176
199
|
try {
|
|
177
200
|
await session.sendControl({
|
|
178
201
|
kind: "request",
|
|
179
202
|
protocolVersion: 1,
|
|
180
|
-
operation: "node-
|
|
203
|
+
operation: "node-apply",
|
|
181
204
|
port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
|
|
182
205
|
});
|
|
206
|
+
for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)));
|
|
207
|
+
await session.sendControl({ kind: "request-end" });
|
|
183
208
|
session.endInput();
|
|
184
|
-
let
|
|
185
|
-
let bodyMode;
|
|
186
|
-
let responseEnded = false;
|
|
187
|
-
let totalBytes = 0;
|
|
188
|
-
const chunks = [];
|
|
209
|
+
let response;
|
|
189
210
|
for await (const frame of session.readFrames()) {
|
|
190
|
-
if (frame.kind
|
|
191
|
-
|
|
192
|
-
if (frame.value.kind === "response" && responseStatus === void 0) {
|
|
193
|
-
responseStatus = typeof frame.value.status === "number" ? frame.value.status : void 0;
|
|
194
|
-
bodyMode = typeof frame.value.bodyMode === "string" ? frame.value.bodyMode : void 0;
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (frame.value.kind === "response-end" && responseStatus !== void 0 && !responseEnded) {
|
|
198
|
-
responseEnded = true;
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
throw new SyncProtocolError("unexpected node manifest control frame");
|
|
202
|
-
}
|
|
203
|
-
if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError("unexpected node manifest data frame");
|
|
204
|
-
totalBytes += frame.data.length;
|
|
205
|
-
if (totalBytes > 107374182400) throw new SyncProtocolError("node manifest exceeds transfer limit");
|
|
206
|
-
chunks.push(frame.data);
|
|
207
|
-
}
|
|
208
|
-
if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError("helper");
|
|
209
|
-
let value;
|
|
210
|
-
try {
|
|
211
|
-
value = JSON.parse(Buffer.concat(chunks, totalBytes).toString("utf8"));
|
|
212
|
-
} catch {
|
|
213
|
-
throw new SyncProtocolError("node manifest response is invalid JSON");
|
|
211
|
+
if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected node apply response frame");
|
|
212
|
+
response = frame.value;
|
|
214
213
|
}
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
if (!response || response.kind === "error") throw new SyncSshProbeError("helper");
|
|
215
|
+
if (response.kind !== "response" || response.status !== 200 || response.body === null || typeof response.body !== "object") throw new SyncSshProbeError("helper");
|
|
216
|
+
const body = response.body;
|
|
217
|
+
if (body.ok !== true || body.result === null || typeof body.result !== "object") throw new SyncSshProbeError("helper");
|
|
218
|
+
const result = body.result;
|
|
219
|
+
for (const key of [
|
|
220
|
+
"created",
|
|
221
|
+
"updated",
|
|
222
|
+
"deleted",
|
|
223
|
+
"pendingRemoval",
|
|
224
|
+
"skipped"
|
|
225
|
+
]) if (!Number.isSafeInteger(result[key]) || result[key] < 0) throw new SyncSshProbeError("protocol");
|
|
226
|
+
return result;
|
|
219
227
|
} catch (error) {
|
|
220
228
|
if (error instanceof SyncSshProbeError) throw error;
|
|
221
229
|
if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
|
|
@@ -224,10 +232,36 @@ var SyncSshTransport = class {
|
|
|
224
232
|
session.close();
|
|
225
233
|
}
|
|
226
234
|
}
|
|
227
|
-
async
|
|
235
|
+
async fetchSessionBundle(peerId) {
|
|
236
|
+
const value = await this.fetchDataOperation(peerId, "session-bundle");
|
|
237
|
+
if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError("helper");
|
|
238
|
+
const bundle = value.bundle;
|
|
239
|
+
if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== "string" || !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== "string") throw new SyncSshProbeError("protocol");
|
|
240
|
+
return bundle;
|
|
241
|
+
}
|
|
242
|
+
async fetchSessionManifest(peerId) {
|
|
243
|
+
const value = await this.fetchDataOperation(peerId, "session-manifest");
|
|
244
|
+
if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError("helper");
|
|
245
|
+
const manifest = value.manifest;
|
|
246
|
+
if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== "string" || !Array.isArray(manifest.entries) || !Array.isArray(manifest.skipped) || typeof manifest.digest !== "string") throw new SyncSshProbeError("protocol");
|
|
247
|
+
return manifest;
|
|
248
|
+
}
|
|
249
|
+
async fetchSessionEntries(peerId, keys, manifestDigest, prefixes = []) {
|
|
250
|
+
const value = await this.fetchDataOperation(peerId, "session-entries", {
|
|
251
|
+
keys,
|
|
252
|
+
manifestDigest,
|
|
253
|
+
prefixes
|
|
254
|
+
});
|
|
255
|
+
if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError("helper");
|
|
256
|
+
const bundle = value.bundle;
|
|
257
|
+
if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== "string" || !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== "string") throw new SyncSshProbeError("protocol");
|
|
258
|
+
return bundle;
|
|
259
|
+
}
|
|
260
|
+
async applySessionBundle(peerId, bundle, sourceName) {
|
|
228
261
|
const requestBody = Buffer.from(JSON.stringify({
|
|
229
|
-
|
|
230
|
-
sourceName
|
|
262
|
+
bundle,
|
|
263
|
+
sourceName,
|
|
264
|
+
peerId
|
|
231
265
|
}), "utf8");
|
|
232
266
|
if (requestBody.length > 107374182400) throw new SyncSshProbeError("protocol");
|
|
233
267
|
const { peer, session } = await this.openSession(peerId);
|
|
@@ -235,7 +269,7 @@ var SyncSshTransport = class {
|
|
|
235
269
|
await session.sendControl({
|
|
236
270
|
kind: "request",
|
|
237
271
|
protocolVersion: 1,
|
|
238
|
-
operation: "
|
|
272
|
+
operation: "session-apply",
|
|
239
273
|
port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
|
|
240
274
|
});
|
|
241
275
|
for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)));
|
|
@@ -243,10 +277,11 @@ var SyncSshTransport = class {
|
|
|
243
277
|
session.endInput();
|
|
244
278
|
let response;
|
|
245
279
|
for await (const frame of session.readFrames()) {
|
|
246
|
-
if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected
|
|
280
|
+
if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected session apply response frame");
|
|
247
281
|
response = frame.value;
|
|
248
282
|
}
|
|
249
283
|
if (!response || response.kind === "error") throw new SyncSshProbeError("helper");
|
|
284
|
+
if (response.kind === "response" && response.status === 409 && isRecord(response.body) && response.body.code === "session-delta-prefix") throw new SyncSessionDeltaPrefixError();
|
|
250
285
|
if (response.kind !== "response" || response.status !== 200 || response.body === null || typeof response.body !== "object") throw new SyncSshProbeError("helper");
|
|
251
286
|
const body = response.body;
|
|
252
287
|
if (body.ok !== true || body.result === null || typeof body.result !== "object") throw new SyncSshProbeError("helper");
|
|
@@ -254,12 +289,14 @@ var SyncSshTransport = class {
|
|
|
254
289
|
for (const key of [
|
|
255
290
|
"created",
|
|
256
291
|
"updated",
|
|
292
|
+
"archived",
|
|
257
293
|
"deleted",
|
|
258
|
-
"
|
|
259
|
-
"
|
|
294
|
+
"skipped",
|
|
295
|
+
"unmapped"
|
|
260
296
|
]) if (!Number.isSafeInteger(result[key]) || result[key] < 0) throw new SyncSshProbeError("protocol");
|
|
261
297
|
return result;
|
|
262
298
|
} catch (error) {
|
|
299
|
+
if (error instanceof SyncSessionDeltaPrefixError) throw error;
|
|
263
300
|
if (error instanceof SyncSshProbeError) throw error;
|
|
264
301
|
if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
|
|
265
302
|
throw new SyncSshProbeError("helper");
|
|
@@ -267,15 +304,26 @@ var SyncSshTransport = class {
|
|
|
267
304
|
session.close();
|
|
268
305
|
}
|
|
269
306
|
}
|
|
270
|
-
async
|
|
307
|
+
async trustObservedHostKey(peerId) {
|
|
308
|
+
const { peer } = await this.peerManager.connectionData(peerId);
|
|
309
|
+
const fingerprint = this.hostKeyChallenges.acceptObserved(peer);
|
|
310
|
+
await this.peerManager.pinHostKey(peerId, fingerprint);
|
|
311
|
+
}
|
|
312
|
+
async fetchDataOperation(peerId, operation, requestBody) {
|
|
313
|
+
const body = requestBody === void 0 ? void 0 : Buffer.from(JSON.stringify(requestBody), "utf8");
|
|
314
|
+
if (body && body.length > 107374182400) throw new SyncSshProbeError("protocol");
|
|
271
315
|
const { peer, session } = await this.openSession(peerId);
|
|
272
316
|
try {
|
|
273
317
|
await session.sendControl({
|
|
274
318
|
kind: "request",
|
|
275
319
|
protocolVersion: 1,
|
|
276
|
-
operation
|
|
320
|
+
operation,
|
|
277
321
|
port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
|
|
278
322
|
});
|
|
323
|
+
if (body) {
|
|
324
|
+
for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)));
|
|
325
|
+
await session.sendControl({ kind: "request-end" });
|
|
326
|
+
}
|
|
279
327
|
session.endInput();
|
|
280
328
|
let responseStatus;
|
|
281
329
|
let bodyMode;
|
|
@@ -294,24 +342,19 @@ var SyncSshTransport = class {
|
|
|
294
342
|
responseEnded = true;
|
|
295
343
|
continue;
|
|
296
344
|
}
|
|
297
|
-
throw new SyncProtocolError(
|
|
345
|
+
throw new SyncProtocolError(`unexpected ${operation} control frame`);
|
|
298
346
|
}
|
|
299
|
-
if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError(
|
|
347
|
+
if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError(`unexpected ${operation} data frame`);
|
|
300
348
|
totalBytes += frame.data.length;
|
|
301
|
-
if (totalBytes > 107374182400) throw new SyncProtocolError(
|
|
349
|
+
if (totalBytes > 107374182400) throw new SyncProtocolError(`${operation} exceeds transfer limit`);
|
|
302
350
|
chunks.push(frame.data);
|
|
303
351
|
}
|
|
304
352
|
if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError("helper");
|
|
305
|
-
let value;
|
|
306
353
|
try {
|
|
307
|
-
|
|
354
|
+
return JSON.parse(Buffer.concat(chunks, totalBytes).toString("utf8"));
|
|
308
355
|
} catch {
|
|
309
|
-
throw new SyncProtocolError(
|
|
356
|
+
throw new SyncProtocolError(`${operation} response is invalid JSON`);
|
|
310
357
|
}
|
|
311
|
-
if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError("helper");
|
|
312
|
-
const bundle = value.bundle;
|
|
313
|
-
if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== "string" || !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== "string") throw new SyncSshProbeError("protocol");
|
|
314
|
-
return bundle;
|
|
315
358
|
} catch (error) {
|
|
316
359
|
if (error instanceof SyncSshProbeError) throw error;
|
|
317
360
|
if (error instanceof SyncProtocolError) {
|
|
@@ -324,56 +367,6 @@ var SyncSshTransport = class {
|
|
|
324
367
|
session.close();
|
|
325
368
|
}
|
|
326
369
|
}
|
|
327
|
-
async applySessionBundle(peerId, bundle, sourceName) {
|
|
328
|
-
const requestBody = Buffer.from(JSON.stringify({
|
|
329
|
-
bundle,
|
|
330
|
-
sourceName,
|
|
331
|
-
peerId
|
|
332
|
-
}), "utf8");
|
|
333
|
-
if (requestBody.length > 107374182400) throw new SyncSshProbeError("protocol");
|
|
334
|
-
const { peer, session } = await this.openSession(peerId);
|
|
335
|
-
try {
|
|
336
|
-
await session.sendControl({
|
|
337
|
-
kind: "request",
|
|
338
|
-
protocolVersion: 1,
|
|
339
|
-
operation: "session-apply",
|
|
340
|
-
port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
|
|
341
|
-
});
|
|
342
|
-
for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)));
|
|
343
|
-
await session.sendControl({ kind: "request-end" });
|
|
344
|
-
session.endInput();
|
|
345
|
-
let response;
|
|
346
|
-
for await (const frame of session.readFrames()) {
|
|
347
|
-
if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected session apply response frame");
|
|
348
|
-
response = frame.value;
|
|
349
|
-
}
|
|
350
|
-
if (!response || response.kind === "error") throw new SyncSshProbeError("helper");
|
|
351
|
-
if (response.kind !== "response" || response.status !== 200 || response.body === null || typeof response.body !== "object") throw new SyncSshProbeError("helper");
|
|
352
|
-
const body = response.body;
|
|
353
|
-
if (body.ok !== true || body.result === null || typeof body.result !== "object") throw new SyncSshProbeError("helper");
|
|
354
|
-
const result = body.result;
|
|
355
|
-
for (const key of [
|
|
356
|
-
"created",
|
|
357
|
-
"updated",
|
|
358
|
-
"archived",
|
|
359
|
-
"deleted",
|
|
360
|
-
"skipped",
|
|
361
|
-
"unmapped"
|
|
362
|
-
]) if (!Number.isSafeInteger(result[key]) || result[key] < 0) throw new SyncSshProbeError("protocol");
|
|
363
|
-
return result;
|
|
364
|
-
} catch (error) {
|
|
365
|
-
if (error instanceof SyncSshProbeError) throw error;
|
|
366
|
-
if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
|
|
367
|
-
throw new SyncSshProbeError("helper");
|
|
368
|
-
} finally {
|
|
369
|
-
session.close();
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
async trustObservedHostKey(peerId) {
|
|
373
|
-
const { peer } = await this.peerManager.connectionData(peerId);
|
|
374
|
-
const fingerprint = this.hostKeyChallenges.acceptObserved(peer);
|
|
375
|
-
await this.peerManager.pinHostKey(peerId, fingerprint);
|
|
376
|
-
}
|
|
377
370
|
async requestHandshake(session, port) {
|
|
378
371
|
await session.sendControl({
|
|
379
372
|
kind: "request",
|
|
@@ -403,6 +396,6 @@ function validateHandshakeResponse(value) {
|
|
|
403
396
|
};
|
|
404
397
|
}
|
|
405
398
|
//#endregion
|
|
406
|
-
export { SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
|
|
399
|
+
export { SyncSessionDeltaPrefixError, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
|
|
407
400
|
|
|
408
401
|
//# sourceMappingURL=ssh-transport.js.map
|