Add authentication, Redis pub/sub, and error handling to SSE

Improvements:
- Add Redis pub/sub support for multi-server deployments
- Add authentication check to SSE stream endpoint
- Add 30s heartbeat keepalive for long-lived connections
- Implement exponential backoff reconnection logic in client
- Fix TypeScript types (websiteId optional, timer types)
- Use specific query key invalidation instead of broad match
- Fix undefined access in session-events listener map
This commit is contained in:
Arthur Sepiol 2025-12-10 16:06:18 +03:00
parent ef9a382cdd
commit 5874cf80f5
4 changed files with 114 additions and 11 deletions

View file

@ -152,7 +152,7 @@ export async function POST(request: Request) {
distinctId: id,
createdAt,
});
emitSessionCreated(sourceId, sessionId);
await emitSessionCreated(sourceId, sessionId);
}
// Visit info

View file

@ -1,20 +1,45 @@
import { subscribeToSessions } from '@/lib/session-events';
import { initializeRedisSubscriber, subscribeToSessions } from '@/lib/session-events';
import { parseRequest } from '@/lib/request';
import { unauthorized } from '@/lib/response';
import { canViewWebsite } from '@/permissions';
const HEARTBEAT_INTERVAL = 30000;
export async function GET(
request: Request,
{ params }: { params: Promise<{ websiteId: string }> },
) {
const { auth, error } = await parseRequest(request);
if (error) {
return error();
}
const { websiteId } = await params;
if (!(await canViewWebsite(auth, websiteId))) {
return unauthorized();
}
await initializeRedisSubscriber();
const stream = new ReadableStream({
start(controller) {
const encoder = new TextEncoder();
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
const unsubscribe = subscribeToSessions(websiteId, (_, sessionId) => {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ sessionId })}\n\n`));
});
heartbeatTimer = setInterval(() => {
controller.enqueue(encoder.encode(': heartbeat\n\n'));
}, HEARTBEAT_INTERVAL);
request.signal.addEventListener('abort', () => {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
}
unsubscribe();
controller.close();
});