Files
scrum-poker-internal/resources/js/composables/useRoomRealtime.ts
T
2026-09-03 11:35:05 +02:00

116 lines
3.0 KiB
TypeScript

import { router } from '@inertiajs/vue3';
import { onScopeDispose, ref, shallowReadonly, watch } from 'vue';
import { getEcho } from '@/lib/echo';
import { home } from '@/routes';
import { join } from '@/routes/room';
import type {
CurrentRoomPlayer,
PokerRoom,
RoomStateChangedPayload,
} from '@/types/room';
const ROOM_EVENT = '.room.state.changed';
export function useRoomRealtime(
initialRoom: () => PokerRoom,
initialCurrentPlayer: () => CurrentRoomPlayer,
) {
const room = ref<PokerRoom>({ ...initialRoom() });
const currentPlayer = ref<CurrentRoomPlayer>({ ...initialCurrentPlayer() });
function handleRoomChange(payload: RoomStateChangedPayload): void {
if (
typeof payload.version !== 'number' ||
payload.version <= room.value.state_version
) {
return;
}
if (payload.change === 'room_deleted') {
router.visit(home().url, { replace: true });
return;
}
if (
payload.change === 'player_kicked' &&
payload.affected_player_id === currentPlayer.value.id
) {
router.visit(home().url, { replace: true });
return;
}
if (payload.change === 'room_closed') {
router.visit(
join({ query: { join_code: room.value.join_code } }).url,
{ replace: true },
);
return;
}
if (payload.state === null || payload.state === undefined) {
return;
}
room.value = {
...room.value,
...payload.state,
};
if (
payload.change === 'votes_reset' ||
payload.change === 'next_round'
) {
currentPlayer.value = {
...currentPlayer.value,
current_vote: null,
};
}
}
const stopWatchingRoom = watch(
() => initialRoom().id,
(nextRoomId, _previousRoomId, onCleanup) => {
const echo = getEcho();
const channelName = `rooms.${nextRoomId}`;
echo.channel(channelName).listen(ROOM_EVENT, handleRoomChange);
onCleanup(() => {
echo.leaveChannel(channelName);
});
},
{ immediate: true },
);
const stopWatchingInitialState = watch(
[initialRoom, initialCurrentPlayer],
([nextRoom, nextCurrentPlayer]) => {
room.value = { ...nextRoom };
currentPlayer.value = { ...nextCurrentPlayer };
},
{ deep: true },
);
onScopeDispose(() => {
stopWatchingRoom();
stopWatchingInitialState();
});
function setCurrentVote(value: string | null): void {
currentPlayer.value = {
...currentPlayer.value,
current_vote: value,
};
}
return {
room: shallowReadonly(room),
currentPlayer: shallowReadonly(currentPlayer),
setCurrentVote,
};
}