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 {