import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { AlertCircle, Eye, Presentation, Users } from "lucide-react"; import { GuacamoleDisplay } from "../../../../plugins/remote-desktop/src/frontend/GuacamoleDisplay.tsx"; import { GuestTerminalView, type SessionParticipantInfo, } 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 [participantCount, setParticipantCount] = useState(0); 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); setParticipantCount(0); } } 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")}
{participantCount >= 2 && ( {participantCount} )} {t("sessionSharing.guestView.readOnlyBadge")}
{error ? ( } text={error} /> ) : !stage ? ( } text={t("collab.guest.waiting")} /> ) : stage.protocol === "ssh" ? ( setParticipantCount(participants.length) } /> ) : stage.connectParams?.token ? ( ) : null}
); } function Note({ icon, text }: { icon: React.ReactNode; text: string }) { return (
{icon}

{text}

); }