import { BOOKING_WINDOW_WS_EVENTS, BOOKING_WINDOW_WS_NAMESPACE, type BookingWindowPhaseEvent, } from "@edr/types"; import { useQueryClient } from "@tanstack/react-query"; import { useEffect } from "react"; import { io } from "socket.io-client"; import { API_BASE_URL } from "@/constants/apiConfig"; import { AUTH_TOKEN_COOKIE, getCookie } from "@/auth/cookies"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; // The socket namespace lives at the server root, not under the `/api` REST // prefix — strip a trailing `/api` if the base URL carries one. const SOCKET_ORIGIN = String(API_BASE_URL ?? "").replace(/\/api\/?$/, ""); /** * Subscribes to live booking-window pushes for staff. Every phase transition * the window engine applies invalidates the GL windows carousel and the batch * board, so both flip the moment the backend does — polling stays only as a * fallback. */ export function useBookingWindowSocket(enabled: boolean = true) { const qc = useQueryClient(); useEffect(() => { if (!enabled) return; const token = getCookie(AUTH_TOKEN_COOKIE); if (!token) return; const socket = io(`${SOCKET_ORIGIN}/${BOOKING_WINDOW_WS_NAMESPACE}`, { auth: { token }, transports: ["websocket"], withCredentials: true, }); socket.on( BOOKING_WINDOW_WS_EVENTS.PHASE, (_event: BookingWindowPhaseEvent) => { qc.invalidateQueries({ queryKey: ["train-scheduling", "all-booking-windows"], }); qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.batchBoard(), }); }, ); return () => { socket.off(); socket.disconnect(); }; }, [enabled, qc]); }