@fleetagent/pi-daemon 0.1.4 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.8](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.7...@fleetagent/pi-daemon-v0.1.8) (2026-07-13)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **@fleetagent/pi-daemon:** Synchronize pi versions
9
+
10
+ ## [0.1.7](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.6...@fleetagent/pi-daemon-v0.1.7) (2026-07-13)
11
+
12
+
13
+ ### Miscellaneous Chores
14
+
15
+ * **@fleetagent/pi-daemon:** Synchronize pi versions
16
+
17
+ ## [0.1.6](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.5...@fleetagent/pi-daemon-v0.1.6) (2026-07-13)
18
+
19
+
20
+ ### Miscellaneous Chores
21
+
22
+ * **@fleetagent/pi-daemon:** Synchronize pi versions
23
+
24
+ ## [0.1.5](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.4...@fleetagent/pi-daemon-v0.1.5) (2026-07-13)
25
+
26
+
27
+ ### Features
28
+
29
+ * add subagents and harden runtime boundaries ([90c6e9e](https://github.com/fleetagent/pi/commit/90c6e9e72dfa72f893cab757dd615bb2bd37b461))
30
+
3
31
  ## [0.1.4](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.3...@fleetagent/pi-daemon-v0.1.4) (2026-06-16)
4
32
 
5
33
 
@@ -41,3 +69,7 @@
41
69
 
42
70
  - Added streamed file upload and download methods to the daemon WebSocket protocol.
43
71
  - Added the initial Pi remote commander daemon package.
72
+
73
+ ### Fixed
74
+
75
+ - Bounded WebSocket frame sizes, concurrent RPC work, process output, and file uploads with disconnect cleanup.
package/README.md CHANGED
@@ -23,6 +23,13 @@ Defaults:
23
23
  - `PI_DAEMON_PORT` / `PORT`: `8787`
24
24
  - `PI_DAEMON_CWD`: current directory
25
25
  - `PI_DAEMON_TOKEN`: optional bearer token
26
+ - `PI_DAEMON_MAX_FRAME_BYTES`: `1048576`
27
+ - `PI_DAEMON_MAX_CONNECTION_REQUESTS`: `8`
28
+ - `PI_DAEMON_MAX_GLOBAL_REQUESTS`: `64`
29
+ - `PI_DAEMON_MAX_CONNECTION_UPLOADS`: `4`
30
+ - `PI_DAEMON_MAX_GLOBAL_UPLOADS`: `32`
31
+ - `PI_DAEMON_MAX_UPLOAD_BYTES`: `104857600`
32
+ - `PI_DAEMON_MAX_BUFFERED_OUTPUT_BYTES`: `8388608`
26
33
 
27
34
  ## Connect from Pi
28
35
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\n\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { once } from \"node:events\";\nimport { constants, createReadStream, createWriteStream, type WriteStream } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { resolve } from \"node:path\";\n\ninterface JsonRpcMessage {\n\tid?: unknown;\n\tmethod?: unknown;\n\tparams?: unknown;\n}\n\ninterface ClientConnection {\n\tsocket: Socket;\n\tbuffer: Buffer;\n\texecs: Map<string, ChildProcessWithoutNullStreams>;\n\tuploads: Map<string, WriteStream>;\n}\n\nconst port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? \"8787\");\nconst host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? \"127.0.0.1\";\nconst cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());\nconst token = process.env.PI_DAEMON_TOKEN;\nconst fileTransferChunkSize = 64 * 1024;\n\nfunction createFrame(payload: unknown): Buffer {\n\tconst data = Buffer.from(JSON.stringify(payload));\n\tlet header: Buffer;\n\tif (data.length < 126) {\n\t\theader = Buffer.from([0x81, data.length]);\n\t} else if (data.length <= 0xffff) {\n\t\theader = Buffer.alloc(4);\n\t\theader[0] = 0x81;\n\t\theader[1] = 126;\n\t\theader.writeUInt16BE(data.length, 2);\n\t} else {\n\t\theader = Buffer.alloc(10);\n\t\theader[0] = 0x81;\n\t\theader[1] = 127;\n\t\theader.writeBigUInt64BE(BigInt(data.length), 2);\n\t}\n\treturn Buffer.concat([header, data]);\n}\n\nfunction sendFrame(socket: Socket, payload: unknown): void {\n\tsocket.write(createFrame(payload));\n}\n\nasync function sendFrameAsync(socket: Socket, payload: unknown): Promise<void> {\n\tif (!socket.write(createFrame(payload))) {\n\t\tawait once(socket, \"drain\");\n\t}\n}\n\nfunction sendResult(connection: ClientConnection, id: string, result: unknown): void {\n\tsendFrame(connection.socket, { id, result });\n}\n\nfunction sendError(connection: ClientConnection, id: string, error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tsendFrame(connection.socket, { id, error: { message } });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction requireString(value: unknown, name: string): string {\n\tif (typeof value !== \"string\") throw new Error(`Missing string param: ${name}`);\n\treturn value;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" ? value : undefined;\n}\n\nfunction accessModeToFsMode(mode: unknown): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid access mode: ${String(mode)}`);\n\t}\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(params: Record<string, unknown>): string[] {\n\tconst pattern = requireString(params.pattern, \"pattern\");\n\tconst path = requireString(params.path, \"path\");\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (params.ignoreCase === true) args.push(\"--ignore-case\");\n\tif (params.literal === true) args.push(\"--fixed-strings\");\n\tconst glob = optionalString(params.glob);\n\tif (glob) args.push(\"--glob\", glob);\n\targs.push(\"--\", pattern, path);\n\treturn args;\n}\n\nasync function runBuffered(command: string, args: string[], runCwd: string): Promise<Buffer> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(command, args, { cwd: runCwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tchild.stdout.on(\"data\", (data: Buffer) => stdout.push(data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => stderr.push(data));\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolvePromise(Buffer.concat(stdout));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n\nfunction handleExec(connection: ClientConnection, id: string, params: Record<string, unknown>): void {\n\tconst command = requireString(params.command, \"command\");\n\tconst runCwd = optionalString(params.cwd) ?? cwd;\n\tconst timeout = optionalNumber(params.timeout);\n\tconst env = isRecord(params.env)\n\t\t? Object.fromEntries(\n\t\t\t\tObject.entries(params.env).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t\t\t)\n\t\t: undefined;\n\tconst child = spawn(\"bash\", [\"-lc\", command], {\n\t\tcwd: runCwd,\n\t\tdetached: process.platform !== \"win32\",\n\t\tenv: env ? { ...process.env, ...env } : process.env,\n\t});\n\tconnection.execs.set(id, child);\n\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\tif (timeout !== undefined && timeout > 0) {\n\t\ttimeoutHandle = setTimeout(() => {\n\t\t\tif (process.platform !== \"win32\" && child.pid) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(-child.pid);\n\t\t\t\t} catch {\n\t\t\t\t\tchild.kill();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tchild.kill();\n\t\t\t}\n\t\t}, timeout * 1000);\n\t}\n\tchild.stdout.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stdout\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.stderr.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stderr\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.on(\"error\", (error) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"error\", error: { message: error.message } });\n\t});\n\tchild.on(\"close\", (code) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: code });\n\t});\n}\n\nfunction cancelExec(connection: ClientConnection, id: string): void {\n\tconst child = connection.execs.get(id);\n\tif (!child) return;\n\tconnection.execs.delete(id);\n\tif (process.platform !== \"win32\" && child.pid) {\n\t\ttry {\n\t\t\tprocess.kill(-child.pid);\n\t\t} catch {\n\t\t\tchild.kill();\n\t\t}\n\t} else {\n\t\tchild.kill();\n\t}\n\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: null, cancelled: true });\n}\n\nasync function handleDownloadFile(connection: ClientConnection, id: string, path: string): Promise<void> {\n\ttry {\n\t\tfor await (const chunk of createReadStream(path, { highWaterMark: fileTransferChunkSize })) {\n\t\t\tconst buffer = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n\t\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileData\", dataBase64: buffer.toString(\"base64\") });\n\t\t}\n\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileEnd\" });\n\t} catch (error) {\n\t\tsendFrame(connection.socket, {\n\t\t\tid,\n\t\t\tevent: \"fileError\",\n\t\t\terror: { message: error instanceof Error ? error.message : String(error) },\n\t\t});\n\t}\n}\n\nfunction writeUploadChunk(stream: WriteStream, chunk: Buffer): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.write(chunk, (error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tif (error) {\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction closeUploadStream(stream: WriteStream): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.end(() => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nasync function handleRequest(connection: ClientConnection, message: JsonRpcMessage): Promise<void> {\n\tconst id = requireString(message.id, \"id\");\n\tconst method = requireString(message.method, \"method\");\n\tconst params = isRecord(message.params) ? message.params : {};\n\ttry {\n\t\tif (method === \"cancel\") {\n\t\t\tcancelExec(connection, id);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"exec\") {\n\t\t\thandleExec(connection, id, params);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"capabilities\") {\n\t\t\tsendResult(connection, id, {\n\t\t\t\tcwd,\n\t\t\t\tfeatures: {\n\t\t\t\t\texec: true,\n\t\t\t\t\tfiles: true,\n\t\t\t\t\tfileTransfer: true,\n\t\t\t\t\tglob: true,\n\t\t\t\t\tgrep: true,\n\t\t\t\t\tinstructions: false,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"access\") {\n\t\t\tawait access(requireString(params.path, \"path\"), accessModeToFsMode(params.mode));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readFile\") {\n\t\t\tconst content = await readFile(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { contentBase64: content.toString(\"base64\") });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"writeFile\") {\n\t\t\tawait writeFile(\n\t\t\t\trequireString(params.path, \"path\"),\n\t\t\t\tBuffer.from(requireString(params.contentBase64, \"contentBase64\"), \"base64\"),\n\t\t\t);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"downloadFile\") {\n\t\t\tawait handleDownloadFile(connection, id, requireString(params.path, \"path\"));\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileStart\") {\n\t\t\tconst path = requireString(params.path, \"path\");\n\t\t\tconst stream = createWriteStream(path);\n\t\t\tstream.on(\"error\", () => undefined);\n\t\t\tconnection.uploads.set(id, stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileChunk\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (!stream) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tawait writeUploadChunk(stream, Buffer.from(requireString(params.dataBase64, \"dataBase64\"), \"base64\"));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileEnd\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (!stream) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tconnection.uploads.delete(uploadId);\n\t\t\tawait closeUploadStream(stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileCancel\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (stream) {\n\t\t\t\tconnection.uploads.delete(uploadId);\n\t\t\t\tstream.destroy();\n\t\t\t}\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"mkdir\") {\n\t\t\tawait mkdir(requireString(params.path, \"path\"), { recursive: params.recursive === true });\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"stat\") {\n\t\t\tconst result = await stat(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { isDirectory: result.isDirectory(), isFile: result.isFile() });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readdir\") {\n\t\t\tsendResult(connection, id, { entries: await readdir(requireString(params.path, \"path\")) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"glob\") {\n\t\t\tconst pattern = requireString(params.pattern, \"pattern\");\n\t\t\tconst runCwd = requireString(params.cwd, \"cwd\");\n\t\t\tconst limit = optionalNumber(params.limit) ?? 1000;\n\t\t\tconst output = await runBuffered(\"fd\", buildFdArgs(pattern, runCwd, limit), runCwd);\n\t\t\tsendResult(connection, id, { matches: output.toString(\"utf-8\").split(\"\\n\").filter(Boolean) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"grep\") {\n\t\t\tconst pathParam = requireString(params.path, \"path\");\n\t\t\tconst isDirectory = (await stat(pathParam)).isDirectory();\n\t\t\tconst output = await runBuffered(\"rg\", buildRgArgs(params), cwd);\n\t\t\tconst matches = output\n\t\t\t\t.toString(\"utf-8\")\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.flatMap((line) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(line) as unknown;\n\t\t\t\t\t\tif (!isRecord(data) || data.type !== \"match\" || !isRecord(data.data)) return [];\n\t\t\t\t\t\tconst filePath = isRecord(data.data.path) ? optionalString(data.data.path.text) : undefined;\n\t\t\t\t\t\tconst lineNumber = optionalNumber(data.data.line_number);\n\t\t\t\t\t\tconst lineText = isRecord(data.data.lines) ? optionalString(data.data.lines.text) : undefined;\n\t\t\t\t\t\treturn filePath && lineNumber !== undefined ? [{ filePath, lineNumber, lineText }] : [];\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tsendResult(connection, id, { isDirectory, matches });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"detectImageMimeType\") {\n\t\t\tconst output = await runBuffered(\"file\", [\"--mime-type\", \"-b\", requireString(params.path, \"path\")], cwd);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\tsendResult(connection, id, {\n\t\t\t\tmimeType: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t} catch (error) {\n\t\tsendError(connection, id, error);\n\t}\n}\n\nfunction parseFrames(connection: ClientConnection): void {\n\twhile (connection.buffer.length >= 2) {\n\t\tconst first = connection.buffer[0];\n\t\tconst second = connection.buffer[1];\n\t\tconst opcode = first & 0x0f;\n\t\tconst masked = (second & 0x80) !== 0;\n\t\tlet payloadLength = second & 0x7f;\n\t\tlet offset = 2;\n\t\tif (payloadLength === 126) {\n\t\t\tif (connection.buffer.length < offset + 2) return;\n\t\t\tpayloadLength = connection.buffer.readUInt16BE(offset);\n\t\t\toffset += 2;\n\t\t} else if (payloadLength === 127) {\n\t\t\tif (connection.buffer.length < offset + 8) return;\n\t\t\tconst largeLength = connection.buffer.readBigUInt64BE(offset);\n\t\t\tif (largeLength > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\t\t\tconnection.socket.destroy(new Error(\"WebSocket frame too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpayloadLength = Number(largeLength);\n\t\t\toffset += 8;\n\t\t}\n\t\tif (!masked) {\n\t\t\tconnection.socket.destroy(new Error(\"Client WebSocket frames must be masked\"));\n\t\t\treturn;\n\t\t}\n\t\tif (connection.buffer.length < offset + 4 + payloadLength) return;\n\t\tconst mask = connection.buffer.subarray(offset, offset + 4);\n\t\toffset += 4;\n\t\tconst payload = Buffer.from(connection.buffer.subarray(offset, offset + payloadLength));\n\t\tconnection.buffer = connection.buffer.subarray(offset + payloadLength);\n\t\tfor (let index = 0; index < payload.length; index++) {\n\t\t\tpayload[index] ^= mask[index % 4];\n\t\t}\n\t\tif (opcode === 0x8) {\n\t\t\tconnection.socket.end();\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1) continue;\n\t\tlet message: unknown;\n\t\ttry {\n\t\t\tmessage = JSON.parse(payload.toString(\"utf-8\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isRecord(message) && message.type === \"ping\") {\n\t\t\tsendFrame(connection.socket, { type: \"pong\", timestamp: message.timestamp });\n\t\t\tcontinue;\n\t\t}\n\t\tvoid handleRequest(connection, message as JsonRpcMessage);\n\t}\n}\n\nfunction isAuthorized(request: IncomingMessage): boolean {\n\tif (!token) return true;\n\tif (request.headers.authorization === `Bearer ${token}`) return true;\n\ttry {\n\t\tconst url = new URL(request.url ?? \"/\", `http://${request.headers.host ?? \"localhost\"}`);\n\t\treturn url.searchParams.get(\"token\") === token;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst server = createServer((_request, response) => {\n\tresponse.writeHead(404);\n\tresponse.end(\"pi-daemon only serves WebSocket remote commander connections\\n\");\n});\n\nserver.on(\"upgrade\", (request, socket) => {\n\tconst netSocket = socket as Socket;\n\tif (!isAuthorized(request)) {\n\t\tnetSocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst key = request.headers[\"sec-websocket-key\"];\n\tif (typeof key !== \"string\") {\n\t\tnetSocket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst accept = createHash(\"sha1\").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(\"base64\");\n\tnetSocket.write(\n\t\t[\n\t\t\t\"HTTP/1.1 101 Switching Protocols\",\n\t\t\t\"Upgrade: websocket\",\n\t\t\t\"Connection: Upgrade\",\n\t\t\t`Sec-WebSocket-Accept: ${accept}`,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t].join(\"\\r\\n\"),\n\t);\n\tconst connection: ClientConnection = {\n\t\tsocket: netSocket,\n\t\tbuffer: Buffer.alloc(0),\n\t\texecs: new Map(),\n\t\tuploads: new Map(),\n\t};\n\tnetSocket.on(\"data\", (chunk: Buffer) => {\n\t\tconnection.buffer = Buffer.concat([connection.buffer, chunk]);\n\t\tparseFrames(connection);\n\t});\n\tnetSocket.on(\"close\", () => {\n\t\tfor (const child of connection.execs.values()) {\n\t\t\tchild.kill();\n\t\t}\n\t\tconnection.execs.clear();\n\t\tfor (const stream of connection.uploads.values()) {\n\t\t\tstream.destroy();\n\t\t}\n\t\tconnection.uploads.clear();\n\t});\n});\n\nserver.listen(port, host, () => {\n\tconsole.log(`pi-daemon listening on ws://${host}:${port} cwd=${cwd}`);\n});\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\n\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { constants, createReadStream, createWriteStream, type WriteStream } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { resolve } from \"node:path\";\n\ninterface JsonRpcMessage {\n\tid?: unknown;\n\tmethod?: unknown;\n\tparams?: unknown;\n}\n\ninterface UploadState {\n\tstream: WriteStream;\n\tbytes: number;\n}\n\ninterface ClientConnection {\n\tsocket: Socket;\n\tbuffer: Buffer;\n\texecs: Map<string, ChildProcessWithoutNullStreams>;\n\tuploads: Map<string, UploadState>;\n\trequestControllers: Set<AbortController>;\n\tactiveRequests: number;\n\tclosed: boolean;\n}\n\nconst port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? \"8787\");\nconst host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? \"127.0.0.1\";\nconst cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());\nconst token = process.env.PI_DAEMON_TOKEN;\nconst fileTransferChunkSize = 64 * 1024;\nconst maxFramePayloadBytes = Number(process.env.PI_DAEMON_MAX_FRAME_BYTES ?? 1024 * 1024);\nconst maxConnectionRequests = Number(process.env.PI_DAEMON_MAX_CONNECTION_REQUESTS ?? 8);\nconst maxGlobalRequests = Number(process.env.PI_DAEMON_MAX_GLOBAL_REQUESTS ?? 64);\nconst maxConnectionBufferBytes = maxFramePayloadBytes + 14;\nconst maxConnectionUploads = Number(process.env.PI_DAEMON_MAX_CONNECTION_UPLOADS ?? 4);\nconst maxGlobalUploads = Number(process.env.PI_DAEMON_MAX_GLOBAL_UPLOADS ?? 32);\nconst maxUploadBytes = Number(process.env.PI_DAEMON_MAX_UPLOAD_BYTES ?? 100 * 1024 * 1024);\nconst maxBufferedProcessOutputBytes = Number(process.env.PI_DAEMON_MAX_BUFFERED_OUTPUT_BYTES ?? 8 * 1024 * 1024);\nlet activeGlobalRequests = 0;\nlet activeGlobalUploads = 0;\n\nfunction createFrame(payload: unknown): Buffer {\n\tconst data = Buffer.from(JSON.stringify(payload));\n\tlet header: Buffer;\n\tif (data.length < 126) {\n\t\theader = Buffer.from([0x81, data.length]);\n\t} else if (data.length <= 0xffff) {\n\t\theader = Buffer.alloc(4);\n\t\theader[0] = 0x81;\n\t\theader[1] = 126;\n\t\theader.writeUInt16BE(data.length, 2);\n\t} else {\n\t\theader = Buffer.alloc(10);\n\t\theader[0] = 0x81;\n\t\theader[1] = 127;\n\t\theader.writeBigUInt64BE(BigInt(data.length), 2);\n\t}\n\treturn Buffer.concat([header, data]);\n}\n\nfunction sendFrame(socket: Socket, payload: unknown): boolean {\n\tif (socket.destroyed || !socket.writable) return true;\n\treturn socket.write(createFrame(payload));\n}\n\nfunction waitForSocketDrain(socket: Socket): Promise<void> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst cleanup = () => {\n\t\t\tsocket.off(\"drain\", onDrain);\n\t\t\tsocket.off(\"close\", onClose);\n\t\t\tsocket.off(\"error\", onError);\n\t\t};\n\t\tconst onDrain = () => {\n\t\t\tcleanup();\n\t\t\tresolvePromise();\n\t\t};\n\t\tconst onClose = () => {\n\t\t\tcleanup();\n\t\t\treject(new Error(\"Socket closed before write drained\"));\n\t\t};\n\t\tconst onError = (error: Error) => {\n\t\t\tcleanup();\n\t\t\treject(error);\n\t\t};\n\t\tsocket.once(\"drain\", onDrain);\n\t\tsocket.once(\"close\", onClose);\n\t\tsocket.once(\"error\", onError);\n\t});\n}\n\nasync function sendFrameAsync(socket: Socket, payload: unknown): Promise<void> {\n\tif (socket.destroyed || !socket.writable) throw new Error(\"Socket is closed\");\n\tif (!socket.write(createFrame(payload))) await waitForSocketDrain(socket);\n}\n\nfunction sendResult(connection: ClientConnection, id: string, result: unknown): void {\n\tsendFrame(connection.socket, { id, result });\n}\n\nfunction sendError(connection: ClientConnection, id: string, error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tsendFrame(connection.socket, { id, error: { message } });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction requireString(value: unknown, name: string): string {\n\tif (typeof value !== \"string\") throw new Error(`Missing string param: ${name}`);\n\treturn value;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" ? value : undefined;\n}\n\nfunction accessModeToFsMode(mode: unknown): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid access mode: ${String(mode)}`);\n\t}\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(params: Record<string, unknown>): string[] {\n\tconst pattern = requireString(params.pattern, \"pattern\");\n\tconst path = requireString(params.path, \"path\");\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (params.ignoreCase === true) args.push(\"--ignore-case\");\n\tif (params.literal === true) args.push(\"--fixed-strings\");\n\tconst glob = optionalString(params.glob);\n\tif (glob) args.push(\"--glob\", glob);\n\targs.push(\"--\", pattern, path);\n\treturn args;\n}\n\nasync function runBuffered(command: string, args: string[], runCwd: string, signal?: AbortSignal): Promise<Buffer> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(command, args, { cwd: runCwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tlet bufferedBytes = 0;\n\t\tlet settled = false;\n\t\tconst finish = (error?: Error, output?: Buffer) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tif (error) reject(error);\n\t\t\telse resolvePromise(output ?? Buffer.alloc(0));\n\t\t};\n\t\tconst onAbort = () => {\n\t\t\tchild.kill();\n\t\t\tfinish(new Error(\"Request cancelled\"));\n\t\t};\n\t\tconst collect = (target: Buffer[], data: Buffer) => {\n\t\t\tbufferedBytes += data.length;\n\t\t\tif (bufferedBytes > maxBufferedProcessOutputBytes) {\n\t\t\t\tchild.kill();\n\t\t\t\tfinish(new Error(`Process output exceeds ${maxBufferedProcessOutputBytes} bytes`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttarget.push(data);\n\t\t};\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\tchild.stdout.on(\"data\", (data: Buffer) => collect(stdout, data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => collect(stderr, data));\n\t\tchild.on(\"error\", (error) => finish(error));\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (settled) return;\n\t\t\tif (code === 0) {\n\t\t\t\tfinish(undefined, Buffer.concat(stdout));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n\nfunction removeUpload(connection: ClientConnection, id: string, destroy: boolean): UploadState | undefined {\n\tconst upload = connection.uploads.get(id);\n\tif (!upload) return undefined;\n\tconnection.uploads.delete(id);\n\tactiveGlobalUploads--;\n\tif (destroy) upload.stream.destroy();\n\treturn upload;\n}\n\nfunction handleExec(\n\tconnection: ClientConnection,\n\tid: string,\n\tparams: Record<string, unknown>,\n\tsignal: AbortSignal,\n): Promise<void> {\n\treturn new Promise((resolvePromise) => {\n\t\tconst command = requireString(params.command, \"command\");\n\t\tconst runCwd = optionalString(params.cwd) ?? cwd;\n\t\tconst timeout = optionalNumber(params.timeout);\n\t\tconst env = isRecord(params.env)\n\t\t\t? Object.fromEntries(\n\t\t\t\t\tObject.entries(params.env).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t\t\t\t)\n\t\t\t: undefined;\n\t\tconst child = spawn(\"bash\", [\"-lc\", command], {\n\t\t\tcwd: runCwd,\n\t\t\tdetached: process.platform !== \"win32\",\n\t\t\tenv: env ? { ...process.env, ...env } : process.env,\n\t\t});\n\t\tconnection.execs.set(id, child);\n\t\tconst kill = () => {\n\t\t\tif (process.platform !== \"win32\" && child.pid) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(-child.pid);\n\t\t\t\t} catch {\n\t\t\t\t\tchild.kill();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tchild.kill();\n\t\t\t}\n\t\t};\n\t\tsignal.addEventListener(\"abort\", kill, { once: true });\n\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\tif (timeout !== undefined && timeout > 0) timeoutHandle = setTimeout(kill, timeout * 1000);\n\t\tconst cleanup = () => {\n\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\tsignal.removeEventListener(\"abort\", kill);\n\t\t\tresolvePromise();\n\t\t};\n\t\tlet drainPromise: Promise<void> | undefined;\n\t\tconst pausedStreams = new Set<typeof child.stdout>();\n\t\tconst sendExecData = (stream: typeof child.stdout, data: Buffer, streamName: \"stdout\" | \"stderr\") => {\n\t\t\tconst writable = sendFrame(connection.socket, {\n\t\t\t\tid,\n\t\t\t\tevent: \"data\",\n\t\t\t\tstream: streamName,\n\t\t\t\tdataBase64: data.toString(\"base64\"),\n\t\t\t});\n\t\t\tif (writable) return;\n\t\t\tstream.pause();\n\t\t\tpausedStreams.add(stream);\n\t\t\tif (drainPromise) return;\n\t\t\tdrainPromise = waitForSocketDrain(connection.socket);\n\t\t\tvoid drainPromise\n\t\t\t\t.then(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tfor (const pausedStream of pausedStreams) pausedStream.resume();\n\t\t\t\t\t\tpausedStreams.clear();\n\t\t\t\t\t},\n\t\t\t\t\t() => kill(),\n\t\t\t\t)\n\t\t\t\t.finally(() => {\n\t\t\t\t\tdrainPromise = undefined;\n\t\t\t\t});\n\t\t};\n\t\tchild.stdout.on(\"data\", (data: Buffer) => sendExecData(child.stdout, data, \"stdout\"));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => sendExecData(child.stderr, data, \"stderr\"));\n\t\tchild.on(\"error\", (error) => {\n\t\t\tconst active = connection.execs.get(id) === child;\n\t\t\tif (active) connection.execs.delete(id);\n\t\t\tif (active) sendFrame(connection.socket, { id, event: \"error\", error: { message: error.message } });\n\t\t\tcleanup();\n\t\t});\n\t\tchild.on(\"close\", (code) => {\n\t\t\tconst active = connection.execs.get(id) === child;\n\t\t\tif (active) connection.execs.delete(id);\n\t\t\tif (active) sendFrame(connection.socket, { id, event: \"exit\", exitCode: code });\n\t\t\tcleanup();\n\t\t});\n\t});\n}\n\nfunction cancelExec(connection: ClientConnection, id: string): void {\n\tconst child = connection.execs.get(id);\n\tif (!child) return;\n\tconnection.execs.delete(id);\n\tif (process.platform !== \"win32\" && child.pid) {\n\t\ttry {\n\t\t\tprocess.kill(-child.pid);\n\t\t} catch {\n\t\t\tchild.kill();\n\t\t}\n\t} else {\n\t\tchild.kill();\n\t}\n\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: null, cancelled: true });\n}\n\nasync function handleDownloadFile(\n\tconnection: ClientConnection,\n\tid: string,\n\tpath: string,\n\tsignal: AbortSignal,\n): Promise<void> {\n\ttry {\n\t\tconst readStream = createReadStream(path, { highWaterMark: fileTransferChunkSize });\n\t\tconst cancel = () => readStream.destroy(new Error(\"Request cancelled\"));\n\t\tsignal.addEventListener(\"abort\", cancel, { once: true });\n\t\ttry {\n\t\t\tfor await (const chunk of readStream) {\n\t\t\t\tconst buffer = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n\t\t\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileData\", dataBase64: buffer.toString(\"base64\") });\n\t\t\t}\n\t\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileEnd\" });\n\t\t} finally {\n\t\t\tsignal.removeEventListener(\"abort\", cancel);\n\t\t}\n\t} catch (error) {\n\t\tsendFrame(connection.socket, {\n\t\t\tid,\n\t\t\tevent: \"fileError\",\n\t\t\terror: { message: error instanceof Error ? error.message : String(error) },\n\t\t});\n\t}\n}\n\nfunction writeUploadChunk(stream: WriteStream, chunk: Buffer): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.write(chunk, (error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tif (error) {\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction closeUploadStream(stream: WriteStream): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.end(() => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nasync function handleRequest(\n\tconnection: ClientConnection,\n\tmessage: JsonRpcMessage,\n\tsignal: AbortSignal,\n): Promise<void> {\n\tconst id = requireString(message.id, \"id\");\n\tconst method = requireString(message.method, \"method\");\n\tconst params = isRecord(message.params) ? message.params : {};\n\ttry {\n\t\tif (method === \"cancel\") {\n\t\t\tcancelExec(connection, id);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"exec\") {\n\t\t\tawait handleExec(connection, id, params, signal);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"capabilities\") {\n\t\t\tsendResult(connection, id, {\n\t\t\t\tcwd,\n\t\t\t\tfeatures: {\n\t\t\t\t\texec: true,\n\t\t\t\t\tfiles: true,\n\t\t\t\t\tfileTransfer: true,\n\t\t\t\t\tglob: true,\n\t\t\t\t\tgrep: true,\n\t\t\t\t\tinstructions: false,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"access\") {\n\t\t\tawait access(requireString(params.path, \"path\"), accessModeToFsMode(params.mode));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readFile\") {\n\t\t\tconst content = await readFile(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { contentBase64: content.toString(\"base64\") });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"writeFile\") {\n\t\t\tawait writeFile(\n\t\t\t\trequireString(params.path, \"path\"),\n\t\t\t\tBuffer.from(requireString(params.contentBase64, \"contentBase64\"), \"base64\"),\n\t\t\t);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"downloadFile\") {\n\t\t\tawait handleDownloadFile(connection, id, requireString(params.path, \"path\"), signal);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileStart\") {\n\t\t\tif (connection.uploads.has(id)) throw new Error(`Upload already exists: ${id}`);\n\t\t\tif (connection.uploads.size >= maxConnectionUploads) {\n\t\t\t\tthrow new Error(`Too many concurrent uploads for this connection (max ${maxConnectionUploads})`);\n\t\t\t}\n\t\t\tif (activeGlobalUploads >= maxGlobalUploads) {\n\t\t\t\tthrow new Error(`Too many concurrent daemon uploads (max ${maxGlobalUploads})`);\n\t\t\t}\n\t\t\tconst path = requireString(params.path, \"path\");\n\t\t\tconst stream = createWriteStream(path);\n\t\t\tconst upload: UploadState = { stream, bytes: 0 };\n\t\t\tconnection.uploads.set(id, upload);\n\t\t\tactiveGlobalUploads++;\n\t\t\tstream.once(\"error\", () => removeUpload(connection, id, false));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileChunk\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst upload = connection.uploads.get(uploadId);\n\t\t\tif (!upload) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tconst chunk = Buffer.from(requireString(params.dataBase64, \"dataBase64\"), \"base64\");\n\t\t\tif (upload.bytes + chunk.length > maxUploadBytes) {\n\t\t\t\tremoveUpload(connection, uploadId, true);\n\t\t\t\tthrow new Error(`Upload exceeds ${maxUploadBytes} bytes`);\n\t\t\t}\n\t\t\tupload.bytes += chunk.length;\n\t\t\tawait writeUploadChunk(upload.stream, chunk);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileEnd\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst upload = removeUpload(connection, uploadId, false);\n\t\t\tif (!upload) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tawait closeUploadStream(upload.stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileCancel\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tremoveUpload(connection, uploadId, true);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"mkdir\") {\n\t\t\tawait mkdir(requireString(params.path, \"path\"), { recursive: params.recursive === true });\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"stat\") {\n\t\t\tconst result = await stat(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { isDirectory: result.isDirectory(), isFile: result.isFile() });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readdir\") {\n\t\t\tsendResult(connection, id, { entries: await readdir(requireString(params.path, \"path\")) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"glob\") {\n\t\t\tconst pattern = requireString(params.pattern, \"pattern\");\n\t\t\tconst runCwd = requireString(params.cwd, \"cwd\");\n\t\t\tconst limit = optionalNumber(params.limit) ?? 1000;\n\t\t\tconst output = await runBuffered(\"fd\", buildFdArgs(pattern, runCwd, limit), runCwd, signal);\n\t\t\tsendResult(connection, id, { matches: output.toString(\"utf-8\").split(\"\\n\").filter(Boolean) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"grep\") {\n\t\t\tconst pathParam = requireString(params.path, \"path\");\n\t\t\tconst isDirectory = (await stat(pathParam)).isDirectory();\n\t\t\tconst output = await runBuffered(\"rg\", buildRgArgs(params), cwd, signal);\n\t\t\tconst matches = output\n\t\t\t\t.toString(\"utf-8\")\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.flatMap((line) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(line) as unknown;\n\t\t\t\t\t\tif (!isRecord(data) || data.type !== \"match\" || !isRecord(data.data)) return [];\n\t\t\t\t\t\tconst filePath = isRecord(data.data.path) ? optionalString(data.data.path.text) : undefined;\n\t\t\t\t\t\tconst lineNumber = optionalNumber(data.data.line_number);\n\t\t\t\t\t\tconst lineText = isRecord(data.data.lines) ? optionalString(data.data.lines.text) : undefined;\n\t\t\t\t\t\treturn filePath && lineNumber !== undefined ? [{ filePath, lineNumber, lineText }] : [];\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tsendResult(connection, id, { isDirectory, matches });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"detectImageMimeType\") {\n\t\t\tconst output = await runBuffered(\n\t\t\t\t\"file\",\n\t\t\t\t[\"--mime-type\", \"-b\", requireString(params.path, \"path\")],\n\t\t\t\tcwd,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\tsendResult(connection, id, {\n\t\t\t\tmimeType: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t} catch (error) {\n\t\tsendError(connection, id, error);\n\t}\n}\n\nfunction getIncomingDeclaredPayloadLength(buffer: Buffer, chunk: Buffer): bigint | undefined {\n\tconst header = Buffer.concat([buffer.subarray(0, 10), chunk.subarray(0, Math.max(0, 10 - buffer.length))]);\n\tif (header.length < 2) return undefined;\n\tconst marker = header[1] & 0x7f;\n\tif (marker < 126) return BigInt(marker);\n\tif (marker === 126) return header.length >= 4 ? BigInt(header.readUInt16BE(2)) : undefined;\n\treturn header.length >= 10 ? header.readBigUInt64BE(2) : undefined;\n}\n\nfunction dispatchRequest(connection: ClientConnection, message: JsonRpcMessage): void {\n\tconst id = typeof message.id === \"string\" ? message.id : \"unknown\";\n\tif (message.method === \"cancel\") {\n\t\tcancelExec(connection, id);\n\t\treturn;\n\t}\n\tif (connection.activeRequests >= maxConnectionRequests) {\n\t\tsendError(\n\t\t\tconnection,\n\t\t\tid,\n\t\t\tnew Error(`Too many concurrent requests for this connection (max ${maxConnectionRequests})`),\n\t\t);\n\t\treturn;\n\t}\n\tif (activeGlobalRequests >= maxGlobalRequests) {\n\t\tsendError(connection, id, new Error(`Too many concurrent daemon requests (max ${maxGlobalRequests})`));\n\t\treturn;\n\t}\n\tconst controller = new AbortController();\n\tconnection.requestControllers.add(controller);\n\tconnection.activeRequests++;\n\tactiveGlobalRequests++;\n\tvoid handleRequest(connection, message, controller.signal)\n\t\t.catch((error: unknown) => sendError(connection, id, error))\n\t\t.finally(() => {\n\t\t\tconnection.requestControllers.delete(controller);\n\t\t\tconnection.activeRequests--;\n\t\t\tactiveGlobalRequests--;\n\t\t});\n}\n\nfunction parseFrames(connection: ClientConnection): void {\n\twhile (connection.buffer.length >= 2) {\n\t\tconst first = connection.buffer[0];\n\t\tconst second = connection.buffer[1];\n\t\tconst reservedBits = first & 0x70;\n\t\tif (reservedBits !== 0) {\n\t\t\tconnection.socket.destroy(new Error(\"WebSocket reserved bits require a negotiated extension\"));\n\t\t\treturn;\n\t\t}\n\t\tconst opcode = first & 0x0f;\n\t\tconst final = (first & 0x80) !== 0;\n\t\tconst masked = (second & 0x80) !== 0;\n\t\tlet payloadLength = second & 0x7f;\n\t\tlet offset = 2;\n\t\tif (payloadLength === 126) {\n\t\t\tif (connection.buffer.length < offset + 2) return;\n\t\t\tpayloadLength = connection.buffer.readUInt16BE(offset);\n\t\t\toffset += 2;\n\t\t} else if (payloadLength === 127) {\n\t\t\tif (connection.buffer.length < offset + 8) return;\n\t\t\tconst largeLength = connection.buffer.readBigUInt64BE(offset);\n\t\t\tif (largeLength > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\t\t\tconnection.socket.destroy(new Error(\"WebSocket frame too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpayloadLength = Number(largeLength);\n\t\t\toffset += 8;\n\t\t}\n\t\tif (payloadLength > maxFramePayloadBytes) {\n\t\t\tconnection.socket.destroy(new Error(`WebSocket frame exceeds ${maxFramePayloadBytes} bytes`));\n\t\t\treturn;\n\t\t}\n\t\tif (!final && (opcode === 0x0 || opcode === 0x1 || opcode === 0x2)) {\n\t\t\tconnection.socket.destroy(new Error(\"Fragmented WebSocket messages are not supported\"));\n\t\t\treturn;\n\t\t}\n\t\tif (opcode >= 0x8 && (!final || payloadLength > 125)) {\n\t\t\tconnection.socket.destroy(new Error(\"Invalid WebSocket control frame\"));\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1 && opcode !== 0x8) {\n\t\t\tconnection.socket.destroy(new Error(`Unsupported WebSocket opcode: ${opcode}`));\n\t\t\treturn;\n\t\t}\n\t\tif (!masked) {\n\t\t\tconnection.socket.destroy(new Error(\"Client WebSocket frames must be masked\"));\n\t\t\treturn;\n\t\t}\n\t\tif (connection.buffer.length < offset + 4 + payloadLength) return;\n\t\tconst mask = connection.buffer.subarray(offset, offset + 4);\n\t\toffset += 4;\n\t\tconst payload = Buffer.from(connection.buffer.subarray(offset, offset + payloadLength));\n\t\tconnection.buffer = connection.buffer.subarray(offset + payloadLength);\n\t\tfor (let index = 0; index < payload.length; index++) {\n\t\t\tpayload[index] ^= mask[index % 4];\n\t\t}\n\t\tif (opcode === 0x8) {\n\t\t\tconnection.socket.end();\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1) continue;\n\t\tlet message: unknown;\n\t\ttry {\n\t\t\tmessage = JSON.parse(payload.toString(\"utf-8\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isRecord(message) && message.type === \"ping\") {\n\t\t\tsendFrame(connection.socket, { type: \"pong\", timestamp: message.timestamp });\n\t\t\tcontinue;\n\t\t}\n\t\tdispatchRequest(connection, message as JsonRpcMessage);\n\t}\n}\n\nfunction receiveSocketData(connection: ClientConnection, chunk: Buffer): void {\n\tlet remaining = chunk;\n\twhile (remaining.length > 0 && !connection.socket.destroyed) {\n\t\tconst available = maxConnectionBufferBytes - connection.buffer.length;\n\t\tif (available <= 0) {\n\t\t\tconnection.socket.destroy(new Error(`WebSocket receive buffer exceeds ${maxConnectionBufferBytes} bytes`));\n\t\t\treturn;\n\t\t}\n\t\tconst next = remaining.subarray(0, available);\n\t\tconst declaredPayloadLength = getIncomingDeclaredPayloadLength(connection.buffer, next);\n\t\tif (declaredPayloadLength !== undefined && declaredPayloadLength > BigInt(maxFramePayloadBytes)) {\n\t\t\tconnection.socket.destroy(new Error(`WebSocket frame exceeds ${maxFramePayloadBytes} bytes`));\n\t\t\treturn;\n\t\t}\n\t\tconnection.buffer = Buffer.concat([connection.buffer, next]);\n\t\tremaining = remaining.subarray(next.length);\n\t\tconst bufferedBeforeParse = connection.buffer.length;\n\t\tparseFrames(connection);\n\t\tif (remaining.length > 0 && connection.buffer.length === bufferedBeforeParse) {\n\t\t\tconnection.socket.destroy(new Error(`WebSocket receive buffer exceeds ${maxConnectionBufferBytes} bytes`));\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nfunction isAuthorized(request: IncomingMessage): boolean {\n\tif (!token) return true;\n\tif (request.headers.authorization === `Bearer ${token}`) return true;\n\ttry {\n\t\tconst url = new URL(request.url ?? \"/\", `http://${request.headers.host ?? \"localhost\"}`);\n\t\treturn url.searchParams.get(\"token\") === token;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst server = createServer((_request, response) => {\n\tresponse.writeHead(404);\n\tresponse.end(\"pi-daemon only serves WebSocket remote commander connections\\n\");\n});\n\nserver.on(\"upgrade\", (request, socket) => {\n\tconst netSocket = socket as Socket;\n\tif (!isAuthorized(request)) {\n\t\tnetSocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst key = request.headers[\"sec-websocket-key\"];\n\tif (typeof key !== \"string\") {\n\t\tnetSocket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst accept = createHash(\"sha1\").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(\"base64\");\n\tnetSocket.write(\n\t\t[\n\t\t\t\"HTTP/1.1 101 Switching Protocols\",\n\t\t\t\"Upgrade: websocket\",\n\t\t\t\"Connection: Upgrade\",\n\t\t\t`Sec-WebSocket-Accept: ${accept}`,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t].join(\"\\r\\n\"),\n\t);\n\tconst connection: ClientConnection = {\n\t\tsocket: netSocket,\n\t\tbuffer: Buffer.alloc(0),\n\t\texecs: new Map(),\n\t\tuploads: new Map(),\n\t\trequestControllers: new Set(),\n\t\tactiveRequests: 0,\n\t\tclosed: false,\n\t};\n\tnetSocket.on(\"error\", () => undefined);\n\tnetSocket.on(\"data\", (chunk: Buffer) => receiveSocketData(connection, chunk));\n\tnetSocket.on(\"close\", () => {\n\t\tconnection.closed = true;\n\t\tfor (const controller of connection.requestControllers) controller.abort();\n\t\tconnection.requestControllers.clear();\n\t\tfor (const child of connection.execs.values()) {\n\t\t\tchild.kill();\n\t\t}\n\t\tconnection.execs.clear();\n\t\tfor (const uploadId of Array.from(connection.uploads.keys())) {\n\t\t\tremoveUpload(connection, uploadId, true);\n\t\t}\n\t});\n});\n\nserver.listen(port, host, () => {\n\tconsole.log(`pi-daemon listening on ws://${host}:${port} cwd=${cwd}`);\n});\n"]}
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
3
  import { createHash } from "node:crypto";
4
- import { once } from "node:events";
5
4
  import { constants, createReadStream, createWriteStream } from "node:fs";
6
5
  import { access, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
7
6
  import { createServer } from "node:http";
@@ -11,6 +10,16 @@ const host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? "127.0.0.1";
11
10
  const cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());
12
11
  const token = process.env.PI_DAEMON_TOKEN;
13
12
  const fileTransferChunkSize = 64 * 1024;
13
+ const maxFramePayloadBytes = Number(process.env.PI_DAEMON_MAX_FRAME_BYTES ?? 1024 * 1024);
14
+ const maxConnectionRequests = Number(process.env.PI_DAEMON_MAX_CONNECTION_REQUESTS ?? 8);
15
+ const maxGlobalRequests = Number(process.env.PI_DAEMON_MAX_GLOBAL_REQUESTS ?? 64);
16
+ const maxConnectionBufferBytes = maxFramePayloadBytes + 14;
17
+ const maxConnectionUploads = Number(process.env.PI_DAEMON_MAX_CONNECTION_UPLOADS ?? 4);
18
+ const maxGlobalUploads = Number(process.env.PI_DAEMON_MAX_GLOBAL_UPLOADS ?? 32);
19
+ const maxUploadBytes = Number(process.env.PI_DAEMON_MAX_UPLOAD_BYTES ?? 100 * 1024 * 1024);
20
+ const maxBufferedProcessOutputBytes = Number(process.env.PI_DAEMON_MAX_BUFFERED_OUTPUT_BYTES ?? 8 * 1024 * 1024);
21
+ let activeGlobalRequests = 0;
22
+ let activeGlobalUploads = 0;
14
23
  function createFrame(payload) {
15
24
  const data = Buffer.from(JSON.stringify(payload));
16
25
  let header;
@@ -32,12 +41,39 @@ function createFrame(payload) {
32
41
  return Buffer.concat([header, data]);
33
42
  }
34
43
  function sendFrame(socket, payload) {
35
- socket.write(createFrame(payload));
44
+ if (socket.destroyed || !socket.writable)
45
+ return true;
46
+ return socket.write(createFrame(payload));
47
+ }
48
+ function waitForSocketDrain(socket) {
49
+ return new Promise((resolvePromise, reject) => {
50
+ const cleanup = () => {
51
+ socket.off("drain", onDrain);
52
+ socket.off("close", onClose);
53
+ socket.off("error", onError);
54
+ };
55
+ const onDrain = () => {
56
+ cleanup();
57
+ resolvePromise();
58
+ };
59
+ const onClose = () => {
60
+ cleanup();
61
+ reject(new Error("Socket closed before write drained"));
62
+ };
63
+ const onError = (error) => {
64
+ cleanup();
65
+ reject(error);
66
+ };
67
+ socket.once("drain", onDrain);
68
+ socket.once("close", onClose);
69
+ socket.once("error", onError);
70
+ });
36
71
  }
37
72
  async function sendFrameAsync(socket, payload) {
38
- if (!socket.write(createFrame(payload))) {
39
- await once(socket, "drain");
40
- }
73
+ if (socket.destroyed || !socket.writable)
74
+ throw new Error("Socket is closed");
75
+ if (!socket.write(createFrame(payload)))
76
+ await waitForSocketDrain(socket);
41
77
  }
42
78
  function sendResult(connection, id, result) {
43
79
  sendFrame(connection.socket, { id, result });
@@ -101,39 +137,76 @@ function buildRgArgs(params) {
101
137
  args.push("--", pattern, path);
102
138
  return args;
103
139
  }
104
- async function runBuffered(command, args, runCwd) {
140
+ async function runBuffered(command, args, runCwd, signal) {
105
141
  return new Promise((resolvePromise, reject) => {
106
142
  const child = spawn(command, args, { cwd: runCwd, stdio: ["ignore", "pipe", "pipe"] });
107
143
  const stdout = [];
108
144
  const stderr = [];
109
- child.stdout.on("data", (data) => stdout.push(data));
110
- child.stderr.on("data", (data) => stderr.push(data));
111
- child.on("error", reject);
145
+ let bufferedBytes = 0;
146
+ let settled = false;
147
+ const finish = (error, output) => {
148
+ if (settled)
149
+ return;
150
+ settled = true;
151
+ signal?.removeEventListener("abort", onAbort);
152
+ if (error)
153
+ reject(error);
154
+ else
155
+ resolvePromise(output ?? Buffer.alloc(0));
156
+ };
157
+ const onAbort = () => {
158
+ child.kill();
159
+ finish(new Error("Request cancelled"));
160
+ };
161
+ const collect = (target, data) => {
162
+ bufferedBytes += data.length;
163
+ if (bufferedBytes > maxBufferedProcessOutputBytes) {
164
+ child.kill();
165
+ finish(new Error(`Process output exceeds ${maxBufferedProcessOutputBytes} bytes`));
166
+ return;
167
+ }
168
+ target.push(data);
169
+ };
170
+ signal?.addEventListener("abort", onAbort, { once: true });
171
+ child.stdout.on("data", (data) => collect(stdout, data));
172
+ child.stderr.on("data", (data) => collect(stderr, data));
173
+ child.on("error", (error) => finish(error));
112
174
  child.on("close", (code) => {
175
+ if (settled)
176
+ return;
113
177
  if (code === 0) {
114
- resolvePromise(Buffer.concat(stdout));
178
+ finish(undefined, Buffer.concat(stdout));
115
179
  return;
116
180
  }
117
- reject(new Error(Buffer.concat(stderr).toString("utf-8").trim() || `${command} exited with code ${code}`));
181
+ finish(new Error(Buffer.concat(stderr).toString("utf-8").trim() || `${command} exited with code ${code}`));
118
182
  });
119
183
  });
120
184
  }
121
- function handleExec(connection, id, params) {
122
- const command = requireString(params.command, "command");
123
- const runCwd = optionalString(params.cwd) ?? cwd;
124
- const timeout = optionalNumber(params.timeout);
125
- const env = isRecord(params.env)
126
- ? Object.fromEntries(Object.entries(params.env).filter((entry) => typeof entry[1] === "string"))
127
- : undefined;
128
- const child = spawn("bash", ["-lc", command], {
129
- cwd: runCwd,
130
- detached: process.platform !== "win32",
131
- env: env ? { ...process.env, ...env } : process.env,
132
- });
133
- connection.execs.set(id, child);
134
- let timeoutHandle;
135
- if (timeout !== undefined && timeout > 0) {
136
- timeoutHandle = setTimeout(() => {
185
+ function removeUpload(connection, id, destroy) {
186
+ const upload = connection.uploads.get(id);
187
+ if (!upload)
188
+ return undefined;
189
+ connection.uploads.delete(id);
190
+ activeGlobalUploads--;
191
+ if (destroy)
192
+ upload.stream.destroy();
193
+ return upload;
194
+ }
195
+ function handleExec(connection, id, params, signal) {
196
+ return new Promise((resolvePromise) => {
197
+ const command = requireString(params.command, "command");
198
+ const runCwd = optionalString(params.cwd) ?? cwd;
199
+ const timeout = optionalNumber(params.timeout);
200
+ const env = isRecord(params.env)
201
+ ? Object.fromEntries(Object.entries(params.env).filter((entry) => typeof entry[1] === "string"))
202
+ : undefined;
203
+ const child = spawn("bash", ["-lc", command], {
204
+ cwd: runCwd,
205
+ detached: process.platform !== "win32",
206
+ env: env ? { ...process.env, ...env } : process.env,
207
+ });
208
+ connection.execs.set(id, child);
209
+ const kill = () => {
137
210
  if (process.platform !== "win32" && child.pid) {
138
211
  try {
139
212
  process.kill(-child.pid);
@@ -145,25 +218,61 @@ function handleExec(connection, id, params) {
145
218
  else {
146
219
  child.kill();
147
220
  }
148
- }, timeout * 1000);
149
- }
150
- child.stdout.on("data", (data) => {
151
- sendFrame(connection.socket, { id, event: "data", stream: "stdout", dataBase64: data.toString("base64") });
152
- });
153
- child.stderr.on("data", (data) => {
154
- sendFrame(connection.socket, { id, event: "data", stream: "stderr", dataBase64: data.toString("base64") });
155
- });
156
- child.on("error", (error) => {
157
- if (timeoutHandle)
158
- clearTimeout(timeoutHandle);
159
- connection.execs.delete(id);
160
- sendFrame(connection.socket, { id, event: "error", error: { message: error.message } });
161
- });
162
- child.on("close", (code) => {
163
- if (timeoutHandle)
164
- clearTimeout(timeoutHandle);
165
- connection.execs.delete(id);
166
- sendFrame(connection.socket, { id, event: "exit", exitCode: code });
221
+ };
222
+ signal.addEventListener("abort", kill, { once: true });
223
+ let timeoutHandle;
224
+ if (timeout !== undefined && timeout > 0)
225
+ timeoutHandle = setTimeout(kill, timeout * 1000);
226
+ const cleanup = () => {
227
+ if (timeoutHandle)
228
+ clearTimeout(timeoutHandle);
229
+ signal.removeEventListener("abort", kill);
230
+ resolvePromise();
231
+ };
232
+ let drainPromise;
233
+ const pausedStreams = new Set();
234
+ const sendExecData = (stream, data, streamName) => {
235
+ const writable = sendFrame(connection.socket, {
236
+ id,
237
+ event: "data",
238
+ stream: streamName,
239
+ dataBase64: data.toString("base64"),
240
+ });
241
+ if (writable)
242
+ return;
243
+ stream.pause();
244
+ pausedStreams.add(stream);
245
+ if (drainPromise)
246
+ return;
247
+ drainPromise = waitForSocketDrain(connection.socket);
248
+ void drainPromise
249
+ .then(() => {
250
+ for (const pausedStream of pausedStreams)
251
+ pausedStream.resume();
252
+ pausedStreams.clear();
253
+ }, () => kill())
254
+ .finally(() => {
255
+ drainPromise = undefined;
256
+ });
257
+ };
258
+ child.stdout.on("data", (data) => sendExecData(child.stdout, data, "stdout"));
259
+ child.stderr.on("data", (data) => sendExecData(child.stderr, data, "stderr"));
260
+ child.on("error", (error) => {
261
+ const active = connection.execs.get(id) === child;
262
+ if (active)
263
+ connection.execs.delete(id);
264
+ if (active)
265
+ sendFrame(connection.socket, { id, event: "error", error: { message: error.message } });
266
+ cleanup();
267
+ });
268
+ child.on("close", (code) => {
269
+ const active = connection.execs.get(id) === child;
270
+ if (active)
271
+ connection.execs.delete(id);
272
+ if (active)
273
+ sendFrame(connection.socket, { id, event: "exit", exitCode: code });
274
+ cleanup();
275
+ });
167
276
  });
168
277
  }
169
278
  function cancelExec(connection, id) {
@@ -184,13 +293,21 @@ function cancelExec(connection, id) {
184
293
  }
185
294
  sendFrame(connection.socket, { id, event: "exit", exitCode: null, cancelled: true });
186
295
  }
187
- async function handleDownloadFile(connection, id, path) {
296
+ async function handleDownloadFile(connection, id, path, signal) {
188
297
  try {
189
- for await (const chunk of createReadStream(path, { highWaterMark: fileTransferChunkSize })) {
190
- const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
191
- await sendFrameAsync(connection.socket, { id, event: "fileData", dataBase64: buffer.toString("base64") });
298
+ const readStream = createReadStream(path, { highWaterMark: fileTransferChunkSize });
299
+ const cancel = () => readStream.destroy(new Error("Request cancelled"));
300
+ signal.addEventListener("abort", cancel, { once: true });
301
+ try {
302
+ for await (const chunk of readStream) {
303
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
304
+ await sendFrameAsync(connection.socket, { id, event: "fileData", dataBase64: buffer.toString("base64") });
305
+ }
306
+ await sendFrameAsync(connection.socket, { id, event: "fileEnd" });
307
+ }
308
+ finally {
309
+ signal.removeEventListener("abort", cancel);
192
310
  }
193
- await sendFrameAsync(connection.socket, { id, event: "fileEnd" });
194
311
  }
195
312
  catch (error) {
196
313
  sendFrame(connection.socket, {
@@ -230,7 +347,7 @@ function closeUploadStream(stream) {
230
347
  });
231
348
  });
232
349
  }
233
- async function handleRequest(connection, message) {
350
+ async function handleRequest(connection, message, signal) {
234
351
  const id = requireString(message.id, "id");
235
352
  const method = requireString(message.method, "method");
236
353
  const params = isRecord(message.params) ? message.params : {};
@@ -240,7 +357,7 @@ async function handleRequest(connection, message) {
240
357
  return;
241
358
  }
242
359
  if (method === "exec") {
243
- handleExec(connection, id, params);
360
+ await handleExec(connection, id, params, signal);
244
361
  return;
245
362
  }
246
363
  if (method === "capabilities") {
@@ -273,43 +390,54 @@ async function handleRequest(connection, message) {
273
390
  return;
274
391
  }
275
392
  if (method === "downloadFile") {
276
- await handleDownloadFile(connection, id, requireString(params.path, "path"));
393
+ await handleDownloadFile(connection, id, requireString(params.path, "path"), signal);
277
394
  return;
278
395
  }
279
396
  if (method === "uploadFileStart") {
397
+ if (connection.uploads.has(id))
398
+ throw new Error(`Upload already exists: ${id}`);
399
+ if (connection.uploads.size >= maxConnectionUploads) {
400
+ throw new Error(`Too many concurrent uploads for this connection (max ${maxConnectionUploads})`);
401
+ }
402
+ if (activeGlobalUploads >= maxGlobalUploads) {
403
+ throw new Error(`Too many concurrent daemon uploads (max ${maxGlobalUploads})`);
404
+ }
280
405
  const path = requireString(params.path, "path");
281
406
  const stream = createWriteStream(path);
282
- stream.on("error", () => undefined);
283
- connection.uploads.set(id, stream);
407
+ const upload = { stream, bytes: 0 };
408
+ connection.uploads.set(id, upload);
409
+ activeGlobalUploads++;
410
+ stream.once("error", () => removeUpload(connection, id, false));
284
411
  sendResult(connection, id, {});
285
412
  return;
286
413
  }
287
414
  if (method === "uploadFileChunk") {
288
415
  const uploadId = requireString(params.uploadId, "uploadId");
289
- const stream = connection.uploads.get(uploadId);
290
- if (!stream)
416
+ const upload = connection.uploads.get(uploadId);
417
+ if (!upload)
291
418
  throw new Error(`Unknown upload: ${uploadId}`);
292
- await writeUploadChunk(stream, Buffer.from(requireString(params.dataBase64, "dataBase64"), "base64"));
419
+ const chunk = Buffer.from(requireString(params.dataBase64, "dataBase64"), "base64");
420
+ if (upload.bytes + chunk.length > maxUploadBytes) {
421
+ removeUpload(connection, uploadId, true);
422
+ throw new Error(`Upload exceeds ${maxUploadBytes} bytes`);
423
+ }
424
+ upload.bytes += chunk.length;
425
+ await writeUploadChunk(upload.stream, chunk);
293
426
  sendResult(connection, id, {});
294
427
  return;
295
428
  }
296
429
  if (method === "uploadFileEnd") {
297
430
  const uploadId = requireString(params.uploadId, "uploadId");
298
- const stream = connection.uploads.get(uploadId);
299
- if (!stream)
431
+ const upload = removeUpload(connection, uploadId, false);
432
+ if (!upload)
300
433
  throw new Error(`Unknown upload: ${uploadId}`);
301
- connection.uploads.delete(uploadId);
302
- await closeUploadStream(stream);
434
+ await closeUploadStream(upload.stream);
303
435
  sendResult(connection, id, {});
304
436
  return;
305
437
  }
306
438
  if (method === "uploadFileCancel") {
307
439
  const uploadId = requireString(params.uploadId, "uploadId");
308
- const stream = connection.uploads.get(uploadId);
309
- if (stream) {
310
- connection.uploads.delete(uploadId);
311
- stream.destroy();
312
- }
440
+ removeUpload(connection, uploadId, true);
313
441
  sendResult(connection, id, {});
314
442
  return;
315
443
  }
@@ -331,14 +459,14 @@ async function handleRequest(connection, message) {
331
459
  const pattern = requireString(params.pattern, "pattern");
332
460
  const runCwd = requireString(params.cwd, "cwd");
333
461
  const limit = optionalNumber(params.limit) ?? 1000;
334
- const output = await runBuffered("fd", buildFdArgs(pattern, runCwd, limit), runCwd);
462
+ const output = await runBuffered("fd", buildFdArgs(pattern, runCwd, limit), runCwd, signal);
335
463
  sendResult(connection, id, { matches: output.toString("utf-8").split("\n").filter(Boolean) });
336
464
  return;
337
465
  }
338
466
  if (method === "grep") {
339
467
  const pathParam = requireString(params.path, "path");
340
468
  const isDirectory = (await stat(pathParam)).isDirectory();
341
- const output = await runBuffered("rg", buildRgArgs(params), cwd);
469
+ const output = await runBuffered("rg", buildRgArgs(params), cwd, signal);
342
470
  const matches = output
343
471
  .toString("utf-8")
344
472
  .split("\n")
@@ -361,7 +489,7 @@ async function handleRequest(connection, message) {
361
489
  return;
362
490
  }
363
491
  if (method === "detectImageMimeType") {
364
- const output = await runBuffered("file", ["--mime-type", "-b", requireString(params.path, "path")], cwd);
492
+ const output = await runBuffered("file", ["--mime-type", "-b", requireString(params.path, "path")], cwd, signal);
365
493
  const mimeType = output.toString("utf-8").trim();
366
494
  sendResult(connection, id, {
367
495
  mimeType: ["image/jpeg", "image/png", "image/gif", "image/webp"].includes(mimeType) ? mimeType : null,
@@ -374,11 +502,54 @@ async function handleRequest(connection, message) {
374
502
  sendError(connection, id, error);
375
503
  }
376
504
  }
505
+ function getIncomingDeclaredPayloadLength(buffer, chunk) {
506
+ const header = Buffer.concat([buffer.subarray(0, 10), chunk.subarray(0, Math.max(0, 10 - buffer.length))]);
507
+ if (header.length < 2)
508
+ return undefined;
509
+ const marker = header[1] & 0x7f;
510
+ if (marker < 126)
511
+ return BigInt(marker);
512
+ if (marker === 126)
513
+ return header.length >= 4 ? BigInt(header.readUInt16BE(2)) : undefined;
514
+ return header.length >= 10 ? header.readBigUInt64BE(2) : undefined;
515
+ }
516
+ function dispatchRequest(connection, message) {
517
+ const id = typeof message.id === "string" ? message.id : "unknown";
518
+ if (message.method === "cancel") {
519
+ cancelExec(connection, id);
520
+ return;
521
+ }
522
+ if (connection.activeRequests >= maxConnectionRequests) {
523
+ sendError(connection, id, new Error(`Too many concurrent requests for this connection (max ${maxConnectionRequests})`));
524
+ return;
525
+ }
526
+ if (activeGlobalRequests >= maxGlobalRequests) {
527
+ sendError(connection, id, new Error(`Too many concurrent daemon requests (max ${maxGlobalRequests})`));
528
+ return;
529
+ }
530
+ const controller = new AbortController();
531
+ connection.requestControllers.add(controller);
532
+ connection.activeRequests++;
533
+ activeGlobalRequests++;
534
+ void handleRequest(connection, message, controller.signal)
535
+ .catch((error) => sendError(connection, id, error))
536
+ .finally(() => {
537
+ connection.requestControllers.delete(controller);
538
+ connection.activeRequests--;
539
+ activeGlobalRequests--;
540
+ });
541
+ }
377
542
  function parseFrames(connection) {
378
543
  while (connection.buffer.length >= 2) {
379
544
  const first = connection.buffer[0];
380
545
  const second = connection.buffer[1];
546
+ const reservedBits = first & 0x70;
547
+ if (reservedBits !== 0) {
548
+ connection.socket.destroy(new Error("WebSocket reserved bits require a negotiated extension"));
549
+ return;
550
+ }
381
551
  const opcode = first & 0x0f;
552
+ const final = (first & 0x80) !== 0;
382
553
  const masked = (second & 0x80) !== 0;
383
554
  let payloadLength = second & 0x7f;
384
555
  let offset = 2;
@@ -399,6 +570,22 @@ function parseFrames(connection) {
399
570
  payloadLength = Number(largeLength);
400
571
  offset += 8;
401
572
  }
573
+ if (payloadLength > maxFramePayloadBytes) {
574
+ connection.socket.destroy(new Error(`WebSocket frame exceeds ${maxFramePayloadBytes} bytes`));
575
+ return;
576
+ }
577
+ if (!final && (opcode === 0x0 || opcode === 0x1 || opcode === 0x2)) {
578
+ connection.socket.destroy(new Error("Fragmented WebSocket messages are not supported"));
579
+ return;
580
+ }
581
+ if (opcode >= 0x8 && (!final || payloadLength > 125)) {
582
+ connection.socket.destroy(new Error("Invalid WebSocket control frame"));
583
+ return;
584
+ }
585
+ if (opcode !== 0x1 && opcode !== 0x8) {
586
+ connection.socket.destroy(new Error(`Unsupported WebSocket opcode: ${opcode}`));
587
+ return;
588
+ }
402
589
  if (!masked) {
403
590
  connection.socket.destroy(new Error("Client WebSocket frames must be masked"));
404
591
  return;
@@ -429,7 +616,31 @@ function parseFrames(connection) {
429
616
  sendFrame(connection.socket, { type: "pong", timestamp: message.timestamp });
430
617
  continue;
431
618
  }
432
- void handleRequest(connection, message);
619
+ dispatchRequest(connection, message);
620
+ }
621
+ }
622
+ function receiveSocketData(connection, chunk) {
623
+ let remaining = chunk;
624
+ while (remaining.length > 0 && !connection.socket.destroyed) {
625
+ const available = maxConnectionBufferBytes - connection.buffer.length;
626
+ if (available <= 0) {
627
+ connection.socket.destroy(new Error(`WebSocket receive buffer exceeds ${maxConnectionBufferBytes} bytes`));
628
+ return;
629
+ }
630
+ const next = remaining.subarray(0, available);
631
+ const declaredPayloadLength = getIncomingDeclaredPayloadLength(connection.buffer, next);
632
+ if (declaredPayloadLength !== undefined && declaredPayloadLength > BigInt(maxFramePayloadBytes)) {
633
+ connection.socket.destroy(new Error(`WebSocket frame exceeds ${maxFramePayloadBytes} bytes`));
634
+ return;
635
+ }
636
+ connection.buffer = Buffer.concat([connection.buffer, next]);
637
+ remaining = remaining.subarray(next.length);
638
+ const bufferedBeforeParse = connection.buffer.length;
639
+ parseFrames(connection);
640
+ if (remaining.length > 0 && connection.buffer.length === bufferedBeforeParse) {
641
+ connection.socket.destroy(new Error(`WebSocket receive buffer exceeds ${maxConnectionBufferBytes} bytes`));
642
+ return;
643
+ }
433
644
  }
434
645
  }
435
646
  function isAuthorized(request) {
@@ -476,20 +687,24 @@ server.on("upgrade", (request, socket) => {
476
687
  buffer: Buffer.alloc(0),
477
688
  execs: new Map(),
478
689
  uploads: new Map(),
690
+ requestControllers: new Set(),
691
+ activeRequests: 0,
692
+ closed: false,
479
693
  };
480
- netSocket.on("data", (chunk) => {
481
- connection.buffer = Buffer.concat([connection.buffer, chunk]);
482
- parseFrames(connection);
483
- });
694
+ netSocket.on("error", () => undefined);
695
+ netSocket.on("data", (chunk) => receiveSocketData(connection, chunk));
484
696
  netSocket.on("close", () => {
697
+ connection.closed = true;
698
+ for (const controller of connection.requestControllers)
699
+ controller.abort();
700
+ connection.requestControllers.clear();
485
701
  for (const child of connection.execs.values()) {
486
702
  child.kill();
487
703
  }
488
704
  connection.execs.clear();
489
- for (const stream of connection.uploads.values()) {
490
- stream.destroy();
705
+ for (const uploadId of Array.from(connection.uploads.keys())) {
706
+ removeUpload(connection, uploadId, true);
491
707
  }
492
- connection.uploads.clear();
493
708
  });
494
709
  });
495
710
  server.listen(port, host, () => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { once } from \"node:events\";\nimport { constants, createReadStream, createWriteStream, type WriteStream } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { resolve } from \"node:path\";\n\ninterface JsonRpcMessage {\n\tid?: unknown;\n\tmethod?: unknown;\n\tparams?: unknown;\n}\n\ninterface ClientConnection {\n\tsocket: Socket;\n\tbuffer: Buffer;\n\texecs: Map<string, ChildProcessWithoutNullStreams>;\n\tuploads: Map<string, WriteStream>;\n}\n\nconst port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? \"8787\");\nconst host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? \"127.0.0.1\";\nconst cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());\nconst token = process.env.PI_DAEMON_TOKEN;\nconst fileTransferChunkSize = 64 * 1024;\n\nfunction createFrame(payload: unknown): Buffer {\n\tconst data = Buffer.from(JSON.stringify(payload));\n\tlet header: Buffer;\n\tif (data.length < 126) {\n\t\theader = Buffer.from([0x81, data.length]);\n\t} else if (data.length <= 0xffff) {\n\t\theader = Buffer.alloc(4);\n\t\theader[0] = 0x81;\n\t\theader[1] = 126;\n\t\theader.writeUInt16BE(data.length, 2);\n\t} else {\n\t\theader = Buffer.alloc(10);\n\t\theader[0] = 0x81;\n\t\theader[1] = 127;\n\t\theader.writeBigUInt64BE(BigInt(data.length), 2);\n\t}\n\treturn Buffer.concat([header, data]);\n}\n\nfunction sendFrame(socket: Socket, payload: unknown): void {\n\tsocket.write(createFrame(payload));\n}\n\nasync function sendFrameAsync(socket: Socket, payload: unknown): Promise<void> {\n\tif (!socket.write(createFrame(payload))) {\n\t\tawait once(socket, \"drain\");\n\t}\n}\n\nfunction sendResult(connection: ClientConnection, id: string, result: unknown): void {\n\tsendFrame(connection.socket, { id, result });\n}\n\nfunction sendError(connection: ClientConnection, id: string, error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tsendFrame(connection.socket, { id, error: { message } });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction requireString(value: unknown, name: string): string {\n\tif (typeof value !== \"string\") throw new Error(`Missing string param: ${name}`);\n\treturn value;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" ? value : undefined;\n}\n\nfunction accessModeToFsMode(mode: unknown): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid access mode: ${String(mode)}`);\n\t}\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(params: Record<string, unknown>): string[] {\n\tconst pattern = requireString(params.pattern, \"pattern\");\n\tconst path = requireString(params.path, \"path\");\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (params.ignoreCase === true) args.push(\"--ignore-case\");\n\tif (params.literal === true) args.push(\"--fixed-strings\");\n\tconst glob = optionalString(params.glob);\n\tif (glob) args.push(\"--glob\", glob);\n\targs.push(\"--\", pattern, path);\n\treturn args;\n}\n\nasync function runBuffered(command: string, args: string[], runCwd: string): Promise<Buffer> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(command, args, { cwd: runCwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tchild.stdout.on(\"data\", (data: Buffer) => stdout.push(data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => stderr.push(data));\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolvePromise(Buffer.concat(stdout));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n\nfunction handleExec(connection: ClientConnection, id: string, params: Record<string, unknown>): void {\n\tconst command = requireString(params.command, \"command\");\n\tconst runCwd = optionalString(params.cwd) ?? cwd;\n\tconst timeout = optionalNumber(params.timeout);\n\tconst env = isRecord(params.env)\n\t\t? Object.fromEntries(\n\t\t\t\tObject.entries(params.env).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t\t\t)\n\t\t: undefined;\n\tconst child = spawn(\"bash\", [\"-lc\", command], {\n\t\tcwd: runCwd,\n\t\tdetached: process.platform !== \"win32\",\n\t\tenv: env ? { ...process.env, ...env } : process.env,\n\t});\n\tconnection.execs.set(id, child);\n\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\tif (timeout !== undefined && timeout > 0) {\n\t\ttimeoutHandle = setTimeout(() => {\n\t\t\tif (process.platform !== \"win32\" && child.pid) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(-child.pid);\n\t\t\t\t} catch {\n\t\t\t\t\tchild.kill();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tchild.kill();\n\t\t\t}\n\t\t}, timeout * 1000);\n\t}\n\tchild.stdout.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stdout\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.stderr.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stderr\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.on(\"error\", (error) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"error\", error: { message: error.message } });\n\t});\n\tchild.on(\"close\", (code) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: code });\n\t});\n}\n\nfunction cancelExec(connection: ClientConnection, id: string): void {\n\tconst child = connection.execs.get(id);\n\tif (!child) return;\n\tconnection.execs.delete(id);\n\tif (process.platform !== \"win32\" && child.pid) {\n\t\ttry {\n\t\t\tprocess.kill(-child.pid);\n\t\t} catch {\n\t\t\tchild.kill();\n\t\t}\n\t} else {\n\t\tchild.kill();\n\t}\n\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: null, cancelled: true });\n}\n\nasync function handleDownloadFile(connection: ClientConnection, id: string, path: string): Promise<void> {\n\ttry {\n\t\tfor await (const chunk of createReadStream(path, { highWaterMark: fileTransferChunkSize })) {\n\t\t\tconst buffer = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n\t\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileData\", dataBase64: buffer.toString(\"base64\") });\n\t\t}\n\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileEnd\" });\n\t} catch (error) {\n\t\tsendFrame(connection.socket, {\n\t\t\tid,\n\t\t\tevent: \"fileError\",\n\t\t\terror: { message: error instanceof Error ? error.message : String(error) },\n\t\t});\n\t}\n}\n\nfunction writeUploadChunk(stream: WriteStream, chunk: Buffer): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.write(chunk, (error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tif (error) {\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction closeUploadStream(stream: WriteStream): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.end(() => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nasync function handleRequest(connection: ClientConnection, message: JsonRpcMessage): Promise<void> {\n\tconst id = requireString(message.id, \"id\");\n\tconst method = requireString(message.method, \"method\");\n\tconst params = isRecord(message.params) ? message.params : {};\n\ttry {\n\t\tif (method === \"cancel\") {\n\t\t\tcancelExec(connection, id);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"exec\") {\n\t\t\thandleExec(connection, id, params);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"capabilities\") {\n\t\t\tsendResult(connection, id, {\n\t\t\t\tcwd,\n\t\t\t\tfeatures: {\n\t\t\t\t\texec: true,\n\t\t\t\t\tfiles: true,\n\t\t\t\t\tfileTransfer: true,\n\t\t\t\t\tglob: true,\n\t\t\t\t\tgrep: true,\n\t\t\t\t\tinstructions: false,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"access\") {\n\t\t\tawait access(requireString(params.path, \"path\"), accessModeToFsMode(params.mode));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readFile\") {\n\t\t\tconst content = await readFile(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { contentBase64: content.toString(\"base64\") });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"writeFile\") {\n\t\t\tawait writeFile(\n\t\t\t\trequireString(params.path, \"path\"),\n\t\t\t\tBuffer.from(requireString(params.contentBase64, \"contentBase64\"), \"base64\"),\n\t\t\t);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"downloadFile\") {\n\t\t\tawait handleDownloadFile(connection, id, requireString(params.path, \"path\"));\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileStart\") {\n\t\t\tconst path = requireString(params.path, \"path\");\n\t\t\tconst stream = createWriteStream(path);\n\t\t\tstream.on(\"error\", () => undefined);\n\t\t\tconnection.uploads.set(id, stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileChunk\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (!stream) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tawait writeUploadChunk(stream, Buffer.from(requireString(params.dataBase64, \"dataBase64\"), \"base64\"));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileEnd\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (!stream) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tconnection.uploads.delete(uploadId);\n\t\t\tawait closeUploadStream(stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileCancel\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (stream) {\n\t\t\t\tconnection.uploads.delete(uploadId);\n\t\t\t\tstream.destroy();\n\t\t\t}\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"mkdir\") {\n\t\t\tawait mkdir(requireString(params.path, \"path\"), { recursive: params.recursive === true });\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"stat\") {\n\t\t\tconst result = await stat(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { isDirectory: result.isDirectory(), isFile: result.isFile() });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readdir\") {\n\t\t\tsendResult(connection, id, { entries: await readdir(requireString(params.path, \"path\")) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"glob\") {\n\t\t\tconst pattern = requireString(params.pattern, \"pattern\");\n\t\t\tconst runCwd = requireString(params.cwd, \"cwd\");\n\t\t\tconst limit = optionalNumber(params.limit) ?? 1000;\n\t\t\tconst output = await runBuffered(\"fd\", buildFdArgs(pattern, runCwd, limit), runCwd);\n\t\t\tsendResult(connection, id, { matches: output.toString(\"utf-8\").split(\"\\n\").filter(Boolean) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"grep\") {\n\t\t\tconst pathParam = requireString(params.path, \"path\");\n\t\t\tconst isDirectory = (await stat(pathParam)).isDirectory();\n\t\t\tconst output = await runBuffered(\"rg\", buildRgArgs(params), cwd);\n\t\t\tconst matches = output\n\t\t\t\t.toString(\"utf-8\")\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.flatMap((line) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(line) as unknown;\n\t\t\t\t\t\tif (!isRecord(data) || data.type !== \"match\" || !isRecord(data.data)) return [];\n\t\t\t\t\t\tconst filePath = isRecord(data.data.path) ? optionalString(data.data.path.text) : undefined;\n\t\t\t\t\t\tconst lineNumber = optionalNumber(data.data.line_number);\n\t\t\t\t\t\tconst lineText = isRecord(data.data.lines) ? optionalString(data.data.lines.text) : undefined;\n\t\t\t\t\t\treturn filePath && lineNumber !== undefined ? [{ filePath, lineNumber, lineText }] : [];\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tsendResult(connection, id, { isDirectory, matches });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"detectImageMimeType\") {\n\t\t\tconst output = await runBuffered(\"file\", [\"--mime-type\", \"-b\", requireString(params.path, \"path\")], cwd);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\tsendResult(connection, id, {\n\t\t\t\tmimeType: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t} catch (error) {\n\t\tsendError(connection, id, error);\n\t}\n}\n\nfunction parseFrames(connection: ClientConnection): void {\n\twhile (connection.buffer.length >= 2) {\n\t\tconst first = connection.buffer[0];\n\t\tconst second = connection.buffer[1];\n\t\tconst opcode = first & 0x0f;\n\t\tconst masked = (second & 0x80) !== 0;\n\t\tlet payloadLength = second & 0x7f;\n\t\tlet offset = 2;\n\t\tif (payloadLength === 126) {\n\t\t\tif (connection.buffer.length < offset + 2) return;\n\t\t\tpayloadLength = connection.buffer.readUInt16BE(offset);\n\t\t\toffset += 2;\n\t\t} else if (payloadLength === 127) {\n\t\t\tif (connection.buffer.length < offset + 8) return;\n\t\t\tconst largeLength = connection.buffer.readBigUInt64BE(offset);\n\t\t\tif (largeLength > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\t\t\tconnection.socket.destroy(new Error(\"WebSocket frame too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpayloadLength = Number(largeLength);\n\t\t\toffset += 8;\n\t\t}\n\t\tif (!masked) {\n\t\t\tconnection.socket.destroy(new Error(\"Client WebSocket frames must be masked\"));\n\t\t\treturn;\n\t\t}\n\t\tif (connection.buffer.length < offset + 4 + payloadLength) return;\n\t\tconst mask = connection.buffer.subarray(offset, offset + 4);\n\t\toffset += 4;\n\t\tconst payload = Buffer.from(connection.buffer.subarray(offset, offset + payloadLength));\n\t\tconnection.buffer = connection.buffer.subarray(offset + payloadLength);\n\t\tfor (let index = 0; index < payload.length; index++) {\n\t\t\tpayload[index] ^= mask[index % 4];\n\t\t}\n\t\tif (opcode === 0x8) {\n\t\t\tconnection.socket.end();\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1) continue;\n\t\tlet message: unknown;\n\t\ttry {\n\t\t\tmessage = JSON.parse(payload.toString(\"utf-8\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isRecord(message) && message.type === \"ping\") {\n\t\t\tsendFrame(connection.socket, { type: \"pong\", timestamp: message.timestamp });\n\t\t\tcontinue;\n\t\t}\n\t\tvoid handleRequest(connection, message as JsonRpcMessage);\n\t}\n}\n\nfunction isAuthorized(request: IncomingMessage): boolean {\n\tif (!token) return true;\n\tif (request.headers.authorization === `Bearer ${token}`) return true;\n\ttry {\n\t\tconst url = new URL(request.url ?? \"/\", `http://${request.headers.host ?? \"localhost\"}`);\n\t\treturn url.searchParams.get(\"token\") === token;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst server = createServer((_request, response) => {\n\tresponse.writeHead(404);\n\tresponse.end(\"pi-daemon only serves WebSocket remote commander connections\\n\");\n});\n\nserver.on(\"upgrade\", (request, socket) => {\n\tconst netSocket = socket as Socket;\n\tif (!isAuthorized(request)) {\n\t\tnetSocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst key = request.headers[\"sec-websocket-key\"];\n\tif (typeof key !== \"string\") {\n\t\tnetSocket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst accept = createHash(\"sha1\").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(\"base64\");\n\tnetSocket.write(\n\t\t[\n\t\t\t\"HTTP/1.1 101 Switching Protocols\",\n\t\t\t\"Upgrade: websocket\",\n\t\t\t\"Connection: Upgrade\",\n\t\t\t`Sec-WebSocket-Accept: ${accept}`,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t].join(\"\\r\\n\"),\n\t);\n\tconst connection: ClientConnection = {\n\t\tsocket: netSocket,\n\t\tbuffer: Buffer.alloc(0),\n\t\texecs: new Map(),\n\t\tuploads: new Map(),\n\t};\n\tnetSocket.on(\"data\", (chunk: Buffer) => {\n\t\tconnection.buffer = Buffer.concat([connection.buffer, chunk]);\n\t\tparseFrames(connection);\n\t});\n\tnetSocket.on(\"close\", () => {\n\t\tfor (const child of connection.execs.values()) {\n\t\t\tchild.kill();\n\t\t}\n\t\tconnection.execs.clear();\n\t\tfor (const stream of connection.uploads.values()) {\n\t\t\tstream.destroy();\n\t\t}\n\t\tconnection.uploads.clear();\n\t});\n});\n\nserver.listen(port, host, () => {\n\tconsole.log(`pi-daemon listening on ws://${host}:${port} cwd=${cwd}`);\n});\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { constants, createReadStream, createWriteStream, type WriteStream } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { resolve } from \"node:path\";\n\ninterface JsonRpcMessage {\n\tid?: unknown;\n\tmethod?: unknown;\n\tparams?: unknown;\n}\n\ninterface UploadState {\n\tstream: WriteStream;\n\tbytes: number;\n}\n\ninterface ClientConnection {\n\tsocket: Socket;\n\tbuffer: Buffer;\n\texecs: Map<string, ChildProcessWithoutNullStreams>;\n\tuploads: Map<string, UploadState>;\n\trequestControllers: Set<AbortController>;\n\tactiveRequests: number;\n\tclosed: boolean;\n}\n\nconst port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? \"8787\");\nconst host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? \"127.0.0.1\";\nconst cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());\nconst token = process.env.PI_DAEMON_TOKEN;\nconst fileTransferChunkSize = 64 * 1024;\nconst maxFramePayloadBytes = Number(process.env.PI_DAEMON_MAX_FRAME_BYTES ?? 1024 * 1024);\nconst maxConnectionRequests = Number(process.env.PI_DAEMON_MAX_CONNECTION_REQUESTS ?? 8);\nconst maxGlobalRequests = Number(process.env.PI_DAEMON_MAX_GLOBAL_REQUESTS ?? 64);\nconst maxConnectionBufferBytes = maxFramePayloadBytes + 14;\nconst maxConnectionUploads = Number(process.env.PI_DAEMON_MAX_CONNECTION_UPLOADS ?? 4);\nconst maxGlobalUploads = Number(process.env.PI_DAEMON_MAX_GLOBAL_UPLOADS ?? 32);\nconst maxUploadBytes = Number(process.env.PI_DAEMON_MAX_UPLOAD_BYTES ?? 100 * 1024 * 1024);\nconst maxBufferedProcessOutputBytes = Number(process.env.PI_DAEMON_MAX_BUFFERED_OUTPUT_BYTES ?? 8 * 1024 * 1024);\nlet activeGlobalRequests = 0;\nlet activeGlobalUploads = 0;\n\nfunction createFrame(payload: unknown): Buffer {\n\tconst data = Buffer.from(JSON.stringify(payload));\n\tlet header: Buffer;\n\tif (data.length < 126) {\n\t\theader = Buffer.from([0x81, data.length]);\n\t} else if (data.length <= 0xffff) {\n\t\theader = Buffer.alloc(4);\n\t\theader[0] = 0x81;\n\t\theader[1] = 126;\n\t\theader.writeUInt16BE(data.length, 2);\n\t} else {\n\t\theader = Buffer.alloc(10);\n\t\theader[0] = 0x81;\n\t\theader[1] = 127;\n\t\theader.writeBigUInt64BE(BigInt(data.length), 2);\n\t}\n\treturn Buffer.concat([header, data]);\n}\n\nfunction sendFrame(socket: Socket, payload: unknown): boolean {\n\tif (socket.destroyed || !socket.writable) return true;\n\treturn socket.write(createFrame(payload));\n}\n\nfunction waitForSocketDrain(socket: Socket): Promise<void> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst cleanup = () => {\n\t\t\tsocket.off(\"drain\", onDrain);\n\t\t\tsocket.off(\"close\", onClose);\n\t\t\tsocket.off(\"error\", onError);\n\t\t};\n\t\tconst onDrain = () => {\n\t\t\tcleanup();\n\t\t\tresolvePromise();\n\t\t};\n\t\tconst onClose = () => {\n\t\t\tcleanup();\n\t\t\treject(new Error(\"Socket closed before write drained\"));\n\t\t};\n\t\tconst onError = (error: Error) => {\n\t\t\tcleanup();\n\t\t\treject(error);\n\t\t};\n\t\tsocket.once(\"drain\", onDrain);\n\t\tsocket.once(\"close\", onClose);\n\t\tsocket.once(\"error\", onError);\n\t});\n}\n\nasync function sendFrameAsync(socket: Socket, payload: unknown): Promise<void> {\n\tif (socket.destroyed || !socket.writable) throw new Error(\"Socket is closed\");\n\tif (!socket.write(createFrame(payload))) await waitForSocketDrain(socket);\n}\n\nfunction sendResult(connection: ClientConnection, id: string, result: unknown): void {\n\tsendFrame(connection.socket, { id, result });\n}\n\nfunction sendError(connection: ClientConnection, id: string, error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tsendFrame(connection.socket, { id, error: { message } });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction requireString(value: unknown, name: string): string {\n\tif (typeof value !== \"string\") throw new Error(`Missing string param: ${name}`);\n\treturn value;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" ? value : undefined;\n}\n\nfunction accessModeToFsMode(mode: unknown): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid access mode: ${String(mode)}`);\n\t}\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(params: Record<string, unknown>): string[] {\n\tconst pattern = requireString(params.pattern, \"pattern\");\n\tconst path = requireString(params.path, \"path\");\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (params.ignoreCase === true) args.push(\"--ignore-case\");\n\tif (params.literal === true) args.push(\"--fixed-strings\");\n\tconst glob = optionalString(params.glob);\n\tif (glob) args.push(\"--glob\", glob);\n\targs.push(\"--\", pattern, path);\n\treturn args;\n}\n\nasync function runBuffered(command: string, args: string[], runCwd: string, signal?: AbortSignal): Promise<Buffer> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(command, args, { cwd: runCwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tlet bufferedBytes = 0;\n\t\tlet settled = false;\n\t\tconst finish = (error?: Error, output?: Buffer) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tif (error) reject(error);\n\t\t\telse resolvePromise(output ?? Buffer.alloc(0));\n\t\t};\n\t\tconst onAbort = () => {\n\t\t\tchild.kill();\n\t\t\tfinish(new Error(\"Request cancelled\"));\n\t\t};\n\t\tconst collect = (target: Buffer[], data: Buffer) => {\n\t\t\tbufferedBytes += data.length;\n\t\t\tif (bufferedBytes > maxBufferedProcessOutputBytes) {\n\t\t\t\tchild.kill();\n\t\t\t\tfinish(new Error(`Process output exceeds ${maxBufferedProcessOutputBytes} bytes`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttarget.push(data);\n\t\t};\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\tchild.stdout.on(\"data\", (data: Buffer) => collect(stdout, data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => collect(stderr, data));\n\t\tchild.on(\"error\", (error) => finish(error));\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (settled) return;\n\t\t\tif (code === 0) {\n\t\t\t\tfinish(undefined, Buffer.concat(stdout));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n\nfunction removeUpload(connection: ClientConnection, id: string, destroy: boolean): UploadState | undefined {\n\tconst upload = connection.uploads.get(id);\n\tif (!upload) return undefined;\n\tconnection.uploads.delete(id);\n\tactiveGlobalUploads--;\n\tif (destroy) upload.stream.destroy();\n\treturn upload;\n}\n\nfunction handleExec(\n\tconnection: ClientConnection,\n\tid: string,\n\tparams: Record<string, unknown>,\n\tsignal: AbortSignal,\n): Promise<void> {\n\treturn new Promise((resolvePromise) => {\n\t\tconst command = requireString(params.command, \"command\");\n\t\tconst runCwd = optionalString(params.cwd) ?? cwd;\n\t\tconst timeout = optionalNumber(params.timeout);\n\t\tconst env = isRecord(params.env)\n\t\t\t? Object.fromEntries(\n\t\t\t\t\tObject.entries(params.env).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t\t\t\t)\n\t\t\t: undefined;\n\t\tconst child = spawn(\"bash\", [\"-lc\", command], {\n\t\t\tcwd: runCwd,\n\t\t\tdetached: process.platform !== \"win32\",\n\t\t\tenv: env ? { ...process.env, ...env } : process.env,\n\t\t});\n\t\tconnection.execs.set(id, child);\n\t\tconst kill = () => {\n\t\t\tif (process.platform !== \"win32\" && child.pid) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(-child.pid);\n\t\t\t\t} catch {\n\t\t\t\t\tchild.kill();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tchild.kill();\n\t\t\t}\n\t\t};\n\t\tsignal.addEventListener(\"abort\", kill, { once: true });\n\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\tif (timeout !== undefined && timeout > 0) timeoutHandle = setTimeout(kill, timeout * 1000);\n\t\tconst cleanup = () => {\n\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\tsignal.removeEventListener(\"abort\", kill);\n\t\t\tresolvePromise();\n\t\t};\n\t\tlet drainPromise: Promise<void> | undefined;\n\t\tconst pausedStreams = new Set<typeof child.stdout>();\n\t\tconst sendExecData = (stream: typeof child.stdout, data: Buffer, streamName: \"stdout\" | \"stderr\") => {\n\t\t\tconst writable = sendFrame(connection.socket, {\n\t\t\t\tid,\n\t\t\t\tevent: \"data\",\n\t\t\t\tstream: streamName,\n\t\t\t\tdataBase64: data.toString(\"base64\"),\n\t\t\t});\n\t\t\tif (writable) return;\n\t\t\tstream.pause();\n\t\t\tpausedStreams.add(stream);\n\t\t\tif (drainPromise) return;\n\t\t\tdrainPromise = waitForSocketDrain(connection.socket);\n\t\t\tvoid drainPromise\n\t\t\t\t.then(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tfor (const pausedStream of pausedStreams) pausedStream.resume();\n\t\t\t\t\t\tpausedStreams.clear();\n\t\t\t\t\t},\n\t\t\t\t\t() => kill(),\n\t\t\t\t)\n\t\t\t\t.finally(() => {\n\t\t\t\t\tdrainPromise = undefined;\n\t\t\t\t});\n\t\t};\n\t\tchild.stdout.on(\"data\", (data: Buffer) => sendExecData(child.stdout, data, \"stdout\"));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => sendExecData(child.stderr, data, \"stderr\"));\n\t\tchild.on(\"error\", (error) => {\n\t\t\tconst active = connection.execs.get(id) === child;\n\t\t\tif (active) connection.execs.delete(id);\n\t\t\tif (active) sendFrame(connection.socket, { id, event: \"error\", error: { message: error.message } });\n\t\t\tcleanup();\n\t\t});\n\t\tchild.on(\"close\", (code) => {\n\t\t\tconst active = connection.execs.get(id) === child;\n\t\t\tif (active) connection.execs.delete(id);\n\t\t\tif (active) sendFrame(connection.socket, { id, event: \"exit\", exitCode: code });\n\t\t\tcleanup();\n\t\t});\n\t});\n}\n\nfunction cancelExec(connection: ClientConnection, id: string): void {\n\tconst child = connection.execs.get(id);\n\tif (!child) return;\n\tconnection.execs.delete(id);\n\tif (process.platform !== \"win32\" && child.pid) {\n\t\ttry {\n\t\t\tprocess.kill(-child.pid);\n\t\t} catch {\n\t\t\tchild.kill();\n\t\t}\n\t} else {\n\t\tchild.kill();\n\t}\n\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: null, cancelled: true });\n}\n\nasync function handleDownloadFile(\n\tconnection: ClientConnection,\n\tid: string,\n\tpath: string,\n\tsignal: AbortSignal,\n): Promise<void> {\n\ttry {\n\t\tconst readStream = createReadStream(path, { highWaterMark: fileTransferChunkSize });\n\t\tconst cancel = () => readStream.destroy(new Error(\"Request cancelled\"));\n\t\tsignal.addEventListener(\"abort\", cancel, { once: true });\n\t\ttry {\n\t\t\tfor await (const chunk of readStream) {\n\t\t\t\tconst buffer = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n\t\t\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileData\", dataBase64: buffer.toString(\"base64\") });\n\t\t\t}\n\t\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileEnd\" });\n\t\t} finally {\n\t\t\tsignal.removeEventListener(\"abort\", cancel);\n\t\t}\n\t} catch (error) {\n\t\tsendFrame(connection.socket, {\n\t\t\tid,\n\t\t\tevent: \"fileError\",\n\t\t\terror: { message: error instanceof Error ? error.message : String(error) },\n\t\t});\n\t}\n}\n\nfunction writeUploadChunk(stream: WriteStream, chunk: Buffer): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.write(chunk, (error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tif (error) {\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction closeUploadStream(stream: WriteStream): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.end(() => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nasync function handleRequest(\n\tconnection: ClientConnection,\n\tmessage: JsonRpcMessage,\n\tsignal: AbortSignal,\n): Promise<void> {\n\tconst id = requireString(message.id, \"id\");\n\tconst method = requireString(message.method, \"method\");\n\tconst params = isRecord(message.params) ? message.params : {};\n\ttry {\n\t\tif (method === \"cancel\") {\n\t\t\tcancelExec(connection, id);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"exec\") {\n\t\t\tawait handleExec(connection, id, params, signal);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"capabilities\") {\n\t\t\tsendResult(connection, id, {\n\t\t\t\tcwd,\n\t\t\t\tfeatures: {\n\t\t\t\t\texec: true,\n\t\t\t\t\tfiles: true,\n\t\t\t\t\tfileTransfer: true,\n\t\t\t\t\tglob: true,\n\t\t\t\t\tgrep: true,\n\t\t\t\t\tinstructions: false,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"access\") {\n\t\t\tawait access(requireString(params.path, \"path\"), accessModeToFsMode(params.mode));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readFile\") {\n\t\t\tconst content = await readFile(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { contentBase64: content.toString(\"base64\") });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"writeFile\") {\n\t\t\tawait writeFile(\n\t\t\t\trequireString(params.path, \"path\"),\n\t\t\t\tBuffer.from(requireString(params.contentBase64, \"contentBase64\"), \"base64\"),\n\t\t\t);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"downloadFile\") {\n\t\t\tawait handleDownloadFile(connection, id, requireString(params.path, \"path\"), signal);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileStart\") {\n\t\t\tif (connection.uploads.has(id)) throw new Error(`Upload already exists: ${id}`);\n\t\t\tif (connection.uploads.size >= maxConnectionUploads) {\n\t\t\t\tthrow new Error(`Too many concurrent uploads for this connection (max ${maxConnectionUploads})`);\n\t\t\t}\n\t\t\tif (activeGlobalUploads >= maxGlobalUploads) {\n\t\t\t\tthrow new Error(`Too many concurrent daemon uploads (max ${maxGlobalUploads})`);\n\t\t\t}\n\t\t\tconst path = requireString(params.path, \"path\");\n\t\t\tconst stream = createWriteStream(path);\n\t\t\tconst upload: UploadState = { stream, bytes: 0 };\n\t\t\tconnection.uploads.set(id, upload);\n\t\t\tactiveGlobalUploads++;\n\t\t\tstream.once(\"error\", () => removeUpload(connection, id, false));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileChunk\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst upload = connection.uploads.get(uploadId);\n\t\t\tif (!upload) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tconst chunk = Buffer.from(requireString(params.dataBase64, \"dataBase64\"), \"base64\");\n\t\t\tif (upload.bytes + chunk.length > maxUploadBytes) {\n\t\t\t\tremoveUpload(connection, uploadId, true);\n\t\t\t\tthrow new Error(`Upload exceeds ${maxUploadBytes} bytes`);\n\t\t\t}\n\t\t\tupload.bytes += chunk.length;\n\t\t\tawait writeUploadChunk(upload.stream, chunk);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileEnd\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst upload = removeUpload(connection, uploadId, false);\n\t\t\tif (!upload) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tawait closeUploadStream(upload.stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileCancel\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tremoveUpload(connection, uploadId, true);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"mkdir\") {\n\t\t\tawait mkdir(requireString(params.path, \"path\"), { recursive: params.recursive === true });\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"stat\") {\n\t\t\tconst result = await stat(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { isDirectory: result.isDirectory(), isFile: result.isFile() });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readdir\") {\n\t\t\tsendResult(connection, id, { entries: await readdir(requireString(params.path, \"path\")) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"glob\") {\n\t\t\tconst pattern = requireString(params.pattern, \"pattern\");\n\t\t\tconst runCwd = requireString(params.cwd, \"cwd\");\n\t\t\tconst limit = optionalNumber(params.limit) ?? 1000;\n\t\t\tconst output = await runBuffered(\"fd\", buildFdArgs(pattern, runCwd, limit), runCwd, signal);\n\t\t\tsendResult(connection, id, { matches: output.toString(\"utf-8\").split(\"\\n\").filter(Boolean) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"grep\") {\n\t\t\tconst pathParam = requireString(params.path, \"path\");\n\t\t\tconst isDirectory = (await stat(pathParam)).isDirectory();\n\t\t\tconst output = await runBuffered(\"rg\", buildRgArgs(params), cwd, signal);\n\t\t\tconst matches = output\n\t\t\t\t.toString(\"utf-8\")\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.flatMap((line) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(line) as unknown;\n\t\t\t\t\t\tif (!isRecord(data) || data.type !== \"match\" || !isRecord(data.data)) return [];\n\t\t\t\t\t\tconst filePath = isRecord(data.data.path) ? optionalString(data.data.path.text) : undefined;\n\t\t\t\t\t\tconst lineNumber = optionalNumber(data.data.line_number);\n\t\t\t\t\t\tconst lineText = isRecord(data.data.lines) ? optionalString(data.data.lines.text) : undefined;\n\t\t\t\t\t\treturn filePath && lineNumber !== undefined ? [{ filePath, lineNumber, lineText }] : [];\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tsendResult(connection, id, { isDirectory, matches });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"detectImageMimeType\") {\n\t\t\tconst output = await runBuffered(\n\t\t\t\t\"file\",\n\t\t\t\t[\"--mime-type\", \"-b\", requireString(params.path, \"path\")],\n\t\t\t\tcwd,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\tsendResult(connection, id, {\n\t\t\t\tmimeType: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t} catch (error) {\n\t\tsendError(connection, id, error);\n\t}\n}\n\nfunction getIncomingDeclaredPayloadLength(buffer: Buffer, chunk: Buffer): bigint | undefined {\n\tconst header = Buffer.concat([buffer.subarray(0, 10), chunk.subarray(0, Math.max(0, 10 - buffer.length))]);\n\tif (header.length < 2) return undefined;\n\tconst marker = header[1] & 0x7f;\n\tif (marker < 126) return BigInt(marker);\n\tif (marker === 126) return header.length >= 4 ? BigInt(header.readUInt16BE(2)) : undefined;\n\treturn header.length >= 10 ? header.readBigUInt64BE(2) : undefined;\n}\n\nfunction dispatchRequest(connection: ClientConnection, message: JsonRpcMessage): void {\n\tconst id = typeof message.id === \"string\" ? message.id : \"unknown\";\n\tif (message.method === \"cancel\") {\n\t\tcancelExec(connection, id);\n\t\treturn;\n\t}\n\tif (connection.activeRequests >= maxConnectionRequests) {\n\t\tsendError(\n\t\t\tconnection,\n\t\t\tid,\n\t\t\tnew Error(`Too many concurrent requests for this connection (max ${maxConnectionRequests})`),\n\t\t);\n\t\treturn;\n\t}\n\tif (activeGlobalRequests >= maxGlobalRequests) {\n\t\tsendError(connection, id, new Error(`Too many concurrent daemon requests (max ${maxGlobalRequests})`));\n\t\treturn;\n\t}\n\tconst controller = new AbortController();\n\tconnection.requestControllers.add(controller);\n\tconnection.activeRequests++;\n\tactiveGlobalRequests++;\n\tvoid handleRequest(connection, message, controller.signal)\n\t\t.catch((error: unknown) => sendError(connection, id, error))\n\t\t.finally(() => {\n\t\t\tconnection.requestControllers.delete(controller);\n\t\t\tconnection.activeRequests--;\n\t\t\tactiveGlobalRequests--;\n\t\t});\n}\n\nfunction parseFrames(connection: ClientConnection): void {\n\twhile (connection.buffer.length >= 2) {\n\t\tconst first = connection.buffer[0];\n\t\tconst second = connection.buffer[1];\n\t\tconst reservedBits = first & 0x70;\n\t\tif (reservedBits !== 0) {\n\t\t\tconnection.socket.destroy(new Error(\"WebSocket reserved bits require a negotiated extension\"));\n\t\t\treturn;\n\t\t}\n\t\tconst opcode = first & 0x0f;\n\t\tconst final = (first & 0x80) !== 0;\n\t\tconst masked = (second & 0x80) !== 0;\n\t\tlet payloadLength = second & 0x7f;\n\t\tlet offset = 2;\n\t\tif (payloadLength === 126) {\n\t\t\tif (connection.buffer.length < offset + 2) return;\n\t\t\tpayloadLength = connection.buffer.readUInt16BE(offset);\n\t\t\toffset += 2;\n\t\t} else if (payloadLength === 127) {\n\t\t\tif (connection.buffer.length < offset + 8) return;\n\t\t\tconst largeLength = connection.buffer.readBigUInt64BE(offset);\n\t\t\tif (largeLength > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\t\t\tconnection.socket.destroy(new Error(\"WebSocket frame too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpayloadLength = Number(largeLength);\n\t\t\toffset += 8;\n\t\t}\n\t\tif (payloadLength > maxFramePayloadBytes) {\n\t\t\tconnection.socket.destroy(new Error(`WebSocket frame exceeds ${maxFramePayloadBytes} bytes`));\n\t\t\treturn;\n\t\t}\n\t\tif (!final && (opcode === 0x0 || opcode === 0x1 || opcode === 0x2)) {\n\t\t\tconnection.socket.destroy(new Error(\"Fragmented WebSocket messages are not supported\"));\n\t\t\treturn;\n\t\t}\n\t\tif (opcode >= 0x8 && (!final || payloadLength > 125)) {\n\t\t\tconnection.socket.destroy(new Error(\"Invalid WebSocket control frame\"));\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1 && opcode !== 0x8) {\n\t\t\tconnection.socket.destroy(new Error(`Unsupported WebSocket opcode: ${opcode}`));\n\t\t\treturn;\n\t\t}\n\t\tif (!masked) {\n\t\t\tconnection.socket.destroy(new Error(\"Client WebSocket frames must be masked\"));\n\t\t\treturn;\n\t\t}\n\t\tif (connection.buffer.length < offset + 4 + payloadLength) return;\n\t\tconst mask = connection.buffer.subarray(offset, offset + 4);\n\t\toffset += 4;\n\t\tconst payload = Buffer.from(connection.buffer.subarray(offset, offset + payloadLength));\n\t\tconnection.buffer = connection.buffer.subarray(offset + payloadLength);\n\t\tfor (let index = 0; index < payload.length; index++) {\n\t\t\tpayload[index] ^= mask[index % 4];\n\t\t}\n\t\tif (opcode === 0x8) {\n\t\t\tconnection.socket.end();\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1) continue;\n\t\tlet message: unknown;\n\t\ttry {\n\t\t\tmessage = JSON.parse(payload.toString(\"utf-8\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isRecord(message) && message.type === \"ping\") {\n\t\t\tsendFrame(connection.socket, { type: \"pong\", timestamp: message.timestamp });\n\t\t\tcontinue;\n\t\t}\n\t\tdispatchRequest(connection, message as JsonRpcMessage);\n\t}\n}\n\nfunction receiveSocketData(connection: ClientConnection, chunk: Buffer): void {\n\tlet remaining = chunk;\n\twhile (remaining.length > 0 && !connection.socket.destroyed) {\n\t\tconst available = maxConnectionBufferBytes - connection.buffer.length;\n\t\tif (available <= 0) {\n\t\t\tconnection.socket.destroy(new Error(`WebSocket receive buffer exceeds ${maxConnectionBufferBytes} bytes`));\n\t\t\treturn;\n\t\t}\n\t\tconst next = remaining.subarray(0, available);\n\t\tconst declaredPayloadLength = getIncomingDeclaredPayloadLength(connection.buffer, next);\n\t\tif (declaredPayloadLength !== undefined && declaredPayloadLength > BigInt(maxFramePayloadBytes)) {\n\t\t\tconnection.socket.destroy(new Error(`WebSocket frame exceeds ${maxFramePayloadBytes} bytes`));\n\t\t\treturn;\n\t\t}\n\t\tconnection.buffer = Buffer.concat([connection.buffer, next]);\n\t\tremaining = remaining.subarray(next.length);\n\t\tconst bufferedBeforeParse = connection.buffer.length;\n\t\tparseFrames(connection);\n\t\tif (remaining.length > 0 && connection.buffer.length === bufferedBeforeParse) {\n\t\t\tconnection.socket.destroy(new Error(`WebSocket receive buffer exceeds ${maxConnectionBufferBytes} bytes`));\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nfunction isAuthorized(request: IncomingMessage): boolean {\n\tif (!token) return true;\n\tif (request.headers.authorization === `Bearer ${token}`) return true;\n\ttry {\n\t\tconst url = new URL(request.url ?? \"/\", `http://${request.headers.host ?? \"localhost\"}`);\n\t\treturn url.searchParams.get(\"token\") === token;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst server = createServer((_request, response) => {\n\tresponse.writeHead(404);\n\tresponse.end(\"pi-daemon only serves WebSocket remote commander connections\\n\");\n});\n\nserver.on(\"upgrade\", (request, socket) => {\n\tconst netSocket = socket as Socket;\n\tif (!isAuthorized(request)) {\n\t\tnetSocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst key = request.headers[\"sec-websocket-key\"];\n\tif (typeof key !== \"string\") {\n\t\tnetSocket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst accept = createHash(\"sha1\").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(\"base64\");\n\tnetSocket.write(\n\t\t[\n\t\t\t\"HTTP/1.1 101 Switching Protocols\",\n\t\t\t\"Upgrade: websocket\",\n\t\t\t\"Connection: Upgrade\",\n\t\t\t`Sec-WebSocket-Accept: ${accept}`,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t].join(\"\\r\\n\"),\n\t);\n\tconst connection: ClientConnection = {\n\t\tsocket: netSocket,\n\t\tbuffer: Buffer.alloc(0),\n\t\texecs: new Map(),\n\t\tuploads: new Map(),\n\t\trequestControllers: new Set(),\n\t\tactiveRequests: 0,\n\t\tclosed: false,\n\t};\n\tnetSocket.on(\"error\", () => undefined);\n\tnetSocket.on(\"data\", (chunk: Buffer) => receiveSocketData(connection, chunk));\n\tnetSocket.on(\"close\", () => {\n\t\tconnection.closed = true;\n\t\tfor (const controller of connection.requestControllers) controller.abort();\n\t\tconnection.requestControllers.clear();\n\t\tfor (const child of connection.execs.values()) {\n\t\t\tchild.kill();\n\t\t}\n\t\tconnection.execs.clear();\n\t\tfor (const uploadId of Array.from(connection.uploads.keys())) {\n\t\t\tremoveUpload(connection, uploadId, true);\n\t\t}\n\t});\n});\n\nserver.listen(port, host, () => {\n\tconsole.log(`pi-daemon listening on ws://${host}:${port} cwd=${cwd}`);\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetagent/pi-daemon",
3
- "version": "0.1.4",
3
+ "version": "0.1.8",
4
4
  "description": "Remote commander daemon for Pi",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,5 +40,8 @@
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=22.19.0"
43
+ },
44
+ "devDependencies": {
45
+ "vitest": "3.2.7"
43
46
  }
44
47
  }