@fleetagent/pi-daemon 0.1.4 → 0.1.7
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 +25 -0
- package/README.md +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +294 -79
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.7](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.6...@fleetagent/pi-daemon-v0.1.7) (2026-07-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **@fleetagent/pi-daemon:** Synchronize pi versions
|
|
9
|
+
|
|
10
|
+
## [0.1.6](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.5...@fleetagent/pi-daemon-v0.1.6) (2026-07-13)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Miscellaneous Chores
|
|
14
|
+
|
|
15
|
+
* **@fleetagent/pi-daemon:** Synchronize pi versions
|
|
16
|
+
|
|
17
|
+
## [0.1.5](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.4...@fleetagent/pi-daemon-v0.1.5) (2026-07-13)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* add subagents and harden runtime boundaries ([90c6e9e](https://github.com/fleetagent/pi/commit/90c6e9e72dfa72f893cab757dd615bb2bd37b461))
|
|
23
|
+
|
|
3
24
|
## [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
25
|
|
|
5
26
|
|
|
@@ -41,3 +62,7 @@
|
|
|
41
62
|
|
|
42
63
|
- Added streamed file upload and download methods to the daemon WebSocket protocol.
|
|
43
64
|
- Added the initial Pi remote commander daemon package.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- 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
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
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.
|
|
39
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
178
|
+
finish(undefined, Buffer.concat(stdout));
|
|
115
179
|
return;
|
|
116
180
|
}
|
|
117
|
-
|
|
181
|
+
finish(new Error(Buffer.concat(stderr).toString("utf-8").trim() || `${command} exited with code ${code}`));
|
|
118
182
|
});
|
|
119
183
|
});
|
|
120
184
|
}
|
|
121
|
-
function
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
283
|
-
connection.uploads.set(id,
|
|
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
|
|
290
|
-
if (!
|
|
416
|
+
const upload = connection.uploads.get(uploadId);
|
|
417
|
+
if (!upload)
|
|
291
418
|
throw new Error(`Unknown upload: ${uploadId}`);
|
|
292
|
-
|
|
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
|
|
299
|
-
if (!
|
|
431
|
+
const upload = removeUpload(connection, uploadId, false);
|
|
432
|
+
if (!upload)
|
|
300
433
|
throw new Error(`Unknown upload: ${uploadId}`);
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
481
|
-
|
|
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
|
|
490
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.7",
|
|
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
|
}
|