import { NOTIFICATION_WS_EVENTS, NOTIFICATION_WS_NAMESPACE, type NotificationDto, } from "@edr/types"; import { useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef } from "react"; import { io } from "socket.io-client"; import { API_BASE_URL } from "@/constants/apiConfig"; import { NOTIFICATIONS_KEY, UNREAD_KEY } from "./useNotifications"; function getAuthToken(): string | undefined { return document.cookie .split("; ") .find((row) => row.startsWith("auth-token=")) ?.split("=")[1]; } // 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 notification pushes for the signed-in user. New items * invalidate the cached lists + fire `onNew` (the host shows a rich toast); * unread-count pushes update the badge instantly. */ export function useNotificationSocket( enabled: boolean, onNew?: (notification: NotificationDto) => void, ) { const qc = useQueryClient(); const onNewRef = useRef(onNew); onNewRef.current = onNew; useEffect(() => { if (!enabled) return; const token = getAuthToken(); if (!token) return; const socket = io(`${SOCKET_ORIGIN}/${NOTIFICATION_WS_NAMESPACE}`, { auth: { token }, transports: ["websocket"], withCredentials: true, }); socket.on(NOTIFICATION_WS_EVENTS.NEW, (n: NotificationDto) => { qc.invalidateQueries({ queryKey: NOTIFICATIONS_KEY }); qc.invalidateQueries({ queryKey: UNREAD_KEY }); onNewRef.current?.(n); }); socket.on(NOTIFICATION_WS_EVENTS.UNREAD_COUNT, (count: number) => { if (typeof count === "number") qc.setQueryData(UNREAD_KEY, count); }); return () => { socket.off(); socket.disconnect(); }; }, [enabled, qc]); }