Files
buzz/desktop/src/shared/api/relayInboundBuffer.test.mjs
Tyler 6ea7a2b221 fix(desktop): preserve early relay auth challenges (#3320)
## 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>
2026-08-18 15:09:58 -07:00

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/);
});