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:
@@ -26,6 +26,18 @@ export function QRCodeScanner({ onScan, onError }: QRCodeScannerProps) {
|
|||||||
const startScanning = async () => {
|
const startScanning = async () => {
|
||||||
try {
|
try {
|
||||||
setError(null);
|
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);
|
const scanner = new Html5Qrcode(scannerIdRef.current);
|
||||||
scannerRef.current = scanner;
|
scannerRef.current = scanner;
|
||||||
|
|
||||||
@@ -60,16 +72,23 @@ export function QRCodeScanner({ onScan, onError }: QRCodeScannerProps) {
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
let errorMsg = 'Failed to start camera';
|
let errorMsg = 'Failed to start camera';
|
||||||
|
|
||||||
if (err.message?.includes('Permission')) {
|
console.error('Scanner error:', err);
|
||||||
errorMsg = 'Camera permission denied. Please allow camera access in your browser settings.';
|
|
||||||
} else if (err.message?.includes('NotFoundError')) {
|
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.';
|
errorMsg = 'No camera found on this device.';
|
||||||
} else if (err.message?.includes('NotAllowedError')) {
|
} else if (err.name === 'NotReadableError' || err.message?.includes('NotReadableError')) {
|
||||||
errorMsg = 'Camera access was denied. Please grant permission and try again.';
|
|
||||||
} else if (err.message?.includes('NotReadableError')) {
|
|
||||||
errorMsg = 'Camera is already in use by another application.';
|
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.';
|
errorMsg = 'Camera access requires HTTPS. Use manual input instead.';
|
||||||
|
} else if (err.message) {
|
||||||
|
errorMsg = `Camera error: ${err.message}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(errorMsg);
|
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 () => {
|
const stopScanning = async () => {
|
||||||
if (scannerRef.current) {
|
if (scannerRef.current) {
|
||||||
try {
|
try {
|
||||||
@@ -111,6 +174,21 @@ export function QRCodeScanner({ onScan, onError }: QRCodeScannerProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<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
|
<div
|
||||||
id={scannerIdRef.current}
|
id={scannerIdRef.current}
|
||||||
className="w-full rounded-lg overflow-hidden bg-black"
|
className="w-full rounded-lg overflow-hidden bg-black"
|
||||||
|
|||||||
Reference in New Issue
Block a user