import { router } from '@inertiajs/vue3'; import { onScopeDispose, watch } from 'vue'; import { getEcho } from '@/lib/echo'; import type { RoomStateChangedPayload } from '@/types/room'; const ROOM_EVENT = '.room.state.changed'; export function useRoomRealtime( roomId: () => string, stateVersion: () => number, ): void { let reloadInProgress = false; let pendingVersion = 0; let disposed = false; function reloadRoom(): void { if (reloadInProgress || disposed) { return; } reloadInProgress = true; router.reload({ only: ['room', 'currentPlayer'], onFinish: () => { reloadInProgress = false; if (disposed) { return; } if (pendingVersion > stateVersion()) { reloadRoom(); return; } pendingVersion = stateVersion(); }, }); } function handleRoomChange(payload: RoomStateChangedPayload): void { if ( typeof payload.version !== 'number' || payload.version <= Math.max(stateVersion(), pendingVersion) ) { return; } pendingVersion = payload.version; if (payload.change === 'room_deleted') { router.visit('/', { replace: true }); return; } reloadRoom(); } const stopWatchingRoom = watch( roomId, (nextRoomId, _previousRoomId, onCleanup) => { const echo = getEcho(); const channelName = `rooms.${nextRoomId}`; pendingVersion = stateVersion(); echo.channel(channelName).listen(ROOM_EVENT, handleRoomChange); onCleanup(() => { echo.leaveChannel(channelName); }); }, { immediate: true }, ); const stopWatchingVersion = watch( stateVersion, (version) => { if (!reloadInProgress) { pendingVersion = Math.max(pendingVersion, version); } }, { flush: 'sync' }, ); onScopeDispose(() => { disposed = true; stopWatchingRoom(); stopWatchingVersion(); }); }