summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/harness
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-17 14:32:46 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-17 14:32:46 +0200
commit28be8579995aef6df3e87b0f293a4fdd4aa4b072 (patch)
treea7ce6ea4d5cd8d2a2840496bc8c7017ffc3f7038 /packages/meshbay-hub/tests/harness
parent621305ab0363a43edc62a69664de7254f03514e7 (diff)
downloadmeshbay-28be8579995aef6df3e87b0f293a4fdd4aa4b072.tar.gz
fix(spa): a film playing waits for the session after a reconnect
Stream credit sent before the new handshake was refused, and the node's "Handshake required" replaced the film. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/harness')
-rw-r--r--packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs29
1 files changed, 24 insertions, 5 deletions
diff --git a/packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs b/packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs
index 0f1cd979..606598da 100644
--- a/packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs
+++ b/packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs
@@ -30,6 +30,7 @@ const {
rttMs = 300, // one request → its reply, during the handshake
lateCloseMs = 0, // >0: the replaced channel's close event fires this late
runUntil = 300000,
+ stream = false, // a film playing instead of a download (see below)
} = JSON.parse(process.argv[4] || '{}');
for (const level of ['log', 'warn', 'error', 'info', 'debug']) {
@@ -106,6 +107,7 @@ globalThis.fetch = async (url) => ({
const OPENING = new Set(['handshake', 'handshake_response', 'keypair_bundle_fetch',
'gek_bundle_fetch', 'join_request', 'device_add_request', 'device_hello']);
let refusedBeforeHandshake = 0;
+const refusedTypes = new Set();
let served = 0;
const pcs = [];
@@ -127,6 +129,7 @@ class NodeSession {
const req_id = msg.req_id;
if (!this.authed && !OPENING.has(msg.type)) {
refusedBeforeHandshake += 1;
+ refusedTypes.add(msg.type);
return this.reply({ type: 'error', detail: 'Handshake required', req_id }, 1);
}
switch (msg.type) {
@@ -210,10 +213,26 @@ main.addReconnectListener(() => { reconnected += 1; });
const written = [];
const writable = { write: async (bytes) => { written.push(bytes[0]); } };
-main.connect('node', 'token', 'group', new Uint8Array(32), identity, null, 'u', 'uid',
- undefined, undefined, '', 'pk')
- .then(() => pipelinedDownload(main, 'gek', 'f'.repeat(64), chunks, null, writable, null, ''))
- .then(() => { outcome = 'done'; }, (err) => { outcome = `failed: ${err.message}`; });
+// A film playing (`stream`): the player goes on granting the node room for
+// segments as it plays from its buffer, and on telling it what it sees, through
+// the connection loss and the reconnect, as video-player.js does. A refusal of
+// either is a node error the player shows in place of the film.
+const opened = main.connect('node', 'token', 'group', new Uint8Array(32), identity, null,
+ 'u', 'uid', undefined, undefined, '', 'pk');
+if (stream) {
+ opened.then(() => {
+ main.requestStream('f'.repeat(64));
+ setInterval(() => {
+ main.grantStreamCredit(1);
+ main.sendStreamDiag({ buffered: 1 });
+ }, 50);
+ outcome = 'done';
+ }, (err) => { outcome = `failed: ${err.message}`; });
+} else {
+ opened
+ .then(() => pipelinedDownload(main, 'gek', 'f'.repeat(64), chunks, null, writable, null, ''))
+ .then(() => { outcome = 'done'; }, (err) => { outcome = `failed: ${err.message}`; });
+}
await run(hangAt);
pcs[0].channel.session.hung = true;
@@ -223,7 +242,7 @@ pcs[0].onconnectionstatechange();
await run(runUntil);
process.stdout.write(JSON.stringify({
- outcome, reconnected, refusedBeforeHandshake, served,
+ outcome, reconnected, refusedBeforeHandshake, refusedTypes: [...refusedTypes], served,
written: written.length,
inOrder: written.every((b, i) => b === (i & 255)),
connections: pcs.length,