From e8f0d3198dbbbc9dd14e4259d9ccc66f99dbfa5a Mon Sep 17 00:00:00 2001 From: mushuiyu886 Date: Thu, 23 Jul 2026 05:59:12 +0800 Subject: [PATCH] fix(nostr): normal shutdown no longer reports relay errors (#111905) --- .../src/nostr-relay-subscription.test.ts | 37 +++++++++++++++++-- .../nostr/src/nostr-relay-subscription.ts | 7 +++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/extensions/nostr/src/nostr-relay-subscription.test.ts b/extensions/nostr/src/nostr-relay-subscription.test.ts index 2a7802eea4e3..0a3e6991d36e 100644 --- a/extensions/nostr/src/nostr-relay-subscription.test.ts +++ b/extensions/nostr/src/nostr-relay-subscription.test.ts @@ -6,25 +6,36 @@ type RelayHandlers = Parameters[2]; function createHarness() { const handlers: RelayHandlers[] = []; + const abortController = new AbortController(); const subscribeMany = vi.fn( (_relays: string[], _filter: unknown, nextHandlers: RelayHandlers) => { handlers.push(nextHandlers); - return { close: vi.fn() }; + nextHandlers.abort?.addEventListener( + "abort", + () => nextHandlers.onclose?.([String(nextHandlers.abort?.reason ?? "aborted")]), + { once: true }, + ); + return { + close: vi.fn(async (reason?: string) => { + nextHandlers.onclose?.([reason ?? "closed by caller"]); + }), + }; }, ); const onBackfillComplete = vi.fn<(relays: string[]) => void>(); + const onClose = vi.fn<(relay: string, reasons: string[]) => void>(); const group = createNostrRelaySubscriptionGroup({ pool: { subscribeMany } as unknown as SimplePool, relays: ["wss://one.example", "wss://two.example"], filter: { kinds: [4] }, - abort: new AbortController().signal, + abort: abortController.signal, onEvent: (_event: Event) => {}, onBackfillComplete, - onClose: () => {}, + onClose, eoseConfirmDeadlineMs: 10, }); group.start(); - return { group, handlers, onBackfillComplete, subscribeMany }; + return { abortController, group, handlers, onBackfillComplete, onClose, subscribeMany }; } describe("Nostr relay subscriptions", () => { @@ -66,4 +77,22 @@ describe("Nostr relay subscriptions", () => { expect(onBackfillComplete).not.toHaveBeenCalled(); await group.close("test complete"); }); + + it("does not report locally requested closes as relay failures", async () => { + const { abortController, group, onClose } = createHarness(); + + abortController.abort("closed by caller"); + await group.close("closed by caller"); + + expect(onClose).not.toHaveBeenCalled(); + }); + + it("still reports closes initiated by the relay", async () => { + const { group, handlers, onClose } = createHarness(); + + handlers[0]?.onclose?.(["relay unavailable"]); + + expect(onClose).toHaveBeenCalledWith("wss://one.example", ["relay unavailable"]); + await group.close("test complete"); + }); }); diff --git a/extensions/nostr/src/nostr-relay-subscription.ts b/extensions/nostr/src/nostr-relay-subscription.ts index d225aa2d9d1a..34ad0e8a794c 100644 --- a/extensions/nostr/src/nostr-relay-subscription.ts +++ b/extensions/nostr/src/nostr-relay-subscription.ts @@ -18,6 +18,7 @@ export function createNostrRelaySubscriptionGroup(options: { const relays = [...new Set(options.relays)]; const subscriptions: Array> = []; const deadlineTimers = new Set>(); + let locallyClosing = false; const backfillStatus = new Map( relays.map((relay): [string, BackfillStatus] => [relay, "pending"]), ); @@ -74,7 +75,9 @@ export function createNostrRelaySubscriptionGroup(options: { clearTimeout(deadlineTimer); deadlineTimers.delete(deadlineTimer); settleBackfill(relay, "incomplete"); - options.onClose(relay, reasons); + if (!locallyClosing && !options.abort.aborted) { + options.onClose(relay, reasons); + } }, // Own earlier deadline marks synthetic library EOSE as incomplete. maxWait: confirmDeadlineMs + LIBRARY_EOSE_TIMEOUT_MARGIN_MS, @@ -84,6 +87,8 @@ export function createNostrRelaySubscriptionGroup(options: { } }, close: async (reason: string): Promise => { + // nostr-tools reports caller-requested closes through the same callback as relay failures. + locallyClosing = true; clearDeadlines(); await Promise.all(subscriptions.map(async (subscription) => subscription.close(reason))); },