import React, { useEffect, useRef, useState } from 'react'; import { Box, Button, color, config, Text } from 'folds'; import jsQR from 'jsqr'; type QrScannerProps = { onScan: (bytes: Uint8ClampedArray) => void; onCancel: () => void; }; // Camera QR scanner. Decodes frames with jsQR and hands back the raw byte // segment (`result.binaryData`) — Matrix QR verification needs the raw bytes, // not a decoded string, so the string-only `BarcodeDetector` can't be used. export function QrScanner({ onScan, onCancel }: QrScannerProps) { const videoRef = useRef(null); const [error, setError] = useState(); const doneRef = useRef(false); useEffect(() => { let stream: MediaStream | undefined; let raf = 0; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d', { willReadFrequently: true }); const tick = () => { const video = videoRef.current; if (!doneRef.current && video && ctx && video.readyState === video.HAVE_ENOUGH_DATA) { canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const image = ctx.getImageData(0, 0, canvas.width, canvas.height); const result = jsQR(image.data, image.width, image.height); if (result && result.binaryData.length > 0) { doneRef.current = true; onScan(new Uint8ClampedArray(result.binaryData)); return; } } raf = requestAnimationFrame(tick); }; (async () => { try { stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, }); if (videoRef.current) { videoRef.current.srcObject = stream; await videoRef.current.play(); } raf = requestAnimationFrame(tick); } catch { setError( 'Could not access the camera. Grant camera permission, or verify with emojis instead.', ); } })(); return () => { doneRef.current = true; cancelAnimationFrame(raf); stream?.getTracks().forEach((track) => track.stop()); }; }, [onScan]); if (error) { return ( {error} ); } return ( Point your camera at the QR code shown on your other device. ); }