'use strict' const fs = require('node:fs') const { randomUUID, createHash } = require('node:crypto') const prefix = '\x1eYCODE_WORKSPACE_READ_V1 ' let selected = false function error(code, message) { return Object.assign(new Error(message), { code }) } function frame(operation, args, id) { return '\n' + prefix + JSON.stringify({ version: 1, requestId: id, operation, args }) + '\n' } function send(operation, args, id) { const bytes = Buffer.from(frame(operation, args, id)) let offset = 0 while (offset < bytes.length) offset += fs.writeSync(1, bytes, offset, bytes.length - offset) } function unpack(line, id) { let response try { response = JSON.parse(line) } catch { throw error('EPROTO', 'Invalid file bridge reply') } if (response.version !== 1 || response.requestId !== id || typeof response.ok !== 'boolean') throw error('EPROTO', 'Unexpected file bridge reply') if (!response.ok) throw error(response.error?.code || 'EACCES', response.error?.message || 'Workspace read denied') return response.value } function claim() { if (selected) throw error('EBUSY', 'Choose one bridge client and mode per process; stdin belongs to that client') selected = true } function decode(part, initial, offset) { if (!part || part.snapshotId !== initial.snapshotId || part.sha256 !== initial.sha256 || part.size !== initial.size || part.offset !== offset || typeof part.data !== 'string') throw error('EPROTO', 'Inconsistent read snapshot') const bytes = Buffer.from(part.data, 'base64') if (bytes.length > 16384 || !bytes.length && offset < initial.size || offset + bytes.length > initial.size || part.eof !== (offset + bytes.length === initial.size)) throw error('EPROTO', 'Invalid read chunk') return bytes } function complete(chunks, initial, encoding) { const bytes = Buffer.concat(chunks) if (bytes.length !== initial.size || createHash('sha256').update(bytes).digest('hex') !== initial.sha256) throw error('EPROTO', 'Read snapshot digest mismatch') return encoding ? bytes.toString(encoding) : bytes } function connectSync() { claim() let buffered = Buffer.alloc(0), closed = false function call(operation, args, id = randomUUID()) { if (closed) throw error('EPIPE', 'File bridge client closed') send(operation, args, id) for (;;) { const end = buffered.indexOf(10) if (end >= 0) { const line = buffered.subarray(0, end).toString('utf8'); buffered = buffered.subarray(end + 1) return unpack(line, id) } if (buffered.length > 32768) throw error('EPROTO', 'File bridge reply exceeds limit') const bytes = Buffer.alloc(32768), length = fs.readSync(0, bytes, 0, bytes.length, null) if (!length) { closed = true; throw error('EPIPE', 'File bridge disconnected') } buffered = Buffer.concat([buffered, bytes.subarray(0, length)]) } } return { call, readFileSync(path, encoding) { const initial = call('workspace.readFile', { path }), chunks = [] try { let part = initial, offset = 0 for (;;) { const bytes = decode(part, initial, offset); chunks.push(bytes); offset += bytes.length if (part.eof) return complete(chunks, initial, encoding) part = call('workspace.readChunk', { snapshotId: initial.snapshotId, offset }) } } finally { call('workspace.closeRead', { snapshotId: initial.snapshotId }) } }, statSync: path => call('workspace.stat', { path }), readdirSync: path => call('workspace.readdir', { path }).entries, querySync: requestId => call('operation.query', { requestId }), close() { closed = true } } } function connect() { claim() const waiting = new Map(), queue = [] let buffered = Buffer.alloc(0), closed = false, failure function shutdown(reason) { if (closed) return closed = true; failure = reason process.stdin.off('data', receive); process.stdin.off('end', ended); process.stdin.off('error', failed); process.stdin.pause() process.stdin.destroy() for (const item of [...waiting.values(), ...queue]) item.reject(reason) waiting.clear(); queue.length = 0 } function flush() { while (!closed && waiting.size < 8 && queue.length) { const item = queue.shift(); waiting.set(item.id, item) try { send(item.operation, item.args, item.id) } catch (reason) { shutdown(reason) } } if (waiting.size) { process.stdin.ref?.(); process.stdin.resume() } else { process.stdin.pause(); process.stdin.unref?.() } } function receive(bytes) { buffered = Buffer.concat([buffered, bytes]) for (;;) { const end = buffered.indexOf(10) if (end < 0) { if (buffered.length > 32768) shutdown(error('EPROTO', 'File bridge reply exceeds limit')); return } const line = buffered.subarray(0, end).toString('utf8'); buffered = buffered.subarray(end + 1) let id try { id = JSON.parse(line).requestId } catch { shutdown(error('EPROTO', 'Invalid file bridge reply')); return } const item = waiting.get(id) if (!item) { shutdown(error('EPROTO', 'Unexpected file bridge reply')); return } waiting.delete(id) try { item.resolve(unpack(line, id)) } catch (reason) { item.reject(reason) } flush() } } const ended = () => shutdown(error('EPIPE', 'File bridge disconnected')) const failed = reason => shutdown(reason) process.stdin.on('data', receive); process.stdin.on('end', ended); process.stdin.on('error', failed); process.stdin.pause() function call(operation, args, id = randomUUID()) { if (closed) return Promise.reject(failure || error('EPIPE', 'File bridge client closed')) if (queue.length + waiting.size >= 32) return Promise.reject(error('EBUSY', 'File bridge client queue full')) return new Promise((resolve, reject) => { queue.push({ id, operation, args, resolve, reject }); flush() }) } return { call, async readFile(path, encoding) { const initial = await call('workspace.readFile', { path }), chunks = [] try { let part = initial, offset = 0 for (;;) { const bytes = decode(part, initial, offset); chunks.push(bytes); offset += bytes.length if (part.eof) return complete(chunks, initial, encoding) part = await call('workspace.readChunk', { snapshotId: initial.snapshotId, offset }) } } finally { await call('workspace.closeRead', { snapshotId: initial.snapshotId }) } }, stat: path => call('workspace.stat', { path }), readdir: async path => (await call('workspace.readdir', { path })).entries, query: requestId => call('operation.query', { requestId }), close() { shutdown(error('EPIPE', 'File bridge client closed')) } } } module.exports = { connect, connectSync }