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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user