wire-mesh-core 1.15.0 → 1.17.0
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/dist/adapters/frame-codec.d.cts +1 -1
- package/dist/adapters/frame-codec.d.mts +1 -1
- package/dist/adapters/node-identity.d.cts +2 -2
- package/dist/adapters/node-identity.d.mts +2 -2
- package/dist/adapters/tcp-transport.d.cts +1 -1
- package/dist/adapters/tcp-transport.d.mts +1 -1
- package/dist/adapters/tls-transport.d.cts +1 -1
- package/dist/adapters/tls-transport.d.mts +1 -1
- package/dist/domain/capability-grant.cjs +90 -0
- package/dist/domain/capability-grant.d.cts +44 -0
- package/dist/domain/capability-grant.d.mts +44 -0
- package/dist/domain/capability-grant.mjs +87 -0
- package/dist/domain/capability-request.d.cts +2 -2
- package/dist/domain/capability-request.d.mts +2 -2
- package/dist/domain/device-id.d.cts +1 -1
- package/dist/domain/device-id.d.mts +1 -1
- package/dist/domain/handshake.d.cts +1 -1
- package/dist/domain/handshake.d.mts +1 -1
- package/dist/domain/mesh-session.d.cts +3 -3
- package/dist/domain/mesh-session.d.mts +3 -3
- package/dist/domain/relay-hub.cjs +21 -3
- package/dist/domain/relay-hub.d.cts +2 -2
- package/dist/domain/relay-hub.d.mts +2 -2
- package/dist/domain/relay-hub.mjs +21 -3
- package/dist/domain/revocation-view.d.cts +1 -1
- package/dist/domain/revocation-view.d.mts +1 -1
- package/dist/domain/room-token-verification.d.cts +1 -1
- package/dist/domain/room-token-verification.d.mts +1 -1
- package/dist/domain/tokens.cjs +2 -1
- package/dist/domain/tokens.d.cts +6 -2
- package/dist/domain/tokens.d.mts +6 -2
- package/dist/domain/tokens.mjs +2 -2
- package/dist/generated/protocol.cjs +6 -0
- package/dist/generated/protocol.d.cts +2 -2
- package/dist/generated/protocol.d.mts +2 -2
- package/dist/generated/protocol.mjs +6 -1
- package/dist/{identity-D8iJZRZZ.d.mts → identity-B1hBV3YA.d.mts} +1 -1
- package/dist/{identity-DkEe1465.d.cts → identity-BldV8xFj.d.cts} +1 -1
- package/dist/ports/identity.d.cts +1 -1
- package/dist/ports/identity.d.mts +1 -1
- package/dist/ports/transport.d.cts +1 -1
- package/dist/ports/transport.d.mts +1 -1
- package/dist/{protocol-MnxTzpRP.d.cts → protocol-Dhswk7YD.d.cts} +39 -1
- package/dist/{protocol-MnxTzpRP.d.mts → protocol-Dhswk7YD.d.mts} +39 -1
- package/dist/{transport-Da7GP9ya.d.cts → transport-BNNdUBq-.d.mts} +1 -1
- package/dist/{transport-ESMXRz3s.d.mts → transport-hEUxrdhT.d.cts} +1 -1
- package/package.json +5 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as Frame } from "../protocol-Dhswk7YD.cjs";
|
|
2
2
|
//#region src/adapters/frame-codec.d.ts
|
|
3
3
|
export declare function messageFromFrame(frame: Frame): Uint8Array<ArrayBuffer>;
|
|
4
4
|
/** A frame that fails schema validation, caught separately from a decode failure so it can be dropped without disconnecting. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as Frame } from "../protocol-Dhswk7YD.mjs";
|
|
2
2
|
//#region src/adapters/frame-codec.d.ts
|
|
3
3
|
export declare function messageFromFrame(frame: Frame): Uint8Array<ArrayBuffer>;
|
|
4
4
|
/** A frame that fails schema validation, caught separately from a decode failure so it can be dropped without disconnecting. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { P as IdentityKey, x as DeviceId } from "../protocol-Dhswk7YD.cjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-BldV8xFj.cjs";
|
|
3
3
|
import { webcrypto } from "node:crypto";
|
|
4
4
|
//#region src/adapters/node-identity.d.ts
|
|
5
5
|
export declare function deriveDeviceId(publicKey: Uint8Array): Promise<DeviceId>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { P as IdentityKey, x as DeviceId } from "../protocol-Dhswk7YD.mjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-B1hBV3YA.mjs";
|
|
3
3
|
import { webcrypto } from "node:crypto";
|
|
4
4
|
//#region src/adapters/node-identity.d.ts
|
|
5
5
|
export declare function deriveDeviceId(publicKey: Uint8Array): Promise<DeviceId>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as Transport } from "../transport-
|
|
1
|
+
import { r as Transport } from "../transport-hEUxrdhT.cjs";
|
|
2
2
|
//#region src/adapters/tcp-transport.d.ts
|
|
3
3
|
/** A Node net.Socket-based Transport: length-prefixed, CBOR-encoded frames over plain TCP -- matching Cascade's own transport shape, since interop with Cascade nodes is wire-mesh's stated goal. Framing (not TLS) is this adapter's own concern; a TLS-terminated variant is a separate adapter behind the same Transport contract. */
|
|
4
4
|
export declare function createTcpTransport(): Transport;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as Transport } from "../transport-
|
|
1
|
+
import { r as Transport } from "../transport-BNNdUBq-.mjs";
|
|
2
2
|
//#region src/adapters/tcp-transport.d.ts
|
|
3
3
|
/** A Node net.Socket-based Transport: length-prefixed, CBOR-encoded frames over plain TCP -- matching Cascade's own transport shape, since interop with Cascade nodes is wire-mesh's stated goal. Framing (not TLS) is this adapter's own concern; a TLS-terminated variant is a separate adapter behind the same Transport contract. */
|
|
4
4
|
export declare function createTcpTransport(): Transport;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_generated_protocol = require("../generated/protocol.cjs");
|
|
3
|
+
const require_domain_tokens = require("./tokens.cjs");
|
|
4
|
+
//#region src/domain/capability-grant.ts
|
|
5
|
+
/**
|
|
6
|
+
* The generic, capability-agnostic capability-grant primitive (wire-mesh#117, spec/management.cddl) -- the unsolicited push counterpart to capability-request.ts's own ask/response primitive. Where capability-request lifts core/room's room.join shape (a pull: the requester asks, the owner mints and returns a grant on the same response) out of that one domain, capability-grant lifts room.invite's own shape (a push: the owner mints unprompted and delivers the grant in the request itself, since there is no approval round-trip to carry it back) the same way. core/room's own room-client.ts is the reference consumer: it builds sendRoomInvite as a thin wrapper over sendCapabilityGrant below, and wires createRoomRouter's dispatch onto createCapabilityGrantHandler for the receiving side.
|
|
7
|
+
*
|
|
8
|
+
* Two responsibilities, split the same way capability-request.ts already splits them: sendCapabilityGrant is the pushing side (a thin wrapper over MeshSession.sendManageRequest), and createCapabilityGrantHandler is the receiving side (validates the pushed token against all four of management.cddl's own capability-grant obligations, then hands the verified grant to the domain). Unlike capability-request's handler, there is no decide()/timeout mechanism here: management.cddl deliberately specifies no protocol-level approval round trip for this primitive (a receiver's manage-response reports validation success or failure only, never a human decision), so onGrant is a plain notification callback, not an event carrying its own responder.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Builds a capability-grant command per management.cddl. The outer `manage-command.verb` is the capability string itself, the identical convention capability-request.ts's own buildCapabilityRequestCommand already establishes -- a receiver's per-capability handler is how it knows which grant this push is even for. `params.verb` is the fixed "capability.grant" marker. No scope or invitee field: manage-request-frame's own top-level scope already carries the former, and the request's own destination already carries the latter -- naming either a second time inside params could only ever disagree with the fact it duplicates.
|
|
12
|
+
*/
|
|
13
|
+
function buildCapabilityGrantCommand(capability, grantedToken) {
|
|
14
|
+
return {
|
|
15
|
+
verb: capability,
|
|
16
|
+
params: {
|
|
17
|
+
verb: "capability.grant",
|
|
18
|
+
"granted-token": grantedToken
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pushes an already-minted grantedToken to whichever peer this request is addressed to -- the caller mints grantedToken itself beforehand (there is no minting step here, unlike capability-request's own accept path, since this primitive carries a token the sender already decided to hand over unprompted). Resolves with the raw manage-response outcome: `{result:"ok"}` on successful validation, an ordinary manage-error otherwise (per management.cddl's own capability-grant obligations, this is a validation-failure code only, never a human "no" -- an application wanting a human-decision gate applies it on the RECEIVING side's own onGrant callback instead, exactly as core/room's own room.invite→room_invite delivery event already does today in agent-comms).
|
|
24
|
+
*
|
|
25
|
+
* scope and targetDevice forward directly to MeshSession.sendManageRequest's own identically-named parameters (the request's own top-level scope obligation 4 checks the token against, and relay routing respectively).
|
|
26
|
+
*/
|
|
27
|
+
async function sendCapabilityGrant(session, capability, grantedToken, scope, targetDevice) {
|
|
28
|
+
return session.sendManageRequest(buildCapabilityGrantCommand(capability, grantedToken), scope, targetDevice);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds a reusable handler for one capability's incoming capability-grants. The returned function checks, in order: the outer verb names this handler's own capability (otherwise `{result:"error", code:"malformed"}`, matching capability-request.ts's identical check); the params payload parses against capability-grant's own CDDL shape (otherwise "malformed"); the embedded token independently passes every ordinary verifyCapabilityToken obligation with `expectedBearer` set to this receiver's OWN identity -- obligations 1 and 3 together, since an unsolicited push must name its actual recipient as bearer and must otherwise be exactly as valid as any other token (a verification failure responds with verifyCapabilityToken's own specific TokenVerdictReason as the error code, e.g. "expired"/"revoked"/"bad_signature", rather than a single generic code, mirroring how capability-request.ts already distinguishes "expired" from "malformed"); the token's own capability claim equals this handler's capability (obligation 2, otherwise "capability_mismatch"); and the token's own scope equals or roots the enclosing request's own top-level scope (obligation 4, via tokens.ts's own scopeNarrows -- otherwise "scope_mismatch"). Only once every obligation passes does it respond `{result:"ok"}` and invoke onGrant -- there is no decide()/timeout mechanism the way createCapabilityRequestHandler has one, since management.cddl specifies no protocol-level approval round trip for this primitive at all.
|
|
32
|
+
*/
|
|
33
|
+
function createCapabilityGrantHandler(options) {
|
|
34
|
+
return async function handleCapabilityGrant(incoming) {
|
|
35
|
+
if (incoming.command.verb !== options.capability) {
|
|
36
|
+
await incoming.respond({
|
|
37
|
+
result: "error",
|
|
38
|
+
code: "malformed"
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const parsed = require_generated_protocol.capabilityGrantSchema.safeParse(incoming.command.params);
|
|
43
|
+
if (!parsed.success) {
|
|
44
|
+
await incoming.respond({
|
|
45
|
+
result: "error",
|
|
46
|
+
code: "malformed"
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const grantedToken = parsed.data["granted-token"];
|
|
51
|
+
const verdict = await require_domain_tokens.verifyCapabilityToken(grantedToken, {
|
|
52
|
+
identity: options.identity,
|
|
53
|
+
clock: options.clock,
|
|
54
|
+
revocation: options.revocation,
|
|
55
|
+
expectedBearer: options.identity.deviceId
|
|
56
|
+
});
|
|
57
|
+
if (!verdict.ok) {
|
|
58
|
+
await incoming.respond({
|
|
59
|
+
result: "error",
|
|
60
|
+
code: verdict.reason
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (verdict.claims.capability !== options.capability) {
|
|
65
|
+
await incoming.respond({
|
|
66
|
+
result: "error",
|
|
67
|
+
code: "capability_mismatch"
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!require_domain_tokens.scopeNarrows(verdict.claims.scope, incoming.scope)) {
|
|
72
|
+
await incoming.respond({
|
|
73
|
+
result: "error",
|
|
74
|
+
code: "scope_mismatch"
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await incoming.respond({ result: "ok" });
|
|
79
|
+
options.onGrant({
|
|
80
|
+
capability: options.capability,
|
|
81
|
+
grantedToken,
|
|
82
|
+
scope: incoming.scope,
|
|
83
|
+
granterDevice: options.granterDevice
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
exports.buildCapabilityGrantCommand = buildCapabilityGrantCommand;
|
|
89
|
+
exports.createCapabilityGrantHandler = createCapabilityGrantHandler;
|
|
90
|
+
exports.sendCapabilityGrant = sendCapabilityGrant;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { F as ManageCommand, o as CapabilityScope, s as CapabilityToken, x as DeviceId } from "../protocol-Dhswk7YD.cjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-BldV8xFj.cjs";
|
|
3
|
+
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
4
|
+
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.cjs";
|
|
5
|
+
import { RevocationCheck } from "./tokens.cjs";
|
|
6
|
+
//#region src/domain/capability-grant.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Builds a capability-grant command per management.cddl. The outer `manage-command.verb` is the capability string itself, the identical convention capability-request.ts's own buildCapabilityRequestCommand already establishes -- a receiver's per-capability handler is how it knows which grant this push is even for. `params.verb` is the fixed "capability.grant" marker. No scope or invitee field: manage-request-frame's own top-level scope already carries the former, and the request's own destination already carries the latter -- naming either a second time inside params could only ever disagree with the fact it duplicates.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildCapabilityGrantCommand(capability: string, grantedToken: CapabilityToken): ManageCommand;
|
|
11
|
+
/**
|
|
12
|
+
* Pushes an already-minted grantedToken to whichever peer this request is addressed to -- the caller mints grantedToken itself beforehand (there is no minting step here, unlike capability-request's own accept path, since this primitive carries a token the sender already decided to hand over unprompted). Resolves with the raw manage-response outcome: `{result:"ok"}` on successful validation, an ordinary manage-error otherwise (per management.cddl's own capability-grant obligations, this is a validation-failure code only, never a human "no" -- an application wanting a human-decision gate applies it on the RECEIVING side's own onGrant callback instead, exactly as core/room's own room.invite→room_invite delivery event already does today in agent-comms).
|
|
13
|
+
*
|
|
14
|
+
* scope and targetDevice forward directly to MeshSession.sendManageRequest's own identically-named parameters (the request's own top-level scope obligation 4 checks the token against, and relay routing respectively).
|
|
15
|
+
*/
|
|
16
|
+
export declare function sendCapabilityGrant(session: Readonly<MeshSession>, capability: string, grantedToken: CapabilityToken, scope: Readonly<CapabilityScope>, targetDevice?: DeviceId): Promise<ManageOutcome>;
|
|
17
|
+
/** One incoming, fully-verified capability-grant, surfaced for the domain to react to (store the token, notify a human, etc.) -- there is no decide() here because management.cddl specifies no protocol-level approval round trip for this primitive; by the time onGrant fires, the wire response has already been sent. */
|
|
18
|
+
export interface CapabilityGrantEvent {
|
|
19
|
+
/** The capability granted -- equals both the outer manage-command.verb and the verified token's own capability claim (obligation 2 already enforced this before onGrant fires). */
|
|
20
|
+
capability: string;
|
|
21
|
+
/** The pushed token, already confirmed to independently pass every ordinary verifyCapabilityToken obligation, name this receiver as its own bearer, and scope-match the enclosing request (obligations 1, 2, 3, and 4 respectively). Ready to use exactly as capability-request.ts's own CapabilityGrantOk["granted-token"] is on the pull side. */
|
|
22
|
+
grantedToken: CapabilityToken;
|
|
23
|
+
/** The scope this grant's own manage-request-frame carried -- e.g. core/room's `{kind:"room", path: roomPath}` -- passed through unchanged so the domain can inspect it (a room handler reads `.path` back out) without this module needing to know its shape. */
|
|
24
|
+
scope: Readonly<CapabilityScope>;
|
|
25
|
+
/** The peer device-id authenticated on the connection this grant arrived on -- the granter, surfaced so the domain can attribute the push (e.g. core/room's own room_invite event names the inviter). Never itself checked against the token's bearer (obligation 1 checks the RECIPIENT's own identity instead -- an unsolicited push names its own recipient by where it was sent, not by who sent it). */
|
|
26
|
+
granterDevice: DeviceId;
|
|
27
|
+
}
|
|
28
|
+
export interface CreateCapabilityGrantHandlerOptions {
|
|
29
|
+
/** The capability this handler accepts pushed grants for. Checked against the incoming command's own outer verb (a mismatch is refused as malformed, mirroring capability-request.ts's identical capability check) and against the verified token's own capability claim (obligation 2); a caller wanting to accept grants for several distinct capabilities over one session constructs one handler per capability, the same way core/room constructs one handler for `room:member` and nothing else. */
|
|
30
|
+
capability: string;
|
|
31
|
+
/** This receiver's own identity -- both the verification primitives every pushed token is checked with, and (via `.deviceId`) the value obligation 1 requires the token's own bearer to equal. There is no separate bearerDevice option the way CreateCapabilityRequestHandlerOptions has one: capability-request's handler mints a NEW token for whichever peer asked, so it needs that peer's device-id as an input; capability-grant's handler verifies an ALREADY-minted token against this side's own identity, which identity.deviceId already provides. */
|
|
32
|
+
identity: IdentityPort;
|
|
33
|
+
clock: Clock;
|
|
34
|
+
revocation: RevocationCheck;
|
|
35
|
+
/** The peer device-id authenticated on this session's own connection -- the granter, surfaced on every CapabilityGrantEvent so the domain can attribute the push. Never used for verification itself (see CapabilityGrantEvent.granterDevice's own doc comment on why obligation 1 checks the recipient's identity instead). */
|
|
36
|
+
granterDevice: DeviceId;
|
|
37
|
+
/** Called once per incoming, fully-verified capability-grant. Fires after the wire response has already been sent (see createCapabilityGrantHandler's own doc comment) -- this is a notification, not a decision point. */
|
|
38
|
+
onGrant: (event: Readonly<CapabilityGrantEvent>) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds a reusable handler for one capability's incoming capability-grants. The returned function checks, in order: the outer verb names this handler's own capability (otherwise `{result:"error", code:"malformed"}`, matching capability-request.ts's identical check); the params payload parses against capability-grant's own CDDL shape (otherwise "malformed"); the embedded token independently passes every ordinary verifyCapabilityToken obligation with `expectedBearer` set to this receiver's OWN identity -- obligations 1 and 3 together, since an unsolicited push must name its actual recipient as bearer and must otherwise be exactly as valid as any other token (a verification failure responds with verifyCapabilityToken's own specific TokenVerdictReason as the error code, e.g. "expired"/"revoked"/"bad_signature", rather than a single generic code, mirroring how capability-request.ts already distinguishes "expired" from "malformed"); the token's own capability claim equals this handler's capability (obligation 2, otherwise "capability_mismatch"); and the token's own scope equals or roots the enclosing request's own top-level scope (obligation 4, via tokens.ts's own scopeNarrows -- otherwise "scope_mismatch"). Only once every obligation passes does it respond `{result:"ok"}` and invoke onGrant -- there is no decide()/timeout mechanism the way createCapabilityRequestHandler has one, since management.cddl specifies no protocol-level approval round trip for this primitive at all.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createCapabilityGrantHandler(options: Readonly<CreateCapabilityGrantHandlerOptions>): (incoming: Readonly<IncomingManageRequest>) => Promise<void>;
|
|
44
|
+
//#endregion
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { F as ManageCommand, o as CapabilityScope, s as CapabilityToken, x as DeviceId } from "../protocol-Dhswk7YD.mjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-B1hBV3YA.mjs";
|
|
3
|
+
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
4
|
+
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.mjs";
|
|
5
|
+
import { RevocationCheck } from "./tokens.mjs";
|
|
6
|
+
//#region src/domain/capability-grant.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Builds a capability-grant command per management.cddl. The outer `manage-command.verb` is the capability string itself, the identical convention capability-request.ts's own buildCapabilityRequestCommand already establishes -- a receiver's per-capability handler is how it knows which grant this push is even for. `params.verb` is the fixed "capability.grant" marker. No scope or invitee field: manage-request-frame's own top-level scope already carries the former, and the request's own destination already carries the latter -- naming either a second time inside params could only ever disagree with the fact it duplicates.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildCapabilityGrantCommand(capability: string, grantedToken: CapabilityToken): ManageCommand;
|
|
11
|
+
/**
|
|
12
|
+
* Pushes an already-minted grantedToken to whichever peer this request is addressed to -- the caller mints grantedToken itself beforehand (there is no minting step here, unlike capability-request's own accept path, since this primitive carries a token the sender already decided to hand over unprompted). Resolves with the raw manage-response outcome: `{result:"ok"}` on successful validation, an ordinary manage-error otherwise (per management.cddl's own capability-grant obligations, this is a validation-failure code only, never a human "no" -- an application wanting a human-decision gate applies it on the RECEIVING side's own onGrant callback instead, exactly as core/room's own room.invite→room_invite delivery event already does today in agent-comms).
|
|
13
|
+
*
|
|
14
|
+
* scope and targetDevice forward directly to MeshSession.sendManageRequest's own identically-named parameters (the request's own top-level scope obligation 4 checks the token against, and relay routing respectively).
|
|
15
|
+
*/
|
|
16
|
+
export declare function sendCapabilityGrant(session: Readonly<MeshSession>, capability: string, grantedToken: CapabilityToken, scope: Readonly<CapabilityScope>, targetDevice?: DeviceId): Promise<ManageOutcome>;
|
|
17
|
+
/** One incoming, fully-verified capability-grant, surfaced for the domain to react to (store the token, notify a human, etc.) -- there is no decide() here because management.cddl specifies no protocol-level approval round trip for this primitive; by the time onGrant fires, the wire response has already been sent. */
|
|
18
|
+
export interface CapabilityGrantEvent {
|
|
19
|
+
/** The capability granted -- equals both the outer manage-command.verb and the verified token's own capability claim (obligation 2 already enforced this before onGrant fires). */
|
|
20
|
+
capability: string;
|
|
21
|
+
/** The pushed token, already confirmed to independently pass every ordinary verifyCapabilityToken obligation, name this receiver as its own bearer, and scope-match the enclosing request (obligations 1, 2, 3, and 4 respectively). Ready to use exactly as capability-request.ts's own CapabilityGrantOk["granted-token"] is on the pull side. */
|
|
22
|
+
grantedToken: CapabilityToken;
|
|
23
|
+
/** The scope this grant's own manage-request-frame carried -- e.g. core/room's `{kind:"room", path: roomPath}` -- passed through unchanged so the domain can inspect it (a room handler reads `.path` back out) without this module needing to know its shape. */
|
|
24
|
+
scope: Readonly<CapabilityScope>;
|
|
25
|
+
/** The peer device-id authenticated on the connection this grant arrived on -- the granter, surfaced so the domain can attribute the push (e.g. core/room's own room_invite event names the inviter). Never itself checked against the token's bearer (obligation 1 checks the RECIPIENT's own identity instead -- an unsolicited push names its own recipient by where it was sent, not by who sent it). */
|
|
26
|
+
granterDevice: DeviceId;
|
|
27
|
+
}
|
|
28
|
+
export interface CreateCapabilityGrantHandlerOptions {
|
|
29
|
+
/** The capability this handler accepts pushed grants for. Checked against the incoming command's own outer verb (a mismatch is refused as malformed, mirroring capability-request.ts's identical capability check) and against the verified token's own capability claim (obligation 2); a caller wanting to accept grants for several distinct capabilities over one session constructs one handler per capability, the same way core/room constructs one handler for `room:member` and nothing else. */
|
|
30
|
+
capability: string;
|
|
31
|
+
/** This receiver's own identity -- both the verification primitives every pushed token is checked with, and (via `.deviceId`) the value obligation 1 requires the token's own bearer to equal. There is no separate bearerDevice option the way CreateCapabilityRequestHandlerOptions has one: capability-request's handler mints a NEW token for whichever peer asked, so it needs that peer's device-id as an input; capability-grant's handler verifies an ALREADY-minted token against this side's own identity, which identity.deviceId already provides. */
|
|
32
|
+
identity: IdentityPort;
|
|
33
|
+
clock: Clock;
|
|
34
|
+
revocation: RevocationCheck;
|
|
35
|
+
/** The peer device-id authenticated on this session's own connection -- the granter, surfaced on every CapabilityGrantEvent so the domain can attribute the push. Never used for verification itself (see CapabilityGrantEvent.granterDevice's own doc comment on why obligation 1 checks the recipient's identity instead). */
|
|
36
|
+
granterDevice: DeviceId;
|
|
37
|
+
/** Called once per incoming, fully-verified capability-grant. Fires after the wire response has already been sent (see createCapabilityGrantHandler's own doc comment) -- this is a notification, not a decision point. */
|
|
38
|
+
onGrant: (event: Readonly<CapabilityGrantEvent>) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds a reusable handler for one capability's incoming capability-grants. The returned function checks, in order: the outer verb names this handler's own capability (otherwise `{result:"error", code:"malformed"}`, matching capability-request.ts's identical check); the params payload parses against capability-grant's own CDDL shape (otherwise "malformed"); the embedded token independently passes every ordinary verifyCapabilityToken obligation with `expectedBearer` set to this receiver's OWN identity -- obligations 1 and 3 together, since an unsolicited push must name its actual recipient as bearer and must otherwise be exactly as valid as any other token (a verification failure responds with verifyCapabilityToken's own specific TokenVerdictReason as the error code, e.g. "expired"/"revoked"/"bad_signature", rather than a single generic code, mirroring how capability-request.ts already distinguishes "expired" from "malformed"); the token's own capability claim equals this handler's capability (obligation 2, otherwise "capability_mismatch"); and the token's own scope equals or roots the enclosing request's own top-level scope (obligation 4, via tokens.ts's own scopeNarrows -- otherwise "scope_mismatch"). Only once every obligation passes does it respond `{result:"ok"}` and invoke onGrant -- there is no decide()/timeout mechanism the way createCapabilityRequestHandler has one, since management.cddl specifies no protocol-level approval round trip for this primitive at all.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createCapabilityGrantHandler(options: Readonly<CreateCapabilityGrantHandlerOptions>): (incoming: Readonly<IncomingManageRequest>) => Promise<void>;
|
|
44
|
+
//#endregion
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { capabilityGrantSchema } from "../generated/protocol.mjs";
|
|
2
|
+
import { scopeNarrows, verifyCapabilityToken } from "./tokens.mjs";
|
|
3
|
+
//#region src/domain/capability-grant.ts
|
|
4
|
+
/**
|
|
5
|
+
* The generic, capability-agnostic capability-grant primitive (wire-mesh#117, spec/management.cddl) -- the unsolicited push counterpart to capability-request.ts's own ask/response primitive. Where capability-request lifts core/room's room.join shape (a pull: the requester asks, the owner mints and returns a grant on the same response) out of that one domain, capability-grant lifts room.invite's own shape (a push: the owner mints unprompted and delivers the grant in the request itself, since there is no approval round-trip to carry it back) the same way. core/room's own room-client.ts is the reference consumer: it builds sendRoomInvite as a thin wrapper over sendCapabilityGrant below, and wires createRoomRouter's dispatch onto createCapabilityGrantHandler for the receiving side.
|
|
6
|
+
*
|
|
7
|
+
* Two responsibilities, split the same way capability-request.ts already splits them: sendCapabilityGrant is the pushing side (a thin wrapper over MeshSession.sendManageRequest), and createCapabilityGrantHandler is the receiving side (validates the pushed token against all four of management.cddl's own capability-grant obligations, then hands the verified grant to the domain). Unlike capability-request's handler, there is no decide()/timeout mechanism here: management.cddl deliberately specifies no protocol-level approval round trip for this primitive (a receiver's manage-response reports validation success or failure only, never a human decision), so onGrant is a plain notification callback, not an event carrying its own responder.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Builds a capability-grant command per management.cddl. The outer `manage-command.verb` is the capability string itself, the identical convention capability-request.ts's own buildCapabilityRequestCommand already establishes -- a receiver's per-capability handler is how it knows which grant this push is even for. `params.verb` is the fixed "capability.grant" marker. No scope or invitee field: manage-request-frame's own top-level scope already carries the former, and the request's own destination already carries the latter -- naming either a second time inside params could only ever disagree with the fact it duplicates.
|
|
11
|
+
*/
|
|
12
|
+
function buildCapabilityGrantCommand(capability, grantedToken) {
|
|
13
|
+
return {
|
|
14
|
+
verb: capability,
|
|
15
|
+
params: {
|
|
16
|
+
verb: "capability.grant",
|
|
17
|
+
"granted-token": grantedToken
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pushes an already-minted grantedToken to whichever peer this request is addressed to -- the caller mints grantedToken itself beforehand (there is no minting step here, unlike capability-request's own accept path, since this primitive carries a token the sender already decided to hand over unprompted). Resolves with the raw manage-response outcome: `{result:"ok"}` on successful validation, an ordinary manage-error otherwise (per management.cddl's own capability-grant obligations, this is a validation-failure code only, never a human "no" -- an application wanting a human-decision gate applies it on the RECEIVING side's own onGrant callback instead, exactly as core/room's own room.invite→room_invite delivery event already does today in agent-comms).
|
|
23
|
+
*
|
|
24
|
+
* scope and targetDevice forward directly to MeshSession.sendManageRequest's own identically-named parameters (the request's own top-level scope obligation 4 checks the token against, and relay routing respectively).
|
|
25
|
+
*/
|
|
26
|
+
async function sendCapabilityGrant(session, capability, grantedToken, scope, targetDevice) {
|
|
27
|
+
return session.sendManageRequest(buildCapabilityGrantCommand(capability, grantedToken), scope, targetDevice);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds a reusable handler for one capability's incoming capability-grants. The returned function checks, in order: the outer verb names this handler's own capability (otherwise `{result:"error", code:"malformed"}`, matching capability-request.ts's identical check); the params payload parses against capability-grant's own CDDL shape (otherwise "malformed"); the embedded token independently passes every ordinary verifyCapabilityToken obligation with `expectedBearer` set to this receiver's OWN identity -- obligations 1 and 3 together, since an unsolicited push must name its actual recipient as bearer and must otherwise be exactly as valid as any other token (a verification failure responds with verifyCapabilityToken's own specific TokenVerdictReason as the error code, e.g. "expired"/"revoked"/"bad_signature", rather than a single generic code, mirroring how capability-request.ts already distinguishes "expired" from "malformed"); the token's own capability claim equals this handler's capability (obligation 2, otherwise "capability_mismatch"); and the token's own scope equals or roots the enclosing request's own top-level scope (obligation 4, via tokens.ts's own scopeNarrows -- otherwise "scope_mismatch"). Only once every obligation passes does it respond `{result:"ok"}` and invoke onGrant -- there is no decide()/timeout mechanism the way createCapabilityRequestHandler has one, since management.cddl specifies no protocol-level approval round trip for this primitive at all.
|
|
31
|
+
*/
|
|
32
|
+
function createCapabilityGrantHandler(options) {
|
|
33
|
+
return async function handleCapabilityGrant(incoming) {
|
|
34
|
+
if (incoming.command.verb !== options.capability) {
|
|
35
|
+
await incoming.respond({
|
|
36
|
+
result: "error",
|
|
37
|
+
code: "malformed"
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const parsed = capabilityGrantSchema.safeParse(incoming.command.params);
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
await incoming.respond({
|
|
44
|
+
result: "error",
|
|
45
|
+
code: "malformed"
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const grantedToken = parsed.data["granted-token"];
|
|
50
|
+
const verdict = await verifyCapabilityToken(grantedToken, {
|
|
51
|
+
identity: options.identity,
|
|
52
|
+
clock: options.clock,
|
|
53
|
+
revocation: options.revocation,
|
|
54
|
+
expectedBearer: options.identity.deviceId
|
|
55
|
+
});
|
|
56
|
+
if (!verdict.ok) {
|
|
57
|
+
await incoming.respond({
|
|
58
|
+
result: "error",
|
|
59
|
+
code: verdict.reason
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (verdict.claims.capability !== options.capability) {
|
|
64
|
+
await incoming.respond({
|
|
65
|
+
result: "error",
|
|
66
|
+
code: "capability_mismatch"
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!scopeNarrows(verdict.claims.scope, incoming.scope)) {
|
|
71
|
+
await incoming.respond({
|
|
72
|
+
result: "error",
|
|
73
|
+
code: "scope_mismatch"
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
await incoming.respond({ result: "ok" });
|
|
78
|
+
options.onGrant({
|
|
79
|
+
capability: options.capability,
|
|
80
|
+
grantedToken,
|
|
81
|
+
scope: incoming.scope,
|
|
82
|
+
granterDevice: options.granterDevice
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { buildCapabilityGrantCommand, createCapabilityGrantHandler, sendCapabilityGrant };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { F as ManageCommand, i as CapabilityGrantOk, o as CapabilityScope, x as DeviceId } from "../protocol-Dhswk7YD.cjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-BldV8xFj.cjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
4
4
|
import { IncomingManageRequest, MeshSession } from "./mesh-session.cjs";
|
|
5
5
|
//#region src/domain/capability-request.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { F as ManageCommand, i as CapabilityGrantOk, o as CapabilityScope, x as DeviceId } from "../protocol-Dhswk7YD.mjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-B1hBV3YA.mjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
4
4
|
import { IncomingManageRequest, MeshSession } from "./mesh-session.mjs";
|
|
5
5
|
//#region src/domain/capability-request.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { x as DeviceId } from "../protocol-Dhswk7YD.cjs";
|
|
2
2
|
//#region src/domain/device-id.d.ts
|
|
3
3
|
/** Lowercase, byte-exact hex for an arbitrary-length byte string -- the same encoding convention deviceIdToHex uses for the fixed-length device-id case, extracted so any other byte string needing a stable, displayable, map-keyable text form (e.g. a token-id, which tokens.cddl defines as an arbitrary-length bstr rather than a 32-byte device-id) can use the identical convention without going through a device-id-shaped function. */
|
|
4
4
|
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { x as DeviceId } from "../protocol-Dhswk7YD.mjs";
|
|
2
2
|
//#region src/domain/device-id.d.ts
|
|
3
3
|
/** Lowercase, byte-exact hex for an arbitrary-length byte string -- the same encoding convention deviceIdToHex uses for the fixed-length device-id case, extracted so any other byte string needing a stable, displayable, map-keyable text form (e.g. a token-id, which tokens.cddl defines as an arbitrary-length bstr rather than a 32-byte device-id) can use the identical convention without going through a device-id-shaped function. */
|
|
4
4
|
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { M as HandshakeFrame, et as ProtocolVersion, w as DomainId } from "../protocol-Dhswk7YD.cjs";
|
|
2
2
|
//#region src/domain/handshake.d.ts
|
|
3
3
|
/** The highest protocol version this build of core understands. */
|
|
4
4
|
export declare const SUPPORTED_PROTOCOL_VERSION: ProtocolVersion;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { M as HandshakeFrame, et as ProtocolVersion, w as DomainId } from "../protocol-Dhswk7YD.mjs";
|
|
2
2
|
//#region src/domain/handshake.d.ts
|
|
3
3
|
/** The highest protocol version this build of core understands. */
|
|
4
4
|
export declare const SUPPORTED_PROTOCOL_VERSION: ProtocolVersion;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { D as Frame, F as ManageCommand, K as PeerAdvert, L as ManageError, R as ManageOk, dt as RevocationEntry, et as ProtocolVersion, o as CapabilityScope, s as CapabilityToken, x as DeviceId } from "../protocol-Dhswk7YD.cjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-BldV8xFj.cjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
4
|
-
import { r as Transport, t as Connection } from "../transport-
|
|
4
|
+
import { r as Transport, t as Connection } from "../transport-hEUxrdhT.cjs";
|
|
5
5
|
//#region src/domain/mesh-session.d.ts
|
|
6
6
|
/** How long to wait for the node's handshake before calling it unanswered. A relay-only node never sends one; that is a state to display, not an error. */
|
|
7
7
|
export declare const HANDSHAKE_TIMEOUT_MS = 3000;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { D as Frame, F as ManageCommand, K as PeerAdvert, L as ManageError, R as ManageOk, dt as RevocationEntry, et as ProtocolVersion, o as CapabilityScope, s as CapabilityToken, x as DeviceId } from "../protocol-Dhswk7YD.mjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-B1hBV3YA.mjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
4
|
-
import { r as Transport, t as Connection } from "../transport-
|
|
4
|
+
import { r as Transport, t as Connection } from "../transport-BNNdUBq-.mjs";
|
|
5
5
|
//#region src/domain/mesh-session.d.ts
|
|
6
6
|
/** How long to wait for the node's handshake before calling it unanswered. A relay-only node never sends one; that is a state to display, not an error. */
|
|
7
7
|
export declare const HANDSHAKE_TIMEOUT_MS = 3000;
|
|
@@ -8,6 +8,7 @@ function deviceKey(device) {
|
|
|
8
8
|
return key;
|
|
9
9
|
}
|
|
10
10
|
function createRelayHub() {
|
|
11
|
+
const connections = /* @__PURE__ */ new Set();
|
|
11
12
|
const devices = /* @__PURE__ */ new Map();
|
|
12
13
|
const connectionDevice = /* @__PURE__ */ new Map();
|
|
13
14
|
const pairings = /* @__PURE__ */ new Map();
|
|
@@ -26,6 +27,7 @@ function createRelayHub() {
|
|
|
26
27
|
mostRecentPairing.set(b, a);
|
|
27
28
|
}
|
|
28
29
|
function forgetConnection(connection) {
|
|
30
|
+
connections.delete(connection);
|
|
29
31
|
for (const [key, registration] of devices) if (registration.connection === connection) devices.delete(key);
|
|
30
32
|
const ownDevice = connectionDevice.get(connection);
|
|
31
33
|
connectionDevice.delete(connection);
|
|
@@ -44,12 +46,26 @@ function createRelayHub() {
|
|
|
44
46
|
if (frame.type === "gossip") {
|
|
45
47
|
for (const advert of frame.peers) devices.set(deviceKey(advert.device), {
|
|
46
48
|
connection,
|
|
47
|
-
|
|
49
|
+
advert
|
|
48
50
|
});
|
|
49
51
|
for (const registration of devices.values()) if (registration.connection === connection) {
|
|
50
|
-
connectionDevice.set(connection, registration.device);
|
|
52
|
+
connectionDevice.set(connection, registration.advert.device);
|
|
51
53
|
break;
|
|
52
54
|
}
|
|
55
|
+
for (const other of connections) {
|
|
56
|
+
if (other === connection) continue;
|
|
57
|
+
try {
|
|
58
|
+
await other.send(frame);
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
const catchUp = [];
|
|
62
|
+
for (const registration of devices.values()) if (registration.connection !== connection) catchUp.push(registration.advert);
|
|
63
|
+
if (catchUp.length > 0) try {
|
|
64
|
+
await connection.send({
|
|
65
|
+
type: "gossip",
|
|
66
|
+
peers: catchUp
|
|
67
|
+
});
|
|
68
|
+
} catch {}
|
|
53
69
|
return;
|
|
54
70
|
}
|
|
55
71
|
if (frame.type === "relay-connect") {
|
|
@@ -57,7 +73,7 @@ function createRelayHub() {
|
|
|
57
73
|
if (!registration || registration.connection === connection) return;
|
|
58
74
|
const initiatorDevice = connectionDevice.get(connection);
|
|
59
75
|
if (initiatorDevice === void 0) return;
|
|
60
|
-
addPairing(connection, initiatorDevice, registration.connection, registration.device);
|
|
76
|
+
addPairing(connection, initiatorDevice, registration.connection, registration.advert.device);
|
|
61
77
|
await registration.connection.send({
|
|
62
78
|
type: "relay-inbound",
|
|
63
79
|
"source-device": initiatorDevice
|
|
@@ -81,6 +97,7 @@ function createRelayHub() {
|
|
|
81
97
|
}
|
|
82
98
|
return {
|
|
83
99
|
async handleConnection(connection) {
|
|
100
|
+
connections.add(connection);
|
|
84
101
|
try {
|
|
85
102
|
for await (const frame of connection.receive()) await handleFrame(connection, frame);
|
|
86
103
|
} catch {} finally {
|
|
@@ -88,6 +105,7 @@ function createRelayHub() {
|
|
|
88
105
|
}
|
|
89
106
|
},
|
|
90
107
|
stop() {
|
|
108
|
+
connections.clear();
|
|
91
109
|
devices.clear();
|
|
92
110
|
connectionDevice.clear();
|
|
93
111
|
pairings.clear();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as Connection } from "../transport-
|
|
1
|
+
import { t as Connection } from "../transport-hEUxrdhT.cjs";
|
|
2
2
|
//#region src/domain/relay-hub.d.ts
|
|
3
3
|
export interface RelayHub {
|
|
4
|
-
/** Drives one accepted connection until it closes: registers gossip-advertised devices, answers relay-connect by pairing and notifying the target, and forwards relay-data within established pairings. Resolves when the connection's frame stream ends. */
|
|
4
|
+
/** Drives one accepted connection until it closes: registers gossip-advertised devices, re-broadcasts each gossip frame to every other connected client and replies to the gossiping connection with a catch-up frame of every other already-known device, answers relay-connect by pairing and notifying the target, and forwards relay-data within established pairings. Resolves when the connection's frame stream ends. */
|
|
5
5
|
handleConnection: (connection: Readonly<Connection>) => Promise<void>;
|
|
6
6
|
/** Drops all registry and pairing state -- used by tests and by transport teardown. */
|
|
7
7
|
stop: () => void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as Connection } from "../transport-
|
|
1
|
+
import { t as Connection } from "../transport-BNNdUBq-.mjs";
|
|
2
2
|
//#region src/domain/relay-hub.d.ts
|
|
3
3
|
export interface RelayHub {
|
|
4
|
-
/** Drives one accepted connection until it closes: registers gossip-advertised devices, answers relay-connect by pairing and notifying the target, and forwards relay-data within established pairings. Resolves when the connection's frame stream ends. */
|
|
4
|
+
/** Drives one accepted connection until it closes: registers gossip-advertised devices, re-broadcasts each gossip frame to every other connected client and replies to the gossiping connection with a catch-up frame of every other already-known device, answers relay-connect by pairing and notifying the target, and forwards relay-data within established pairings. Resolves when the connection's frame stream ends. */
|
|
5
5
|
handleConnection: (connection: Readonly<Connection>) => Promise<void>;
|
|
6
6
|
/** Drops all registry and pairing state -- used by tests and by transport teardown. */
|
|
7
7
|
stop: () => void;
|
|
@@ -7,6 +7,7 @@ function deviceKey(device) {
|
|
|
7
7
|
return key;
|
|
8
8
|
}
|
|
9
9
|
function createRelayHub() {
|
|
10
|
+
const connections = /* @__PURE__ */ new Set();
|
|
10
11
|
const devices = /* @__PURE__ */ new Map();
|
|
11
12
|
const connectionDevice = /* @__PURE__ */ new Map();
|
|
12
13
|
const pairings = /* @__PURE__ */ new Map();
|
|
@@ -25,6 +26,7 @@ function createRelayHub() {
|
|
|
25
26
|
mostRecentPairing.set(b, a);
|
|
26
27
|
}
|
|
27
28
|
function forgetConnection(connection) {
|
|
29
|
+
connections.delete(connection);
|
|
28
30
|
for (const [key, registration] of devices) if (registration.connection === connection) devices.delete(key);
|
|
29
31
|
const ownDevice = connectionDevice.get(connection);
|
|
30
32
|
connectionDevice.delete(connection);
|
|
@@ -43,12 +45,26 @@ function createRelayHub() {
|
|
|
43
45
|
if (frame.type === "gossip") {
|
|
44
46
|
for (const advert of frame.peers) devices.set(deviceKey(advert.device), {
|
|
45
47
|
connection,
|
|
46
|
-
|
|
48
|
+
advert
|
|
47
49
|
});
|
|
48
50
|
for (const registration of devices.values()) if (registration.connection === connection) {
|
|
49
|
-
connectionDevice.set(connection, registration.device);
|
|
51
|
+
connectionDevice.set(connection, registration.advert.device);
|
|
50
52
|
break;
|
|
51
53
|
}
|
|
54
|
+
for (const other of connections) {
|
|
55
|
+
if (other === connection) continue;
|
|
56
|
+
try {
|
|
57
|
+
await other.send(frame);
|
|
58
|
+
} catch {}
|
|
59
|
+
}
|
|
60
|
+
const catchUp = [];
|
|
61
|
+
for (const registration of devices.values()) if (registration.connection !== connection) catchUp.push(registration.advert);
|
|
62
|
+
if (catchUp.length > 0) try {
|
|
63
|
+
await connection.send({
|
|
64
|
+
type: "gossip",
|
|
65
|
+
peers: catchUp
|
|
66
|
+
});
|
|
67
|
+
} catch {}
|
|
52
68
|
return;
|
|
53
69
|
}
|
|
54
70
|
if (frame.type === "relay-connect") {
|
|
@@ -56,7 +72,7 @@ function createRelayHub() {
|
|
|
56
72
|
if (!registration || registration.connection === connection) return;
|
|
57
73
|
const initiatorDevice = connectionDevice.get(connection);
|
|
58
74
|
if (initiatorDevice === void 0) return;
|
|
59
|
-
addPairing(connection, initiatorDevice, registration.connection, registration.device);
|
|
75
|
+
addPairing(connection, initiatorDevice, registration.connection, registration.advert.device);
|
|
60
76
|
await registration.connection.send({
|
|
61
77
|
type: "relay-inbound",
|
|
62
78
|
"source-device": initiatorDevice
|
|
@@ -80,6 +96,7 @@ function createRelayHub() {
|
|
|
80
96
|
}
|
|
81
97
|
return {
|
|
82
98
|
async handleConnection(connection) {
|
|
99
|
+
connections.add(connection);
|
|
83
100
|
try {
|
|
84
101
|
for await (const frame of connection.receive()) await handleFrame(connection, frame);
|
|
85
102
|
} catch {} finally {
|
|
@@ -87,6 +104,7 @@ function createRelayHub() {
|
|
|
87
104
|
}
|
|
88
105
|
},
|
|
89
106
|
stop() {
|
|
107
|
+
connections.clear();
|
|
90
108
|
devices.clear();
|
|
91
109
|
connectionDevice.clear();
|
|
92
110
|
pairings.clear();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dt as RevocationEntry } from "../protocol-Dhswk7YD.cjs";
|
|
2
2
|
import { RevocationCheck, RevocationEntryVerdict, VerifyRevocationEntryOptions } from "./tokens.cjs";
|
|
3
3
|
//#region src/domain/revocation-view.d.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dt as RevocationEntry } from "../protocol-Dhswk7YD.mjs";
|
|
2
2
|
import { RevocationCheck, RevocationEntryVerdict, VerifyRevocationEntryOptions } from "./tokens.mjs";
|
|
3
3
|
//#region src/domain/revocation-view.d.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { kt as TokenClaims, s as CapabilityToken, x as DeviceId } from "../protocol-Dhswk7YD.cjs";
|
|
2
2
|
import { TokenVerdictReason, VerifyCapabilityTokenOptions } from "./tokens.cjs";
|
|
3
3
|
//#region src/domain/room-token-verification.d.ts
|
|
4
4
|
/** The one capability every core/room verb (room.send/read/leave/members) is gated by, per spec/room.cddl -- room.join/room.invite are deliberately ungated instead and need no token check at all. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { kt as TokenClaims, s as CapabilityToken, x as DeviceId } from "../protocol-Dhswk7YD.mjs";
|
|
2
2
|
import { TokenVerdictReason, VerifyCapabilityTokenOptions } from "./tokens.mjs";
|
|
3
3
|
//#region src/domain/room-token-verification.d.ts
|
|
4
4
|
/** The one capability every core/room verb (room.send/read/leave/members) is gated by, per spec/room.cddl -- room.join/room.invite are deliberately ungated instead and need no token check at all. */
|