@earendil-works/pi-server 0.84.4 → 0.85.0
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 +2 -0
- package/README.md +66 -37
- package/dist/connection.d.ts +9 -5
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js.map +1 -1
- package/dist/errors.d.ts +16 -18
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +23 -27
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/listener.d.ts +1 -3
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js.map +1 -1
- package/dist/server.d.ts +16 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +268 -86
- package/dist/server.js.map +1 -1
- package/dist/session-router.d.ts +40 -0
- package/dist/session-router.d.ts.map +1 -0
- package/dist/session-router.js +264 -0
- package/dist/session-router.js.map +1 -0
- package/dist/testing/client.d.ts +6 -2
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +23 -3
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/host.d.ts +57 -0
- package/dist/testing/host.d.ts.map +1 -0
- package/dist/testing/host.js +189 -0
- package/dist/testing/host.js.map +1 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/server.d.ts +8 -7
- package/dist/testing/server.d.ts.map +1 -1
- package/dist/testing/server.js +7 -7
- package/dist/testing/server.js.map +1 -1
- package/dist/transports/unix/address.d.ts +3 -0
- package/dist/transports/unix/address.d.ts.map +1 -0
- package/dist/transports/unix/address.js +9 -0
- package/dist/transports/unix/address.js.map +1 -0
- package/dist/transports/unix/index.d.ts +1 -0
- package/dist/transports/unix/index.d.ts.map +1 -1
- package/dist/transports/unix/index.js +1 -0
- package/dist/transports/unix/index.js.map +1 -1
- package/dist/transports/unix/listener.d.ts +2 -3
- package/dist/transports/unix/listener.d.ts.map +1 -1
- package/dist/transports/unix/listener.js +13 -24
- package/dist/transports/unix/listener.js.map +1 -1
- package/dist/transports/unix/preset.d.ts +5 -4
- package/dist/transports/unix/preset.d.ts.map +1 -1
- package/dist/transports/unix/preset.js +5 -4
- package/dist/transports/unix/preset.js.map +1 -1
- package/dist/transports/unix/types.d.ts +2 -2
- package/dist/transports/unix/types.d.ts.map +1 -1
- package/dist/transports/unix/types.js.map +1 -1
- package/dist/types.d.ts +40 -47
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -3
- package/dist/protocol.d.ts +0 -22
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js +0 -266
- package/dist/protocol.js.map +0 -1
- package/dist/sessions.d.ts +0 -2174
- package/dist/sessions.d.ts.map +0 -1
- package/dist/sessions.js +0 -305
- package/dist/sessions.js.map +0 -1
- package/dist/snapshots.d.ts +0 -24
- package/dist/snapshots.d.ts.map +0 -1
- package/dist/snapshots.js +0 -39
- package/dist/snapshots.js.map +0 -1
- package/dist/testing/service.d.ts +0 -59
- package/dist/testing/service.d.ts.map +0 -1
- package/dist/testing/service.js +0 -240
- package/dist/testing/service.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+C,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,cAAc,EAA0B,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAStD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,SAAqB,GAAG,IAAI,CAK3F;AAoLD,gEAAgE;AAChE,qBAAa,kBAAmB,YAAW,cAAc;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAa;IAElC,YAAY,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAIlF;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBrC;IAED,KAAK,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B5C;IAED,UAAU,IAAI,IAAI,CAMjB;IAED,OAAO,CAAC,KAAK;CAsBb;AAgGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,gBAAgB,CAEjF","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { chmod, link, lstat, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { PiServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\nconst MAX_UNIX_SOCKET_PATH_BYTES = process.platform === \"linux\" ? 107 : 103;\n\nexport function validateUnixSocketPath(path: string, description = \"Unix socket path\"): void {\n\tif (!path) throw new TypeError(`${description} must not be empty`);\n\tif (Buffer.byteLength(path) > MAX_UNIX_SOCKET_PATH_BYTES) {\n\t\tthrow new TypeError(`${description} is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);\n\t}\n}\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements PiServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate boundPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tget address(): string | undefined {\n\t\treturn this.boundPath;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tvalidateUnixSocketPath(ownedBindPath, \"PiServer private Unix bind path\");\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tthis.boundPath = this.path;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tthis.boundPath = undefined;\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `.c-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `.p-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `.s-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): PiServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tvalidateUnixSocketPath(options.path, \"PiServer Unix socket path\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"PiServer Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`PiServer maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"PiServer maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`PiServer gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+C,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,cAAc,EAA0B,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAqLtD,gEAAgE;AAChE,qBAAa,kBAAmB,YAAW,cAAc;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAa;IAElC,YAAY,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAIlF;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBrC;IAED,KAAK,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B5C;IAED,UAAU,IAAI,IAAI,CAMjB;IAED,OAAO,CAAC,KAAK;CAsBb;AAgGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAE/E","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { chmod, link, lstat, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { ServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements ServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tawait removePath(ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\t// Remove an unpublished startup bind path before the public route.\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `cleanup-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `bind-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `stale-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): ServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tif (!options.path) throw new TypeError(\"Server Unix socket path must not be empty\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"Server Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"Server maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`Server gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
|
|
@@ -8,14 +8,6 @@ const DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;
|
|
|
8
8
|
const MAX_UINT32 = 0xffff_ffff;
|
|
9
9
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
10
10
|
const SOCKET_PROBE_TIMEOUT_MS = 1_000;
|
|
11
|
-
const MAX_UNIX_SOCKET_PATH_BYTES = process.platform === "linux" ? 107 : 103;
|
|
12
|
-
export function validateUnixSocketPath(path, description = "Unix socket path") {
|
|
13
|
-
if (!path)
|
|
14
|
-
throw new TypeError(`${description} must not be empty`);
|
|
15
|
-
if (Buffer.byteLength(path) > MAX_UNIX_SOCKET_PATH_BYTES) {
|
|
16
|
-
throw new TypeError(`${description} is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
11
|
class UnixListener {
|
|
20
12
|
options;
|
|
21
13
|
path;
|
|
@@ -24,7 +16,6 @@ class UnixListener {
|
|
|
24
16
|
server;
|
|
25
17
|
socketIdentity;
|
|
26
18
|
ownedBindPath;
|
|
27
|
-
boundPath;
|
|
28
19
|
closing = false;
|
|
29
20
|
closePromise;
|
|
30
21
|
accept;
|
|
@@ -33,9 +24,6 @@ class UnixListener {
|
|
|
33
24
|
this.path = this.options.path;
|
|
34
25
|
this.mode = this.options.mode;
|
|
35
26
|
}
|
|
36
|
-
get address() {
|
|
37
|
-
return this.boundPath;
|
|
38
|
-
}
|
|
39
27
|
async start(accept) {
|
|
40
28
|
if (this.server)
|
|
41
29
|
throw new Error("Unix listener is already started");
|
|
@@ -43,7 +31,6 @@ class UnixListener {
|
|
|
43
31
|
throw new Error("Unix listener is closing or closed");
|
|
44
32
|
this.accept = accept;
|
|
45
33
|
const ownedBindPath = getOwnedBindPath(this.path);
|
|
46
|
-
validateUnixSocketPath(ownedBindPath, "PiServer private Unix bind path");
|
|
47
34
|
await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
48
35
|
await removeStaleSocket(this.path);
|
|
49
36
|
await removeStaleSocket(ownedBindPath);
|
|
@@ -71,7 +58,8 @@ class UnixListener {
|
|
|
71
58
|
this.socketIdentity = { dev: stats.dev, ino: stats.ino };
|
|
72
59
|
await link(ownedBindPath, this.path);
|
|
73
60
|
await setSocketMode(this.path, this.mode);
|
|
74
|
-
|
|
61
|
+
await removePath(ownedBindPath);
|
|
62
|
+
this.ownedBindPath = undefined;
|
|
75
63
|
}
|
|
76
64
|
catch (error) {
|
|
77
65
|
await this.closeServerAndCleanup(server);
|
|
@@ -113,7 +101,6 @@ class UnixListener {
|
|
|
113
101
|
});
|
|
114
102
|
}
|
|
115
103
|
async closeInternal() {
|
|
116
|
-
this.boundPath = undefined;
|
|
117
104
|
const serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();
|
|
118
105
|
await Promise.all([...this.connections].map((connection) => connection.close()));
|
|
119
106
|
await serverClosed;
|
|
@@ -128,10 +115,11 @@ class UnixListener {
|
|
|
128
115
|
await closeNetServer(server, (error) => this.reportError(error));
|
|
129
116
|
}
|
|
130
117
|
finally {
|
|
131
|
-
|
|
118
|
+
// Remove an unpublished startup bind path before the public route.
|
|
132
119
|
if (this.ownedBindPath)
|
|
133
120
|
await removePath(this.ownedBindPath);
|
|
134
121
|
this.ownedBindPath = undefined;
|
|
122
|
+
await this.cleanupOwnedSocket();
|
|
135
123
|
}
|
|
136
124
|
}
|
|
137
125
|
async cleanupOwnedSocket() {
|
|
@@ -150,7 +138,7 @@ class UnixListener {
|
|
|
150
138
|
}
|
|
151
139
|
if (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino)
|
|
152
140
|
return;
|
|
153
|
-
const preserved = join(dirname(this.path),
|
|
141
|
+
const preserved = join(dirname(this.path), `cleanup-${randomUUID().slice(0, 6)}`);
|
|
154
142
|
try {
|
|
155
143
|
await rename(this.path, preserved);
|
|
156
144
|
}
|
|
@@ -294,7 +282,7 @@ export class UnixByteConnection {
|
|
|
294
282
|
}
|
|
295
283
|
function getOwnedBindPath(path) {
|
|
296
284
|
const suffix = createHash("sha256").update(path).digest("hex").slice(0, 8);
|
|
297
|
-
return join(dirname(path),
|
|
285
|
+
return join(dirname(path), `bind-${suffix}`);
|
|
298
286
|
}
|
|
299
287
|
async function removeStaleSocket(path) {
|
|
300
288
|
let original;
|
|
@@ -310,7 +298,7 @@ async function removeStaleSocket(path) {
|
|
|
310
298
|
throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);
|
|
311
299
|
if (await isSocketLive(path))
|
|
312
300
|
throw new Error(`Unix listener is already running: ${path}`);
|
|
313
|
-
const preserved = join(dirname(path),
|
|
301
|
+
const preserved = join(dirname(path), `stale-${randomUUID().slice(0, 6)}`);
|
|
314
302
|
try {
|
|
315
303
|
await rename(path, preserved);
|
|
316
304
|
}
|
|
@@ -402,24 +390,25 @@ export function createUnixListener(options) {
|
|
|
402
390
|
return new UnixListener(options);
|
|
403
391
|
}
|
|
404
392
|
function resolveUnixListenerOptions(options) {
|
|
405
|
-
|
|
393
|
+
if (!options.path)
|
|
394
|
+
throw new TypeError("Server Unix socket path must not be empty");
|
|
406
395
|
const mode = options.mode ?? DEFAULT_SOCKET_MODE;
|
|
407
396
|
if (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {
|
|
408
|
-
throw new TypeError("
|
|
397
|
+
throw new TypeError("Server Unix socket mode must be an integer between 0 and 0o777");
|
|
409
398
|
}
|
|
410
399
|
const maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;
|
|
411
400
|
if (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {
|
|
412
|
-
throw new TypeError(`
|
|
401
|
+
throw new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);
|
|
413
402
|
}
|
|
414
403
|
const maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;
|
|
415
404
|
if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {
|
|
416
|
-
throw new TypeError("
|
|
405
|
+
throw new TypeError("Server maxPendingBytes must be a safe integer at least maxFrameLength + 4");
|
|
417
406
|
}
|
|
418
407
|
const gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;
|
|
419
408
|
if (!Number.isSafeInteger(gracefulCloseTimeoutMs) ||
|
|
420
409
|
gracefulCloseTimeoutMs <= 0 ||
|
|
421
410
|
gracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS) {
|
|
422
|
-
throw new TypeError(`
|
|
411
|
+
throw new TypeError(`Server gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);
|
|
423
412
|
}
|
|
424
413
|
return {
|
|
425
414
|
path: options.path,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listener.js","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { chmod, link, lstat, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { PiServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\nconst MAX_UNIX_SOCKET_PATH_BYTES = process.platform === \"linux\" ? 107 : 103;\n\nexport function validateUnixSocketPath(path: string, description = \"Unix socket path\"): void {\n\tif (!path) throw new TypeError(`${description} must not be empty`);\n\tif (Buffer.byteLength(path) > MAX_UNIX_SOCKET_PATH_BYTES) {\n\t\tthrow new TypeError(`${description} is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);\n\t}\n}\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements PiServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate boundPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tget address(): string | undefined {\n\t\treturn this.boundPath;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tvalidateUnixSocketPath(ownedBindPath, \"PiServer private Unix bind path\");\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tthis.boundPath = this.path;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tthis.boundPath = undefined;\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `.c-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `.p-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `.s-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): PiServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tvalidateUnixSocketPath(options.path, \"PiServer Unix socket path\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"PiServer Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`PiServer maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"PiServer maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`PiServer gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"listener.js","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { chmod, link, lstat, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_MAX_FRAME_LENGTH } from \"@earendil-works/pi-protocol\";\nimport type { ByteConnection, ByteConnectionAcceptor } from \"../../connection.ts\";\nimport type { ServerListener } from \"../../listener.ts\";\nimport type { UnixListenerOptions } from \"./types.ts\";\n\nconst DEFAULT_SOCKET_MODE = 0o600;\nconst DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\nconst SOCKET_PROBE_TIMEOUT_MS = 1_000;\n\ninterface ResolvedUnixListenerOptions {\n\tpath: string;\n\tmode: number;\n\tgracefulCloseTimeoutMs: number;\n\tmaxPendingBytes: number;\n\tonError?: (error: Error) => void;\n}\n\ninterface FileIdentity {\n\tdev: number;\n\tino: number;\n}\nclass UnixListener implements ServerListener {\n\tprivate readonly options: ResolvedUnixListenerOptions;\n\tprivate readonly path: string;\n\tprivate readonly mode: number;\n\tprivate readonly connections = new Set<UnixByteConnection>();\n\tprivate server?: Server;\n\tprivate socketIdentity?: FileIdentity;\n\tprivate ownedBindPath?: string;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate accept?: ByteConnectionAcceptor;\n\n\tconstructor(options: UnixListenerOptions) {\n\t\tthis.options = resolveUnixListenerOptions(options);\n\t\tthis.path = this.options.path;\n\t\tthis.mode = this.options.mode;\n\t}\n\n\tasync start(accept: ByteConnectionAcceptor): Promise<void> {\n\t\tif (this.server) throw new Error(\"Unix listener is already started\");\n\t\tif (this.closing) throw new Error(\"Unix listener is closing or closed\");\n\t\tthis.accept = accept;\n\n\t\tconst ownedBindPath = getOwnedBindPath(this.path);\n\t\tawait mkdir(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\tawait removeStaleSocket(this.path);\n\t\tawait removeStaleSocket(ownedBindPath);\n\t\tthis.ownedBindPath = ownedBindPath;\n\t\tconst server = createServer((socket) => this.acceptSocket(socket));\n\t\tserver.on(\"error\", (error) => this.reportError(error));\n\t\tthis.server = server;\n\t\ttry {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tconst onError = (error: Error): void => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = (): void => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(ownedBindPath);\n\t\t\t});\n\t\t\tconst stats = await lstat(ownedBindPath);\n\t\t\tif (!stats.isSocket()) throw new Error(`Unix listener path is not a socket after binding: ${ownedBindPath}`);\n\t\t\tthis.socketIdentity = { dev: stats.dev, ino: stats.ino };\n\t\t\tawait link(ownedBindPath, this.path);\n\t\t\tawait setSocketMode(this.path, this.mode);\n\t\t\tawait removePath(ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t} catch (error) {\n\t\t\tawait this.closeServerAndCleanup(server);\n\t\t\tthis.server = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate acceptSocket(socket: Socket): void {\n\t\tif (this.closing) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst connection = new UnixByteConnection(\n\t\t\tsocket,\n\t\t\tthis.options.gracefulCloseTimeoutMs,\n\t\t\tthis.options.maxPendingBytes,\n\t\t);\n\t\tthis.connections.add(connection);\n\t\tconst accept = this.accept;\n\t\tif (!accept) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst handler = accept(connection);\n\t\tsocket.on(\"data\", (chunk) => {\n\t\t\thandler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t\t});\n\t\tsocket.on(\"error\", (error) => {\n\t\t\thandler.onError(error);\n\t\t\tsocket.destroy();\n\t\t});\n\t\tsocket.once(\"close\", () => {\n\t\t\tconnection.markClosed();\n\t\t\tthis.connections.delete(connection);\n\t\t\thandler.onClose();\n\t\t});\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tconst serverClosed = this.server ? this.closeServerAndCleanup(this.server) : this.cleanupOwnedSocket();\n\t\tawait Promise.all([...this.connections].map((connection) => connection.close()));\n\t\tawait serverClosed;\n\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\tthis.ownedBindPath = undefined;\n\t\tthis.connections.clear();\n\t\tthis.server = undefined;\n\t}\n\n\tprivate async closeServerAndCleanup(server: Server): Promise<void> {\n\t\ttry {\n\t\t\tawait closeNetServer(server, (error) => this.reportError(error));\n\t\t} finally {\n\t\t\t// Remove an unpublished startup bind path before the public route.\n\t\t\tif (this.ownedBindPath) await removePath(this.ownedBindPath);\n\t\t\tthis.ownedBindPath = undefined;\n\t\t\tawait this.cleanupOwnedSocket();\n\t\t}\n\t}\n\n\tprivate async cleanupOwnedSocket(): Promise<void> {\n\t\tconst identity = this.socketIdentity;\n\t\tthis.socketIdentity = undefined;\n\t\tif (!identity) return;\n\t\tlet current: Stats;\n\t\ttry {\n\t\t\tcurrent = await lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tif (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino) return;\n\n\t\tconst preserved = join(dirname(this.path), `cleanup-${randomUUID().slice(0, 6)}`);\n\t\ttry {\n\t\t\tawait rename(this.path, preserved);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\t\tthrow error;\n\t\t}\n\t\tconst moved = await lstat(preserved);\n\t\tif (moved.isSocket() && moved.dev === identity.dev && moved.ino === identity.ino) {\n\t\t\tawait removePath(preserved);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait lstat(this.path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, this.path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed during cleanup; preserved replacement at ${preserved}`);\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.options.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect listener state.\n\t\t}\n\t}\n}\n\n/** @internal Exported only for transport-level verification. */\nexport class UnixByteConnection implements ByteConnection {\n\tprivate readonly socket: Socket;\n\tprivate readonly gracefulCloseTimeoutMs: number;\n\tprivate readonly maxPendingBytes: number;\n\tprivate pendingBytes = 0;\n\tprivate closedValue = false;\n\tprivate closing = false;\n\tprivate writeTail: Promise<void> = Promise.resolve();\n\tprivate closePromise?: Promise<void>;\n\tprivate resolveClose?: () => void;\n\n\tconstructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number) {\n\t\tthis.socket = socket;\n\t\tthis.gracefulCloseTimeoutMs = gracefulCloseTimeoutMs;\n\t\tthis.maxPendingBytes = maxPendingBytes;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array)) {\n\t\t\treturn Promise.reject(new TypeError(\"Unix connection chunks must be Uint8Array\"));\n\t\t}\n\t\tif (this.closedValue || this.closing) return Promise.reject(new Error(\"Unix connection is closed\"));\n\t\tif (this.pendingBytes + chunk.byteLength > this.maxPendingBytes) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.pendingBytes += chunk.byteLength;\n\t\tconst bytes = chunk.slice();\n\t\tconst write = this.writeTail.then(() => this.write(bytes));\n\t\tconst tracked = write.finally(() => {\n\t\t\tthis.pendingBytes -= bytes.byteLength;\n\t\t});\n\t\tthis.writeTail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.socket.destroyed) {\n\t\t\tthis.markClosed();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tconst finalBytes = finalChunk?.slice();\n\t\tthis.closePromise = new Promise<void>((resolve) => {\n\t\t\tthis.resolveClose = resolve;\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (!this.socket.destroyed) this.socket.destroy();\n\t\t\t\tthis.markClosed();\n\t\t\t}, this.gracefulCloseTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tthis.socket.once(\"close\", () => clearTimeout(timer));\n\t\t\tvoid this.writeTail.then(() => {\n\t\t\t\tif (this.socket.destroyed) {\n\t\t\t\t\tthis.markClosed();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (finalBytes) this.socket.end(finalBytes);\n\t\t\t\t\telse this.socket.end();\n\t\t\t\t} catch {\n\t\t\t\t\tthis.socket.destroy();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closing = true;\n\t\tthis.resolveClose?.();\n\t\tthis.resolveClose = undefined;\n\t}\n\n\tprivate write(chunk: Uint8Array): Promise<void> {\n\t\tif (this.closedValue || this.closing || !this.socket.writable) {\n\t\t\treturn Promise.reject(new Error(\"Unix connection is closed\"));\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst onClose = (): void => finish(new Error(\"Unix connection closed during write\"));\n\t\t\tconst finish = (error?: Error | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.socket.off(\"close\", onClose);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tthis.socket.once(\"close\", onClose);\n\t\t\ttry {\n\t\t\t\tthis.socket.write(chunk, finish);\n\t\t\t} catch (error) {\n\t\t\t\tfinish(error instanceof Error ? error : new Error(String(error)));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getOwnedBindPath(path: string): string {\n\tconst suffix = createHash(\"sha256\").update(path).digest(\"hex\").slice(0, 8);\n\treturn join(dirname(path), `bind-${suffix}`);\n}\n\nasync function removeStaleSocket(path: string): Promise<void> {\n\tlet original: Stats;\n\ttry {\n\t\toriginal = await lstat(path);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tif (!original.isSocket()) throw new Error(`Refusing to remove non-socket Unix listener path: ${path}`);\n\tif (await isSocketLive(path)) throw new Error(`Unix listener is already running: ${path}`);\n\n\tconst preserved = join(dirname(path), `stale-${randomUUID().slice(0, 6)}`);\n\ttry {\n\t\tawait rename(path, preserved);\n\t} catch (error) {\n\t\tif (isErrorCode(error, \"ENOENT\")) return;\n\t\tthrow error;\n\t}\n\tconst current = await lstat(preserved);\n\tif (!current.isSocket() || current.dev !== original.dev || current.ino !== original.ino) {\n\t\ttry {\n\t\t\tawait lstat(path);\n\t\t} catch (error) {\n\t\t\tif (isErrorCode(error, \"ENOENT\")) await rename(preserved, path);\n\t\t\telse throw error;\n\t\t}\n\t\tthrow new Error(`Unix listener path changed while checking for a stale socket: ${path}`);\n\t}\n\tawait removePath(preserved);\n}\n\nasync function removePath(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOENT\")) throw error;\n\t}\n}\n\nfunction isSocketLive(path: string): Promise<boolean> {\n\treturn new Promise<boolean>((resolve, reject) => {\n\t\tconst socket = createConnection(path);\n\t\tlet settled = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst finish = (result: boolean, error?: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(result);\n\t\t};\n\t\tsocket.once(\"connect\", () => finish(true));\n\t\tsocket.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tif ([\"ECONNREFUSED\", \"ENOENT\", \"EPIPE\", \"ECONNRESET\"].includes(error.code ?? \"\")) {\n\t\t\t\tfinish(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(false, error);\n\t\t});\n\t\ttimer = setTimeout(() => finish(true), SOCKET_PROBE_TIMEOUT_MS);\n\t\ttimer.unref();\n\t});\n}\n\nasync function setSocketMode(path: string, mode: number): Promise<void> {\n\tif (process.platform === \"win32\") return;\n\ttry {\n\t\tawait chmod(path, mode);\n\t} catch (error) {\n\t\tif (!isErrorCode(error, \"ENOSYS\") && !isErrorCode(error, \"ENOTSUP\")) throw error;\n\t}\n}\n\nfunction closeNetServer(server: Server, reportError: (error: Error) => void): Promise<void> {\n\tif (!server.listening) return Promise.resolve();\n\treturn new Promise<void>((resolve) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) reportError(error);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nexport function createUnixListener(options: UnixListenerOptions): ServerListener {\n\treturn new UnixListener(options);\n}\n\nfunction resolveUnixListenerOptions(options: UnixListenerOptions): ResolvedUnixListenerOptions {\n\tif (!options.path) throw new TypeError(\"Server Unix socket path must not be empty\");\n\tconst mode = options.mode ?? DEFAULT_SOCKET_MODE;\n\tif (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {\n\t\tthrow new TypeError(\"Server Unix socket mode must be an integer between 0 and 0o777\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst maxPendingBytes = options.maxPendingBytes ?? maxFrameLength * 4;\n\tif (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes < maxFrameLength + 4) {\n\t\tthrow new TypeError(\"Server maxPendingBytes must be a safe integer at least maxFrameLength + 4\");\n\t}\n\tconst gracefulCloseTimeoutMs = options.gracefulCloseTimeoutMs ?? DEFAULT_GRACEFUL_CLOSE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(gracefulCloseTimeoutMs) ||\n\t\tgracefulCloseTimeoutMs <= 0 ||\n\t\tgracefulCloseTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`Server gracefulCloseTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn {\n\t\tpath: options.path,\n\t\tmode,\n\t\tmaxPendingBytes,\n\t\tgracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t};\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { SessionMetadata } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { Server } from "../../server.ts";
|
|
3
|
+
import type { ServerHost } from "../../types.ts";
|
|
3
4
|
import type { UnixServerOptions } from "./types.ts";
|
|
4
|
-
/** Compose
|
|
5
|
-
export declare function createUnixServer(
|
|
5
|
+
/** Compose Server with one Unix-domain socket listener. */
|
|
6
|
+
export declare function createUnixServer<TMetadata extends SessionMetadata>(host: ServerHost<TMetadata>, options: UnixServerOptions): Server<TMetadata>;
|
|
6
7
|
//# sourceMappingURL=preset.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,eAAe,EACjE,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,OAAO,EAAE,iBAAiB,GACxB,MAAM,CAAC,SAAS,CAAC,CAiBnB","sourcesContent":["import type { SessionMetadata } from \"@earendil-works/pi-agent-core\";\nimport { Server } from \"../../server.ts\";\nimport type { ServerHost } from \"../../types.ts\";\nimport { createUnixListener } from \"./listener.ts\";\nimport type { UnixServerOptions } from \"./types.ts\";\n\n/** Compose Server with one Unix-domain socket listener. */\nexport function createUnixServer<TMetadata extends SessionMetadata>(\n\thost: ServerHost<TMetadata>,\n\toptions: UnixServerOptions,\n): Server<TMetadata> {\n\tconst listener = createUnixListener({\n\t\tpath: options.path,\n\t\tmode: options.mode,\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\tmaxPendingBytes: options.maxPendingBytes,\n\t\tgracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t});\n\treturn new Server(host, {\n\t\tlisteners: [listener],\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\tonConnectionCountChanged: options.onConnectionCountChanged,\n\t\tserverId: options.serverId,\n\t\tonError: options.onError,\n\t});\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Server } from "../../server.js";
|
|
2
2
|
import { createUnixListener } from "./listener.js";
|
|
3
|
-
/** Compose
|
|
4
|
-
export function createUnixServer(
|
|
3
|
+
/** Compose Server with one Unix-domain socket listener. */
|
|
4
|
+
export function createUnixServer(host, options) {
|
|
5
5
|
const listener = createUnixListener({
|
|
6
6
|
path: options.path,
|
|
7
7
|
mode: options.mode,
|
|
@@ -10,10 +10,11 @@ export function createUnixServer(service, options) {
|
|
|
10
10
|
gracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,
|
|
11
11
|
onError: options.onError,
|
|
12
12
|
});
|
|
13
|
-
return new
|
|
13
|
+
return new Server(host, {
|
|
14
14
|
listeners: [listener],
|
|
15
15
|
maxFrameLength: options.maxFrameLength,
|
|
16
16
|
handshakeTimeoutMs: options.handshakeTimeoutMs,
|
|
17
|
+
onConnectionCountChanged: options.onConnectionCountChanged,
|
|
17
18
|
serverId: options.serverId,
|
|
18
19
|
onError: options.onError,
|
|
19
20
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preset.js","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preset.js","sourceRoot":"","sources":["../../../src/transports/unix/preset.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,2DAA2D;AAC3D,MAAM,UAAU,gBAAgB,CAC/B,IAA2B,EAC3B,OAA0B,EACN;IACpB,MAAM,QAAQ,GAAG,kBAAkB,CAAC;QACnC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,sBAAsB,EAAE,OAAO,CAAC,sBAAsB;QACtD,OAAO,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;IACH,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE;QACvB,SAAS,EAAE,CAAC,QAAQ,CAAC;QACrB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,wBAAwB,EAAE,OAAO,CAAC,wBAAwB;QAC1D,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;AAAA,CACH","sourcesContent":["import type { SessionMetadata } from \"@earendil-works/pi-agent-core\";\nimport { Server } from \"../../server.ts\";\nimport type { ServerHost } from \"../../types.ts\";\nimport { createUnixListener } from \"./listener.ts\";\nimport type { UnixServerOptions } from \"./types.ts\";\n\n/** Compose Server with one Unix-domain socket listener. */\nexport function createUnixServer<TMetadata extends SessionMetadata>(\n\thost: ServerHost<TMetadata>,\n\toptions: UnixServerOptions,\n): Server<TMetadata> {\n\tconst listener = createUnixListener({\n\t\tpath: options.path,\n\t\tmode: options.mode,\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\tmaxPendingBytes: options.maxPendingBytes,\n\t\tgracefulCloseTimeoutMs: options.gracefulCloseTimeoutMs,\n\t\tonError: options.onError,\n\t});\n\treturn new Server(host, {\n\t\tlisteners: [listener],\n\t\tmaxFrameLength: options.maxFrameLength,\n\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\tonConnectionCountChanged: options.onConnectionCountChanged,\n\t\tserverId: options.serverId,\n\t\tonError: options.onError,\n\t});\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ServerOptions } from "../../types.ts";
|
|
2
2
|
export interface UnixListenerOptions {
|
|
3
3
|
path: string;
|
|
4
4
|
/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */
|
|
@@ -10,6 +10,6 @@ export interface UnixListenerOptions {
|
|
|
10
10
|
maxFrameLength?: number;
|
|
11
11
|
onError?: (error: Error) => void;
|
|
12
12
|
}
|
|
13
|
-
export interface UnixServerOptions extends Omit<
|
|
13
|
+
export interface UnixServerOptions extends Omit<ServerOptions, "listeners">, UnixListenerOptions {
|
|
14
14
|
}
|
|
15
15
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,EAAE,mBAAmB;CAAG","sourcesContent":["import type { ServerOptions } from \"../../types.ts\";\n\nexport interface UnixListenerOptions {\n\tpath: string;\n\t/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */\n\tmode?: number;\n\t/** Maximum framed bytes queued per connection before a slow peer is disconnected. */\n\tmaxPendingBytes?: number;\n\tgracefulCloseTimeoutMs?: number;\n\t/** Used to derive and validate maxPendingBytes. Must match the server when customized. */\n\tmaxFrameLength?: number;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UnixServerOptions extends Omit<ServerOptions, \"listeners\">, UnixListenerOptions {}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/transports/unix/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/transports/unix/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ServerOptions } from \"../../types.ts\";\n\nexport interface UnixListenerOptions {\n\tpath: string;\n\t/** Socket filesystem permissions. Defaults to owner read/write only (0o600). */\n\tmode?: number;\n\t/** Maximum framed bytes queued per connection before a slow peer is disconnected. */\n\tmaxPendingBytes?: number;\n\tgracefulCloseTimeoutMs?: number;\n\t/** Used to derive and validate maxPendingBytes. Must match the server when customized. */\n\tmaxFrameLength?: number;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UnixServerOptions extends Omit<ServerOptions, \"listeners\">, UnixListenerOptions {}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,56 +1,49 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
export interface
|
|
5
|
-
listeners: readonly
|
|
1
|
+
import type { JsonValue, ServiceCall, ServiceProviderUpdate } from "@earendil-works/chord";
|
|
2
|
+
import type { Context, SessionMetadata } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import type { ServerListener } from "./listener.ts";
|
|
4
|
+
export interface ServerOptions {
|
|
5
|
+
listeners: readonly ServerListener[];
|
|
6
|
+
/** Stable logical server identity supplied by the installation or profile. */
|
|
7
|
+
serverId: string;
|
|
6
8
|
maxFrameLength?: number;
|
|
7
9
|
handshakeTimeoutMs?: number;
|
|
8
|
-
|
|
10
|
+
onConnectionCountChanged?: (count: number) => void;
|
|
9
11
|
onError?: (error: Error) => void;
|
|
10
12
|
}
|
|
11
13
|
export type MaybePromise<T> = T | Promise<T>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}>, "command" | "sessionId">;
|
|
18
|
-
export interface CreateSessionOptions {
|
|
19
|
-
/** A collision-resistant ID assigned by PiServer. The service must persist this exact ID. */
|
|
20
|
-
id: string;
|
|
21
|
-
cwd?: string;
|
|
22
|
-
name?: string;
|
|
23
|
-
model?: ModelRef;
|
|
24
|
-
thinkingLevel?: ThinkingLevel;
|
|
14
|
+
/** One presentation connection's live capability for a hosted Session. */
|
|
15
|
+
export interface RoutedSessionAttachment {
|
|
16
|
+
/** Route one contract-agnostic service operation to the attached Session endpoint. */
|
|
17
|
+
invokeService(call: ServiceCall, publish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>, context: Context): Promise<JsonValue | undefined>;
|
|
18
|
+
release(context: Context): MaybePromise<void>;
|
|
25
19
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
type: "error";
|
|
33
|
-
error: PiServerError;
|
|
34
|
-
};
|
|
35
|
-
/** One acquired durable session. Conflicting operations must reject rather than queue. */
|
|
36
|
-
export interface PiSessionRuntime {
|
|
37
|
-
snapshot(): MaybePromise<SessionSnapshot>;
|
|
38
|
-
getPhase(): SessionPhase;
|
|
39
|
-
prompt(input: PromptInput): Promise<void>;
|
|
40
|
-
steer(input: SteerInput): Promise<void>;
|
|
41
|
-
abort(): Promise<void>;
|
|
42
|
-
setModel(model: ModelRef): Promise<void>;
|
|
43
|
-
setThinking(thinkingLevel: ThinkingLevel): Promise<void>;
|
|
44
|
-
subscribe(listener: (event: PiSessionRuntimeEvent) => void): () => void;
|
|
45
|
-
dispose(): Promise<void>;
|
|
20
|
+
/** Presentation-scoped routing capabilities available to server service implementations. */
|
|
21
|
+
export interface RoutedServerPresentation {
|
|
22
|
+
attachSession(sessionId: string, context: Context): Promise<void>;
|
|
23
|
+
detachSession(context: Context): Promise<void>;
|
|
24
|
+
/** Release routed attachments and handles before the application deletes durable metadata. */
|
|
25
|
+
prepareSessionRemoval(sessionId: string, context: Context): Promise<void>;
|
|
46
26
|
}
|
|
47
|
-
/**
|
|
48
|
-
export interface
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
27
|
+
/** One connection's server-scoped service endpoint. */
|
|
28
|
+
export interface RoutedServerServiceAttachment {
|
|
29
|
+
invokeService(call: ServiceCall, publish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>, context: Context): Promise<JsonValue | undefined>;
|
|
30
|
+
release(context: Context): MaybePromise<void>;
|
|
31
|
+
}
|
|
32
|
+
export interface RoutedServerServiceHost {
|
|
33
|
+
attachClient(presentation: RoutedServerPresentation, context: Context): MaybePromise<RoutedServerServiceAttachment>;
|
|
34
|
+
}
|
|
35
|
+
/** A process-safe handle that acquires presentation-scoped Session capabilities. */
|
|
36
|
+
export interface RoutedSessionHandle {
|
|
37
|
+
attachClient(context: Context): MaybePromise<RoutedSessionAttachment>;
|
|
38
|
+
/** Resolves with an error for unexpected termination, or undefined after an expected close. */
|
|
39
|
+
readonly terminated?: Promise<Error | undefined>;
|
|
40
|
+
close(context: Context): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/** Application capabilities used by server-wide management and Session routing. */
|
|
43
|
+
export interface ServerHost<TMetadata extends SessionMetadata = SessionMetadata> {
|
|
44
|
+
readonly serverServices: RoutedServerServiceHost;
|
|
45
|
+
/** Resolve one durable Session ID or throw a bounded routing error. */
|
|
46
|
+
resolveSession(sessionId: string, context: Context): Promise<TMetadata>;
|
|
47
|
+
openSession(metadata: TMetadata, context: Context): Promise<RoutedSessionHandle>;
|
|
53
48
|
}
|
|
54
|
-
export type SessionRuntime = PiSessionRuntime;
|
|
55
|
-
export type SessionRuntimeEvent = PiSessionRuntimeEvent;
|
|
56
49
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonValue, ServiceCall, ServiceProviderUpdate } from \"@earendil-works/chord\";\nimport type { Context, SessionMetadata } from \"@earendil-works/pi-agent-core\";\nimport type { ServerListener } from \"./listener.ts\";\n\nexport interface ServerOptions {\n\tlisteners: readonly ServerListener[];\n\t/** Stable logical server identity supplied by the installation or profile. */\n\tserverId: string;\n\tmaxFrameLength?: number;\n\thandshakeTimeoutMs?: number;\n\tonConnectionCountChanged?: (count: number) => void;\n\tonError?: (error: Error) => void;\n}\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n/** One presentation connection's live capability for a hosted Session. */\nexport interface RoutedSessionAttachment {\n\t/** Route one contract-agnostic service operation to the attached Session endpoint. */\n\tinvokeService(\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined>;\n\trelease(context: Context): MaybePromise<void>;\n}\n\n/** Presentation-scoped routing capabilities available to server service implementations. */\nexport interface RoutedServerPresentation {\n\tattachSession(sessionId: string, context: Context): Promise<void>;\n\tdetachSession(context: Context): Promise<void>;\n\t/** Release routed attachments and handles before the application deletes durable metadata. */\n\tprepareSessionRemoval(sessionId: string, context: Context): Promise<void>;\n}\n\n/** One connection's server-scoped service endpoint. */\nexport interface RoutedServerServiceAttachment {\n\tinvokeService(\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => MaybePromise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined>;\n\trelease(context: Context): MaybePromise<void>;\n}\n\nexport interface RoutedServerServiceHost {\n\tattachClient(presentation: RoutedServerPresentation, context: Context): MaybePromise<RoutedServerServiceAttachment>;\n}\n\n/** A process-safe handle that acquires presentation-scoped Session capabilities. */\nexport interface RoutedSessionHandle {\n\tattachClient(context: Context): MaybePromise<RoutedSessionAttachment>;\n\t/** Resolves with an error for unexpected termination, or undefined after an expected close. */\n\treadonly terminated?: Promise<Error | undefined>;\n\tclose(context: Context): Promise<void>;\n}\n\n/** Application capabilities used by server-wide management and Session routing. */\nexport interface ServerHost<TMetadata extends SessionMetadata = SessionMetadata> {\n\treadonly serverServices: RoutedServerServiceHost;\n\t/** Resolve one durable Session ID or throw a bounded routing error. */\n\tresolveSession(sessionId: string, context: Context): Promise<TMetadata>;\n\topenSession(metadata: TMetadata, context: Context): Promise<RoutedSessionHandle>;\n}\n"]}
|