mirror of
https://github.com/umami-software/umami.git
synced 2026-02-05 21:27:20 +01:00
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:
parent
ef9a382cdd
commit
5874cf80f5
4 changed files with 114 additions and 11 deletions
|
|
@ -1,18 +1,61 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
export function useSessionStream(websiteId: string) {
|
||||
const MAX_RETRY_DELAY = 30000;
|
||||
const INITIAL_RETRY_DELAY = 1000;
|
||||
|
||||
export function useSessionStream(websiteId?: string) {
|
||||
const queryClient = useQueryClient();
|
||||
const retryCountRef = useRef(0);
|
||||
const retryTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!websiteId) return;
|
||||
|
||||
const eventSource = new EventSource(`/api/websites/${websiteId}/sessions/stream`);
|
||||
let eventSource: EventSource | null = null;
|
||||
let isMounted = true;
|
||||
|
||||
eventSource.onmessage = () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
||||
const connect = () => {
|
||||
if (!isMounted) return;
|
||||
|
||||
eventSource = new EventSource(`/api/websites/${websiteId}/sessions/stream`);
|
||||
|
||||
eventSource.onmessage = event => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.sessionId) {
|
||||
retryCountRef.current = 0;
|
||||
queryClient.invalidateQueries({ queryKey: ['sessions', { websiteId }] });
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to parse session event:', error);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = () => {
|
||||
eventSource?.close();
|
||||
|
||||
if (!isMounted) return;
|
||||
|
||||
const delay = Math.min(
|
||||
INITIAL_RETRY_DELAY * Math.pow(2, retryCountRef.current),
|
||||
MAX_RETRY_DELAY,
|
||||
);
|
||||
retryCountRef.current += 1;
|
||||
|
||||
retryTimeoutRef.current = setTimeout(connect, delay);
|
||||
};
|
||||
};
|
||||
|
||||
return () => eventSource.close();
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
if (retryTimeoutRef.current) {
|
||||
clearTimeout(retryTimeoutRef.current);
|
||||
}
|
||||
eventSource?.close();
|
||||
};
|
||||
}, [websiteId, queryClient]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue