@vscode/component-explorer-cli 0.2.1-18 → 0.2.1-180
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/README.md +130 -12
- package/dist/WorktreePool.d.ts +5 -4
- package/dist/WorktreePool.d.ts.map +1 -1
- package/dist/WorktreePool.js +3 -3
- package/dist/WorktreePool.js.map +1 -1
- package/dist/_virtual/_build-info.js +1 -1
- package/dist/browserPage.d.ts +55 -1
- package/dist/browserPage.d.ts.map +1 -1
- package/dist/browserPage.js +103 -7
- package/dist/browserPage.js.map +1 -1
- package/dist/browserPage.test.d.ts +2 -0
- package/dist/browserPage.test.d.ts.map +1 -0
- package/dist/commands/acceptCommand.d.ts +1 -1
- package/dist/commands/acceptCommand.d.ts.map +1 -1
- package/dist/commands/acceptCommand.js +21 -14
- package/dist/commands/acceptCommand.js.map +1 -1
- package/dist/commands/checkStabilityCommand.d.ts +2 -0
- package/dist/commands/checkStabilityCommand.d.ts.map +1 -1
- package/dist/commands/checkStabilityCommand.js +25 -8
- package/dist/commands/checkStabilityCommand.js.map +1 -1
- package/dist/commands/compareCommand.d.ts +1 -1
- package/dist/commands/compareCommand.d.ts.map +1 -1
- package/dist/commands/compareCommand.js +31 -22
- package/dist/commands/compareCommand.js.map +1 -1
- package/dist/commands/inputArg.d.ts +33 -0
- package/dist/commands/inputArg.d.ts.map +1 -0
- package/dist/commands/inputArg.js +103 -0
- package/dist/commands/inputArg.js.map +1 -0
- package/dist/commands/mcpCommand.d.ts.map +1 -1
- package/dist/commands/mcpCommand.js +18 -12
- package/dist/commands/mcpCommand.js.map +1 -1
- package/dist/commands/renderCommand.d.ts +10 -1
- package/dist/commands/renderCommand.d.ts.map +1 -1
- package/dist/commands/renderCommand.js +390 -61
- package/dist/commands/renderCommand.js.map +1 -1
- package/dist/commands/renderCommand.test.d.ts +2 -0
- package/dist/commands/renderCommand.test.d.ts.map +1 -0
- package/dist/commands/serveCommand.d.ts +4 -1
- package/dist/commands/serveCommand.d.ts.map +1 -1
- package/dist/commands/serveCommand.js +105 -36
- package/dist/commands/serveCommand.js.map +1 -1
- package/dist/commands/serviceDiffCommitsCommand.d.ts.map +1 -1
- package/dist/commands/serviceDiffCommitsCommand.js +10 -9
- package/dist/commands/serviceDiffCommitsCommand.js.map +1 -1
- package/dist/commands/watchCommand.d.ts +1 -0
- package/dist/commands/watchCommand.d.ts.map +1 -1
- package/dist/commands/watchCommand.js +31 -20
- package/dist/commands/watchCommand.js.map +1 -1
- package/dist/comparison.d.ts +4 -3
- package/dist/comparison.d.ts.map +1 -1
- package/dist/comparison.js +17 -14
- package/dist/comparison.js.map +1 -1
- package/dist/component-explorer-config.schema.json +55 -2
- package/dist/componentExplorer.d.ts +110 -12
- package/dist/componentExplorer.d.ts.map +1 -1
- package/dist/componentExplorer.js +177 -24
- package/dist/componentExplorer.js.map +1 -1
- package/dist/config.d.ts +33 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +64 -27
- package/dist/config.js.map +1 -1
- package/dist/coverage.d.ts +64 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +212 -0
- package/dist/coverage.js.map +1 -0
- package/dist/daemon/DaemonService.d.ts +5 -199
- package/dist/daemon/DaemonService.d.ts.map +1 -1
- package/dist/daemon/DaemonService.js +248 -300
- package/dist/daemon/DaemonService.js.map +1 -1
- package/dist/daemon/approvalStore.d.ts +2 -1
- package/dist/daemon/approvalStore.d.ts.map +1 -1
- package/dist/daemon/approvalStore.js +4 -3
- package/dist/daemon/approvalStore.js.map +1 -1
- package/dist/daemon/client.d.ts +76 -5
- package/dist/daemon/client.d.ts.map +1 -1
- package/dist/daemon/daemonRpc.test.d.ts +2 -0
- package/dist/daemon/daemonRpc.test.d.ts.map +1 -0
- package/dist/daemon/endpointServer.d.ts +4 -0
- package/dist/daemon/endpointServer.d.ts.map +1 -0
- package/dist/daemon/endpointServer.js +24 -0
- package/dist/daemon/endpointServer.js.map +1 -0
- package/dist/daemon/inProcessClient.d.ts +3 -9
- package/dist/daemon/inProcessClient.d.ts.map +1 -1
- package/dist/daemon/inProcessClient.js +29 -30
- package/dist/daemon/inProcessClient.js.map +1 -1
- package/dist/daemon/lifecycle.d.ts +5 -5
- package/dist/daemon/lifecycle.d.ts.map +1 -1
- package/dist/daemon/lifecycle.js +4 -3
- package/dist/daemon/lifecycle.js.map +1 -1
- package/dist/daemon/pipeClient.d.ts +2 -3
- package/dist/daemon/pipeClient.d.ts.map +1 -1
- package/dist/daemon/pipeClient.js +93 -194
- package/dist/daemon/pipeClient.js.map +1 -1
- package/dist/daemon/pipeName.d.ts +3 -3
- package/dist/daemon/pipeName.d.ts.map +1 -1
- package/dist/daemon/pipeName.js +6 -5
- package/dist/daemon/pipeName.js.map +1 -1
- package/dist/daemon/pipeServer.d.ts +2 -2
- package/dist/daemon/pipeServer.d.ts.map +1 -1
- package/dist/daemon/pipeServer.js +79 -70
- package/dist/daemon/pipeServer.js.map +1 -1
- package/dist/daemon/registerDaemonConnection.d.ts +7 -0
- package/dist/daemon/registerDaemonConnection.d.ts.map +1 -0
- package/dist/daemon/registerDaemonConnection.js +37 -0
- package/dist/daemon/registerDaemonConnection.js.map +1 -0
- package/dist/daemon/transports.test.d.ts +2 -0
- package/dist/daemon/transports.test.d.ts.map +1 -0
- package/dist/daemon/types.d.ts +1655 -8
- package/dist/daemon/types.d.ts.map +1 -1
- package/dist/daemon/types.js +4 -0
- package/dist/daemon/types.js.map +1 -0
- package/dist/daemon/version.d.ts +1 -5
- package/dist/daemon/version.d.ts.map +1 -1
- package/dist/daemon/version.js +3 -7
- package/dist/daemon/version.js.map +1 -1
- package/dist/dependencyInstaller.d.ts +2 -1
- package/dist/dependencyInstaller.d.ts.map +1 -1
- package/dist/dependencyInstaller.js +4 -4
- package/dist/dependencyInstaller.js.map +1 -1
- package/dist/explorerSession.d.ts +5 -1
- package/dist/explorerSession.d.ts.map +1 -1
- package/dist/explorerSession.js +24 -7
- package/dist/explorerSession.js.map +1 -1
- package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/inspectionHost-BA6ZmC_P.js +2523 -0
- package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/inspectionHost-BA6ZmC_P.js.map +1 -0
- package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js +1871 -0
- package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js.map +1 -0
- package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/webSocketTransport-Ct4OIdG9.js +220 -0
- package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/webSocketTransport-Ct4OIdG9.js.map +1 -0
- package/dist/external/linkrpc/typescript/packages/linkrpc/dist/node.js +721 -0
- package/dist/external/linkrpc/typescript/packages/linkrpc/dist/node.js.map +1 -0
- package/dist/git/gitCommitResolver.d.ts +2 -1
- package/dist/git/gitCommitResolver.d.ts.map +1 -1
- package/dist/git/gitCommitResolver.js +8 -8
- package/dist/git/gitCommitResolver.js.map +1 -1
- package/dist/git/gitIndexResolver.d.ts +2 -1
- package/dist/git/gitIndexResolver.d.ts.map +1 -1
- package/dist/git/gitIndexResolver.js +7 -7
- package/dist/git/gitIndexResolver.js.map +1 -1
- package/dist/git/gitService.d.ts +5 -4
- package/dist/git/gitService.d.ts.map +1 -1
- package/dist/git/gitService.js.map +1 -1
- package/dist/git/gitUtils.d.ts +6 -5
- package/dist/git/gitUtils.d.ts.map +1 -1
- package/dist/git/gitUtils.js +5 -4
- package/dist/git/gitUtils.js.map +1 -1
- package/dist/git/gitWorktreeManager.d.ts +15 -14
- package/dist/git/gitWorktreeManager.d.ts.map +1 -1
- package/dist/git/gitWorktreeManager.js +10 -11
- package/dist/git/gitWorktreeManager.js.map +1 -1
- package/dist/git/testUtils.d.ts +2 -1
- package/dist/git/testUtils.d.ts.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/js-component-explorer/packages/common/dist/daemonRpc.js +165 -0
- package/dist/js-component-explorer/packages/common/dist/daemonRpc.js.map +1 -0
- package/dist/js-component-explorer/packages/common/dist/explorerUrl.js +21 -0
- package/dist/js-component-explorer/packages/common/dist/explorerUrl.js.map +1 -0
- package/dist/js-component-explorer/packages/common/dist/renderManifest.js +68 -0
- package/dist/js-component-explorer/packages/common/dist/renderManifest.js.map +1 -0
- package/dist/js-component-explorer/packages/semver/dist/index.js.map +1 -0
- package/dist/manifest.schema.json +44 -16
- package/dist/mcp/DaemonAccessor.d.ts +2 -3
- package/dist/mcp/DaemonAccessor.d.ts.map +1 -1
- package/dist/mcp/McpServer.d.ts +1 -1
- package/dist/mcp/McpServer.d.ts.map +1 -1
- package/dist/mcp/McpServer.js +252 -159
- package/dist/mcp/McpServer.js.map +1 -1
- package/dist/path.d.ts +30 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +78 -0
- package/dist/path.js.map +1 -0
- package/dist/processTree.d.ts +6 -1
- package/dist/processTree.d.ts.map +1 -1
- package/dist/processTree.js +48 -5
- package/dist/processTree.js.map +1 -1
- package/dist/renderManifest.d.ts +70 -5
- package/dist/renderManifest.js +1 -1
- package/dist/server/httpServer.d.ts +1 -1
- package/dist/server/httpServer.d.ts.map +1 -1
- package/dist/server/httpServer.js +4 -8
- package/dist/server/httpServer.js.map +1 -1
- package/dist/server/serverConfig.d.ts +23 -8
- package/dist/server/serverConfig.d.ts.map +1 -1
- package/dist/server/serverConfig.js +11 -18
- package/dist/server/serverConfig.js.map +1 -1
- package/dist/server/viteServer.d.ts +16 -3
- package/dist/server/viteServer.d.ts.map +1 -1
- package/dist/server/viteServer.js +86 -5
- package/dist/server/viteServer.js.map +1 -1
- package/dist/storage.d.ts +2 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1 -1
- package/dist/storage.js.map +1 -1
- package/dist/utils.d.ts +3 -30
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -24
- package/dist/utils.js.map +1 -1
- package/dist/visualCache.d.ts +2 -1
- package/dist/visualCache.d.ts.map +1 -1
- package/dist/visualCache.js +4 -3
- package/dist/visualCache.js.map +1 -1
- package/dist/viteProjectRef.d.ts +4 -12
- package/dist/viteProjectRef.d.ts.map +1 -1
- package/dist/viteProjectRef.js +9 -17
- package/dist/viteProjectRef.js.map +1 -1
- package/dist/vscode-observables/observables/dist/disposables.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/base.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/commonFacade/deps.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/debugLocation.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/debugName.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/index.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/logging/consoleObservableLogger.js.map +1 -0
- package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/debugGetDependencyGraph.js +1 -1
- package/dist/vscode-observables/observables/dist/observableInternal/logging/debugGetDependencyGraph.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/logging/logging.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/baseObservable.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/derived.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/derivedImpl.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/observableValue.js.map +1 -0
- package/dist/{external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js → vscode-observables/observables/dist/observableInternal/reactions/createEffect.js} +9 -5
- package/dist/vscode-observables/observables/dist/observableInternal/reactions/createEffect.js.map +1 -0
- package/dist/{external/vscode-observables/observables/dist/observableInternal/reactions/autorunImpl.js → vscode-observables/observables/dist/observableInternal/reactions/createEffectImpl.js} +1 -1
- package/dist/vscode-observables/observables/dist/observableInternal/reactions/createEffectImpl.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/transaction.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/utils/utils.js.map +1 -0
- package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/utils/utilsCancellation.js +2 -2
- package/dist/vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js.map +1 -0
- package/package.json +18 -15
- package/dist/commands/artifactCommand.d.ts +0 -14
- package/dist/commands/artifactCommand.d.ts.map +0 -1
- package/dist/external/vscode-observables/observables/dist/disposables.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/base.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/commonFacade/deps.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/debugLocation.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/debugName.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/index.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/logging/consoleObservableLogger.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/logging/debugGetDependencyGraph.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/logging/logging.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/baseObservable.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/derived.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/derivedImpl.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/observableValue.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/reactions/autorunImpl.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/transaction.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/utils/utils.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js.map +0 -1
- package/dist/formatValue.js +0 -96
- package/dist/formatValue.js.map +0 -1
- package/dist/packages/common/dist/renderManifest.js +0 -46
- package/dist/packages/common/dist/renderManifest.js.map +0 -1
- package/dist/packages/semver/dist/index.js.map +0 -1
- package/dist/packages/simple-api/dist/chunk-FJ7AVNQE.js +0 -138
- package/dist/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +0 -1
- package/dist/packages/simple-api/dist/chunk-TTRCY65Z.js +0 -27
- package/dist/packages/simple-api/dist/chunk-TTRCY65Z.js.map +0 -1
- package/dist/packages/simple-api/dist/chunk-WNXMRXWV.js +0 -28
- package/dist/packages/simple-api/dist/chunk-WNXMRXWV.js.map +0 -1
- package/dist/packages/simple-api/dist/express.js +0 -104
- package/dist/packages/simple-api/dist/express.js.map +0 -1
- /package/dist/{packages → js-component-explorer/packages}/semver/dist/index.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/disposables.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/base.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/commonFacade/deps.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/debugLocation.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/debugName.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/index.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/consoleObservableLogger.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/logging.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/baseObservable.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/derived.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/derivedImpl.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/observableFromEvent.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/observableValue.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/transaction.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/utils/utils.js +0 -0
|
@@ -0,0 +1,1871 @@
|
|
|
1
|
+
import { Q as preferRicherInterfaceSchema, xt as ErrorCode, d as nodeInterface, t as InspectionHost, l as StreamDir, c as StreamControlType, o as STREAM_METHOD, wt as isResponse, St as isNotification, Ct as isRequest, M as bytesToBase64Url, z as defineInterface, mt as requestType, Tt as RpcFailure, ft as isApplicationErrorValue, vt as parseDeclaredErrorBody, _t as jsonIssues, bt as brandApplicationError } from './inspectionHost-BA6ZmC_P.js';
|
|
2
|
+
import { object, array, string, optional, boolean, number, int, nonnegative, positive, unknown, union } from 'zod/mini';
|
|
3
|
+
import { safeParse } from 'zod/v4/core';
|
|
4
|
+
|
|
5
|
+
/*---------------------------------------------------------------------------------------------
|
|
6
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
7
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
8
|
+
*--------------------------------------------------------------------------------------------*/
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/connection/nonCompliantServerError.ts
|
|
11
|
+
var NonCompliantServerError = class extends Error {
|
|
12
|
+
original;
|
|
13
|
+
issues;
|
|
14
|
+
kind = "nonCompliantServer";
|
|
15
|
+
constructor(original, issues) {
|
|
16
|
+
super(`Server error ${original.code} does not match its declared schema: ${issues.map((issue) => `${issue.path || "/"}: ${issue.message}`).join("; ")}`);
|
|
17
|
+
this.original = original;
|
|
18
|
+
this.issues = issues;
|
|
19
|
+
this.name = "NonCompliantServerError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function validateBarePrefix(prefix) {
|
|
23
|
+
if (prefix.includes("::") || !/^[\x20-\x7e]*$/.test(prefix)) throw new Error("Bare interface prefix must contain only printable ASCII and must not contain \"::\".");
|
|
24
|
+
}
|
|
25
|
+
new Set("break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with implements interface let package private protected public static yield await".split(" "));
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/protocol/methodName.ts
|
|
28
|
+
/**
|
|
29
|
+
* Parse a JSON-RPC method string. Returns `undefined` if any segment is
|
|
30
|
+
* empty or the segment count is out of range.
|
|
31
|
+
*/
|
|
32
|
+
function parseMethodName(method) {
|
|
33
|
+
const parts = method.split("::");
|
|
34
|
+
if (parts.length === 3 && parts[0].length === 0 && parts[1].length > 0 && parts[2].length > 0) return {
|
|
35
|
+
kind: "interface",
|
|
36
|
+
interfaceId: parts[1],
|
|
37
|
+
member: parts[2]
|
|
38
|
+
};
|
|
39
|
+
if (parts.some((p) => p.length === 0)) return void 0;
|
|
40
|
+
if (parts.length === 1) return {
|
|
41
|
+
kind: "bare",
|
|
42
|
+
member: parts[0]
|
|
43
|
+
};
|
|
44
|
+
if (parts.length === 2) return {
|
|
45
|
+
kind: "interface",
|
|
46
|
+
interfaceId: parts[0],
|
|
47
|
+
member: parts[1]
|
|
48
|
+
};
|
|
49
|
+
if (parts.length === 3) return {
|
|
50
|
+
kind: "full",
|
|
51
|
+
serviceId: parts[0],
|
|
52
|
+
interfaceId: parts[1],
|
|
53
|
+
member: parts[2]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/protocol/signedObject.ts
|
|
58
|
+
/**
|
|
59
|
+
* The HubRPC **signed-object** standard: one rule for committing a
|
|
60
|
+
* signature (and a content hash) over any JSON object.
|
|
61
|
+
*
|
|
62
|
+
* > The signature `obj.$hubrpcSignature[domain]` covers
|
|
63
|
+
* > `jcs({ [domainValue(domain)]: obj without $hubrpcSignature and $hubrpcUnsigned })`.
|
|
64
|
+
*
|
|
65
|
+
* Both reserved keys are stripped before canonicalization — on **every**
|
|
66
|
+
* object, call or capability alike:
|
|
67
|
+
*
|
|
68
|
+
* - {@link HUBRPC_SIGNATURE_KEY} (`$hubrpcSignature`) holds the signature
|
|
69
|
+
* itself (a domain-keyed map of `{ keyId, sig }` entries). It can't sign
|
|
70
|
+
* itself, so it's excluded.
|
|
71
|
+
* - {@link HUBRPC_UNSIGNED_KEY} (`$hubrpcUnsigned`) holds extrinsic
|
|
72
|
+
* *attachments* (e.g. the presented capability bag) authored by someone
|
|
73
|
+
* other than this signer, so it's excluded too.
|
|
74
|
+
*
|
|
75
|
+
* Domain separation is intrinsic: the per-domain {@link signingDomainValue}
|
|
76
|
+
* is the sole key of the canonicalized wrapper object. A `"call"` signature
|
|
77
|
+
* can therefore never verify as a `"capability"` (or vice-versa). The domain
|
|
78
|
+
* is *supplied by context* by signer and verifier;
|
|
79
|
+
* the on-wire `$hubrpcSignature` key is a convenience index, never a trust
|
|
80
|
+
* claim.
|
|
81
|
+
*
|
|
82
|
+
* This module is pure (no private keys, no `crypto.verify`): it only defines
|
|
83
|
+
* the canonical bytes. Signing/verifying live in `../identity/signedObject`.
|
|
84
|
+
*/
|
|
85
|
+
/** Reserved wire-key: the signature map `{ [domain]: SignatureEnvelope }`. */
|
|
86
|
+
const HUBRPC_SIGNATURE_KEY = "$hubrpcSignature";
|
|
87
|
+
/** Reserved wire-key: extrinsic unsigned attachments (e.g. the capability bag). */
|
|
88
|
+
const HUBRPC_UNSIGNED_KEY = "$hubrpcUnsigned";
|
|
89
|
+
/** Reserved wire-key: signed call meta, present only on calls (dodges the JSON-RPC param namespace). */
|
|
90
|
+
const HUBRPC_META_KEY = "$hubrpc";
|
|
91
|
+
/**
|
|
92
|
+
* Return the application-authored portion of a LinkRPC params value.
|
|
93
|
+
*
|
|
94
|
+
* Routing and gate layers must continue forwarding the original wire params so
|
|
95
|
+
* every chained hub can independently verify the envelope. Terminal dispatch
|
|
96
|
+
* may use this derived value for application-schema validation without mutating
|
|
97
|
+
* or replacing the wire message.
|
|
98
|
+
*/
|
|
99
|
+
function stripLinkRpcWireMeta(wireParams) {
|
|
100
|
+
if (wireParams === void 0) return void 0;
|
|
101
|
+
if (wireParams === null || typeof wireParams !== "object" || Array.isArray(wireParams)) return wireParams;
|
|
102
|
+
const object = wireParams;
|
|
103
|
+
const reserved = [
|
|
104
|
+
HUBRPC_META_KEY,
|
|
105
|
+
HUBRPC_SIGNATURE_KEY,
|
|
106
|
+
HUBRPC_UNSIGNED_KEY
|
|
107
|
+
];
|
|
108
|
+
if (!reserved.some((key) => key in object)) return object;
|
|
109
|
+
const userParams = {};
|
|
110
|
+
for (const key of Object.keys(object)) {
|
|
111
|
+
if (reserved.includes(key)) continue;
|
|
112
|
+
userParams[key] = object[key];
|
|
113
|
+
}
|
|
114
|
+
return userParams;
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/hub/common/reflection.interfaces.ts
|
|
118
|
+
const defaultsInterface = defineInterface({
|
|
119
|
+
id: "hubrpc.defaults",
|
|
120
|
+
description: "Reflection: preset default service / interface on this connection."
|
|
121
|
+
}, {
|
|
122
|
+
get: requestType(object({}), object({
|
|
123
|
+
serviceId: optional(string()),
|
|
124
|
+
interfaceId: optional(string()),
|
|
125
|
+
/** Hash of the preset interface, if known. */
|
|
126
|
+
interfaceHash: optional(string())
|
|
127
|
+
})),
|
|
128
|
+
listBindings: requestType(object({}), object({ bindings: array(object({
|
|
129
|
+
prefix: string(),
|
|
130
|
+
serviceId: optional(string()),
|
|
131
|
+
interfaceId: string(),
|
|
132
|
+
interfaceHash: string()
|
|
133
|
+
})) }))
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* A single root-principal requirement. `transitive: true` means the requirement
|
|
137
|
+
* also applies to every service reachable *through* this one — i.e. when this
|
|
138
|
+
* listing is a `hubrpc.directory` reference, everything it lists inherits the
|
|
139
|
+
* requirement (also as transitive).
|
|
140
|
+
*/
|
|
141
|
+
const zRootPrincipalReq = object({
|
|
142
|
+
principal: string(),
|
|
143
|
+
transitive: optional(boolean())
|
|
144
|
+
});
|
|
145
|
+
/** One OR-set. The set is satisfied by holding *any one* of its principals. */
|
|
146
|
+
const zRootPrincipalSet = array(zRootPrincipalReq);
|
|
147
|
+
const zServiceIdPattern = union([object({ exact: string() }), object({ prefix: string() })]);
|
|
148
|
+
const zServiceListing = object({
|
|
149
|
+
serviceId: string(),
|
|
150
|
+
interfaceId: string(),
|
|
151
|
+
/** Hash of the interface as implemented by this service. */
|
|
152
|
+
interfaceHash: string(),
|
|
153
|
+
/** Optional, non-authoritative discovery labels of this implemented interface. */
|
|
154
|
+
tags: optional(array(string())),
|
|
155
|
+
/** Optional non-normative description of the owning service. */
|
|
156
|
+
serviceDescription: optional(string()),
|
|
157
|
+
/**
|
|
158
|
+
* Root principals required to access this service, in CNF: the caller must
|
|
159
|
+
* satisfy **every** set (AND), and a set is satisfied by **any one** of its
|
|
160
|
+
* principals (OR). Omitted/empty => no root-principal requirement.
|
|
161
|
+
*/
|
|
162
|
+
rootPrincipalSets: optional(array(zRootPrincipalSet)),
|
|
163
|
+
/**
|
|
164
|
+
* Service-id regions reachable through this referral. Meaningful only for
|
|
165
|
+
* `hubrpc.directory` entries; omission defaults to the referral service-id
|
|
166
|
+
* subtree.
|
|
167
|
+
*/
|
|
168
|
+
reachableServiceIds: optional(array(zServiceIdPattern))
|
|
169
|
+
});
|
|
170
|
+
const directoryInterface = defineInterface({
|
|
171
|
+
id: "hubrpc.directory",
|
|
172
|
+
description: "Reflection: list services exposed by this endpoint. Can also list other directory services that can be explored."
|
|
173
|
+
}, {
|
|
174
|
+
list: requestType(object({
|
|
175
|
+
/** Filter: only return services implementing this interface id. */
|
|
176
|
+
interfaceId: optional(string()),
|
|
177
|
+
/** Filter: only return services whose interface id starts with this prefix. */
|
|
178
|
+
interfaceIdPrefix: optional(string()),
|
|
179
|
+
/** Filter: only return entries for this service id. */
|
|
180
|
+
serviceId: optional(string()),
|
|
181
|
+
/** Union filter used to constrain a transitive directory walk. */
|
|
182
|
+
serviceIdScopes: optional(array(zServiceIdPattern)),
|
|
183
|
+
/** Paging: opaque continuation token from a previous response. */
|
|
184
|
+
cursor: optional(string()),
|
|
185
|
+
/** Paging: max items in this page. Server MAY return fewer. */
|
|
186
|
+
limit: optional(number().check(int(), positive())),
|
|
187
|
+
/** Soft cap on time the server spends gathering this page, in ms. */
|
|
188
|
+
timeoutMs: optional(number().check(int(), nonnegative()))
|
|
189
|
+
}), object({
|
|
190
|
+
items: array(zServiceListing),
|
|
191
|
+
/** Omitted => no more pages. */
|
|
192
|
+
nextCursor: optional(string()),
|
|
193
|
+
/** True if `timeoutMs` cut the page short before exhausting results. */
|
|
194
|
+
truncated: optional(boolean())
|
|
195
|
+
})),
|
|
196
|
+
/**
|
|
197
|
+
* Coarse change tap on the directory.
|
|
198
|
+
*
|
|
199
|
+
* `watch` is a long-lived streaming request that emits an **empty tick**
|
|
200
|
+
* whenever the (optionally filtered) directory *might* have changed. The
|
|
201
|
+
* tick carries no delta and no payload — its only meaning is "re-`list`
|
|
202
|
+
* now". The consumer reconciles against its own last snapshot.
|
|
203
|
+
*
|
|
204
|
+
* This keeps the server stateless: it never computes or replays
|
|
205
|
+
* per-item deltas, never does an initial-sync replay. Over-emission is
|
|
206
|
+
* allowed (the consumer re-lists and finds nothing new); under-emission
|
|
207
|
+
* is not. Ticks are coalesced. The `interfaceId` / `serviceId` filters
|
|
208
|
+
* mirror `list` and are a relevance hint, not a guarantee.
|
|
209
|
+
*
|
|
210
|
+
* The request resolves when the caller cancels (or the connection
|
|
211
|
+
* drops); the runtime auto-detaches the stream when it settles.
|
|
212
|
+
*/
|
|
213
|
+
watch: requestType(object({
|
|
214
|
+
/** Relevance hint: changes touching this interface id. */
|
|
215
|
+
interfaceId: optional(string()),
|
|
216
|
+
/** Relevance hint: changes touching an interface id with this prefix. */
|
|
217
|
+
interfaceIdPrefix: optional(string()),
|
|
218
|
+
/** Relevance hint: changes touching this service id. */
|
|
219
|
+
serviceId: optional(string()),
|
|
220
|
+
/** Relevance hint: changes within any of these service-id regions. */
|
|
221
|
+
serviceIdScopes: optional(array(zServiceIdPattern))
|
|
222
|
+
}), object({})).withStream({
|
|
223
|
+
/** Empty tick: "the directory may have changed; re-list." */
|
|
224
|
+
server: object({}) })
|
|
225
|
+
});
|
|
226
|
+
const schemasInterface = defineInterface({
|
|
227
|
+
id: "hubrpc.schemas",
|
|
228
|
+
description: "Reflection: fetch interface schemas by id (+ optional hash). Must serve the interfaces advertised in this endpoint's directory."
|
|
229
|
+
}, { get: requestType(object({
|
|
230
|
+
interfaceId: string(),
|
|
231
|
+
/** Omit => server picks the version it exposes. */
|
|
232
|
+
hash: optional(string())
|
|
233
|
+
}), object({
|
|
234
|
+
/** A full `SvcInterfaceSchema`. */
|
|
235
|
+
schema: unknown() })) });
|
|
236
|
+
/** Segment-aware service-id pattern matching (`foo` includes `foo/bar`, not `foobar`). */
|
|
237
|
+
function serviceIdMatchesPattern(serviceId, pattern) {
|
|
238
|
+
if ("exact" in pattern) return serviceId === pattern.exact;
|
|
239
|
+
return pattern.prefix === "" || serviceId === pattern.prefix || serviceId.startsWith(`${pattern.prefix}/`);
|
|
240
|
+
}
|
|
241
|
+
/** Whether a service id is included by a union of patterns. `undefined` means unfiltered. */
|
|
242
|
+
function serviceIdMatchesScopes(serviceId, scopes) {
|
|
243
|
+
return scopes === void 0 || scopes.some((pattern) => serviceIdMatchesPattern(serviceId, pattern));
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/connection/channel.ts
|
|
247
|
+
var Channel = class Channel {
|
|
248
|
+
sender;
|
|
249
|
+
_setHandler;
|
|
250
|
+
_setWireObserver;
|
|
251
|
+
constructor(sender, _setHandler, _setWireObserver) {
|
|
252
|
+
this.sender = sender;
|
|
253
|
+
this._setHandler = _setHandler;
|
|
254
|
+
this._setWireObserver = _setWireObserver;
|
|
255
|
+
}
|
|
256
|
+
/** Bind the inbound request/notification handler. May be called before or after construction of {@link LinkRpcConnection}. */
|
|
257
|
+
setRequestHandler(handler) {
|
|
258
|
+
this._setHandler(handler);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Install the endpoint inspection observer at the JSON-RPC wire boundary.
|
|
262
|
+
* Intended for {@link LinkRpcConnection}; ordinary consumers should use the
|
|
263
|
+
* public `hubrpc.traffic` service instead.
|
|
264
|
+
*/
|
|
265
|
+
setWireMessageObserver(observer) {
|
|
266
|
+
this._setWireObserver?.(observer);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Compose this channel with a sender-side decorator (typically the
|
|
270
|
+
* signing layer). The decorator wraps {@link sender} only; the
|
|
271
|
+
* {@link setRequestHandler} binding is shared with the original channel
|
|
272
|
+
* so the receive side is bound exactly once regardless of decoration depth.
|
|
273
|
+
*/
|
|
274
|
+
withSender(decorate) {
|
|
275
|
+
return new Channel(decorate(this.sender), this._setHandler, this._setWireObserver);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var RpcError = class extends Error {
|
|
279
|
+
code;
|
|
280
|
+
origin;
|
|
281
|
+
data;
|
|
282
|
+
hasData;
|
|
283
|
+
constructor(message, code, data, origin = "local", hasData = data !== void 0) {
|
|
284
|
+
super(message);
|
|
285
|
+
this.code = code;
|
|
286
|
+
this.origin = origin;
|
|
287
|
+
this.message = message;
|
|
288
|
+
this.name = "RpcError";
|
|
289
|
+
this.hasData = hasData;
|
|
290
|
+
if (hasData) this.data = data;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/connection/jsonRpcChannel.ts
|
|
295
|
+
/**
|
|
296
|
+
* How often a streaming-enabled call emits a `toCallee` keepalive ping so a
|
|
297
|
+
* routing hub's per-request idle timer never reaps the in-flight request.
|
|
298
|
+
* Must stay comfortably below the hub's idle timeout (default 30 min).
|
|
299
|
+
*/
|
|
300
|
+
const KEEPALIVE_PING_INTERVAL_MS = 6e5;
|
|
301
|
+
/**
|
|
302
|
+
* Minimal JSON-RPC 2.0 channel: correlates requests with responses,
|
|
303
|
+
* dispatches incoming requests/notifications to a handler.
|
|
304
|
+
*
|
|
305
|
+
* Construct via {@link JsonRpcChannel.create}: it returns a
|
|
306
|
+
* {@link Channel} factory which materialises the live channel once a
|
|
307
|
+
* handler is supplied via `.connect(handler)`. The handler is fixed for
|
|
308
|
+
* the channel's lifetime — no setter, no mid-flight swap.
|
|
309
|
+
*
|
|
310
|
+
* The outbound `TOutCtx` of the produced sender is `unknown` — this
|
|
311
|
+
* channel ignores per-call ctx entirely. Decorators (e.g.
|
|
312
|
+
* `SigningSender`) lift it to a concrete shape.
|
|
313
|
+
*/
|
|
314
|
+
var JsonRpcChannel = class JsonRpcChannel {
|
|
315
|
+
_transport;
|
|
316
|
+
_requestsDeferred;
|
|
317
|
+
/**
|
|
318
|
+
* Wrap a transport in a {@link Channel}. The channel's sender is live
|
|
319
|
+
* immediately; call {@link Channel.setRequestHandler} (or pass the channel
|
|
320
|
+
* to {@link LinkRpcConnection}) to bind the inbound handler.
|
|
321
|
+
*/
|
|
322
|
+
static create(transport) {
|
|
323
|
+
return this.createWithClose(transport).channel;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Wrap a transport with lifecycle hooks. deferRequests queues requests,
|
|
327
|
+
* notifications, and their input streams until resumeRequests is called.
|
|
328
|
+
* Responses and output streams remain live for setup-time outbound calls.
|
|
329
|
+
*/
|
|
330
|
+
static createWithClose(transport, options = {}) {
|
|
331
|
+
const jrc = new JsonRpcChannel(transport, options.deferRequests === true);
|
|
332
|
+
return {
|
|
333
|
+
channel: new Channel(jrc, (h) => jrc.setRequestHandler(h), (observer) => jrc.setWireMessageObserver(observer)),
|
|
334
|
+
close: () => jrc.close(),
|
|
335
|
+
resumeRequests: () => jrc.resumeRequests()
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
_nextId = 1;
|
|
339
|
+
_pending = /* @__PURE__ */ new Map();
|
|
340
|
+
/**
|
|
341
|
+
* Per-request callbacks invoked when a {@link STREAM_METHOD}
|
|
342
|
+
* notification arrives with a matching `requestId`. Used in both
|
|
343
|
+
* directions: outgoing-request callers register here keyed by the
|
|
344
|
+
* id they sent; per-call incoming-request stream handles register
|
|
345
|
+
* keyed by the id they observed. Entries are removed when the
|
|
346
|
+
* corresponding request completes (response received for outgoing,
|
|
347
|
+
* response sent for incoming).
|
|
348
|
+
*/
|
|
349
|
+
_streamListeners = /* @__PURE__ */ new Map();
|
|
350
|
+
/**
|
|
351
|
+
* Per-incoming-request handlers for reserved `control` messages
|
|
352
|
+
* (cancel / ping / pong) arriving as stream notifications. Keyed
|
|
353
|
+
* by the id observed; removed when the request settles.
|
|
354
|
+
*/
|
|
355
|
+
_streamControl = /* @__PURE__ */ new Map();
|
|
356
|
+
_incomingAborts = /* @__PURE__ */ new Map();
|
|
357
|
+
_handler;
|
|
358
|
+
_wireObserver;
|
|
359
|
+
_closeError;
|
|
360
|
+
_deferredMessages = [];
|
|
361
|
+
setRequestHandler(handler) {
|
|
362
|
+
this._handler = handler;
|
|
363
|
+
}
|
|
364
|
+
setWireMessageObserver(observer) {
|
|
365
|
+
this._wireObserver = observer;
|
|
366
|
+
}
|
|
367
|
+
constructor(_transport, _requestsDeferred) {
|
|
368
|
+
this._transport = _transport;
|
|
369
|
+
this._requestsDeferred = _requestsDeferred;
|
|
370
|
+
this._transport.setListener((m) => this._onMessage(m));
|
|
371
|
+
}
|
|
372
|
+
resumeRequests() {
|
|
373
|
+
while (this._deferredMessages.length > 0) this._dispatchMessage(this._deferredMessages.shift());
|
|
374
|
+
this._requestsDeferred = false;
|
|
375
|
+
}
|
|
376
|
+
async sendRequest(method, params, opts) {
|
|
377
|
+
this._throwIfClosed();
|
|
378
|
+
const id = this._nextId++;
|
|
379
|
+
const req = {
|
|
380
|
+
jsonrpc: "2.0",
|
|
381
|
+
id,
|
|
382
|
+
method
|
|
383
|
+
};
|
|
384
|
+
if (params !== void 0) req.params = params;
|
|
385
|
+
const promise = new Promise((resolve, reject) => {
|
|
386
|
+
this._pending.set(String(id), {
|
|
387
|
+
resolve,
|
|
388
|
+
reject
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
this._observeOutbound(req, opts);
|
|
392
|
+
try {
|
|
393
|
+
await this._transport.send(req);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
this._pending.delete(String(id));
|
|
396
|
+
this._observeSendFailure(id, error);
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
return promise;
|
|
400
|
+
}
|
|
401
|
+
async sendNotification(method, params, opts) {
|
|
402
|
+
this._throwIfClosed();
|
|
403
|
+
const n = {
|
|
404
|
+
jsonrpc: "2.0",
|
|
405
|
+
method
|
|
406
|
+
};
|
|
407
|
+
if (params !== void 0) n.params = params;
|
|
408
|
+
this._observeOutbound(n, opts);
|
|
409
|
+
await this._transport.send(n);
|
|
410
|
+
}
|
|
411
|
+
sendRequestWithStream(method, params, opts) {
|
|
412
|
+
this._throwIfClosed();
|
|
413
|
+
const id = this._nextId++;
|
|
414
|
+
const key = String(id);
|
|
415
|
+
let resolveResult;
|
|
416
|
+
let rejectResult;
|
|
417
|
+
const result = new Promise((resolve, reject) => {
|
|
418
|
+
resolveResult = resolve;
|
|
419
|
+
rejectResult = reject;
|
|
420
|
+
});
|
|
421
|
+
this._pending.set(key, {
|
|
422
|
+
resolve: resolveResult,
|
|
423
|
+
reject: rejectResult
|
|
424
|
+
});
|
|
425
|
+
if (opts?.onStreamMessage) this._streamListeners.set(key, opts.onStreamMessage);
|
|
426
|
+
const pinger = this._makePinger(id, StreamDir.toCallee);
|
|
427
|
+
this._streamControl.set(key, pinger.onControl);
|
|
428
|
+
const ping = setInterval(() => {
|
|
429
|
+
this._sendStream(id, StreamDir.toCallee, { control: { type: StreamControlType.ping } });
|
|
430
|
+
}, KEEPALIVE_PING_INTERVAL_MS);
|
|
431
|
+
ping.unref?.();
|
|
432
|
+
const stopPing = () => {
|
|
433
|
+
clearInterval(ping);
|
|
434
|
+
pinger.dispose();
|
|
435
|
+
this._streamControl.delete(key);
|
|
436
|
+
};
|
|
437
|
+
const dispose = (reason = "streaming request disposed") => {
|
|
438
|
+
if (!this._pending.delete(key)) return;
|
|
439
|
+
this._streamListeners.delete(key);
|
|
440
|
+
stopPing();
|
|
441
|
+
rejectResult(new RpcError(reason, ErrorCode.cancelled));
|
|
442
|
+
};
|
|
443
|
+
result.then(stopPing, stopPing);
|
|
444
|
+
(async () => {
|
|
445
|
+
const req = {
|
|
446
|
+
jsonrpc: "2.0",
|
|
447
|
+
id,
|
|
448
|
+
method
|
|
449
|
+
};
|
|
450
|
+
if (params !== void 0) req.params = params;
|
|
451
|
+
this._observeOutbound(req, opts);
|
|
452
|
+
await this._transport.send(req);
|
|
453
|
+
})().catch((err) => {
|
|
454
|
+
this._pending.delete(key);
|
|
455
|
+
this._streamListeners.delete(key);
|
|
456
|
+
this._observeSendFailure(id, err);
|
|
457
|
+
stopPing();
|
|
458
|
+
rejectResult(err instanceof Error ? err : new Error(String(err)));
|
|
459
|
+
});
|
|
460
|
+
return {
|
|
461
|
+
result,
|
|
462
|
+
send: (payload) => this._sendStream(id, StreamDir.toCallee, { payload }),
|
|
463
|
+
cancel: (reason) => this._sendStream(id, StreamDir.toCallee, { control: reason !== void 0 ? {
|
|
464
|
+
type: StreamControlType.cancel,
|
|
465
|
+
reason
|
|
466
|
+
} : { type: StreamControlType.cancel } }),
|
|
467
|
+
dispose,
|
|
468
|
+
ping: pinger.ping
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Build the symmetric ping/pong machinery for one in-flight request.
|
|
473
|
+
*
|
|
474
|
+
* `outboundDir` is the direction *this* side emits controls in
|
|
475
|
+
* (`toCallee` for the caller, `toCaller` for the callee). A received
|
|
476
|
+
* ping is answered with a pong in that same direction, echoing the
|
|
477
|
+
* ping's nonce; a received pong resolves the matching outstanding
|
|
478
|
+
* {@link ping} probe.
|
|
479
|
+
*/
|
|
480
|
+
_makePinger(requestId, outboundDir) {
|
|
481
|
+
const pending = /* @__PURE__ */ new Map();
|
|
482
|
+
let nextNonce = 1;
|
|
483
|
+
const ping = () => {
|
|
484
|
+
const nonce = `${requestId}:${nextNonce++}`;
|
|
485
|
+
const p = new Promise((resolve, reject) => pending.set(nonce, {
|
|
486
|
+
resolve,
|
|
487
|
+
reject
|
|
488
|
+
}));
|
|
489
|
+
this._sendStream(requestId, outboundDir, { control: {
|
|
490
|
+
type: StreamControlType.ping,
|
|
491
|
+
nonce
|
|
492
|
+
} });
|
|
493
|
+
return p;
|
|
494
|
+
};
|
|
495
|
+
const onControl = (control) => {
|
|
496
|
+
if (control.type === StreamControlType.ping) this._sendStream(requestId, outboundDir, { control: {
|
|
497
|
+
type: StreamControlType.pong,
|
|
498
|
+
nonce: control.nonce
|
|
499
|
+
} });
|
|
500
|
+
else if (control.type === StreamControlType.pong && control.nonce !== void 0) {
|
|
501
|
+
const waiter = pending.get(control.nonce);
|
|
502
|
+
if (waiter) {
|
|
503
|
+
pending.delete(control.nonce);
|
|
504
|
+
waiter.resolve();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
const dispose = () => {
|
|
509
|
+
const err = new RpcError("Request settled before pong", ErrorCode.cancelled);
|
|
510
|
+
for (const w of pending.values()) w.reject(err);
|
|
511
|
+
pending.clear();
|
|
512
|
+
};
|
|
513
|
+
return {
|
|
514
|
+
ping,
|
|
515
|
+
onControl,
|
|
516
|
+
dispose
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Emit a stream notification ({@link STREAM_METHOD}) associated with
|
|
521
|
+
* an in-flight request. Internal: outgoing-side callers reach this
|
|
522
|
+
* via {@link RawStreamingCall.send} / `cancel`; incoming-side handlers
|
|
523
|
+
* reach it via {@link IncomingStream.send}.
|
|
524
|
+
*/
|
|
525
|
+
async _sendStream(requestId, dir, fields) {
|
|
526
|
+
this._throwIfClosed();
|
|
527
|
+
const params = {
|
|
528
|
+
requestId,
|
|
529
|
+
dir,
|
|
530
|
+
...fields
|
|
531
|
+
};
|
|
532
|
+
const n = {
|
|
533
|
+
jsonrpc: "2.0",
|
|
534
|
+
method: STREAM_METHOD,
|
|
535
|
+
params
|
|
536
|
+
};
|
|
537
|
+
this._observeOutbound(n);
|
|
538
|
+
await this._transport.send(n);
|
|
539
|
+
}
|
|
540
|
+
close() {
|
|
541
|
+
if (this._closeError) return;
|
|
542
|
+
const error = new RpcError("Connection closed", ErrorCode.peerDisconnected);
|
|
543
|
+
this._closeError = error;
|
|
544
|
+
this._deferredMessages.length = 0;
|
|
545
|
+
for (const abort of this._incomingAborts.values()) abort.abort(error);
|
|
546
|
+
this._incomingAborts.clear();
|
|
547
|
+
this._transport.dispose();
|
|
548
|
+
for (const p of this._pending.values()) p.reject(error);
|
|
549
|
+
this._pending.clear();
|
|
550
|
+
this._streamListeners.clear();
|
|
551
|
+
this._streamControl.clear();
|
|
552
|
+
}
|
|
553
|
+
_throwIfClosed() {
|
|
554
|
+
if (this._closeError) throw this._closeError;
|
|
555
|
+
}
|
|
556
|
+
_onMessage(m) {
|
|
557
|
+
this._observeInbound(m);
|
|
558
|
+
if (this._requestsDeferred && !isResponse(m) && !(isNotification(m) && m.method === STREAM_METHOD && m.params?.dir === StreamDir.toCaller)) {
|
|
559
|
+
this._deferredMessages.push(m);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
this._dispatchMessage(m);
|
|
563
|
+
}
|
|
564
|
+
_dispatchMessage(m) {
|
|
565
|
+
const ctx = m.context;
|
|
566
|
+
if (isResponse(m)) {
|
|
567
|
+
if (m.id === null) return;
|
|
568
|
+
const key = String(m.id);
|
|
569
|
+
const pending = this._pending.get(key);
|
|
570
|
+
if (!pending) return;
|
|
571
|
+
this._pending.delete(key);
|
|
572
|
+
this._streamListeners.delete(key);
|
|
573
|
+
if ("error" in m) {
|
|
574
|
+
const e = m.error;
|
|
575
|
+
const err = new RpcError(e.message, e.code, e.data, "remote", Object.hasOwn(e, "data"));
|
|
576
|
+
pending.reject(err);
|
|
577
|
+
} else pending.resolve(m.result);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (isRequest(m)) {
|
|
581
|
+
this._handleRequest(m, ctx).catch(() => {});
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (isNotification(m)) {
|
|
585
|
+
if (m.method === STREAM_METHOD) {
|
|
586
|
+
this._handleStreamNotification(m);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
this._handleNotification(m, ctx);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
_handleStreamNotification(m) {
|
|
594
|
+
const params = m.params;
|
|
595
|
+
if (!params) return;
|
|
596
|
+
const requestId = params.requestId;
|
|
597
|
+
if (requestId === void 0) return;
|
|
598
|
+
const key = String(requestId);
|
|
599
|
+
if (params.control) {
|
|
600
|
+
this._streamControl.get(key)?.(params.control);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const listener = this._streamListeners.get(key);
|
|
604
|
+
if (!listener) return;
|
|
605
|
+
listener(params.payload);
|
|
606
|
+
}
|
|
607
|
+
async _handleRequest(m, context) {
|
|
608
|
+
const key = String(m.id);
|
|
609
|
+
const pinger = this._makePinger(m.id, StreamDir.toCaller);
|
|
610
|
+
const stream = {
|
|
611
|
+
send: (payload) => this._sendStream(m.id, StreamDir.toCaller, { payload }),
|
|
612
|
+
onMessage: (listener) => {
|
|
613
|
+
if (listener) this._streamListeners.set(key, listener);
|
|
614
|
+
else this._streamListeners.delete(key);
|
|
615
|
+
},
|
|
616
|
+
ping: pinger.ping
|
|
617
|
+
};
|
|
618
|
+
const abort = new AbortController();
|
|
619
|
+
this._incomingAborts.set(key, abort);
|
|
620
|
+
this._streamControl.set(key, (control) => {
|
|
621
|
+
if (control.type === StreamControlType.cancel) abort.abort(new RpcError(control.reason ?? "cancelled", ErrorCode.cancelled));
|
|
622
|
+
else pinger.onControl(control);
|
|
623
|
+
});
|
|
624
|
+
let result;
|
|
625
|
+
try {
|
|
626
|
+
if (!this._handler) result = { error: {
|
|
627
|
+
code: ErrorCode.methodNotFound,
|
|
628
|
+
message: `No handler registered on this endpoint (method: ${m.method}).`
|
|
629
|
+
} };
|
|
630
|
+
else try {
|
|
631
|
+
result = await this._handler.handleRequest({
|
|
632
|
+
method: m.method,
|
|
633
|
+
params: m.params,
|
|
634
|
+
context,
|
|
635
|
+
stream,
|
|
636
|
+
signal: abort.signal
|
|
637
|
+
});
|
|
638
|
+
} catch (e) {
|
|
639
|
+
result = { error: {
|
|
640
|
+
code: ErrorCode.internalError,
|
|
641
|
+
message: e instanceof Error ? e.message : String(e)
|
|
642
|
+
} };
|
|
643
|
+
}
|
|
644
|
+
} finally {
|
|
645
|
+
this._streamListeners.delete(key);
|
|
646
|
+
this._streamControl.delete(key);
|
|
647
|
+
this._incomingAborts.delete(key);
|
|
648
|
+
pinger.dispose();
|
|
649
|
+
}
|
|
650
|
+
if ("result" in result) {
|
|
651
|
+
const response = {
|
|
652
|
+
jsonrpc: "2.0",
|
|
653
|
+
id: m.id,
|
|
654
|
+
result: result.result
|
|
655
|
+
};
|
|
656
|
+
this._observeOutbound(response);
|
|
657
|
+
await this._transport.send(response);
|
|
658
|
+
} else {
|
|
659
|
+
const resp = {
|
|
660
|
+
jsonrpc: "2.0",
|
|
661
|
+
id: m.id,
|
|
662
|
+
error: result.error
|
|
663
|
+
};
|
|
664
|
+
this._observeOutbound(resp);
|
|
665
|
+
await this._transport.send(resp);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
_handleNotification(m, context) {
|
|
669
|
+
if (!this._handler) return;
|
|
670
|
+
try {
|
|
671
|
+
this._handler.handleNotification({
|
|
672
|
+
method: m.method,
|
|
673
|
+
params: m.params,
|
|
674
|
+
context,
|
|
675
|
+
stream: _NOOP_STREAM,
|
|
676
|
+
signal: _NEVER_ABORT
|
|
677
|
+
});
|
|
678
|
+
} catch {}
|
|
679
|
+
}
|
|
680
|
+
_observeInbound(message) {
|
|
681
|
+
const observer = this._wireObserver;
|
|
682
|
+
if (observer === void 0) return;
|
|
683
|
+
observer("inbound", message);
|
|
684
|
+
}
|
|
685
|
+
_observeOutbound(message, opts) {
|
|
686
|
+
const observer = this._wireObserver;
|
|
687
|
+
if (observer === void 0) return;
|
|
688
|
+
observer("outbound", message);
|
|
689
|
+
}
|
|
690
|
+
_observeSendFailure(requestId, error) {
|
|
691
|
+
if (this._wireObserver === void 0) return;
|
|
692
|
+
this._observeInbound({
|
|
693
|
+
jsonrpc: "2.0",
|
|
694
|
+
id: requestId,
|
|
695
|
+
error: {
|
|
696
|
+
code: ErrorCode.peerDisconnected,
|
|
697
|
+
message: error instanceof Error ? error.message : String(error)
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
/** Stub stream handle for notifications, which carry no request id. */
|
|
703
|
+
const _NOOP_STREAM = {
|
|
704
|
+
send: () => Promise.resolve(),
|
|
705
|
+
onMessage: () => {},
|
|
706
|
+
ping: () => Promise.resolve()
|
|
707
|
+
};
|
|
708
|
+
/** A signal that never aborts — handed to notification handlers. */
|
|
709
|
+
const _NEVER_ABORT = new AbortController().signal;
|
|
710
|
+
function defaultInterfaceTarget(iface) {
|
|
711
|
+
return Object.freeze({
|
|
712
|
+
mode: "default",
|
|
713
|
+
interface: iface
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
function isInterfaceTarget(value) {
|
|
717
|
+
return typeof value === "object" && value !== null && "interface" in value && "mode" in value && (value.mode === "qualified" || value.mode === "default" || value.mode === "bare");
|
|
718
|
+
}
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region src/connection/linkRpcConnection.ts
|
|
721
|
+
/**
|
|
722
|
+
* High-level linkrpc connection. Layers method-name routing and zod-driven
|
|
723
|
+
* validation on top of a plain JSON-RPC channel.
|
|
724
|
+
*
|
|
725
|
+
* Typed proxies built by this class merely forward each call's
|
|
726
|
+
* `interfaceHash` hint to the channel so the signer stamps it into the
|
|
727
|
+
* `$hubrpc` envelope.
|
|
728
|
+
*
|
|
729
|
+
* `TInCtx` is the per-call out-of-band context the connection's transport
|
|
730
|
+
* carries. Default `undefined` covers cross-process and ordinary in-process
|
|
731
|
+
* transports. The hub's self/overlay connections instantiate with a
|
|
732
|
+
* concrete `TInCtx` (e.g. `Participant`) so handlers can see who originated
|
|
733
|
+
* the call.
|
|
734
|
+
*
|
|
735
|
+
* `TOutCtx` is the per-call override / extension bag the outbound sender
|
|
736
|
+
* understands (see {@link SendOpts.ctx}). It only ever flows into the
|
|
737
|
+
* sender as input, so it is a contravariant type parameter. Default `any`
|
|
738
|
+
* keeps the bare `LinkRpcConnection` a valid supertype for holders that do
|
|
739
|
+
* not care about the outbound ctx; pass a concrete shape (e.g.
|
|
740
|
+
* `SigningCallCtx`) to get precise {@link get} typing. The connection
|
|
741
|
+
* itself is agnostic to its contents — it merely forwards the `ctx`
|
|
742
|
+
* defaults supplied to {@link get}.
|
|
743
|
+
*/
|
|
744
|
+
var LinkRpcConnection = class LinkRpcConnection {
|
|
745
|
+
/**
|
|
746
|
+
* Convenience: build a {@link JsonRpcChannel} `Channel` from the
|
|
747
|
+
* given transport and wrap it in an `LinkRpcConnection`. Use this
|
|
748
|
+
* when you have a transport at hand and don't need a decorator
|
|
749
|
+
* stack (e.g. signing).
|
|
750
|
+
*/
|
|
751
|
+
static fromTransport(transport, options = {}) {
|
|
752
|
+
return new LinkRpcConnection(JsonRpcChannel.create(transport), options);
|
|
753
|
+
}
|
|
754
|
+
/** Underlying JSON-RPC sender — useful for callers that need raw access (e.g. to call hub-served methods that bypass the interface registry). */
|
|
755
|
+
channel;
|
|
756
|
+
/** key = `${serviceId ?? ""}::${interfaceId}` */
|
|
757
|
+
_registry = /* @__PURE__ */ new Map();
|
|
758
|
+
/** Descriptions for services that have been registered with a `serviceDescription`. */
|
|
759
|
+
_serviceDescriptions = /* @__PURE__ */ new Map();
|
|
760
|
+
/** Root-node-id requirement sets recorded per serviceId. */
|
|
761
|
+
_serviceRootPrincipalSets = /* @__PURE__ */ new Map();
|
|
762
|
+
_directoryWatchers = /* @__PURE__ */ new Set();
|
|
763
|
+
_directoryListeners = /* @__PURE__ */ new Set();
|
|
764
|
+
_inspection;
|
|
765
|
+
_inspectionHost;
|
|
766
|
+
_serviceInspectionRegistrations = /* @__PURE__ */ new Map();
|
|
767
|
+
_wireObservers = /* @__PURE__ */ new Set();
|
|
768
|
+
_closeListeners = /* @__PURE__ */ new Set();
|
|
769
|
+
_closed = false;
|
|
770
|
+
_wireChannel;
|
|
771
|
+
_validateOutboundParams;
|
|
772
|
+
/** Bare-method bindings, keyed by their exact wire prefix. */
|
|
773
|
+
_bareBindings = /* @__PURE__ */ new Map();
|
|
774
|
+
_generateTopologyId;
|
|
775
|
+
/**
|
|
776
|
+
* Construct from a {@link Channel} (binds the inbound handler and uses
|
|
777
|
+
* `channel.sender` for outbound calls) or from a bare
|
|
778
|
+
* {@link IRequestSender} (send-only — no inbound handler is registered,
|
|
779
|
+
* useful for bootstrap flows like `createManagedIdentity`).
|
|
780
|
+
*/
|
|
781
|
+
constructor(channel, options = {}) {
|
|
782
|
+
this._validateOutboundParams = options.validateOutboundParams !== false;
|
|
783
|
+
this._generateTopologyId = options.generateTopologyId ?? createInspectionId;
|
|
784
|
+
if ("sender" in channel) {
|
|
785
|
+
this._wireChannel = channel;
|
|
786
|
+
this.channel = channel.sender;
|
|
787
|
+
channel.setRequestHandler({
|
|
788
|
+
handleRequest: (c) => this._handleRequest(c),
|
|
789
|
+
handleNotification: (c) => this._handleNotification(c)
|
|
790
|
+
});
|
|
791
|
+
} else {
|
|
792
|
+
this._wireChannel = void 0;
|
|
793
|
+
this.channel = channel;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
get(ifaceOrTarget, opts) {
|
|
797
|
+
return this._getClient(ifaceOrTarget, opts, false);
|
|
798
|
+
}
|
|
799
|
+
_getClient(ifaceOrTarget, opts, allFailuresAsValues) {
|
|
800
|
+
if (isInterfaceTarget(ifaceOrTarget)) {
|
|
801
|
+
if (opts !== void 0) throw new Error(`${allFailuresAsValues ? "getResultClient" : "get"}: ${ifaceOrTarget.mode === "bare" ? "bare " : ""}interface targets do not accept service or call options.`);
|
|
802
|
+
if (ifaceOrTarget.mode === "bare") return this._buildBareClient(ifaceOrTarget.interface, ifaceOrTarget.prefix, allFailuresAsValues);
|
|
803
|
+
return this._buildClient(ifaceOrTarget.interface, ifaceOrTarget.mode === "qualified" ? { serviceId: ifaceOrTarget.serviceId } : {}, void 0, ifaceOrTarget.mode === "default", allFailuresAsValues);
|
|
804
|
+
}
|
|
805
|
+
return this._buildClient(ifaceOrTarget, opts ?? {}, void 0, false, allFailuresAsValues);
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Get a typed client that emits foreign-protocol bare method names.
|
|
809
|
+
* Unlike {@link get}, these calls carry no LinkRPC interface metadata.
|
|
810
|
+
*/
|
|
811
|
+
getBare(iface, opts = {}) {
|
|
812
|
+
return this._buildBareClient(iface, opts.prefix ?? "", false);
|
|
813
|
+
}
|
|
814
|
+
_buildBareClient(iface, prefix, allFailuresAsValues) {
|
|
815
|
+
validateBarePrefix(prefix);
|
|
816
|
+
for (const [name, member] of Object.entries(iface.members)) if (member.kind === "request" && (member.clientStreamSchema !== void 0 || member.serverStreamSchema !== void 0)) throw new Error(`getBare: streaming method "${name}" is not supported on foreign wires.`);
|
|
817
|
+
return this._buildClient(iface, {}, prefix, false, allFailuresAsValues);
|
|
818
|
+
}
|
|
819
|
+
getResultClient(ifaceOrTarget, opts) {
|
|
820
|
+
return this._getClient(ifaceOrTarget, opts, true);
|
|
821
|
+
}
|
|
822
|
+
/** Get a service-scoped handle; all interfaces obtained from it route via `serviceId` (form 3). */
|
|
823
|
+
service(serviceId) {
|
|
824
|
+
return new ServiceHandle(this, serviceId);
|
|
825
|
+
}
|
|
826
|
+
/** Register handlers, adding preset routing when passed a default or bare target. */
|
|
827
|
+
register(ifaceOrTarget, handlers, opts = {}) {
|
|
828
|
+
if (isInterfaceTarget(ifaceOrTarget)) {
|
|
829
|
+
if (ifaceOrTarget.mode === "qualified") {
|
|
830
|
+
if (opts.serviceId !== void 0 && opts.serviceId !== ifaceOrTarget.serviceId) throw new Error("register: serviceId conflicts with interface target.");
|
|
831
|
+
return this._register(ifaceOrTarget.interface, handlers, {
|
|
832
|
+
...opts,
|
|
833
|
+
serviceId: ifaceOrTarget.serviceId || void 0
|
|
834
|
+
}, false);
|
|
835
|
+
}
|
|
836
|
+
const prefix = ifaceOrTarget.mode === "bare" ? ifaceOrTarget.prefix : "";
|
|
837
|
+
validateBarePrefix(prefix);
|
|
838
|
+
return this._register(ifaceOrTarget.interface, handlers, opts, false, prefix);
|
|
839
|
+
}
|
|
840
|
+
return this._register(ifaceOrTarget, handlers, opts, false);
|
|
841
|
+
}
|
|
842
|
+
/** Register inspection contracts without recursively enabling service inspection. */
|
|
843
|
+
registerInspection(iface, handlers, opts = {}) {
|
|
844
|
+
return this._register(iface, handlers, opts, true);
|
|
845
|
+
}
|
|
846
|
+
/** Observe this connection without replacing another inspection scope's observer. */
|
|
847
|
+
observeWireMessages(observer) {
|
|
848
|
+
if (this._closed) throw new Error("Connection is closed");
|
|
849
|
+
if (this._wireChannel === void 0) throw new Error("Connection has no observable wire channel");
|
|
850
|
+
this._wireObservers.add(observer);
|
|
851
|
+
this._wireChannel.setWireMessageObserver((direction, message) => {
|
|
852
|
+
for (const listener of [...this._wireObservers]) try {
|
|
853
|
+
listener(direction, message);
|
|
854
|
+
} catch {}
|
|
855
|
+
});
|
|
856
|
+
return { dispose: () => {
|
|
857
|
+
this._wireObservers.delete(observer);
|
|
858
|
+
if (this._wireObservers.size === 0) this._wireChannel?.setWireMessageObserver(void 0);
|
|
859
|
+
} };
|
|
860
|
+
}
|
|
861
|
+
onDidClose(listener) {
|
|
862
|
+
if (this._closed) throw new Error("Connection is closed");
|
|
863
|
+
this._closeListeners.add(listener);
|
|
864
|
+
return { dispose: () => {
|
|
865
|
+
this._closeListeners.delete(listener);
|
|
866
|
+
} };
|
|
867
|
+
}
|
|
868
|
+
_register(iface, handlers, opts, internalInspection, barePrefix) {
|
|
869
|
+
const serviceId = opts.serviceId;
|
|
870
|
+
const key = `${serviceId ?? ""}::${iface.info.id}`;
|
|
871
|
+
if (this._registry.has(key)) throw new Error(`Interface "${iface.info.id}" already registered${serviceId ? ` under service "${serviceId}"` : ""}.`);
|
|
872
|
+
if (barePrefix !== void 0 && this._bareBindings.has(barePrefix)) throw new Error(`Bare interface prefix "${barePrefix}" is already registered.`);
|
|
873
|
+
let descriptionWasAdded = false;
|
|
874
|
+
if (opts.serviceDescription !== void 0) {
|
|
875
|
+
if (serviceId === void 0) throw new Error("register: `serviceDescription` requires `serviceId`.");
|
|
876
|
+
const existing = this._serviceDescriptions.get(serviceId);
|
|
877
|
+
if (existing !== void 0 && existing !== opts.serviceDescription) throw new Error(`register: conflicting descriptions for service "${serviceId}".`);
|
|
878
|
+
descriptionWasAdded = existing === void 0;
|
|
879
|
+
}
|
|
880
|
+
let rootPrincipalSetsWereAdded = false;
|
|
881
|
+
if (opts.rootPrincipalSets !== void 0) {
|
|
882
|
+
if (serviceId === void 0) throw new Error("register: `rootPrincipalSets` requires `serviceId`.");
|
|
883
|
+
const existing = this._serviceRootPrincipalSets.get(serviceId);
|
|
884
|
+
if (existing !== void 0 && !rootPrincipalSetsEqual(existing, opts.rootPrincipalSets)) throw new Error(`register: conflicting rootPrincipalSets for service "${serviceId}".`);
|
|
885
|
+
rootPrincipalSetsWereAdded = existing === void 0;
|
|
886
|
+
}
|
|
887
|
+
const entry = {
|
|
888
|
+
iface,
|
|
889
|
+
handlers: iface.flattenHandlers(handlers),
|
|
890
|
+
serviceId,
|
|
891
|
+
internalInspection
|
|
892
|
+
};
|
|
893
|
+
if (descriptionWasAdded) this._serviceDescriptions.set(serviceId, opts.serviceDescription);
|
|
894
|
+
if (rootPrincipalSetsWereAdded) this._serviceRootPrincipalSets.set(serviceId, opts.rootPrincipalSets);
|
|
895
|
+
this._registry.set(key, entry);
|
|
896
|
+
if (barePrefix !== void 0) this._bareBindings.set(barePrefix, {
|
|
897
|
+
prefix: barePrefix,
|
|
898
|
+
entry
|
|
899
|
+
});
|
|
900
|
+
this._notifyDirectoryWatchers(entry, descriptionWasAdded || rootPrincipalSetsWereAdded);
|
|
901
|
+
if (!internalInspection && serviceId !== void 0 && this._inspection !== void 0 && !this._serviceInspectionRegistrations.has(serviceId)) try {
|
|
902
|
+
this._installServiceInspection(serviceId);
|
|
903
|
+
} catch (error) {
|
|
904
|
+
this._registry.delete(key);
|
|
905
|
+
if (barePrefix !== void 0) this._bareBindings.delete(barePrefix);
|
|
906
|
+
if (descriptionWasAdded) this._serviceDescriptions.delete(serviceId);
|
|
907
|
+
if (rootPrincipalSetsWereAdded) this._serviceRootPrincipalSets.delete(serviceId);
|
|
908
|
+
this._notifyDirectoryWatchers(entry);
|
|
909
|
+
throw error;
|
|
910
|
+
}
|
|
911
|
+
let disposed = false;
|
|
912
|
+
return { dispose: () => {
|
|
913
|
+
if (disposed) return;
|
|
914
|
+
disposed = true;
|
|
915
|
+
if (this._registry.get(key) !== entry) return;
|
|
916
|
+
this._registry.delete(key);
|
|
917
|
+
for (const [prefix, binding] of this._bareBindings) if (binding.entry === entry) this._bareBindings.delete(prefix);
|
|
918
|
+
this._notifyDirectoryWatchers(entry);
|
|
919
|
+
if (serviceId !== void 0 && !this._hasBusinessServiceRegistration(serviceId)) {
|
|
920
|
+
this._removeServiceInspection(serviceId);
|
|
921
|
+
this._serviceDescriptions.delete(serviceId);
|
|
922
|
+
this._serviceRootPrincipalSets.delete(serviceId);
|
|
923
|
+
}
|
|
924
|
+
} };
|
|
925
|
+
}
|
|
926
|
+
/** Snapshot of every interface currently registered on this connection. */
|
|
927
|
+
listRegisteredInterfaces() {
|
|
928
|
+
return Array.from(this._registry.values()).map((r) => {
|
|
929
|
+
const sid = r.serviceId ?? "";
|
|
930
|
+
const item = {
|
|
931
|
+
serviceId: sid,
|
|
932
|
+
interfaceId: r.iface.info.id,
|
|
933
|
+
interfaceHash: r.iface.schemaHash
|
|
934
|
+
};
|
|
935
|
+
if (r.iface.info.tags !== void 0) item.tags = r.iface.info.tags;
|
|
936
|
+
const desc = sid === "" ? void 0 : this._serviceDescriptions.get(sid);
|
|
937
|
+
if (desc !== void 0) item.serviceDescription = desc;
|
|
938
|
+
const sets = sid === "" ? void 0 : this._serviceRootPrincipalSets.get(sid);
|
|
939
|
+
if (sets !== void 0) item.rootPrincipalSets = sets;
|
|
940
|
+
return item;
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
/** Subscribe to coarse local directory changes; listeners must re-list. */
|
|
944
|
+
onDidChangeDirectory(listener) {
|
|
945
|
+
this._directoryListeners.add(listener);
|
|
946
|
+
return () => this._directoryListeners.delete(listener);
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Look up a registered interface definition by id (and optional content
|
|
950
|
+
* hash). Returns `undefined` if no registered interface matches.
|
|
951
|
+
*/
|
|
952
|
+
findRegisteredInterface(interfaceId, hash) {
|
|
953
|
+
let selected;
|
|
954
|
+
for (const r of this._registry.values()) {
|
|
955
|
+
if (r.iface.info.id !== interfaceId) continue;
|
|
956
|
+
if (hash !== void 0 && r.iface.schemaHash !== hash) continue;
|
|
957
|
+
if (selected === void 0 || preferRicherInterfaceSchema(selected.toSchema(), r.iface.toSchema()) === r.iface.toSchema()) selected = r.iface;
|
|
958
|
+
}
|
|
959
|
+
return selected;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Register the three linkrpc reflection interfaces (`defaults`,
|
|
963
|
+
* `directory`, `schemas`), backed by this connection's live registry.
|
|
964
|
+
*
|
|
965
|
+
* By default they live under the root service (form-2 reachable as
|
|
966
|
+
* `hubrpc.directory::list`). Pass `serviceId` to additionally mount
|
|
967
|
+
* them under a specific service — useful for participants that live
|
|
968
|
+
* behind a hub, so callers can reach reflection via form-3
|
|
969
|
+
* `<serviceId>::hubrpc.directory::list`.
|
|
970
|
+
*
|
|
971
|
+
* Idempotent: re-registering the same `(serviceId, interfaceId)` pair
|
|
972
|
+
* is a no-op.
|
|
973
|
+
*/
|
|
974
|
+
enableReflection(opts = {}) {
|
|
975
|
+
const key = `${opts.serviceId ?? ""}::${defaultsInterface.info.id}`;
|
|
976
|
+
if (this._registry.has(key)) return { dispose() {} };
|
|
977
|
+
const prefix = `${opts.serviceId ?? ""}::`;
|
|
978
|
+
for (const iface of [directoryInterface, schemasInterface]) if (this._registry.has(`${prefix}${iface.info.id}`)) throw new Error(`enableReflection: partial reflection registration for service "${opts.serviceId ?? ""}".`);
|
|
979
|
+
const regOpts = opts.serviceId !== void 0 ? { serviceId: opts.serviceId } : {};
|
|
980
|
+
const registrations = [];
|
|
981
|
+
registrations.push(this.register(defaultsInterface, {
|
|
982
|
+
get: () => {
|
|
983
|
+
const binding = this._bareBindings.get("");
|
|
984
|
+
if (!binding) return {};
|
|
985
|
+
return {
|
|
986
|
+
serviceId: binding.entry.serviceId,
|
|
987
|
+
interfaceId: binding.entry.iface.info.id,
|
|
988
|
+
interfaceHash: binding.entry.iface.schemaHash
|
|
989
|
+
};
|
|
990
|
+
},
|
|
991
|
+
listBindings: () => ({ bindings: Array.from(this._bareBindings.values()).sort((a, b) => a.prefix < b.prefix ? -1 : a.prefix > b.prefix ? 1 : 0).map((binding) => ({
|
|
992
|
+
prefix: binding.prefix,
|
|
993
|
+
serviceId: binding.entry.serviceId,
|
|
994
|
+
interfaceId: binding.entry.iface.info.id,
|
|
995
|
+
interfaceHash: binding.entry.iface.schemaHash
|
|
996
|
+
})) })
|
|
997
|
+
}, regOpts));
|
|
998
|
+
registrations.push(this.register(directoryInterface, {
|
|
999
|
+
list: ({ interfaceId, interfaceIdPrefix, serviceId, serviceIdScopes, cursor, limit }) => {
|
|
1000
|
+
const all = this.listRegisteredInterfaces().filter((r) => opts.serviceId === void 0 || r.serviceId === opts.serviceId).filter((r) => interfaceId === void 0 || r.interfaceId === interfaceId).filter((r) => interfaceIdPrefix === void 0 || r.interfaceId.startsWith(interfaceIdPrefix)).filter((r) => serviceId === void 0 || r.serviceId === serviceId).filter((r) => serviceIdMatchesScopes(r.serviceId, serviceIdScopes));
|
|
1001
|
+
const start = cursor === void 0 ? 0 : Number.parseInt(cursor, 10) || 0;
|
|
1002
|
+
const end = limit === void 0 ? all.length : Math.min(all.length, start + limit);
|
|
1003
|
+
const page = all.slice(start, end).map((r) => ({
|
|
1004
|
+
serviceId: r.serviceId,
|
|
1005
|
+
interfaceId: r.interfaceId,
|
|
1006
|
+
interfaceHash: r.interfaceHash,
|
|
1007
|
+
tags: r.tags === void 0 ? void 0 : [...r.tags],
|
|
1008
|
+
serviceDescription: r.serviceDescription,
|
|
1009
|
+
rootPrincipalSets: r.rootPrincipalSets?.map((set) => set.map((p) => ({ ...p })))
|
|
1010
|
+
}));
|
|
1011
|
+
return end < all.length ? {
|
|
1012
|
+
items: page,
|
|
1013
|
+
nextCursor: String(end)
|
|
1014
|
+
} : { items: page };
|
|
1015
|
+
},
|
|
1016
|
+
watch: (params, _ctx, stream) => new Promise((resolve) => {
|
|
1017
|
+
const watcher = {
|
|
1018
|
+
interfaceId: params.interfaceId,
|
|
1019
|
+
interfaceIdPrefix: params.interfaceIdPrefix,
|
|
1020
|
+
serviceId: params.serviceId,
|
|
1021
|
+
serviceIdScopes: params.serviceIdScopes,
|
|
1022
|
+
send: () => stream.send({})
|
|
1023
|
+
};
|
|
1024
|
+
this._directoryWatchers.add(watcher);
|
|
1025
|
+
const finish = () => {
|
|
1026
|
+
this._directoryWatchers.delete(watcher);
|
|
1027
|
+
resolve({});
|
|
1028
|
+
};
|
|
1029
|
+
if (stream.signal.aborted) {
|
|
1030
|
+
finish();
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
stream.signal.addEventListener("abort", finish, { once: true });
|
|
1034
|
+
})
|
|
1035
|
+
}, regOpts));
|
|
1036
|
+
registrations.push(this.register(schemasInterface, { get: ({ interfaceId, hash }) => {
|
|
1037
|
+
const iface = this.findRegisteredInterface(interfaceId, hash);
|
|
1038
|
+
if (!iface) throw new RpcError("Interface not found", ErrorCode.methodNotFound, {
|
|
1039
|
+
reason: "unknown-interface",
|
|
1040
|
+
interfaceId,
|
|
1041
|
+
hash
|
|
1042
|
+
});
|
|
1043
|
+
return { schema: iface.toSchema() };
|
|
1044
|
+
} }, regOpts));
|
|
1045
|
+
if (opts.serviceId !== void 0) try {
|
|
1046
|
+
this.enableReflection();
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
for (const registration of registrations.reverse()) registration.dispose();
|
|
1049
|
+
throw error;
|
|
1050
|
+
}
|
|
1051
|
+
let disposed = false;
|
|
1052
|
+
return { dispose: () => {
|
|
1053
|
+
if (disposed) return;
|
|
1054
|
+
disposed = true;
|
|
1055
|
+
for (const registration of registrations.reverse()) registration.dispose();
|
|
1056
|
+
} };
|
|
1057
|
+
}
|
|
1058
|
+
_notifyDirectoryWatchers(entry, serviceMetadataChanged = false) {
|
|
1059
|
+
for (const listener of this._directoryListeners) try {
|
|
1060
|
+
listener();
|
|
1061
|
+
} catch {}
|
|
1062
|
+
for (const watcher of this._directoryWatchers) {
|
|
1063
|
+
if (watcher.pending) continue;
|
|
1064
|
+
const directlyMatches = directoryWatcherMatchesEntry(watcher, entry);
|
|
1065
|
+
const metadataMatches = serviceMetadataChanged && entry.serviceId !== void 0 && [...this._registry.values()].some((candidate) => candidate.serviceId === entry.serviceId && directoryWatcherMatchesEntry(watcher, candidate));
|
|
1066
|
+
if (!directlyMatches && !metadataMatches) continue;
|
|
1067
|
+
watcher.pending = true;
|
|
1068
|
+
queueMicrotask(() => {
|
|
1069
|
+
watcher.pending = false;
|
|
1070
|
+
if (this._directoryWatchers.has(watcher)) watcher.send();
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Enable the root `hubrpc.node::getNodeId` topology-bootstrap service.
|
|
1076
|
+
*
|
|
1077
|
+
* The generated ids are stable while this registration is active. They are
|
|
1078
|
+
* unauthenticated correlation labels only; callers must not use them for
|
|
1079
|
+
* identity, capability, or authorization decisions.
|
|
1080
|
+
*/
|
|
1081
|
+
enableInspection(descriptors) {
|
|
1082
|
+
if (this._inspection !== void 0) return this._inspection;
|
|
1083
|
+
const info = {
|
|
1084
|
+
nodeId: this._generateTopologyId("node"),
|
|
1085
|
+
portId: this._generateTopologyId("port"),
|
|
1086
|
+
...descriptors !== void 0 ? { descriptors: [...descriptors] } : {}
|
|
1087
|
+
};
|
|
1088
|
+
const registration = this._register(nodeInterface, { getNodeId: () => info }, {}, true);
|
|
1089
|
+
const host = new InspectionHost({
|
|
1090
|
+
nodeId: info.nodeId,
|
|
1091
|
+
descriptors
|
|
1092
|
+
});
|
|
1093
|
+
try {
|
|
1094
|
+
if (this._wireChannel !== void 0) host.trackConnection(this, { portId: info.portId });
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
host.dispose();
|
|
1097
|
+
registration.dispose();
|
|
1098
|
+
throw error;
|
|
1099
|
+
}
|
|
1100
|
+
this._inspectionHost = host;
|
|
1101
|
+
let disposed = false;
|
|
1102
|
+
const result = {
|
|
1103
|
+
...info,
|
|
1104
|
+
observeTraffic: (observer) => host.observeTraffic(observer),
|
|
1105
|
+
dispose: () => {
|
|
1106
|
+
if (disposed) return;
|
|
1107
|
+
disposed = true;
|
|
1108
|
+
for (const serviceId of [...this._serviceInspectionRegistrations.keys()]) this._removeServiceInspection(serviceId);
|
|
1109
|
+
host.dispose();
|
|
1110
|
+
if (this._inspectionHost === host) this._inspectionHost = void 0;
|
|
1111
|
+
registration.dispose();
|
|
1112
|
+
if (this._inspection === result) this._inspection = void 0;
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
this._inspection = result;
|
|
1116
|
+
try {
|
|
1117
|
+
const services = new Set(Array.from(this._registry.values()).filter((entry) => entry.serviceId !== void 0 && !entry.internalInspection).map((entry) => entry.serviceId));
|
|
1118
|
+
for (const serviceId of services) this._installServiceInspection(serviceId);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
result.dispose();
|
|
1121
|
+
throw error;
|
|
1122
|
+
}
|
|
1123
|
+
return result;
|
|
1124
|
+
}
|
|
1125
|
+
/** Number of active endpoint traffic stream subscribers. */
|
|
1126
|
+
get trafficObserverCount() {
|
|
1127
|
+
return this._inspectionHost?.observerCount ?? 0;
|
|
1128
|
+
}
|
|
1129
|
+
close() {
|
|
1130
|
+
if (this._closed) return;
|
|
1131
|
+
this._closed = true;
|
|
1132
|
+
for (const listener of [...this._closeListeners]) listener();
|
|
1133
|
+
this._closeListeners.clear();
|
|
1134
|
+
this._inspection?.dispose();
|
|
1135
|
+
this._wireObservers.clear();
|
|
1136
|
+
this._wireChannel?.setWireMessageObserver(void 0);
|
|
1137
|
+
this.channel.close();
|
|
1138
|
+
}
|
|
1139
|
+
_buildClient(iface, opts, barePrefix, defaultRoute = false, allFailuresAsValues = false) {
|
|
1140
|
+
const { serviceId, ...ctxRest } = opts;
|
|
1141
|
+
const sendOpts = barePrefix === void 0 ? {
|
|
1142
|
+
ctx: ctxRest,
|
|
1143
|
+
interfaceHash: iface.schemaHash
|
|
1144
|
+
} : void 0;
|
|
1145
|
+
const prefix = barePrefix ?? (defaultRoute ? "" : serviceId ? `${serviceId}::${iface.info.id}::` : `${iface.info.id}::`);
|
|
1146
|
+
const proxy = Object.create(null);
|
|
1147
|
+
for (const [name, member] of Object.entries(iface.members)) {
|
|
1148
|
+
const wireMethod = `${prefix}${name}`;
|
|
1149
|
+
if (member.kind === "request") {
|
|
1150
|
+
if (member.clientStreamSchema !== void 0 || member.serverStreamSchema !== void 0) {
|
|
1151
|
+
const serverStreamSchema = member.serverStreamSchema;
|
|
1152
|
+
proxy[name] = (params, callOpts) => {
|
|
1153
|
+
const userListener = callOpts?.onMessage;
|
|
1154
|
+
const onStreamMessage = userListener !== void 0 ? (raw) => {
|
|
1155
|
+
if (serverStreamSchema !== void 0) {
|
|
1156
|
+
const r = safeParse(serverStreamSchema, raw);
|
|
1157
|
+
if (!r.success) return;
|
|
1158
|
+
userListener(r.data);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
userListener(raw);
|
|
1162
|
+
} : void 0;
|
|
1163
|
+
const channelOpts = onStreamMessage !== void 0 ? {
|
|
1164
|
+
...sendOpts,
|
|
1165
|
+
onStreamMessage
|
|
1166
|
+
} : sendOpts;
|
|
1167
|
+
let call;
|
|
1168
|
+
try {
|
|
1169
|
+
this._validateOutboundParamsFor(member, wireMethod, params);
|
|
1170
|
+
call = this.channel.sendRequestWithStream(wireMethod, params, channelOpts);
|
|
1171
|
+
} catch (cause) {
|
|
1172
|
+
if (!allFailuresAsValues) throw cause;
|
|
1173
|
+
call = {
|
|
1174
|
+
result: Promise.reject(cause),
|
|
1175
|
+
send: () => {
|
|
1176
|
+
throw cause;
|
|
1177
|
+
},
|
|
1178
|
+
cancel: () => {
|
|
1179
|
+
throw cause;
|
|
1180
|
+
},
|
|
1181
|
+
ping: async () => {
|
|
1182
|
+
throw cause;
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
const checked = addCheckedResult(call.result.then((raw) => validateWireResult(member.resultSchema, raw, wireMethod)), member, allFailuresAsValues);
|
|
1187
|
+
return Object.assign(checked, {
|
|
1188
|
+
send: async (payload) => call.send(payload),
|
|
1189
|
+
cancel: async (reason) => call.cancel(reason),
|
|
1190
|
+
dispose: (reason) => call.dispose?.(reason),
|
|
1191
|
+
ping: () => call.ping()
|
|
1192
|
+
});
|
|
1193
|
+
};
|
|
1194
|
+
} else proxy[name] = (params) => {
|
|
1195
|
+
return addCheckedResult((async () => {
|
|
1196
|
+
this._validateOutboundParamsFor(member, wireMethod, params);
|
|
1197
|
+
const raw = await this.channel.sendRequest(wireMethod, params, sendOpts);
|
|
1198
|
+
return validateWireResult(member.resultSchema, raw, wireMethod);
|
|
1199
|
+
})(), member, allFailuresAsValues);
|
|
1200
|
+
};
|
|
1201
|
+
} else {
|
|
1202
|
+
const notify = (params) => {
|
|
1203
|
+
this._validateOutboundParamsFor(member, wireMethod, params);
|
|
1204
|
+
this.channel.sendNotification(wireMethod, params, sendOpts);
|
|
1205
|
+
};
|
|
1206
|
+
proxy[name] = barePrefix === void 0 ? async (params) => notify(params) : notify;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return iface.nestClient(proxy);
|
|
1210
|
+
}
|
|
1211
|
+
_validateOutboundParamsFor(member, wireMethod, params) {
|
|
1212
|
+
if (!this._validateOutboundParams) return;
|
|
1213
|
+
validateValue(member.paramsSchema, params, `Invalid params for ${wireMethod}`, ErrorCode.invalidParams);
|
|
1214
|
+
}
|
|
1215
|
+
async _handleRequest(call) {
|
|
1216
|
+
const parsed = this._parseRouted(call.method);
|
|
1217
|
+
if (!parsed.ok) return notFound(parsed.reason, call.method);
|
|
1218
|
+
const { entry, memberName } = parsed;
|
|
1219
|
+
const member = entry.iface.members[memberName];
|
|
1220
|
+
if (!member) return notFound("unknown-method", call.method);
|
|
1221
|
+
const handler = entry.handlers[memberName];
|
|
1222
|
+
if (!handler) return notFound("unknown-method", call.method);
|
|
1223
|
+
const userParams = stripLinkRpcWireMeta(call.params);
|
|
1224
|
+
const parsedParams = safeParse(member.paramsSchema, userParams);
|
|
1225
|
+
if (!parsedParams.success) return { error: {
|
|
1226
|
+
code: ErrorCode.invalidParams,
|
|
1227
|
+
message: "Invalid params",
|
|
1228
|
+
data: { issues: parsedParams.error.issues }
|
|
1229
|
+
} };
|
|
1230
|
+
if (member.kind !== "request") return notFound("unknown-method", call.method);
|
|
1231
|
+
const stream = this._buildStreamApi(call.stream, call.signal, member);
|
|
1232
|
+
try {
|
|
1233
|
+
const result = await handler(parsedParams.data, call.context, stream);
|
|
1234
|
+
const encodedError = encodeApplicationError(member, result);
|
|
1235
|
+
if (encodedError !== void 0) return { error: encodedError };
|
|
1236
|
+
validateValue(member.resultSchema, result, `Invalid result for ${call.method}`, ErrorCode.internalError);
|
|
1237
|
+
return { result: result === void 0 ? null : result };
|
|
1238
|
+
} catch (e) {
|
|
1239
|
+
if (e instanceof RpcError) return { error: {
|
|
1240
|
+
code: e.code,
|
|
1241
|
+
message: e.message,
|
|
1242
|
+
data: e.data
|
|
1243
|
+
} };
|
|
1244
|
+
return { error: {
|
|
1245
|
+
code: ErrorCode.internalError,
|
|
1246
|
+
message: e instanceof Error ? e.message : String(e)
|
|
1247
|
+
} };
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
_buildStreamApi(callStream, signal, member) {
|
|
1251
|
+
return {
|
|
1252
|
+
send: (payload) => {
|
|
1253
|
+
if (member.serverStreamSchema) {
|
|
1254
|
+
const r = safeParse(member.serverStreamSchema, payload);
|
|
1255
|
+
if (!r.success) throw new Error(`Server stream payload failed schema validation: ${r.error.message}`);
|
|
1256
|
+
}
|
|
1257
|
+
return callStream.send(payload);
|
|
1258
|
+
},
|
|
1259
|
+
onMessage: (listener) => {
|
|
1260
|
+
callStream.onMessage((raw) => {
|
|
1261
|
+
if (member.clientStreamSchema) {
|
|
1262
|
+
const r = safeParse(member.clientStreamSchema, raw);
|
|
1263
|
+
if (!r.success) return;
|
|
1264
|
+
listener(r.data);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
},
|
|
1269
|
+
ping: () => callStream.ping(),
|
|
1270
|
+
signal
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
_handleNotification(call) {
|
|
1274
|
+
const parsed = this._parseRouted(call.method);
|
|
1275
|
+
if (!parsed.ok) return;
|
|
1276
|
+
const { entry, memberName } = parsed;
|
|
1277
|
+
const member = entry.iface.members[memberName];
|
|
1278
|
+
if (member?.kind !== "notification") return;
|
|
1279
|
+
const handler = entry.handlers[memberName];
|
|
1280
|
+
if (!handler) return;
|
|
1281
|
+
const userParams = stripLinkRpcWireMeta(call.params);
|
|
1282
|
+
const parsedParams = safeParse(member.paramsSchema, userParams);
|
|
1283
|
+
if (!parsedParams.success) return;
|
|
1284
|
+
handler(parsedParams.data, call.context);
|
|
1285
|
+
}
|
|
1286
|
+
_parseRouted(method) {
|
|
1287
|
+
const parsed = parseMethodName(method);
|
|
1288
|
+
if (!parsed) return {
|
|
1289
|
+
ok: false,
|
|
1290
|
+
reason: "bad-method-grammar"
|
|
1291
|
+
};
|
|
1292
|
+
if (parsed.kind === "bare") {
|
|
1293
|
+
let selected;
|
|
1294
|
+
for (const binding of this._bareBindings.values()) if (parsed.member.startsWith(binding.prefix) && (selected === void 0 || binding.prefix.length > selected.prefix.length)) selected = binding;
|
|
1295
|
+
if (!selected) return {
|
|
1296
|
+
ok: false,
|
|
1297
|
+
reason: "no-preset"
|
|
1298
|
+
};
|
|
1299
|
+
return {
|
|
1300
|
+
ok: true,
|
|
1301
|
+
entry: selected.entry,
|
|
1302
|
+
memberName: parsed.member.slice(selected.prefix.length)
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
const serviceId = parsed.kind === "full" ? parsed.serviceId : void 0;
|
|
1306
|
+
const key = `${serviceId ?? ""}::${parsed.interfaceId}`;
|
|
1307
|
+
const entry = this._registry.get(key);
|
|
1308
|
+
if (!entry) return {
|
|
1309
|
+
ok: false,
|
|
1310
|
+
reason: serviceId ? "unknown-service" : "unknown-interface"
|
|
1311
|
+
};
|
|
1312
|
+
return {
|
|
1313
|
+
ok: true,
|
|
1314
|
+
entry,
|
|
1315
|
+
memberName: parsed.member
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
_installServiceInspection(serviceId) {
|
|
1319
|
+
if (this._serviceInspectionRegistrations.has(serviceId)) return;
|
|
1320
|
+
const info = this._inspection;
|
|
1321
|
+
const host = this._inspectionHost;
|
|
1322
|
+
if (info === void 0 || host === void 0) return;
|
|
1323
|
+
this._serviceInspectionRegistrations.set(serviceId, [host.expose(this, {
|
|
1324
|
+
serviceId,
|
|
1325
|
+
portId: info.portId
|
|
1326
|
+
})]);
|
|
1327
|
+
}
|
|
1328
|
+
_removeServiceInspection(serviceId) {
|
|
1329
|
+
const registrations = this._serviceInspectionRegistrations.get(serviceId);
|
|
1330
|
+
if (registrations === void 0) return;
|
|
1331
|
+
this._serviceInspectionRegistrations.delete(serviceId);
|
|
1332
|
+
for (const registration of registrations.reverse()) registration.dispose();
|
|
1333
|
+
}
|
|
1334
|
+
_hasBusinessServiceRegistration(serviceId) {
|
|
1335
|
+
return Array.from(this._registry.values()).some((entry) => entry.serviceId === serviceId && !entry.internalInspection);
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
function createInspectionId(kind) {
|
|
1339
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
1340
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
1341
|
+
return `${kind}:${bytesToBase64Url(bytes)}`;
|
|
1342
|
+
}
|
|
1343
|
+
/** Structural equality for two CNF root-node-id requirement collections. */
|
|
1344
|
+
function rootPrincipalSetsEqual(a, b) {
|
|
1345
|
+
if (a.length !== b.length) return false;
|
|
1346
|
+
for (let i = 0; i < a.length; i++) {
|
|
1347
|
+
const sa = a[i];
|
|
1348
|
+
const sb = b[i];
|
|
1349
|
+
if (sa.length !== sb.length) return false;
|
|
1350
|
+
for (let j = 0; j < sa.length; j++) {
|
|
1351
|
+
if (sa[j].principal !== sb[j].principal) return false;
|
|
1352
|
+
if (sa[j].transitive === true !== (sb[j].transitive === true)) return false;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return true;
|
|
1356
|
+
}
|
|
1357
|
+
function directoryWatcherMatchesEntry(watcher, entry) {
|
|
1358
|
+
const serviceId = entry.serviceId ?? "";
|
|
1359
|
+
return (watcher.interfaceId === void 0 || watcher.interfaceId === entry.iface.info.id) && (watcher.interfaceIdPrefix === void 0 || entry.iface.info.id.startsWith(watcher.interfaceIdPrefix)) && (watcher.serviceId === void 0 || watcher.serviceId === serviceId) && serviceIdMatchesScopes(serviceId, watcher.serviceIdScopes);
|
|
1360
|
+
}
|
|
1361
|
+
function addCheckedResult(promise, member, allFailuresAsValues = false) {
|
|
1362
|
+
const settled = promise.then((value) => ({
|
|
1363
|
+
ok: true,
|
|
1364
|
+
value
|
|
1365
|
+
}), (cause) => ({
|
|
1366
|
+
ok: false,
|
|
1367
|
+
error: decodeCheckedError(cause, member.applicationErrors),
|
|
1368
|
+
cause
|
|
1369
|
+
}));
|
|
1370
|
+
const result = settled.then((outcome) => {
|
|
1371
|
+
if (outcome.ok) return outcome.value;
|
|
1372
|
+
if (allFailuresAsValues) return new RpcFailure(outcome.error.kind === "application" ? {
|
|
1373
|
+
kind: "application",
|
|
1374
|
+
error: outcome.error
|
|
1375
|
+
} : {
|
|
1376
|
+
kind: "generic",
|
|
1377
|
+
error: outcome.error
|
|
1378
|
+
});
|
|
1379
|
+
if (outcome.error.kind === "application") return new RpcFailure(outcome.error);
|
|
1380
|
+
throw outcome.error.kind === "nonCompliantServer" ? outcome.error : outcome.cause;
|
|
1381
|
+
});
|
|
1382
|
+
return Object.assign(result, { result: async () => {
|
|
1383
|
+
result.catch(() => {});
|
|
1384
|
+
const outcome = await settled;
|
|
1385
|
+
return outcome.ok ? outcome : {
|
|
1386
|
+
ok: false,
|
|
1387
|
+
error: outcome.error
|
|
1388
|
+
};
|
|
1389
|
+
} });
|
|
1390
|
+
}
|
|
1391
|
+
function decodeCheckedError(cause, descriptors) {
|
|
1392
|
+
if (cause instanceof NonCompliantServerError) return cause;
|
|
1393
|
+
if (!(cause instanceof RpcError) || cause.origin !== "remote") return {
|
|
1394
|
+
kind: cause instanceof RpcError && cause.origin === "transport" ? "transport" : "local",
|
|
1395
|
+
cause
|
|
1396
|
+
};
|
|
1397
|
+
const original = {
|
|
1398
|
+
code: cause.code,
|
|
1399
|
+
message: cause.message,
|
|
1400
|
+
...cause.hasData ? { data: cause.data } : {}
|
|
1401
|
+
};
|
|
1402
|
+
const candidates = descriptors.filter((candidate) => candidate.code === cause.code).sort((a, b) => Number(b.type !== void 0) - Number(a.type !== void 0));
|
|
1403
|
+
const body = {
|
|
1404
|
+
message: cause.message,
|
|
1405
|
+
...cause.hasData ? { data: cause.data } : {}
|
|
1406
|
+
};
|
|
1407
|
+
const issues = [];
|
|
1408
|
+
for (const descriptor of candidates) {
|
|
1409
|
+
const parsed = parseDeclaredErrorBody(descriptor, body);
|
|
1410
|
+
if (!parsed.success) {
|
|
1411
|
+
issues.push(...parsed.issues);
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
if (descriptor.type === void 0) return brandApplicationError({
|
|
1415
|
+
...parsed.data,
|
|
1416
|
+
kind: "application",
|
|
1417
|
+
code: descriptor.code
|
|
1418
|
+
});
|
|
1419
|
+
const envelope = parsed.data.data;
|
|
1420
|
+
return brandApplicationError({
|
|
1421
|
+
kind: "application",
|
|
1422
|
+
code: descriptor.code,
|
|
1423
|
+
message: parsed.data.message,
|
|
1424
|
+
type: descriptor.type,
|
|
1425
|
+
...typeof envelope === "object" && envelope !== null && "data" in envelope ? { data: envelope.data } : {}
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
if (candidates.length > 0) return new NonCompliantServerError(original, issues);
|
|
1429
|
+
return {
|
|
1430
|
+
kind: "remote",
|
|
1431
|
+
...original
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
function encodeApplicationError(member, value) {
|
|
1435
|
+
if (!isApplicationErrorValue(value)) return void 0;
|
|
1436
|
+
const candidate = value;
|
|
1437
|
+
const descriptor = member.applicationErrors.find((error) => error.code === candidate.code && error.type === candidate.type && (error.bodySchema !== void 0 || error.type !== void 0 || error.message === candidate.message));
|
|
1438
|
+
if (descriptor === void 0) throw new RpcError(`Undeclared application error ${String(candidate.code)}`, ErrorCode.internalError);
|
|
1439
|
+
const data = Object.hasOwn(value, "data") ? { data: value.data } : {};
|
|
1440
|
+
const body = {
|
|
1441
|
+
message: value.message,
|
|
1442
|
+
...descriptor.type === void 0 ? data : { data: {
|
|
1443
|
+
type: descriptor.type,
|
|
1444
|
+
...data
|
|
1445
|
+
} }
|
|
1446
|
+
};
|
|
1447
|
+
const parsed = parseDeclaredErrorBody(descriptor, body);
|
|
1448
|
+
const issues = parsed.success ? jsonIssues(body) : parsed.issues;
|
|
1449
|
+
if (issues.length > 0) throw new RpcError(`Invalid data for application error ${descriptor.code}`, ErrorCode.internalError, { issues: issues.map((issue) => ({ ...issue })) });
|
|
1450
|
+
return {
|
|
1451
|
+
code: descriptor.code,
|
|
1452
|
+
...body
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function validateWireResult(schema, raw, wireMethod) {
|
|
1456
|
+
const value = normalizeWireResult(schema, raw);
|
|
1457
|
+
validateValue(schema, value, `Invalid result for ${wireMethod}`, ErrorCode.internalError);
|
|
1458
|
+
return value;
|
|
1459
|
+
}
|
|
1460
|
+
/** Validate at both ends without stripping extra keys or applying schema transforms. */
|
|
1461
|
+
function validateValue(schema, value, message, code) {
|
|
1462
|
+
const checked = safeParse(schema, value);
|
|
1463
|
+
if (!checked.success) throw new RpcError(message, code, { issues: checked.error.issues });
|
|
1464
|
+
}
|
|
1465
|
+
/** Undo the server's undefined → null convention only when the schema rejects null. */
|
|
1466
|
+
function normalizeWireResult(schema, raw) {
|
|
1467
|
+
if (raw !== null) return raw;
|
|
1468
|
+
if (safeParse(schema, null).success) return null;
|
|
1469
|
+
if (safeParse(schema, void 0).success) return void 0;
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
/** Service-scoped handle returned by `connection.service(id)`. */
|
|
1473
|
+
var ServiceHandle = class {
|
|
1474
|
+
_connection;
|
|
1475
|
+
_serviceId;
|
|
1476
|
+
constructor(_connection, _serviceId) {
|
|
1477
|
+
this._connection = _connection;
|
|
1478
|
+
this._serviceId = _serviceId;
|
|
1479
|
+
}
|
|
1480
|
+
getResultClient(iface, opts = {}) {
|
|
1481
|
+
return this._connection.getResultClient(iface, {
|
|
1482
|
+
...opts,
|
|
1483
|
+
serviceId: this._serviceId
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
get(iface, opts = {}) {
|
|
1487
|
+
return this._connection.get(iface, {
|
|
1488
|
+
...opts,
|
|
1489
|
+
serviceId: this._serviceId
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
register(ifaceOrTarget, handlers, opts = {}) {
|
|
1493
|
+
return this._connection.register(ifaceOrTarget, handlers, {
|
|
1494
|
+
...opts,
|
|
1495
|
+
serviceId: this._serviceId
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
function notFound(reason, method) {
|
|
1500
|
+
return { error: {
|
|
1501
|
+
code: ErrorCode.methodNotFound,
|
|
1502
|
+
message: `Method not found: ${method}`,
|
|
1503
|
+
data: {
|
|
1504
|
+
reason,
|
|
1505
|
+
method
|
|
1506
|
+
}
|
|
1507
|
+
} };
|
|
1508
|
+
}
|
|
1509
|
+
//#endregion
|
|
1510
|
+
//#region src/transport/messageTransport.ts
|
|
1511
|
+
/**
|
|
1512
|
+
* Observe every message crossing a transport without changing its buffering or
|
|
1513
|
+
* lifecycle behavior.
|
|
1514
|
+
*/
|
|
1515
|
+
function traceMessageTransport(transport, trace) {
|
|
1516
|
+
return {
|
|
1517
|
+
send(message) {
|
|
1518
|
+
trace("send", message);
|
|
1519
|
+
return transport.send(message);
|
|
1520
|
+
},
|
|
1521
|
+
setListener(listener) {
|
|
1522
|
+
transport.setListener(listener === void 0 ? void 0 : (message) => {
|
|
1523
|
+
trace("receive", message);
|
|
1524
|
+
listener(message);
|
|
1525
|
+
});
|
|
1526
|
+
},
|
|
1527
|
+
dispose() {
|
|
1528
|
+
transport.dispose();
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Two in-memory transports wired back-to-back. Useful for tests and for
|
|
1534
|
+
* same-process bridges.
|
|
1535
|
+
*
|
|
1536
|
+
* Generic in the per-direction payload type so asymmetric pairs (e.g. one
|
|
1537
|
+
* side sends plain `JsonRpcMessage`, the other side sends
|
|
1538
|
+
* `JsonRpcMessage & { context: Participant }`) are expressible at the type
|
|
1539
|
+
* level. At runtime both halves just hand objects through by reference.
|
|
1540
|
+
*/
|
|
1541
|
+
var TransportPair = class {
|
|
1542
|
+
a;
|
|
1543
|
+
b;
|
|
1544
|
+
constructor() {
|
|
1545
|
+
const aT = new _InMemoryTransport();
|
|
1546
|
+
const bT = new _InMemoryTransport();
|
|
1547
|
+
aT._peer = bT;
|
|
1548
|
+
bT._peer = aT;
|
|
1549
|
+
this.a = aT;
|
|
1550
|
+
this.b = bT;
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
var _InMemoryTransport = class {
|
|
1554
|
+
_peer;
|
|
1555
|
+
_listener;
|
|
1556
|
+
_buffer = [];
|
|
1557
|
+
_closed = false;
|
|
1558
|
+
send(message) {
|
|
1559
|
+
if (this._closed || this._peer._closed) return;
|
|
1560
|
+
this._peer._deliver(message);
|
|
1561
|
+
}
|
|
1562
|
+
setListener(listener) {
|
|
1563
|
+
this._listener = listener;
|
|
1564
|
+
if (listener) while (this._buffer.length > 0 && this._listener) {
|
|
1565
|
+
const m = this._buffer.shift();
|
|
1566
|
+
this._listener(m);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
dispose() {
|
|
1570
|
+
this._closed = true;
|
|
1571
|
+
}
|
|
1572
|
+
_deliver(m) {
|
|
1573
|
+
if (this._listener) this._listener(m);
|
|
1574
|
+
else this._buffer.push(m);
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
//#endregion
|
|
1578
|
+
//#region src/connection/endpointUri.ts
|
|
1579
|
+
const TOKEN_PARAM = "token";
|
|
1580
|
+
const BROKER_PARAM = "broker";
|
|
1581
|
+
const COMMAND_PARAM = "command";
|
|
1582
|
+
const ARGV_PARAM = "argv";
|
|
1583
|
+
const PROVISION_SLOT_PARAM = "provisionSlot";
|
|
1584
|
+
const ENV_PARAM = "env";
|
|
1585
|
+
const CWD_PARAM = "cwd";
|
|
1586
|
+
function _tryParseUrl(uri) {
|
|
1587
|
+
try {
|
|
1588
|
+
return new URL(uri);
|
|
1589
|
+
} catch {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
function _looksLikeWsUrl(uri) {
|
|
1594
|
+
return /^wss?:\/\//i.test(uri);
|
|
1595
|
+
}
|
|
1596
|
+
/** True for a Windows named-pipe path (`\\.\pipe\…` or `\\?\pipe\…`). */
|
|
1597
|
+
function _isWindowsPipePath(path) {
|
|
1598
|
+
return /^\\\\[.?]\\pipe\\/i.test(path);
|
|
1599
|
+
}
|
|
1600
|
+
function _parseCommand(url) {
|
|
1601
|
+
const argv = url.searchParams.getAll(ARGV_PARAM);
|
|
1602
|
+
if (argv.length > 0) return { argv };
|
|
1603
|
+
const command = url.searchParams.get(COMMAND_PARAM);
|
|
1604
|
+
if (command !== null) return { command };
|
|
1605
|
+
throw new Error(`endpoint '${url.protocol}' requires a '${COMMAND_PARAM}' or '${ARGV_PARAM}' query parameter`);
|
|
1606
|
+
}
|
|
1607
|
+
/** Parse repeated `env=KEY=VALUE` params into a record (first `=` splits). */
|
|
1608
|
+
function _parseEnv(url) {
|
|
1609
|
+
const entries = url.searchParams.getAll(ENV_PARAM);
|
|
1610
|
+
if (entries.length === 0) return;
|
|
1611
|
+
const env = {};
|
|
1612
|
+
for (const entry of entries) {
|
|
1613
|
+
const eq = entry.indexOf("=");
|
|
1614
|
+
if (eq === -1) throw new Error(`endpoint '${ENV_PARAM}' param must be 'KEY=VALUE', got '${entry}'`);
|
|
1615
|
+
env[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
1616
|
+
}
|
|
1617
|
+
return env;
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Parse a strict endpoint URI into an {@link ResolvedEndpoint}. Throws on an
|
|
1621
|
+
* unknown scheme or a malformed command endpoint. A bare (scheme-less) string
|
|
1622
|
+
* is auto-detected as `ws`/`wss` URL or a socket path.
|
|
1623
|
+
*/
|
|
1624
|
+
function parseEndpointUri(uri) {
|
|
1625
|
+
const trimmed = uri.trim();
|
|
1626
|
+
if (trimmed === "") throw new Error("endpoint URI is empty");
|
|
1627
|
+
const url = _tryParseUrl(trimmed);
|
|
1628
|
+
if (!url) {
|
|
1629
|
+
if (_looksLikeWsUrl(trimmed)) return {
|
|
1630
|
+
kind: "ws",
|
|
1631
|
+
url: trimmed
|
|
1632
|
+
};
|
|
1633
|
+
return {
|
|
1634
|
+
kind: "socket",
|
|
1635
|
+
path: trimmed
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
switch (url.protocol) {
|
|
1639
|
+
case "ws:":
|
|
1640
|
+
case "wss:": {
|
|
1641
|
+
const token = url.searchParams.get(TOKEN_PARAM) ?? void 0;
|
|
1642
|
+
url.searchParams.delete(TOKEN_PARAM);
|
|
1643
|
+
const cleanUrl = url.toString();
|
|
1644
|
+
return token !== void 0 ? {
|
|
1645
|
+
kind: "ws",
|
|
1646
|
+
url: cleanUrl,
|
|
1647
|
+
token
|
|
1648
|
+
} : {
|
|
1649
|
+
kind: "ws",
|
|
1650
|
+
url: cleanUrl
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
case "ws-no-init:": return {
|
|
1654
|
+
kind: "ws-no-init",
|
|
1655
|
+
url: new URL(trimmed.replace(/^ws-no-init:/i, "ws:")).toString()
|
|
1656
|
+
};
|
|
1657
|
+
case "unix:": {
|
|
1658
|
+
const token = url.searchParams.get(TOKEN_PARAM) ?? void 0;
|
|
1659
|
+
const brokerMode = _parseBrokerMode(url);
|
|
1660
|
+
return {
|
|
1661
|
+
kind: "socket",
|
|
1662
|
+
path: decodeURIComponent(url.pathname),
|
|
1663
|
+
...token !== void 0 ? { token } : {},
|
|
1664
|
+
...brokerMode !== void 0 ? { brokerMode } : {}
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
case "npipe:": {
|
|
1668
|
+
const token = url.searchParams.get(TOKEN_PARAM) ?? void 0;
|
|
1669
|
+
const brokerMode = _parseBrokerMode(url);
|
|
1670
|
+
return {
|
|
1671
|
+
kind: "socket",
|
|
1672
|
+
path: `\\\\${url.host === "" ? "." : url.host}${decodeURIComponent(url.pathname).replace(/\//g, "\\")}`,
|
|
1673
|
+
...token !== void 0 ? { token } : {},
|
|
1674
|
+
...brokerMode !== void 0 ? { brokerMode } : {}
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
case "cmd-stdio:": {
|
|
1678
|
+
const env = _parseEnv(url);
|
|
1679
|
+
const cwd = url.searchParams.get(CWD_PARAM) ?? void 0;
|
|
1680
|
+
return {
|
|
1681
|
+
kind: "cmd-stdio",
|
|
1682
|
+
command: _parseCommand(url),
|
|
1683
|
+
...env !== void 0 ? { env } : {},
|
|
1684
|
+
...cwd !== void 0 ? { cwd } : {}
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
case "cmd:": {
|
|
1688
|
+
const command = _parseCommand(url);
|
|
1689
|
+
const provisionSlot = url.searchParams.get(PROVISION_SLOT_PARAM) ?? void 0;
|
|
1690
|
+
const env = _parseEnv(url);
|
|
1691
|
+
const cwd = url.searchParams.get(CWD_PARAM) ?? void 0;
|
|
1692
|
+
return {
|
|
1693
|
+
kind: "cmd-env",
|
|
1694
|
+
command,
|
|
1695
|
+
...provisionSlot !== void 0 ? { provisionSlot } : {},
|
|
1696
|
+
...env !== void 0 ? { env } : {},
|
|
1697
|
+
...cwd !== void 0 ? { cwd } : {}
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
default: throw new Error(`unsupported endpoint scheme '${url.protocol}' (expected unix:, npipe:, ws:, wss:, ws-no-init:, cmd:, or cmd-stdio:)`);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
const REDACTED = "***";
|
|
1704
|
+
function _appendToken(params, token, reveal) {
|
|
1705
|
+
if (token === void 0) return;
|
|
1706
|
+
params.set(TOKEN_PARAM, REDACTED);
|
|
1707
|
+
}
|
|
1708
|
+
function _parseBrokerMode(url) {
|
|
1709
|
+
const value = url.searchParams.get(BROKER_PARAM);
|
|
1710
|
+
if (value === null) return void 0;
|
|
1711
|
+
if (value === "linkrpc" || value === "raw") return value;
|
|
1712
|
+
throw new Error(`endpoint '${BROKER_PARAM}' param must be 'linkrpc' or 'raw', got '${value}'`);
|
|
1713
|
+
}
|
|
1714
|
+
function _appendCommand(params, command) {
|
|
1715
|
+
if ("argv" in command) for (const a of command.argv) params.append(ARGV_PARAM, a);
|
|
1716
|
+
else params.set(COMMAND_PARAM, command.command);
|
|
1717
|
+
}
|
|
1718
|
+
function _appendEnv(params, env) {
|
|
1719
|
+
if (env === void 0) return;
|
|
1720
|
+
for (const [key, value] of Object.entries(env)) params.append(ENV_PARAM, `${key}=${value}`);
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Render an {@link ResolvedEndpoint} back to a canonical strict URI. The token is
|
|
1724
|
+
* redacted (`***`) unless `revealToken` is set, so the result is paste-safe for
|
|
1725
|
+
* logs. Round-trips with {@link parseEndpointUri} when `revealToken` is true.
|
|
1726
|
+
*/
|
|
1727
|
+
function formatEndpointUri(spec, options) {
|
|
1728
|
+
switch (spec.kind) {
|
|
1729
|
+
case "socket": {
|
|
1730
|
+
const params = new URLSearchParams();
|
|
1731
|
+
_appendToken(params, spec.token);
|
|
1732
|
+
if (spec.brokerMode !== void 0) params.set(BROKER_PARAM, spec.brokerMode);
|
|
1733
|
+
const query = params.toString();
|
|
1734
|
+
const suffix = query === "" ? "" : `?${query}`;
|
|
1735
|
+
if (_isWindowsPipePath(spec.path)) {
|
|
1736
|
+
const m = spec.path.match(/^\\\\([.?])\\(.*)$/);
|
|
1737
|
+
return `npipe://${m ? m[1] : "."}/${(m ? m[2] : spec.path).replace(/\\/g, "/")}${suffix}`;
|
|
1738
|
+
}
|
|
1739
|
+
return `unix:${spec.path}${suffix}`;
|
|
1740
|
+
}
|
|
1741
|
+
case "ws": {
|
|
1742
|
+
const url = new URL(spec.url);
|
|
1743
|
+
_appendToken(url.searchParams, spec.token);
|
|
1744
|
+
return url.toString();
|
|
1745
|
+
}
|
|
1746
|
+
case "ws-no-init": return spec.url.replace(/^ws:/i, "ws-no-init:");
|
|
1747
|
+
case "cmd-stdio": {
|
|
1748
|
+
const params = new URLSearchParams();
|
|
1749
|
+
_appendCommand(params, spec.command);
|
|
1750
|
+
_appendEnv(params, spec.env);
|
|
1751
|
+
if (spec.cwd !== void 0) params.set(CWD_PARAM, spec.cwd);
|
|
1752
|
+
return `cmd-stdio:?${params.toString()}`;
|
|
1753
|
+
}
|
|
1754
|
+
case "cmd-env": {
|
|
1755
|
+
const params = new URLSearchParams();
|
|
1756
|
+
_appendCommand(params, spec.command);
|
|
1757
|
+
if (spec.provisionSlot !== void 0) params.set(PROVISION_SLOT_PARAM, spec.provisionSlot);
|
|
1758
|
+
_appendEnv(params, spec.env);
|
|
1759
|
+
if (spec.cwd !== void 0) params.set(CWD_PARAM, spec.cwd);
|
|
1760
|
+
return `cmd:?${params.toString()}`;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
createMergeProxy(globalThis, {});
|
|
1765
|
+
function createMergeProxy(baseObj, extObj) {
|
|
1766
|
+
return new Proxy(baseObj, {
|
|
1767
|
+
get(_target, prop, _receiver) {
|
|
1768
|
+
if (prop in extObj) return extObj[prop];
|
|
1769
|
+
else return baseObj[prop];
|
|
1770
|
+
},
|
|
1771
|
+
set(_target, prop, value) {
|
|
1772
|
+
if (prop in extObj) delete extObj[prop];
|
|
1773
|
+
baseObj[prop] = value;
|
|
1774
|
+
return true;
|
|
1775
|
+
},
|
|
1776
|
+
deleteProperty(_target, prop) {
|
|
1777
|
+
let success = false;
|
|
1778
|
+
if (prop in extObj) {
|
|
1779
|
+
delete extObj[prop];
|
|
1780
|
+
success = true;
|
|
1781
|
+
}
|
|
1782
|
+
if (prop in baseObj) {
|
|
1783
|
+
delete baseObj[prop];
|
|
1784
|
+
success = true;
|
|
1785
|
+
}
|
|
1786
|
+
return success;
|
|
1787
|
+
},
|
|
1788
|
+
ownKeys(_target) {
|
|
1789
|
+
const baseKeys = Reflect.ownKeys(baseObj);
|
|
1790
|
+
const extKeys = Reflect.ownKeys(extObj);
|
|
1791
|
+
const extKeysSet = new Set(extKeys);
|
|
1792
|
+
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
1793
|
+
},
|
|
1794
|
+
defineProperty(_target, prop, desc) {
|
|
1795
|
+
if (prop in extObj) delete extObj[prop];
|
|
1796
|
+
Reflect.defineProperty(baseObj, prop, desc);
|
|
1797
|
+
return true;
|
|
1798
|
+
},
|
|
1799
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
1800
|
+
if (prop in extObj) return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
1801
|
+
else return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
1802
|
+
},
|
|
1803
|
+
has(_target, prop) {
|
|
1804
|
+
return prop in extObj || prop in baseObj;
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
//#endregion
|
|
1809
|
+
//#region src/identity/identity.interfaces.ts
|
|
1810
|
+
const zB64 = string();
|
|
1811
|
+
/**
|
|
1812
|
+
* Per-participant key oracle. Reached on the participant's root overlay
|
|
1813
|
+
* (form-2 method names like `identity::sign`). Private key material lives
|
|
1814
|
+
* in the executor; the participant only sees the operations.
|
|
1815
|
+
*
|
|
1816
|
+
* Wire shape:
|
|
1817
|
+
* sign / wrap / unwrap all take and return base64url-encoded bytes.
|
|
1818
|
+
* `domain` is a caller-chosen string bound into HPKE info and the AEAD
|
|
1819
|
+
* AAD. `unwrap` succeeds only when invoked with the exact `domain` that
|
|
1820
|
+
* `wrap` was called with. Use a stable, namespaced string (e.g.
|
|
1821
|
+
* `secrets.vault.master-key.v1`).
|
|
1822
|
+
*/
|
|
1823
|
+
defineInterface({
|
|
1824
|
+
id: "identity",
|
|
1825
|
+
description: "Per-participant key oracle: exposes signing and HPKE-based wrap/unwrap for the identity the executor has attached to this participant's root overlay. Private keys never leave the executor."
|
|
1826
|
+
}, {
|
|
1827
|
+
getPrincipal: requestType(object({}), object({ principal: string() })),
|
|
1828
|
+
getWrapPublicKey: requestType(object({}), object({ wrapPublicKey: zB64 })),
|
|
1829
|
+
sign: requestType(object({ bytes: zB64 }), object({ signature: zB64 })),
|
|
1830
|
+
wrap: requestType(object({
|
|
1831
|
+
domain: string(),
|
|
1832
|
+
bytes: zB64
|
|
1833
|
+
}), object({ blob: zB64 })),
|
|
1834
|
+
unwrap: requestType(object({
|
|
1835
|
+
domain: string(),
|
|
1836
|
+
blob: zB64
|
|
1837
|
+
}), object({ bytes: zB64 }))
|
|
1838
|
+
});
|
|
1839
|
+
/**
|
|
1840
|
+
* Per-identity persistent key/value store. Reached on the participant's
|
|
1841
|
+
* root overlay as `identity.storage::get` etc. — same overlay that serves
|
|
1842
|
+
* `identity::*`, same single-participant addressability.
|
|
1843
|
+
*
|
|
1844
|
+
* Storage scope is the executor's managed-identity slot (e.g.
|
|
1845
|
+
* `service:demo`). Lifecycle is bound to the identity: when the executor
|
|
1846
|
+
* deletes the identity for a slot, the storage for that slot is wiped in
|
|
1847
|
+
* the same operation. New identity issued for the same slot starts empty.
|
|
1848
|
+
*
|
|
1849
|
+
* The executor encrypts the backing file at rest with the same secret it
|
|
1850
|
+
* uses for identity files — see {@link IdentityKeystore} in
|
|
1851
|
+
* `@hediet/linkrpc/node`.
|
|
1852
|
+
*
|
|
1853
|
+
* Keys must match `^[A-Za-z0-9._/-]{1,256}$`. `/` is conventional for
|
|
1854
|
+
* namespacing (`caps.v1`, `prefs/foo`) and `list({ prefix })` honours
|
|
1855
|
+
* that. Values are arbitrary JSON.
|
|
1856
|
+
*/
|
|
1857
|
+
defineInterface({
|
|
1858
|
+
id: "identity.storage",
|
|
1859
|
+
description: "Per-identity persistent key/value store. Scope is the executor's managed-identity slot; lifecycle is tied to the identity itself. Backed by an at-rest-encrypted file in the executor."
|
|
1860
|
+
}, {
|
|
1861
|
+
get: requestType(object({ key: string() }), object({ value: optional(unknown()) })),
|
|
1862
|
+
set: requestType(object({
|
|
1863
|
+
key: string(),
|
|
1864
|
+
value: unknown()
|
|
1865
|
+
}), object({})),
|
|
1866
|
+
delete: requestType(object({ key: string() }), object({ existed: boolean() })),
|
|
1867
|
+
list: requestType(object({ prefix: optional(string()) }), object({ keys: array(string()) }))
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
export { HUBRPC_UNSIGNED_KEY as At, formatEndpointUri as B, stripLinkRpcWireMeta as Dt, TransportPair as G, parseEndpointUri as H, HUBRPC_META_KEY as Ot, directoryInterface as St, zServiceIdPattern as Tt, parseMethodName as Vt, NonCompliantServerError as Xt, JsonRpcChannel as at, LinkRpcConnection as et, serviceIdMatchesScopes as gt, serviceIdMatchesPattern as ht, isInterfaceTarget as it, HUBRPC_SIGNATURE_KEY as kt, defaultInterfaceTarget as nt, Channel as ot, traceMessageTransport as q, RpcError as st, ServiceHandle as tt, schemasInterface as wt, defaultsInterface as xt };
|
|
1871
|
+
//# sourceMappingURL=src-DYVSK_tz.js.map
|