wire-mesh-core 1.56.0 → 1.58.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/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/async-queue.cjs +30 -0
- package/dist/domain/async-queue.d.cts +10 -0
- package/dist/domain/async-queue.d.mts +10 -0
- package/dist/domain/async-queue.mjs +29 -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 +4 -0
- package/dist/domain/gossip-extensions.d.cts +6 -0
- package/dist/domain/gossip-extensions.d.mts +6 -0
- package/dist/domain/gossip-extensions.mjs +2 -0
- 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 +30 -148
- package/dist/domain/mesh-session.d.cts +5 -3
- package/dist/domain/mesh-session.d.mts +5 -3
- package/dist/domain/mesh-session.mjs +25 -141
- package/dist/domain/notice-board.d.cts +3 -3
- package/dist/domain/notice-board.d.mts +3 -3
- package/dist/domain/path-trace.cjs +58 -0
- package/dist/domain/path-trace.d.cts +44 -0
- package/dist/domain/path-trace.d.mts +44 -0
- package/dist/domain/path-trace.mjs +52 -0
- package/dist/domain/ping-round-trips.cjs +38 -0
- package/dist/domain/ping-round-trips.d.cts +13 -0
- package/dist/domain/ping-round-trips.d.mts +13 -0
- package/dist/domain/ping-round-trips.mjs +37 -0
- package/dist/domain/relay-hub.d.cts +2 -2
- package/dist/domain/relay-hub.d.mts +2 -2
- 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.cjs +39 -0
- package/dist/domain/topology-snapshot.d.cts +31 -0
- package/dist/domain/topology-snapshot.d.mts +31 -0
- package/dist/domain/topology-snapshot.mjs +37 -0
- package/dist/domain/topology.cjs +87 -0
- package/dist/domain/topology.d.cts +38 -0
- package/dist/domain/topology.d.mts +38 -0
- package/dist/domain/topology.mjs +81 -0
- package/dist/domain/webrtc-signaling.d.cts +2 -2
- package/dist/domain/webrtc-signaling.d.mts +2 -2
- package/dist/generated/protocol.cjs +11 -0
- package/dist/generated/protocol.d.cts +2 -2
- package/dist/generated/protocol.d.mts +2 -2
- package/dist/generated/protocol.mjs +10 -1
- package/dist/gossip-extensions-Cdy5mQhY.mjs +41 -0
- package/dist/gossip-extensions-Dr_T7hk_.cjs +78 -0
- package/dist/{identity-Br6e0Rx5.d.mts → identity-BRlM-Krs.d.cts} +1 -1
- package/dist/{identity-pOt5KxJB.d.cts → identity-DiuayfEd.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-D2Ue2nd2.d.cts → protocol-BnbpCk0Z.d.cts} +23 -1
- package/dist/{protocol-D2Ue2nd2.d.mts → protocol-BnbpCk0Z.d.mts} +23 -1
- package/dist/{room-token-verification-CNXMjIQd.d.cts → room-token-verification-D9ogJTO3.d.cts} +2 -2
- package/dist/{room-token-verification-ZHgJhnsu.d.mts → room-token-verification-NjaqYY4k.d.mts} +2 -2
- package/dist/{tokens-BtczHfrc.d.cts → tokens-BL2BzOmN.d.cts} +2 -2
- package/dist/{tokens-Hnq9oURj.d.mts → tokens-DtjdYS_a.d.mts} +2 -2
- package/dist/{transport-C5wU0UtA.d.mts → transport-BH8kGWXt.d.mts} +1 -1
- package/dist/{transport-fUY3alfO.d.cts → transport-Ca_ZyyZe.d.cts} +1 -1
- package/package.json +25 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_rolldown_runtime = require("../rolldown-runtime-VH7oDXx4.cjs");
|
|
3
2
|
const require_domain_device_id = require("./device-id.cjs");
|
|
4
3
|
const require_domain_handshake = require("./handshake.cjs");
|
|
4
|
+
const require_domain_async_queue = require("./async-queue.cjs");
|
|
5
|
+
const require_domain_ping_round_trips = require("./ping-round-trips.cjs");
|
|
6
|
+
const require_domain_topology_snapshot = require("./topology-snapshot.cjs");
|
|
7
|
+
const require_gossip_extensions = require("../gossip-extensions-Dr_T7hk_.cjs");
|
|
5
8
|
const require_adapters_frame_codec = require("../adapters/frame-codec.cjs");
|
|
6
|
-
let package_json = require("../../package.json");
|
|
7
|
-
package_json = require_rolldown_runtime.__toESM(package_json, 1);
|
|
8
9
|
//#region src/domain/relay-pairing.ts
|
|
9
10
|
function createRelayPairings() {
|
|
10
11
|
const established = /* @__PURE__ */ new Map();
|
|
@@ -12,82 +13,11 @@ function createRelayPairings() {
|
|
|
12
13
|
has: (device) => established.has(require_domain_device_id.deviceIdToHex(device)),
|
|
13
14
|
add: (device) => {
|
|
14
15
|
established.set(require_domain_device_id.deviceIdToHex(device), device);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/domain/ping-round-trips.ts
|
|
20
|
-
function createPingRoundTrips() {
|
|
21
|
-
const pending = [];
|
|
22
|
-
function cancel(sentAt) {
|
|
23
|
-
const index = pending.findIndex((entry) => entry.sentAt === sentAt);
|
|
24
|
-
if (index === -1) return false;
|
|
25
|
-
pending.splice(index, 1);
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
resolveOldest(nowMs) {
|
|
30
|
-
const entry = pending.shift();
|
|
31
|
-
if (entry !== void 0) entry.resolve(nowMs - entry.sentAt);
|
|
32
16
|
},
|
|
33
|
-
|
|
34
|
-
for (const entry of pending.splice(0)) entry.reject(new Error(reason));
|
|
35
|
-
},
|
|
36
|
-
async sendAndAwait(sentAt, sendPing, timeoutMs) {
|
|
37
|
-
const rtt = new Promise((resolve, reject) => {
|
|
38
|
-
pending.push({
|
|
39
|
-
sentAt,
|
|
40
|
-
resolve,
|
|
41
|
-
reject
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
await sendPing();
|
|
45
|
-
if (timeoutMs === void 0) return rtt;
|
|
46
|
-
return Promise.race([rtt, new Promise((_resolve, reject) => {
|
|
47
|
-
setTimeout(() => {
|
|
48
|
-
if (cancel(sentAt)) reject(/* @__PURE__ */ new Error("timed out waiting for pong"));
|
|
49
|
-
}, timeoutMs);
|
|
50
|
-
})]);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
//#endregion
|
|
55
|
-
//#region src/domain/own-version.ts
|
|
56
|
-
/** This node's own actual, currently-running wire-mesh-core version -- read once from the installed package's own package.json, never from anything gossiped or cached, so buildSelfAdvert's own self-advert extension and version.get's manage-response both always answer with the truth for this exact running process. */
|
|
57
|
-
const OWN_VERSION = package_json.default.version;
|
|
58
|
-
/** The gossip extension key wire-mesh-core itself injects into every self-advert (mesh-session.ts's own buildSelfAdvert) to report OWN_VERSION -- reserved so a sendGossipUpdate caller can never also supply it, separately from peer-advert's own typed struct fields (RESERVED_PEER_ADVERT_KEYS in mesh-session.ts), since this is just an extension-tail key (transport.cddl), not a struct field. */
|
|
59
|
-
const CORE_VERSION_GOSSIP_KEY = "wire-mesh/version";
|
|
60
|
-
const VERSION_GET_VERB = "version.get";
|
|
61
|
-
/** True when params is the core/version domain's own version-get command (version.cddl) -- a receiver answers this unconditionally, bypassing incomingManageRequests entirely, since it is deliberately ungated: exactly as sensitive as what CORE_VERSION_GOSSIP_KEY already broadcasts unconditionally via gossip. */
|
|
62
|
-
function isVersionGetCommand(params) {
|
|
63
|
-
return "verb" in params && params.verb === VERSION_GET_VERB;
|
|
64
|
-
}
|
|
65
|
-
/** The manage-ok outcome mesh-session.ts answers a version.get request with -- OWN_VERSION on manage-ok's own open `* tstr => any` tail, the same pattern exec-list's own exec-session-info result already uses. */
|
|
66
|
-
function versionGetOutcome() {
|
|
67
|
-
return {
|
|
68
|
-
result: "ok",
|
|
69
|
-
version: OWN_VERSION
|
|
17
|
+
list: () => [...established.values()]
|
|
70
18
|
};
|
|
71
19
|
}
|
|
72
20
|
//#endregion
|
|
73
|
-
//#region src/domain/gossip-extensions.ts
|
|
74
|
-
/** peer-advert's own three typed fields -- reserved so a `sendGossipUpdate` caller can never override the session's own device-id, address list, or freshness timestamp by supplying an extension of the same name. */
|
|
75
|
-
const RESERVED_PEER_ADVERT_KEYS = /* @__PURE__ */ new Set([
|
|
76
|
-
"device",
|
|
77
|
-
"addresses",
|
|
78
|
-
"snapshot-seconds"
|
|
79
|
-
]);
|
|
80
|
-
/** A gossip extension key must be domain-qualified as `<domain>/<field>` (lowercase kebab-case each side), per `spec/CONVENTIONS.md`'s gossip-extension-namespacing convention -- this is what stops two independent applications sharing one gossip tail from silently colliding on a bare name like "status". */
|
|
81
|
-
const GOSSIP_EXTENSION_KEY_PATTERN = /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/;
|
|
82
|
-
/** Rejects a `sendGossipUpdate` extension bag that would either shadow one of peer-advert's own mandatory fields, use the key wire-mesh-core itself reserves for its own self-reported version, or use a bare, non-domain-qualified key -- all are caller bugs that must fail loudly at the call site, not silently corrupt or ambiguously merge into the wire frame. */
|
|
83
|
-
function validateGossipExtensions(extensions) {
|
|
84
|
-
for (const key of Object.keys(extensions)) {
|
|
85
|
-
if (RESERVED_PEER_ADVERT_KEYS.has(key)) throw new Error(`sendGossipUpdate extension key "${key}" collides with a mandatory peer-advert field`);
|
|
86
|
-
if (key === "wire-mesh/version") throw new Error(`sendGossipUpdate extension key "${key}" is reserved for wire-mesh-core's own self-reported version`);
|
|
87
|
-
if (!GOSSIP_EXTENSION_KEY_PATTERN.test(key)) throw new Error(`sendGossipUpdate extension key "${key}" must be domain-qualified as "<domain>/<field>" (e.g. "presence/status")`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
//#endregion
|
|
91
21
|
//#region src/domain/mesh-session.ts
|
|
92
22
|
const MS_PER_SECOND = 1e3;
|
|
93
23
|
/** 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. */
|
|
@@ -109,20 +39,22 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
109
39
|
const directory = /* @__PURE__ */ new Map();
|
|
110
40
|
const frameLog = [];
|
|
111
41
|
let feedCancelled = false;
|
|
112
|
-
const
|
|
113
|
-
const eventBacklog = [];
|
|
42
|
+
const eventQueue = require_domain_async_queue.createAsyncQueue();
|
|
114
43
|
let handshakeTimer = null;
|
|
115
44
|
let reconnectTimer = null;
|
|
116
45
|
let attempt = 0;
|
|
117
46
|
let currentToken = null;
|
|
118
47
|
let nextRequestId = 0;
|
|
119
48
|
const relayPairings = createRelayPairings();
|
|
49
|
+
const topology = require_domain_topology_snapshot.createTopologySnapshotTracker({
|
|
50
|
+
getAuthenticatedPeer: () => connection?.peerDeviceId,
|
|
51
|
+
isAccepted: dial === null,
|
|
52
|
+
getRelayedDevices: () => relayPairings.list()
|
|
53
|
+
});
|
|
120
54
|
const pendingManageRequests = /* @__PURE__ */ new Map();
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
const revocationBacklog = [];
|
|
125
|
-
const pingRoundTrips = createPingRoundTrips();
|
|
55
|
+
const incomingQueue = require_domain_async_queue.createAsyncQueue();
|
|
56
|
+
const revocationQueue = require_domain_async_queue.createAsyncQueue();
|
|
57
|
+
const pingRoundTrips = require_domain_ping_round_trips.createPingRoundTrips();
|
|
126
58
|
function snapshot() {
|
|
127
59
|
return {
|
|
128
60
|
state,
|
|
@@ -131,20 +63,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
131
63
|
};
|
|
132
64
|
}
|
|
133
65
|
function emit() {
|
|
134
|
-
|
|
135
|
-
const waiter = eventWaiters.shift();
|
|
136
|
-
if (waiter) waiter(event);
|
|
137
|
-
else eventBacklog.push(event);
|
|
138
|
-
}
|
|
139
|
-
function emitIncomingManageRequest(request) {
|
|
140
|
-
const waiter = incomingWaiters.shift();
|
|
141
|
-
if (waiter) waiter(request);
|
|
142
|
-
else incomingBacklog.push(request);
|
|
143
|
-
}
|
|
144
|
-
function emitRevocationEntry(entry) {
|
|
145
|
-
const waiter = revocationWaiters.shift();
|
|
146
|
-
if (waiter) waiter(entry);
|
|
147
|
-
else revocationBacklog.push(entry);
|
|
66
|
+
eventQueue.push(snapshot());
|
|
148
67
|
}
|
|
149
68
|
function rejectPendingManageRequests(reason) {
|
|
150
69
|
for (const pending of pendingManageRequests.values()) pending.reject(new Error(reason));
|
|
@@ -210,11 +129,11 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
210
129
|
emit();
|
|
211
130
|
}
|
|
212
131
|
};
|
|
213
|
-
if (isVersionGetCommand(frame.command.params)) {
|
|
214
|
-
incoming.respond(versionGetOutcome());
|
|
132
|
+
if (require_gossip_extensions.isVersionGetCommand(frame.command.params)) {
|
|
133
|
+
incoming.respond(require_gossip_extensions.versionGetOutcome());
|
|
215
134
|
return;
|
|
216
135
|
}
|
|
217
|
-
|
|
136
|
+
incomingQueue.push(incoming);
|
|
218
137
|
}
|
|
219
138
|
function applyFrame(frame) {
|
|
220
139
|
if (frame.type === "relay-data") {
|
|
@@ -244,13 +163,14 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
244
163
|
device: advert.device,
|
|
245
164
|
advert
|
|
246
165
|
});
|
|
166
|
+
topology.recordAdvert(advert.device);
|
|
247
167
|
onPeerAdvert?.(advert);
|
|
248
168
|
}
|
|
249
169
|
else if (frame.type === "relay-inbound") relayPairings.add(frame["source-device"]);
|
|
250
170
|
else if (frame.type === "manage-response") applyManageResponse(frame);
|
|
251
171
|
else if (frame.type === "manage-request") applyManageRequest(frame);
|
|
252
172
|
else if (frame.type === "pong") pingRoundTrips.resolveOldest(clock.now());
|
|
253
|
-
else if (frame.type === "revocation-announce") for (const entry of frame.entries)
|
|
173
|
+
else if (frame.type === "revocation-announce") for (const entry of frame.entries) revocationQueue.push(entry);
|
|
254
174
|
}
|
|
255
175
|
/** Establishes a relay-connect pairing to targetDevice if this session isn't already paired with it -- a no-op when it already is, whether that pairing was established by this session's own prior relay-connect (initiator role) or learned from an incoming relay-inbound (target role, replying back to whoever dialed it). Pairing with a new target never tears down an existing pairing with a different one: this connection can hold several simultaneously (wire-mesh#30's own multiplexed adjacency map), so a later request back to an already-paired target must not re-send relay-connect for it. relay-connect has no ack frame: the initiator proceeds to relay-data right after sending it. */
|
|
256
176
|
async function ensureRelayPairing(targetDevice) {
|
|
@@ -328,14 +248,15 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
328
248
|
onSessionEnd?.(link);
|
|
329
249
|
if (state.status === "connected") handleDisconnect("node closed the connection", address, localDomains);
|
|
330
250
|
}
|
|
331
|
-
/** Builds this side's own self-advert: this node's own directly-reachable addresses (wire-mesh#38), or none for a caller with nothing to offer (a browser client, which cannot accept inbound connections) -- either is an honest advert, not a stopgap. extensions merge onto peer-advert's own open `* tstr => any` tail -- the mechanism sendGossipUpdate uses to keep a gossiped fact (presence status, an accept/refuse policy, or any future domain's own) live over the connection's lifetime. Extensions and
|
|
251
|
+
/** Builds this side's own self-advert: this node's own directly-reachable addresses (wire-mesh#38), or none for a caller with nothing to offer (a browser client, which cannot accept inbound connections) -- either is an honest advert, not a stopgap. extensions merge onto peer-advert's own open `* tstr => any` tail -- the mechanism sendGossipUpdate uses to keep a gossiped fact (presence status, an accept/refuse policy, or any future domain's own) live over the connection's lifetime. Extensions, CORE_VERSION_GOSSIP_KEY, and topology/peers are all spread before the mandatory fields (never after) so a caller-supplied key of the same name can never shadow them on the wire -- validateGossipExtensions already rejects every such collision loudly, but the field order is kept safe in its own right rather than relying solely on the guard staying in sync. topology/peers (wire-mesh#180) is recomputed fresh on every call, the same "always current, never cached" treatment snapshot-seconds already gets, since a session's own connection identity and relay pairings can change between one self-advert and the next. */
|
|
332
252
|
function buildSelfAdvert(extensions) {
|
|
333
|
-
if (extensions !== void 0) validateGossipExtensions(extensions);
|
|
253
|
+
if (extensions !== void 0) require_gossip_extensions.validateGossipExtensions(extensions);
|
|
334
254
|
return {
|
|
335
255
|
type: "gossip",
|
|
336
256
|
peers: [{
|
|
337
257
|
...extensions,
|
|
338
|
-
[CORE_VERSION_GOSSIP_KEY]: OWN_VERSION,
|
|
258
|
+
[require_gossip_extensions.CORE_VERSION_GOSSIP_KEY]: require_gossip_extensions.OWN_VERSION,
|
|
259
|
+
[require_gossip_extensions.TOPOLOGY_PEERS_GOSSIP_KEY]: topology.compute(),
|
|
339
260
|
device: identity.deviceId,
|
|
340
261
|
addresses: [...addresses],
|
|
341
262
|
"snapshot-seconds": Math.floor(clock.now() / MS_PER_SECOND)
|
|
@@ -401,51 +322,9 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
401
322
|
return {
|
|
402
323
|
wireUpConnection,
|
|
403
324
|
session: {
|
|
404
|
-
events:
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (backlogEvent) resolve({
|
|
408
|
-
value: backlogEvent,
|
|
409
|
-
done: false
|
|
410
|
-
});
|
|
411
|
-
else eventWaiters.push((event) => {
|
|
412
|
-
resolve({
|
|
413
|
-
value: event,
|
|
414
|
-
done: false
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
}) };
|
|
418
|
-
} },
|
|
419
|
-
incomingManageRequests: { [Symbol.asyncIterator]() {
|
|
420
|
-
return { next: async () => new Promise((resolve) => {
|
|
421
|
-
const backlogRequest = incomingBacklog.shift();
|
|
422
|
-
if (backlogRequest) resolve({
|
|
423
|
-
value: backlogRequest,
|
|
424
|
-
done: false
|
|
425
|
-
});
|
|
426
|
-
else incomingWaiters.push((request) => {
|
|
427
|
-
resolve({
|
|
428
|
-
value: request,
|
|
429
|
-
done: false
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
}) };
|
|
433
|
-
} },
|
|
434
|
-
revocationAnnouncements: { [Symbol.asyncIterator]() {
|
|
435
|
-
return { next: async () => new Promise((resolve) => {
|
|
436
|
-
const backlogEntry = revocationBacklog.shift();
|
|
437
|
-
if (backlogEntry) resolve({
|
|
438
|
-
value: backlogEntry,
|
|
439
|
-
done: false
|
|
440
|
-
});
|
|
441
|
-
else revocationWaiters.push((entry) => {
|
|
442
|
-
resolve({
|
|
443
|
-
value: entry,
|
|
444
|
-
done: false
|
|
445
|
-
});
|
|
446
|
-
});
|
|
447
|
-
}) };
|
|
448
|
-
} },
|
|
325
|
+
events: eventQueue.stream,
|
|
326
|
+
incomingManageRequests: incomingQueue.stream,
|
|
327
|
+
revocationAnnouncements: revocationQueue.stream,
|
|
449
328
|
async connect(address, localDomains) {
|
|
450
329
|
if (connection !== null) throw new Error("a session connects once; create a new one to reconnect");
|
|
451
330
|
attempt = 0;
|
|
@@ -526,6 +405,9 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
526
405
|
await transmit(frame, false);
|
|
527
406
|
emit();
|
|
528
407
|
},
|
|
408
|
+
getTopologyPeers() {
|
|
409
|
+
return topology.compute();
|
|
410
|
+
},
|
|
529
411
|
async sendDataFrame(frame) {
|
|
530
412
|
requireConnectedLink();
|
|
531
413
|
frameLog.push({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, et as PeerAdvert, f as CapabilityScope, lt as ProtocolVersion, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, en as TopologyPeers, et as PeerAdvert, f as CapabilityScope, lt as ProtocolVersion, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-BnbpCk0Z.cjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-BRlM-Krs.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-Ca_ZyyZe.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;
|
|
@@ -87,6 +87,8 @@ export interface MeshSession {
|
|
|
87
87
|
sendRevocationAnnounce: (entries: readonly RevocationEntry[]) => Promise<void>;
|
|
88
88
|
/** Re-sends this side's own self-advert with a fresh snapshot-seconds and, when given, extensions merged onto peer-advert's own open `* tstr => any` tail -- the mechanism a caller uses to keep gossiped presence status (or any other advertised fact) live over a connection's lifetime, since the initial self-advert wireUpConnection sends at connect time is otherwise never repeated. Callers own their own re-advertisement cadence (there is no timer inside MeshSession itself, matching its own DOM-free, fully unit-testable design); a caller not calling this again after connecting is exactly today's existing gossip-once-on-connect behaviour. */
|
|
89
89
|
sendGossipUpdate: (extensions?: Record<string, unknown>) => Promise<void>;
|
|
90
|
+
/** This session's own current topology snapshot -- the identical `topology/peers` value buildSelfAdvert merges into every gossiped self-advert (wire-mesh#180), read back directly rather than only from a possibly-stale gossiped copy elsewhere in the mesh. The live, cache-bust half of the cached-vs-live split `topology.get` itself establishes: a caller answering an incoming topology.get manage-request (see topology.ts's createTopologyGetHandler) calls this to build the response. Synchronous and side-effect-free -- unlike every other method here, it sends nothing and works even before this session has ever connected (an unconnected session simply has no direct peer and no relay pairings yet, both honestly empty). */
|
|
91
|
+
getTopologyPeers: () => TopologyPeers;
|
|
90
92
|
/** Sends one core/data frame (data-have, data-request, or data-entries) directly over this session's own connection, never relay-wrapped -- the transport half of an application's own noticeboard replication policy (data-sync.ts owns what the frames MEAN; this owns getting one onto the wire), the same layering sendRevocationAnnounce already established for its own frame kind. Rejects when not connected, exactly like every other send method here. */
|
|
91
93
|
sendDataFrame: (frame: DataHaveFrame | DataRequestFrame | DataEntriesFrame) => Promise<void>;
|
|
92
94
|
/** Sends a manage-request and resolves with the matching manage-response's outcome, correlated by request-id. When targetDevice is given, the request is routed to that specific peer via an established relay-connect pairing (wrapped as relay-data) rather than sent directly over this session's own Connection -- relay-hub deliberately drops manage-request/manage-response frames sent to it directly, since routing between two connected peers is not the relay role's business, so a specific peer reachable only through a relay hub can only be addressed this way. Absent, this sends directly over the Connection exactly as before. When token is given, it is attached to this one request instead of whatever setToken last set -- a single session routinely needs a different token per request when its peer shares more than one scope with this side (e.g. several core/room memberships over one connection), and a session-global token can only ever be correct for one of them. Absent, this request carries setToken's own session-global token exactly as before. When timeoutMs is given, the returned promise resolves with `{ result: "error", code: "timeout" }` rather than hanging forever if no manage-response arrives in time -- a held-open request (a human approval, a not-yet-online peer) otherwise has no way for the caller to give up on it. Absent, this request waits exactly as before, with no time limit of its own. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, et as PeerAdvert, f as CapabilityScope, lt as ProtocolVersion, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, en as TopologyPeers, et as PeerAdvert, f as CapabilityScope, lt as ProtocolVersion, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-BnbpCk0Z.mjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-DiuayfEd.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-BH8kGWXt.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;
|
|
@@ -87,6 +87,8 @@ export interface MeshSession {
|
|
|
87
87
|
sendRevocationAnnounce: (entries: readonly RevocationEntry[]) => Promise<void>;
|
|
88
88
|
/** Re-sends this side's own self-advert with a fresh snapshot-seconds and, when given, extensions merged onto peer-advert's own open `* tstr => any` tail -- the mechanism a caller uses to keep gossiped presence status (or any other advertised fact) live over a connection's lifetime, since the initial self-advert wireUpConnection sends at connect time is otherwise never repeated. Callers own their own re-advertisement cadence (there is no timer inside MeshSession itself, matching its own DOM-free, fully unit-testable design); a caller not calling this again after connecting is exactly today's existing gossip-once-on-connect behaviour. */
|
|
89
89
|
sendGossipUpdate: (extensions?: Record<string, unknown>) => Promise<void>;
|
|
90
|
+
/** This session's own current topology snapshot -- the identical `topology/peers` value buildSelfAdvert merges into every gossiped self-advert (wire-mesh#180), read back directly rather than only from a possibly-stale gossiped copy elsewhere in the mesh. The live, cache-bust half of the cached-vs-live split `topology.get` itself establishes: a caller answering an incoming topology.get manage-request (see topology.ts's createTopologyGetHandler) calls this to build the response. Synchronous and side-effect-free -- unlike every other method here, it sends nothing and works even before this session has ever connected (an unconnected session simply has no direct peer and no relay pairings yet, both honestly empty). */
|
|
91
|
+
getTopologyPeers: () => TopologyPeers;
|
|
90
92
|
/** Sends one core/data frame (data-have, data-request, or data-entries) directly over this session's own connection, never relay-wrapped -- the transport half of an application's own noticeboard replication policy (data-sync.ts owns what the frames MEAN; this owns getting one onto the wire), the same layering sendRevocationAnnounce already established for its own frame kind. Rejects when not connected, exactly like every other send method here. */
|
|
91
93
|
sendDataFrame: (frame: DataHaveFrame | DataRequestFrame | DataEntriesFrame) => Promise<void>;
|
|
92
94
|
/** Sends a manage-request and resolves with the matching manage-response's outcome, correlated by request-id. When targetDevice is given, the request is routed to that specific peer via an established relay-connect pairing (wrapped as relay-data) rather than sent directly over this session's own Connection -- relay-hub deliberately drops manage-request/manage-response frames sent to it directly, since routing between two connected peers is not the relay role's business, so a specific peer reachable only through a relay hub can only be addressed this way. Absent, this sends directly over the Connection exactly as before. When token is given, it is attached to this one request instead of whatever setToken last set -- a single session routinely needs a different token per request when its peer shares more than one scope with this side (e.g. several core/room memberships over one connection), and a session-global token can only ever be correct for one of them. Absent, this request carries setToken's own session-global token exactly as before. When timeoutMs is given, the returned promise resolves with `{ result: "error", code: "timeout" }` rather than hanging forever if no manage-response arrives in time -- a held-open request (a human approval, a not-yet-online peer) otherwise has no way for the caller to give up on it. Absent, this request waits exactly as before, with no time limit of its own. */
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { deviceIdToHex } from "./device-id.mjs";
|
|
2
2
|
import { negotiate } from "./handshake.mjs";
|
|
3
|
+
import { createAsyncQueue } from "./async-queue.mjs";
|
|
4
|
+
import { createPingRoundTrips } from "./ping-round-trips.mjs";
|
|
5
|
+
import { createTopologySnapshotTracker } from "./topology-snapshot.mjs";
|
|
6
|
+
import { a as isVersionGetCommand, i as OWN_VERSION, n as validateGossipExtensions, o as versionGetOutcome, r as CORE_VERSION_GOSSIP_KEY, t as TOPOLOGY_PEERS_GOSSIP_KEY } from "../gossip-extensions-Cdy5mQhY.mjs";
|
|
3
7
|
import { tryDecodeFrame, wrapRelayData } from "../adapters/frame-codec.mjs";
|
|
4
|
-
import packageJson from "../../package.json" with { type: "json" };
|
|
5
8
|
//#region src/domain/relay-pairing.ts
|
|
6
9
|
function createRelayPairings() {
|
|
7
10
|
const established = /* @__PURE__ */ new Map();
|
|
@@ -9,82 +12,11 @@ function createRelayPairings() {
|
|
|
9
12
|
has: (device) => established.has(deviceIdToHex(device)),
|
|
10
13
|
add: (device) => {
|
|
11
14
|
established.set(deviceIdToHex(device), device);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/domain/ping-round-trips.ts
|
|
17
|
-
function createPingRoundTrips() {
|
|
18
|
-
const pending = [];
|
|
19
|
-
function cancel(sentAt) {
|
|
20
|
-
const index = pending.findIndex((entry) => entry.sentAt === sentAt);
|
|
21
|
-
if (index === -1) return false;
|
|
22
|
-
pending.splice(index, 1);
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
resolveOldest(nowMs) {
|
|
27
|
-
const entry = pending.shift();
|
|
28
|
-
if (entry !== void 0) entry.resolve(nowMs - entry.sentAt);
|
|
29
15
|
},
|
|
30
|
-
|
|
31
|
-
for (const entry of pending.splice(0)) entry.reject(new Error(reason));
|
|
32
|
-
},
|
|
33
|
-
async sendAndAwait(sentAt, sendPing, timeoutMs) {
|
|
34
|
-
const rtt = new Promise((resolve, reject) => {
|
|
35
|
-
pending.push({
|
|
36
|
-
sentAt,
|
|
37
|
-
resolve,
|
|
38
|
-
reject
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
await sendPing();
|
|
42
|
-
if (timeoutMs === void 0) return rtt;
|
|
43
|
-
return Promise.race([rtt, new Promise((_resolve, reject) => {
|
|
44
|
-
setTimeout(() => {
|
|
45
|
-
if (cancel(sentAt)) reject(/* @__PURE__ */ new Error("timed out waiting for pong"));
|
|
46
|
-
}, timeoutMs);
|
|
47
|
-
})]);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region src/domain/own-version.ts
|
|
53
|
-
/** This node's own actual, currently-running wire-mesh-core version -- read once from the installed package's own package.json, never from anything gossiped or cached, so buildSelfAdvert's own self-advert extension and version.get's manage-response both always answer with the truth for this exact running process. */
|
|
54
|
-
const OWN_VERSION = packageJson.version;
|
|
55
|
-
/** The gossip extension key wire-mesh-core itself injects into every self-advert (mesh-session.ts's own buildSelfAdvert) to report OWN_VERSION -- reserved so a sendGossipUpdate caller can never also supply it, separately from peer-advert's own typed struct fields (RESERVED_PEER_ADVERT_KEYS in mesh-session.ts), since this is just an extension-tail key (transport.cddl), not a struct field. */
|
|
56
|
-
const CORE_VERSION_GOSSIP_KEY = "wire-mesh/version";
|
|
57
|
-
const VERSION_GET_VERB = "version.get";
|
|
58
|
-
/** True when params is the core/version domain's own version-get command (version.cddl) -- a receiver answers this unconditionally, bypassing incomingManageRequests entirely, since it is deliberately ungated: exactly as sensitive as what CORE_VERSION_GOSSIP_KEY already broadcasts unconditionally via gossip. */
|
|
59
|
-
function isVersionGetCommand(params) {
|
|
60
|
-
return "verb" in params && params.verb === VERSION_GET_VERB;
|
|
61
|
-
}
|
|
62
|
-
/** The manage-ok outcome mesh-session.ts answers a version.get request with -- OWN_VERSION on manage-ok's own open `* tstr => any` tail, the same pattern exec-list's own exec-session-info result already uses. */
|
|
63
|
-
function versionGetOutcome() {
|
|
64
|
-
return {
|
|
65
|
-
result: "ok",
|
|
66
|
-
version: OWN_VERSION
|
|
16
|
+
list: () => [...established.values()]
|
|
67
17
|
};
|
|
68
18
|
}
|
|
69
19
|
//#endregion
|
|
70
|
-
//#region src/domain/gossip-extensions.ts
|
|
71
|
-
/** peer-advert's own three typed fields -- reserved so a `sendGossipUpdate` caller can never override the session's own device-id, address list, or freshness timestamp by supplying an extension of the same name. */
|
|
72
|
-
const RESERVED_PEER_ADVERT_KEYS = /* @__PURE__ */ new Set([
|
|
73
|
-
"device",
|
|
74
|
-
"addresses",
|
|
75
|
-
"snapshot-seconds"
|
|
76
|
-
]);
|
|
77
|
-
/** A gossip extension key must be domain-qualified as `<domain>/<field>` (lowercase kebab-case each side), per `spec/CONVENTIONS.md`'s gossip-extension-namespacing convention -- this is what stops two independent applications sharing one gossip tail from silently colliding on a bare name like "status". */
|
|
78
|
-
const GOSSIP_EXTENSION_KEY_PATTERN = /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/;
|
|
79
|
-
/** Rejects a `sendGossipUpdate` extension bag that would either shadow one of peer-advert's own mandatory fields, use the key wire-mesh-core itself reserves for its own self-reported version, or use a bare, non-domain-qualified key -- all are caller bugs that must fail loudly at the call site, not silently corrupt or ambiguously merge into the wire frame. */
|
|
80
|
-
function validateGossipExtensions(extensions) {
|
|
81
|
-
for (const key of Object.keys(extensions)) {
|
|
82
|
-
if (RESERVED_PEER_ADVERT_KEYS.has(key)) throw new Error(`sendGossipUpdate extension key "${key}" collides with a mandatory peer-advert field`);
|
|
83
|
-
if (key === "wire-mesh/version") throw new Error(`sendGossipUpdate extension key "${key}" is reserved for wire-mesh-core's own self-reported version`);
|
|
84
|
-
if (!GOSSIP_EXTENSION_KEY_PATTERN.test(key)) throw new Error(`sendGossipUpdate extension key "${key}" must be domain-qualified as "<domain>/<field>" (e.g. "presence/status")`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
//#endregion
|
|
88
20
|
//#region src/domain/mesh-session.ts
|
|
89
21
|
const MS_PER_SECOND = 1e3;
|
|
90
22
|
/** 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. */
|
|
@@ -106,19 +38,21 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
106
38
|
const directory = /* @__PURE__ */ new Map();
|
|
107
39
|
const frameLog = [];
|
|
108
40
|
let feedCancelled = false;
|
|
109
|
-
const
|
|
110
|
-
const eventBacklog = [];
|
|
41
|
+
const eventQueue = createAsyncQueue();
|
|
111
42
|
let handshakeTimer = null;
|
|
112
43
|
let reconnectTimer = null;
|
|
113
44
|
let attempt = 0;
|
|
114
45
|
let currentToken = null;
|
|
115
46
|
let nextRequestId = 0;
|
|
116
47
|
const relayPairings = createRelayPairings();
|
|
48
|
+
const topology = createTopologySnapshotTracker({
|
|
49
|
+
getAuthenticatedPeer: () => connection?.peerDeviceId,
|
|
50
|
+
isAccepted: dial === null,
|
|
51
|
+
getRelayedDevices: () => relayPairings.list()
|
|
52
|
+
});
|
|
117
53
|
const pendingManageRequests = /* @__PURE__ */ new Map();
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const revocationWaiters = [];
|
|
121
|
-
const revocationBacklog = [];
|
|
54
|
+
const incomingQueue = createAsyncQueue();
|
|
55
|
+
const revocationQueue = createAsyncQueue();
|
|
122
56
|
const pingRoundTrips = createPingRoundTrips();
|
|
123
57
|
function snapshot() {
|
|
124
58
|
return {
|
|
@@ -128,20 +62,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
128
62
|
};
|
|
129
63
|
}
|
|
130
64
|
function emit() {
|
|
131
|
-
|
|
132
|
-
const waiter = eventWaiters.shift();
|
|
133
|
-
if (waiter) waiter(event);
|
|
134
|
-
else eventBacklog.push(event);
|
|
135
|
-
}
|
|
136
|
-
function emitIncomingManageRequest(request) {
|
|
137
|
-
const waiter = incomingWaiters.shift();
|
|
138
|
-
if (waiter) waiter(request);
|
|
139
|
-
else incomingBacklog.push(request);
|
|
140
|
-
}
|
|
141
|
-
function emitRevocationEntry(entry) {
|
|
142
|
-
const waiter = revocationWaiters.shift();
|
|
143
|
-
if (waiter) waiter(entry);
|
|
144
|
-
else revocationBacklog.push(entry);
|
|
65
|
+
eventQueue.push(snapshot());
|
|
145
66
|
}
|
|
146
67
|
function rejectPendingManageRequests(reason) {
|
|
147
68
|
for (const pending of pendingManageRequests.values()) pending.reject(new Error(reason));
|
|
@@ -211,7 +132,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
211
132
|
incoming.respond(versionGetOutcome());
|
|
212
133
|
return;
|
|
213
134
|
}
|
|
214
|
-
|
|
135
|
+
incomingQueue.push(incoming);
|
|
215
136
|
}
|
|
216
137
|
function applyFrame(frame) {
|
|
217
138
|
if (frame.type === "relay-data") {
|
|
@@ -241,13 +162,14 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
241
162
|
device: advert.device,
|
|
242
163
|
advert
|
|
243
164
|
});
|
|
165
|
+
topology.recordAdvert(advert.device);
|
|
244
166
|
onPeerAdvert?.(advert);
|
|
245
167
|
}
|
|
246
168
|
else if (frame.type === "relay-inbound") relayPairings.add(frame["source-device"]);
|
|
247
169
|
else if (frame.type === "manage-response") applyManageResponse(frame);
|
|
248
170
|
else if (frame.type === "manage-request") applyManageRequest(frame);
|
|
249
171
|
else if (frame.type === "pong") pingRoundTrips.resolveOldest(clock.now());
|
|
250
|
-
else if (frame.type === "revocation-announce") for (const entry of frame.entries)
|
|
172
|
+
else if (frame.type === "revocation-announce") for (const entry of frame.entries) revocationQueue.push(entry);
|
|
251
173
|
}
|
|
252
174
|
/** Establishes a relay-connect pairing to targetDevice if this session isn't already paired with it -- a no-op when it already is, whether that pairing was established by this session's own prior relay-connect (initiator role) or learned from an incoming relay-inbound (target role, replying back to whoever dialed it). Pairing with a new target never tears down an existing pairing with a different one: this connection can hold several simultaneously (wire-mesh#30's own multiplexed adjacency map), so a later request back to an already-paired target must not re-send relay-connect for it. relay-connect has no ack frame: the initiator proceeds to relay-data right after sending it. */
|
|
253
175
|
async function ensureRelayPairing(targetDevice) {
|
|
@@ -325,7 +247,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
325
247
|
onSessionEnd?.(link);
|
|
326
248
|
if (state.status === "connected") handleDisconnect("node closed the connection", address, localDomains);
|
|
327
249
|
}
|
|
328
|
-
/** Builds this side's own self-advert: this node's own directly-reachable addresses (wire-mesh#38), or none for a caller with nothing to offer (a browser client, which cannot accept inbound connections) -- either is an honest advert, not a stopgap. extensions merge onto peer-advert's own open `* tstr => any` tail -- the mechanism sendGossipUpdate uses to keep a gossiped fact (presence status, an accept/refuse policy, or any future domain's own) live over the connection's lifetime. Extensions and
|
|
250
|
+
/** Builds this side's own self-advert: this node's own directly-reachable addresses (wire-mesh#38), or none for a caller with nothing to offer (a browser client, which cannot accept inbound connections) -- either is an honest advert, not a stopgap. extensions merge onto peer-advert's own open `* tstr => any` tail -- the mechanism sendGossipUpdate uses to keep a gossiped fact (presence status, an accept/refuse policy, or any future domain's own) live over the connection's lifetime. Extensions, CORE_VERSION_GOSSIP_KEY, and topology/peers are all spread before the mandatory fields (never after) so a caller-supplied key of the same name can never shadow them on the wire -- validateGossipExtensions already rejects every such collision loudly, but the field order is kept safe in its own right rather than relying solely on the guard staying in sync. topology/peers (wire-mesh#180) is recomputed fresh on every call, the same "always current, never cached" treatment snapshot-seconds already gets, since a session's own connection identity and relay pairings can change between one self-advert and the next. */
|
|
329
251
|
function buildSelfAdvert(extensions) {
|
|
330
252
|
if (extensions !== void 0) validateGossipExtensions(extensions);
|
|
331
253
|
return {
|
|
@@ -333,6 +255,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
333
255
|
peers: [{
|
|
334
256
|
...extensions,
|
|
335
257
|
[CORE_VERSION_GOSSIP_KEY]: OWN_VERSION,
|
|
258
|
+
[TOPOLOGY_PEERS_GOSSIP_KEY]: topology.compute(),
|
|
336
259
|
device: identity.deviceId,
|
|
337
260
|
addresses: [...addresses],
|
|
338
261
|
"snapshot-seconds": Math.floor(clock.now() / MS_PER_SECOND)
|
|
@@ -398,51 +321,9 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
398
321
|
return {
|
|
399
322
|
wireUpConnection,
|
|
400
323
|
session: {
|
|
401
|
-
events:
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (backlogEvent) resolve({
|
|
405
|
-
value: backlogEvent,
|
|
406
|
-
done: false
|
|
407
|
-
});
|
|
408
|
-
else eventWaiters.push((event) => {
|
|
409
|
-
resolve({
|
|
410
|
-
value: event,
|
|
411
|
-
done: false
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
}) };
|
|
415
|
-
} },
|
|
416
|
-
incomingManageRequests: { [Symbol.asyncIterator]() {
|
|
417
|
-
return { next: async () => new Promise((resolve) => {
|
|
418
|
-
const backlogRequest = incomingBacklog.shift();
|
|
419
|
-
if (backlogRequest) resolve({
|
|
420
|
-
value: backlogRequest,
|
|
421
|
-
done: false
|
|
422
|
-
});
|
|
423
|
-
else incomingWaiters.push((request) => {
|
|
424
|
-
resolve({
|
|
425
|
-
value: request,
|
|
426
|
-
done: false
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
}) };
|
|
430
|
-
} },
|
|
431
|
-
revocationAnnouncements: { [Symbol.asyncIterator]() {
|
|
432
|
-
return { next: async () => new Promise((resolve) => {
|
|
433
|
-
const backlogEntry = revocationBacklog.shift();
|
|
434
|
-
if (backlogEntry) resolve({
|
|
435
|
-
value: backlogEntry,
|
|
436
|
-
done: false
|
|
437
|
-
});
|
|
438
|
-
else revocationWaiters.push((entry) => {
|
|
439
|
-
resolve({
|
|
440
|
-
value: entry,
|
|
441
|
-
done: false
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
}) };
|
|
445
|
-
} },
|
|
324
|
+
events: eventQueue.stream,
|
|
325
|
+
incomingManageRequests: incomingQueue.stream,
|
|
326
|
+
revocationAnnouncements: revocationQueue.stream,
|
|
446
327
|
async connect(address, localDomains) {
|
|
447
328
|
if (connection !== null) throw new Error("a session connects once; create a new one to reconnect");
|
|
448
329
|
attempt = 0;
|
|
@@ -523,6 +404,9 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
523
404
|
await transmit(frame, false);
|
|
524
405
|
emit();
|
|
525
406
|
},
|
|
407
|
+
getTopologyPeers() {
|
|
408
|
+
return topology.compute();
|
|
409
|
+
},
|
|
526
410
|
async sendDataFrame(frame) {
|
|
527
411
|
requireConnectedLink();
|
|
528
412
|
frameLog.push({
|
|
@@ -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-BnbpCk0Z.cjs";
|
|
2
2
|
import { KeyValueStorage } from "../ports/storage.cjs";
|
|
3
|
-
import { t as IdentityPort } from "../identity-
|
|
3
|
+
import { t as IdentityPort } from "../identity-BRlM-Krs.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-BL2BzOmN.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-BnbpCk0Z.mjs";
|
|
2
2
|
import { KeyValueStorage } from "../ports/storage.mjs";
|
|
3
|
-
import { t as IdentityPort } from "../identity-
|
|
3
|
+
import { t as IdentityPort } from "../identity-DiuayfEd.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-DtjdYS_a.mjs";
|
|
6
6
|
import { RoomNoticeVerdictReason } from "./room.mjs";
|
|
7
7
|
//#region src/domain/notice-board.d.ts
|
|
8
8
|
/**
|