mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
fix(desktop): report authenticated relay recovery (#3812)
## Summary - report the relay as connected immediately after socket open and successful AUTH - keep rate-limited subscription replay, the connect promise, and reconnect listeners unchanged - cover authenticated reconnect while replay is held behind the shared rate-limit gate ## Why After WARP recovery, the socket could reopen and authenticate successfully while subscription replay waited behind the existing rate-limit gate. `connect()` kept `ConnectionState` at `reconnecting` during that intentional delay, so the desktop displayed “Can’t reach the relay” despite authenticated traffic already flowing. This is separate from #3774: that fix keeps routine operations from bypassing scheduled reconnect backoff. This patch preserves those protections and only corrects the authenticated transport-state boundary. ## Failure semantics If replay fails after the early `connected` transition, the existing `replayLiveSubscriptions()` catch calls `resetConnection()`, closes the socket, returns state to `reconnecting`, and schedules recovery. Operation waiters and reconnect notifications still do not complete until replay succeeds. ## Validation At commit `c8a4308e1079f4f9e6a72f0f0bfba280fe822ec0` with a clean working tree: - `pnpm --dir desktop typecheck` - `pnpm --dir desktop test` — 3,847 passed - `pnpm --dir desktop check` — passed; two pre-existing informational template-literal notices - `pnpm --dir desktop exec playwright test tests/e2e/relay-reconnect.spec.ts` — 8 passed - regression test proven red before the production ordering change (`reconnecting` after 3 seconds) and green after it Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -566,8 +566,8 @@ export class RelayClient {
|
||||
this.reconnectDelayMs = RECONNECT_BASE_DELAY_MS;
|
||||
}, BACKOFF_RESET_STABLE_MS);
|
||||
|
||||
await this.replayLiveSubscriptions();
|
||||
this.connectionStateEmitter.set("connected");
|
||||
await this.replayLiveSubscriptions();
|
||||
this.stallWatchdog.start();
|
||||
this.emitReconnectIfNeeded();
|
||||
} catch (error) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "./e2eBridgeCustomHarnesses.ts";
|
||||
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { activateRateLimit } from "@/shared/api/relayRateLimitGate";
|
||||
import type { ConnectionState } from "@/shared/api/relayClientShared";
|
||||
import type { ChannelTemplate, RelayEvent } from "@/shared/api/types";
|
||||
import { getMarkdownParseCount } from "@/shared/ui/markdown/nodeCache";
|
||||
@@ -1115,6 +1116,7 @@ declare global {
|
||||
unavailable: boolean,
|
||||
) => void;
|
||||
__BUZZ_E2E_GET_WEBSOCKET_CONNECT_ATTEMPTS__?: () => number[];
|
||||
__BUZZ_E2E_ACTIVATE_RELAY_RATE_LIMIT__?: (seconds: number) => void;
|
||||
__BUZZ_E2E_RESET_WEBSOCKET_CONNECT_ATTEMPTS__?: () => void;
|
||||
__BUZZ_E2E_SET_MESH__?: (mesh: {
|
||||
admitted?: boolean;
|
||||
@@ -9706,6 +9708,9 @@ export function maybeInstallE2eTauriMocks() {
|
||||
window.__BUZZ_E2E_GET_WEBSOCKET_CONNECT_ATTEMPTS__ = () => [
|
||||
...relayWebsocketConnectAttemptStarts,
|
||||
];
|
||||
window.__BUZZ_E2E_ACTIVATE_RELAY_RATE_LIMIT__ = (seconds) => {
|
||||
activateRateLimit(seconds);
|
||||
};
|
||||
window.__BUZZ_E2E_RESET_WEBSOCKET_CONNECT_ATTEMPTS__ = () => {
|
||||
relayWebsocketConnectAttemptStarts.length = 0;
|
||||
};
|
||||
|
||||
@@ -62,6 +62,19 @@ async function setMockWebsocketUnavailable(
|
||||
}, unavailable);
|
||||
}
|
||||
|
||||
async function activateRelayRateLimit(
|
||||
page: import("@playwright/test").Page,
|
||||
seconds: number,
|
||||
) {
|
||||
await page.evaluate((duration) => {
|
||||
const activate = window.__BUZZ_E2E_ACTIVATE_RELAY_RATE_LIMIT__;
|
||||
if (!activate) {
|
||||
throw new Error("E2E relay rate-limit seam is not installed.");
|
||||
}
|
||||
activate(duration);
|
||||
}, seconds);
|
||||
}
|
||||
|
||||
async function getMockWebsocketConnectAttempts(
|
||||
page: import("@playwright/test").Page,
|
||||
) {
|
||||
@@ -195,6 +208,28 @@ test("routine traffic cannot bypass outage backoff and recovery stays automatic"
|
||||
);
|
||||
});
|
||||
|
||||
test("authenticated reconnect reports connected while replay is rate-limited", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("channel-general")).toBeVisible();
|
||||
|
||||
await activateRelayRateLimit(page, 5);
|
||||
await disconnectMockWebsockets(page);
|
||||
|
||||
// Replay remains intentionally blocked behind admission control, but socket
|
||||
// open + successful AUTH is already a healthy connection. The UI must not
|
||||
// claim the relay is unreachable for the rest of the gate window.
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
page.evaluate(() => window.__BUZZ_E2E_GET_RELAY_CONNECTION_STATE__?.()),
|
||||
{ timeout: 3_000 },
|
||||
)
|
||||
.toBe("connected");
|
||||
await expect(page.getByTestId("sidebar-relay-unreachable")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("service restart close resets accumulated backoff", async ({ page }) => {
|
||||
await installMockBridge(page, {
|
||||
websocketConnectErrors: ["down 1", "down 2", "down 3"],
|
||||
|
||||
Reference in New Issue
Block a user