import { CameraPermissionState, CaptureSession, PrismSession, PrismSessionState, } from "@prismlabs/web-scan-core"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { dispatchScanComplete } from "../../dispatch"; import "../../i18n/i18n"; import { isMobile } from "../../utils/mobile"; import CameraFeed from "../camera/CameraFeed"; import { AlertContainer } from "../components"; import Banner from "../components/Banner"; import ScreenContainer from "../components/ScreenContainer"; import LevelScreen from "../level/LevelScreen"; import Modal, { ModalContentContainer } from "../modal/Modal"; import PosingScreen from "../posing/PosingScreen"; import PositionScreen from "../positioning/PositionScreen"; import ProcessingScreen from "../processing/ProcessingScreen"; import RecordingScreen from "../recording/RecordingScreen"; interface PrismSessionViewProps { onSessionStateChange?: (state: PrismSessionState) => void; onClose: () => void; } function getIsPortraitMobile() { return ( isMobile(window.navigator) && window.screen.height > window.screen.width ); } export function PrismSessionView({ onSessionStateChange, onClose, }: PrismSessionViewProps) { const { t } = useTranslation(); const isFirefox = typeof navigator !== "undefined" && /firefox|fxios/i.test(navigator.userAgent); const [isPortraitMobile, setIsPortraitMobile] = useState( getIsPortraitMobile() ); const [isSessionInitialized, setIsSessionInitialized] = useState(false); const [isVideoReady, setIsVideoReady] = useState(false); const [cameraError, setCameraError] = useState(); const [scanBlob, setScanBlob] = useState(); const [sessionState, setSessionState] = useState( PrismSessionState.IDLE ); const [prismSession] = useState(() => { const captureSession = new CaptureSession(); return new PrismSession(captureSession); }); const [hasCameraPermission, setHasCameraPermission] = useState< boolean | null >(null); function handleFirefoxPermissionIssues(error: any) { const err = error as any; const name = err?.name || ""; const message = (err?.message || "").toLowerCase(); // Detect permission-denied patterns across browsers (including Firefox) const isPermissionDenied = name === "NotAllowedError" || name === "SecurityError" || message.includes("not allowed") || message.includes("permission") || message.includes("denied"); if (isPermissionDenied) { setHasCameraPermission(false); } } useEffect(() => { if (!isVideoReady || !isSessionInitialized) { return; } if (sessionState === PrismSessionState.IDLE) { prismSession.continue(); } }, [isVideoReady, isSessionInitialized]); useEffect(() => { if (onSessionStateChange) { onSessionStateChange(sessionState); } }, [sessionState, onSessionStateChange]); useEffect(() => { const handleResize = () => setIsPortraitMobile(getIsPortraitMobile()); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); useEffect(() => { const subscription = prismSession.currentSessionState.subscribe((state) => { setSessionState(state); }); // Start the session automatically prismSession .init(isPortraitMobile) .then(() => { setIsSessionInitialized(true); }) .catch((error) => { handleFirefoxPermissionIssues(error); setCameraError(error); console.error("Error starting Prism session:", error); }) .finally(() => { const granted = prismSession.captureSession.cameraManager.isPermissionGranted; if (granted === true) { setHasCameraPermission(true); } }); // Listen for camera permission changes prismSession.captureSession.cameraManager.onPermissionChange = ( state: CameraPermissionState ) => { if (state === CameraPermissionState.Unknown) return; setHasCameraPermission(state !== CameraPermissionState.Denied); }; const recordingSubscription = prismSession.captureSession.recorder.recording$.subscribe((blob) => { setScanBlob(blob); }); return () => { subscription.unsubscribe(); recordingSubscription.unsubscribe(); prismSession.cancel(); }; }, [prismSession]); const handleCancel = () => { prismSession.cancel(); onClose(); }; const handleVideoReady = async (video: HTMLVideoElement) => { try { await prismSession.captureSession.cameraManager.startVideoPlayback(video); setIsVideoReady(true); // THIS IS A FIREFOX FIX \\ // If playback started successfully, permission is effectively granted (helps Firefox) setHasCameraPermission(true); } catch (error) { handleFirefoxPermissionIssues(error); setCameraError(error as Error); console.error("Camera error:", error); } }; const endSession = () => { prismSession.completeSession(); setIsSessionInitialized(false); setIsVideoReady(false); onClose(); // notify that the scan is complete dispatchScanComplete(scanBlob); }; return ( {/* Camera feed as background */}
{isSessionInitialized && ( )}
{/* Foreground content */} {cameraError ? (

Error accessing the camera feed

{hasCameraPermission === false ? (

Please enable camera permissions

) : (

{cameraError.message}

)}
) : (
{sessionState === PrismSessionState.LEVELING && ( )} {sessionState === PrismSessionState.POSITIONING && ( )} {sessionState === PrismSessionState.POSING && ( )} {sessionState === PrismSessionState.RECORDING && ( )} {sessionState === PrismSessionState.PROCESSING && ( )} {sessionState === PrismSessionState.FINISHED && (

{t("prism.finished.title")}

{t("prism.finished.description")}

)} {hasCameraPermission === false && !isFirefox && ( { setHasCameraPermission(null); onClose(); }} /> )}
)}
); }