mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 20:46:44 +03:00
feat: enhance SSE connection with reconnect logic and error handling
- Updated `useSSE` hook to include max reconnect attempts and exponential backoff for reconnections. - Improved error handling for SSE messages and connection errors. - Added connection status reset on successful connection. fix: improve SQLite database connection handling - Simplified database initialization and connection logic. - Ensured the database file is created if it doesn't exist. fix: enhance Redis client connection with retry strategy - Implemented exponential backoff for Redis connection retries. - Added event handlers for connection success and error handling. feat: improve SSE API endpoint with robust Redis connection management - Added connection retry logic for Redis in the SSE API. - Implemented heartbeat messages to keep the connection alive. - Enhanced error handling for Redis subscription and connection attempts.
This commit is contained in:
@@ -1,34 +1,61 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useEffect, useState, useRef, useCallback } from "react";
|
||||
import type { MirrorJob } from "@/lib/db/schema";
|
||||
|
||||
interface UseSSEOptions {
|
||||
userId?: string;
|
||||
onMessage: (data: MirrorJob) => void;
|
||||
maxReconnectAttempts?: number;
|
||||
reconnectDelay?: number;
|
||||
}
|
||||
|
||||
export const useSSE = ({ userId, onMessage }: UseSSEOptions) => {
|
||||
export const useSSE = ({
|
||||
userId,
|
||||
onMessage,
|
||||
maxReconnectAttempts = 5,
|
||||
reconnectDelay = 3000
|
||||
}: UseSSEOptions) => {
|
||||
const [connected, setConnected] = useState<boolean>(false);
|
||||
const [reconnectCount, setReconnectCount] = useState<number>(0);
|
||||
const onMessageRef = useRef(onMessage);
|
||||
const eventSourceRef = useRef<EventSource | null>(null);
|
||||
const reconnectTimeoutRef = useRef<number | null>(null);
|
||||
|
||||
// Update the ref when onMessage changes
|
||||
useEffect(() => {
|
||||
onMessageRef.current = onMessage;
|
||||
}, [onMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
// Create a stable connect function that can be called for reconnection
|
||||
const connect = useCallback(() => {
|
||||
if (!userId) return;
|
||||
|
||||
// Clean up any existing connection
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
}
|
||||
|
||||
// Clear any pending reconnect timeout
|
||||
if (reconnectTimeoutRef.current) {
|
||||
window.clearTimeout(reconnectTimeoutRef.current);
|
||||
reconnectTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
// Create new EventSource connection
|
||||
const eventSource = new EventSource(`/api/sse?userId=${userId}`);
|
||||
eventSourceRef.current = eventSource;
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
try {
|
||||
// Check if this is an error message from our server
|
||||
if (event.data.startsWith('{"error":')) {
|
||||
console.warn("SSE server error:", event.data);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedMessage: MirrorJob = JSON.parse(event.data);
|
||||
|
||||
// console.log("Received new log:", parsedMessage);
|
||||
|
||||
onMessageRef.current(parsedMessage); // Use ref instead of prop directly
|
||||
onMessageRef.current(parsedMessage);
|
||||
} catch (error) {
|
||||
console.error("Error parsing message:", error);
|
||||
console.error("Error parsing SSE message:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,19 +63,50 @@ export const useSSE = ({ userId, onMessage }: UseSSEOptions) => {
|
||||
|
||||
eventSource.onopen = () => {
|
||||
setConnected(true);
|
||||
setReconnectCount(0); // Reset reconnect counter on successful connection
|
||||
console.log(`Connected to SSE for user: ${userId}`);
|
||||
};
|
||||
|
||||
eventSource.onerror = () => {
|
||||
console.error("SSE connection error");
|
||||
eventSource.onerror = (error) => {
|
||||
console.error("SSE connection error:", error);
|
||||
setConnected(false);
|
||||
eventSource.close();
|
||||
};
|
||||
eventSourceRef.current = null;
|
||||
|
||||
return () => {
|
||||
eventSource.close();
|
||||
// Attempt to reconnect if we haven't exceeded max attempts
|
||||
if (reconnectCount < maxReconnectAttempts) {
|
||||
const nextReconnectDelay = Math.min(reconnectDelay * Math.pow(1.5, reconnectCount), 30000);
|
||||
console.log(`Attempting to reconnect in ${nextReconnectDelay}ms (attempt ${reconnectCount + 1}/${maxReconnectAttempts})`);
|
||||
|
||||
reconnectTimeoutRef.current = window.setTimeout(() => {
|
||||
setReconnectCount(prev => prev + 1);
|
||||
connect();
|
||||
}, nextReconnectDelay);
|
||||
} else {
|
||||
console.error(`Failed to reconnect after ${maxReconnectAttempts} attempts`);
|
||||
}
|
||||
};
|
||||
}, [userId]); // Only depends on userId now
|
||||
}, [userId, maxReconnectAttempts, reconnectDelay, reconnectCount]);
|
||||
|
||||
// Set up the connection
|
||||
useEffect(() => {
|
||||
if (!userId) return;
|
||||
|
||||
connect();
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
eventSourceRef.current = null;
|
||||
}
|
||||
|
||||
if (reconnectTimeoutRef.current) {
|
||||
window.clearTimeout(reconnectTimeoutRef.current);
|
||||
reconnectTimeoutRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [userId, connect]);
|
||||
|
||||
return { connected };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user