f2457113a2
Die Middleware liest jetzt zusaetzlich dv/dc/dos aus der Anfrage und legt daraus das Cookie tessera_desktop_client an (bereinigt per Muster, nur wenn alle drei Werte gueltig sind); desktop-client.ts liest es zurueck. Der Fehler-melden-Dialog fuellt daraus vier neue Nutzlastfelder (clientKind/clientOs/clientVersion/clientCommit), damit die API die Herkunft der Meldung ausweisen kann. Ohne das zweite Cookie (alter Client) bleibt es bei "Desktop-App (unbekannt)". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
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;
|
|
}
|