import { useEffect } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { io, type Socket } from 'socket.io-client'; import { API_URL } from '../api/client'; import { useAuth } from '../auth/AuthContext'; let sharedSocket: Socket | null = null; function getSocket(token: string): Socket { if (!sharedSocket) { sharedSocket = io(API_URL, { auth: { token }, transports: ['websocket', 'polling'] }); } return sharedSocket; } // Joins the Socket.io room for a campaign and keeps React Query's cache in // sync with server-pushed events (HP/condition changes, encounter rounds, // new characters) so every open tab in the campaign updates live. export function useCampaignSocket(campaignId: string | undefined) { const { token } = useAuth(); const qc = useQueryClient(); useEffect(() => { if (!campaignId || !token) return; const socket = getSocket(token); socket.emit('campaign:join', campaignId); const onCharacterUpdated = () => { qc.invalidateQueries({ queryKey: ['campaign', campaignId, 'characters'] }); qc.invalidateQueries({ queryKey: ['character'] }); }; const onCharacterCreated = () => { qc.invalidateQueries({ queryKey: ['campaign', campaignId, 'characters'] }); }; const onParticipantUpdated = () => { qc.invalidateQueries({ queryKey: ['encounter'] }); }; const onRoundAdvanced = () => { qc.invalidateQueries({ queryKey: ['encounter'] }); }; socket.on('character:updated', onCharacterUpdated); socket.on('character:created', onCharacterCreated); socket.on('encounter:participant-updated', onParticipantUpdated); socket.on('encounter:round-advanced', onRoundAdvanced); return () => { socket.emit('campaign:leave', campaignId); socket.off('character:updated', onCharacterUpdated); socket.off('character:created', onCharacterCreated); socket.off('encounter:participant-updated', onParticipantUpdated); socket.off('encounter:round-advanced', onRoundAdvanced); }; }, [campaignId, token, qc]); }