Add real-time session updates via Server-Sent Events

Implements push-based real-time updates for the Sessions page. New sessions now appear instantly without manual reload or polling.

Changes:
- Add SSE event emitter for session creation notifications
- Create SSE stream endpoint at /api/websites/[id]/sessions/stream
- Emit session events in tracking endpoint when sessions are created
- Add useSessionStream hook to connect to SSE and invalidate queries
- Fix LoadingPanel to prevent flicker during background refetches
This commit is contained in:
Arthur Sepiol 2025-12-10 16:02:44 +03:00
parent 81e27fc18c
commit ef9a382cdd
7 changed files with 78 additions and 5 deletions

View file

@ -1,8 +1,9 @@
import { DataGrid } from '@/components/common/DataGrid';
import { useWebsiteSessionsQuery } from '@/components/hooks';
import { useSessionStream, useWebsiteSessionsQuery } from '@/components/hooks';
import { SessionsTable } from './SessionsTable';
export function SessionsDataTable({ websiteId }: { websiteId?: string; teamId?: string }) {
useSessionStream(websiteId);
const queryResult = useWebsiteSessionsQuery(websiteId);
return (

View file

@ -12,6 +12,7 @@ import { parseRequest } from '@/lib/request';
import { badRequest, forbidden, json, serverError } from '@/lib/response';
import { anyObjectParam, urlOrPathParam } from '@/lib/schema';
import { safeDecodeURI, safeDecodeURIComponent } from '@/lib/url';
import { emitSessionCreated } from '@/lib/session-events';
import { createSession, saveEvent, saveSessionData } from '@/queries/sql';
interface Cache {
@ -151,6 +152,7 @@ export async function POST(request: Request) {
distinctId: id,
createdAt,
});
emitSessionCreated(sourceId, sessionId);
}
// Visit info

View file

@ -0,0 +1,31 @@
import { subscribeToSessions } from '@/lib/session-events';
export async function GET(
request: Request,
{ params }: { params: Promise<{ websiteId: string }> },
) {
const { websiteId } = await params;
const stream = new ReadableStream({
start(controller) {
const encoder = new TextEncoder();
const unsubscribe = subscribeToSessions(websiteId, (_, sessionId) => {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ sessionId })}\n\n`));
});
request.signal.addEventListener('abort', () => {
unsubscribe();
controller.close();
});
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
});
}