Fix UX-015: ZXing async callback silently swallows camera errors - add handleCameraError(), 5s cameraFailTimer, error branch in decodeFromVideoDevice callback
This commit is contained in:
+47
-18
@@ -2893,35 +2893,64 @@
|
||||
|
||||
setScanStatus('Point camera at a barcode', 'success');
|
||||
|
||||
// Camera readiness check: if the video never starts streaming
|
||||
// (no media attached within 5s), treat as camera failure
|
||||
const cameraFailTimer = setTimeout(() => {
|
||||
if (video.readyState === 0 && scanningActive) {
|
||||
console.warn('Camera stream never started — showing manual fallback');
|
||||
scanningActive = false;
|
||||
video.style.display = 'none';
|
||||
if (codeReader) {
|
||||
try { codeReader.reset(); } catch (e) { /* ignore */ }
|
||||
}
|
||||
handleCameraError(new Error('No camera stream — connect a camera or enter ID manually'));
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// decodeFromVideoDevice handles camera + scanning in one call
|
||||
codeReader.decodeFromVideoDevice(deviceId, video, (result, err) => {
|
||||
// Clear the timeout once we get any callback (stream started or error)
|
||||
clearTimeout(cameraFailTimer);
|
||||
if (result && scanningActive) {
|
||||
const val = result.getText();
|
||||
if (val && val !== currentScannedMachineId) {
|
||||
handleBarcode(val.trim());
|
||||
}
|
||||
} else if (err && scanningActive) {
|
||||
// ZXing reports camera errors through the callback (not as exceptions)
|
||||
console.error('ZXing camera error:', err);
|
||||
handleCameraError(new Error(err.message || err));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Camera error:', e);
|
||||
setScanStatus('Camera failed: ' + e.message, 'error');
|
||||
const ph = document.getElementById('cameraPlaceholder');
|
||||
if (ph) ph.innerHTML = `<span class="cam-icon">${gi("📷")}</span><div class="cam-hint" style="margin-top:4px;">Camera: ${e.message}</div>`;
|
||||
// Show manual search fallback
|
||||
const fallback = document.getElementById('manualSearchFallback');
|
||||
if (fallback) {
|
||||
fallback.style.display = 'block';
|
||||
fallback.innerHTML = `
|
||||
<div style="background:var(--red-bg);color:var(--red);padding:10px;border-radius:8px;margin-bottom:10px;font-size:13px;">
|
||||
<strong>Camera unavailable</strong><br>
|
||||
${getCameraErrorMessage(e)}
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--text2);margin-bottom:6px;">Enter ID manually:</div>
|
||||
<input type="text" id="manualBarcodeInput" class="input-field" placeholder="Connect ID or barcode..." onkeypress="if(event.key==='Enter')handleManualBarcode()">
|
||||
<button class="btn btn-primary btn-sm" onclick="handleManualBarcode()" style="margin-top:6px;width:100%;">Search</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="startScanning()" style="margin-top:6px;width:100%;">Try camera again</button>
|
||||
`;
|
||||
}
|
||||
handleCameraError(e);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCameraError(e) {
|
||||
setScanStatus('Camera failed: ' + e.message, 'error');
|
||||
const ph = document.getElementById('cameraPlaceholder');
|
||||
if (ph) {
|
||||
ph.style.display = 'block';
|
||||
ph.innerHTML = `<span class="cam-icon">${gi("📷")}</span><div class="cam-hint" style="margin-top:4px;">Camera: ${e.message}</div>`;
|
||||
}
|
||||
// Show manual search fallback
|
||||
const fallback = document.getElementById('manualSearchFallback');
|
||||
if (fallback) {
|
||||
fallback.style.display = 'block';
|
||||
fallback.innerHTML = `
|
||||
<div style="background:var(--red-bg);color:var(--red);padding:10px;border-radius:8px;margin-bottom:10px;font-size:13px;">
|
||||
<strong>Camera unavailable</strong><br>
|
||||
${getCameraErrorMessage(e)}
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--text2);margin-bottom:6px;">Enter ID manually:</div>
|
||||
<input type="text" id="manualBarcodeInput" class="input-field" placeholder="Connect ID or barcode..." onkeypress="if(event.key==='Enter')handleManualBarcode()">
|
||||
<button class="btn btn-primary btn-sm" onclick="handleManualBarcode()" style="margin-top:6px;width:100%;">Search</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="startScanning()" style="margin-top:6px;width:100%;">Try camera again</button>
|
||||
`;
|
||||
// Focus the manual input for convenience
|
||||
setTimeout(() => document.getElementById('manualBarcodeInput')?.focus(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user