feat: improve camera permission handling for QR scanner

- Add pre-check for camera devices to trigger permission prompt
- Add helpful info message explaining camera permission requirement
- Implement fallback to try default camera if facingMode constraint fails
- Better error messages for different camera access scenarios
- Log errors to console for debugging
- Handle NotAllowedError, NotFoundError, NotReadableError, OverconstrainedError
This commit is contained in:
2025-11-20 19:02:33 +00:00
parent 31af9bd083
commit cc01e9adc8

View File

@@ -26,6 +26,18 @@ export function QRCodeScanner({ onScan, onError }: QRCodeScannerProps) {
const startScanning = async () => {
try {
setError(null);
// First, check if we can get camera devices (this will trigger permission prompt)
try {
const devices = await Html5Qrcode.getCameras();
if (!devices || devices.length === 0) {
throw new Error('No camera found on this device.');
}
} catch (permErr: any) {
// If we can't get cameras, it might be a permission issue
console.error('Camera access error:', permErr);
}
const scanner = new Html5Qrcode(scannerIdRef.current);
scannerRef.current = scanner;
@@ -60,16 +72,23 @@ export function QRCodeScanner({ onScan, onError }: QRCodeScannerProps) {
} catch (err: any) {
let errorMsg = 'Failed to start camera';
if (err.message?.includes('Permission')) {
errorMsg = 'Camera permission denied. Please allow camera access in your browser settings.';
} else if (err.message?.includes('NotFoundError')) {
console.error('Scanner error:', err);
if (err.name === 'NotAllowedError' || err.message?.includes('NotAllowedError')) {
errorMsg = 'Camera permission denied. Please click "Allow" when your browser asks for camera access.';
} else if (err.name === 'NotFoundError' || err.message?.includes('NotFoundError')) {
errorMsg = 'No camera found on this device.';
} else if (err.message?.includes('NotAllowedError')) {
errorMsg = 'Camera access was denied. Please grant permission and try again.';
} else if (err.message?.includes('NotReadableError')) {
} else if (err.name === 'NotReadableError' || err.message?.includes('NotReadableError')) {
errorMsg = 'Camera is already in use by another application.';
} else if (err.message?.includes('secure')) {
} else if (err.name === 'OverconstrainedError' || err.message?.includes('OverconstrainedError')) {
errorMsg = 'Camera constraints not supported. Trying with default camera...';
// Try again without facingMode constraint
setTimeout(() => startScanningWithoutConstraints(), 1000);
return;
} else if (err.message?.includes('secure') || err.message?.includes('https')) {
errorMsg = 'Camera access requires HTTPS. Use manual input instead.';
} else if (err.message) {
errorMsg = `Camera error: ${err.message}`;
}
setError(errorMsg);
@@ -79,6 +98,50 @@ export function QRCodeScanner({ onScan, onError }: QRCodeScannerProps) {
}
};
const startScanningWithoutConstraints = async () => {
try {
setError(null);
const scanner = new Html5Qrcode(scannerIdRef.current);
scannerRef.current = scanner;
// Try with any available camera
const devices = await Html5Qrcode.getCameras();
if (devices && devices.length > 0 && devices[0]) {
await scanner.start(
devices[0].id, // Use first available camera
{
fps: 10,
qrbox: { width: 250, height: 250 },
},
(decodedText) => {
try {
const data = JSON.parse(decodedText);
if (data.type === 'attendee-checkin' && data.userId) {
onScan(data.userId);
} else {
const errorMsg = 'Invalid QR code format';
setError(errorMsg);
onError?.(errorMsg);
}
} catch (err) {
const errorMsg = 'Failed to parse QR code';
setError(errorMsg);
onError?.(errorMsg);
}
},
() => {
// Ignore scanning errors
}
);
setIsScanning(true);
}
} catch (err: any) {
console.error('Fallback scanner error:', err);
setError('Unable to access camera. Please use manual check-in instead.');
setShowManualInput(true);
}
};
const stopScanning = async () => {
if (scannerRef.current) {
try {
@@ -111,6 +174,21 @@ export function QRCodeScanner({ onScan, onError }: QRCodeScannerProps) {
return (
<div className="space-y-4">
{/* Info message before scanning */}
{!isScanning && !error && (
<div className="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg">
<div className="flex items-start">
<svg className="w-5 h-5 mr-2 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
<div className="text-sm">
<p className="font-medium mb-1">Camera Permission Required</p>
<p>When you click "Start Camera Scan", your browser will ask for camera permission. Please click "Allow" to enable QR code scanning.</p>
</div>
</div>
</div>
)}
<div
id={scannerIdRef.current}
className="w-full rounded-lg overflow-hidden bg-black"