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