import { inject } from '@angular/core'; import { type ActivatedRouteSnapshot, type CanActivateFn, type ResolveFn, Router, type UrlTree } from '@angular/router'; import { createSessionContextStore } from '../../../src/spa/session-context-store'; import { normalizeLocale, resolvePreferredLocale, setPreferredLocale } from './lobby-i18n'; export interface RouteSessionContext { sessionCode: string | null; playerId: number | null; token: string | null; locale: string; } const HOST_STORAGE_KEY = 'wpp.host-session-code'; function normalizeCode(value: string): string { return value.trim().toUpperCase(); } function isCodeLike(value: string | null | undefined): value is string { return !!value && /^[A-Za-z0-9]{4,12}$/.test(value.trim()); } function hasPlayerSessionContext(sessionCode: string): boolean { const context = createSessionContextStore(window.localStorage).get(); if (!context) { return false; } return ( isCodeLike(context.sessionCode) && normalizeCode(context.sessionCode) === normalizeCode(sessionCode) && Number.isInteger(context.playerId) && context.playerId > 0 && !!context.token.trim() ); } export function resolveSessionCode(route: ActivatedRouteSnapshot, mode: 'host' | 'player'): string | null { const contextParam = route.paramMap.get('context'); const queryCode = route.queryParamMap.get('session'); if (isCodeLike(contextParam)) { return normalizeCode(contextParam); } if (isCodeLike(queryCode)) { return normalizeCode(queryCode); } if (mode === 'player') { const persisted = createSessionContextStore(window.localStorage).get()?.sessionCode; if (isCodeLike(persisted)) { return normalizeCode(persisted); } return null; } const stored = window.sessionStorage.getItem(HOST_STORAGE_KEY); if (isCodeLike(stored)) { return normalizeCode(stored); } return null; } function resolveRouteLocale(route: ActivatedRouteSnapshot): string { const langParam = route.queryParamMap.get('lang'); if (langParam !== null) { const locale = normalizeLocale(langParam); setPreferredLocale(locale); return locale; } return resolvePreferredLocale(); } async function sessionExists(code: string): Promise { const response = await fetch(`/lobby/sessions/${encodeURIComponent(code)}`, { method: 'GET', headers: { Accept: 'application/json' }, credentials: 'same-origin', }); return response.ok; } async function requireSessionContext(route: ActivatedRouteSnapshot, mode: 'host' | 'player'): Promise { const phase = route.paramMap.get('phase'); const code = resolveSessionCode(route, mode); if (!phase) { if (mode === 'host' && code) { window.sessionStorage.setItem(HOST_STORAGE_KEY, code); } return true; } if (!code) { return false; } if (mode === 'player' && !hasPlayerSessionContext(code)) { return false; } const ok = await sessionExists(code); if (!ok) { return false; } if (mode === 'host') { window.sessionStorage.setItem(HOST_STORAGE_KEY, code); } return true; } async function guard(mode: 'host' | 'player', route: ActivatedRouteSnapshot): Promise { const router = inject(Router); const allowed = await requireSessionContext(route, mode); if (allowed) { return true; } return router.createUrlTree([`/${mode}`]); } export const hostRouteGuard: CanActivateFn = (route) => guard('host', route); export const playerRouteGuard: CanActivateFn = (route) => guard('player', route); export const hostRouteContextResolver: ResolveFn = (route) => { const code = resolveSessionCode(route, 'host'); const locale = resolveRouteLocale(route); if (code) { window.sessionStorage.setItem(HOST_STORAGE_KEY, code); } return { sessionCode: code, playerId: null, token: null, locale }; }; export const playerRouteContextResolver: ResolveFn = (route) => { const code = resolveSessionCode(route, 'player'); const locale = resolveRouteLocale(route); const context = createSessionContextStore(window.localStorage).get(); if (!code || !context || normalizeCode(context.sessionCode) !== code) { return { sessionCode: code, playerId: null, token: null, locale }; } return { sessionCode: code, playerId: Number.isInteger(context.playerId) && context.playerId > 0 ? context.playerId : null, token: context.token.trim() || null, locale, }; };