import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { AlertCircle, Presentation } from "lucide-react"; import { GuacamoleDisplay } from "@/features/guacamole/GuacamoleDisplay.tsx"; import { GuestTerminalView } from "@/features/session-sharing/SharedSessionView"; import { resolveCollabGuestStage, type CollabGuestStage, } from "@/api/collab-api"; const POLL_MS = 5000; /** * Anonymous guest page for a collab room (?view=collab-guest&token=...). * Guests have no account and no event channel, so they poll the public * resolve endpoint and remount the viewer whenever the stage share changes. */ export default function CollabGuestView() { const { t } = useTranslation(); const token = new URLSearchParams(window.location.search).get("token"); const [roomName, setRoomName] = useState(null); const [stage, setStage] = useState(null); const [error, setError] = useState(null); const stageShareIdRef = useRef(null); useEffect(() => { if (!token) { setError(t("collab.guest.linkInvalid")); return; } let cancelled = false; const poll = async () => { try { const result = await resolveCollabGuestStage(token); if (cancelled) return; setRoomName(result.roomName); setError(null); const nextShareId = result.stage?.shareId ?? null; // Tokens are minted per resolve; only swap the viewer on a real change. if (nextShareId !== stageShareIdRef.current) { stageShareIdRef.current = nextShareId; setStage(result.stage); } } catch { if (!cancelled) setError(t("collab.guest.linkInvalid")); } }; void poll(); const timer = setInterval(() => void poll(), POLL_MS); return () => { cancelled = true; clearInterval(timer); }; }, [token, t]); return (
{roomName ?? t("collab.guest.title")} {t("sessionSharing.guestView.readOnlyBadge")}
{error ? ( } text={error} /> ) : !stage ? ( } text={t("collab.guest.waiting")} /> ) : stage.protocol === "ssh" ? ( ) : stage.connectParams?.token ? ( ) : null}
); } function Note({ icon, text }: { icon: React.ReactNode; text: string }) { return (
{icon}

{text}

); }