@metamask-previews/wallet-cli 0.0.0-preview-b2742a2fc → 0.0.0-preview-a8fd340
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 +1 -2
- package/dist/daemon/daemon-client.cjs +140 -0
- package/dist/daemon/daemon-client.cjs.map +1 -0
- package/dist/daemon/daemon-client.d.cts +79 -0
- package/dist/daemon/daemon-client.d.cts.map +1 -0
- package/dist/daemon/daemon-client.d.mts +79 -0
- package/dist/daemon/daemon-client.d.mts.map +1 -0
- package/dist/daemon/daemon-client.mjs +135 -0
- package/dist/daemon/daemon-client.mjs.map +1 -0
- package/dist/daemon/daemon-spawn.cjs +106 -0
- package/dist/daemon/daemon-spawn.cjs.map +1 -0
- package/dist/daemon/daemon-spawn.d.cts +28 -0
- package/dist/daemon/daemon-spawn.d.cts.map +1 -0
- package/dist/daemon/daemon-spawn.d.mts +28 -0
- package/dist/daemon/daemon-spawn.d.mts.map +1 -0
- package/dist/daemon/daemon-spawn.mjs +102 -0
- package/dist/daemon/daemon-spawn.mjs.map +1 -0
- package/dist/daemon/prompts.cjs +21 -0
- package/dist/daemon/prompts.cjs.map +1 -0
- package/dist/daemon/prompts.d.cts +11 -0
- package/dist/daemon/prompts.d.cts.map +1 -0
- package/dist/daemon/prompts.d.mts +11 -0
- package/dist/daemon/prompts.d.mts.map +1 -0
- package/dist/daemon/prompts.mjs +17 -0
- package/dist/daemon/prompts.mjs.map +1 -0
- package/dist/daemon/rpc-socket-server.cjs +271 -0
- package/dist/daemon/rpc-socket-server.cjs.map +1 -0
- package/dist/daemon/rpc-socket-server.d.cts +44 -0
- package/dist/daemon/rpc-socket-server.d.cts.map +1 -0
- package/dist/daemon/rpc-socket-server.d.mts +44 -0
- package/dist/daemon/rpc-socket-server.d.mts.map +1 -0
- package/dist/daemon/rpc-socket-server.mjs +267 -0
- package/dist/daemon/rpc-socket-server.mjs.map +1 -0
- package/dist/daemon/socket-line.cjs +89 -0
- package/dist/daemon/socket-line.cjs.map +1 -0
- package/dist/daemon/socket-line.d.cts +19 -0
- package/dist/daemon/socket-line.d.cts.map +1 -0
- package/dist/daemon/socket-line.d.mts +19 -0
- package/dist/daemon/socket-line.d.mts.map +1 -0
- package/dist/daemon/socket-line.mjs +84 -0
- package/dist/daemon/socket-line.mjs.map +1 -0
- package/dist/daemon/stop-daemon.cjs +103 -0
- package/dist/daemon/stop-daemon.cjs.map +1 -0
- package/dist/daemon/stop-daemon.d.cts +18 -0
- package/dist/daemon/stop-daemon.d.cts.map +1 -0
- package/dist/daemon/stop-daemon.d.mts +18 -0
- package/dist/daemon/stop-daemon.d.mts.map +1 -0
- package/dist/daemon/stop-daemon.mjs +99 -0
- package/dist/daemon/stop-daemon.mjs.map +1 -0
- package/dist/daemon/types.cjs.map +1 -1
- package/dist/daemon/types.d.cts +28 -0
- package/dist/daemon/types.d.cts.map +1 -1
- package/dist/daemon/types.d.mts +28 -0
- package/dist/daemon/types.d.mts.map +1 -1
- package/dist/daemon/types.mjs.map +1 -1
- package/dist/daemon/utils.cjs +109 -0
- package/dist/daemon/utils.cjs.map +1 -0
- package/dist/daemon/utils.d.cts +50 -0
- package/dist/daemon/utils.d.cts.map +1 -0
- package/dist/daemon/utils.d.mts +50 -0
- package/dist/daemon/utils.d.mts.map +1 -0
- package/dist/daemon/utils.mjs +101 -0
- package/dist/daemon/utils.mjs.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RpcHandlerMap } from "./types.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Handle returned by {@link startRpcSocketServer}.
|
|
4
|
+
*/
|
|
5
|
+
export type RpcSocketServerHandle = {
|
|
6
|
+
close: () => Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Options for {@link startRpcSocketServer}.
|
|
10
|
+
*/
|
|
11
|
+
export type StartRpcSocketServerOptions = {
|
|
12
|
+
/** The Unix socket path to listen on. */
|
|
13
|
+
socketPath: string;
|
|
14
|
+
/** Map of RPC method names to handler functions. */
|
|
15
|
+
handlers: RpcHandlerMap;
|
|
16
|
+
/** Callback invoked when a `shutdown` RPC is received. */
|
|
17
|
+
onShutdown?: (() => Promise<void>) | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Optional logger for server-side diagnostics (unexpected socket errors,
|
|
20
|
+
* unhandled handler rejections, `onShutdown` callback failures). Without
|
|
21
|
+
* this, failures fall back to `process.stderr.write`, which is discarded
|
|
22
|
+
* when the daemon is spawned with `stdio: 'ignore'`.
|
|
23
|
+
*/
|
|
24
|
+
log?: ((message: string) => void) | undefined;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Start a Unix socket server that processes JSON-RPC requests.
|
|
28
|
+
*
|
|
29
|
+
* Each connection reads one newline-delimited JSON-RPC request, processes it
|
|
30
|
+
* via the provided handler map, writes a JSON-RPC response, and closes.
|
|
31
|
+
*
|
|
32
|
+
* The special `shutdown` method is intercepted before handler dispatch and
|
|
33
|
+
* triggers the provided {@link StartRpcSocketServerOptions.onShutdown} callback
|
|
34
|
+
* after responding.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Server options.
|
|
37
|
+
* @param options.socketPath - The Unix socket path to listen on.
|
|
38
|
+
* @param options.handlers - Map of RPC method names to handler functions.
|
|
39
|
+
* @param options.onShutdown - Optional callback invoked when a `shutdown` RPC is received.
|
|
40
|
+
* @param options.log - Optional logger for server-side diagnostics.
|
|
41
|
+
* @returns A handle with a `close()` function for cleanup.
|
|
42
|
+
*/
|
|
43
|
+
export declare function startRpcSocketServer({ socketPath, handlers, onShutdown, log, }: StartRpcSocketServerOptions): Promise<RpcSocketServerHandle>;
|
|
44
|
+
//# sourceMappingURL=rpc-socket-server.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-socket-server.d.mts","sourceRoot":"","sources":["../../src/daemon/rpc-socket-server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAgB;AAK7C;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,QAAQ,EAAE,aAAa,CAAC;IACxB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IAC/C;;;;;OAKG;IACH,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC/C,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CAAC,EACzC,UAAU,EACV,QAAQ,EACR,UAAU,EACV,GAAG,GACJ,EAAE,2BAA2B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAqF9D"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { rpcErrors } from "@metamask/rpc-errors";
|
|
2
|
+
import { hasProperty, isJsonRpcRequest } from "@metamask/utils";
|
|
3
|
+
import { chmod, unlink } from "node:fs/promises";
|
|
4
|
+
import { createServer } from "node:net";
|
|
5
|
+
import { isErrorWithCode } from "./utils.mjs";
|
|
6
|
+
const CONNECTION_TIMEOUT_MS = 30000;
|
|
7
|
+
/**
|
|
8
|
+
* Start a Unix socket server that processes JSON-RPC requests.
|
|
9
|
+
*
|
|
10
|
+
* Each connection reads one newline-delimited JSON-RPC request, processes it
|
|
11
|
+
* via the provided handler map, writes a JSON-RPC response, and closes.
|
|
12
|
+
*
|
|
13
|
+
* The special `shutdown` method is intercepted before handler dispatch and
|
|
14
|
+
* triggers the provided {@link StartRpcSocketServerOptions.onShutdown} callback
|
|
15
|
+
* after responding.
|
|
16
|
+
*
|
|
17
|
+
* @param options - Server options.
|
|
18
|
+
* @param options.socketPath - The Unix socket path to listen on.
|
|
19
|
+
* @param options.handlers - Map of RPC method names to handler functions.
|
|
20
|
+
* @param options.onShutdown - Optional callback invoked when a `shutdown` RPC is received.
|
|
21
|
+
* @param options.log - Optional logger for server-side diagnostics.
|
|
22
|
+
* @returns A handle with a `close()` function for cleanup.
|
|
23
|
+
*/
|
|
24
|
+
export async function startRpcSocketServer({ socketPath, handlers, onShutdown, log, }) {
|
|
25
|
+
const logFn = log ?? defaultLog;
|
|
26
|
+
const server = createServer((socket) => {
|
|
27
|
+
let buffer = '';
|
|
28
|
+
// Destroy connections that never send a complete request line. `unref` so
|
|
29
|
+
// the timer alone cannot keep the event loop alive at shutdown.
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
socket.destroy();
|
|
32
|
+
}, CONNECTION_TIMEOUT_MS);
|
|
33
|
+
timer.unref();
|
|
34
|
+
/**
|
|
35
|
+
* Clear the idle-connection timer. Called from data, close, and error
|
|
36
|
+
* paths so the timer never outlives the connection itself.
|
|
37
|
+
*/
|
|
38
|
+
const clearIdleTimer = () => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
};
|
|
41
|
+
const onData = (data) => {
|
|
42
|
+
buffer += data.toString();
|
|
43
|
+
const idx = buffer.indexOf('\n');
|
|
44
|
+
if (idx === -1) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
clearIdleTimer();
|
|
48
|
+
// One request per connection.
|
|
49
|
+
socket.removeListener('data', onData);
|
|
50
|
+
const line = buffer.slice(0, idx);
|
|
51
|
+
const remaining = buffer.slice(idx + 1);
|
|
52
|
+
buffer = '';
|
|
53
|
+
if (remaining.length > 0) {
|
|
54
|
+
socket.end(`${JSON.stringify({
|
|
55
|
+
jsonrpc: '2.0',
|
|
56
|
+
error: rpcErrors
|
|
57
|
+
.invalidRequest({
|
|
58
|
+
message: 'Only one request per connection is allowed',
|
|
59
|
+
})
|
|
60
|
+
.serialize(),
|
|
61
|
+
})}\n`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
handleRequest(handlers, line, onShutdown, logFn)
|
|
65
|
+
.then((response) => {
|
|
66
|
+
socket.end(`${JSON.stringify(response)}\n`);
|
|
67
|
+
return undefined;
|
|
68
|
+
})
|
|
69
|
+
.catch((dispatchError) => {
|
|
70
|
+
logFn(`Unhandled RPC dispatch error: ${String(dispatchError)}`);
|
|
71
|
+
socket.end(`${JSON.stringify({
|
|
72
|
+
jsonrpc: '2.0',
|
|
73
|
+
error: rpcErrors
|
|
74
|
+
.internal({ message: 'Internal error' })
|
|
75
|
+
.serialize(),
|
|
76
|
+
})}\n`);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
socket.on('data', onData);
|
|
80
|
+
socket.once('close', clearIdleTimer);
|
|
81
|
+
socket.on('error', (socketError) => {
|
|
82
|
+
clearIdleTimer();
|
|
83
|
+
const { code } = socketError;
|
|
84
|
+
if (code === 'EPIPE' || code === 'ECONNRESET') {
|
|
85
|
+
return; // Expected during probe/disconnect.
|
|
86
|
+
}
|
|
87
|
+
logFn(`Unexpected socket error: ${String(socketError)}`);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
await listen(server, socketPath);
|
|
91
|
+
return {
|
|
92
|
+
close: async () => closeServer(server),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Default fallback logger: writes to stderr. Daemons spawned with
|
|
97
|
+
* `stdio: 'ignore'` should always pass an explicit `log`.
|
|
98
|
+
*
|
|
99
|
+
* @param message - The message to log.
|
|
100
|
+
*/
|
|
101
|
+
function defaultLog(message) {
|
|
102
|
+
process.stderr.write(`${message}\n`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Handle a single JSON-RPC request line, intercepting the `shutdown` method.
|
|
106
|
+
*
|
|
107
|
+
* @param handlers - The RPC handler map.
|
|
108
|
+
* @param line - The raw JSON line from the socket.
|
|
109
|
+
* @param onShutdown - Optional shutdown callback.
|
|
110
|
+
* @param log - Logger for diagnostic messages.
|
|
111
|
+
* @returns A JSON-RPC response object.
|
|
112
|
+
*/
|
|
113
|
+
async function handleRequest(handlers, line, onShutdown, log) {
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(line);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return {
|
|
120
|
+
jsonrpc: '2.0',
|
|
121
|
+
id: null,
|
|
122
|
+
error: rpcErrors.parse({ message: 'Parse error' }).serialize(),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (!isJsonRpcRequest(parsed)) {
|
|
126
|
+
const id = typeof parsed === 'object' &&
|
|
127
|
+
parsed !== null &&
|
|
128
|
+
hasProperty(parsed, 'id') &&
|
|
129
|
+
isValidJsonRpcId(parsed.id)
|
|
130
|
+
? parsed.id
|
|
131
|
+
: null;
|
|
132
|
+
return {
|
|
133
|
+
jsonrpc: '2.0',
|
|
134
|
+
id,
|
|
135
|
+
error: rpcErrors
|
|
136
|
+
.invalidRequest({ message: 'Invalid JSON-RPC request' })
|
|
137
|
+
.serialize(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const { id, method, params } = parsed;
|
|
141
|
+
try {
|
|
142
|
+
if (method === 'shutdown') {
|
|
143
|
+
if (onShutdown) {
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
onShutdown().catch((error) => {
|
|
146
|
+
log(`onShutdown callback failed: ${String(error)}`);
|
|
147
|
+
});
|
|
148
|
+
}, 0);
|
|
149
|
+
}
|
|
150
|
+
return { jsonrpc: '2.0', id, result: { status: 'shutting down' } };
|
|
151
|
+
}
|
|
152
|
+
const handler = Object.prototype.hasOwnProperty.call(handlers, method)
|
|
153
|
+
? handlers[method]
|
|
154
|
+
: undefined;
|
|
155
|
+
if (!handler) {
|
|
156
|
+
return {
|
|
157
|
+
jsonrpc: '2.0',
|
|
158
|
+
id,
|
|
159
|
+
error: rpcErrors
|
|
160
|
+
.methodNotFound({ message: `Method not found: ${method}` })
|
|
161
|
+
.serialize(),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const result = await handler(coerceHandlerParams(params));
|
|
165
|
+
return { jsonrpc: '2.0', id, result: result ?? null };
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
log(`RPC handler "${method}" failed: ${String(error)}`);
|
|
169
|
+
if (isRpcError(error)) {
|
|
170
|
+
return { jsonrpc: '2.0', id, error };
|
|
171
|
+
}
|
|
172
|
+
const message = error instanceof Error ? error.message : 'Internal error';
|
|
173
|
+
return {
|
|
174
|
+
jsonrpc: '2.0',
|
|
175
|
+
id,
|
|
176
|
+
error: rpcErrors.internal({ message }).serialize(),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Narrow `params` to the shape handlers expect. JSON-RPC 2.0 requires
|
|
182
|
+
* `params`, when present, to be an array or object; both are valid `Json`.
|
|
183
|
+
*
|
|
184
|
+
* @param params - The validated `params` field from a JSON-RPC request.
|
|
185
|
+
* @returns The same value, or `null` when absent.
|
|
186
|
+
*/
|
|
187
|
+
function coerceHandlerParams(params) {
|
|
188
|
+
return params ?? null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Per JSON-RPC 2.0, `id` must be a string, number, or null. Used when
|
|
192
|
+
* salvaging an `id` from a parse-success-but-not-valid-request payload.
|
|
193
|
+
*
|
|
194
|
+
* @param value - The candidate id.
|
|
195
|
+
* @returns True if the value is an acceptable JSON-RPC id.
|
|
196
|
+
*/
|
|
197
|
+
function isValidJsonRpcId(value) {
|
|
198
|
+
return (value === null || typeof value === 'string' || typeof value === 'number');
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Check if an error is an RPC error with a numeric code.
|
|
202
|
+
*
|
|
203
|
+
* @param error - The error to check.
|
|
204
|
+
* @returns True if the error has a numeric code property.
|
|
205
|
+
*/
|
|
206
|
+
function isRpcError(error) {
|
|
207
|
+
return (typeof error === 'object' &&
|
|
208
|
+
error !== null &&
|
|
209
|
+
hasProperty(error, 'code') &&
|
|
210
|
+
typeof error.code === 'number' &&
|
|
211
|
+
hasProperty(error, 'message') &&
|
|
212
|
+
typeof error.message === 'string');
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Start listening on a Unix socket path, removing any stale socket file.
|
|
216
|
+
*
|
|
217
|
+
* @param server - The net.Server instance.
|
|
218
|
+
* @param socketPath - The Unix socket path.
|
|
219
|
+
*/
|
|
220
|
+
async function listen(server, socketPath) {
|
|
221
|
+
try {
|
|
222
|
+
await unlink(socketPath);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (!isErrorWithCode(error, 'ENOENT')) {
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
await new Promise((resolve, reject) => {
|
|
230
|
+
server.on('error', reject);
|
|
231
|
+
server.listen(socketPath, () => {
|
|
232
|
+
server.removeListener('error', reject);
|
|
233
|
+
resolve();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
// Restrict the socket to its owner. The daemon hosts an unlocked wallet, so
|
|
237
|
+
// a world-connectable socket would let any local user drive it. listen()
|
|
238
|
+
// creates the socket with umask-derived (typically world-accessible) perms.
|
|
239
|
+
try {
|
|
240
|
+
await chmod(socketPath, 0o600);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
// Never leave a possibly world-accessible socket listening with no handle
|
|
244
|
+
// to close it. Cleanup is best-effort so the chmod failure still surfaces.
|
|
245
|
+
await closeServer(server).catch(() => undefined);
|
|
246
|
+
await unlink(socketPath).catch(() => undefined);
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Close a server, resolving once it stops accepting connections.
|
|
252
|
+
*
|
|
253
|
+
* @param server - The net.Server instance.
|
|
254
|
+
*/
|
|
255
|
+
async function closeServer(server) {
|
|
256
|
+
await new Promise((resolve, reject) => {
|
|
257
|
+
server.close((error) => {
|
|
258
|
+
if (error) {
|
|
259
|
+
reject(error);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
resolve();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=rpc-socket-server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-socket-server.mjs","sourceRoot":"","sources":["../../src/daemon/rpc-socket-server.ts"],"names":[],"mappings":"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","sourcesContent":["import { rpcErrors } from '@metamask/rpc-errors';\nimport type {\n JsonRpcId,\n JsonRpcParams,\n JsonRpcResponse,\n} from '@metamask/utils';\nimport { hasProperty, isJsonRpcRequest } from '@metamask/utils';\nimport { chmod, unlink } from 'node:fs/promises';\nimport { createServer } from 'node:net';\nimport type { Server } from 'node:net';\n\nimport type { RpcHandlerMap } from './types';\nimport { isErrorWithCode } from './utils';\n\nconst CONNECTION_TIMEOUT_MS = 30_000;\n\n/**\n * Handle returned by {@link startRpcSocketServer}.\n */\nexport type RpcSocketServerHandle = {\n close: () => Promise<void>;\n};\n\n/**\n * Options for {@link startRpcSocketServer}.\n */\nexport type StartRpcSocketServerOptions = {\n /** The Unix socket path to listen on. */\n socketPath: string;\n /** Map of RPC method names to handler functions. */\n handlers: RpcHandlerMap;\n /** Callback invoked when a `shutdown` RPC is received. */\n onShutdown?: (() => Promise<void>) | undefined;\n /**\n * Optional logger for server-side diagnostics (unexpected socket errors,\n * unhandled handler rejections, `onShutdown` callback failures). Without\n * this, failures fall back to `process.stderr.write`, which is discarded\n * when the daemon is spawned with `stdio: 'ignore'`.\n */\n log?: ((message: string) => void) | undefined;\n};\n\n/**\n * Start a Unix socket server that processes JSON-RPC requests.\n *\n * Each connection reads one newline-delimited JSON-RPC request, processes it\n * via the provided handler map, writes a JSON-RPC response, and closes.\n *\n * The special `shutdown` method is intercepted before handler dispatch and\n * triggers the provided {@link StartRpcSocketServerOptions.onShutdown} callback\n * after responding.\n *\n * @param options - Server options.\n * @param options.socketPath - The Unix socket path to listen on.\n * @param options.handlers - Map of RPC method names to handler functions.\n * @param options.onShutdown - Optional callback invoked when a `shutdown` RPC is received.\n * @param options.log - Optional logger for server-side diagnostics.\n * @returns A handle with a `close()` function for cleanup.\n */\nexport async function startRpcSocketServer({\n socketPath,\n handlers,\n onShutdown,\n log,\n}: StartRpcSocketServerOptions): Promise<RpcSocketServerHandle> {\n const logFn = log ?? defaultLog;\n\n const server = createServer((socket) => {\n let buffer = '';\n\n // Destroy connections that never send a complete request line. `unref` so\n // the timer alone cannot keep the event loop alive at shutdown.\n const timer = setTimeout(() => {\n socket.destroy();\n }, CONNECTION_TIMEOUT_MS);\n timer.unref();\n\n /**\n * Clear the idle-connection timer. Called from data, close, and error\n * paths so the timer never outlives the connection itself.\n */\n const clearIdleTimer = (): void => {\n clearTimeout(timer);\n };\n\n const onData = (data: Buffer): void => {\n buffer += data.toString();\n const idx = buffer.indexOf('\\n');\n if (idx === -1) {\n return;\n }\n\n clearIdleTimer();\n\n // One request per connection.\n socket.removeListener('data', onData);\n\n const line = buffer.slice(0, idx);\n const remaining = buffer.slice(idx + 1);\n buffer = '';\n\n if (remaining.length > 0) {\n socket.end(\n `${JSON.stringify({\n jsonrpc: '2.0',\n error: rpcErrors\n .invalidRequest({\n message: 'Only one request per connection is allowed',\n })\n .serialize(),\n })}\\n`,\n );\n return;\n }\n\n handleRequest(handlers, line, onShutdown, logFn)\n .then((response) => {\n socket.end(`${JSON.stringify(response)}\\n`);\n return undefined;\n })\n .catch((dispatchError: unknown) => {\n logFn(`Unhandled RPC dispatch error: ${String(dispatchError)}`);\n socket.end(\n `${JSON.stringify({\n jsonrpc: '2.0',\n error: rpcErrors\n .internal({ message: 'Internal error' })\n .serialize(),\n })}\\n`,\n );\n });\n };\n socket.on('data', onData);\n socket.once('close', clearIdleTimer);\n socket.on('error', (socketError: NodeJS.ErrnoException) => {\n clearIdleTimer();\n const { code } = socketError;\n if (code === 'EPIPE' || code === 'ECONNRESET') {\n return; // Expected during probe/disconnect.\n }\n logFn(`Unexpected socket error: ${String(socketError)}`);\n });\n });\n\n await listen(server, socketPath);\n\n return {\n close: async (): Promise<void> => closeServer(server),\n };\n}\n\n/**\n * Default fallback logger: writes to stderr. Daemons spawned with\n * `stdio: 'ignore'` should always pass an explicit `log`.\n *\n * @param message - The message to log.\n */\nfunction defaultLog(message: string): void {\n process.stderr.write(`${message}\\n`);\n}\n\n/**\n * Handle a single JSON-RPC request line, intercepting the `shutdown` method.\n *\n * @param handlers - The RPC handler map.\n * @param line - The raw JSON line from the socket.\n * @param onShutdown - Optional shutdown callback.\n * @param log - Logger for diagnostic messages.\n * @returns A JSON-RPC response object.\n */\nasync function handleRequest(\n handlers: RpcHandlerMap,\n line: string,\n onShutdown: (() => Promise<void>) | undefined,\n log: (message: string) => void,\n): Promise<JsonRpcResponse> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n return {\n jsonrpc: '2.0',\n id: null,\n error: rpcErrors.parse({ message: 'Parse error' }).serialize(),\n };\n }\n\n if (!isJsonRpcRequest(parsed)) {\n const id: JsonRpcId =\n typeof parsed === 'object' &&\n parsed !== null &&\n hasProperty(parsed, 'id') &&\n isValidJsonRpcId(parsed.id)\n ? parsed.id\n : null;\n return {\n jsonrpc: '2.0',\n id,\n error: rpcErrors\n .invalidRequest({ message: 'Invalid JSON-RPC request' })\n .serialize(),\n };\n }\n\n const { id, method, params } = parsed;\n\n try {\n if (method === 'shutdown') {\n if (onShutdown) {\n setTimeout(() => {\n onShutdown().catch((error: unknown) => {\n log(`onShutdown callback failed: ${String(error)}`);\n });\n }, 0);\n }\n return { jsonrpc: '2.0', id, result: { status: 'shutting down' } };\n }\n\n const handler = Object.prototype.hasOwnProperty.call(handlers, method)\n ? handlers[method]\n : undefined;\n if (!handler) {\n return {\n jsonrpc: '2.0',\n id,\n error: rpcErrors\n .methodNotFound({ message: `Method not found: ${method}` })\n .serialize(),\n };\n }\n\n const result = await handler(coerceHandlerParams(params));\n return { jsonrpc: '2.0', id, result: result ?? null };\n } catch (error) {\n log(`RPC handler \"${method}\" failed: ${String(error)}`);\n if (isRpcError(error)) {\n return { jsonrpc: '2.0', id, error };\n }\n const message = error instanceof Error ? error.message : 'Internal error';\n return {\n jsonrpc: '2.0',\n id,\n error: rpcErrors.internal({ message }).serialize(),\n };\n }\n}\n\n/**\n * Narrow `params` to the shape handlers expect. JSON-RPC 2.0 requires\n * `params`, when present, to be an array or object; both are valid `Json`.\n *\n * @param params - The validated `params` field from a JSON-RPC request.\n * @returns The same value, or `null` when absent.\n */\nfunction coerceHandlerParams(\n params: JsonRpcParams | undefined,\n): JsonRpcParams | null {\n return params ?? null;\n}\n\n/**\n * Per JSON-RPC 2.0, `id` must be a string, number, or null. Used when\n * salvaging an `id` from a parse-success-but-not-valid-request payload.\n *\n * @param value - The candidate id.\n * @returns True if the value is an acceptable JSON-RPC id.\n */\nfunction isValidJsonRpcId(value: unknown): value is JsonRpcId {\n return (\n value === null || typeof value === 'string' || typeof value === 'number'\n );\n}\n\n/**\n * Check if an error is an RPC error with a numeric code.\n *\n * @param error - The error to check.\n * @returns True if the error has a numeric code property.\n */\nfunction isRpcError(\n error: unknown,\n): error is { code: number; message: string } {\n return (\n typeof error === 'object' &&\n error !== null &&\n hasProperty(error, 'code') &&\n typeof error.code === 'number' &&\n hasProperty(error, 'message') &&\n typeof error.message === 'string'\n );\n}\n\n/**\n * Start listening on a Unix socket path, removing any stale socket file.\n *\n * @param server - The net.Server instance.\n * @param socketPath - The Unix socket path.\n */\nasync function listen(server: Server, socketPath: string): Promise<void> {\n try {\n await unlink(socketPath);\n } catch (error) {\n if (!isErrorWithCode(error, 'ENOENT')) {\n throw error;\n }\n }\n\n await new Promise<void>((resolve, reject) => {\n server.on('error', reject);\n server.listen(socketPath, () => {\n server.removeListener('error', reject);\n resolve();\n });\n });\n\n // Restrict the socket to its owner. The daemon hosts an unlocked wallet, so\n // a world-connectable socket would let any local user drive it. listen()\n // creates the socket with umask-derived (typically world-accessible) perms.\n try {\n await chmod(socketPath, 0o600);\n } catch (error) {\n // Never leave a possibly world-accessible socket listening with no handle\n // to close it. Cleanup is best-effort so the chmod failure still surfaces.\n await closeServer(server).catch(() => undefined);\n await unlink(socketPath).catch(() => undefined);\n throw error;\n }\n}\n\n/**\n * Close a server, resolving once it stops accepting connections.\n *\n * @param server - The net.Server instance.\n */\nasync function closeServer(server: Server): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readLine = exports.writeLine = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Write a newline-delimited line to a socket.
|
|
6
|
+
*
|
|
7
|
+
* @param socket - The socket to write to.
|
|
8
|
+
* @param line - The line to write (without trailing newline).
|
|
9
|
+
*/
|
|
10
|
+
async function writeLine(socket, line) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
// A socket 'error' can fire while the write is in flight without ever
|
|
13
|
+
// reaching the write callback; without this listener Node would treat it
|
|
14
|
+
// as an unhandled 'error' event and crash the process.
|
|
15
|
+
const onError = (error) => {
|
|
16
|
+
socket.removeListener('error', onError);
|
|
17
|
+
reject(error);
|
|
18
|
+
};
|
|
19
|
+
socket.once('error', onError);
|
|
20
|
+
socket.write(`${line}\n`, (error) => {
|
|
21
|
+
socket.removeListener('error', onError);
|
|
22
|
+
if (error) {
|
|
23
|
+
reject(error);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
resolve();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
exports.writeLine = writeLine;
|
|
32
|
+
/**
|
|
33
|
+
* Read a single newline-delimited line from a socket.
|
|
34
|
+
*
|
|
35
|
+
* @param socket - The socket to read from.
|
|
36
|
+
* @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout
|
|
37
|
+
* error if no complete line is received within the limit.
|
|
38
|
+
* @returns The line read (without trailing newline).
|
|
39
|
+
*/
|
|
40
|
+
async function readLine(socket, timeoutMs) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
let buffer = '';
|
|
43
|
+
let timer;
|
|
44
|
+
if (timeoutMs !== undefined) {
|
|
45
|
+
timer = setTimeout(() => {
|
|
46
|
+
cleanup();
|
|
47
|
+
reject(new Error('Socket read timed out'));
|
|
48
|
+
}, timeoutMs);
|
|
49
|
+
}
|
|
50
|
+
const onData = (data) => {
|
|
51
|
+
buffer += data.toString();
|
|
52
|
+
const idx = buffer.indexOf('\n');
|
|
53
|
+
if (idx !== -1) {
|
|
54
|
+
cleanup();
|
|
55
|
+
resolve(buffer.slice(0, idx));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const onError = (error) => {
|
|
59
|
+
cleanup();
|
|
60
|
+
reject(error);
|
|
61
|
+
};
|
|
62
|
+
const onEnd = () => {
|
|
63
|
+
cleanup();
|
|
64
|
+
reject(new Error('Socket closed before response received'));
|
|
65
|
+
};
|
|
66
|
+
const onClose = () => {
|
|
67
|
+
cleanup();
|
|
68
|
+
reject(new Error('Socket closed before response received'));
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Remove listeners registered by this call and clear the timeout.
|
|
72
|
+
*/
|
|
73
|
+
function cleanup() {
|
|
74
|
+
if (timer !== undefined) {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
socket.removeListener('data', onData);
|
|
78
|
+
socket.removeListener('error', onError);
|
|
79
|
+
socket.removeListener('end', onEnd);
|
|
80
|
+
socket.removeListener('close', onClose);
|
|
81
|
+
}
|
|
82
|
+
socket.on('data', onData);
|
|
83
|
+
socket.once('error', onError);
|
|
84
|
+
socket.once('end', onEnd);
|
|
85
|
+
socket.once('close', onClose);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
exports.readLine = readLine;
|
|
89
|
+
//# sourceMappingURL=socket-line.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-line.cjs","sourceRoot":"","sources":["../../src/daemon/socket-line.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Socket } from 'node:net';\n\n/**\n * Write a newline-delimited line to a socket.\n *\n * @param socket - The socket to write to.\n * @param line - The line to write (without trailing newline).\n */\nexport async function writeLine(socket: Socket, line: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // A socket 'error' can fire while the write is in flight without ever\n // reaching the write callback; without this listener Node would treat it\n // as an unhandled 'error' event and crash the process.\n const onError = (error: Error): void => {\n socket.removeListener('error', onError);\n reject(error);\n };\n socket.once('error', onError);\n\n socket.write(`${line}\\n`, (error) => {\n socket.removeListener('error', onError);\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n\n/**\n * Read a single newline-delimited line from a socket.\n *\n * @param socket - The socket to read from.\n * @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout\n * error if no complete line is received within the limit.\n * @returns The line read (without trailing newline).\n */\nexport async function readLine(\n socket: Socket,\n timeoutMs?: number,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n let buffer = '';\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (timeoutMs !== undefined) {\n timer = setTimeout(() => {\n cleanup();\n reject(new Error('Socket read timed out'));\n }, timeoutMs);\n }\n\n const onData = (data: Buffer): void => {\n buffer += data.toString();\n const idx = buffer.indexOf('\\n');\n if (idx !== -1) {\n cleanup();\n resolve(buffer.slice(0, idx));\n }\n };\n\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n\n const onEnd = (): void => {\n cleanup();\n reject(new Error('Socket closed before response received'));\n };\n\n const onClose = (): void => {\n cleanup();\n reject(new Error('Socket closed before response received'));\n };\n\n /**\n * Remove listeners registered by this call and clear the timeout.\n */\n function cleanup(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n socket.removeListener('data', onData);\n socket.removeListener('error', onError);\n socket.removeListener('end', onEnd);\n socket.removeListener('close', onClose);\n }\n\n socket.on('data', onData);\n socket.once('error', onError);\n socket.once('end', onEnd);\n socket.once('close', onClose);\n });\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { Socket } from "node:net";
|
|
3
|
+
/**
|
|
4
|
+
* Write a newline-delimited line to a socket.
|
|
5
|
+
*
|
|
6
|
+
* @param socket - The socket to write to.
|
|
7
|
+
* @param line - The line to write (without trailing newline).
|
|
8
|
+
*/
|
|
9
|
+
export declare function writeLine(socket: Socket, line: string): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Read a single newline-delimited line from a socket.
|
|
12
|
+
*
|
|
13
|
+
* @param socket - The socket to read from.
|
|
14
|
+
* @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout
|
|
15
|
+
* error if no complete line is received within the limit.
|
|
16
|
+
* @returns The line read (without trailing newline).
|
|
17
|
+
*/
|
|
18
|
+
export declare function readLine(socket: Socket, timeoutMs?: number): Promise<string>;
|
|
19
|
+
//# sourceMappingURL=socket-line.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-line.d.cts","sourceRoot":"","sources":["../../src/daemon/socket-line.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB;AAEvC;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAsDjB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { Socket } from "node:net";
|
|
3
|
+
/**
|
|
4
|
+
* Write a newline-delimited line to a socket.
|
|
5
|
+
*
|
|
6
|
+
* @param socket - The socket to write to.
|
|
7
|
+
* @param line - The line to write (without trailing newline).
|
|
8
|
+
*/
|
|
9
|
+
export declare function writeLine(socket: Socket, line: string): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Read a single newline-delimited line from a socket.
|
|
12
|
+
*
|
|
13
|
+
* @param socket - The socket to read from.
|
|
14
|
+
* @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout
|
|
15
|
+
* error if no complete line is received within the limit.
|
|
16
|
+
* @returns The line read (without trailing newline).
|
|
17
|
+
*/
|
|
18
|
+
export declare function readLine(socket: Socket, timeoutMs?: number): Promise<string>;
|
|
19
|
+
//# sourceMappingURL=socket-line.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-line.d.mts","sourceRoot":"","sources":["../../src/daemon/socket-line.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB;AAEvC;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAsDjB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write a newline-delimited line to a socket.
|
|
3
|
+
*
|
|
4
|
+
* @param socket - The socket to write to.
|
|
5
|
+
* @param line - The line to write (without trailing newline).
|
|
6
|
+
*/
|
|
7
|
+
export async function writeLine(socket, line) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
// A socket 'error' can fire while the write is in flight without ever
|
|
10
|
+
// reaching the write callback; without this listener Node would treat it
|
|
11
|
+
// as an unhandled 'error' event and crash the process.
|
|
12
|
+
const onError = (error) => {
|
|
13
|
+
socket.removeListener('error', onError);
|
|
14
|
+
reject(error);
|
|
15
|
+
};
|
|
16
|
+
socket.once('error', onError);
|
|
17
|
+
socket.write(`${line}\n`, (error) => {
|
|
18
|
+
socket.removeListener('error', onError);
|
|
19
|
+
if (error) {
|
|
20
|
+
reject(error);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
resolve();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read a single newline-delimited line from a socket.
|
|
30
|
+
*
|
|
31
|
+
* @param socket - The socket to read from.
|
|
32
|
+
* @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout
|
|
33
|
+
* error if no complete line is received within the limit.
|
|
34
|
+
* @returns The line read (without trailing newline).
|
|
35
|
+
*/
|
|
36
|
+
export async function readLine(socket, timeoutMs) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
let buffer = '';
|
|
39
|
+
let timer;
|
|
40
|
+
if (timeoutMs !== undefined) {
|
|
41
|
+
timer = setTimeout(() => {
|
|
42
|
+
cleanup();
|
|
43
|
+
reject(new Error('Socket read timed out'));
|
|
44
|
+
}, timeoutMs);
|
|
45
|
+
}
|
|
46
|
+
const onData = (data) => {
|
|
47
|
+
buffer += data.toString();
|
|
48
|
+
const idx = buffer.indexOf('\n');
|
|
49
|
+
if (idx !== -1) {
|
|
50
|
+
cleanup();
|
|
51
|
+
resolve(buffer.slice(0, idx));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const onError = (error) => {
|
|
55
|
+
cleanup();
|
|
56
|
+
reject(error);
|
|
57
|
+
};
|
|
58
|
+
const onEnd = () => {
|
|
59
|
+
cleanup();
|
|
60
|
+
reject(new Error('Socket closed before response received'));
|
|
61
|
+
};
|
|
62
|
+
const onClose = () => {
|
|
63
|
+
cleanup();
|
|
64
|
+
reject(new Error('Socket closed before response received'));
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Remove listeners registered by this call and clear the timeout.
|
|
68
|
+
*/
|
|
69
|
+
function cleanup() {
|
|
70
|
+
if (timer !== undefined) {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
}
|
|
73
|
+
socket.removeListener('data', onData);
|
|
74
|
+
socket.removeListener('error', onError);
|
|
75
|
+
socket.removeListener('end', onEnd);
|
|
76
|
+
socket.removeListener('close', onClose);
|
|
77
|
+
}
|
|
78
|
+
socket.on('data', onData);
|
|
79
|
+
socket.once('error', onError);
|
|
80
|
+
socket.once('end', onEnd);
|
|
81
|
+
socket.once('close', onClose);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=socket-line.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-line.mjs","sourceRoot":"","sources":["../../src/daemon/socket-line.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Socket } from 'node:net';\n\n/**\n * Write a newline-delimited line to a socket.\n *\n * @param socket - The socket to write to.\n * @param line - The line to write (without trailing newline).\n */\nexport async function writeLine(socket: Socket, line: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // A socket 'error' can fire while the write is in flight without ever\n // reaching the write callback; without this listener Node would treat it\n // as an unhandled 'error' event and crash the process.\n const onError = (error: Error): void => {\n socket.removeListener('error', onError);\n reject(error);\n };\n socket.once('error', onError);\n\n socket.write(`${line}\\n`, (error) => {\n socket.removeListener('error', onError);\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n\n/**\n * Read a single newline-delimited line from a socket.\n *\n * @param socket - The socket to read from.\n * @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout\n * error if no complete line is received within the limit.\n * @returns The line read (without trailing newline).\n */\nexport async function readLine(\n socket: Socket,\n timeoutMs?: number,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n let buffer = '';\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (timeoutMs !== undefined) {\n timer = setTimeout(() => {\n cleanup();\n reject(new Error('Socket read timed out'));\n }, timeoutMs);\n }\n\n const onData = (data: Buffer): void => {\n buffer += data.toString();\n const idx = buffer.indexOf('\\n');\n if (idx !== -1) {\n cleanup();\n resolve(buffer.slice(0, idx));\n }\n };\n\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n\n const onEnd = (): void => {\n cleanup();\n reject(new Error('Socket closed before response received'));\n };\n\n const onClose = (): void => {\n cleanup();\n reject(new Error('Socket closed before response received'));\n };\n\n /**\n * Remove listeners registered by this call and clear the timeout.\n */\n function cleanup(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n socket.removeListener('data', onData);\n socket.removeListener('error', onError);\n socket.removeListener('end', onEnd);\n socket.removeListener('close', onClose);\n }\n\n socket.on('data', onData);\n socket.once('error', onError);\n socket.once('end', onEnd);\n socket.once('close', onClose);\n });\n}\n"]}
|