wire-mesh-core 2.0.2 → 3.0.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/README.md +1 -0
- 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/threshold-identity.d.cts +2 -2
- package/dist/adapters/threshold-identity.d.mts +2 -2
- package/dist/adapters/threshold-wasm.d.cts +1 -1
- package/dist/adapters/threshold-wasm.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/advert-extension-policy.cjs +20 -0
- package/dist/domain/advert-extension-policy.d.cts +21 -0
- package/dist/domain/advert-extension-policy.d.mts +21 -0
- package/dist/domain/advert-extension-policy.mjs +19 -0
- package/dist/domain/bulk.d.cts +2 -2
- package/dist/domain/bulk.d.mts +2 -2
- package/dist/domain/capability-grant.d.cts +3 -3
- package/dist/domain/capability-grant.d.mts +3 -3
- package/dist/domain/capability-request.d.cts +2 -2
- package/dist/domain/capability-request.d.mts +2 -2
- package/dist/domain/data-sync.d.cts +2 -2
- package/dist/domain/data-sync.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/direct-manage-request.d.cts +2 -2
- package/dist/domain/direct-manage-request.d.mts +2 -2
- package/dist/domain/gossip-expansion.d.cts +1 -1
- package/dist/domain/gossip-expansion.d.mts +1 -1
- package/dist/domain/gossip-extensions.cjs +2 -1
- package/dist/domain/gossip-extensions.d.cts +2 -0
- package/dist/domain/gossip-extensions.d.mts +2 -0
- package/dist/domain/gossip-extensions.mjs +2 -2
- package/dist/domain/grant-candidates.d.cts +1 -1
- package/dist/domain/grant-candidates.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.cjs +327 -25
- package/dist/domain/mesh-session.d.cts +4 -4
- package/dist/domain/mesh-session.d.mts +4 -4
- package/dist/domain/mesh-session.mjs +328 -26
- package/dist/domain/notice-board.d.cts +3 -3
- package/dist/domain/notice-board.d.mts +3 -3
- package/dist/domain/path-trace.d.cts +1 -1
- package/dist/domain/path-trace.d.mts +1 -1
- package/dist/domain/peer-advert.d.cts +2 -2
- package/dist/domain/peer-advert.d.mts +2 -2
- package/dist/domain/relay-hub.cjs +11 -0
- package/dist/domain/relay-hub.d.cts +15 -2
- package/dist/domain/relay-hub.d.mts +15 -2
- package/dist/domain/relay-hub.mjs +11 -0
- package/dist/domain/relay-use-gate.d.cts +2 -2
- package/dist/domain/relay-use-gate.d.mts +2 -2
- package/dist/domain/revocation-view.d.cts +2 -2
- package/dist/domain/revocation-view.d.mts +2 -2
- package/dist/domain/room-rekey.d.cts +3 -3
- package/dist/domain/room-rekey.d.mts +3 -3
- package/dist/domain/room-token-verification.d.cts +1 -1
- package/dist/domain/room-token-verification.d.mts +1 -1
- package/dist/domain/room.d.cts +4 -4
- package/dist/domain/room.d.mts +4 -4
- package/dist/domain/shard-manifest.d.cts +1 -1
- package/dist/domain/shard-manifest.d.mts +1 -1
- package/dist/domain/tokens.d.cts +1 -1
- package/dist/domain/tokens.d.mts +1 -1
- package/dist/domain/topology-snapshot.d.cts +1 -1
- package/dist/domain/topology-snapshot.d.mts +1 -1
- package/dist/domain/topology.cjs +1 -1
- package/dist/domain/topology.d.cts +1 -1
- package/dist/domain/topology.d.mts +1 -1
- package/dist/domain/topology.mjs +1 -1
- package/dist/domain/webrtc-signaling.d.cts +2 -2
- package/dist/domain/webrtc-signaling.d.mts +2 -2
- package/dist/generated/protocol.cjs +18 -1
- package/dist/generated/protocol.d.cts +2 -2
- package/dist/generated/protocol.d.mts +2 -2
- package/dist/generated/protocol.mjs +17 -2
- package/dist/{gossip-extensions-BNiI44sU.mjs → gossip-extensions-B6WJjkAr.mjs} +1 -1
- package/dist/{gossip-extensions-DRVTHKnR.cjs → gossip-extensions-BBYgMZ2N.cjs} +6 -0
- package/dist/{identity-DImeXZcq.d.cts → identity-DVCNuFLG.d.cts} +1 -1
- package/dist/{identity-Dc4xmwp3.d.mts → identity-DcaYgjXT.d.mts} +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-Bi2fzBCc.d.cts → protocol-DCDae3z4.d.cts} +47 -1
- package/dist/{protocol-Bi2fzBCc.d.mts → protocol-DCDae3z4.d.mts} +47 -1
- package/dist/{room-token-verification-CnHNaA8J.d.mts → room-token-verification-CkGnFuQc.d.mts} +2 -2
- package/dist/{room-token-verification-CDXkm_Tg.d.cts → room-token-verification-PSv4eQ1x.d.cts} +2 -2
- package/dist/{tokens-0r9ipK1r.d.mts → tokens-CBiSYuzp.d.mts} +2 -2
- package/dist/{tokens-BDVxEvl2.d.cts → tokens-DqFu7iMX.d.cts} +2 -2
- package/dist/{transport-Cq_odHy3.d.cts → transport-CfW7fsGK.d.cts} +1 -1
- package/dist/{transport-GcJ4LYVE.d.mts → transport-iLBxIkRS.d.mts} +1 -1
- package/package.json +5 -1
- package/wasm-dist/wire_mesh_threshold_wasm_bg.wasm +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { a as OWN_VERSION, i as CORE_VERSION_GOSSIP_KEY, n as TOPOLOGY_PEERS_GOSSIP_KEY, o as isVersionGetCommand, r as validateGossipExtensions, s as versionGetOutcome } from "../gossip-extensions-B6WJjkAr.mjs";
|
|
2
|
+
import { t as bytesEqual } from "../token-scope-Z4bmci4M.mjs";
|
|
3
|
+
import { signPeerAdvert, verifyPeerAdvert } from "./peer-advert.mjs";
|
|
1
4
|
import { deviceIdToHex } from "./device-id.mjs";
|
|
2
5
|
import { negotiate } from "./handshake.mjs";
|
|
3
6
|
import { createAsyncQueue } from "./async-queue.mjs";
|
|
4
7
|
import { createPingRoundTrips } from "./ping-round-trips.mjs";
|
|
5
8
|
import { createTopologySnapshotTracker } from "./topology-snapshot.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import { signPeerAdvert, verifyPeerAdvert } from "./peer-advert.mjs";
|
|
8
|
-
import { tryDecodeFrame, wrapRelayData } from "../adapters/frame-codec.mjs";
|
|
9
|
+
import { messageFromFrame, tryDecodeFrame, wrapRelayData } from "../adapters/frame-codec.mjs";
|
|
9
10
|
//#region src/domain/relay-pairing.ts
|
|
10
11
|
function createRelayPairings() {
|
|
11
12
|
const established = /* @__PURE__ */ new Map();
|
|
@@ -21,6 +22,260 @@ function createRelayPairings() {
|
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
//#endregion
|
|
25
|
+
//#region src/domain/secure-channel.ts
|
|
26
|
+
/**
|
|
27
|
+
* The end-to-end channel two devices open between themselves inside a relay pairing (spec/secure-channel.cddl). A relay only forwards the bytes of a `relay-data` frame, and without this it could read them, alter them, and say who sent them. Here the two devices authenticate each other's identity keys over a fresh ECDH exchange, derive one AES-256-GCM key per direction, and seal every frame they exchange so the relay sees ciphertext and every receiver learns the sender's identity from the handshake and nothing a relay asserts.
|
|
28
|
+
*
|
|
29
|
+
* Pure crypto and framing, with no connection or session in it: beginHandshake and verifyHello and establishChannel are the three steps of the handshake, and a SecureChannel then seals and opens frames. mesh-session.ts owns when to do each, and where the results travel.
|
|
30
|
+
*/
|
|
31
|
+
const NONCE_BYTE_LENGTH = 16;
|
|
32
|
+
const AES_KEY_BIT_LENGTH = 256;
|
|
33
|
+
const ECDH_SECRET_BIT_LENGTH = 256;
|
|
34
|
+
const GCM_IV_BYTE_LENGTH = 12;
|
|
35
|
+
/** The counter fills the last eight bytes of the IV; the four before it stay zero. */
|
|
36
|
+
const IV_COUNTER_OFFSET = 4;
|
|
37
|
+
const COUNTER_BYTE_LENGTH = 8;
|
|
38
|
+
const encoder = new TextEncoder();
|
|
39
|
+
const HELLO_CONTEXT = encoder.encode("wire-mesh/secure-hello/v1\0");
|
|
40
|
+
const CHANNEL_CONTEXT = encoder.encode("wire-mesh/secure-channel/v1\0");
|
|
41
|
+
const DATA_CONTEXT = encoder.encode("wire-mesh/secure-data/v1\0");
|
|
42
|
+
const LABEL_LOW_TO_HIGH = encoder.encode("lo-to-hi");
|
|
43
|
+
const LABEL_HIGH_TO_LOW = encoder.encode("hi-to-lo");
|
|
44
|
+
const ECDH_PARAMS = {
|
|
45
|
+
name: "ECDH",
|
|
46
|
+
namedCurve: "P-256"
|
|
47
|
+
};
|
|
48
|
+
function concat(...parts) {
|
|
49
|
+
const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
50
|
+
let offset = 0;
|
|
51
|
+
for (const part of parts) {
|
|
52
|
+
out.set(part, offset);
|
|
53
|
+
offset += part.length;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/** Bytewise order of two device-ids: negative when a sorts first. */
|
|
58
|
+
function compareBytes(a, b) {
|
|
59
|
+
for (let i = 0; i < Math.min(a.length, b.length); i += 1) {
|
|
60
|
+
const difference = (a[i] ?? 0) - (b[i] ?? 0);
|
|
61
|
+
if (difference !== 0) return difference;
|
|
62
|
+
}
|
|
63
|
+
return a.length - b.length;
|
|
64
|
+
}
|
|
65
|
+
function counterBytes(counter) {
|
|
66
|
+
const bytes = new Uint8Array(COUNTER_BYTE_LENGTH);
|
|
67
|
+
new DataView(bytes.buffer).setBigUint64(0, BigInt(counter), false);
|
|
68
|
+
return bytes;
|
|
69
|
+
}
|
|
70
|
+
/** Exactly what a hello's signature covers: see spec/secure-channel.cddl. */
|
|
71
|
+
function helloSigningInput(ephemeralKey, nonce, sender, recipient) {
|
|
72
|
+
return concat(HELLO_CONTEXT, ephemeralKey, nonce, sender, recipient);
|
|
73
|
+
}
|
|
74
|
+
/** Generates this side's ephemeral key pair and nonce and signs a hello for peer. */
|
|
75
|
+
async function beginHandshake(identity, peer) {
|
|
76
|
+
const pair = await crypto.subtle.generateKey(ECDH_PARAMS, false, ["deriveBits"]);
|
|
77
|
+
const ephemeralKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
|
|
78
|
+
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_BYTE_LENGTH));
|
|
79
|
+
const signature = await identity.sign(helloSigningInput(ephemeralKey, nonce, identity.deviceId, peer));
|
|
80
|
+
return {
|
|
81
|
+
peer,
|
|
82
|
+
privateKey: pair.privateKey,
|
|
83
|
+
hello: {
|
|
84
|
+
type: "secure-hello",
|
|
85
|
+
"ephemeral-key": ephemeralKey,
|
|
86
|
+
nonce,
|
|
87
|
+
"identity-key": identity.identityKey,
|
|
88
|
+
"to-device": peer,
|
|
89
|
+
signature
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Checks a received hello: its identity key must hash to a device-id (which is then the peer), it must be addressed to this device, and its signature must verify under that key over the input spec/secure-channel.cddl defines. Never throws: a hostile hello gets a verdict.
|
|
95
|
+
*/
|
|
96
|
+
async function verifyHello(identity, hello) {
|
|
97
|
+
const key = hello["identity-key"];
|
|
98
|
+
try {
|
|
99
|
+
const sender = await identity.deriveDeviceId(key["public-key"]);
|
|
100
|
+
if (!bytesEqual(hello["to-device"], identity.deviceId)) return {
|
|
101
|
+
ok: false,
|
|
102
|
+
reason: "wrong_recipient"
|
|
103
|
+
};
|
|
104
|
+
return await identity.verify(key, helloSigningInput(hello["ephemeral-key"], hello.nonce, sender, identity.deviceId), hello.signature) ? {
|
|
105
|
+
ok: true,
|
|
106
|
+
peer: {
|
|
107
|
+
deviceId: sender,
|
|
108
|
+
identityKey: key
|
|
109
|
+
}
|
|
110
|
+
} : {
|
|
111
|
+
ok: false,
|
|
112
|
+
reason: "bad_signature"
|
|
113
|
+
};
|
|
114
|
+
} catch {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
reason: "identity_mismatch"
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function ivFor(counter) {
|
|
122
|
+
const iv = new Uint8Array(GCM_IV_BYTE_LENGTH);
|
|
123
|
+
iv.set(counterBytes(counter), IV_COUNTER_OFFSET);
|
|
124
|
+
return iv;
|
|
125
|
+
}
|
|
126
|
+
function associatedData(sender, recipient, counter) {
|
|
127
|
+
return concat(DATA_CONTEXT, sender, recipient, counterBytes(counter));
|
|
128
|
+
}
|
|
129
|
+
async function deriveDirectionKey(secret, salt, low, high, label) {
|
|
130
|
+
const material = await crypto.subtle.importKey("raw", Uint8Array.from(secret), "HKDF", false, ["deriveKey"]);
|
|
131
|
+
return crypto.subtle.deriveKey({
|
|
132
|
+
name: "HKDF",
|
|
133
|
+
hash: "SHA-256",
|
|
134
|
+
salt: Uint8Array.from(salt),
|
|
135
|
+
info: concat(CHANNEL_CONTEXT, low, high, label)
|
|
136
|
+
}, material, {
|
|
137
|
+
name: "AES-GCM",
|
|
138
|
+
length: AES_KEY_BIT_LENGTH
|
|
139
|
+
}, false, ["encrypt", "decrypt"]);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Completes a handshake once the peer's hello has verified: derives the two directional keys from the ECDH result of the ephemeral keys and returns the channel. Returns undefined when the peer's ephemeral key is not a valid P-256 point, which only a hostile or broken peer sends.
|
|
143
|
+
*
|
|
144
|
+
* `peer` is the device verifyHello authenticated for `remote`, and `pending` is this side's own half, whether the handshake was begun here or in answer to that hello.
|
|
145
|
+
*/
|
|
146
|
+
async function establishChannel(identity, pending, remote, peer) {
|
|
147
|
+
let secret;
|
|
148
|
+
try {
|
|
149
|
+
const remoteKey = await crypto.subtle.importKey("raw", Uint8Array.from(remote["ephemeral-key"]), ECDH_PARAMS, false, []);
|
|
150
|
+
secret = new Uint8Array(await crypto.subtle.deriveBits({
|
|
151
|
+
name: "ECDH",
|
|
152
|
+
public: remoteKey
|
|
153
|
+
}, pending.privateKey, ECDH_SECRET_BIT_LENGTH));
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const localIsLow = compareBytes(identity.deviceId, peer.deviceId) < 0;
|
|
158
|
+
const low = localIsLow ? identity.deviceId : peer.deviceId;
|
|
159
|
+
const high = localIsLow ? peer.deviceId : identity.deviceId;
|
|
160
|
+
const salt = concat(localIsLow ? pending.hello.nonce : remote.nonce, localIsLow ? remote.nonce : pending.hello.nonce);
|
|
161
|
+
const lowToHigh = await deriveDirectionKey(secret, salt, low, high, LABEL_LOW_TO_HIGH);
|
|
162
|
+
const highToLow = await deriveDirectionKey(secret, salt, low, high, LABEL_HIGH_TO_LOW);
|
|
163
|
+
const sendKey = localIsLow ? lowToHigh : highToLow;
|
|
164
|
+
const receiveKey = localIsLow ? highToLow : lowToHigh;
|
|
165
|
+
let nextSendCounter = 0;
|
|
166
|
+
let highestReceived = -1;
|
|
167
|
+
let sealing = Promise.resolve();
|
|
168
|
+
let opening = Promise.resolve();
|
|
169
|
+
async function sealAt(frame, counter) {
|
|
170
|
+
return {
|
|
171
|
+
type: "secure-data",
|
|
172
|
+
counter,
|
|
173
|
+
ciphertext: new Uint8Array(await crypto.subtle.encrypt({
|
|
174
|
+
name: "AES-GCM",
|
|
175
|
+
iv: ivFor(counter),
|
|
176
|
+
additionalData: associatedData(identity.deviceId, peer.deviceId, counter)
|
|
177
|
+
}, sendKey, messageFromFrame(frame)))
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async function openOne(data) {
|
|
181
|
+
if (data.counter <= highestReceived) return void 0;
|
|
182
|
+
let plaintext;
|
|
183
|
+
try {
|
|
184
|
+
plaintext = new Uint8Array(await crypto.subtle.decrypt({
|
|
185
|
+
name: "AES-GCM",
|
|
186
|
+
iv: ivFor(data.counter),
|
|
187
|
+
additionalData: associatedData(peer.deviceId, identity.deviceId, data.counter)
|
|
188
|
+
}, receiveKey, Uint8Array.from(data.ciphertext)));
|
|
189
|
+
} catch {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
highestReceived = data.counter;
|
|
193
|
+
return tryDecodeFrame(plaintext) ?? void 0;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
peer,
|
|
197
|
+
seal: async (frame) => {
|
|
198
|
+
const counter = nextSendCounter;
|
|
199
|
+
nextSendCounter += 1;
|
|
200
|
+
const sealed = sealing.then(async () => sealAt(frame, counter));
|
|
201
|
+
sealing = sealed.catch(() => void 0);
|
|
202
|
+
return sealed;
|
|
203
|
+
},
|
|
204
|
+
open: async (data) => {
|
|
205
|
+
const opened = opening.then(async () => openOne(data));
|
|
206
|
+
opening = opened.catch(() => void 0);
|
|
207
|
+
return opened;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/domain/relay-channels.ts
|
|
213
|
+
function createRelayChannels(identity, sendHello) {
|
|
214
|
+
const channels = /* @__PURE__ */ new Map();
|
|
215
|
+
/** Registers the peer as awaiting at once, so a hello arriving meanwhile completes this handshake instead of starting a second, then sends this side's hello. */
|
|
216
|
+
function start(peer) {
|
|
217
|
+
const key = deviceIdToHex(peer);
|
|
218
|
+
const settle = Promise.withResolvers();
|
|
219
|
+
const pending = beginHandshake(identity, peer);
|
|
220
|
+
const entry = {
|
|
221
|
+
status: "awaiting",
|
|
222
|
+
pending,
|
|
223
|
+
settle
|
|
224
|
+
};
|
|
225
|
+
channels.set(key, entry);
|
|
226
|
+
pending.then(async ({ hello }) => sendHello(peer, hello)).catch((error) => {
|
|
227
|
+
settle.reject(error instanceof Error ? error : new Error(String(error)));
|
|
228
|
+
if (channels.get(key) === entry) channels.delete(key);
|
|
229
|
+
});
|
|
230
|
+
return entry;
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
ensure: async (peer) => {
|
|
234
|
+
const existing = channels.get(deviceIdToHex(peer));
|
|
235
|
+
if (existing?.status === "established") return existing.channel;
|
|
236
|
+
return (existing ?? start(peer)).settle.promise;
|
|
237
|
+
},
|
|
238
|
+
applyHello: async (hello) => {
|
|
239
|
+
const verdict = await verifyHello(identity, hello);
|
|
240
|
+
if (!verdict.ok) return;
|
|
241
|
+
const key = deviceIdToHex(verdict.peer.deviceId);
|
|
242
|
+
const current = channels.get(key);
|
|
243
|
+
const entry = current?.status === "awaiting" ? current : start(verdict.peer.deviceId);
|
|
244
|
+
const channel = await establishChannel(identity, await entry.pending, hello, verdict.peer);
|
|
245
|
+
if (channel === void 0) {
|
|
246
|
+
entry.settle.reject(/* @__PURE__ */ new Error("the peer sent an unusable ephemeral key"));
|
|
247
|
+
if (channels.get(key) === entry) channels.delete(key);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
channels.set(key, {
|
|
251
|
+
status: "established",
|
|
252
|
+
channel
|
|
253
|
+
});
|
|
254
|
+
entry.settle.resolve(channel);
|
|
255
|
+
},
|
|
256
|
+
open: async (data, claimedFrom) => {
|
|
257
|
+
if (claimedFrom === void 0) return;
|
|
258
|
+
const entry = channels.get(deviceIdToHex(claimedFrom));
|
|
259
|
+
if (entry?.status !== "established") return;
|
|
260
|
+
const frame = await entry.channel.open(data);
|
|
261
|
+
return frame === void 0 ? void 0 : {
|
|
262
|
+
frame,
|
|
263
|
+
from: entry.channel.peer.deviceId
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
reset: (reason) => {
|
|
267
|
+
for (const entry of channels.values()) if (entry.status === "awaiting") entry.settle.reject(new Error(reason));
|
|
268
|
+
channels.clear();
|
|
269
|
+
},
|
|
270
|
+
forget: (peer) => {
|
|
271
|
+
const key = deviceIdToHex(peer);
|
|
272
|
+
const entry = channels.get(key);
|
|
273
|
+
if (entry?.status === "awaiting") entry.settle.reject(/* @__PURE__ */ new Error("the relay pairing was lost"));
|
|
274
|
+
channels.delete(key);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
24
279
|
//#region src/domain/mesh-session.ts
|
|
25
280
|
const MS_PER_SECOND = 1e3;
|
|
26
281
|
/** 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. */
|
|
@@ -55,6 +310,15 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
55
310
|
getRelayedDevices: () => relayPairings.list()
|
|
56
311
|
});
|
|
57
312
|
const pendingManageRequests = /* @__PURE__ */ new Map();
|
|
313
|
+
const relayChannels = createRelayChannels(identity, async (peer, hello) => {
|
|
314
|
+
if (connection === null) throw new Error("not connected");
|
|
315
|
+
frameLog.push({
|
|
316
|
+
direction: "sent",
|
|
317
|
+
frame: hello
|
|
318
|
+
});
|
|
319
|
+
await connection.send(wrapRelayData(hello, peer));
|
|
320
|
+
emit();
|
|
321
|
+
});
|
|
58
322
|
const incomingQueue = createAsyncQueue();
|
|
59
323
|
const revocationQueue = createAsyncQueue();
|
|
60
324
|
const pingRoundTrips = createPingRoundTrips();
|
|
@@ -89,28 +353,32 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
89
353
|
if (connection === null || state.status !== "connected") throw new Error("not connected");
|
|
90
354
|
return connection;
|
|
91
355
|
}
|
|
92
|
-
/** Sends a frame
|
|
356
|
+
/** Sends a frame. Through a relay it is sealed for toDevice on their secure channel first (spec/secure-channel.cddl) and only then wrapped as relay-data, so the hub carries ciphertext. This is the single choke point every outbound manage-request and manage-response passes through, so a consumer of sendManageRequest or respond never needs its own relay logic. */
|
|
93
357
|
async function transmit(frame, viaRelay, toDevice) {
|
|
94
358
|
if (connection === null) throw new Error("not connected");
|
|
95
|
-
if (viaRelay) {
|
|
96
|
-
await connection.send(
|
|
359
|
+
if (!viaRelay) {
|
|
360
|
+
await connection.send(frame);
|
|
97
361
|
return;
|
|
98
362
|
}
|
|
99
|
-
|
|
363
|
+
if (toDevice === void 0) throw new Error("a relayed frame needs the device it is for");
|
|
364
|
+
const sealed = await (await relayChannels.ensure(toDevice)).seal(frame);
|
|
365
|
+
await connection.send(wrapRelayData(sealed, toDevice));
|
|
100
366
|
}
|
|
101
|
-
|
|
367
|
+
/** `from` is the device whose secure channel the response arrived through, absent for one that arrived directly. A pending request only accepts an answer from where it was sent: a peer must not be able to answer a request meant for another by guessing its request-id. */
|
|
368
|
+
function applyManageResponse(frame, from) {
|
|
102
369
|
const requestId = frame["request-id"];
|
|
103
370
|
const pending = pendingManageRequests.get(requestId);
|
|
104
|
-
if (pending
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
371
|
+
if (pending === void 0) return;
|
|
372
|
+
const expected = pending.expectedFrom;
|
|
373
|
+
if (!(expected === void 0 ? from === void 0 : from !== void 0 && deviceIdToHex(from) === deviceIdToHex(expected))) return;
|
|
374
|
+
pendingManageRequests.delete(requestId);
|
|
375
|
+
pending.resolve(frame.outcome);
|
|
108
376
|
}
|
|
109
|
-
/**
|
|
110
|
-
function applyManageRequest(frame,
|
|
377
|
+
/** `origin` is present only for a manage-request that arrived through a relay pairing: who sent it, as their secure channel authenticated them, and the device the relay-data frame was addressed to, if it named one. See IncomingManageRequest's own fromDevice/toDevice doc comments for what each means and why neither is ever guessed from pairing state. */
|
|
378
|
+
function applyManageRequest(frame, origin) {
|
|
111
379
|
const requestId = frame["request-id"];
|
|
112
|
-
const fromDevice =
|
|
113
|
-
const toDevice =
|
|
380
|
+
const fromDevice = origin?.fromDevice;
|
|
381
|
+
const toDevice = origin?.toDevice;
|
|
114
382
|
const incoming = {
|
|
115
383
|
requestId,
|
|
116
384
|
command: frame.command,
|
|
@@ -128,7 +396,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
128
396
|
direction: "sent",
|
|
129
397
|
frame: response
|
|
130
398
|
});
|
|
131
|
-
await transmit(response,
|
|
399
|
+
await transmit(response, origin !== void 0, fromDevice);
|
|
132
400
|
emit();
|
|
133
401
|
}
|
|
134
402
|
};
|
|
@@ -141,13 +409,32 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
141
409
|
async function applyFrame(frame) {
|
|
142
410
|
if (frame.type === "relay-data") {
|
|
143
411
|
const inner = tryDecodeFrame(frame.payload);
|
|
144
|
-
if (inner
|
|
412
|
+
if (inner?.type === "secure-hello") {
|
|
145
413
|
frameLog.push({
|
|
146
414
|
direction: "received",
|
|
147
415
|
frame: inner
|
|
148
416
|
});
|
|
149
|
-
|
|
150
|
-
|
|
417
|
+
await relayChannels.applyHello(inner);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (inner?.type === "secure-data") {
|
|
421
|
+
const opened = await relayChannels.open(inner, frame["from-device"]);
|
|
422
|
+
if (opened?.frame.type === "manage-response") {
|
|
423
|
+
frameLog.push({
|
|
424
|
+
direction: "received",
|
|
425
|
+
frame: opened.frame
|
|
426
|
+
});
|
|
427
|
+
applyManageResponse(opened.frame, opened.from);
|
|
428
|
+
} else if (opened?.frame.type === "manage-request") {
|
|
429
|
+
frameLog.push({
|
|
430
|
+
direction: "received",
|
|
431
|
+
frame: opened.frame
|
|
432
|
+
});
|
|
433
|
+
applyManageRequest(opened.frame, {
|
|
434
|
+
fromDevice: opened.from,
|
|
435
|
+
...frame["to-device"] !== void 0 ? { toDevice: frame["to-device"] } : {}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
151
438
|
return;
|
|
152
439
|
}
|
|
153
440
|
frameLog.push({
|
|
@@ -271,6 +558,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
271
558
|
/** Everything a connection needs once it exists, regardless of whether it was dialled (createMeshSession's own doConnect, below) or handed over already established (acceptMeshSession): send this side's handshake and self-advert, arm the handshake timeout, and start consuming frames. The two entry points differ only in how link itself came to exist and what address means for it -- a real dial target for one, a caller-chosen label for the other, since the Connection/Transport ports expose no remote-address concept of their own for an accepted connection. */
|
|
272
559
|
async function wireUpConnection(link, address, localDomains) {
|
|
273
560
|
connection = link;
|
|
561
|
+
relayChannels.reset("the connection changed before the channel was ready");
|
|
274
562
|
localHandshakeSent = localHandshake(localDomains);
|
|
275
563
|
handshake = { status: "pending" };
|
|
276
564
|
state = {
|
|
@@ -368,20 +656,33 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
368
656
|
const outcome = new Promise((resolve, reject) => {
|
|
369
657
|
pendingManageRequests.set(requestId, {
|
|
370
658
|
resolve,
|
|
371
|
-
reject
|
|
659
|
+
reject,
|
|
660
|
+
expectedFrom: targetDevice
|
|
372
661
|
});
|
|
373
662
|
});
|
|
374
663
|
frameLog.push({
|
|
375
664
|
direction: "sent",
|
|
376
665
|
frame
|
|
377
666
|
});
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
667
|
+
const exchange = (async () => {
|
|
668
|
+
try {
|
|
669
|
+
await transmit(frame, targetDevice !== void 0, targetDevice);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
pendingManageRequests.delete(requestId);
|
|
672
|
+
outcome.catch(() => void 0);
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
emit();
|
|
676
|
+
return outcome;
|
|
677
|
+
})();
|
|
678
|
+
if (timeoutMs === void 0) return exchange;
|
|
679
|
+
return Promise.race([exchange, new Promise((resolve) => {
|
|
382
680
|
setTimeout(() => {
|
|
383
681
|
if (pendingManageRequests.delete(requestId)) {
|
|
384
|
-
if (targetDevice !== void 0)
|
|
682
|
+
if (targetDevice !== void 0) {
|
|
683
|
+
relayPairings.remove(targetDevice);
|
|
684
|
+
relayChannels.forget(targetDevice);
|
|
685
|
+
}
|
|
385
686
|
resolve({
|
|
386
687
|
result: "error",
|
|
387
688
|
code: "timeout"
|
|
@@ -436,6 +737,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
436
737
|
reconnectTimer = null;
|
|
437
738
|
}
|
|
438
739
|
rejectPendingManageRequests("connection closed before a response arrived");
|
|
740
|
+
relayChannels.reset("the session closed before the channel was ready");
|
|
439
741
|
pingRoundTrips.rejectAll("connection closed before a pong arrived");
|
|
440
742
|
if (connection !== null) await connection.close();
|
|
441
743
|
if (state.status === "connected" || state.status === "connecting" || state.status === "reconnecting") state = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { D as DeviceId, Ot as RoomNotice, T as DataHaveFrame, kt as RoomNoticeClaims, p as CapabilityToken } from "../protocol-
|
|
1
|
+
import { D as DeviceId, Ot as RoomNotice, T as DataHaveFrame, kt as RoomNoticeClaims, p as CapabilityToken } from "../protocol-DCDae3z4.cjs";
|
|
2
2
|
import { KeyValueStorage } from "../ports/storage.cjs";
|
|
3
|
-
import { t as IdentityPort } from "../identity-
|
|
3
|
+
import { t as IdentityPort } from "../identity-DVCNuFLG.cjs";
|
|
4
4
|
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
5
|
-
import { a as RevocationCheck } from "../tokens-
|
|
5
|
+
import { a as RevocationCheck } from "../tokens-DqFu7iMX.cjs";
|
|
6
6
|
import { RoomNoticeVerdictReason } from "./room.cjs";
|
|
7
7
|
//#region src/domain/notice-board.d.ts
|
|
8
8
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { D as DeviceId, Ot as RoomNotice, T as DataHaveFrame, kt as RoomNoticeClaims, p as CapabilityToken } from "../protocol-
|
|
1
|
+
import { D as DeviceId, Ot as RoomNotice, T as DataHaveFrame, kt as RoomNoticeClaims, p as CapabilityToken } from "../protocol-DCDae3z4.mjs";
|
|
2
2
|
import { KeyValueStorage } from "../ports/storage.mjs";
|
|
3
|
-
import { t as IdentityPort } from "../identity-
|
|
3
|
+
import { t as IdentityPort } from "../identity-DcaYgjXT.mjs";
|
|
4
4
|
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
5
|
-
import { a as RevocationCheck } from "../tokens-
|
|
5
|
+
import { a as RevocationCheck } from "../tokens-CBiSYuzp.mjs";
|
|
6
6
|
import { RoomNoticeVerdictReason } from "./room.mjs";
|
|
7
7
|
//#region src/domain/notice-board.d.ts
|
|
8
8
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DeviceId, H as ManageCommandParams, Q as PathTrace, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-
|
|
1
|
+
import { D as DeviceId, H as ManageCommandParams, Q as PathTrace, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DCDae3z4.cjs";
|
|
2
2
|
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
3
3
|
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.cjs";
|
|
4
4
|
//#region src/domain/path-trace.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DeviceId, H as ManageCommandParams, Q as PathTrace, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-
|
|
1
|
+
import { D as DeviceId, H as ManageCommandParams, Q as PathTrace, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DCDae3z4.mjs";
|
|
2
2
|
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
3
3
|
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.mjs";
|
|
4
4
|
//#region src/domain/path-trace.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as IdentityKey, D as DeviceId, et as PeerAdvert } from "../protocol-
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { B as IdentityKey, D as DeviceId, et as PeerAdvert } from "../protocol-DCDae3z4.cjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-DVCNuFLG.cjs";
|
|
3
3
|
//#region src/domain/peer-advert.d.ts
|
|
4
4
|
/** peer-advert's own signature field, the one entry the signature does not cover, named once here so the signing input and the reserved-extension-key guard cannot drift apart. */
|
|
5
5
|
export declare const PEER_ADVERT_SIGNATURE_KEY = "signature";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as IdentityKey, D as DeviceId, et as PeerAdvert } from "../protocol-
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { B as IdentityKey, D as DeviceId, et as PeerAdvert } from "../protocol-DCDae3z4.mjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-DcaYgjXT.mjs";
|
|
3
3
|
//#region src/domain/peer-advert.d.ts
|
|
4
4
|
/** peer-advert's own signature field, the one entry the signature does not cover, named once here so the signing input and the reserved-extension-key guard cannot drift apart. */
|
|
5
5
|
export declare const PEER_ADVERT_SIGNATURE_KEY = "signature";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_domain_peer_advert = require("./peer-advert.cjs");
|
|
3
|
+
const require_domain_advert_extension_policy = require("./advert-extension-policy.cjs");
|
|
3
4
|
//#region src/domain/relay-hub.ts
|
|
4
5
|
const HEX_RADIX = 16;
|
|
5
6
|
const HEX_DIGITS_PER_BYTE = 2;
|
|
@@ -115,6 +116,16 @@ function createRelayHub(options) {
|
|
|
115
116
|
const accepted = [];
|
|
116
117
|
for (const advert of frame.peers) {
|
|
117
118
|
if (!await require_domain_peer_advert.verifyPeerAdvert(options.identity, advert)) continue;
|
|
119
|
+
if (options.extensionPolicy !== void 0) {
|
|
120
|
+
const verdict = require_domain_advert_extension_policy.checkAdvertExtensions(advert, options.extensionPolicy);
|
|
121
|
+
if (!verdict.ok) {
|
|
122
|
+
options.onAdvertRefused?.({
|
|
123
|
+
device: advert.device,
|
|
124
|
+
key: verdict.key
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
118
129
|
const key = deviceKey(advert.device);
|
|
119
130
|
const previous = devices.get(key);
|
|
120
131
|
if (previous !== void 0 && !supersedes(previous, advert, connection)) continue;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { D as DeviceId, N as Frame, et as PeerAdvert } from "../protocol-
|
|
2
|
-
import { t as Connection } from "../transport-
|
|
1
|
+
import { D as DeviceId, N as Frame, et as PeerAdvert } from "../protocol-DCDae3z4.cjs";
|
|
2
|
+
import { t as Connection } from "../transport-CfW7fsGK.cjs";
|
|
3
|
+
import { AdvertExtensionPolicy } from "./advert-extension-policy.cjs";
|
|
3
4
|
import { PeerAdvertVerifier } from "./peer-advert.cjs";
|
|
4
5
|
//#region src/domain/relay-hub.d.ts
|
|
5
6
|
/**
|
|
@@ -20,9 +21,21 @@ export interface RelayConnectionRestore {
|
|
|
20
21
|
readonly connection: Readonly<Connection>;
|
|
21
22
|
readonly state: RelayConnectionState;
|
|
22
23
|
}
|
|
24
|
+
/** An advert the hub refused to carry because of what its extension tail holds. */
|
|
25
|
+
export interface AdvertRefusal {
|
|
26
|
+
readonly device: DeviceId;
|
|
27
|
+
/** The extension key that was not allowed, or whose value failed its validator. */
|
|
28
|
+
readonly key: string;
|
|
29
|
+
}
|
|
23
30
|
export interface RelayHubOptions {
|
|
24
31
|
/** The crypto primitives this hub verifies every gossiped advert with (wire-mesh#225). Narrowed to verify/deriveDeviceId rather than a full IdentityPort because an advert is self-certifying: a hub checks one against the key the advert itself carries, and never needs a signing identity of its own to do it. */
|
|
25
32
|
readonly identity: Readonly<PeerAdvertVerifier>;
|
|
33
|
+
/**
|
|
34
|
+
* Which extension entries of an advert this hub will carry. Absent, every extension is carried, which is right for a hub whose every client belongs to one mesh. A hub open to anyone should set it: an advert is forwarded whole to every client, so this is the only control the hub has over what an application publishes to strangers.
|
|
35
|
+
*/
|
|
36
|
+
readonly extensionPolicy?: AdvertExtensionPolicy;
|
|
37
|
+
/** Called with each advert refused by extensionPolicy, so the host can report it; the advert's sender is given no reply, since the transport spec defines none. */
|
|
38
|
+
readonly onAdvertRefused?: (refusal: Readonly<AdvertRefusal>) => void;
|
|
26
39
|
/** Called whenever the value exportConnection returns for a connection changes: it gossiped an advert, gained or lost a pairing, or had one of its adverts taken over by another connection. A host whose own instance can be torn down while its connections stay open (the Cloudflare hub's hibernating Durable Object) reads the new value back with exportConnection, persists it against the connection, and hands it to a later instance through restoreConnections. */
|
|
27
40
|
readonly onConnectionStateChanged?: (connection: Readonly<Connection>) => void;
|
|
28
41
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { D as DeviceId, N as Frame, et as PeerAdvert } from "../protocol-
|
|
2
|
-
import { t as Connection } from "../transport-
|
|
1
|
+
import { D as DeviceId, N as Frame, et as PeerAdvert } from "../protocol-DCDae3z4.mjs";
|
|
2
|
+
import { t as Connection } from "../transport-iLBxIkRS.mjs";
|
|
3
|
+
import { AdvertExtensionPolicy } from "./advert-extension-policy.mjs";
|
|
3
4
|
import { PeerAdvertVerifier } from "./peer-advert.mjs";
|
|
4
5
|
//#region src/domain/relay-hub.d.ts
|
|
5
6
|
/**
|
|
@@ -20,9 +21,21 @@ export interface RelayConnectionRestore {
|
|
|
20
21
|
readonly connection: Readonly<Connection>;
|
|
21
22
|
readonly state: RelayConnectionState;
|
|
22
23
|
}
|
|
24
|
+
/** An advert the hub refused to carry because of what its extension tail holds. */
|
|
25
|
+
export interface AdvertRefusal {
|
|
26
|
+
readonly device: DeviceId;
|
|
27
|
+
/** The extension key that was not allowed, or whose value failed its validator. */
|
|
28
|
+
readonly key: string;
|
|
29
|
+
}
|
|
23
30
|
export interface RelayHubOptions {
|
|
24
31
|
/** The crypto primitives this hub verifies every gossiped advert with (wire-mesh#225). Narrowed to verify/deriveDeviceId rather than a full IdentityPort because an advert is self-certifying: a hub checks one against the key the advert itself carries, and never needs a signing identity of its own to do it. */
|
|
25
32
|
readonly identity: Readonly<PeerAdvertVerifier>;
|
|
33
|
+
/**
|
|
34
|
+
* Which extension entries of an advert this hub will carry. Absent, every extension is carried, which is right for a hub whose every client belongs to one mesh. A hub open to anyone should set it: an advert is forwarded whole to every client, so this is the only control the hub has over what an application publishes to strangers.
|
|
35
|
+
*/
|
|
36
|
+
readonly extensionPolicy?: AdvertExtensionPolicy;
|
|
37
|
+
/** Called with each advert refused by extensionPolicy, so the host can report it; the advert's sender is given no reply, since the transport spec defines none. */
|
|
38
|
+
readonly onAdvertRefused?: (refusal: Readonly<AdvertRefusal>) => void;
|
|
26
39
|
/** Called whenever the value exportConnection returns for a connection changes: it gossiped an advert, gained or lost a pairing, or had one of its adverts taken over by another connection. A host whose own instance can be torn down while its connections stay open (the Cloudflare hub's hibernating Durable Object) reads the new value back with exportConnection, persists it against the connection, and hands it to a later instance through restoreConnections. */
|
|
27
40
|
readonly onConnectionStateChanged?: (connection: Readonly<Connection>) => void;
|
|
28
41
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { verifyPeerAdvert } from "./peer-advert.mjs";
|
|
2
|
+
import { checkAdvertExtensions } from "./advert-extension-policy.mjs";
|
|
2
3
|
//#region src/domain/relay-hub.ts
|
|
3
4
|
const HEX_RADIX = 16;
|
|
4
5
|
const HEX_DIGITS_PER_BYTE = 2;
|
|
@@ -114,6 +115,16 @@ function createRelayHub(options) {
|
|
|
114
115
|
const accepted = [];
|
|
115
116
|
for (const advert of frame.peers) {
|
|
116
117
|
if (!await verifyPeerAdvert(options.identity, advert)) continue;
|
|
118
|
+
if (options.extensionPolicy !== void 0) {
|
|
119
|
+
const verdict = checkAdvertExtensions(advert, options.extensionPolicy);
|
|
120
|
+
if (!verdict.ok) {
|
|
121
|
+
options.onAdvertRefused?.({
|
|
122
|
+
device: advert.device,
|
|
123
|
+
key: verdict.key
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
117
128
|
const key = deviceKey(advert.device);
|
|
118
129
|
const previous = devices.get(key);
|
|
119
130
|
if (previous !== void 0 && !supersedes(previous, advert, connection)) continue;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { N as Frame } from "../protocol-
|
|
2
|
-
import { t as Connection } from "../transport-
|
|
1
|
+
import { N as Frame } from "../protocol-DCDae3z4.cjs";
|
|
2
|
+
import { t as Connection } from "../transport-CfW7fsGK.cjs";
|
|
3
3
|
//#region src/domain/relay-use-gate.d.ts
|
|
4
4
|
export interface RelayUseAuthorizationTracker {
|
|
5
5
|
/** True once authorize() has been called for this exact connection and revoke() has not been called since. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { N as Frame } from "../protocol-
|
|
2
|
-
import { t as Connection } from "../transport-
|
|
1
|
+
import { N as Frame } from "../protocol-DCDae3z4.mjs";
|
|
2
|
+
import { t as Connection } from "../transport-iLBxIkRS.mjs";
|
|
3
3
|
//#region src/domain/relay-use-gate.d.ts
|
|
4
4
|
export interface RelayUseAuthorizationTracker {
|
|
5
5
|
/** True once authorize() has been called for this exact connection and revoke() has not been called since. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { bt as RevocationEntry } from "../protocol-
|
|
2
|
-
import { a as RevocationCheck, d as VerifyRevocationEntryOptions, o as RevocationEntryVerdict } from "../tokens-
|
|
1
|
+
import { bt as RevocationEntry } from "../protocol-DCDae3z4.cjs";
|
|
2
|
+
import { a as RevocationCheck, d as VerifyRevocationEntryOptions, o as RevocationEntryVerdict } from "../tokens-DqFu7iMX.cjs";
|
|
3
3
|
//#region src/domain/revocation-view.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* An in-memory RevocationCheck fed by ingested revocation-announce frames. Keyed by token-id alone, per management.cddl's contract: a token-id can legitimately carry multiple recorded entries from different issuers (the token's own issuer, and/or any number of parties holding a delegated manage:revoke authorization over it) -- entriesFor returns every one of them unfiltered, since checking which actually apply (issuer-match, or a verified authorization) is verifyTokenChain's own obligation, not this store's.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { bt as RevocationEntry } from "../protocol-
|
|
2
|
-
import { a as RevocationCheck, d as VerifyRevocationEntryOptions, o as RevocationEntryVerdict } from "../tokens-
|
|
1
|
+
import { bt as RevocationEntry } from "../protocol-DCDae3z4.mjs";
|
|
2
|
+
import { a as RevocationCheck, d as VerifyRevocationEntryOptions, o as RevocationEntryVerdict } from "../tokens-CBiSYuzp.mjs";
|
|
3
3
|
//#region src/domain/revocation-view.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* An in-memory RevocationCheck fed by ingested revocation-announce frames. Keyed by token-id alone, per management.cddl's contract: a token-id can legitimately carry multiple recorded entries from different issuers (the token's own issuer, and/or any number of parties holding a delegated manage:revoke authorization over it) -- entriesFor returns every one of them unfiltered, since checking which actually apply (issuer-match, or a verified authorization) is verifyTokenChain's own obligation, not this store's.
|