From d4824f9a8f00a09c3049cc863cea56f7f0aa08e8 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 13 Apr 2026 16:47:52 +0100 Subject: [PATCH] fix(nostr): retry inbound events after handler failures --- .../nostr/src/nostr-bus.inbound.test.ts | 26 +++++++++++++++++++ extensions/nostr/src/nostr-bus.ts | 8 +++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/extensions/nostr/src/nostr-bus.inbound.test.ts b/extensions/nostr/src/nostr-bus.inbound.test.ts index 0f5f6b274ad..55a118db68c 100644 --- a/extensions/nostr/src/nostr-bus.inbound.test.ts +++ b/extensions/nostr/src/nostr-bus.inbound.test.ts @@ -235,6 +235,32 @@ describe("startNostrBus inbound guards", () => { bus.close(); }); + it("retries a replayed event after the message handler fails", async () => { + const onMessage = vi + .fn<(sender: string, plaintext: string) => Promise>() + .mockRejectedValueOnce(new Error("boom")) + .mockResolvedValueOnce(undefined); + const bus = await startNostrBus({ + privateKey: TEST_HEX_PRIVATE_KEY, + onMessage, + onMetric: () => {}, + }); + + const event = createEvent({ + id: "retry-after-handler-failure", + }); + + await emitEvent(event); + await emitEvent(event); + + expect(mockState.verifyEvent).toHaveBeenCalledTimes(2); + expect(mockState.decrypt).toHaveBeenCalledTimes(2); + expect(onMessage).toHaveBeenCalledTimes(2); + expect(bus.getMetrics().eventsProcessed).toBe(1); + + bus.close(); + }); + it("does not rate limit an allowed sender while another authorization is still pending", async () => { const onMessage = vi.fn(async () => {}); let resolveBlocked: ((value: "block") => void) | undefined; diff --git a/extensions/nostr/src/nostr-bus.ts b/extensions/nostr/src/nostr-bus.ts index 6a10c6cb314..41a8fdbc1f3 100644 --- a/extensions/nostr/src/nostr-bus.ts +++ b/extensions/nostr/src/nostr-bus.ts @@ -617,15 +617,13 @@ export async function startNostrBus(options: NostrBusOptions): Promise guardPolicy.maxPlaintextBytes) { + markSeen(); metrics.emit("event.rejected.oversized_plaintext"); return; } @@ -643,6 +642,9 @@ export async function startNostrBus(options: NostrBusOptions): Promise