@artenx/dshb 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/lib/auth/decorate-webserver.d.ts +2 -1
  2. package/lib/auth/decorate-webserver.js +21 -10
  3. package/lib/auth/decorate-webserver.js.map +1 -1
  4. package/lib/auth/gate.d.ts +3 -1
  5. package/lib/auth/gate.js +18 -1
  6. package/lib/auth/gate.js.map +1 -1
  7. package/lib/auth/index.js +11 -3
  8. package/lib/auth/index.js.map +1 -1
  9. package/lib/auth/webserver.d.ts +3 -1
  10. package/lib/auth/webserver.js +10 -1
  11. package/lib/auth/webserver.js.map +1 -1
  12. package/lib/client.js +2157 -567
  13. package/lib/client.js.map +1 -1
  14. package/lib/core/index.js +72 -4
  15. package/lib/core/index.js.map +1 -1
  16. package/lib/core/node-registry.d.ts +2 -108
  17. package/lib/core/node-registry.js +43 -2
  18. package/lib/core/node-registry.js.map +1 -1
  19. package/lib/core/routes.d.ts +4 -3
  20. package/lib/core/routes.js +67 -23
  21. package/lib/core/routes.js.map +1 -1
  22. package/lib/core/sync/coordinator.d.ts +48 -0
  23. package/lib/core/sync/coordinator.js +247 -0
  24. package/lib/core/sync/coordinator.js.map +1 -0
  25. package/lib/core/sync/host-key.d.ts +2 -0
  26. package/lib/core/sync/host-key.js +71 -0
  27. package/lib/core/sync/host-key.js.map +1 -0
  28. package/lib/core/sync/internal-routes.d.ts +9 -0
  29. package/lib/core/sync/internal-routes.js +236 -0
  30. package/lib/core/sync/internal-routes.js.map +1 -0
  31. package/lib/core/sync/model.d.ts +2 -0
  32. package/lib/core/sync/model.js +106 -0
  33. package/lib/core/sync/model.js.map +1 -0
  34. package/lib/core/sync/node-sync.d.ts +2 -0
  35. package/lib/core/sync/node-sync.js +660 -0
  36. package/lib/core/sync/node-sync.js.map +1 -0
  37. package/lib/core/sync/offline-archive.d.ts +55 -0
  38. package/lib/core/sync/offline-archive.js +673 -0
  39. package/lib/core/sync/offline-archive.js.map +1 -0
  40. package/lib/core/sync/offline-journal.d.ts +22 -0
  41. package/lib/core/sync/offline-journal.js +74 -0
  42. package/lib/core/sync/offline-journal.js.map +1 -0
  43. package/lib/core/sync/offline-routes.d.ts +7 -0
  44. package/lib/core/sync/offline-routes.js +192 -0
  45. package/lib/core/sync/offline-routes.js.map +1 -0
  46. package/lib/core/sync/peer-manager.d.ts +56 -0
  47. package/lib/core/sync/peer-manager.js +258 -0
  48. package/lib/core/sync/peer-manager.js.map +1 -0
  49. package/lib/core/sync/protocol.d.ts +2 -0
  50. package/lib/core/sync/protocol.js +124 -0
  51. package/lib/core/sync/protocol.js.map +1 -0
  52. package/lib/core/sync/remote-helper.d.ts +5 -0
  53. package/lib/core/sync/remote-helper.js +160 -0
  54. package/lib/core/sync/remote-helper.js.map +1 -0
  55. package/lib/core/sync/revision.d.ts +2 -0
  56. package/lib/core/sync/revision.js +77 -0
  57. package/lib/core/sync/revision.js.map +1 -0
  58. package/lib/core/sync/routes.d.ts +9 -0
  59. package/lib/core/sync/routes.js +339 -0
  60. package/lib/core/sync/routes.js.map +1 -0
  61. package/lib/core/sync/scheduler.d.ts +14 -0
  62. package/lib/core/sync/scheduler.js +81 -0
  63. package/lib/core/sync/scheduler.js.map +1 -0
  64. package/lib/core/sync/session-journal.d.ts +2 -0
  65. package/lib/core/sync/session-journal.js +191 -0
  66. package/lib/core/sync/session-journal.js.map +1 -0
  67. package/lib/core/sync/session-routes.d.ts +7 -0
  68. package/lib/core/sync/session-routes.js +67 -0
  69. package/lib/core/sync/session-routes.js.map +1 -0
  70. package/lib/core/sync/session-sync.d.ts +2 -0
  71. package/lib/core/sync/session-sync.js +2 -0
  72. package/lib/core/sync/ssh-transport.d.ts +2 -0
  73. package/lib/core/sync/ssh-transport.js +408 -0
  74. package/lib/core/sync/ssh-transport.js.map +1 -0
  75. package/lib/core/sync/stores.d.ts +3 -0
  76. package/lib/core/sync/stores.js +385 -0
  77. package/lib/core/sync/stores.js.map +1 -0
  78. package/lib/core/test.d.ts +1 -1
  79. package/lib/core/workspace-bindings.d.ts +2 -28
  80. package/lib/core/workspace-bindings.js +14 -14
  81. package/lib/core/workspace-bindings.js.map +1 -1
  82. package/lib/core/workspace-instruction-context.d.ts +3 -2260
  83. package/lib/core/workspace-instruction-context.js +104 -2
  84. package/lib/core/workspace-instruction-context.js.map +1 -0
  85. package/lib/core/workspace-instruction-routes.d.ts +1 -1
  86. package/lib/core/workspace-instructions.d.ts +2 -68
  87. package/lib/core/workspace-instructions.js +13 -0
  88. package/lib/core/workspace-instructions.js.map +1 -1
  89. package/lib/core/workspaces.d.ts +4 -3
  90. package/lib/core/workspaces.js +17 -1
  91. package/lib/core/workspaces.js.map +1 -1
  92. package/lib/host-key-C5afhAUd.d.ts +30 -0
  93. package/lib/index-VF8bcBeX.d.ts +2293 -0
  94. package/lib/{workspace-instruction-context-DytAmaPY.js → lib-gdtU_WKT.js} +159 -102
  95. package/lib/lib-gdtU_WKT.js.map +1 -0
  96. package/lib/model-BP7Gw1yk.d.ts +104 -0
  97. package/lib/node-registry-BTnnM5-k.d.ts +112 -0
  98. package/lib/node-sync-3Z9QLz2n.d.ts +89 -0
  99. package/lib/protocol-CV7LGE44.d.ts +35 -0
  100. package/lib/revision-C9sOlDZ6.d.ts +25 -0
  101. package/lib/router/router-fs.d.ts +12 -3
  102. package/lib/router/router-fs.js.map +1 -1
  103. package/lib/session-journal-pgPqcWn9.d.ts +40 -0
  104. package/lib/session-sync-EdfwUON5.js +2177 -0
  105. package/lib/session-sync-EdfwUON5.js.map +1 -0
  106. package/lib/session-sync-g6qKZzjV.d.ts +1252 -0
  107. package/lib/ssh-transport-BcIiLxsX.d.ts +55 -0
  108. package/lib/stores-CKyEt0yk.d.ts +31 -0
  109. package/lib/workspace-bindings-CsB0TIJB.d.ts +28 -0
  110. package/lib/workspace-instructions-DirYYiku.d.ts +69 -0
  111. package/package.json +20 -1
  112. package/lib/workspace-instruction-context-DytAmaPY.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-archive.js","names":[],"sources":["../../../src/core/sync/offline-archive.ts"],"sourcesContent":["import { createCipheriv, createDecipheriv, createHash, randomBytes, scrypt as scryptCallback } from 'node:crypto'\nimport { appendFileSync, chmodSync, closeSync, createReadStream, createWriteStream, mkdirSync, mkdtempSync, openSync, readSync, readdirSync, rmSync, statfsSync, statSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { Dirent } from 'node:fs'\nimport { inflateRawSync } from 'node:zlib'\nimport { Readable, Transform } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport { SESSION_FORMAT_VERSION } from '@deepseek-ai/dsh-session'\nimport { SyncNodeService, type SyncNodeManifest } from './node-sync.js'\nimport { SyncSessionError, SyncSessionService, validateSyncSessionBundle, type SyncSessionBundle } from './session-sync.js'\nimport { SyncOfflineArchiveJournalStore, type SyncOfflineImportJournal } from './offline-journal.js'\nimport type { SyncStateStore } from './stores.js'\nimport type { SyncImportMethod, SyncRecordMetadata } from './model.js'\n\nconst ARCHIVE_MAGIC = Buffer.from('DSHBSYNC')\nconst ARCHIVE_VERSION = 1\nconst HEADER_SIZE = 50\nconst TAG_SIZE = 16\nconst SALT_SIZE = 16\nconst NONCE_SIZE = 12\nconst SCRYPT_N = 32_768\nconst SCRYPT_R = 8\nconst SCRYPT_P = 1\nconst KEY_BYTES = 32\nconst MIN_PASSWORD_BYTES = 8\nconst MAX_PASSWORD_BYTES = 1024\nexport const MAX_OFFLINE_ARCHIVE_BYTES = 128 * 1024 * 1024\nconst MAX_ARCHIVE_EXPANDED_BYTES = 128 * 1024 * 1024\nconst MAX_ZIP_BYTES = MAX_OFFLINE_ARCHIVE_BYTES - HEADER_SIZE - TAG_SIZE\nconst ZIP_EOCD_SIGNATURE = 0x06054b50\nconst ZIP_LOCAL_SIGNATURE = 0x04034b50\nconst ZIP_CENTRAL_SIGNATURE = 0x02014b50\n\nconst CRC_TABLE = Uint32Array.from({ length: 256 }, (_, index) => {\n let crc = index\n for (let bit = 0; bit < 8; bit += 1) crc = (crc & 1) ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1\n return crc >>> 0\n})\n\nexport class SyncOfflineArchiveError extends Error {\n constructor(readonly code: string, message: string, cause?: unknown) {\n super(message, cause === undefined ? undefined : { cause })\n this.name = 'SyncOfflineArchiveError'\n }\n}\n\nexport interface SyncOfflineArchiveFile {\n path: string\n bytes: number\n cleanup(): void\n}\n\nexport interface SyncOfflineImportResult {\n nodes: { created: number; updated: number; deleted: number; pendingRemoval: number; skipped: number }\n sessions?: { created: number; updated: number; archived: number; deleted: number; skipped: number; unmapped: number }\n}\n\ninterface ZipFile {\n name: string\n data: Buffer\n}\n\ninterface OfflineRecordSummary {\n kind: 'node' | 'session'\n originInstanceId: string\n originEntityId: string\n revision: SyncRecordMetadata['revision']\n contentDigest: string\n deleted: boolean\n branchId?: string\n}\n\ninterface OfflineArchiveManifest {\n format: 'dshb-sync'\n formatVersion: 1\n instanceId: string\n createdAt: string\n includeSessions: boolean\n sessionFormatVersion?: number\n files: {\n nodes: { bytes: number; digest: string }\n sessions?: { bytes: number; digest: string }\n }\n records: OfflineRecordSummary[]\n}\n\nfunction sha256(data: Uint8Array): string {\n return `sha256:${createHash('sha256').update(data).digest('hex')}`\n}\n\nfunction canonicalJson(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`\n const object = value as Record<string, unknown>\n return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`).join(',')}}`\n}\n\nfunction crc32(data: Uint8Array): number {\n let crc = 0xffffffff\n for (const byte of data) crc = CRC_TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8)\n return (crc ^ 0xffffffff) >>> 0\n}\n\nfunction validatePassword(password: string): Buffer {\n if (typeof password !== 'string') throw new SyncOfflineArchiveError('password-required', 'Package password is required')\n const bytes = Buffer.from(password, 'utf8')\n if (bytes.byteLength < MIN_PASSWORD_BYTES || bytes.byteLength > MAX_PASSWORD_BYTES || password.includes('\\0')) {\n throw new SyncOfflineArchiveError('password-invalid', `Package password must be ${MIN_PASSWORD_BYTES}-${MAX_PASSWORD_BYTES} UTF-8 bytes`)\n }\n return bytes\n}\n\nasync function deriveKey(password: Buffer, salt: Buffer): Promise<Buffer> {\n return new Promise<Buffer>((resolve, reject) => {\n scryptCallback(password, salt, KEY_BYTES, {\n N: SCRYPT_N,\n r: SCRYPT_R,\n p: SCRYPT_P,\n maxmem: 64 * 1024 * 1024,\n }, (error, key) => {\n if (error) reject(error)\n else resolve(key as Buffer)\n })\n })\n}\n\nfunction archiveHeader(salt: Buffer, nonce: Buffer): Buffer {\n const header = Buffer.alloc(HEADER_SIZE)\n ARCHIVE_MAGIC.copy(header, 0)\n header.writeUInt16BE(ARCHIVE_VERSION, 8)\n header.writeUInt32BE(SCRYPT_N, 10)\n header.writeUInt32BE(SCRYPT_R, 14)\n header.writeUInt32BE(SCRYPT_P, 18)\n salt.copy(header, 22)\n nonce.copy(header, 22 + SALT_SIZE)\n return header\n}\n\nfunction parseArchiveHeader(header: Buffer): { salt: Buffer; nonce: Buffer } {\n if (header.length !== HEADER_SIZE || !header.subarray(0, 8).equals(ARCHIVE_MAGIC) ||\n header.readUInt16BE(8) !== ARCHIVE_VERSION || header.readUInt32BE(10) !== SCRYPT_N ||\n header.readUInt32BE(14) !== SCRYPT_R || header.readUInt32BE(18) !== SCRYPT_P) {\n throw new SyncOfflineArchiveError('format-unsupported', 'Offline archive format or KDF parameters are unsupported')\n }\n return {\n salt: header.subarray(22, 22 + SALT_SIZE),\n nonce: header.subarray(22 + SALT_SIZE, HEADER_SIZE),\n }\n}\n\nfunction safeZipName(name: string): boolean {\n return Boolean(name) && !name.startsWith('/') && !name.includes('\\\\') && !name.includes('\\0') &&\n name.split('/').every((part) => part !== '' && part !== '.' && part !== '..') && !name.includes(':')\n}\n\nfunction writeZipParts(files: readonly ZipFile[]): { chunks: Buffer[]; bytes: number } {\n const localRecords: Buffer[] = []\n const centralRecords: Buffer[] = []\n let localOffset = 0\n let expandedBytes = 0\n\n for (const file of files) {\n if (!safeZipName(file.name)) throw new SyncOfflineArchiveError('path-invalid', 'Offline archive entry path is invalid')\n expandedBytes += file.data.byteLength\n if (expandedBytes > MAX_ARCHIVE_EXPANDED_BYTES || file.data.byteLength > 0xffffffff) {\n throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive contents exceed the size limit')\n }\n const name = Buffer.from(file.name, 'utf8')\n const checksum = crc32(file.data)\n const localHeader = Buffer.alloc(30)\n localHeader.writeUInt32LE(ZIP_LOCAL_SIGNATURE, 0)\n localHeader.writeUInt16LE(20, 4)\n localHeader.writeUInt16LE(0x0800, 6)\n localHeader.writeUInt16LE(0, 8)\n localHeader.writeUInt32LE(checksum, 14)\n localHeader.writeUInt32LE(file.data.byteLength, 18)\n localHeader.writeUInt32LE(file.data.byteLength, 22)\n localHeader.writeUInt16LE(name.byteLength, 26)\n localHeader.writeUInt16LE(0, 28)\n localRecords.push(localHeader, name, file.data)\n\n const centralHeader = Buffer.alloc(46)\n centralHeader.writeUInt32LE(ZIP_CENTRAL_SIGNATURE, 0)\n centralHeader.writeUInt16LE(0x0314, 4)\n centralHeader.writeUInt16LE(20, 6)\n centralHeader.writeUInt16LE(0x0800, 8)\n centralHeader.writeUInt16LE(0, 10)\n centralHeader.writeUInt32LE(checksum, 16)\n centralHeader.writeUInt32LE(file.data.byteLength, 20)\n centralHeader.writeUInt32LE(file.data.byteLength, 24)\n centralHeader.writeUInt16LE(name.byteLength, 28)\n centralHeader.writeUInt16LE(0, 30)\n centralHeader.writeUInt16LE(0, 32)\n centralHeader.writeUInt16LE(0, 34)\n centralHeader.writeUInt16LE(0, 36)\n centralHeader.writeUInt32LE((0o100600 << 16) >>> 0, 38)\n centralHeader.writeUInt32LE(localOffset, 42)\n centralRecords.push(centralHeader, name)\n localOffset += localHeader.byteLength + name.byteLength + file.data.byteLength\n }\n\n if (files.length > 0xffff || localOffset > 0xffffffff) {\n throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive exceeds classic ZIP limits')\n }\n const centralBytes = centralRecords.reduce((total, record) => total + record.byteLength, 0)\n const end = Buffer.alloc(22)\n end.writeUInt32LE(ZIP_EOCD_SIGNATURE, 0)\n end.writeUInt16LE(files.length, 8)\n end.writeUInt16LE(files.length, 10)\n end.writeUInt32LE(centralBytes, 12)\n end.writeUInt32LE(localOffset, 16)\n end.writeUInt16LE(0, 20)\n const bytes = localOffset + centralBytes + end.byteLength\n if (bytes > MAX_ZIP_BYTES) {\n throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive exceeds the size limit')\n }\n return { chunks: [...localRecords, ...centralRecords, end], bytes }\n}\n\nfunction parseZip(archive: Buffer): { files: Map<string, Buffer>; expandedBytes: number } {\n const minimumEocd = Math.max(0, archive.length - 65_557)\n let eocd = -1\n for (let offset = archive.length - 22; offset >= minimumEocd; offset -= 1) {\n if (archive.readUInt32LE(offset) === ZIP_EOCD_SIGNATURE) { eocd = offset; break }\n }\n if (eocd < 0 || eocd + 22 > archive.length) throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive ZIP directory is invalid')\n const commentLength = archive.readUInt16LE(eocd + 20)\n if (eocd + 22 + commentLength !== archive.length || archive.readUInt16LE(eocd + 4) !== 0 ||\n archive.readUInt16LE(eocd + 6) !== 0 || archive.readUInt16LE(eocd + 8) !== archive.readUInt16LE(eocd + 10)) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive ZIP directory is inconsistent')\n }\n const count = archive.readUInt16LE(eocd + 10)\n const centralSize = archive.readUInt32LE(eocd + 12)\n const centralOffset = archive.readUInt32LE(eocd + 16)\n if (count < 2 || count > 3 || centralOffset + centralSize !== eocd) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive contains an unsupported entry count or layout')\n }\n\n const entries: Array<{ name: string; method: number; crc: number; compressedBytes: number; expandedBytes: number; localOffset: number }> = []\n let offset = centralOffset\n let totalExpanded = 0\n const names = new Set<string>()\n for (let index = 0; index < count; index += 1) {\n if (offset + 46 > eocd || archive.readUInt32LE(offset) !== ZIP_CENTRAL_SIGNATURE) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive central entry is invalid')\n }\n const madeBy = archive.readUInt16LE(offset + 4)\n const flags = archive.readUInt16LE(offset + 8)\n const method = archive.readUInt16LE(offset + 10)\n const checksum = archive.readUInt32LE(offset + 16)\n const compressedBytes = archive.readUInt32LE(offset + 20)\n const expandedBytes = archive.readUInt32LE(offset + 24)\n const nameLength = archive.readUInt16LE(offset + 28)\n const extraLength = archive.readUInt16LE(offset + 30)\n const entryCommentLength = archive.readUInt16LE(offset + 32)\n const diskNumber = archive.readUInt16LE(offset + 34)\n const externalAttributes = archive.readUInt32LE(offset + 38)\n const localOffset = archive.readUInt32LE(offset + 42)\n const end = offset + 46 + nameLength + extraLength + entryCommentLength\n if (end > eocd || nameLength === 0 || diskNumber !== 0 || (flags & ~0x0800) !== 0 || (method !== 0 && method !== 8)) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive entry flags or compression are unsupported')\n }\n let name: string\n try {\n name = new TextDecoder('utf-8', { fatal: true }).decode(archive.subarray(offset + 46, offset + 46 + nameLength))\n } catch (error) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive entry name is not valid UTF-8', error)\n }\n const unixMode = madeBy >> 8 === 3 ? externalAttributes >>> 16 : 0\n const fileType = unixMode & 0o170000\n if (!safeZipName(name) || names.has(name) || (fileType !== 0 && fileType !== 0o100000) || expandedBytes > MAX_ARCHIVE_EXPANDED_BYTES) {\n throw new SyncOfflineArchiveError('path-invalid', 'Offline archive contains a duplicate, unsafe, linked or oversized path')\n }\n if (method === 0 && compressedBytes !== expandedBytes) throw new SyncOfflineArchiveError('zip-invalid', 'Stored ZIP entry sizes differ')\n totalExpanded += expandedBytes\n if (totalExpanded > MAX_ARCHIVE_EXPANDED_BYTES) throw new SyncOfflineArchiveError('archive-too-large', 'Expanded offline archive exceeds the size limit')\n names.add(name)\n entries.push({ name, method, crc: checksum, compressedBytes, expandedBytes, localOffset })\n offset = end\n }\n if (offset !== eocd || !names.has('manifest.json') || !names.has('nodes.json') ||\n (names.has('sessions.json') !== (count === 3))) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive has missing or unexpected files')\n }\n\n const ranges: Array<[number, number]> = []\n const files = new Map<string, Buffer>()\n for (const entry of entries) {\n const local = entry.localOffset\n if (local + 30 > centralOffset || archive.readUInt32LE(local) !== ZIP_LOCAL_SIGNATURE) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive local entry is invalid')\n }\n const flags = archive.readUInt16LE(local + 6)\n const method = archive.readUInt16LE(local + 8)\n const crc = archive.readUInt32LE(local + 14)\n const compressedBytes = archive.readUInt32LE(local + 18)\n const expandedBytes = archive.readUInt32LE(local + 22)\n const nameLength = archive.readUInt16LE(local + 26)\n const extraLength = archive.readUInt16LE(local + 28)\n const nameStart = local + 30\n const dataStart = nameStart + nameLength + extraLength\n const dataEnd = dataStart + entry.compressedBytes\n const localName = archive.subarray(nameStart, nameStart + nameLength).toString('utf8')\n if (flags !== 0x0800 || method !== entry.method || crc !== entry.crc || compressedBytes !== entry.compressedBytes ||\n expandedBytes !== entry.expandedBytes || localName !== entry.name || dataEnd > centralOffset) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive local and central entries do not match')\n }\n ranges.push([local, dataEnd])\n const compressed = archive.subarray(dataStart, dataEnd)\n let data: Buffer\n try {\n data = entry.method === 0\n ? Buffer.from(compressed)\n : inflateRawSync(compressed, { maxOutputLength: entry.expandedBytes + 1 })\n } catch (error) {\n throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive entry could not be expanded safely', error)\n }\n if (data.byteLength !== entry.expandedBytes || crc32(data) !== entry.crc) {\n throw new SyncOfflineArchiveError('archive-integrity', 'Offline archive entry checksum does not match')\n }\n files.set(entry.name, data)\n }\n ranges.sort((a, b) => a[0] - b[0])\n for (let index = 1; index < ranges.length; index += 1) {\n if (ranges[index - 1]![1] > ranges[index]![0]) throw new SyncOfflineArchiveError('zip-invalid', 'Offline archive entries overlap')\n }\n return { files, expandedBytes: totalExpanded }\n}\n\nexport function validateOfflineZipArchive(archive: Buffer): void {\n parseZip(archive)\n}\n\nfunction parseJson<T>(data: Buffer, label: string): T {\n try {\n return JSON.parse(data.toString('utf8')) as T\n } catch (error) {\n throw new SyncOfflineArchiveError('archive-invalid', `${label} is invalid JSON`, error)\n }\n}\n\nfunction recordSummary(metadata: SyncRecordMetadata, branchId?: string): OfflineRecordSummary {\n return {\n kind: metadata.kind as 'node' | 'session',\n originInstanceId: metadata.originInstanceId,\n originEntityId: metadata.originEntityId,\n revision: metadata.revision,\n contentDigest: metadata.contentDigest,\n deleted: Boolean(metadata.deleted),\n ...(branchId ? { branchId } : {}),\n }\n}\n\nfunction summaryRecords(nodes: SyncNodeManifest, sessions?: SyncSessionBundle): OfflineRecordSummary[] {\n const records = [\n ...nodes.records.map((entry) => recordSummary(entry.metadata)),\n ...(sessions?.entries.map((entry) => recordSummary(entry.metadata, entry.branchId)) ?? []),\n ]\n return records.sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b)))\n}\n\nfunction validateManifest(value: unknown, files: Map<string, Buffer>): { manifest: OfflineArchiveManifest; nodes: SyncNodeManifest; sessions?: SyncSessionBundle } {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new SyncOfflineArchiveError('archive-invalid', 'Offline archive manifest is invalid')\n }\n const manifest = value as OfflineArchiveManifest\n if (Object.keys(manifest).some((key) => !['format', 'formatVersion', 'instanceId', 'createdAt', 'includeSessions', 'sessionFormatVersion', 'files', 'records'].includes(key)) ||\n manifest.files === null || typeof manifest.files !== 'object' || Array.isArray(manifest.files) ||\n !manifest.files.nodes || typeof manifest.files.nodes !== 'object' || Array.isArray(manifest.files.nodes)) {\n throw new SyncOfflineArchiveError('archive-invalid', 'Offline archive manifest contains unsupported fields')\n }\n if (manifest.format !== 'dshb-sync' || manifest.formatVersion !== ARCHIVE_VERSION ||\n typeof manifest.instanceId !== 'string' || !/^i-[a-f0-9]{32}$/.test(manifest.instanceId) ||\n typeof manifest.createdAt !== 'string' || !Number.isFinite(Date.parse(manifest.createdAt)) ||\n typeof manifest.includeSessions !== 'boolean' || !Array.isArray(manifest.records) ||\n manifest.includeSessions !== files.has('sessions.json') ||\n (manifest.includeSessions && manifest.sessionFormatVersion !== SESSION_FORMAT_VERSION) ||\n (!manifest.includeSessions && manifest.sessionFormatVersion !== undefined)) {\n throw new SyncOfflineArchiveError('format-unsupported', 'Offline archive manifest version or fields are unsupported')\n }\n const nodesData = files.get('nodes.json')\n const manifestData = files.get('manifest.json')\n if (!nodesData || !manifestData || manifest.files.nodes.bytes !== nodesData.byteLength ||\n manifest.files.nodes.digest !== sha256(nodesData)) {\n throw new SyncOfflineArchiveError('archive-integrity', 'Offline archive node manifest digest does not match')\n }\n const nodes = parseJson<SyncNodeManifest>(nodesData, 'Node manifest')\n if (nodes.protocolVersion !== 1 || nodes.instanceId !== manifest.instanceId || !Array.isArray(nodes.records)) {\n throw new SyncOfflineArchiveError('archive-invalid', 'Offline archive node manifest identity is invalid')\n }\n let sessions: SyncSessionBundle | undefined\n const sessionsData = files.get('sessions.json')\n if (sessionsData) {\n if (!manifest.files.sessions || manifest.files.sessions.bytes !== sessionsData.byteLength || manifest.files.sessions.digest !== sha256(sessionsData)) {\n throw new SyncOfflineArchiveError('archive-integrity', 'Offline archive session bundle digest does not match')\n }\n sessions = validateSyncSessionBundle(parseJson(sessionsData, 'Session bundle'))\n if (sessions.instanceId !== manifest.instanceId) throw new SyncOfflineArchiveError('archive-invalid', 'Session bundle source identity differs from archive')\n } else if (manifest.files.sessions !== undefined) {\n throw new SyncOfflineArchiveError('archive-invalid', 'Offline archive declares a missing session file')\n }\n if (canonicalJson(manifest.records) !== canonicalJson(summaryRecords(nodes, sessions))) {\n throw new SyncOfflineArchiveError('archive-integrity', 'Offline archive record summary does not match payloads')\n }\n return { manifest, nodes, ...(sessions ? { sessions } : {}) }\n}\n\nfunction decodeOfflinePayload(zip: Buffer, nodesService: SyncNodeService): {\n files: Map<string, Buffer>\n payloads: ReturnType<typeof validateManifest>\n} {\n const parsed = parseZip(zip)\n const manifestData = parsed.files.get('manifest.json')\n if (!manifestData) throw new SyncOfflineArchiveError('archive-invalid', 'Offline archive manifest is missing')\n const payloads = validateManifest(parseJson(manifestData, 'Archive manifest'), parsed.files)\n try {\n nodesService.validateManifest(payloads.nodes.records)\n if (payloads.sessions) validateSyncSessionBundle(payloads.sessions)\n } catch (error) {\n throw new SyncOfflineArchiveError('archive-invalid', 'Offline archive contains an invalid sync manifest', error)\n }\n return { files: parsed.files, payloads }\n}\n\nfunction readFileRange(path: string, position: number, length: number): Buffer {\n const file = openSync(path, 'r')\n try {\n const buffer = Buffer.alloc(length)\n const bytes = readSync(file, buffer, 0, length, position)\n if (bytes !== length) throw new SyncOfflineArchiveError('archive-invalid', 'Offline archive header is truncated')\n return buffer\n } finally {\n closeSync(file)\n }\n}\n\nfunction homeDirectory(home?: string): string {\n return home ?? process.env.DSH_HOME ?? join(homedir(), '.dsh')\n}\n\nfunction availableBytes(path: string): bigint {\n const available = statfsSync(path, { bigint: true })\n return available.bavail * available.bsize\n}\n\nfunction checkAvailableSpace(path: string, requiredBytes: number, getAvailableBytes: (path: string) => bigint): void {\n try {\n if (getAvailableBytes(path) < BigInt(requiredBytes)) {\n throw new SyncOfflineArchiveError('disk-space', 'Insufficient disk space for offline archive import')\n }\n } catch (error) {\n if (error instanceof SyncOfflineArchiveError) throw error\n throw new SyncOfflineArchiveError('disk-space', 'Unable to verify available disk space', error)\n }\n}\n\nfunction requiredBytesForImport(\n payloads: ReturnType<typeof validateManifest>,\n state: SyncStateStore | undefined,\n nodesPending: boolean,\n sessionsPending: boolean,\n): number {\n const attachmentBytes = sessionsPending\n ? payloads.sessions?.entries.reduce((total, entry) =>\n total + (entry.objects?.reduce((sum, object) => sum + object.bytes, 0) ?? 0), 0,\n ) ?? 0\n : 0\n const sessionBytes = sessionsPending && payloads.sessions\n ? Buffer.byteLength(`${JSON.stringify(payloads.sessions, null, 2)}\\n`, 'utf8')\n : 0\n const nodeBytes = nodesPending ? payloads.manifest.files.nodes.bytes : 0\n const stateBytes = nodesPending || sessionsPending\n ? (state ? Buffer.byteLength(`${JSON.stringify(state.read(), null, 2)}\\n`, 'utf8') : 0)\n : 0\n return attachmentBytes * 2 + sessionBytes * 3 + nodeBytes * 2 + stateBytes * 2 + 4 * 1024 * 1024\n}\n\nexport class SyncOfflineArchiveService {\n private operationTail: Promise<void> = Promise.resolve()\n private readonly home: string\n private readonly journal: SyncOfflineArchiveJournalStore\n\n constructor(\n private readonly nodes: SyncNodeService,\n private readonly sessions: SyncSessionService,\n home?: string,\n private readonly getAvailableBytes: (path: string) => bigint = availableBytes,\n private readonly state?: SyncStateStore,\n ) {\n this.home = homeDirectory(home)\n this.journal = new SyncOfflineArchiveJournalStore(this.home)\n }\n\n exportArchive(password: string, includeSessions = false): Promise<SyncOfflineArchiveFile> {\n return this.serializeMutation(() => this.exportArchiveNow(password, includeSessions))\n }\n\n importArchive(password: string, input: AsyncIterable<Uint8Array>): Promise<SyncOfflineImportResult> {\n return this.serializeMutation(async () => {\n await this.recoverPendingNow()\n await this.sessions.recoverPending()\n return this.importArchiveNow(password, input)\n })\n }\n\n recoverPending(): Promise<void> {\n return this.serializeMutation(async () => {\n await this.recoverPendingNow()\n })\n }\n\n cleanupStagingDirectories(): void {\n const root = join(this.home, 'dshb')\n let entries: Dirent[]\n try {\n entries = readdirSync(root, { withFileTypes: true })\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return\n throw error\n }\n const pendingDirectory = this.journal.read()?.stagingDirectory\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name !== pendingDirectory && /^(?:sync-offline-staging|sync-offline-export)-/.test(entry.name)) {\n rmSync(join(root, entry.name), { recursive: true, force: true })\n }\n }\n }\n\n private async exportArchiveNow(passwordValue: string, includeSessions: boolean): Promise<SyncOfflineArchiveFile> {\n const password = validatePassword(passwordValue)\n if (typeof includeSessions !== 'boolean') throw new SyncOfflineArchiveError('request-invalid', 'Session inclusion option is invalid')\n const nodeManifest = await this.nodes.exportManifest()\n this.nodes.validateManifest(nodeManifest.records)\n const sessionBundle = includeSessions ? await this.sessions.exportBundle() : undefined\n const nodesData = Buffer.from(JSON.stringify(nodeManifest), 'utf8')\n const sessionsData = sessionBundle ? Buffer.from(JSON.stringify(sessionBundle), 'utf8') : undefined\n const records = summaryRecords(nodeManifest, sessionBundle)\n const manifest: OfflineArchiveManifest = {\n format: 'dshb-sync',\n formatVersion: ARCHIVE_VERSION,\n instanceId: nodeManifest.instanceId,\n createdAt: new Date().toISOString(),\n includeSessions,\n ...(sessionBundle ? { sessionFormatVersion: SESSION_FORMAT_VERSION } : {}),\n files: {\n nodes: { bytes: nodesData.byteLength, digest: sha256(nodesData) },\n ...(sessionsData ? { sessions: { bytes: sessionsData.byteLength, digest: sha256(sessionsData) } } : {}),\n },\n records,\n }\n const manifestData = Buffer.from(JSON.stringify(manifest), 'utf8')\n const zip = writeZipParts([\n { name: 'manifest.json', data: manifestData },\n { name: 'nodes.json', data: nodesData },\n ...(sessionsData ? [{ name: 'sessions.json', data: sessionsData }] : []),\n ])\n if (zip.bytes > MAX_ARCHIVE_EXPANDED_BYTES) throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive contents exceed the size limit')\n\n mkdirSync(join(this.home, 'dshb'), { recursive: true, mode: 0o700 })\n const directory = mkdtempSync(join(this.home, 'dshb', 'sync-offline-export-'))\n chmodSync(directory, 0o700)\n const path = join(directory, 'export.dshb-sync')\n const salt = randomBytes(SALT_SIZE)\n const nonce = randomBytes(NONCE_SIZE)\n const header = archiveHeader(salt, nonce)\n let key: Buffer | undefined\n try {\n key = await deriveKey(password, salt)\n const cipher = createCipheriv('aes-256-gcm', key, nonce)\n cipher.setAAD(header)\n writeFileSync(path, header, { flag: 'wx', mode: 0o600 })\n await pipeline(Readable.from(zip.chunks), cipher, createWriteStream(path, { flags: 'a', mode: 0o600 }))\n appendFileSync(path, cipher.getAuthTag())\n chmodSync(path, 0o600)\n const bytes = statSync(path).size\n if (bytes > MAX_OFFLINE_ARCHIVE_BYTES) throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive exceeds the size limit')\n return {\n path,\n bytes,\n cleanup: () => rmSync(directory, { recursive: true, force: true }),\n }\n } catch (error) {\n rmSync(directory, { recursive: true, force: true })\n throw error instanceof SyncOfflineArchiveError\n ? error\n : new SyncOfflineArchiveError('archive-export-failed', 'Offline archive export failed', error)\n } finally {\n password.fill(0)\n key?.fill(0)\n for (const chunk of zip.chunks) chunk.fill(0)\n }\n }\n\n private async importArchiveNow(passwordValue: string, input: AsyncIterable<Uint8Array>): Promise<SyncOfflineImportResult> {\n const password = validatePassword(passwordValue)\n mkdirSync(join(this.home, 'dshb'), { recursive: true, mode: 0o700 })\n const batchId = randomBytes(16).toString('hex')\n const stagingDirectory = `sync-offline-staging-${batchId}`\n const directory = join(this.home, 'dshb', stagingDirectory)\n mkdirSync(directory, { mode: 0o700 })\n chmodSync(directory, 0o700)\n const encryptedPath = join(directory, 'input.enc')\n const zipPath = join(directory, 'payload.zip')\n let key: Buffer | undefined\n let plaintextZip: Buffer | undefined\n let payloadFiles: Map<string, Buffer> | undefined\n try {\n let encryptedBytes = 0\n const sizeLimit = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n encryptedBytes += chunk.byteLength\n if (encryptedBytes > MAX_OFFLINE_ARCHIVE_BYTES) {\n callback(new SyncOfflineArchiveError('archive-too-large', 'Offline archive exceeds the upload size limit'))\n return\n }\n callback(null, chunk)\n },\n })\n await pipeline(Readable.from(input), sizeLimit, createWriteStream(encryptedPath, { flags: 'wx', mode: 0o600 }))\n if (encryptedBytes < HEADER_SIZE + TAG_SIZE + 22) throw new SyncOfflineArchiveError('archive-invalid', 'Offline archive is truncated')\n const header = readFileRange(encryptedPath, 0, HEADER_SIZE)\n const { salt, nonce } = parseArchiveHeader(header)\n const tag = readFileRange(encryptedPath, encryptedBytes - TAG_SIZE, TAG_SIZE)\n key = await deriveKey(password, salt)\n const decipher = createDecipheriv('aes-256-gcm', key, nonce)\n decipher.setAAD(header)\n decipher.setAuthTag(tag)\n try {\n await pipeline(\n createReadStream(encryptedPath, { start: HEADER_SIZE, end: encryptedBytes - TAG_SIZE - 1 }),\n decipher,\n createWriteStream(zipPath, { flags: 'wx', mode: 0o600 }),\n )\n } catch (error) {\n throw new SyncOfflineArchiveError('password-or-integrity', 'Package password is incorrect or archive authentication failed', error)\n }\n const zipStat = statSync(zipPath)\n if (zipStat.size > MAX_ARCHIVE_EXPANDED_BYTES) throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive contents exceed the size limit')\n plaintextZip = readFileRange(zipPath, 0, zipStat.size)\n const decoded = decodeOfflinePayload(plaintextZip, this.nodes)\n payloadFiles = decoded.files\n const payloads = decoded.payloads\n const requiredBytes = requiredBytesForImport(payloads, this.state, true, Boolean(payloads.sessions))\n if (!Number.isSafeInteger(requiredBytes)) throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive storage estimate exceeds the supported range')\n checkAvailableSpace(this.home, requiredBytes, this.getAvailableBytes)\n await this.preflightPayloads(payloads, true, Boolean(payloads.sessions))\n\n const journal: SyncOfflineImportJournal = {\n version: 1,\n batchId,\n stagingDirectory,\n zipDigest: sha256(plaintextZip),\n includeSessions: Boolean(payloads.sessions),\n nodesApplied: false,\n sessionsApplied: !payloads.sessions,\n startedAt: new Date().toISOString(),\n }\n this.journal.begin(journal)\n return await this.applyJournaledPayloads(payloads, journal, false)\n } catch (error) {\n if (error instanceof SyncOfflineArchiveError || error instanceof SyncSessionError) throw error\n throw new SyncOfflineArchiveError('archive-import-failed', 'Offline archive import failed', error)\n } finally {\n password.fill(0)\n key?.fill(0)\n plaintextZip?.fill(0)\n if (payloadFiles) for (const data of payloadFiles.values()) data.fill(0)\n let preserveStaging = false\n try {\n preserveStaging = this.journal.read()?.stagingDirectory === stagingDirectory\n } catch {\n preserveStaging = true\n }\n if (!preserveStaging) rmSync(directory, { recursive: true, force: true })\n }\n }\n\n private async recoverPendingNow(): Promise<void> {\n const journal = this.journal.read()\n if (!journal) return\n const directory = join(this.home, 'dshb', journal.stagingDirectory)\n const zipPath = join(directory, 'payload.zip')\n let zip: Buffer | undefined\n let files: Map<string, Buffer> | undefined\n try {\n const zipBytes = statSync(zipPath).size\n if (zipBytes > MAX_ARCHIVE_EXPANDED_BYTES) throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive staging exceeds the size limit')\n zip = readFileRange(zipPath, 0, zipBytes)\n if (sha256(zip) !== journal.zipDigest) throw new SyncOfflineArchiveError('journal-integrity', 'Staged offline archive digest does not match its journal')\n const decoded = decodeOfflinePayload(zip, this.nodes)\n files = decoded.files\n if (decoded.payloads.manifest.includeSessions !== journal.includeSessions) {\n throw new SyncOfflineArchiveError('journal-invalid', 'Offline archive journal options do not match the staged payload')\n }\n await this.applyJournaledPayloads(decoded.payloads, journal, true)\n } finally {\n zip?.fill(0)\n if (files) for (const data of files.values()) data.fill(0)\n }\n }\n\n private async preflightPayloads(\n payloads: ReturnType<typeof validateManifest>,\n nodesPending: boolean,\n sessionsPending: boolean,\n ): Promise<void> {\n if (nodesPending) {\n try {\n await this.nodes.preflightManifest(payloads.nodes.records)\n } catch (error) {\n throw new SyncOfflineArchiveError('archive-conflict', 'Target node state conflicts with the offline archive', error)\n }\n }\n if (sessionsPending && payloads.sessions) {\n await this.sessions.recoverPending()\n await this.sessions.exportBundle()\n this.sessions.preflightBundle(payloads.sessions, { allowSameInstance: true })\n }\n }\n\n private async applyJournaledPayloads(\n payloads: ReturnType<typeof validateManifest>,\n initialJournal: SyncOfflineImportJournal,\n runPreflight: boolean,\n ): Promise<SyncOfflineImportResult> {\n let journal = initialJournal\n const nodesPending = !journal.nodesApplied\n const sessionsPending = journal.includeSessions && !journal.sessionsApplied\n if (!nodesPending && !sessionsPending) {\n this.journal.complete()\n rmSync(join(this.home, 'dshb', journal.stagingDirectory), { recursive: true, force: true })\n return { nodes: { created: 0, updated: 0, deleted: 0, pendingRemoval: 0, skipped: 0 } }\n }\n if (runPreflight) {\n const requiredBytes = requiredBytesForImport(payloads, this.state, nodesPending, sessionsPending)\n if (!Number.isSafeInteger(requiredBytes)) throw new SyncOfflineArchiveError('archive-too-large', 'Offline archive storage estimate exceeds the supported range')\n checkAvailableSpace(this.home, requiredBytes, this.getAvailableBytes)\n await this.preflightPayloads(payloads, nodesPending, sessionsPending)\n }\n\n const nodeResult = nodesPending\n ? await this.nodes.applyManifest(payloads.nodes.records, 'offline archive', 'offline' satisfies SyncImportMethod)\n : { created: 0, updated: 0, deleted: 0, pendingRemoval: 0, skipped: 0 }\n if (nodesPending) journal = this.journal.update({ nodesApplied: true })\n\n let sessionResult: SyncOfflineImportResult['sessions']\n if (sessionsPending && payloads.sessions) {\n sessionResult = await this.sessions.applyBundle(\n payloads.sessions,\n 'offline archive',\n 'p-000000000000000000000000',\n { importedVia: 'offline', allowSameInstance: true },\n )\n journal = this.journal.update({ sessionsApplied: true })\n }\n\n this.journal.complete()\n rmSync(join(this.home, 'dshb', journal.stagingDirectory), { recursive: true, force: true })\n return { nodes: nodeResult, ...(sessionResult ? { sessions: sessionResult } : {}) }\n }\n\n private serializeMutation<T>(operation: () => Promise<T>): Promise<T> {\n const result = this.operationTail.then(operation)\n this.operationTail = result.then(() => undefined, () => undefined)\n return result\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ //#region src/core/sync/offline-journal.d.ts
2
+ interface SyncOfflineImportJournal {
3
+ version: 1;
4
+ batchId: string;
5
+ stagingDirectory: string;
6
+ zipDigest: string;
7
+ includeSessions: boolean;
8
+ nodesApplied: boolean;
9
+ sessionsApplied: boolean;
10
+ startedAt: string;
11
+ }
12
+ declare class SyncOfflineArchiveJournalStore {
13
+ private readonly filePath;
14
+ constructor(home: string);
15
+ read(): SyncOfflineImportJournal | undefined;
16
+ begin(journal: SyncOfflineImportJournal): void;
17
+ update(patch: Partial<Pick<SyncOfflineImportJournal, 'nodesApplied' | 'sessionsApplied'>>): SyncOfflineImportJournal;
18
+ complete(): void;
19
+ }
20
+ //#endregion
21
+ export { SyncOfflineArchiveJournalStore, SyncOfflineImportJournal };
22
+ //# sourceMappingURL=offline-journal.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { randomBytes } from "node:crypto";
4
+ //#region src/core/sync/offline-journal.ts
5
+ function atomicWrite(path, value) {
6
+ mkdirSync(dirname(path), {
7
+ recursive: true,
8
+ mode: 448
9
+ });
10
+ const temporary = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
11
+ let descriptor;
12
+ try {
13
+ descriptor = openSync(temporary, "wx", 384);
14
+ writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`, "utf8");
15
+ fsyncSync(descriptor);
16
+ closeSync(descriptor);
17
+ descriptor = void 0;
18
+ renameSync(temporary, path);
19
+ chmodSync(path, 384);
20
+ } catch (error) {
21
+ if (descriptor !== void 0) closeSync(descriptor);
22
+ try {
23
+ unlinkSync(temporary);
24
+ } catch (cleanupError) {
25
+ if (!(cleanupError instanceof Error) || !("code" in cleanupError) || cleanupError.code !== "ENOENT") throw new AggregateError([error, cleanupError], "offline journal write and cleanup failed");
26
+ }
27
+ throw error;
28
+ }
29
+ }
30
+ function validateJournal(value) {
31
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid offline import journal");
32
+ const journal = value;
33
+ if (journal.version !== 1 || typeof journal.batchId !== "string" || !/^[a-f0-9]{32}$/.test(journal.batchId) || typeof journal.stagingDirectory !== "string" || journal.stagingDirectory !== `sync-offline-staging-${journal.batchId}` || typeof journal.zipDigest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(journal.zipDigest) || typeof journal.includeSessions !== "boolean" || typeof journal.nodesApplied !== "boolean" || typeof journal.sessionsApplied !== "boolean" || !journal.includeSessions && !journal.sessionsApplied || journal.includeSessions && journal.sessionsApplied && !journal.nodesApplied || typeof journal.startedAt !== "string" || journal.startedAt.length > 64) throw new Error("Invalid offline import journal fields");
34
+ return journal;
35
+ }
36
+ var SyncOfflineArchiveJournalStore = class {
37
+ filePath;
38
+ constructor(home) {
39
+ this.filePath = join(home, "dshb", "sync-offline-journal.json");
40
+ }
41
+ read() {
42
+ try {
43
+ return validateJournal(JSON.parse(readFileSync(this.filePath, "utf8")));
44
+ } catch (error) {
45
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
46
+ throw error;
47
+ }
48
+ }
49
+ begin(journal) {
50
+ if (this.read()) throw new Error("An offline import batch is already pending");
51
+ atomicWrite(this.filePath, validateJournal(journal));
52
+ }
53
+ update(patch) {
54
+ const current = this.read();
55
+ if (!current) throw new Error("Offline import journal is missing");
56
+ const updated = validateJournal({
57
+ ...current,
58
+ ...patch
59
+ });
60
+ atomicWrite(this.filePath, updated);
61
+ return updated;
62
+ }
63
+ complete() {
64
+ try {
65
+ unlinkSync(this.filePath);
66
+ } catch (error) {
67
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
68
+ }
69
+ }
70
+ };
71
+ //#endregion
72
+ export { SyncOfflineArchiveJournalStore };
73
+
74
+ //# sourceMappingURL=offline-journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-journal.js","names":[],"sources":["../../../src/core/sync/offline-journal.ts"],"sourcesContent":["import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { randomBytes } from 'node:crypto'\n\nexport interface SyncOfflineImportJournal {\n version: 1\n batchId: string\n stagingDirectory: string\n zipDigest: string\n includeSessions: boolean\n nodesApplied: boolean\n sessionsApplied: boolean\n startedAt: string\n}\n\nfunction atomicWrite(path: string, value: unknown): void {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 })\n const temporary = `${path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`\n let descriptor: number | undefined\n try {\n descriptor = openSync(temporary, 'wx', 0o600)\n writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\\n`, 'utf8')\n fsyncSync(descriptor)\n closeSync(descriptor)\n descriptor = undefined\n renameSync(temporary, path)\n chmodSync(path, 0o600)\n } catch (error) {\n if (descriptor !== undefined) closeSync(descriptor)\n try {\n unlinkSync(temporary)\n } catch (cleanupError) {\n if (!(cleanupError instanceof Error) || !('code' in cleanupError) || cleanupError.code !== 'ENOENT') {\n throw new AggregateError([error, cleanupError], 'offline journal write and cleanup failed')\n }\n }\n throw error\n }\n}\n\nfunction validateJournal(value: unknown): SyncOfflineImportJournal {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid offline import journal')\n const journal = value as Record<string, unknown>\n if (journal.version !== 1 || typeof journal.batchId !== 'string' || !/^[a-f0-9]{32}$/.test(journal.batchId) ||\n typeof journal.stagingDirectory !== 'string' || journal.stagingDirectory !== `sync-offline-staging-${journal.batchId}` ||\n typeof journal.zipDigest !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(journal.zipDigest) ||\n typeof journal.includeSessions !== 'boolean' || typeof journal.nodesApplied !== 'boolean' ||\n typeof journal.sessionsApplied !== 'boolean' || (!journal.includeSessions && !journal.sessionsApplied) ||\n (journal.includeSessions && journal.sessionsApplied && !journal.nodesApplied) ||\n typeof journal.startedAt !== 'string' || journal.startedAt.length > 64) {\n throw new Error('Invalid offline import journal fields')\n }\n return journal as unknown as SyncOfflineImportJournal\n}\n\nexport class SyncOfflineArchiveJournalStore {\n private readonly filePath: string\n\n constructor(home: string) {\n this.filePath = join(home, 'dshb', 'sync-offline-journal.json')\n }\n\n read(): SyncOfflineImportJournal | undefined {\n try {\n return validateJournal(JSON.parse(readFileSync(this.filePath, 'utf8')) as unknown)\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return undefined\n throw error\n }\n }\n\n begin(journal: SyncOfflineImportJournal): void {\n if (this.read()) throw new Error('An offline import batch is already pending')\n atomicWrite(this.filePath, validateJournal(journal))\n }\n\n update(patch: Partial<Pick<SyncOfflineImportJournal, 'nodesApplied' | 'sessionsApplied'>>): SyncOfflineImportJournal {\n const current = this.read()\n if (!current) throw new Error('Offline import journal is missing')\n const updated = validateJournal({ ...current, ...patch })\n atomicWrite(this.filePath, updated)\n return updated\n }\n\n complete(): void {\n try {\n unlinkSync(this.filePath)\n } catch (error) {\n if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') throw error\n }\n }\n}\n"],"mappings":";;;;AAeA,SAAS,YAAY,MAAc,OAAsB;CACvD,UAAU,QAAQ,IAAI,GAAG;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CACzD,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE;CAC3E,IAAI;CACJ,IAAI;EACF,aAAa,SAAS,WAAW,MAAM,GAAK;EAC5C,cAAc,YAAY,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,EAAE,KAAK,MAAM;EACvE,UAAU,UAAU;EACpB,UAAU,UAAU;EACpB,aAAa,KAAA;EACb,WAAW,WAAW,IAAI;EAC1B,UAAU,MAAM,GAAK;CACvB,SAAS,OAAO;EACd,IAAI,eAAe,KAAA,GAAW,UAAU,UAAU;EAClD,IAAI;GACF,WAAW,SAAS;EACtB,SAAS,cAAc;GACrB,IAAI,EAAE,wBAAwB,UAAU,EAAE,UAAU,iBAAiB,aAAa,SAAS,UACzF,MAAM,IAAI,eAAe,CAAC,OAAO,YAAY,GAAG,0CAA0C;EAE9F;EACA,MAAM;CACR;AACF;AAEA,SAAS,gBAAgB,OAA0C;CACjE,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,MAAM,IAAI,MAAM,gCAAgC;CACzH,MAAM,UAAU;CAChB,IAAI,QAAQ,YAAY,KAAK,OAAO,QAAQ,YAAY,YAAY,CAAC,iBAAiB,KAAK,QAAQ,OAAO,KACxG,OAAO,QAAQ,qBAAqB,YAAY,QAAQ,qBAAqB,wBAAwB,QAAQ,aAC7G,OAAO,QAAQ,cAAc,YAAY,CAAC,wBAAwB,KAAK,QAAQ,SAAS,KACxF,OAAO,QAAQ,oBAAoB,aAAa,OAAO,QAAQ,iBAAiB,aAChF,OAAO,QAAQ,oBAAoB,aAAc,CAAC,QAAQ,mBAAmB,CAAC,QAAQ,mBACrF,QAAQ,mBAAmB,QAAQ,mBAAmB,CAAC,QAAQ,gBAChE,OAAO,QAAQ,cAAc,YAAY,QAAQ,UAAU,SAAS,IACpE,MAAM,IAAI,MAAM,uCAAuC;CAEzD,OAAO;AACT;AAEA,IAAa,iCAAb,MAA4C;CAC1C;CAEA,YAAY,MAAc;EACxB,KAAK,WAAW,KAAK,MAAM,QAAQ,2BAA2B;CAChE;CAEA,OAA6C;EAC3C,IAAI;GACF,OAAO,gBAAgB,KAAK,MAAM,aAAa,KAAK,UAAU,MAAM,CAAC,CAAY;EACnF,SAAS,OAAO;GACd,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAAU,OAAO,KAAA;GACjF,MAAM;EACR;CACF;CAEA,MAAM,SAAyC;EAC7C,IAAI,KAAK,KAAK,GAAG,MAAM,IAAI,MAAM,4CAA4C;EAC7E,YAAY,KAAK,UAAU,gBAAgB,OAAO,CAAC;CACrD;CAEA,OAAO,OAA8G;EACnH,MAAM,UAAU,KAAK,KAAK;EAC1B,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,mCAAmC;EACjE,MAAM,UAAU,gBAAgB;GAAE,GAAG;GAAS,GAAG;EAAM,CAAC;EACxD,YAAY,KAAK,UAAU,OAAO;EAClC,OAAO;CACT;CAEA,WAAiB;EACf,IAAI;GACF,WAAW,KAAK,QAAQ;EAC1B,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,UAAU,EAAE,UAAU,UAAU,MAAM,SAAS,UAAU,MAAM;EACxF;CACF;AACF"}
@@ -0,0 +1,7 @@
1
+ import { SyncOfflineArchiveService } from "./offline-archive.js";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/core/sync/offline-routes.d.ts
4
+ declare function registerSyncOfflineRoutes(ctx: Context, archives: SyncOfflineArchiveService): void;
5
+ //#endregion
6
+ export { registerSyncOfflineRoutes };
7
+ //# sourceMappingURL=offline-routes.d.ts.map
@@ -0,0 +1,192 @@
1
+ import { t as SyncSessionError } from "../../session-sync-EdfwUON5.js";
2
+ import { SyncOfflineArchiveError } from "./offline-archive.js";
3
+ import { createReadStream } from "node:fs";
4
+ import { randomBytes } from "node:crypto";
5
+ import { pipeline } from "node:stream/promises";
6
+ //#region src/core/sync/offline-routes.ts
7
+ const MAX_JSON_BYTES = 8192;
8
+ const UPLOAD_TOKEN_TTL_MS = 3e5;
9
+ function sendJson(res, status, body) {
10
+ res.writeHead(status, {
11
+ "content-type": "application/json",
12
+ "cache-control": "no-store"
13
+ });
14
+ res.end(JSON.stringify(body));
15
+ }
16
+ function pathSegments(req) {
17
+ return new URL(req.url ?? "/", "http://x").pathname.split("/").filter(Boolean);
18
+ }
19
+ function readJson(req) {
20
+ return new Promise((resolve) => {
21
+ if (req.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") {
22
+ resolve(void 0);
23
+ return;
24
+ }
25
+ const chunks = [];
26
+ let bytes = 0;
27
+ let settled = false;
28
+ const fail = () => {
29
+ if (settled) return;
30
+ settled = true;
31
+ resolve(void 0);
32
+ };
33
+ req.on("data", (chunk) => {
34
+ if (settled) return;
35
+ bytes += chunk.byteLength;
36
+ if (bytes > MAX_JSON_BYTES) {
37
+ fail();
38
+ req.resume();
39
+ return;
40
+ }
41
+ chunks.push(chunk);
42
+ });
43
+ req.on("end", () => {
44
+ if (settled) return;
45
+ try {
46
+ const value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
47
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return fail();
48
+ settled = true;
49
+ resolve(value);
50
+ } catch {
51
+ fail();
52
+ }
53
+ });
54
+ req.on("error", fail);
55
+ });
56
+ }
57
+ function archiveFailure(res, error) {
58
+ if (error instanceof SyncSessionError) {
59
+ sendJson(res, error.code === "bundle-too-large" || error.code === "session-too-large" ? 413 : error.code.includes("conflict") ? 409 : 422, {
60
+ ok: false,
61
+ code: error.code,
62
+ error: error.message
63
+ });
64
+ return;
65
+ }
66
+ if (!(error instanceof SyncOfflineArchiveError)) {
67
+ sendJson(res, 500, {
68
+ ok: false,
69
+ code: "archive-failed",
70
+ error: "Offline archive operation failed"
71
+ });
72
+ return;
73
+ }
74
+ sendJson(res, error.code === "archive-too-large" ? 413 : error.code === "disk-space" ? 507 : error.code === "archive-conflict" ? 409 : error.code === "password-required" || error.code === "password-invalid" || error.code === "request-invalid" ? 400 : error.code === "format-unsupported" ? 415 : error.code === "password-or-integrity" || error.code === "archive-integrity" || error.code === "zip-invalid" || error.code === "path-invalid" ? 422 : error.code === "archive-invalid" ? 422 : error.code.endsWith("-failed") ? 500 : 400, {
75
+ ok: false,
76
+ code: error.code,
77
+ error: error.message
78
+ });
79
+ }
80
+ function registerSyncOfflineRoutes(ctx, archives) {
81
+ const uploadTokens = /* @__PURE__ */ new Map();
82
+ ctx.effect(() => {
83
+ const unregister = ctx.webServer.register({
84
+ kind: "prefix",
85
+ path: "/api/dshb/sync/offline",
86
+ handler: async (req, res) => {
87
+ const segments = pathSegments(req);
88
+ if (segments.length === 5 && segments[4] === "export" && req.method === "POST") {
89
+ const body = await readJson(req);
90
+ if (!body || typeof body.password !== "string" || body.includeSessions !== void 0 && typeof body.includeSessions !== "boolean") {
91
+ sendJson(res, 400, {
92
+ ok: false,
93
+ code: "request-invalid",
94
+ error: "Export request is invalid"
95
+ });
96
+ return;
97
+ }
98
+ let archive;
99
+ try {
100
+ archive = await archives.exportArchive(body.password, body.includeSessions === true);
101
+ res.writeHead(200, {
102
+ "content-type": "application/octet-stream",
103
+ "content-length": archive.bytes,
104
+ "content-disposition": `attachment; filename="dshb-sync-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.dshb-sync"`,
105
+ "cache-control": "no-store",
106
+ "x-content-type-options": "nosniff"
107
+ });
108
+ await pipeline(createReadStream(archive.path), res);
109
+ } catch (error) {
110
+ if (!res.headersSent) archiveFailure(res, error);
111
+ } finally {
112
+ archive?.cleanup();
113
+ }
114
+ return;
115
+ }
116
+ if (segments.length === 5 && segments[4] === "import" && req.method === "POST") {
117
+ const body = await readJson(req);
118
+ if (!body || typeof body.password !== "string" || Buffer.byteLength(body.password, "utf8") < 8 || Buffer.byteLength(body.password, "utf8") > 1024 || body.password.includes("\0")) {
119
+ sendJson(res, 400, {
120
+ ok: false,
121
+ code: "password-invalid",
122
+ error: "Package password must be 8-1024 UTF-8 bytes"
123
+ });
124
+ return;
125
+ }
126
+ const token = randomBytes(24).toString("hex");
127
+ const timer = setTimeout(() => uploadTokens.delete(token), UPLOAD_TOKEN_TTL_MS);
128
+ timer.unref();
129
+ uploadTokens.set(token, {
130
+ password: body.password,
131
+ timer
132
+ });
133
+ sendJson(res, 201, {
134
+ ok: true,
135
+ uploadToken: token,
136
+ expiresInMs: UPLOAD_TOKEN_TTL_MS
137
+ });
138
+ return;
139
+ }
140
+ if (segments.length === 6 && segments[4] === "import" && /^[a-f0-9]{48}$/.test(segments[5] ?? "") && req.method === "PUT") {
141
+ const token = segments[5];
142
+ const pending = uploadTokens.get(token);
143
+ if (!pending) {
144
+ req.resume();
145
+ sendJson(res, 404, {
146
+ ok: false,
147
+ code: "upload-token-expired",
148
+ error: "Import upload token expired"
149
+ });
150
+ return;
151
+ }
152
+ clearTimeout(pending.timer);
153
+ uploadTokens.delete(token);
154
+ const contentType = req.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase();
155
+ const contentLength = Number(req.headers["content-length"]);
156
+ if (contentType !== "application/octet-stream" || Number.isFinite(contentLength) && contentLength > 134217728) {
157
+ req.resume();
158
+ sendJson(res, contentLength > 134217728 ? 413 : 415, {
159
+ ok: false,
160
+ code: contentLength > 134217728 ? "archive-too-large" : "content-type-invalid",
161
+ error: contentLength > 134217728 ? "Offline archive exceeds the upload size limit" : "Expected an encrypted archive body"
162
+ });
163
+ return;
164
+ }
165
+ try {
166
+ sendJson(res, 200, {
167
+ ok: true,
168
+ result: await archives.importArchive(pending.password, req)
169
+ });
170
+ } catch (error) {
171
+ req.resume();
172
+ archiveFailure(res, error);
173
+ }
174
+ return;
175
+ }
176
+ sendJson(res, 404, {
177
+ ok: false,
178
+ error: "not found"
179
+ });
180
+ }
181
+ });
182
+ return () => {
183
+ unregister();
184
+ for (const pending of uploadTokens.values()) clearTimeout(pending.timer);
185
+ uploadTokens.clear();
186
+ };
187
+ });
188
+ }
189
+ //#endregion
190
+ export { registerSyncOfflineRoutes };
191
+
192
+ //# sourceMappingURL=offline-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-routes.js","names":[],"sources":["../../../src/core/sync/offline-routes.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport { createReadStream } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport { pipeline } from 'node:stream/promises'\nimport { MAX_OFFLINE_ARCHIVE_BYTES, SyncOfflineArchiveError, SyncOfflineArchiveService } from './offline-archive.js'\nimport { SyncSessionError } from './session-sync.js'\n\nconst MAX_JSON_BYTES = 8 * 1024\nconst UPLOAD_TOKEN_TTL_MS = 5 * 60 * 1000\n\ninterface UploadToken {\n password: string\n timer: NodeJS.Timeout\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' })\n res.end(JSON.stringify(body))\n}\n\nfunction pathSegments(req: IncomingMessage): string[] {\n return new URL(req.url ?? '/', 'http://x').pathname.split('/').filter(Boolean)\n}\n\nfunction readJson(req: IncomingMessage): Promise<Record<string, unknown> | undefined> {\n return new Promise((resolve) => {\n if (req.headers['content-type']?.split(';', 1)[0]?.trim().toLowerCase() !== 'application/json') {\n resolve(undefined)\n return\n }\n const chunks: Buffer[] = []\n let bytes = 0\n let settled = false\n const fail = (): void => {\n if (settled) return\n settled = true\n resolve(undefined)\n }\n req.on('data', (chunk: Buffer) => {\n if (settled) return\n bytes += chunk.byteLength\n if (bytes > MAX_JSON_BYTES) {\n fail()\n req.resume()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n if (settled) return\n try {\n const value: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return fail()\n settled = true\n resolve(value as Record<string, unknown>)\n } catch {\n fail()\n }\n })\n req.on('error', fail)\n })\n}\n\nfunction archiveFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncSessionError) {\n const status = error.code === 'bundle-too-large' || error.code === 'session-too-large' ? 413\n : error.code.includes('conflict') ? 409\n : 422\n sendJson(res, status, { ok: false, code: error.code, error: error.message })\n return\n }\n if (!(error instanceof SyncOfflineArchiveError)) {\n sendJson(res, 500, { ok: false, code: 'archive-failed', error: 'Offline archive operation failed' })\n return\n }\n const status = error.code === 'archive-too-large' ? 413\n : error.code === 'disk-space' ? 507\n : error.code === 'archive-conflict' ? 409\n : error.code === 'password-required' || error.code === 'password-invalid' || error.code === 'request-invalid' ? 400\n : error.code === 'format-unsupported' ? 415\n : error.code === 'password-or-integrity' || error.code === 'archive-integrity' || error.code === 'zip-invalid' || error.code === 'path-invalid' ? 422\n : error.code === 'archive-invalid' ? 422\n : error.code.endsWith('-failed') ? 500 : 400\n sendJson(res, status, { ok: false, code: error.code, error: error.message })\n}\n\nexport function registerSyncOfflineRoutes(ctx: Context, archives: SyncOfflineArchiveService): void {\n const uploadTokens = new Map<string, UploadToken>()\n ctx.effect(() => {\n const unregister = ctx.webServer.register({\n kind: 'prefix',\n path: '/api/dshb/sync/offline',\n handler: async (req, res) => {\n const segments = pathSegments(req)\n if (segments.length === 5 && segments[4] === 'export' && req.method === 'POST') {\n const body = await readJson(req)\n if (!body || typeof body.password !== 'string' ||\n (body.includeSessions !== undefined && typeof body.includeSessions !== 'boolean')) {\n sendJson(res, 400, { ok: false, code: 'request-invalid', error: 'Export request is invalid' })\n return\n }\n let archive: Awaited<ReturnType<SyncOfflineArchiveService['exportArchive']>> | undefined\n try {\n archive = await archives.exportArchive(body.password, body.includeSessions === true)\n res.writeHead(200, {\n 'content-type': 'application/octet-stream',\n 'content-length': archive.bytes,\n 'content-disposition': `attachment; filename=\"dshb-sync-${new Date().toISOString().slice(0, 10)}.dshb-sync\"`,\n 'cache-control': 'no-store',\n 'x-content-type-options': 'nosniff',\n })\n await pipeline(createReadStream(archive.path), res)\n } catch (error) {\n if (!res.headersSent) archiveFailure(res, error)\n } finally {\n archive?.cleanup()\n }\n return\n }\n\n if (segments.length === 5 && segments[4] === 'import' && req.method === 'POST') {\n const body = await readJson(req)\n if (!body || typeof body.password !== 'string' || Buffer.byteLength(body.password, 'utf8') < 8 ||\n Buffer.byteLength(body.password, 'utf8') > 1024 || body.password.includes('\\0')) {\n sendJson(res, 400, { ok: false, code: 'password-invalid', error: 'Package password must be 8-1024 UTF-8 bytes' })\n return\n }\n const token = randomBytes(24).toString('hex')\n const timer = setTimeout(() => uploadTokens.delete(token), UPLOAD_TOKEN_TTL_MS)\n timer.unref()\n uploadTokens.set(token, { password: body.password, timer })\n sendJson(res, 201, { ok: true, uploadToken: token, expiresInMs: UPLOAD_TOKEN_TTL_MS })\n return\n }\n\n if (segments.length === 6 && segments[4] === 'import' && /^[a-f0-9]{48}$/.test(segments[5] ?? '') && req.method === 'PUT') {\n const token = segments[5]!\n const pending = uploadTokens.get(token)\n if (!pending) {\n req.resume()\n sendJson(res, 404, { ok: false, code: 'upload-token-expired', error: 'Import upload token expired' })\n return\n }\n clearTimeout(pending.timer)\n uploadTokens.delete(token)\n const contentType = req.headers['content-type']?.split(';', 1)[0]?.trim().toLowerCase()\n const contentLength = Number(req.headers['content-length'])\n if (contentType !== 'application/octet-stream' || (Number.isFinite(contentLength) && contentLength > MAX_OFFLINE_ARCHIVE_BYTES)) {\n req.resume()\n sendJson(res, contentLength > MAX_OFFLINE_ARCHIVE_BYTES ? 413 : 415, {\n ok: false,\n code: contentLength > MAX_OFFLINE_ARCHIVE_BYTES ? 'archive-too-large' : 'content-type-invalid',\n error: contentLength > MAX_OFFLINE_ARCHIVE_BYTES ? 'Offline archive exceeds the upload size limit' : 'Expected an encrypted archive body',\n })\n return\n }\n try {\n const result = await archives.importArchive(pending.password, req)\n sendJson(res, 200, { ok: true, result })\n } catch (error) {\n req.resume()\n archiveFailure(res, error)\n }\n return\n }\n sendJson(res, 404, { ok: false, error: 'not found' })\n },\n })\n return () => {\n unregister()\n for (const pending of uploadTokens.values()) clearTimeout(pending.timer)\n uploadTokens.clear()\n }\n })\n}\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { a as SyncPeer, s as SyncPeerProgress } from "../../model-BP7Gw1yk.js";
2
+ import { n as SyncStateStore, t as SyncPeerStore } from "../../stores-CKyEt0yk.js";
3
+ import { CredentialKey } from "@deepseek-ai/dsh-credentials";
4
+ //#region src/core/sync/peer-manager.d.ts
5
+ interface SyncPeerSecrets {
6
+ password?: string;
7
+ privateKey?: string;
8
+ passphrase?: string;
9
+ }
10
+ interface SyncPeerView extends SyncPeer {
11
+ hasSecret: {
12
+ hasPassword: boolean;
13
+ hasKey: boolean;
14
+ hasPassphrase: boolean;
15
+ };
16
+ progress?: SyncPeerProgress;
17
+ }
18
+ type SyncPeerChangeListener = (peerId: string, peer: SyncPeer | undefined) => void;
19
+ interface CredentialsLike {
20
+ readRecord(key: CredentialKey): Promise<unknown>;
21
+ modifyRecord(key: CredentialKey, mutate: (current: unknown) => Promise<unknown>): Promise<unknown>;
22
+ deleteRecord(key: CredentialKey): Promise<void>;
23
+ }
24
+ declare class SyncPeerInputError extends Error {
25
+ constructor(message: string);
26
+ }
27
+ declare class SyncPeerNotFoundError extends Error {
28
+ constructor(id: string);
29
+ }
30
+ declare class SyncPeerManager {
31
+ private readonly peers;
32
+ private readonly state;
33
+ private readonly credentials;
34
+ private readonly listeners;
35
+ constructor(peers: SyncPeerStore, state: SyncStateStore, credentials: CredentialsLike);
36
+ list(): Promise<SyncPeerView[]>;
37
+ subscribe(listener: SyncPeerChangeListener): () => void;
38
+ updateProgress(id: string, patch: SyncPeerProgress): void;
39
+ get(id: string): Promise<SyncPeerView | undefined>;
40
+ connectionData(id: string): Promise<{
41
+ peer: SyncPeer;
42
+ secrets: SyncPeerSecrets;
43
+ }>;
44
+ pinHostKey(id: string, fingerprint: string): Promise<SyncPeerView>;
45
+ create(draftValue: unknown, secretsValue?: unknown): Promise<SyncPeerView>;
46
+ update(id: string, patchValue: unknown, secretsValue?: unknown): Promise<SyncPeerView>;
47
+ remove(id: string): Promise<boolean>;
48
+ private view;
49
+ private notify;
50
+ private writeSecrets;
51
+ private restoreCredentialRecord;
52
+ private credentialKey;
53
+ }
54
+ //#endregion
55
+ export { SyncPeerChangeListener, SyncPeerInputError, SyncPeerManager, SyncPeerNotFoundError, SyncPeerSecrets, SyncPeerView };
56
+ //# sourceMappingURL=peer-manager.d.ts.map