diff --git a/src/components/QRScanner/QRScannerDialog.vue b/src/components/QRScanner/QRScannerDialog.vue index 75e5213d..49660a57 100644 --- a/src/components/QRScanner/QRScannerDialog.vue +++ b/src/components/QRScanner/QRScannerDialog.vue @@ -67,8 +67,8 @@ {{ initializationStatus }} @@ -164,6 +164,40 @@ + + +
@@ -275,7 +309,8 @@ export default class QRScannerDialog extends Vue { async onInit(promise: Promise): Promise { if (this.isNativePlatform) { - logger.log("Skipping web scanner initialization on native platform"); + logger.log("Closing QR dialog on native platform"); + this.$nextTick(() => this.close()); return; } @@ -291,14 +326,28 @@ export default class QRScannerDialog extends Vue { ); } + // Check for video devices + const devices = await navigator.mediaDevices.enumerateDevices(); + const videoDevices = devices.filter( + (device) => device.kind === "videoinput", + ); + + if (videoDevices.length === 0) { + throw new Error("No camera found on this device"); + } + logger.log("Starting QR scanner initialization...", { mediaDevices: !!navigator.mediaDevices, getUserMedia: !!( navigator.mediaDevices && navigator.mediaDevices.getUserMedia ), + videoDevices: videoDevices.length, constraints: { - video: true, - facingMode: this.preferredCamera, + video: { + facingMode: this.preferredCamera, + width: { ideal: 1280 }, + height: { ideal: 720 }, + }, }, }); @@ -308,6 +357,8 @@ export default class QRScannerDialog extends Vue { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: this.preferredCamera, + width: { ideal: 1280 }, + height: { ideal: 720 }, }, }); @@ -350,7 +401,7 @@ export default class QRScannerDialog extends Vue { // Now initialize the QR scanner this.initializationStatus = "Starting QR scanner..."; logger.log("Initializing QR scanner..."); - // await promise; // <-- comment this out for debugging + await promise; this.isInitializing = false; this.cameraStatus = "Ready"; @@ -401,8 +452,8 @@ export default class QRScannerDialog extends Vue { } }; - // Handle both promise and non-promise results - if (result && typeof result.then === "function") { + // Use instanceof Promise for type narrowing + if (result instanceof Promise) { result .then(processResult) .catch((error: Error) => this.handleError(error)) diff --git a/src/services/QRScanner/WebDialogQRScanner.ts b/src/services/QRScanner/WebDialogQRScanner.ts index 4af57c7e..ac7dfd3e 100644 --- a/src/services/QRScanner/WebDialogQRScanner.ts +++ b/src/services/QRScanner/WebDialogQRScanner.ts @@ -28,26 +28,95 @@ export class WebDialogQRScanner implements QRScannerService { async requestPermissions(): Promise { try { - const stream = await navigator.mediaDevices.getUserMedia({ video: true }); + // 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) { + const wrappedError = + error instanceof Error ? error : new Error(String(error)); logger.error("Error requesting camera permissions:", { - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, + 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 { - // Check for secure context first - if (!window.isSecureContext) { - logger.warn("Camera access requires HTTPS (secure context)"); + try { + // Check for secure context first + if (!window.isSecureContext) { + 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 {