@joe-broadhead/messaging 0.0.0-stage → 0.7.1
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 +220 -0
- package/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +36 -2
- package/dist/bin/messaging-mcp.js +26524 -0
- package/dist/bin/messaging-relay.js +19768 -0
- package/dist/bin/messaging.js +15465 -0
- package/dist/bin/messagingd.js +29449 -0
- package/dist/lib/core-testing.js +1 -0
- package/dist/lib/core.js +1 -0
- package/dist/lib/ipc-testing.js +1 -0
- package/dist/lib/ipc.js +1 -0
- package/dist/lib/modules/core/address.js +42 -0
- package/dist/lib/modules/core/agent/operations.js +595 -0
- package/dist/lib/modules/core/agent/present.js +244 -0
- package/dist/lib/modules/core/agent/text.js +79 -0
- package/dist/lib/modules/core/agent-tools.js +477 -0
- package/dist/lib/modules/core/b64u.js +20 -0
- package/dist/lib/modules/core/cards.js +55 -0
- package/dist/lib/modules/core/check.js +20 -0
- package/dist/lib/modules/core/consent.js +199 -0
- package/dist/lib/modules/core/contact-card.js +74 -0
- package/dist/lib/modules/core/conversation/context.js +80 -0
- package/dist/lib/modules/core/conversation/event.js +100 -0
- package/dist/lib/modules/core/conversation/ids.js +38 -0
- package/dist/lib/modules/core/conversation/link.js +35 -0
- package/dist/lib/modules/core/conversation/log.js +766 -0
- package/dist/lib/modules/core/conversation/ratchet.js +243 -0
- package/dist/lib/modules/core/conversation/seal.js +198 -0
- package/dist/lib/modules/core/delegation.js +174 -0
- package/dist/lib/modules/core/directory/device-log.js +258 -0
- package/dist/lib/modules/core/directory/epoch.js +103 -0
- package/dist/lib/modules/core/directory/handle.js +29 -0
- package/dist/lib/modules/core/directory/jelly-device.js +204 -0
- package/dist/lib/modules/core/directory/jelly-issuer.js +12 -0
- package/dist/lib/modules/core/directory/jelly-token.js +305 -0
- package/dist/lib/modules/core/directory/keys-statement.js +43 -0
- package/dist/lib/modules/core/directory/link.js +81 -0
- package/dist/lib/modules/core/directory/merkle-log.js +250 -0
- package/dist/lib/modules/core/directory/person-view.js +197 -0
- package/dist/lib/modules/core/directory/person.js +40 -0
- package/dist/lib/modules/core/directory/realm.js +59 -0
- package/dist/lib/modules/core/directory/smt.js +266 -0
- package/dist/lib/modules/core/envelope.js +320 -0
- package/dist/lib/modules/core/hash.js +5 -0
- package/dist/lib/modules/core/head.js +27 -0
- package/dist/lib/modules/core/identity.js +71 -0
- package/dist/lib/modules/core/index.js +53 -0
- package/dist/lib/modules/core/jcs.js +96 -0
- package/dist/lib/modules/core/jelly-host-double.js +35 -0
- package/dist/lib/modules/core/jelly-host.js +211 -0
- package/dist/lib/modules/core/keys.js +79 -0
- package/dist/lib/modules/core/peer-text.js +563 -0
- package/dist/lib/modules/core/realm.js +1 -0
- package/dist/lib/modules/core/roster.js +454 -0
- package/dist/lib/modules/core/schema/a2a.js +85 -0
- package/dist/lib/modules/core/schema/common.js +310 -0
- package/dist/lib/modules/core/schema/consent.js +120 -0
- package/dist/lib/modules/core/schema/conversation.js +292 -0
- package/dist/lib/modules/core/schema/delegation.js +58 -0
- package/dist/lib/modules/core/schema/directory-frames.js +270 -0
- package/dist/lib/modules/core/schema/directory.js +169 -0
- package/dist/lib/modules/core/schema/envelope.js +211 -0
- package/dist/lib/modules/core/schema/errors.js +104 -0
- package/dist/lib/modules/core/schema/index.js +16 -0
- package/dist/lib/modules/core/schema/ipc/consent.js +181 -0
- package/dist/lib/modules/core/schema/ipc/device.js +276 -0
- package/dist/lib/modules/core/schema/ipc/jelly.js +189 -0
- package/dist/lib/modules/core/schema/ipc/shapes.js +185 -0
- package/dist/lib/modules/core/schema/ipc/surfaces.js +223 -0
- package/dist/lib/modules/core/schema/ipc.js +451 -0
- package/dist/lib/modules/core/schema/jelly.js +122 -0
- package/dist/lib/modules/core/schema/realm.js +27 -0
- package/dist/lib/modules/core/schema/relay.js +112 -0
- package/dist/lib/modules/core/schema/roster.js +126 -0
- package/dist/lib/modules/core/sign.js +43 -0
- package/dist/lib/modules/core/task.js +37 -0
- package/dist/lib/modules/core/testing.js +21 -0
- package/dist/lib/modules/core/ulid.js +36 -0
- package/dist/lib/modules/core/version.js +6 -0
- package/dist/lib/modules/core/wire.js +255 -0
- package/dist/lib/modules/ipc/client.js +289 -0
- package/dist/lib/modules/ipc/framing.js +33 -0
- package/dist/lib/modules/ipc/index.js +6 -0
- package/dist/lib/modules/ipc/paths.js +102 -0
- package/dist/lib/modules/ipc/pipes.js +65 -0
- package/dist/lib/modules/ipc/rpc.js +8 -0
- package/dist/lib/modules/ipc/server.js +317 -0
- package/dist/lib/modules/ipc/testing.js +145 -0
- package/dist/lib/modules/ipc/trust.js +166 -0
- package/dist/lib/modules/ipc/windows-acl.js +275 -0
- package/dist/lib/modules/vendor/zod/index.js +7 -0
- package/dist/lib/modules/vendor/zod/v4/classic/checks.js +1 -0
- package/dist/lib/modules/vendor/zod/v4/classic/coerce.js +17 -0
- package/dist/lib/modules/vendor/zod/v4/classic/compat.js +31 -0
- package/dist/lib/modules/vendor/zod/v4/classic/deep-partial.js +13 -0
- package/dist/lib/modules/vendor/zod/v4/classic/errors.js +57 -0
- package/dist/lib/modules/vendor/zod/v4/classic/external.js +17 -0
- package/dist/lib/modules/vendor/zod/v4/classic/from-json-schema.js +881 -0
- package/dist/lib/modules/vendor/zod/v4/classic/in-out.js +36 -0
- package/dist/lib/modules/vendor/zod/v4/classic/iso.js +15 -0
- package/dist/lib/modules/vendor/zod/v4/classic/parse.js +16 -0
- package/dist/lib/modules/vendor/zod/v4/classic/schemas.js +1535 -0
- package/dist/lib/modules/vendor/zod/v4/core/api.js +1107 -0
- package/dist/lib/modules/vendor/zod/v4/core/checks.js +516 -0
- package/dist/lib/modules/vendor/zod/v4/core/compile.js +1866 -0
- package/dist/lib/modules/vendor/zod/v4/core/core.js +141 -0
- package/dist/lib/modules/vendor/zod/v4/core/doc.js +38 -0
- package/dist/lib/modules/vendor/zod/v4/core/errors.js +267 -0
- package/dist/lib/modules/vendor/zod/v4/core/index.js +19 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema-generator.js +99 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema-processors.js +843 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema.js +1 -0
- package/dist/lib/modules/vendor/zod/v4/core/memoizer.js +311 -0
- package/dist/lib/modules/vendor/zod/v4/core/parse.js +175 -0
- package/dist/lib/modules/vendor/zod/v4/core/regexes.js +156 -0
- package/dist/lib/modules/vendor/zod/v4/core/registries.js +51 -0
- package/dist/lib/modules/vendor/zod/v4/core/schemas.js +2636 -0
- package/dist/lib/modules/vendor/zod/v4/core/to-json-schema.js +651 -0
- package/dist/lib/modules/vendor/zod/v4/core/util.js +873 -0
- package/dist/lib/modules/vendor/zod/v4/core/versions.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/core/visit.js +169 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ar.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/az.js +110 -0
- package/dist/lib/modules/vendor/zod/v4/locales/be.js +168 -0
- package/dist/lib/modules/vendor/zod/v4/locales/bg.js +125 -0
- package/dist/lib/modules/vendor/zod/v4/locales/bn.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ca.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ckb.js +133 -0
- package/dist/lib/modules/vendor/zod/v4/locales/cs.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/da.js +120 -0
- package/dist/lib/modules/vendor/zod/v4/locales/de.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/el.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/en.js +125 -0
- package/dist/lib/modules/vendor/zod/v4/locales/eo.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/es.js +136 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fa.js +119 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fi.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fr-CA.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fr.js +129 -0
- package/dist/lib/modules/vendor/zod/v4/locales/gu.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/he.js +221 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hi.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hr.js +126 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hu.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hy.js +158 -0
- package/dist/lib/modules/vendor/zod/v4/locales/id.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/index.js +63 -0
- package/dist/lib/modules/vendor/zod/v4/locales/is.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/it.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ja.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ka.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/kh.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/locales/km.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/kn.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ko.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/lt.js +207 -0
- package/dist/lib/modules/vendor/zod/v4/locales/mk.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ms.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ne.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/nl.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/nn.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/no.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ota.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pl.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ps.js +119 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pt-BR.js +143 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pt.js +142 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ro.js +122 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ru.js +168 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sk.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sl.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sv.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ta.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tg.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/th.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tk.js +107 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tr.js +110 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ua.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/locales/uk.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ur.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/uz.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/vi.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/yo.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/zh-CN.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/zh-TW.js +112 -0
- package/dist/lib/testing-jelly-id.js +1219 -0
- package/dist/lib/zod.js +1 -0
- package/dist/spec/messaging-v1.md +3744 -0
- package/dist/third-party-notices.md +239 -0
- package/dist/third-party.json +88 -0
- package/dist/types/core/address.d.ts +16 -0
- package/dist/types/core/agent/operations.d.ts +129 -0
- package/dist/types/core/agent/present.d.ts +57 -0
- package/dist/types/core/agent/text.d.ts +62 -0
- package/dist/types/core/agent-tools.d.ts +966 -0
- package/dist/types/core/b64u.d.ts +6 -0
- package/dist/types/core/cards.d.ts +21 -0
- package/dist/types/core/check.d.ts +10 -0
- package/dist/types/core/consent.d.ts +132 -0
- package/dist/types/core/contact-card.d.ts +34 -0
- package/dist/types/core/conversation/context.d.ts +56 -0
- package/dist/types/core/conversation/event.d.ts +36 -0
- package/dist/types/core/conversation/ids.d.ts +18 -0
- package/dist/types/core/conversation/link.d.ts +16 -0
- package/dist/types/core/conversation/log.d.ts +142 -0
- package/dist/types/core/conversation/ratchet.d.ts +95 -0
- package/dist/types/core/conversation/seal.d.ts +47 -0
- package/dist/types/core/delegation.d.ts +71 -0
- package/dist/types/core/directory/device-log.d.ts +119 -0
- package/dist/types/core/directory/epoch.d.ts +53 -0
- package/dist/types/core/directory/handle.d.ts +11 -0
- package/dist/types/core/directory/jelly-device.d.ts +88 -0
- package/dist/types/core/directory/jelly-issuer.d.ts +8 -0
- package/dist/types/core/directory/jelly-token.d.ts +116 -0
- package/dist/types/core/directory/keys-statement.d.ts +22 -0
- package/dist/types/core/directory/link.d.ts +36 -0
- package/dist/types/core/directory/merkle-log.d.ts +40 -0
- package/dist/types/core/directory/person-view.d.ts +90 -0
- package/dist/types/core/directory/person.d.ts +8 -0
- package/dist/types/core/directory/realm.d.ts +23 -0
- package/dist/types/core/directory/smt.d.ts +52 -0
- package/dist/types/core/envelope.d.ts +68 -0
- package/dist/types/core/hash.d.ts +2 -0
- package/dist/types/core/head.d.ts +34 -0
- package/dist/types/core/identity.d.ts +35 -0
- package/dist/types/core/index.d.ts +45 -0
- package/dist/types/core/jcs.d.ts +12 -0
- package/dist/types/core/jelly-host-double.d.ts +21 -0
- package/dist/types/core/jelly-host.d.ts +86 -0
- package/dist/types/core/keys.d.ts +16 -0
- package/dist/types/core/peer-text.d.ts +87 -0
- package/dist/types/core/realm.d.ts +36 -0
- package/dist/types/core/roster.d.ts +146 -0
- package/dist/types/core/schema/a2a.d.ts +251 -0
- package/dist/types/core/schema/common.d.ts +328 -0
- package/dist/types/core/schema/consent.d.ts +140 -0
- package/dist/types/core/schema/conversation.d.ts +1657 -0
- package/dist/types/core/schema/delegation.d.ts +58 -0
- package/dist/types/core/schema/directory-frames.d.ts +1301 -0
- package/dist/types/core/schema/directory.d.ts +375 -0
- package/dist/types/core/schema/envelope.d.ts +756 -0
- package/dist/types/core/schema/errors.d.ts +142 -0
- package/dist/types/core/schema/index.d.ts +16 -0
- package/dist/types/core/schema/ipc/consent.d.ts +555 -0
- package/dist/types/core/schema/ipc/device.d.ts +735 -0
- package/dist/types/core/schema/ipc/jelly.d.ts +501 -0
- package/dist/types/core/schema/ipc/shapes.d.ts +414 -0
- package/dist/types/core/schema/ipc/surfaces.d.ts +680 -0
- package/dist/types/core/schema/ipc.d.ts +3146 -0
- package/dist/types/core/schema/jelly.d.ts +160 -0
- package/dist/types/core/schema/realm.d.ts +38 -0
- package/dist/types/core/schema/relay.d.ts +1134 -0
- package/dist/types/core/schema/roster.d.ts +347 -0
- package/dist/types/core/sign.d.ts +13 -0
- package/dist/types/core/task.d.ts +6 -0
- package/dist/types/core/testing.d.ts +19 -0
- package/dist/types/core/ulid.d.ts +5 -0
- package/dist/types/core/version.d.ts +6 -0
- package/dist/types/core/wire.d.ts +14 -0
- package/dist/types/ipc/client.d.ts +85 -0
- package/dist/types/ipc/framing.d.ts +12 -0
- package/dist/types/ipc/index.d.ts +6 -0
- package/dist/types/ipc/paths.d.ts +39 -0
- package/dist/types/ipc/pipes.d.ts +16 -0
- package/dist/types/ipc/rpc.d.ts +13 -0
- package/dist/types/ipc/server.d.ts +94 -0
- package/dist/types/ipc/testing.d.ts +89 -0
- package/dist/types/ipc/trust.d.ts +49 -0
- package/dist/types/ipc/windows-acl.d.ts +56 -0
- package/dist/types/testing/jelly-id.d.ts +518 -0
- package/dist/types/zod.d.ts +1 -0
- package/package.json +77 -4
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { ErrorCode, IPC_METHODS, IPC_PROTOCOL, IpcEvent, IpcHello, LIMITS, MESSAGING_ERROR_RPC_CODE, MessagingError, VERSION, parseWire, } from "../core/index.js";
|
|
4
|
+
import { encodeLine, LineDecoder } from "./framing.js";
|
|
5
|
+
import { configuredDaemonUid, IS_WINDOWS, messagingHome, socketPath as defaultSocketPath } from "./paths.js";
|
|
6
|
+
import { readPointer } from "./pipes.js";
|
|
7
|
+
import { RPC } from "./rpc.js";
|
|
8
|
+
import { checkPrivateDir, checkSocket, trustedOwners } from "./trust.js";
|
|
9
|
+
/**
|
|
10
|
+
* `daemon_mismatch`: the daemon on the socket is another release or IPC protocol, or did not say
|
|
11
|
+
* which (spec §8.2). `hello` is what it said, when it said anything this client can read.
|
|
12
|
+
*/
|
|
13
|
+
export class DaemonMismatchError extends MessagingError {
|
|
14
|
+
hello;
|
|
15
|
+
constructor(message, hello) {
|
|
16
|
+
super("daemon_mismatch", message);
|
|
17
|
+
this.hello = hello;
|
|
18
|
+
this.name = "DaemonMismatchError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const REMEDY = "Stop it with `messaging daemon stop`, then start this version's daemon.";
|
|
22
|
+
/** Another process's version text, printable and short, for a message. */
|
|
23
|
+
function shown(version) {
|
|
24
|
+
return version.replace(/[^\x21-\x7e]/g, "?").slice(0, 64);
|
|
25
|
+
}
|
|
26
|
+
/** Non-Messaging JSON-RPC failure (schema, unknown method, internal). */
|
|
27
|
+
export class IpcRpcError extends Error {
|
|
28
|
+
rpcCode;
|
|
29
|
+
constructor(rpcCode, message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.rpcCode = rpcCode;
|
|
32
|
+
this.name = "IpcRpcError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* One connection to the local daemon. It does not reconnect; watch `onClose` and connect again.
|
|
37
|
+
* Results are validated against the contract; malformed events are dropped.
|
|
38
|
+
*/
|
|
39
|
+
export class MessagingClient {
|
|
40
|
+
socket;
|
|
41
|
+
callTimeoutMs;
|
|
42
|
+
pending = new Map();
|
|
43
|
+
eventListeners = new Set();
|
|
44
|
+
closeListeners = new Set();
|
|
45
|
+
nextId = 1;
|
|
46
|
+
isClosed = false;
|
|
47
|
+
greeting;
|
|
48
|
+
/** The daemon's hello: the first line on the connection (spec §8.2). */
|
|
49
|
+
greeted;
|
|
50
|
+
daemonHello;
|
|
51
|
+
constructor(socket, callTimeoutMs) {
|
|
52
|
+
this.socket = socket;
|
|
53
|
+
this.callTimeoutMs = callTimeoutMs;
|
|
54
|
+
this.greeted = new Promise((resolve, reject) => (this.greeting = { resolve, reject }));
|
|
55
|
+
this.greeted.catch(() => undefined);
|
|
56
|
+
const decoder = new LineDecoder(LIMITS.ipcLineBytes);
|
|
57
|
+
socket.setEncoding("utf8");
|
|
58
|
+
socket.on("data", (chunk) => {
|
|
59
|
+
let lines;
|
|
60
|
+
try {
|
|
61
|
+
lines = decoder.push(chunk);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
this.shutdown(error);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const line of lines)
|
|
68
|
+
this.receive(line);
|
|
69
|
+
});
|
|
70
|
+
socket.on("error", (error) => this.shutdown(error));
|
|
71
|
+
socket.on("close", () => this.shutdown());
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Connects to the daemon and reads its hello before anything else. A daemon of another release or
|
|
75
|
+
* IPC protocol, or one that does not say which within `helloTimeoutMs`, fails with
|
|
76
|
+
* `daemon_mismatch` (`DaemonMismatchError`, with the remedy in its message) and the connection is
|
|
77
|
+
* closed. The socket's directory must be private (a real directory owned by this
|
|
78
|
+
* user, or by the configured daemon uid, that no group or other user can write) and the socket owned
|
|
79
|
+
* by one of them; otherwise this fails with `forbidden`, because whoever can write that directory can
|
|
80
|
+
* stand in for the daemon.
|
|
81
|
+
*
|
|
82
|
+
* On Windows (spec §8.2, "Windows") `socketPath` is the pointer file that names the daemon's pipe. It and its
|
|
83
|
+
* directory must be private by ACL; the client connects to the pipe it names, and refuses (`forbidden`) a
|
|
84
|
+
* hello whose `pid` is not the pointer's.
|
|
85
|
+
*/
|
|
86
|
+
static async connect(options = {}) {
|
|
87
|
+
const path = options.socketPath ?? defaultSocketPath(options.home ?? messagingHome(), options.tier ?? "agent");
|
|
88
|
+
const owners = trustedOwners(options.daemonUid ?? configuredDaemonUid());
|
|
89
|
+
let pointer;
|
|
90
|
+
try {
|
|
91
|
+
checkPrivateDir(dirname(path), "the Messaging daemon's socket directory", owners);
|
|
92
|
+
checkSocket(path, owners);
|
|
93
|
+
if (IS_WINDOWS)
|
|
94
|
+
pointer = readPointer(path);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// A missing directory or socket is no daemon: the connect below reports `daemon_unavailable`.
|
|
98
|
+
if (!(error instanceof MessagingError) || error.code !== "daemon_unavailable")
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
if (IS_WINDOWS && pointer === undefined)
|
|
102
|
+
throw new MessagingError("daemon_unavailable", "cannot reach the Messaging daemon (ENOENT)");
|
|
103
|
+
const timeout = options.connectTimeoutMs ?? 2_000;
|
|
104
|
+
const socket = await new Promise((resolve, reject) => {
|
|
105
|
+
const s = net.connect(pointer?.pipe ?? path);
|
|
106
|
+
const timer = setTimeout(() => {
|
|
107
|
+
s.destroy();
|
|
108
|
+
reject(new MessagingError("daemon_unavailable", "timed out connecting to the Messaging daemon"));
|
|
109
|
+
}, timeout);
|
|
110
|
+
s.once("connect", () => {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
resolve(s);
|
|
113
|
+
});
|
|
114
|
+
s.once("error", (error) => {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
reject(new MessagingError("daemon_unavailable", `cannot reach the Messaging daemon (${error.code ?? "error"})`, { cause: error }));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
const client = new MessagingClient(socket, options.callTimeoutMs ?? 30_000);
|
|
120
|
+
const wait = options.helloTimeoutMs ?? Math.max(timeout, 5_000);
|
|
121
|
+
let timer;
|
|
122
|
+
try {
|
|
123
|
+
const hello = await Promise.race([
|
|
124
|
+
client.greeted,
|
|
125
|
+
new Promise((_, reject) => {
|
|
126
|
+
timer = setTimeout(() => reject(new DaemonMismatchError(`the process on the Messaging daemon socket did not say which version it is within ${wait} ms: it is a messagingd from before this version, or it is stuck. ${REMEDY}`)), wait);
|
|
127
|
+
}),
|
|
128
|
+
]);
|
|
129
|
+
// A pipe found through a stale pointer may be someone else's: only the daemon that wrote it is trusted.
|
|
130
|
+
if (pointer !== undefined && hello.pid !== pointer.pid) {
|
|
131
|
+
throw new MessagingError("forbidden", "the process on the Messaging daemon's pipe is not the daemon that published it");
|
|
132
|
+
}
|
|
133
|
+
const expected = options.version ?? VERSION;
|
|
134
|
+
if (hello.protocol !== IPC_PROTOCOL || hello.version !== expected) {
|
|
135
|
+
throw new DaemonMismatchError(`the Messaging daemon for this home is version ${shown(hello.version)} (IPC protocol ${hello.protocol}), and this client is version ${shown(expected)} (IPC protocol ${IPC_PROTOCOL}). ${REMEDY}`, hello);
|
|
136
|
+
}
|
|
137
|
+
client.daemonHello = hello;
|
|
138
|
+
return client;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
client.close();
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** What the daemon said when this connection opened: its release, IPC protocol and pid. */
|
|
149
|
+
get daemon() {
|
|
150
|
+
return this.daemonHello;
|
|
151
|
+
}
|
|
152
|
+
get closed() {
|
|
153
|
+
return this.isClosed;
|
|
154
|
+
}
|
|
155
|
+
call(method, params, options = {}) {
|
|
156
|
+
if (this.isClosed)
|
|
157
|
+
return Promise.reject(new MessagingError("daemon_unavailable", "connection to the Messaging daemon is closed"));
|
|
158
|
+
if (options.signal?.aborted)
|
|
159
|
+
return Promise.reject(abortError(options.signal));
|
|
160
|
+
const id = this.nextId++;
|
|
161
|
+
const line = encodeLine({ jsonrpc: "2.0", id, method, params, ...(options.grant !== undefined ? { grant: options.grant } : {}) });
|
|
162
|
+
// The daemon closes a connection that sends an oversized line; refuse it here instead.
|
|
163
|
+
if (Buffer.byteLength(line, "utf8") - 1 > LIMITS.ipcLineBytes)
|
|
164
|
+
return Promise.reject(new MessagingError("too_large", `the ${method} request exceeds the IPC line limit`));
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
const timer = setTimeout(() => this.settle(id, undefined, new MessagingError("daemon_unavailable", `${method} timed out`)), options.timeoutMs ?? this.callTimeoutMs);
|
|
167
|
+
const onAbort = () => this.settle(id, undefined, abortError(options.signal));
|
|
168
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
169
|
+
this.pending.set(id, {
|
|
170
|
+
method,
|
|
171
|
+
resolve: resolve,
|
|
172
|
+
reject,
|
|
173
|
+
cleanup: () => {
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
this.socket.write(line);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
onEvent(listener) {
|
|
182
|
+
this.eventListeners.add(listener);
|
|
183
|
+
return () => this.eventListeners.delete(listener);
|
|
184
|
+
}
|
|
185
|
+
onClose(listener) {
|
|
186
|
+
this.closeListeners.add(listener);
|
|
187
|
+
return () => this.closeListeners.delete(listener);
|
|
188
|
+
}
|
|
189
|
+
close() {
|
|
190
|
+
this.socket.destroy();
|
|
191
|
+
this.shutdown();
|
|
192
|
+
}
|
|
193
|
+
receive(line) {
|
|
194
|
+
let message;
|
|
195
|
+
try {
|
|
196
|
+
const value = parseWire(line, { maxBytes: LIMITS.ipcLineBytes });
|
|
197
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value))
|
|
198
|
+
message = value;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
message = undefined;
|
|
202
|
+
}
|
|
203
|
+
if (this.greeting) {
|
|
204
|
+
// The first line must be the hello; anything else is not a daemon this client can talk to.
|
|
205
|
+
const greeting = this.greeting;
|
|
206
|
+
this.greeting = undefined;
|
|
207
|
+
const hello = message?.method === "hello" ? IpcHello.safeParse(message.params) : undefined;
|
|
208
|
+
if (hello?.success)
|
|
209
|
+
greeting.resolve(hello.data);
|
|
210
|
+
else
|
|
211
|
+
greeting.reject(new DaemonMismatchError(`the process on the Messaging daemon socket did not start with a hello this client can read: it is a messagingd of another version, or not a messagingd. ${REMEDY}`));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!message)
|
|
215
|
+
return;
|
|
216
|
+
if (message.method === "event") {
|
|
217
|
+
const event = IpcEvent.safeParse(message.params);
|
|
218
|
+
if (!event.success)
|
|
219
|
+
return;
|
|
220
|
+
for (const listener of this.eventListeners) {
|
|
221
|
+
try {
|
|
222
|
+
listener(event.data);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// a listener failure must not break the connection
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (typeof message.id !== "number")
|
|
231
|
+
return;
|
|
232
|
+
const pending = this.pending.get(message.id);
|
|
233
|
+
if (!pending)
|
|
234
|
+
return;
|
|
235
|
+
if (message.error !== undefined) {
|
|
236
|
+
this.settle(message.id, undefined, toError(message.error));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const checked = IPC_METHODS[pending.method].result.safeParse(message.result);
|
|
240
|
+
if (!checked.success) {
|
|
241
|
+
this.settle(message.id, undefined, new MessagingError("internal", `daemon returned an invalid result for ${pending.method}`));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
this.settle(message.id, checked.data);
|
|
245
|
+
}
|
|
246
|
+
settle(id, value, error) {
|
|
247
|
+
const pending = this.pending.get(id);
|
|
248
|
+
if (!pending)
|
|
249
|
+
return;
|
|
250
|
+
this.pending.delete(id);
|
|
251
|
+
pending.cleanup();
|
|
252
|
+
if (error)
|
|
253
|
+
pending.reject(error);
|
|
254
|
+
else
|
|
255
|
+
pending.resolve(value);
|
|
256
|
+
}
|
|
257
|
+
shutdown(error) {
|
|
258
|
+
if (this.isClosed)
|
|
259
|
+
return;
|
|
260
|
+
this.isClosed = true;
|
|
261
|
+
this.greeting?.reject(new MessagingError("daemon_unavailable", "the Messaging daemon closed the connection before it said hello"));
|
|
262
|
+
this.greeting = undefined;
|
|
263
|
+
for (const id of [...this.pending.keys()]) {
|
|
264
|
+
this.settle(id, undefined, new MessagingError("daemon_unavailable", "connection to the Messaging daemon closed"));
|
|
265
|
+
}
|
|
266
|
+
for (const listener of this.closeListeners) {
|
|
267
|
+
try {
|
|
268
|
+
listener(error);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// ignore
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
this.eventListeners.clear();
|
|
275
|
+
this.closeListeners.clear();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function toError(raw) {
|
|
279
|
+
const e = (raw ?? {});
|
|
280
|
+
const message = typeof e.message === "string" ? e.message : "daemon error";
|
|
281
|
+
if (e.code === MESSAGING_ERROR_RPC_CODE) {
|
|
282
|
+
const code = ErrorCode.safeParse(e.data?.code);
|
|
283
|
+
return new MessagingError(code.success ? code.data : "internal", message);
|
|
284
|
+
}
|
|
285
|
+
return new IpcRpcError(typeof e.code === "number" ? e.code : RPC.internalError, message);
|
|
286
|
+
}
|
|
287
|
+
function abortError(signal) {
|
|
288
|
+
return signal.reason instanceof Error ? signal.reason : new DOMException("aborted", "AbortError");
|
|
289
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Splits a byte stream into newline-delimited lines, enforcing a per-line byte limit. */
|
|
2
|
+
export class LineDecoder {
|
|
3
|
+
maxBytes;
|
|
4
|
+
pending = "";
|
|
5
|
+
constructor(maxBytes) {
|
|
6
|
+
this.maxBytes = maxBytes;
|
|
7
|
+
}
|
|
8
|
+
/** Returns complete lines. Throws `LineTooLongError` once a line exceeds the limit. */
|
|
9
|
+
push(chunk) {
|
|
10
|
+
this.pending += chunk;
|
|
11
|
+
const parts = this.pending.split("\n");
|
|
12
|
+
this.pending = parts.pop() ?? "";
|
|
13
|
+
if (Buffer.byteLength(this.pending, "utf8") > this.maxBytes)
|
|
14
|
+
throw new LineTooLongError(this.maxBytes);
|
|
15
|
+
const lines = [];
|
|
16
|
+
for (const line of parts) {
|
|
17
|
+
if (Buffer.byteLength(line, "utf8") > this.maxBytes)
|
|
18
|
+
throw new LineTooLongError(this.maxBytes);
|
|
19
|
+
if (line.trim() !== "")
|
|
20
|
+
lines.push(line);
|
|
21
|
+
}
|
|
22
|
+
return lines;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class LineTooLongError extends Error {
|
|
26
|
+
constructor(maxBytes) {
|
|
27
|
+
super(`line exceeds ${maxBytes} bytes`);
|
|
28
|
+
this.name = "LineTooLongError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function encodeLine(value) {
|
|
32
|
+
return `${JSON.stringify(value)}\n`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { DaemonMismatchError, MessagingClient, IpcRpcError } from "./client.js";
|
|
2
|
+
export { createIpcServer } from "./server.js";
|
|
3
|
+
export { configuredDaemonUid, daemonEnv, IS_WINDOWS, messagingHome, socketPath } from "./paths.js";
|
|
4
|
+
export { RPC } from "./rpc.js";
|
|
5
|
+
export { adoptPrivateDir, openNoFollow, privacyProblem, trustedOwners } from "./trust.js";
|
|
6
|
+
export { aclProblem } from "./windows-acl.js";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { MessagingError } from "../core/index.js";
|
|
4
|
+
/** macOS limits sun_path to 104 bytes including the terminator. */
|
|
5
|
+
export const MAX_SOCKET_PATH_BYTES = 103;
|
|
6
|
+
/**
|
|
7
|
+
* Windows: the daemon's endpoints are named pipes found through pointer files (`pipes.ts`), its directories
|
|
8
|
+
* and files are private by ACL (`windows-acl.ts`), and there are no POSIX signals.
|
|
9
|
+
*/
|
|
10
|
+
export const IS_WINDOWS = process.platform === "win32";
|
|
11
|
+
/** `$MESSAGING_HOME`, else `~/.messaging`. */
|
|
12
|
+
export function messagingHome(env = process.env) {
|
|
13
|
+
const configured = env.MESSAGING_HOME;
|
|
14
|
+
return configured ? resolve(configured) : join(homedir(), ".messaging");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Where a client finds the `tier` endpoint of the daemon of `home`: the socket `run/<tier>.sock`, or on Windows
|
|
18
|
+
* the pointer file `run\<tier>.pipe` that names the tier's pipe (spec §8.2, "Windows").
|
|
19
|
+
*/
|
|
20
|
+
export function socketPath(home, tier) {
|
|
21
|
+
return join(home, "run", `${tier}.${IS_WINDOWS ? "pipe" : "sock"}`);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `MESSAGING_DAEMON_UID`: the uid a client also trusts to own the daemon's `run/` directory and socket,
|
|
25
|
+
* when `messagingd` runs as its own user (spec §8.2). Unset: only this process's own user. POSIX only: on
|
|
26
|
+
* Windows a set value is `invalid_request`.
|
|
27
|
+
*/
|
|
28
|
+
export function configuredDaemonUid(env = process.env, windows = IS_WINDOWS) {
|
|
29
|
+
const raw = env.MESSAGING_DAEMON_UID;
|
|
30
|
+
if (raw === undefined || raw === "")
|
|
31
|
+
return undefined;
|
|
32
|
+
// A daemon under its own user needs an endpoint shared with a group, which Node cannot make on Windows.
|
|
33
|
+
if (windows)
|
|
34
|
+
throw new MessagingError("invalid_request", "MESSAGING_DAEMON_UID is not supported on Windows: messagingd runs as the user of its clients there");
|
|
35
|
+
if (!/^\d{1,10}$/.test(raw) || Number(raw) > 0xffff_fffe)
|
|
36
|
+
throw new MessagingError("invalid_request", "MESSAGING_DAEMON_UID must be a numeric uid");
|
|
37
|
+
return Number(raw);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Environment variables a client passes through when it starts the long-lived daemon (`messaging daemon
|
|
41
|
+
* start`, the OpenCode plugin's autostart). Provider API keys, cloud credentials, tokens and
|
|
42
|
+
* NODE_OPTIONS, which could preload code into the daemon, stay behind.
|
|
43
|
+
*/
|
|
44
|
+
export const DAEMON_ENV = [
|
|
45
|
+
"PATH",
|
|
46
|
+
"HOME",
|
|
47
|
+
"USER",
|
|
48
|
+
"LOGNAME",
|
|
49
|
+
"SHELL",
|
|
50
|
+
"LANG",
|
|
51
|
+
"LC_ALL",
|
|
52
|
+
"LC_CTYPE",
|
|
53
|
+
"TZ",
|
|
54
|
+
"TMPDIR",
|
|
55
|
+
"XDG_CONFIG_HOME",
|
|
56
|
+
"XDG_DATA_HOME",
|
|
57
|
+
"XDG_STATE_HOME",
|
|
58
|
+
"XDG_CACHE_HOME",
|
|
59
|
+
"HTTPS_PROXY",
|
|
60
|
+
"HTTP_PROXY",
|
|
61
|
+
"NO_PROXY",
|
|
62
|
+
"https_proxy",
|
|
63
|
+
"http_proxy",
|
|
64
|
+
"no_proxy",
|
|
65
|
+
"NODE_EXTRA_CA_CERTS",
|
|
66
|
+
"MESSAGING_LOG_LEVEL",
|
|
67
|
+
"MESSAGING_IDENTITY_STORE",
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* Also passed on Windows: the variables Windows programs need to run at all (`SystemRoot`: Winsock and the
|
|
71
|
+
* crypto provider fail without it), find the user's profile and temp directories, and run system tools.
|
|
72
|
+
*/
|
|
73
|
+
export const DAEMON_ENV_WINDOWS = [
|
|
74
|
+
"SystemRoot",
|
|
75
|
+
"SystemDrive",
|
|
76
|
+
"windir",
|
|
77
|
+
"ComSpec",
|
|
78
|
+
"PATHEXT",
|
|
79
|
+
"USERPROFILE",
|
|
80
|
+
"USERNAME",
|
|
81
|
+
"USERDOMAIN",
|
|
82
|
+
"HOMEDRIVE",
|
|
83
|
+
"HOMEPATH",
|
|
84
|
+
"APPDATA",
|
|
85
|
+
"LOCALAPPDATA",
|
|
86
|
+
"ProgramData",
|
|
87
|
+
"TEMP",
|
|
88
|
+
"TMP",
|
|
89
|
+
"NUMBER_OF_PROCESSORS",
|
|
90
|
+
"PROCESSOR_ARCHITECTURE",
|
|
91
|
+
];
|
|
92
|
+
/** The daemon environment: the `DAEMON_ENV` part of `env` (and on Windows `DAEMON_ENV_WINDOWS`), plus `MESSAGING_HOME`. */
|
|
93
|
+
export function daemonEnv(env, home, windows = IS_WINDOWS) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const name of windows ? [...DAEMON_ENV, ...DAEMON_ENV_WINDOWS] : DAEMON_ENV) {
|
|
96
|
+
const value = env[name];
|
|
97
|
+
if (value !== undefined)
|
|
98
|
+
out[name] = value;
|
|
99
|
+
}
|
|
100
|
+
out.MESSAGING_HOME = home;
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The daemon's endpoints on Windows (spec §8.2, "Windows"): a named pipe per tier, found through a pointer file
|
|
3
|
+
* in `run\` that only this user can write.
|
|
4
|
+
*
|
|
5
|
+
* Pipe names share one machine-wide namespace in which any user can create a pipe. A predictable name could be
|
|
6
|
+
* created by someone else before the daemon starts, and a client would then hand that process its session keys
|
|
7
|
+
* and messages. So every server picks a fresh name with 128 random bits, `\\.\pipe\messaging-<hex>-<tier>`, and
|
|
8
|
+
* publishes it, with its pid, in `run\<tier>.pipe`. The pointer file is the trust root, as `run/` is on POSIX:
|
|
9
|
+
* clients check its ACL and its directory's before they read it, and require the daemon's hello to name the
|
|
10
|
+
* pointer's pid.
|
|
11
|
+
*/
|
|
12
|
+
import { randomBytes } from "node:crypto";
|
|
13
|
+
import { readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { MessagingError, parseWire } from "../core/index.js";
|
|
15
|
+
/** The only pipe names a client connects to. */
|
|
16
|
+
export const PIPE_NAME = /^\\\\\.\\pipe\\messaging-[0-9a-f]{32}-(?:agent|admin)$/;
|
|
17
|
+
const POINTER_BYTES = 1_024;
|
|
18
|
+
/** A fresh, unpredictable pipe name for one server of `tier`. */
|
|
19
|
+
export function newPipeName(tier) {
|
|
20
|
+
return `\\\\.\\pipe\\messaging-${randomBytes(16).toString("hex")}-${tier}`;
|
|
21
|
+
}
|
|
22
|
+
/** Writes the pointer atomically: a temp file beside it (inheriting the directory's ACL), renamed over it. */
|
|
23
|
+
export function writePointer(path, pointer) {
|
|
24
|
+
const temp = `${path}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
25
|
+
try {
|
|
26
|
+
writeFileSync(temp, `${JSON.stringify(pointer)}\n`, { flag: "wx" });
|
|
27
|
+
renameSync(temp, path);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
rmSync(temp, { force: true });
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reads a pointer file: `daemon_unavailable` when it is missing, `forbidden` when it is not exactly
|
|
36
|
+
* `{pipe, pid}` with a pipe name of the shape `newPipeName` makes.
|
|
37
|
+
*/
|
|
38
|
+
export function readPointer(path) {
|
|
39
|
+
let text;
|
|
40
|
+
try {
|
|
41
|
+
const bytes = readFileSync(path);
|
|
42
|
+
if (bytes.length > POINTER_BYTES)
|
|
43
|
+
throw new MessagingError("forbidden", "the Messaging daemon's pipe file is too large");
|
|
44
|
+
text = bytes.toString("utf8");
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof MessagingError)
|
|
48
|
+
throw error;
|
|
49
|
+
if (error.code === "ENOENT")
|
|
50
|
+
throw new MessagingError("daemon_unavailable", "the Messaging daemon's pipe file does not exist");
|
|
51
|
+
throw new MessagingError("forbidden", "cannot read the Messaging daemon's pipe file");
|
|
52
|
+
}
|
|
53
|
+
let value;
|
|
54
|
+
try {
|
|
55
|
+
value = parseWire(text);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new MessagingError("forbidden", "the Messaging daemon's pipe file is not valid JSON");
|
|
59
|
+
}
|
|
60
|
+
const { pipe, pid, ...rest } = (typeof value === "object" && value !== null && !Array.isArray(value) ? value : {});
|
|
61
|
+
if (typeof pipe !== "string" || !PIPE_NAME.test(pipe) || typeof pid !== "number" || !Number.isSafeInteger(pid) || pid < 1 || Object.keys(rest).length > 0) {
|
|
62
|
+
throw new MessagingError("forbidden", "the Messaging daemon's pipe file does not name a Messaging pipe");
|
|
63
|
+
}
|
|
64
|
+
return { pipe, pid };
|
|
65
|
+
}
|