forked from jsnbuchanan/crowd-funder-for-time-pwa
feat(qr): improve camera error feedback and robustness in QR scanner
- Display prominent, actionable error banners in QR scanner dialog for camera access issues - Add troubleshooting tips for common camera errors (no device, denied, in use, HTTPS) - Enhance error handling and logging in WebDialogQRScanner for device detection and permissions - Use proper type narrowing for promise handling in QRScannerDialog to resolve linter errors - Improve user experience and clarity when camera access fails or is unavailable
This commit is contained in:
@@ -28,26 +28,79 @@ export class WebDialogQRScanner implements QRScannerService {
|
||||
|
||||
async requestPermissions(): Promise<boolean> {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
// First check if we have any video devices
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
||||
|
||||
if (videoDevices.length === 0) {
|
||||
logger.error('No video devices found');
|
||||
throw new Error('No camera found on this device');
|
||||
}
|
||||
|
||||
// Try to get a stream with specific constraints
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: {
|
||||
facingMode: 'environment',
|
||||
width: { ideal: 1280 },
|
||||
height: { ideal: 720 }
|
||||
}
|
||||
});
|
||||
|
||||
// Stop the test stream immediately
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("Error requesting camera permissions:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
const wrappedError = error instanceof Error ? error : new Error(String(error));
|
||||
logger.error('Error requesting camera permissions:', {
|
||||
error: wrappedError.message,
|
||||
stack: wrappedError.stack,
|
||||
name: wrappedError.name
|
||||
});
|
||||
return false;
|
||||
|
||||
// Provide more specific error messages
|
||||
if (wrappedError.name === 'NotFoundError' || wrappedError.name === 'DevicesNotFoundError') {
|
||||
throw new Error('No camera found on this device');
|
||||
} else if (wrappedError.name === 'NotAllowedError' || wrappedError.name === 'PermissionDeniedError') {
|
||||
throw new Error('Camera access denied. Please grant camera permission and try again');
|
||||
} else if (wrappedError.name === 'NotReadableError' || wrappedError.name === 'TrackStartError') {
|
||||
throw new Error('Camera is in use by another application');
|
||||
} else {
|
||||
throw new Error(`Camera error: ${wrappedError.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async isSupported(): Promise<boolean> {
|
||||
try {
|
||||
// Check for secure context first
|
||||
if (!window.isSecureContext) {
|
||||
logger.warn("Camera access requires HTTPS (secure context)");
|
||||
logger.warn('Camera access requires HTTPS (secure context)');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for camera API support
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
logger.warn('Camera API not supported in this browser');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we have any video devices
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const hasVideoDevices = devices.some(device => device.kind === 'videoinput');
|
||||
|
||||
if (!hasVideoDevices) {
|
||||
logger.warn('No video devices found');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Error checking camera support:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined
|
||||
});
|
||||
return false;
|
||||
}
|
||||
// Then check for camera API support
|
||||
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
|
||||
}
|
||||
|
||||
async startScan(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user