agent-comms 6.0.0 → 8.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -3
- package/dist/bridges/claude-code/channel.d.ts.map +1 -1
- package/dist/bridges/claude-code/channel.js +2 -2
- package/dist/bridges/claude-code/channel.js.map +1 -1
- package/dist/bridges/codex/tool.d.ts.map +1 -1
- package/dist/bridges/codex/tool.js +2 -2
- package/dist/bridges/codex/tool.js.map +1 -1
- package/dist/bridges/mcp/server.d.ts.map +1 -1
- package/dist/bridges/mcp/server.js +2 -2
- package/dist/bridges/mcp/server.js.map +1 -1
- package/dist/bridges/opencode/plugin.js +2 -2
- package/dist/bridges/opencode/plugin.js.map +1 -1
- package/dist/bridges/pi/extension.d.ts.map +1 -1
- package/dist/bridges/pi/extension.js +2 -9
- package/dist/bridges/pi/extension.js.map +1 -1
- package/dist/bridges/user/controller.d.ts +1 -1
- package/dist/bridges/user/controller.d.ts.map +1 -1
- package/dist/bridges/user/controller.js +4 -4
- package/dist/bridges/user/controller.js.map +1 -1
- package/dist/bridges/user/web/server.d.ts +5 -11
- package/dist/bridges/user/web/server.d.ts.map +1 -1
- package/dist/bridges/user/web/server.js +15 -14
- package/dist/bridges/user/web/server.js.map +1 -1
- package/dist/bridges/user/web/test/bridge-web-server.integration.test.d.ts +2 -0
- package/dist/bridges/user/web/test/bridge-web-server.integration.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/bridge-web-server.integration.test.js +47 -0
- package/dist/bridges/user/web/test/bridge-web-server.integration.test.js.map +1 -0
- package/dist/core/bridge-mesh.d.ts +1 -1
- package/dist/core/bridge-mesh.d.ts.map +1 -1
- package/dist/core/bridge-mesh.js +2 -1
- package/dist/core/bridge-mesh.js.map +1 -1
- package/dist/core/gateway-trust.d.ts +5 -5
- package/dist/core/gateway-trust.d.ts.map +1 -1
- package/dist/core/gateway-trust.js +5 -5
- package/dist/core/gateway-trust.js.map +1 -1
- package/dist/core/gossip-directory.d.ts +1 -1
- package/dist/core/gossip-directory.d.ts.map +1 -1
- package/dist/core/gossip-directory.js +4 -2
- package/dist/core/gossip-directory.js.map +1 -1
- package/dist/core/hub-forwarding.d.ts +4 -18
- package/dist/core/hub-forwarding.d.ts.map +1 -1
- package/dist/core/hub-forwarding.js +2 -41
- package/dist/core/hub-forwarding.js.map +1 -1
- package/dist/core/hub-link.d.ts +58 -0
- package/dist/core/hub-link.d.ts.map +1 -0
- package/dist/core/hub-link.js +118 -0
- package/dist/core/hub-link.js.map +1 -0
- package/dist/core/hub-session.d.ts +14 -15
- package/dist/core/hub-session.d.ts.map +1 -1
- package/dist/core/hub-session.js +30 -51
- package/dist/core/hub-session.js.map +1 -1
- package/dist/core/mesh-graph.d.ts +1 -1
- package/dist/core/mesh-graph.js +1 -1
- package/dist/core/mesh-store.d.ts +20 -5
- package/dist/core/mesh-store.d.ts.map +1 -1
- package/dist/core/mesh-store.js +52 -26
- package/dist/core/mesh-store.js.map +1 -1
- package/dist/core/peer-lifecycle.d.ts +2 -5
- package/dist/core/peer-lifecycle.d.ts.map +1 -1
- package/dist/core/peer-lifecycle.js +0 -1
- package/dist/core/peer-lifecycle.js.map +1 -1
- package/dist/core/receiver-set.d.ts +15 -0
- package/dist/core/receiver-set.d.ts.map +1 -0
- package/dist/core/receiver-set.js +32 -0
- package/dist/core/receiver-set.js.map +1 -0
- package/dist/core/room-router.d.ts +2 -2
- package/dist/core/room-router.d.ts.map +1 -1
- package/dist/core/room-router.js +2 -32
- package/dist/core/room-router.js.map +1 -1
- package/dist/core/shutdown-signals.d.ts +1 -1
- package/dist/core/shutdown-signals.js +1 -1
- package/dist/core/tool.d.ts +1 -1
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +1 -1
- package/dist/core/tool.js.map +1 -1
- package/dist/core/transport.d.ts +7 -7
- package/dist/core/transport.d.ts.map +1 -1
- package/dist/core/wire-mesh-transport.d.ts +7 -3
- package/dist/core/wire-mesh-transport.d.ts.map +1 -1
- package/dist/core/wire-mesh-transport.js +39 -22
- package/dist/core/wire-mesh-transport.js.map +1 -1
- package/dist/test/bridge-mesh.integration.test.js +15 -1
- package/dist/test/bridge-mesh.integration.test.js.map +1 -1
- package/dist/test/chat-controller-mesh-errors.integration.test.js +7 -1
- package/dist/test/chat-controller-mesh-errors.integration.test.js.map +1 -1
- package/dist/test/coordinator-failover-hub.integration.test.d.ts +5 -0
- package/dist/test/coordinator-failover-hub.integration.test.d.ts.map +1 -0
- package/dist/test/coordinator-failover-hub.integration.test.js +78 -0
- package/dist/test/coordinator-failover-hub.integration.test.js.map +1 -0
- package/dist/test/coordinator-failover.integration.test.js +3 -6
- package/dist/test/coordinator-failover.integration.test.js.map +1 -1
- package/dist/test/coordinator-failover.unit.test.js +0 -7
- package/dist/test/coordinator-failover.unit.test.js.map +1 -1
- package/dist/test/gateway-trust.integration.test.d.ts +1 -1
- package/dist/test/gateway-trust.integration.test.js +26 -2
- package/dist/test/gateway-trust.integration.test.js.map +1 -1
- package/dist/test/gateway-trust.unit.test.js +1 -1
- package/dist/test/hub-exposure.integration.test.d.ts +5 -0
- package/dist/test/hub-exposure.integration.test.d.ts.map +1 -0
- package/dist/test/hub-exposure.integration.test.js +121 -0
- package/dist/test/hub-exposure.integration.test.js.map +1 -0
- package/dist/test/hub-helpers.d.ts +20 -3
- package/dist/test/hub-helpers.d.ts.map +1 -1
- package/dist/test/hub-helpers.js +43 -3
- package/dist/test/hub-helpers.js.map +1 -1
- package/dist/test/hub-link.unit.test.d.ts +2 -0
- package/dist/test/hub-link.unit.test.d.ts.map +1 -0
- package/dist/test/hub-link.unit.test.js +210 -0
- package/dist/test/hub-link.unit.test.js.map +1 -0
- package/dist/test/hub-peer-per-store.integration.test.d.ts +9 -0
- package/dist/test/hub-peer-per-store.integration.test.d.ts.map +1 -0
- package/dist/test/hub-peer-per-store.integration.test.js +211 -0
- package/dist/test/hub-peer-per-store.integration.test.js.map +1 -0
- package/dist/test/hub-room-domain-verb-trust-bypass.integration.test.js +2 -2
- package/dist/test/hub-room-domain-verb-trust-bypass.integration.test.js.map +1 -1
- package/dist/test/hub-session-close.integration.test.js +1 -1
- package/dist/test/hub-session-close.integration.test.js.map +1 -1
- package/dist/test/hub-session-dispatch.unit.test.js +1 -66
- package/dist/test/hub-session-dispatch.unit.test.js.map +1 -1
- package/dist/test/mesh-graph-trace.integration.test.js +2 -2
- package/dist/test/mesh-graph-trace.integration.test.js.map +1 -1
- package/dist/test/mesh-smoke.runner.js +1 -3
- package/dist/test/mesh-smoke.runner.js.map +1 -1
- package/dist/test/mesh-store-delivery-listeners.integration.test.d.ts +2 -0
- package/dist/test/mesh-store-delivery-listeners.integration.test.d.ts.map +1 -0
- package/dist/test/mesh-store-delivery-listeners.integration.test.js +160 -0
- package/dist/test/mesh-store-delivery-listeners.integration.test.js.map +1 -0
- package/dist/test/mesh-store-orchestration.unit.test.js +11 -41
- package/dist/test/mesh-store-orchestration.unit.test.js.map +1 -1
- package/dist/test/peer-lifecycle.unit.test.js +0 -8
- package/dist/test/peer-lifecycle.unit.test.js.map +1 -1
- package/dist/test/principal-membership-trust.integration.test.js +8 -1
- package/dist/test/principal-membership-trust.integration.test.js.map +1 -1
- package/dist/test/room-join-by-bare-name.integration.test.js +1 -1
- package/dist/test/room-join-by-bare-name.integration.test.js.map +1 -1
- package/dist/test/test-transport.d.ts +1 -1
- package/dist/test/test-transport.d.ts.map +1 -1
- package/dist/test/test-transport.js +1 -1
- package/dist/test/test-transport.js.map +1 -1
- package/dist/test/wire-mesh-transport-hub.integration.test.d.ts +1 -1
- package/dist/test/wire-mesh-transport-hub.integration.test.js +96 -9
- package/dist/test/wire-mesh-transport-hub.integration.test.js.map +1 -1
- package/package.json +1 -1
- package/dist/core/coordinator-gateway.d.ts +0 -25
- package/dist/core/coordinator-gateway.d.ts.map +0 -1
- package/dist/core/coordinator-gateway.js +0 -34
- package/dist/core/coordinator-gateway.js.map +0 -1
- package/dist/test/coordinator-gateway.unit.test.d.ts +0 -2
- package/dist/test/coordinator-gateway.unit.test.d.ts.map +0 -1
- package/dist/test/coordinator-gateway.unit.test.js +0 -88
- package/dist/test/coordinator-gateway.unit.test.js.map +0 -1
- package/dist/test/gateway-forwarding.integration.test.d.ts +0 -9
- package/dist/test/gateway-forwarding.integration.test.d.ts.map +0 -1
- package/dist/test/gateway-forwarding.integration.test.js +0 -226
- package/dist/test/gateway-forwarding.integration.test.js.map +0 -1
- package/dist/test/hub-forwarding.unit.test.d.ts +0 -2
- package/dist/test/hub-forwarding.unit.test.d.ts.map +0 -1
- package/dist/test/hub-forwarding.unit.test.js +0 -102
- package/dist/test/hub-forwarding.unit.test.js.map +0 -1
- package/dist/test/hub-todevice-routing.integration.test.d.ts +0 -7
- package/dist/test/hub-todevice-routing.integration.test.d.ts.map +0 -1
- package/dist/test/hub-todevice-routing.integration.test.js +0 -121
- package/dist/test/hub-todevice-routing.integration.test.js.map +0 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a store puts on the relay hub, seen the way anyone connected to a public hub sees it (agent-comms#293): a bare client that trusts nobody and records every advert the hub broadcasts to it. A store holds a hub session only when its machine trusts someone and its agent is not a ghost, and advertises its agent, presence, hosted rooms and versions only when that agent is visible, so a store with nothing to say to another machine puts nothing on the hub, and a hidden agent only its device advert.
|
|
3
|
+
*/
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { MeshStore } from "../core/mesh-store.js";
|
|
6
|
+
import { AGENT_SELF_GOSSIP_KEY, HOSTED_ROOMS_GOSSIP_KEY, PRESENCE_GOSSIP_KEY, } from "../core/gossip-extensions.js";
|
|
7
|
+
import { observeHub, realHubOverWs, TeardownStack, waitForCondition, } from "./hub-helpers.js";
|
|
8
|
+
import { wireTestTransport } from "./test-transport.js";
|
|
9
|
+
/** A device-id is a 64-character lowercase hex SHA-256 digest. */
|
|
10
|
+
const DEVICE_ID_HEX_LENGTH = 64;
|
|
11
|
+
let nextPort = 24_300;
|
|
12
|
+
function freshPort() {
|
|
13
|
+
nextPort += 1;
|
|
14
|
+
return nextPort;
|
|
15
|
+
}
|
|
16
|
+
/** Short enough that several gossip rounds fit inside a test's own watch window. */
|
|
17
|
+
const FAST_GOSSIP_INTERVAL_MS = 50;
|
|
18
|
+
/** How long a store that must stay silent is watched for: many gossip rounds and several link polls. */
|
|
19
|
+
const SILENCE_WATCH_MS = 1500;
|
|
20
|
+
const sleep = async (ms) => new Promise((resolve) => {
|
|
21
|
+
setTimeout(resolve, ms);
|
|
22
|
+
});
|
|
23
|
+
const cleanups = new TeardownStack();
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
await cleanups.run();
|
|
26
|
+
});
|
|
27
|
+
/** A store on its own machine that dials hubUrl, with an agent of the given visibility registered when one is asked for. */
|
|
28
|
+
async function startStore(hubUrl, visibility) {
|
|
29
|
+
const store = new MeshStore({ coordinatorPort: freshPort(), hubUrl });
|
|
30
|
+
await wireTestTransport(store, {
|
|
31
|
+
presenceReadvertiseIntervalMs: FAST_GOSSIP_INTERVAL_MS,
|
|
32
|
+
});
|
|
33
|
+
await store.init();
|
|
34
|
+
cleanups.push(async () => store.shutdown());
|
|
35
|
+
if (visibility !== undefined) {
|
|
36
|
+
await store.registerAgent({
|
|
37
|
+
name: `${visibility}-agent`,
|
|
38
|
+
harness: "test",
|
|
39
|
+
cwd: `/test/${visibility}`,
|
|
40
|
+
pid: process.pid,
|
|
41
|
+
visibility,
|
|
42
|
+
tags: [],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return store;
|
|
46
|
+
}
|
|
47
|
+
/** Trusts an arbitrary remote device, which is all it takes for a store to have something to say to another machine. */
|
|
48
|
+
const SOME_REMOTE_DEVICE = "f".repeat(DEVICE_ID_HEX_LENGTH);
|
|
49
|
+
describe("what a store puts on the hub", () => {
|
|
50
|
+
it("puts nothing on the hub while its machine trusts nobody, not even its device id", async () => {
|
|
51
|
+
const hub = await realHubOverWs();
|
|
52
|
+
cleanups.push(hub.close);
|
|
53
|
+
const observer = await observeHub(hub.url);
|
|
54
|
+
cleanups.push(observer.close);
|
|
55
|
+
const store = await startStore(hub.url, "visible");
|
|
56
|
+
await sleep(SILENCE_WATCH_MS);
|
|
57
|
+
expect(observer.advertsFor(store.peerId)).toEqual([]);
|
|
58
|
+
expect(hub.connectionCount()).toBe(1);
|
|
59
|
+
});
|
|
60
|
+
it("puts nothing on the hub for a store with no agent yet, even when its machine trusts someone", async () => {
|
|
61
|
+
const hub = await realHubOverWs();
|
|
62
|
+
cleanups.push(hub.close);
|
|
63
|
+
const observer = await observeHub(hub.url);
|
|
64
|
+
cleanups.push(observer.close);
|
|
65
|
+
const store = await startStore(hub.url, undefined);
|
|
66
|
+
store.addTrustedGateway(SOME_REMOTE_DEVICE);
|
|
67
|
+
await sleep(SILENCE_WATCH_MS);
|
|
68
|
+
expect(observer.advertsFor(store.peerId)).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
it("puts nothing on the hub for a ghost agent, even when its machine trusts someone", async () => {
|
|
71
|
+
const hub = await realHubOverWs();
|
|
72
|
+
cleanups.push(hub.close);
|
|
73
|
+
const observer = await observeHub(hub.url);
|
|
74
|
+
cleanups.push(observer.close);
|
|
75
|
+
const store = await startStore(hub.url, "ghost");
|
|
76
|
+
store.addTrustedGateway(SOME_REMOTE_DEVICE);
|
|
77
|
+
await sleep(SILENCE_WATCH_MS);
|
|
78
|
+
expect(observer.advertsFor(store.peerId)).toEqual([]);
|
|
79
|
+
expect(hub.connectionCount()).toBe(1);
|
|
80
|
+
});
|
|
81
|
+
it("puts only its device advert on the hub for a hidden agent: no agent, presence or hosted rooms", async () => {
|
|
82
|
+
const hub = await realHubOverWs();
|
|
83
|
+
cleanups.push(hub.close);
|
|
84
|
+
const observer = await observeHub(hub.url);
|
|
85
|
+
cleanups.push(observer.close);
|
|
86
|
+
const store = await startStore(hub.url, "hidden");
|
|
87
|
+
store.addTrustedGateway(SOME_REMOTE_DEVICE);
|
|
88
|
+
await waitForCondition(() => observer.advertsFor(store.peerId).length > 0);
|
|
89
|
+
await sleep(SILENCE_WATCH_MS);
|
|
90
|
+
for (const advert of observer.advertsFor(store.peerId)) {
|
|
91
|
+
expect(advert[AGENT_SELF_GOSSIP_KEY]).toBeUndefined();
|
|
92
|
+
expect(advert[PRESENCE_GOSSIP_KEY]).toBeUndefined();
|
|
93
|
+
expect(advert[HOSTED_ROOMS_GOSSIP_KEY]).toBeUndefined();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
it("advertises a visible agent and its presence once its machine trusts someone", async () => {
|
|
97
|
+
const hub = await realHubOverWs();
|
|
98
|
+
cleanups.push(hub.close);
|
|
99
|
+
const observer = await observeHub(hub.url);
|
|
100
|
+
cleanups.push(observer.close);
|
|
101
|
+
const store = await startStore(hub.url, "visible");
|
|
102
|
+
store.addTrustedGateway(SOME_REMOTE_DEVICE);
|
|
103
|
+
await waitForCondition(() => observer
|
|
104
|
+
.advertsFor(store.peerId)
|
|
105
|
+
.some((advert) => advert[AGENT_SELF_GOSSIP_KEY] !== undefined &&
|
|
106
|
+
advert[PRESENCE_GOSSIP_KEY] !== undefined));
|
|
107
|
+
});
|
|
108
|
+
it("joins the hub as soon as trust is added and leaves it when the trust is withdrawn", async () => {
|
|
109
|
+
const hub = await realHubOverWs();
|
|
110
|
+
cleanups.push(hub.close);
|
|
111
|
+
const observer = await observeHub(hub.url);
|
|
112
|
+
cleanups.push(observer.close);
|
|
113
|
+
const store = await startStore(hub.url, "visible");
|
|
114
|
+
expect(hub.connectionCount()).toBe(1);
|
|
115
|
+
store.addTrustedGateway(SOME_REMOTE_DEVICE);
|
|
116
|
+
await waitForCondition(() => hub.connectionCount() === 2);
|
|
117
|
+
store.removeTrustedGateway(SOME_REMOTE_DEVICE);
|
|
118
|
+
await waitForCondition(() => hub.connectionCount() === 1);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=hub-exposure.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub-exposure.integration.test.js","sourceRoot":"","sources":["../../src/test/hub-exposure.integration.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,UAAU,EACV,aAAa,EACb,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,kEAAkE;AAClE,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,IAAI,QAAQ,GAAG,MAAM,CAAC;AACtB,SAAS,SAAS;IAChB,QAAQ,IAAI,CAAC,CAAC;IACd,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,oFAAoF;AACpF,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAEnC,wGAAwG;AACxG,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,MAAM,KAAK,GAAG,KAAK,EAAE,EAAU,EAAiB,EAAE,CAChD,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;IAC5B,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEL,MAAM,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;AAErC,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,4HAA4H;AAC5H,KAAK,UAAU,UAAU,CACvB,MAAc,EACd,UAAkC;IAElC,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,EAAE,eAAe,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACtE,MAAM,iBAAiB,CAAC,KAAK,EAAE;QAC7B,6BAA6B,EAAE,uBAAuB;KACvD,CAAC,CAAC;IACH,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IACnB,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5C,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,KAAK,CAAC,aAAa,CAAC;YACxB,IAAI,EAAE,GAAG,UAAU,QAAQ;YAC3B,OAAO,EAAE,MAAM;YACf,GAAG,EAAE,SAAS,UAAU,EAAE;YAC1B,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,UAAU;YACV,IAAI,EAAE,EAAE;SACT,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wHAAwH;AACxH,MAAM,kBAAkB,GAAG,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAE5D,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,iFAAiF,EAAE,KAAK,IAAI,EAAE;QAC/F,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE9B,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACnD,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAE9B,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6FAA6F,EAAE,KAAK,IAAI,EAAE;QAC3G,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE9B,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACnD,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAE9B,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iFAAiF,EAAE,KAAK,IAAI,EAAE;QAC/F,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE9B,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACjD,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAE9B,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+FAA+F,EAAE,KAAK,IAAI,EAAE;QAC7G,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE9B,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAClD,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;QAC5C,MAAM,gBAAgB,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC3E,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAE9B,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;YACtD,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;YACpD,MAAM,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6EAA6E,EAAE,KAAK,IAAI,EAAE;QAC3F,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE9B,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACnD,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;QAE5C,MAAM,gBAAgB,CAAC,GAAG,EAAE,CAC1B,QAAQ;aACL,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;aACxB,IAAI,CACH,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,qBAAqB,CAAC,KAAK,SAAS;YAC3C,MAAM,CAAC,mBAAmB,CAAC,KAAK,SAAS,CAC5C,CACJ,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;QACjG,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEtC,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;QAC5C,MAAM,gBAAgB,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC;QAE1D,KAAK,CAAC,oBAAoB,CAAC,kBAAkB,CAAC,CAAC;QAC/C,MAAM,gBAAgB,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import type { Frame } from "wire-mesh-core/generated/protocol";
|
|
1
|
+
import type { Frame, PeerAdvert } from "wire-mesh-core/generated/protocol";
|
|
2
2
|
export declare function cbor2ToBytes(frame: Frame): Uint8Array;
|
|
3
3
|
/** A real relay hub served over ws: each accepted socket is wrapped as a Connection for createRelayHub, exactly what the production Durable Object does (pre-hibernation shape). Also exposes the hub's own live-connection count, since a test asserting a client-side disconnect actually reached the far end needs an observable on the hub itself, not just the client. */
|
|
4
|
-
export declare function realHubOverWs(
|
|
4
|
+
export declare function realHubOverWs(options?: {
|
|
5
|
+
port?: number;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
port: number;
|
|
5
8
|
url: string;
|
|
6
9
|
connectionCount: () => number;
|
|
7
10
|
close: () => Promise<void>;
|
|
8
11
|
}>;
|
|
9
|
-
/** A ws:// URL nothing is listening on, for a test that
|
|
12
|
+
/** A ws:// URL nothing is listening on, for a test that hands a store or transport a hub to dial (a bridge entry point defaults to the production hub, which a hermetic test must not touch) and wants the dial to fail fast and leave hub.isConnected false. Binds an ephemeral TCP port and closes it immediately, so the returned URL names a real, momentarily-free port with nothing behind it: connectWsUrl's own "error" handler fires on the resulting ECONNREFUSED near-instantly, well under its own 10s CONNECT_TIMEOUT_MS, rather than genuinely waiting out a black-holed address. A store built directly with no hubUrl never dials at all, so it needs no URL. */
|
|
10
13
|
export declare function unreachableHubUrl(): Promise<string>;
|
|
11
14
|
/** Polls `condition` until it's true or `timeoutMs` elapses, rejecting on timeout -- the shared shape every hub integration test uses to wait out real async gossip/discovery/close propagation rather than asserting immediately after firing an action. */
|
|
12
15
|
export declare function waitForCondition(condition: () => boolean, timeoutMs?: number): Promise<void>;
|
|
@@ -19,4 +22,18 @@ export declare class TeardownStack {
|
|
|
19
22
|
/** Runs every registered step, latest first, and empties the stack so the next test starts clean. */
|
|
20
23
|
run(): Promise<void>;
|
|
21
24
|
}
|
|
25
|
+
/** Everything an untrusted client of the hub can read: every advert the hub has broadcast to it, and the connection count that includes itself. */
|
|
26
|
+
export interface HubObserver {
|
|
27
|
+
/** Every advert received in a gossip frame so far, in arrival order. */
|
|
28
|
+
adverts: () => readonly PeerAdvert[];
|
|
29
|
+
/** The adverts received for one device (hex), or none. */
|
|
30
|
+
advertsFor: (deviceHex: string) => readonly PeerAdvert[];
|
|
31
|
+
close: () => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Connects a bare client to the hub that never gossips, presents no identity and trusts nobody, and records every advert the hub broadcasts to it. This is the view of anyone connected to a public hub, which is what a test about what a store puts on the hub has to look at: asserting on another store's directory only shows what that store chose to accept.
|
|
35
|
+
*/
|
|
36
|
+
export declare function observeHub(url: string): Promise<HubObserver>;
|
|
37
|
+
/** A localhost TCP port nothing is listening on right now, for a test that must not collide with whatever else runs on the machine: a fixed port can already belong to another process, and a store that connects to one that accepts but never answers as a coordinator waits for it indefinitely. */
|
|
38
|
+
export declare function freeLocalPort(): Promise<number>;
|
|
22
39
|
//# sourceMappingURL=hub-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hub-helpers.d.ts","sourceRoot":"","sources":["../../src/test/hub-helpers.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"hub-helpers.d.ts","sourceRoot":"","sources":["../../src/test/hub-helpers.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAS3E,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,CAErD;AA0FD,+WAA+W;AAC/W,wBAAsB,aAAa,CAAC,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC,CAmCD;AAED,ipBAAipB;AACjpB,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAiBzD;AAED,6PAA6P;AAC7P,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,OAAO,EACxB,SAAS,SAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IAErD,IAAI,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIrC,qGAAqG;IAC/F,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAK3B;AAED,mJAAmJ;AACnJ,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,OAAO,EAAE,MAAM,SAAS,UAAU,EAAE,CAAC;IACrC,0DAA0D;IAC1D,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,SAAS,UAAU,EAAE,CAAC;IACzD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAclE;AAED,uSAAuS;AACvS,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAiBrD"}
|
package/dist/test/hub-helpers.js
CHANGED
|
@@ -6,6 +6,8 @@ import { cdeDecodeOptions, cdeEncodeOptions, decode, encode } from "cbor2";
|
|
|
6
6
|
import { frameSchema } from "wire-mesh-core/generated/protocol";
|
|
7
7
|
import { createRelayHub } from "wire-mesh-core/domain/relay-hub";
|
|
8
8
|
import { deriveDeviceId, verifyWithPublicKey, } from "wire-mesh-core/adapters/node-identity";
|
|
9
|
+
import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
|
|
10
|
+
import { connectWsUrl } from "../core/ws-dial.js";
|
|
9
11
|
const SHUTDOWN_GRACE_MS = 250;
|
|
10
12
|
const CLOSE_NORMAL = 1000; // RFC 6455 normal closure
|
|
11
13
|
const POLL_INTERVAL_MS = 25;
|
|
@@ -99,7 +101,7 @@ function wsConnection(socket) {
|
|
|
99
101
|
};
|
|
100
102
|
}
|
|
101
103
|
/** A real relay hub served over ws: each accepted socket is wrapped as a Connection for createRelayHub, exactly what the production Durable Object does (pre-hibernation shape). Also exposes the hub's own live-connection count, since a test asserting a client-side disconnect actually reached the far end needs an observable on the hub itself, not just the client. */
|
|
102
|
-
export async function realHubOverWs() {
|
|
104
|
+
export async function realHubOverWs(options) {
|
|
103
105
|
const http = createServer();
|
|
104
106
|
const wss = new WebSocketServer({ server: http });
|
|
105
107
|
const hub = createRelayHub({
|
|
@@ -110,7 +112,7 @@ export async function realHubOverWs() {
|
|
|
110
112
|
void hub.handleConnection(wsConnection(socket));
|
|
111
113
|
});
|
|
112
114
|
await new Promise((resolve) => {
|
|
113
|
-
http.listen(0, "127.0.0.1", () => {
|
|
115
|
+
http.listen(options?.port ?? 0, "127.0.0.1", () => {
|
|
114
116
|
resolve();
|
|
115
117
|
});
|
|
116
118
|
});
|
|
@@ -119,6 +121,7 @@ export async function realHubOverWs() {
|
|
|
119
121
|
throw new Error("expected a TCP listen address");
|
|
120
122
|
}
|
|
121
123
|
return {
|
|
124
|
+
port: address.port,
|
|
122
125
|
url: `ws://127.0.0.1:${String(address.port)}/`,
|
|
123
126
|
connectionCount: () => wss.clients.size,
|
|
124
127
|
close: async () => new Promise((resolve) => {
|
|
@@ -133,7 +136,7 @@ export async function realHubOverWs() {
|
|
|
133
136
|
}),
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
|
-
/** A ws:// URL nothing is listening on, for a test that
|
|
139
|
+
/** A ws:// URL nothing is listening on, for a test that hands a store or transport a hub to dial (a bridge entry point defaults to the production hub, which a hermetic test must not touch) and wants the dial to fail fast and leave hub.isConnected false. Binds an ephemeral TCP port and closes it immediately, so the returned URL names a real, momentarily-free port with nothing behind it: connectWsUrl's own "error" handler fires on the resulting ECONNREFUSED near-instantly, well under its own 10s CONNECT_TIMEOUT_MS, rather than genuinely waiting out a black-holed address. A store built directly with no hubUrl never dials at all, so it needs no URL. */
|
|
137
140
|
export async function unreachableHubUrl() {
|
|
138
141
|
return new Promise((resolve, reject) => {
|
|
139
142
|
const probe = net.createServer();
|
|
@@ -185,4 +188,41 @@ export class TeardownStack {
|
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Connects a bare client to the hub that never gossips, presents no identity and trusts nobody, and records every advert the hub broadcasts to it. This is the view of anyone connected to a public hub, which is what a test about what a store puts on the hub has to look at: asserting on another store's directory only shows what that store chose to accept.
|
|
193
|
+
*/
|
|
194
|
+
export async function observeHub(url) {
|
|
195
|
+
const connection = await connectWsUrl(url);
|
|
196
|
+
const received = [];
|
|
197
|
+
void (async () => {
|
|
198
|
+
for await (const frame of connection.receive()) {
|
|
199
|
+
if (frame.type === "gossip")
|
|
200
|
+
received.push(...frame.peers);
|
|
201
|
+
}
|
|
202
|
+
})();
|
|
203
|
+
return {
|
|
204
|
+
adverts: () => received,
|
|
205
|
+
advertsFor: (deviceHex) => received.filter((advert) => deviceIdToHex(advert.device) === deviceHex),
|
|
206
|
+
close: async () => connection.close(),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/** A localhost TCP port nothing is listening on right now, for a test that must not collide with whatever else runs on the machine: a fixed port can already belong to another process, and a store that connects to one that accepts but never answers as a coordinator waits for it indefinitely. */
|
|
210
|
+
export async function freeLocalPort() {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const probe = net.createServer();
|
|
213
|
+
probe.listen(0, "127.0.0.1", () => {
|
|
214
|
+
const address = probe.address();
|
|
215
|
+
if (address === null || typeof address === "string") {
|
|
216
|
+
probe.close();
|
|
217
|
+
reject(new Error("expected a TCP listen address"));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const { port } = address;
|
|
221
|
+
probe.close(() => {
|
|
222
|
+
resolve(port);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
probe.on("error", reject);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
188
228
|
//# sourceMappingURL=hub-helpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hub-helpers.js","sourceRoot":"","sources":["../../src/test/hub-helpers.ts"],"names":[],"mappings":"AAAA,2UAA2U;AAE3U,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AACtD,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAE,eAAe,EAA8B,MAAM,IAAI,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EACL,cAAc,EACd,mBAAmB,GACpB,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"hub-helpers.js","sourceRoot":"","sources":["../../src/test/hub-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub-link.unit.test.d.ts","sourceRoot":"","sources":["../../src/test/hub-link.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for HubLink (agent-comms#293): the per-store loop that holds a hub session, against a fake session so the dial, the loss of a session and the backoff between dials are all driven directly.
|
|
3
|
+
*/
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { HUB_LINK_POLL_INTERVAL_MS, HUB_RECONNECT_INITIAL_DELAY_MS, HUB_RECONNECT_MAX_DELAY_MS, HubLink, } from "../core/hub-link.js";
|
|
6
|
+
const HUB_URL = "wss://hub.example.com/";
|
|
7
|
+
/** A hub session whose dials succeed or fail as scripted and whose live session ends only when the test says so. */
|
|
8
|
+
class FakeHub {
|
|
9
|
+
dials = [];
|
|
10
|
+
disconnects = 0;
|
|
11
|
+
failuresLeft = 0;
|
|
12
|
+
live = false;
|
|
13
|
+
endSession;
|
|
14
|
+
get isConnected() {
|
|
15
|
+
return this.live;
|
|
16
|
+
}
|
|
17
|
+
/** Makes the next `count` dials fail before one succeeds. */
|
|
18
|
+
failNextDials(count) {
|
|
19
|
+
this.failuresLeft = count;
|
|
20
|
+
}
|
|
21
|
+
async connect(url) {
|
|
22
|
+
this.dials.push(url);
|
|
23
|
+
if (this.failuresLeft > 0) {
|
|
24
|
+
this.failuresLeft -= 1;
|
|
25
|
+
throw new Error("hub unreachable");
|
|
26
|
+
}
|
|
27
|
+
this.live = true;
|
|
28
|
+
}
|
|
29
|
+
async disconnect() {
|
|
30
|
+
this.disconnects += 1;
|
|
31
|
+
this.dropSession();
|
|
32
|
+
}
|
|
33
|
+
async whenDisconnected() {
|
|
34
|
+
if (!this.live)
|
|
35
|
+
return;
|
|
36
|
+
await new Promise((resolve) => {
|
|
37
|
+
this.endSession = resolve;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** The hub closing the session from its end. */
|
|
41
|
+
dropSession() {
|
|
42
|
+
this.live = false;
|
|
43
|
+
this.endSession?.();
|
|
44
|
+
this.endSession = undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function newLink(hub, shouldConnect = () => true) {
|
|
48
|
+
let connectedCount = 0;
|
|
49
|
+
const errors = [];
|
|
50
|
+
const link = new HubLink({
|
|
51
|
+
hub,
|
|
52
|
+
url: HUB_URL,
|
|
53
|
+
shouldConnect,
|
|
54
|
+
onConnected: () => {
|
|
55
|
+
connectedCount += 1;
|
|
56
|
+
},
|
|
57
|
+
onError: (error) => {
|
|
58
|
+
errors.push(error);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
return { link, connected: () => connectedCount, errors };
|
|
62
|
+
}
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
vi.useFakeTimers();
|
|
65
|
+
});
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
vi.useRealTimers();
|
|
68
|
+
});
|
|
69
|
+
describe("HubLink", () => {
|
|
70
|
+
it("dials the configured hub in the background and reports the connection", async () => {
|
|
71
|
+
const hub = new FakeHub();
|
|
72
|
+
const { link, connected } = newLink(hub);
|
|
73
|
+
link.start();
|
|
74
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
75
|
+
expect(hub.dials).toEqual([HUB_URL]);
|
|
76
|
+
expect(connected()).toBe(1);
|
|
77
|
+
await link.stop();
|
|
78
|
+
});
|
|
79
|
+
it("does nothing on a second start while already running", async () => {
|
|
80
|
+
const hub = new FakeHub();
|
|
81
|
+
const { link } = newLink(hub);
|
|
82
|
+
link.start();
|
|
83
|
+
link.start();
|
|
84
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
85
|
+
expect(hub.dials).toHaveLength(1);
|
|
86
|
+
await link.stop();
|
|
87
|
+
});
|
|
88
|
+
it("dials again after the hub ends the session, once the initial delay has passed", async () => {
|
|
89
|
+
const hub = new FakeHub();
|
|
90
|
+
const { link, connected } = newLink(hub);
|
|
91
|
+
link.start();
|
|
92
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
93
|
+
hub.dropSession();
|
|
94
|
+
await vi.advanceTimersByTimeAsync(HUB_RECONNECT_INITIAL_DELAY_MS - 1);
|
|
95
|
+
expect(hub.dials).toHaveLength(1);
|
|
96
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
97
|
+
expect(hub.dials).toHaveLength(2);
|
|
98
|
+
expect(connected()).toBe(2);
|
|
99
|
+
await link.stop();
|
|
100
|
+
});
|
|
101
|
+
it("reports a failed dial and retries, doubling the delay each time up to the ceiling", async () => {
|
|
102
|
+
const hub = new FakeHub();
|
|
103
|
+
const failures = 8;
|
|
104
|
+
hub.failNextDials(failures);
|
|
105
|
+
const { link, errors } = newLink(hub);
|
|
106
|
+
link.start();
|
|
107
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
108
|
+
expect(errors).toHaveLength(1);
|
|
109
|
+
let delayMs = HUB_RECONNECT_INITIAL_DELAY_MS;
|
|
110
|
+
for (let attempt = 1; attempt < failures; attempt += 1) {
|
|
111
|
+
await vi.advanceTimersByTimeAsync(delayMs - 1);
|
|
112
|
+
expect(hub.dials).toHaveLength(attempt);
|
|
113
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
114
|
+
expect(hub.dials).toHaveLength(attempt + 1);
|
|
115
|
+
delayMs = Math.min(delayMs * 2, HUB_RECONNECT_MAX_DELAY_MS);
|
|
116
|
+
}
|
|
117
|
+
expect(errors).toHaveLength(failures);
|
|
118
|
+
expect(delayMs).toBe(HUB_RECONNECT_MAX_DELAY_MS);
|
|
119
|
+
await link.stop();
|
|
120
|
+
});
|
|
121
|
+
it("starts the backoff over after a session has been established", async () => {
|
|
122
|
+
const hub = new FakeHub();
|
|
123
|
+
hub.failNextDials(2);
|
|
124
|
+
const { link } = newLink(hub);
|
|
125
|
+
link.start();
|
|
126
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
127
|
+
await vi.advanceTimersByTimeAsync(HUB_RECONNECT_INITIAL_DELAY_MS);
|
|
128
|
+
await vi.advanceTimersByTimeAsync(HUB_RECONNECT_INITIAL_DELAY_MS * 2);
|
|
129
|
+
expect(hub.isConnected).toBe(true);
|
|
130
|
+
const dialsBeforeDrop = hub.dials.length;
|
|
131
|
+
hub.dropSession();
|
|
132
|
+
await vi.advanceTimersByTimeAsync(HUB_RECONNECT_INITIAL_DELAY_MS);
|
|
133
|
+
expect(hub.dials).toHaveLength(dialsBeforeDrop + 1);
|
|
134
|
+
await link.stop();
|
|
135
|
+
});
|
|
136
|
+
it("stops without waiting out a pending delay, and does not dial again", async () => {
|
|
137
|
+
const hub = new FakeHub();
|
|
138
|
+
hub.failNextDials(1);
|
|
139
|
+
const { link } = newLink(hub);
|
|
140
|
+
link.start();
|
|
141
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
142
|
+
await link.stop();
|
|
143
|
+
await vi.advanceTimersByTimeAsync(HUB_RECONNECT_MAX_DELAY_MS);
|
|
144
|
+
expect(hub.dials).toHaveLength(1);
|
|
145
|
+
expect(hub.disconnects).toBe(1);
|
|
146
|
+
});
|
|
147
|
+
it("drops a live session when stopped, without treating that as a loss to redial", async () => {
|
|
148
|
+
const hub = new FakeHub();
|
|
149
|
+
const { link } = newLink(hub);
|
|
150
|
+
link.start();
|
|
151
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
152
|
+
await link.stop();
|
|
153
|
+
await vi.advanceTimersByTimeAsync(HUB_RECONNECT_MAX_DELAY_MS);
|
|
154
|
+
expect(hub.isConnected).toBe(false);
|
|
155
|
+
expect(hub.dials).toHaveLength(1);
|
|
156
|
+
});
|
|
157
|
+
it("does not dial while the store does not want a session, and dials within a poll once it does", async () => {
|
|
158
|
+
const hub = new FakeHub();
|
|
159
|
+
let wanted = false;
|
|
160
|
+
const { link } = newLink(hub, () => wanted);
|
|
161
|
+
link.start();
|
|
162
|
+
await vi.advanceTimersByTimeAsync(HUB_LINK_POLL_INTERVAL_MS * 2);
|
|
163
|
+
expect(hub.dials).toHaveLength(0);
|
|
164
|
+
wanted = true;
|
|
165
|
+
await vi.advanceTimersByTimeAsync(HUB_LINK_POLL_INTERVAL_MS);
|
|
166
|
+
expect(hub.dials).toHaveLength(1);
|
|
167
|
+
await link.stop();
|
|
168
|
+
});
|
|
169
|
+
it("dials at once on reconsider instead of waiting out the poll", async () => {
|
|
170
|
+
const hub = new FakeHub();
|
|
171
|
+
let wanted = false;
|
|
172
|
+
const { link } = newLink(hub, () => wanted);
|
|
173
|
+
link.start();
|
|
174
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
175
|
+
wanted = true;
|
|
176
|
+
link.reconsider();
|
|
177
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
178
|
+
expect(hub.dials).toHaveLength(1);
|
|
179
|
+
expect(hub.isConnected).toBe(true);
|
|
180
|
+
await link.stop();
|
|
181
|
+
});
|
|
182
|
+
it("drops a live session when the store stops wanting one, and does not redial while it stays unwanted", async () => {
|
|
183
|
+
const hub = new FakeHub();
|
|
184
|
+
let wanted = true;
|
|
185
|
+
const { link } = newLink(hub, () => wanted);
|
|
186
|
+
link.start();
|
|
187
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
188
|
+
expect(hub.isConnected).toBe(true);
|
|
189
|
+
wanted = false;
|
|
190
|
+
link.reconsider();
|
|
191
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
192
|
+
expect(hub.isConnected).toBe(false);
|
|
193
|
+
expect(hub.disconnects).toBe(1);
|
|
194
|
+
await vi.advanceTimersByTimeAsync(HUB_RECONNECT_MAX_DELAY_MS);
|
|
195
|
+
expect(hub.dials).toHaveLength(1);
|
|
196
|
+
await link.stop();
|
|
197
|
+
});
|
|
198
|
+
it("notices a session it no longer wants at the next poll when nobody calls reconsider", async () => {
|
|
199
|
+
const hub = new FakeHub();
|
|
200
|
+
let wanted = true;
|
|
201
|
+
const { link } = newLink(hub, () => wanted);
|
|
202
|
+
link.start();
|
|
203
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
204
|
+
wanted = false;
|
|
205
|
+
await vi.advanceTimersByTimeAsync(HUB_LINK_POLL_INTERVAL_MS);
|
|
206
|
+
expect(hub.isConnected).toBe(false);
|
|
207
|
+
await link.stop();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
//# sourceMappingURL=hub-link.unit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub-link.unit.test.js","sourceRoot":"","sources":["../../src/test/hub-link.unit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for every store being its own peer on the relay hub (agent-comms#293), the cross-machine leg of the mesh epic (#153). Two independent local meshes ("machine A" and "machine B") on a shared real relay hub (createRelayHub, the same domain logic the production mesh.exadev.io Durable Object runs, served over local WebSockets via hub-helpers.ts): neither mesh is ever directly connected to the other, so anything either side learns about the other arrived via the hub.
|
|
3
|
+
*
|
|
4
|
+
* Machine A has a coordinator (a1) and an ordinary local peer (a2). The point of the tests is that a2, which is not the coordinator, is reachable and can reach out with its own identity, exactly as a1 is: no store depends on the coordinator to speak to another machine.
|
|
5
|
+
*
|
|
6
|
+
* Trust is deny-all by default (agent-comms#156), so every test trusts the specific device ids it needs. See gateway-trust.integration.test.ts for the deny-by-default behaviour itself.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=hub-peer-per-store.integration.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub-peer-per-store.integration.test.d.ts","sourceRoot":"","sources":["../../src/test/hub-peer-per-store.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|