157 lines
4.4 KiB
TypeScript
157 lines
4.4 KiB
TypeScript
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<boolean> {
|
|
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<boolean> {
|
|
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<boolean | UrlTree> {
|
|
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<RouteSessionContext> = (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<RouteSessionContext> = (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,
|
|
};
|
|
};
|