From eb44b624d652326c821438c514662659c86a5a07 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Mon, 28 Apr 2025 07:25:25 +0000 Subject: [PATCH 1/7] fix(qr): add retry logic to QR scanner initialization - Retries QR scanner initialization up to 3 times if it fails, with a delay between attempts - Improves user experience on slow or delayed camera hardware/browser permission responses - Updates status message to reflect retry attempts --- src/components/QRScanner/QRScannerDialog.vue | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/QRScanner/QRScannerDialog.vue b/src/components/QRScanner/QRScannerDialog.vue index 75e5213d..ebc93eea 100644 --- a/src/components/QRScanner/QRScannerDialog.vue +++ b/src/components/QRScanner/QRScannerDialog.vue @@ -273,7 +273,7 @@ export default class QRScannerDialog extends Vue { } } - async onInit(promise: Promise): Promise { + async onInit(promise: Promise, attempt = 1): Promise { if (this.isNativePlatform) { logger.log("Skipping web scanner initialization on native platform"); return; @@ -350,12 +350,17 @@ 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"; logger.log("QR scanner initialized successfully"); } catch (error) { + if (attempt < 3) { + // Retry after a short delay + setTimeout(() => this.onInit(promise, attempt + 1), 1500); + this.initializationStatus = `Retrying camera initialization (attempt ${attempt + 1})...`; + return; + } const wrappedError = error instanceof Error ? error : new Error(String(error)); this.error = wrappedError.message; From 559f52e6d63fbdd840996c5523318bb7e30b4dea Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Mon, 28 Apr 2025 08:01:21 +0000 Subject: [PATCH 2/7] fix(qr): add timeout fallback for QR scanner initialization - Adds a 4-second timeout to force initialization complete if the QR scanner promise never resolves - Prevents UI from being stuck on "Checking camera access..." when camera is active but init promise hangs - Retains retry logic for transient initialization failures --- src/components/QRScanner/QRScannerDialog.vue | 90 ++++---------------- 1 file changed, 16 insertions(+), 74 deletions(-) diff --git a/src/components/QRScanner/QRScannerDialog.vue b/src/components/QRScanner/QRScannerDialog.vue index ebc93eea..3f5084c7 100644 --- a/src/components/QRScanner/QRScannerDialog.vue +++ b/src/components/QRScanner/QRScannerDialog.vue @@ -274,87 +274,29 @@ export default class QRScannerDialog extends Vue { } async onInit(promise: Promise, attempt = 1): Promise { - if (this.isNativePlatform) { - logger.log("Skipping web scanner initialization on native platform"); - return; - } - + if (this.isNativePlatform) return; this.isInitializing = true; this.error = null; this.initializationStatus = "Checking camera access..."; - try { - // First check if mediaDevices API is available - if (!navigator.mediaDevices) { - throw new Error( - "Camera API not available. Please ensure you're using HTTPS.", - ); - } - - logger.log("Starting QR scanner initialization...", { - mediaDevices: !!navigator.mediaDevices, - getUserMedia: !!( - navigator.mediaDevices && navigator.mediaDevices.getUserMedia - ), - constraints: { - video: true, - facingMode: this.preferredCamera, - }, - }); - - // Explicitly request camera permission first - this.initializationStatus = "Requesting camera permission..."; - try { - const stream = await navigator.mediaDevices.getUserMedia({ - video: { - facingMode: this.preferredCamera, - }, - }); - - // Stop the test stream immediately - stream.getTracks().forEach((track) => track.stop()); - - this.initializationStatus = "Camera permission granted..."; - logger.log("Camera permission granted"); - } catch (permissionError) { - const error = permissionError as Error; - logger.error("Camera permission error:", { - name: error.name, - message: error.message, - }); - - if ( - error.name === "NotAllowedError" || - error.name === "PermissionDeniedError" - ) { - throw new Error( - "Camera access denied. Please grant camera permission and try again.", - ); - } else if ( - error.name === "NotFoundError" || - error.name === "DevicesNotFoundError" - ) { - throw new Error( - "No camera found. Please ensure your device has a camera.", - ); - } else if ( - error.name === "NotReadableError" || - error.name === "TrackStartError" - ) { - throw new Error("Camera is in use by another application."); - } else { - throw new Error(`Camera error: ${error.message}`); - } - } + let timeoutHit = false; + const timeout = setTimeout(() => { + timeoutHit = true; + this.isInitializing = false; + this.cameraStatus = "Ready (timeout fallback)"; + this.initializationStatus = "Camera ready (fallback)"; + // Optionally log a warning or show a subtle message + }, 4000); // 4 seconds - // Now initialize the QR scanner - this.initializationStatus = "Starting QR scanner..."; - logger.log("Initializing QR scanner..."); + try { await promise; - this.isInitializing = false; - this.cameraStatus = "Ready"; - logger.log("QR scanner initialized successfully"); + if (!timeoutHit) { + clearTimeout(timeout); + this.isInitializing = false; + this.cameraStatus = "Ready"; + } } catch (error) { + clearTimeout(timeout); if (attempt < 3) { // Retry after a short delay setTimeout(() => this.onInit(promise, attempt + 1), 1500); From b9cafbe269e8f7e6ff14491534dc41dd35243077 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Mon, 28 Apr 2025 08:49:16 +0000 Subject: [PATCH 3/7] debug: add an old-school alert --- src/components/QRScanner/QRScannerDialog.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/QRScanner/QRScannerDialog.vue b/src/components/QRScanner/QRScannerDialog.vue index 3f5084c7..d9ed0f9e 100644 --- a/src/components/QRScanner/QRScannerDialog.vue +++ b/src/components/QRScanner/QRScannerDialog.vue @@ -285,8 +285,8 @@ export default class QRScannerDialog extends Vue { this.isInitializing = false; this.cameraStatus = "Ready (timeout fallback)"; this.initializationStatus = "Camera ready (fallback)"; - // Optionally log a warning or show a subtle message - }, 4000); // 4 seconds + alert("Timeout fallback triggered"); + }, 4000); try { await promise; From 35b038036aee1b17429fd1b2aa3c969383837c50 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Mon, 28 Apr 2025 09:07:27 +0000 Subject: [PATCH 4/7] chore(qr): add visible debug output and version bump for device-side troubleshooting - Bump version string in QRScannerDialog to include build number for cache-busting and verification - Add debugMessage UI panel to display internal state and debug info directly in the dialog - Add alert() and debugMessage updates at key points in QR scanner initialization for device-visible feedback --- src/components/QRScanner/QRScannerDialog.vue | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/QRScanner/QRScannerDialog.vue b/src/components/QRScanner/QRScannerDialog.vue index d9ed0f9e..032286e1 100644 --- a/src/components/QRScanner/QRScannerDialog.vue +++ b/src/components/QRScanner/QRScannerDialog.vue @@ -13,7 +13,7 @@ >

Scan QR Code

- v1.1.0 + v1.1.0 build 00000