444 use websocket to trigger reloads (#458)

* [#444] added refresh on sync token calendar update

* [#444] added refresh on register and tests

* [#444] extracted logic for useEffects and updates

* [#444] refactored code structure to use absolute paths
This commit is contained in:
Camille Moussu
2026-01-19 09:09:23 +01:00
committed by GitHub
parent 80110bdf52
commit 00c3c0d6f0
32 changed files with 849 additions and 163 deletions
+52 -76
View File
@@ -1,103 +1,79 @@
import { useEffect, useRef, useState } from "react";
import { useAppSelector } from "../app/hooks";
import { useSelectedCalendars } from "../utils/storage/useSelectedCalendars";
import {
createWebSocketConnection,
WebSocketWithCleanup,
} from "./createWebSocketConnection";
import { registerToCalendars } from "./ws/registerToCalendars";
import { unregisterToCalendars } from "./ws/unregisterToCalendars";
import { useAppDispatch, useAppSelector } from "@/app/hooks";
import { useSelectedCalendars } from "@/utils/storage/useSelectedCalendars";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { WebSocketWithCleanup } from "./connection";
import { closeWebSocketConnection } from "./connection/lifecycle/closeWebSocketConnection";
import { establishWebSocketConnection } from "./connection/lifecycle/establishWebSocketConnection";
import { updateCalendars } from "./messaging";
import { syncCalendarRegistrations } from "./operations";
export function WebSocketGate() {
const socketRef = useRef<WebSocketWithCleanup | null>(null);
const previousCalendarListRef = useRef<string[]>([]);
const dispatch = useAppDispatch();
const isAuthenticated = useAppSelector((state) =>
Boolean(state.user.userData && state.user.tokens)
);
const [isSocketOpen, setIsSocketOpen] = useState(false);
const calendarList = useSelectedCalendars();
const onMessage = useCallback(
(message: unknown) => {
updateCalendars(message, dispatch);
},
[dispatch]
);
const onClose = useCallback((event: CloseEvent) => {
// Socket already cleaned up by internal handler before this callback fires
socketRef.current = null;
setIsSocketOpen(false);
// TODO: Add reconnection logic here
}, []);
const onError = useCallback((error: Event) => {
console.error("WebSocket error:", error);
}, []);
const callBacks = useMemo(
() => ({
onMessage,
onClose,
onError,
}),
[onMessage, onClose, onError]
);
// Manage WebSocket connection
useEffect(() => {
const abortController = new AbortController();
if (!isAuthenticated) {
if (socketRef.current) {
socketRef.current.cleanup();
socketRef.current.close();
socketRef.current = null;
setIsSocketOpen(false);
}
closeWebSocketConnection(socketRef, setIsSocketOpen);
return;
}
const connect = async () => {
try {
const socket = await createWebSocketConnection();
socketRef.current = socket;
socket.addEventListener("close", () => {
setIsSocketOpen(false);
socketRef.current = null;
});
// Check if socket closed during setup
if (socket.readyState === WebSocket.OPEN) {
setIsSocketOpen(true);
}
} catch (error) {
console.error("Failed to create WebSocket connection:", error);
}
};
connect();
establishWebSocketConnection(
callBacks,
socketRef,
setIsSocketOpen,
abortController.signal
);
return () => {
if (socketRef.current) {
socketRef.current.cleanup();
socketRef.current.close();
socketRef.current = null;
setIsSocketOpen(false);
}
abortController.abort();
closeWebSocketConnection(socketRef, setIsSocketOpen);
};
}, [isAuthenticated]);
}, [isAuthenticated, callBacks]);
// Register using a diff with previous calendars
useEffect(() => {
if (
!isSocketOpen ||
!socketRef.current ||
socketRef.current.readyState !== WebSocket.OPEN
)
return;
const currentPaths = calendarList.map((cal) => `/calendars/${cal}`);
const previousPaths = previousCalendarListRef.current.map(
(cal) => `/calendars/${cal}`
syncCalendarRegistrations(
isSocketOpen,
socketRef,
calendarList,
previousCalendarListRef
);
// calendars to register
const toRegister = currentPaths.filter(
(path) => !previousPaths.includes(path)
);
// calendars to unregister
const toUnregister = previousPaths.filter(
(path) => !currentPaths.includes(path)
);
try {
if (toRegister.length > 0) {
registerToCalendars(socketRef.current, toRegister);
}
if (toUnregister.length > 0) {
unregisterToCalendars(socketRef.current, toUnregister);
}
// Only update the ref if operations succeeded
previousCalendarListRef.current = calendarList;
} catch (error) {
console.error("Failed to update calendar registrations:", error);
}
}, [isSocketOpen, calendarList]);
return null;