mirror of
https://github.com/block/buzz.git
synced 2026-10-28 04:51:40 +00:00
## Summary - buffer native WebSocket frames until `plugin:websocket|connect` returns the socket ID and the NIP-42 auth waiter is installed - drain those frames in order before normal inbound handling begins - add a deterministic E2E seam and regression for an AUTH challenge delivered before connect resolves ## Root cause The native WebSocket task starts forwarding relay frames before the connect command returns its socket ID. Buzz relay sends the NIP-42 AUTH challenge immediately on connection, so Desktop could process that challenge while `wsId` and `authRequest` were still unset. The challenge was discarded, the cold connection failed authentication, and the first plain-text send failed. Channel history still appeared because it loads through the Tauri channel-window command rather than this frontend WebSocket. ## Verification - deterministic regression is red before the fix (`connecting` after 5s) and green after it - `pnpm test`: 3,721 passed - `pnpm build:e2e`: passed - focused early-AUTH E2E: passed - existing failed-initial-dial E2E: passed - `pnpm typecheck`: passed - Biome + file-size pre-commit checks: passed The complete 7-test relay-reconnect file passed once before the final line-count-only compaction. After that compaction, two full-file reruns each had the same unrelated startup-seam flake in the existing initial-dial test; both the new test and that existing test pass independently at the committed HEAD. --------- Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
47 lines
1.3 KiB
JavaScript
47 lines
1.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
createRelayInboundBuffer,
|
|
MAX_PENDING_RELAY_FRAMES,
|
|
} from "./relayInboundBuffer.ts";
|
|
|
|
test("drains queued frames in order, including frames received during drain", async () => {
|
|
const handled = [];
|
|
let releaseFirst;
|
|
const firstBlocked = new Promise((resolve) => {
|
|
releaseFirst = resolve;
|
|
});
|
|
const inbound = createRelayInboundBuffer(async (message) => {
|
|
handled.push(message);
|
|
if (message === "first") await firstBlocked;
|
|
}, assert.fail);
|
|
|
|
inbound.receive("first");
|
|
const drain = inbound.drain();
|
|
inbound.receive("second");
|
|
releaseFirst();
|
|
await drain;
|
|
inbound.receive("live");
|
|
await new Promise((resolve) => setTimeout(resolve));
|
|
|
|
assert.deepEqual(handled, ["first", "second", "live"]);
|
|
});
|
|
|
|
test("rejects, resets, and stops accepting frames when the cap is exceeded", async () => {
|
|
let overflowError;
|
|
const inbound = createRelayInboundBuffer(
|
|
async () => {},
|
|
(error) => {
|
|
overflowError = error;
|
|
},
|
|
);
|
|
for (let i = 0; i < MAX_PENDING_RELAY_FRAMES + 1; i++) inbound.receive(i);
|
|
|
|
await assert.rejects(
|
|
inbound.overflow,
|
|
/Relay sent too many frames while connecting/,
|
|
);
|
|
assert.match(overflowError.message, /too many frames/);
|
|
});
|