import { isTransientRequestError } from "@opengeni/sdk"; import { useEffect, useMemo, useRef, useState } from "react"; import { LoadingPanel, ProblemPanel } from "../components/common"; import { Button } from "../../ui/button"; import { CrossSlotDeepLinkRecovery } from "../components/cross-slot-deep-link-recovery"; import { useAppContext } from "../app-context"; import { DEFAULT_TRANSIENT_READ_RETRY, transientReadRetryStep, usePageLiveActivity, } from "../../hooks/internal"; import { authorizedSessionReadWorkspaceIds, canonicalSessionDeepLinkTarget, resolveAuthorizedSessionWorkspace, } from "../lib/session-deep-link"; import { workspaceSessionsPath } from "../lib/routes"; import { usePage } from "../lib/navigation"; export function SessionUnavailableRoute(props: { workspaceId: string; sessionId: string }) { const page = usePage(); const context = useAppContext(); const [state, setState] = useState<"resolving" | "not-found" | "error" | "transient">( "resolving", ); // Each attempt re-runs the lookup; transient failures schedule the next one. const [attempt, setAttempt] = useState(0); const transientStreakRef = useRef<{ key: string; since: number; attempts: number; surfaced: boolean; } | null>(null); // A hidden page defers its next retry until it is visible again, and then // looks with a fresh quiet window. const pageLive = usePageLiveActivity(); const pageLiveRef = useRef(pageLive); pageLiveRef.current = pageLive; const deferredRetryRef = useRef(false); useEffect(() => { if (!pageLive || !deferredRetryRef.current) return; deferredRetryRef.current = false; transientStreakRef.current = null; setAttempt((value) => value + 1); }, [pageLive]); const sessionWorkspaceIds = useMemo( () => authorizedSessionReadWorkspaceIds(context.accessContext, context.workspaces), [context.accessContext, context.workspaces], ); const backWorkspaceId = sessionWorkspaceIds.includes(props.workspaceId) ? props.workspaceId : (sessionWorkspaceIds.find( (workspaceId) => workspaceId === context.accessContext.defaultWorkspaceId, ) ?? sessionWorkspaceIds[0] ?? null); useEffect(() => { let cancelled = false; let retryTimer: ReturnType | null = null; deferredRetryRef.current = false; const key = `${props.workspaceId}\u0000${props.sessionId}`; if (transientStreakRef.current?.key !== key) transientStreakRef.current = null; // While a transient failure is already shown, background retries keep it. setState((current) => current === "transient" && transientStreakRef.current ? current : "resolving", ); void resolveAuthorizedSessionWorkspace( context.client, context.accessContext.workspaceGrants, props.sessionId, { authorizedWorkspaceIds: sessionWorkspaceIds, excludeWorkspaceId: props.workspaceId, }, ).then((resolution) => { if (cancelled) return; if (resolution.status === "resolved") { transientStreakRef.current = null; page.assign( canonicalSessionDeepLinkTarget( resolution.workspaceId, props.sessionId, new URL(page.href()), ), { replace: true }, ); return; } if (resolution.status === "error" && isTransientRequestError(resolution.error)) { // A deploy or network blip: keep looking quietly, and show the problem // only if it outlasts the quiet window. Keep retrying afterwards. const now = Date.now(); const streak = (transientStreakRef.current ??= { key, since: now, attempts: 0, surfaced: false, }); const step = transientReadRetryStep(streak, now, DEFAULT_TRANSIENT_READ_RETRY); if (!step.quiet) setState("transient"); retryTimer = setTimeout(() => { retryTimer = null; if (pageLiveRef.current) setAttempt((value) => value + 1); else deferredRetryRef.current = true; }, step.delayMs); return; } transientStreakRef.current = null; setState(resolution.status === "error" ? "error" : "not-found"); }); return () => { cancelled = true; if (retryTimer !== null) clearTimeout(retryTimer); }; }, [ page, context.accessContext.workspaceGrants, context.client, sessionWorkspaceIds, props.sessionId, props.workspaceId, attempt, ]); if (state === "resolving") return ; if (state === "transient") { return ( setAttempt((value) => value + 1)}> Try again } /> ); } const unavailable = ( Back to sessions ) : undefined } /> ); return state === "not-found" ? ( ) : ( unavailable ); }