import { useEffect, useState } from 'react'; /** * desktop-client — erkennt, ob die Seite im Desktop-Client laeuft * (260917-h2s). Gegenstueck zur Middleware (apps/web/src/middleware.ts, * `withDesktopCookie`): der Rust-Client haengt `desktop=1` an seine erste * Navigation, die Middleware setzt daraufhin das Cookie `tessera_desktop` * auf jede Antwort. `isDesktopClient()` liest das Cookie synchron. */ export const DESKTOP_COOKIE_NAME = 'tessera_desktop'; /** * Gegenstueck zu `buildDesktopClientCookieValue()` in der Middleware * (quick-260918-gza): dort werden `dv`/`dc`/`dos` mit `${dv}|${dc}|${dos}` * zusammengesetzt und von Next per `encodeURIComponent` serialisiert -- * `|` wird `%7C` (gemessen). `document.cookie` liefert deshalb die * KODIERTE Form; `parseDesktopClientCookie()` dekodiert (in try/catch, * falls der Wert bereits roh vorliegt, z. B. direkt gesetzt im Test). */ export const DESKTOP_CLIENT_COOKIE_NAME = 'tessera_desktop_client'; export interface DesktopClientInfo { version: string; commit: string; os: string; } /** * Parst den Wert des Cookies `tessera_desktop_client` aus einem rohen * `document.cookie`-String. `null` bei fehlendem Cookie oder einer Form, * die nicht genau drei durch `|` getrennte Teile mit nicht-leerer Version * und nicht-leerem Betriebssystem ergibt (Commit darf leer sein). */ export function parseDesktopClientCookie(cookieString: string): DesktopClientInfo | null { const entry = cookieString .split(';') .map((c) => c.trim()) .find((c) => c.startsWith(`${DESKTOP_CLIENT_COOKIE_NAME}=`)); if (!entry) return null; const raw = entry.slice(DESKTOP_CLIENT_COOKIE_NAME.length + 1); let value = raw; try { value = decodeURIComponent(raw); } catch { // Rohwert (z. B. bereits dekodiert im Test) -- unveraendert weiterverwenden. } const parts = value.split('|'); if (parts.length !== 3) return null; const [version, commit, os] = parts; if (!version || !os) return null; return { version, commit, os }; } /** `null` ohne `document` (SSR) oder ohne gueltiges Cookie. */ export function getDesktopClientInfo(): DesktopClientInfo | null { if (typeof document === 'undefined') { return null; } return parseDesktopClientCookie(document.cookie); } export function isDesktopClient(): boolean { if (typeof document === 'undefined') { return false; } return document.cookie .split(';') .some((c) => c.trim() === `${DESKTOP_COOKIE_NAME}=1`); } /** * Hook statt direktem Aufruf beim Render: `isDesktopClient()` synchron im * ersten Render zu nutzen wuerde Server-HTML (kein `document`) und den * ersten Client-Render auseinanderlaufen lassen -- Hydration-Fehler. Der * `useEffect` gleicht das an; `useState(false)` haelt Server- und * Erst-Render-Ergebnis identisch. */ export function useIsDesktopClient(): boolean { const [isDesktop, setIsDesktop] = useState(false); useEffect(() => { setIsDesktop(isDesktopClient()); }, []); return isDesktop; }