@metamask-previews/wallet-cli 0.0.0-preview-9de9aa4 → 0.0.0-preview-b2742a2fc
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 -1
- 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 +1 -3
- 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
package/CHANGELOG.md
CHANGED
|
@@ -9,8 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
- Add a daemon transport layer: a JSON-RPC client and server over a Unix socket, plus daemon spawn/stop lifecycle helpers ([#9108](https://github.com/MetaMask/core/pull/9108))
|
|
13
12
|
- Add SQLite-backed persistence for wallet controller state ([#9067](https://github.com/MetaMask/core/pull/9067))
|
|
13
|
+
- A `KeyValueStore` backed by `better-sqlite3` for synchronous reads and writes.
|
|
14
|
+
- `loadState` to rehydrate persist-flagged controller state from the store and `subscribeToChanges` to write persist-flagged controller state through to disk on every `stateChanged` event.
|
|
14
15
|
- Initial package scaffold for `@metamask/wallet-cli`, an [oclif](https://oclif.io)-based `mm` CLI for `@metamask/wallet` ([#9065](https://github.com/MetaMask/core/pull/9065)).
|
|
15
16
|
|
|
16
17
|
### Changed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n"]}
|
package/dist/daemon/types.d.cts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
import type { Json } from "@metamask/utils";
|
|
2
|
-
/**
|
|
3
|
-
* A function that handles a JSON-RPC method call.
|
|
4
|
-
*
|
|
5
|
-
* The `params` argument will be `null` if the client did not provide params.
|
|
6
|
-
*/
|
|
7
|
-
export type RpcHandler = (params: Json) => Promise<Json | void>;
|
|
8
|
-
/**
|
|
9
|
-
* A map of RPC method names to their handler functions.
|
|
10
|
-
*/
|
|
11
|
-
export type RpcHandlerMap = Record<string, RpcHandler>;
|
|
12
1
|
/**
|
|
13
2
|
* Resolved paths for daemon state files.
|
|
14
3
|
*/
|
|
@@ -18,21 +7,4 @@ export type DaemonPaths = {
|
|
|
18
7
|
logPath: string;
|
|
19
8
|
dbPath: string;
|
|
20
9
|
};
|
|
21
|
-
/**
|
|
22
|
-
* Status information returned by the daemon's `getStatus` RPC method.
|
|
23
|
-
*/
|
|
24
|
-
export type DaemonStatusInfo = {
|
|
25
|
-
pid: number;
|
|
26
|
-
uptime: number;
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Configuration passed to the daemon spawner.
|
|
30
|
-
*/
|
|
31
|
-
export type DaemonSpawnConfig = {
|
|
32
|
-
dataDir: string;
|
|
33
|
-
infuraProjectId: string;
|
|
34
|
-
password: string;
|
|
35
|
-
srp: string;
|
|
36
|
-
packageRoot: string;
|
|
37
|
-
};
|
|
38
10
|
//# sourceMappingURL=types.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
package/dist/daemon/types.d.mts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
import type { Json } from "@metamask/utils";
|
|
2
|
-
/**
|
|
3
|
-
* A function that handles a JSON-RPC method call.
|
|
4
|
-
*
|
|
5
|
-
* The `params` argument will be `null` if the client did not provide params.
|
|
6
|
-
*/
|
|
7
|
-
export type RpcHandler = (params: Json) => Promise<Json | void>;
|
|
8
|
-
/**
|
|
9
|
-
* A map of RPC method names to their handler functions.
|
|
10
|
-
*/
|
|
11
|
-
export type RpcHandlerMap = Record<string, RpcHandler>;
|
|
12
1
|
/**
|
|
13
2
|
* Resolved paths for daemon state files.
|
|
14
3
|
*/
|
|
@@ -18,21 +7,4 @@ export type DaemonPaths = {
|
|
|
18
7
|
logPath: string;
|
|
19
8
|
dbPath: string;
|
|
20
9
|
};
|
|
21
|
-
/**
|
|
22
|
-
* Status information returned by the daemon's `getStatus` RPC method.
|
|
23
|
-
*/
|
|
24
|
-
export type DaemonStatusInfo = {
|
|
25
|
-
pid: number;
|
|
26
|
-
uptime: number;
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Configuration passed to the daemon spawner.
|
|
30
|
-
*/
|
|
31
|
-
export type DaemonSpawnConfig = {
|
|
32
|
-
dataDir: string;
|
|
33
|
-
infuraProjectId: string;
|
|
34
|
-
password: string;
|
|
35
|
-
srp: string;
|
|
36
|
-
packageRoot: string;
|
|
37
|
-
};
|
|
38
10
|
//# sourceMappingURL=types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"types.mjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/wallet-cli",
|
|
3
|
-
"version": "0.0.0-preview-
|
|
3
|
+
"version": "0.0.0-preview-b2742a2fc",
|
|
4
4
|
"description": "The CLI of @metamask/wallet",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -43,9 +43,7 @@
|
|
|
43
43
|
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@inquirer/confirm": "^6.0.11",
|
|
47
46
|
"@metamask/base-controller": "^9.1.0",
|
|
48
|
-
"@metamask/rpc-errors": "^7.0.2",
|
|
49
47
|
"@metamask/utils": "^11.11.0",
|
|
50
48
|
"@metamask/wallet": "^4.0.0",
|
|
51
49
|
"@oclif/core": "^4.10.5",
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pingDaemon = exports.sendCommand = void 0;
|
|
4
|
-
const utils_1 = require("@metamask/utils");
|
|
5
|
-
const node_crypto_1 = require("node:crypto");
|
|
6
|
-
const node_net_1 = require("node:net");
|
|
7
|
-
const socket_line_1 = require("./socket-line.cjs");
|
|
8
|
-
const utils_2 = require("./utils.cjs");
|
|
9
|
-
const DEFAULT_TIMEOUT_MS = 30000;
|
|
10
|
-
async function connectSocket(socketPath) {
|
|
11
|
-
return new Promise((resolve, reject) => {
|
|
12
|
-
const socket = (0, node_net_1.createConnection)(socketPath, () => {
|
|
13
|
-
socket.removeAllListeners('error');
|
|
14
|
-
resolve(socket);
|
|
15
|
-
});
|
|
16
|
-
socket.on('error', (error) => {
|
|
17
|
-
// A failed connect never reaches the caller, so destroy the socket here
|
|
18
|
-
// to avoid leaking its file descriptor.
|
|
19
|
-
socket.destroy();
|
|
20
|
-
reject(error);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Send a JSON-RPC request to the daemon over a Unix socket and return the
|
|
26
|
-
* response.
|
|
27
|
-
*
|
|
28
|
-
* Opens a connection, writes one JSON-RPC request line, reads one JSON-RPC
|
|
29
|
-
* response line, then closes the connection. Retries once after a short delay
|
|
30
|
-
* on transient connection errors (ECONNREFUSED, ECONNRESET). Verifies that the
|
|
31
|
-
* response `id` matches the outgoing request `id`.
|
|
32
|
-
*
|
|
33
|
-
* @param options - Command options.
|
|
34
|
-
* @param options.socketPath - The Unix socket path.
|
|
35
|
-
* @param options.method - The RPC method name.
|
|
36
|
-
* @param options.params - Optional method parameters.
|
|
37
|
-
* @param options.timeoutMs - Read timeout in milliseconds.
|
|
38
|
-
* @returns The parsed JSON-RPC response.
|
|
39
|
-
*/
|
|
40
|
-
async function sendCommand({ socketPath, method, params, timeoutMs, }) {
|
|
41
|
-
const id = (0, node_crypto_1.randomUUID)();
|
|
42
|
-
const request = {
|
|
43
|
-
jsonrpc: '2.0',
|
|
44
|
-
id,
|
|
45
|
-
method,
|
|
46
|
-
...(params === undefined ? {} : { params }),
|
|
47
|
-
};
|
|
48
|
-
const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
49
|
-
const attempt = async () => {
|
|
50
|
-
const socket = await connectSocket(socketPath);
|
|
51
|
-
try {
|
|
52
|
-
await (0, socket_line_1.writeLine)(socket, JSON.stringify(request));
|
|
53
|
-
const responseLine = await (0, socket_line_1.readLine)(socket, effectiveTimeout);
|
|
54
|
-
const parsed = JSON.parse(responseLine);
|
|
55
|
-
(0, utils_1.assertIsJsonRpcResponse)(parsed);
|
|
56
|
-
if (parsed.id !== id) {
|
|
57
|
-
throw new Error(`JSON-RPC response id ${JSON.stringify(parsed.id)} does not match request id ${JSON.stringify(id)}`);
|
|
58
|
-
}
|
|
59
|
-
return parsed;
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
socket.destroy();
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
try {
|
|
66
|
-
return await attempt();
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
if (!(0, utils_2.isErrorWithCode)(error, 'ECONNREFUSED') &&
|
|
70
|
-
!(0, utils_2.isErrorWithCode)(error, 'ECONNRESET')) {
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
74
|
-
return attempt();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
exports.sendCommand = sendCommand;
|
|
78
|
-
/**
|
|
79
|
-
* Normalize an unknown throw into a real Error instance so that downstream
|
|
80
|
-
* consumers (which read `.message`) cannot crash on string/object throws.
|
|
81
|
-
*
|
|
82
|
-
* @param error - The caught value.
|
|
83
|
-
* @returns An Error mirroring the caught value.
|
|
84
|
-
*/
|
|
85
|
-
function toError(error) {
|
|
86
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Categorise an unreachable error by Node errno / message shape so callers can
|
|
90
|
-
* make decisions per failure mode rather than parsing message strings.
|
|
91
|
-
*
|
|
92
|
-
* @param error - The caught value.
|
|
93
|
-
* @returns A {@link PingUnreachableReason} label.
|
|
94
|
-
*/
|
|
95
|
-
function classifyUnreachable(error) {
|
|
96
|
-
if ((0, utils_2.isErrorWithCode)(error, 'ECONNREFUSED') ||
|
|
97
|
-
(0, utils_2.isErrorWithCode)(error, 'ECONNRESET')) {
|
|
98
|
-
return 'refused';
|
|
99
|
-
}
|
|
100
|
-
if ((0, utils_2.isErrorWithCode)(error, 'EACCES') || (0, utils_2.isErrorWithCode)(error, 'EPERM')) {
|
|
101
|
-
return 'permission';
|
|
102
|
-
}
|
|
103
|
-
if (error instanceof Error && error.message === 'Socket read timed out') {
|
|
104
|
-
return 'timeout';
|
|
105
|
-
}
|
|
106
|
-
if (error instanceof Error &&
|
|
107
|
-
(error.message.includes('JSON-RPC response id') ||
|
|
108
|
-
/Expected .* JSON-RPC/u.test(error.message) ||
|
|
109
|
-
error.name === 'SyntaxError')) {
|
|
110
|
-
return 'protocol';
|
|
111
|
-
}
|
|
112
|
-
return 'other';
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Check whether the daemon is running by sending a lightweight `getStatus`
|
|
116
|
-
* RPC call. Distinguishes "no daemon present" (socket file missing) from
|
|
117
|
-
* "daemon present but unreachable" (socket file exists but the daemon is
|
|
118
|
-
* wedged, mid-shutdown, or owned by a different user).
|
|
119
|
-
*
|
|
120
|
-
* @param socketPath - The Unix socket path.
|
|
121
|
-
* @returns A {@link PingResult} describing the daemon's reachability.
|
|
122
|
-
*/
|
|
123
|
-
async function pingDaemon(socketPath) {
|
|
124
|
-
try {
|
|
125
|
-
await sendCommand({ socketPath, method: 'getStatus', timeoutMs: 3000 });
|
|
126
|
-
return { status: 'responsive' };
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
if ((0, utils_2.isErrorWithCode)(error, 'ENOENT')) {
|
|
130
|
-
return { status: 'absent' };
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
status: 'unreachable',
|
|
134
|
-
reason: classifyUnreachable(error),
|
|
135
|
-
error: toError(error),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
exports.pingDaemon = pingDaemon;
|
|
140
|
-
//# sourceMappingURL=daemon-client.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-client.cjs","sourceRoot":"","sources":["../../src/daemon/daemon-client.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonRpcParams, JsonRpcResponse } from '@metamask/utils';\nimport { assertIsJsonRpcResponse } from '@metamask/utils';\nimport { randomUUID } from 'node:crypto';\nimport { createConnection } from 'node:net';\nimport type { Socket } from 'node:net';\n\nimport { readLine, writeLine } from './socket-line';\nimport { isErrorWithCode } from './utils';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/**\n * Options for {@link sendCommand}.\n */\ntype SendCommandOptions = {\n /** The Unix socket path. */\n socketPath: string;\n /** The RPC method name. */\n method: string;\n /** Optional method parameters (object or positional array). */\n params?: JsonRpcParams | undefined;\n /** Response read timeout in milliseconds (default: 30 000). */\n timeoutMs?: number | undefined;\n};\n\nasync function connectSocket(socketPath: string): Promise<Socket> {\n return new Promise((resolve, reject) => {\n const socket = createConnection(socketPath, () => {\n socket.removeAllListeners('error');\n resolve(socket);\n });\n socket.on('error', (error) => {\n // A failed connect never reaches the caller, so destroy the socket here\n // to avoid leaking its file descriptor.\n socket.destroy();\n reject(error);\n });\n });\n}\n\n/**\n * Send a JSON-RPC request to the daemon over a Unix socket and return the\n * response.\n *\n * Opens a connection, writes one JSON-RPC request line, reads one JSON-RPC\n * response line, then closes the connection. Retries once after a short delay\n * on transient connection errors (ECONNREFUSED, ECONNRESET). Verifies that the\n * response `id` matches the outgoing request `id`.\n *\n * @param options - Command options.\n * @param options.socketPath - The Unix socket path.\n * @param options.method - The RPC method name.\n * @param options.params - Optional method parameters.\n * @param options.timeoutMs - Read timeout in milliseconds.\n * @returns The parsed JSON-RPC response.\n */\nexport async function sendCommand({\n socketPath,\n method,\n params,\n timeoutMs,\n}: SendCommandOptions): Promise<JsonRpcResponse> {\n const id = randomUUID();\n const request = {\n jsonrpc: '2.0',\n id,\n method,\n ...(params === undefined ? {} : { params }),\n };\n\n const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const attempt = async (): Promise<JsonRpcResponse> => {\n const socket = await connectSocket(socketPath);\n try {\n await writeLine(socket, JSON.stringify(request));\n const responseLine = await readLine(socket, effectiveTimeout);\n const parsed: unknown = JSON.parse(responseLine);\n assertIsJsonRpcResponse(parsed);\n if (parsed.id !== id) {\n throw new Error(\n `JSON-RPC response id ${JSON.stringify(parsed.id)} does not match request id ${JSON.stringify(id)}`,\n );\n }\n return parsed;\n } finally {\n socket.destroy();\n }\n };\n\n try {\n return await attempt();\n } catch (error: unknown) {\n if (\n !isErrorWithCode(error, 'ECONNREFUSED') &&\n !isErrorWithCode(error, 'ECONNRESET')\n ) {\n throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, 100));\n return attempt();\n }\n}\n\n/**\n * Why an unreachable daemon cannot be queried.\n *\n * - `'refused'`: connection refused after retry (`ECONNREFUSED` / `ECONNRESET`).\n * Typical of a daemon that has crashed or is mid-restart.\n * - `'timeout'`: the daemon accepted the connection but did not respond within\n * the read timeout — most likely wedged on a long-running operation.\n * - `'permission'`: the socket exists but cannot be opened (`EACCES` / `EPERM`).\n * The daemon almost certainly belongs to another user.\n * - `'protocol'`: the daemon responded but the response did not parse as a\n * valid JSON-RPC response, or the response id did not match.\n * - `'other'`: anything else.\n */\nexport type PingUnreachableReason =\n | 'refused'\n | 'timeout'\n | 'permission'\n | 'protocol'\n | 'other';\n\n/**\n * Outcome of a daemon health check.\n *\n * - `'responsive'`: the daemon answered a `getStatus` RPC.\n * - `'absent'`: the socket connect attempt failed with `ENOENT`, i.e. no\n * socket file exists at the path. No daemon present.\n * - `'unreachable'`: any other non-success outcome. The daemon may still be\n * alive but is not responding. Callers should not silently take over the\n * slot or assume the daemon is dead. The `reason` field categorises the\n * failure so callers can distinguish a wedged sibling daemon from a\n * foreign-user daemon from a transient crash. User-initiated stop / purge\n * flows may still escalate to signals against the recorded PID.\n */\nexport type PingResult =\n | { status: 'responsive' }\n | { status: 'absent' }\n | { status: 'unreachable'; reason: PingUnreachableReason; error: Error };\n\n/**\n * Normalize an unknown throw into a real Error instance so that downstream\n * consumers (which read `.message`) cannot crash on string/object throws.\n *\n * @param error - The caught value.\n * @returns An Error mirroring the caught value.\n */\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\n/**\n * Categorise an unreachable error by Node errno / message shape so callers can\n * make decisions per failure mode rather than parsing message strings.\n *\n * @param error - The caught value.\n * @returns A {@link PingUnreachableReason} label.\n */\nfunction classifyUnreachable(error: unknown): PingUnreachableReason {\n if (\n isErrorWithCode(error, 'ECONNREFUSED') ||\n isErrorWithCode(error, 'ECONNRESET')\n ) {\n return 'refused';\n }\n if (isErrorWithCode(error, 'EACCES') || isErrorWithCode(error, 'EPERM')) {\n return 'permission';\n }\n if (error instanceof Error && error.message === 'Socket read timed out') {\n return 'timeout';\n }\n if (\n error instanceof Error &&\n (error.message.includes('JSON-RPC response id') ||\n /Expected .* JSON-RPC/u.test(error.message) ||\n error.name === 'SyntaxError')\n ) {\n return 'protocol';\n }\n return 'other';\n}\n\n/**\n * Check whether the daemon is running by sending a lightweight `getStatus`\n * RPC call. Distinguishes \"no daemon present\" (socket file missing) from\n * \"daemon present but unreachable\" (socket file exists but the daemon is\n * wedged, mid-shutdown, or owned by a different user).\n *\n * @param socketPath - The Unix socket path.\n * @returns A {@link PingResult} describing the daemon's reachability.\n */\nexport async function pingDaemon(socketPath: string): Promise<PingResult> {\n try {\n await sendCommand({ socketPath, method: 'getStatus', timeoutMs: 3_000 });\n return { status: 'responsive' };\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ENOENT')) {\n return { status: 'absent' };\n }\n return {\n status: 'unreachable',\n reason: classifyUnreachable(error),\n error: toError(error),\n };\n }\n}\n"]}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { JsonRpcParams, JsonRpcResponse } from "@metamask/utils";
|
|
2
|
-
/**
|
|
3
|
-
* Options for {@link sendCommand}.
|
|
4
|
-
*/
|
|
5
|
-
type SendCommandOptions = {
|
|
6
|
-
/** The Unix socket path. */
|
|
7
|
-
socketPath: string;
|
|
8
|
-
/** The RPC method name. */
|
|
9
|
-
method: string;
|
|
10
|
-
/** Optional method parameters (object or positional array). */
|
|
11
|
-
params?: JsonRpcParams | undefined;
|
|
12
|
-
/** Response read timeout in milliseconds (default: 30 000). */
|
|
13
|
-
timeoutMs?: number | undefined;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Send a JSON-RPC request to the daemon over a Unix socket and return the
|
|
17
|
-
* response.
|
|
18
|
-
*
|
|
19
|
-
* Opens a connection, writes one JSON-RPC request line, reads one JSON-RPC
|
|
20
|
-
* response line, then closes the connection. Retries once after a short delay
|
|
21
|
-
* on transient connection errors (ECONNREFUSED, ECONNRESET). Verifies that the
|
|
22
|
-
* response `id` matches the outgoing request `id`.
|
|
23
|
-
*
|
|
24
|
-
* @param options - Command options.
|
|
25
|
-
* @param options.socketPath - The Unix socket path.
|
|
26
|
-
* @param options.method - The RPC method name.
|
|
27
|
-
* @param options.params - Optional method parameters.
|
|
28
|
-
* @param options.timeoutMs - Read timeout in milliseconds.
|
|
29
|
-
* @returns The parsed JSON-RPC response.
|
|
30
|
-
*/
|
|
31
|
-
export declare function sendCommand({ socketPath, method, params, timeoutMs, }: SendCommandOptions): Promise<JsonRpcResponse>;
|
|
32
|
-
/**
|
|
33
|
-
* Why an unreachable daemon cannot be queried.
|
|
34
|
-
*
|
|
35
|
-
* - `'refused'`: connection refused after retry (`ECONNREFUSED` / `ECONNRESET`).
|
|
36
|
-
* Typical of a daemon that has crashed or is mid-restart.
|
|
37
|
-
* - `'timeout'`: the daemon accepted the connection but did not respond within
|
|
38
|
-
* the read timeout — most likely wedged on a long-running operation.
|
|
39
|
-
* - `'permission'`: the socket exists but cannot be opened (`EACCES` / `EPERM`).
|
|
40
|
-
* The daemon almost certainly belongs to another user.
|
|
41
|
-
* - `'protocol'`: the daemon responded but the response did not parse as a
|
|
42
|
-
* valid JSON-RPC response, or the response id did not match.
|
|
43
|
-
* - `'other'`: anything else.
|
|
44
|
-
*/
|
|
45
|
-
export type PingUnreachableReason = 'refused' | 'timeout' | 'permission' | 'protocol' | 'other';
|
|
46
|
-
/**
|
|
47
|
-
* Outcome of a daemon health check.
|
|
48
|
-
*
|
|
49
|
-
* - `'responsive'`: the daemon answered a `getStatus` RPC.
|
|
50
|
-
* - `'absent'`: the socket connect attempt failed with `ENOENT`, i.e. no
|
|
51
|
-
* socket file exists at the path. No daemon present.
|
|
52
|
-
* - `'unreachable'`: any other non-success outcome. The daemon may still be
|
|
53
|
-
* alive but is not responding. Callers should not silently take over the
|
|
54
|
-
* slot or assume the daemon is dead. The `reason` field categorises the
|
|
55
|
-
* failure so callers can distinguish a wedged sibling daemon from a
|
|
56
|
-
* foreign-user daemon from a transient crash. User-initiated stop / purge
|
|
57
|
-
* flows may still escalate to signals against the recorded PID.
|
|
58
|
-
*/
|
|
59
|
-
export type PingResult = {
|
|
60
|
-
status: 'responsive';
|
|
61
|
-
} | {
|
|
62
|
-
status: 'absent';
|
|
63
|
-
} | {
|
|
64
|
-
status: 'unreachable';
|
|
65
|
-
reason: PingUnreachableReason;
|
|
66
|
-
error: Error;
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Check whether the daemon is running by sending a lightweight `getStatus`
|
|
70
|
-
* RPC call. Distinguishes "no daemon present" (socket file missing) from
|
|
71
|
-
* "daemon present but unreachable" (socket file exists but the daemon is
|
|
72
|
-
* wedged, mid-shutdown, or owned by a different user).
|
|
73
|
-
*
|
|
74
|
-
* @param socketPath - The Unix socket path.
|
|
75
|
-
* @returns A {@link PingResult} describing the daemon's reachability.
|
|
76
|
-
*/
|
|
77
|
-
export declare function pingDaemon(socketPath: string): Promise<PingResult>;
|
|
78
|
-
export {};
|
|
79
|
-
//# sourceMappingURL=daemon-client.d.cts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-client.d.cts","sourceRoot":"","sources":["../../src/daemon/daemon-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,wBAAwB;AAWtE;;GAEG;AACH,KAAK,kBAAkB,GAAG;IACxB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACnC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAAC;AAiBF;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAAC,EAChC,UAAU,EACV,MAAM,EACN,MAAM,EACN,SAAS,GACV,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAyC/C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,qBAAqB,GAC7B,SAAS,GACT,SAAS,GACT,YAAY,GACZ,UAAU,GACV,OAAO,CAAC;AAEZ;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,GACxB;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AA4C3E;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAcxE"}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { JsonRpcParams, JsonRpcResponse } from "@metamask/utils";
|
|
2
|
-
/**
|
|
3
|
-
* Options for {@link sendCommand}.
|
|
4
|
-
*/
|
|
5
|
-
type SendCommandOptions = {
|
|
6
|
-
/** The Unix socket path. */
|
|
7
|
-
socketPath: string;
|
|
8
|
-
/** The RPC method name. */
|
|
9
|
-
method: string;
|
|
10
|
-
/** Optional method parameters (object or positional array). */
|
|
11
|
-
params?: JsonRpcParams | undefined;
|
|
12
|
-
/** Response read timeout in milliseconds (default: 30 000). */
|
|
13
|
-
timeoutMs?: number | undefined;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Send a JSON-RPC request to the daemon over a Unix socket and return the
|
|
17
|
-
* response.
|
|
18
|
-
*
|
|
19
|
-
* Opens a connection, writes one JSON-RPC request line, reads one JSON-RPC
|
|
20
|
-
* response line, then closes the connection. Retries once after a short delay
|
|
21
|
-
* on transient connection errors (ECONNREFUSED, ECONNRESET). Verifies that the
|
|
22
|
-
* response `id` matches the outgoing request `id`.
|
|
23
|
-
*
|
|
24
|
-
* @param options - Command options.
|
|
25
|
-
* @param options.socketPath - The Unix socket path.
|
|
26
|
-
* @param options.method - The RPC method name.
|
|
27
|
-
* @param options.params - Optional method parameters.
|
|
28
|
-
* @param options.timeoutMs - Read timeout in milliseconds.
|
|
29
|
-
* @returns The parsed JSON-RPC response.
|
|
30
|
-
*/
|
|
31
|
-
export declare function sendCommand({ socketPath, method, params, timeoutMs, }: SendCommandOptions): Promise<JsonRpcResponse>;
|
|
32
|
-
/**
|
|
33
|
-
* Why an unreachable daemon cannot be queried.
|
|
34
|
-
*
|
|
35
|
-
* - `'refused'`: connection refused after retry (`ECONNREFUSED` / `ECONNRESET`).
|
|
36
|
-
* Typical of a daemon that has crashed or is mid-restart.
|
|
37
|
-
* - `'timeout'`: the daemon accepted the connection but did not respond within
|
|
38
|
-
* the read timeout — most likely wedged on a long-running operation.
|
|
39
|
-
* - `'permission'`: the socket exists but cannot be opened (`EACCES` / `EPERM`).
|
|
40
|
-
* The daemon almost certainly belongs to another user.
|
|
41
|
-
* - `'protocol'`: the daemon responded but the response did not parse as a
|
|
42
|
-
* valid JSON-RPC response, or the response id did not match.
|
|
43
|
-
* - `'other'`: anything else.
|
|
44
|
-
*/
|
|
45
|
-
export type PingUnreachableReason = 'refused' | 'timeout' | 'permission' | 'protocol' | 'other';
|
|
46
|
-
/**
|
|
47
|
-
* Outcome of a daemon health check.
|
|
48
|
-
*
|
|
49
|
-
* - `'responsive'`: the daemon answered a `getStatus` RPC.
|
|
50
|
-
* - `'absent'`: the socket connect attempt failed with `ENOENT`, i.e. no
|
|
51
|
-
* socket file exists at the path. No daemon present.
|
|
52
|
-
* - `'unreachable'`: any other non-success outcome. The daemon may still be
|
|
53
|
-
* alive but is not responding. Callers should not silently take over the
|
|
54
|
-
* slot or assume the daemon is dead. The `reason` field categorises the
|
|
55
|
-
* failure so callers can distinguish a wedged sibling daemon from a
|
|
56
|
-
* foreign-user daemon from a transient crash. User-initiated stop / purge
|
|
57
|
-
* flows may still escalate to signals against the recorded PID.
|
|
58
|
-
*/
|
|
59
|
-
export type PingResult = {
|
|
60
|
-
status: 'responsive';
|
|
61
|
-
} | {
|
|
62
|
-
status: 'absent';
|
|
63
|
-
} | {
|
|
64
|
-
status: 'unreachable';
|
|
65
|
-
reason: PingUnreachableReason;
|
|
66
|
-
error: Error;
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Check whether the daemon is running by sending a lightweight `getStatus`
|
|
70
|
-
* RPC call. Distinguishes "no daemon present" (socket file missing) from
|
|
71
|
-
* "daemon present but unreachable" (socket file exists but the daemon is
|
|
72
|
-
* wedged, mid-shutdown, or owned by a different user).
|
|
73
|
-
*
|
|
74
|
-
* @param socketPath - The Unix socket path.
|
|
75
|
-
* @returns A {@link PingResult} describing the daemon's reachability.
|
|
76
|
-
*/
|
|
77
|
-
export declare function pingDaemon(socketPath: string): Promise<PingResult>;
|
|
78
|
-
export {};
|
|
79
|
-
//# sourceMappingURL=daemon-client.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-client.d.mts","sourceRoot":"","sources":["../../src/daemon/daemon-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,wBAAwB;AAWtE;;GAEG;AACH,KAAK,kBAAkB,GAAG;IACxB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACnC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAAC;AAiBF;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAAC,EAChC,UAAU,EACV,MAAM,EACN,MAAM,EACN,SAAS,GACV,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAyC/C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,qBAAqB,GAC7B,SAAS,GACT,SAAS,GACT,YAAY,GACZ,UAAU,GACV,OAAO,CAAC;AAEZ;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,GACxB;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AA4C3E;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAcxE"}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { assertIsJsonRpcResponse } from "@metamask/utils";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { createConnection } from "node:net";
|
|
4
|
-
import { readLine, writeLine } from "./socket-line.mjs";
|
|
5
|
-
import { isErrorWithCode } from "./utils.mjs";
|
|
6
|
-
const DEFAULT_TIMEOUT_MS = 30000;
|
|
7
|
-
async function connectSocket(socketPath) {
|
|
8
|
-
return new Promise((resolve, reject) => {
|
|
9
|
-
const socket = createConnection(socketPath, () => {
|
|
10
|
-
socket.removeAllListeners('error');
|
|
11
|
-
resolve(socket);
|
|
12
|
-
});
|
|
13
|
-
socket.on('error', (error) => {
|
|
14
|
-
// A failed connect never reaches the caller, so destroy the socket here
|
|
15
|
-
// to avoid leaking its file descriptor.
|
|
16
|
-
socket.destroy();
|
|
17
|
-
reject(error);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Send a JSON-RPC request to the daemon over a Unix socket and return the
|
|
23
|
-
* response.
|
|
24
|
-
*
|
|
25
|
-
* Opens a connection, writes one JSON-RPC request line, reads one JSON-RPC
|
|
26
|
-
* response line, then closes the connection. Retries once after a short delay
|
|
27
|
-
* on transient connection errors (ECONNREFUSED, ECONNRESET). Verifies that the
|
|
28
|
-
* response `id` matches the outgoing request `id`.
|
|
29
|
-
*
|
|
30
|
-
* @param options - Command options.
|
|
31
|
-
* @param options.socketPath - The Unix socket path.
|
|
32
|
-
* @param options.method - The RPC method name.
|
|
33
|
-
* @param options.params - Optional method parameters.
|
|
34
|
-
* @param options.timeoutMs - Read timeout in milliseconds.
|
|
35
|
-
* @returns The parsed JSON-RPC response.
|
|
36
|
-
*/
|
|
37
|
-
export async function sendCommand({ socketPath, method, params, timeoutMs, }) {
|
|
38
|
-
const id = randomUUID();
|
|
39
|
-
const request = {
|
|
40
|
-
jsonrpc: '2.0',
|
|
41
|
-
id,
|
|
42
|
-
method,
|
|
43
|
-
...(params === undefined ? {} : { params }),
|
|
44
|
-
};
|
|
45
|
-
const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
46
|
-
const attempt = async () => {
|
|
47
|
-
const socket = await connectSocket(socketPath);
|
|
48
|
-
try {
|
|
49
|
-
await writeLine(socket, JSON.stringify(request));
|
|
50
|
-
const responseLine = await readLine(socket, effectiveTimeout);
|
|
51
|
-
const parsed = JSON.parse(responseLine);
|
|
52
|
-
assertIsJsonRpcResponse(parsed);
|
|
53
|
-
if (parsed.id !== id) {
|
|
54
|
-
throw new Error(`JSON-RPC response id ${JSON.stringify(parsed.id)} does not match request id ${JSON.stringify(id)}`);
|
|
55
|
-
}
|
|
56
|
-
return parsed;
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
socket.destroy();
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
try {
|
|
63
|
-
return await attempt();
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
if (!isErrorWithCode(error, 'ECONNREFUSED') &&
|
|
67
|
-
!isErrorWithCode(error, 'ECONNRESET')) {
|
|
68
|
-
throw error;
|
|
69
|
-
}
|
|
70
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
71
|
-
return attempt();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Normalize an unknown throw into a real Error instance so that downstream
|
|
76
|
-
* consumers (which read `.message`) cannot crash on string/object throws.
|
|
77
|
-
*
|
|
78
|
-
* @param error - The caught value.
|
|
79
|
-
* @returns An Error mirroring the caught value.
|
|
80
|
-
*/
|
|
81
|
-
function toError(error) {
|
|
82
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Categorise an unreachable error by Node errno / message shape so callers can
|
|
86
|
-
* make decisions per failure mode rather than parsing message strings.
|
|
87
|
-
*
|
|
88
|
-
* @param error - The caught value.
|
|
89
|
-
* @returns A {@link PingUnreachableReason} label.
|
|
90
|
-
*/
|
|
91
|
-
function classifyUnreachable(error) {
|
|
92
|
-
if (isErrorWithCode(error, 'ECONNREFUSED') ||
|
|
93
|
-
isErrorWithCode(error, 'ECONNRESET')) {
|
|
94
|
-
return 'refused';
|
|
95
|
-
}
|
|
96
|
-
if (isErrorWithCode(error, 'EACCES') || isErrorWithCode(error, 'EPERM')) {
|
|
97
|
-
return 'permission';
|
|
98
|
-
}
|
|
99
|
-
if (error instanceof Error && error.message === 'Socket read timed out') {
|
|
100
|
-
return 'timeout';
|
|
101
|
-
}
|
|
102
|
-
if (error instanceof Error &&
|
|
103
|
-
(error.message.includes('JSON-RPC response id') ||
|
|
104
|
-
/Expected .* JSON-RPC/u.test(error.message) ||
|
|
105
|
-
error.name === 'SyntaxError')) {
|
|
106
|
-
return 'protocol';
|
|
107
|
-
}
|
|
108
|
-
return 'other';
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Check whether the daemon is running by sending a lightweight `getStatus`
|
|
112
|
-
* RPC call. Distinguishes "no daemon present" (socket file missing) from
|
|
113
|
-
* "daemon present but unreachable" (socket file exists but the daemon is
|
|
114
|
-
* wedged, mid-shutdown, or owned by a different user).
|
|
115
|
-
*
|
|
116
|
-
* @param socketPath - The Unix socket path.
|
|
117
|
-
* @returns A {@link PingResult} describing the daemon's reachability.
|
|
118
|
-
*/
|
|
119
|
-
export async function pingDaemon(socketPath) {
|
|
120
|
-
try {
|
|
121
|
-
await sendCommand({ socketPath, method: 'getStatus', timeoutMs: 3000 });
|
|
122
|
-
return { status: 'responsive' };
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
if (isErrorWithCode(error, 'ENOENT')) {
|
|
126
|
-
return { status: 'absent' };
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
status: 'unreachable',
|
|
130
|
-
reason: classifyUnreachable(error),
|
|
131
|
-
error: toError(error),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
//# sourceMappingURL=daemon-client.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-client.mjs","sourceRoot":"","sources":["../../src/daemon/daemon-client.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonRpcParams, JsonRpcResponse } from '@metamask/utils';\nimport { assertIsJsonRpcResponse } from '@metamask/utils';\nimport { randomUUID } from 'node:crypto';\nimport { createConnection } from 'node:net';\nimport type { Socket } from 'node:net';\n\nimport { readLine, writeLine } from './socket-line';\nimport { isErrorWithCode } from './utils';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/**\n * Options for {@link sendCommand}.\n */\ntype SendCommandOptions = {\n /** The Unix socket path. */\n socketPath: string;\n /** The RPC method name. */\n method: string;\n /** Optional method parameters (object or positional array). */\n params?: JsonRpcParams | undefined;\n /** Response read timeout in milliseconds (default: 30 000). */\n timeoutMs?: number | undefined;\n};\n\nasync function connectSocket(socketPath: string): Promise<Socket> {\n return new Promise((resolve, reject) => {\n const socket = createConnection(socketPath, () => {\n socket.removeAllListeners('error');\n resolve(socket);\n });\n socket.on('error', (error) => {\n // A failed connect never reaches the caller, so destroy the socket here\n // to avoid leaking its file descriptor.\n socket.destroy();\n reject(error);\n });\n });\n}\n\n/**\n * Send a JSON-RPC request to the daemon over a Unix socket and return the\n * response.\n *\n * Opens a connection, writes one JSON-RPC request line, reads one JSON-RPC\n * response line, then closes the connection. Retries once after a short delay\n * on transient connection errors (ECONNREFUSED, ECONNRESET). Verifies that the\n * response `id` matches the outgoing request `id`.\n *\n * @param options - Command options.\n * @param options.socketPath - The Unix socket path.\n * @param options.method - The RPC method name.\n * @param options.params - Optional method parameters.\n * @param options.timeoutMs - Read timeout in milliseconds.\n * @returns The parsed JSON-RPC response.\n */\nexport async function sendCommand({\n socketPath,\n method,\n params,\n timeoutMs,\n}: SendCommandOptions): Promise<JsonRpcResponse> {\n const id = randomUUID();\n const request = {\n jsonrpc: '2.0',\n id,\n method,\n ...(params === undefined ? {} : { params }),\n };\n\n const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const attempt = async (): Promise<JsonRpcResponse> => {\n const socket = await connectSocket(socketPath);\n try {\n await writeLine(socket, JSON.stringify(request));\n const responseLine = await readLine(socket, effectiveTimeout);\n const parsed: unknown = JSON.parse(responseLine);\n assertIsJsonRpcResponse(parsed);\n if (parsed.id !== id) {\n throw new Error(\n `JSON-RPC response id ${JSON.stringify(parsed.id)} does not match request id ${JSON.stringify(id)}`,\n );\n }\n return parsed;\n } finally {\n socket.destroy();\n }\n };\n\n try {\n return await attempt();\n } catch (error: unknown) {\n if (\n !isErrorWithCode(error, 'ECONNREFUSED') &&\n !isErrorWithCode(error, 'ECONNRESET')\n ) {\n throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, 100));\n return attempt();\n }\n}\n\n/**\n * Why an unreachable daemon cannot be queried.\n *\n * - `'refused'`: connection refused after retry (`ECONNREFUSED` / `ECONNRESET`).\n * Typical of a daemon that has crashed or is mid-restart.\n * - `'timeout'`: the daemon accepted the connection but did not respond within\n * the read timeout — most likely wedged on a long-running operation.\n * - `'permission'`: the socket exists but cannot be opened (`EACCES` / `EPERM`).\n * The daemon almost certainly belongs to another user.\n * - `'protocol'`: the daemon responded but the response did not parse as a\n * valid JSON-RPC response, or the response id did not match.\n * - `'other'`: anything else.\n */\nexport type PingUnreachableReason =\n | 'refused'\n | 'timeout'\n | 'permission'\n | 'protocol'\n | 'other';\n\n/**\n * Outcome of a daemon health check.\n *\n * - `'responsive'`: the daemon answered a `getStatus` RPC.\n * - `'absent'`: the socket connect attempt failed with `ENOENT`, i.e. no\n * socket file exists at the path. No daemon present.\n * - `'unreachable'`: any other non-success outcome. The daemon may still be\n * alive but is not responding. Callers should not silently take over the\n * slot or assume the daemon is dead. The `reason` field categorises the\n * failure so callers can distinguish a wedged sibling daemon from a\n * foreign-user daemon from a transient crash. User-initiated stop / purge\n * flows may still escalate to signals against the recorded PID.\n */\nexport type PingResult =\n | { status: 'responsive' }\n | { status: 'absent' }\n | { status: 'unreachable'; reason: PingUnreachableReason; error: Error };\n\n/**\n * Normalize an unknown throw into a real Error instance so that downstream\n * consumers (which read `.message`) cannot crash on string/object throws.\n *\n * @param error - The caught value.\n * @returns An Error mirroring the caught value.\n */\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\n/**\n * Categorise an unreachable error by Node errno / message shape so callers can\n * make decisions per failure mode rather than parsing message strings.\n *\n * @param error - The caught value.\n * @returns A {@link PingUnreachableReason} label.\n */\nfunction classifyUnreachable(error: unknown): PingUnreachableReason {\n if (\n isErrorWithCode(error, 'ECONNREFUSED') ||\n isErrorWithCode(error, 'ECONNRESET')\n ) {\n return 'refused';\n }\n if (isErrorWithCode(error, 'EACCES') || isErrorWithCode(error, 'EPERM')) {\n return 'permission';\n }\n if (error instanceof Error && error.message === 'Socket read timed out') {\n return 'timeout';\n }\n if (\n error instanceof Error &&\n (error.message.includes('JSON-RPC response id') ||\n /Expected .* JSON-RPC/u.test(error.message) ||\n error.name === 'SyntaxError')\n ) {\n return 'protocol';\n }\n return 'other';\n}\n\n/**\n * Check whether the daemon is running by sending a lightweight `getStatus`\n * RPC call. Distinguishes \"no daemon present\" (socket file missing) from\n * \"daemon present but unreachable\" (socket file exists but the daemon is\n * wedged, mid-shutdown, or owned by a different user).\n *\n * @param socketPath - The Unix socket path.\n * @returns A {@link PingResult} describing the daemon's reachability.\n */\nexport async function pingDaemon(socketPath: string): Promise<PingResult> {\n try {\n await sendCommand({ socketPath, method: 'getStatus', timeoutMs: 3_000 });\n return { status: 'responsive' };\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ENOENT')) {\n return { status: 'absent' };\n }\n return {\n status: 'unreachable',\n reason: classifyUnreachable(error),\n error: toError(error),\n };\n }\n}\n"]}
|