Browse Source

Merge branch 'qrcode-reboot' of ssh://173.199.124.46:222/trent_larson/crowd-funder-for-time-pwa into qrcode-reboot

qrcode-reboot
Jose Olarte III 3 days ago
parent
commit
22283e32f2
  1. 67
      src/components/QRScanner/QRScannerDialog.vue
  2. 87
      src/services/QRScanner/WebDialogQRScanner.ts

67
src/components/QRScanner/QRScannerDialog.vue

@ -67,8 +67,8 @@
<path <path
class="opacity-75" class="opacity-75"
fill="currentColor" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0
3.042 1.135 5.824 3 7.938l3-2.647z" 3.042 1.135 5.824 3 7.938l3-2.647z"
></path> ></path>
</svg> </svg>
<span>{{ initializationStatus }}</span> <span>{{ initializationStatus }}</span>
@ -164,6 +164,40 @@
</div> </div>
</div> </div>
<!-- Error Banner -->
<div
v-if="error"
class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4"
role="alert"
>
<strong class="font-bold">Camera Error:</strong>
<span class="block sm:inline">{{ error }}</span>
<ul class="mt-2 text-sm text-red-600 list-disc list-inside">
<li v-if="error.includes('No camera found')">
Check if your device has a camera and it is enabled.
</li>
<li v-if="error.includes('denied')">
Allow camera access in your browser settings and reload the page.
</li>
<li v-if="error.includes('in use')">
Close other applications that may be using the camera.
</li>
<li v-if="error.includes('HTTPS')">
Ensure you are using a secure (HTTPS) connection.
</li>
<li
v-if="
!error.includes('No camera found') &&
!error.includes('denied') &&
!error.includes('in use') &&
!error.includes('HTTPS')
"
>
Try refreshing the page or using a different browser/device.
</li>
</ul>
</div>
<!-- Footer --> <!-- Footer -->
<div class="p-4 border-t border-gray-200"> <div class="p-4 border-t border-gray-200">
<div class="flex flex-col space-y-4"> <div class="flex flex-col space-y-4">
@ -275,7 +309,8 @@ export default class QRScannerDialog extends Vue {
async onInit(promise: Promise<void>): Promise<void> { async onInit(promise: Promise<void>): Promise<void> {
if (this.isNativePlatform) { 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; 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...", { logger.log("Starting QR scanner initialization...", {
mediaDevices: !!navigator.mediaDevices, mediaDevices: !!navigator.mediaDevices,
getUserMedia: !!( getUserMedia: !!(
navigator.mediaDevices && navigator.mediaDevices.getUserMedia navigator.mediaDevices && navigator.mediaDevices.getUserMedia
), ),
videoDevices: videoDevices.length,
constraints: { constraints: {
video: true, video: {
facingMode: this.preferredCamera, 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({ const stream = await navigator.mediaDevices.getUserMedia({
video: { video: {
facingMode: this.preferredCamera, facingMode: this.preferredCamera,
width: { ideal: 1280 },
height: { ideal: 720 },
}, },
}); });
@ -350,7 +401,7 @@ export default class QRScannerDialog extends Vue {
// Now initialize the QR scanner // Now initialize the QR scanner
this.initializationStatus = "Starting QR scanner..."; this.initializationStatus = "Starting QR scanner...";
logger.log("Initializing QR scanner..."); logger.log("Initializing QR scanner...");
// await promise; // <-- comment this out for debugging await promise;
this.isInitializing = false; this.isInitializing = false;
this.cameraStatus = "Ready"; this.cameraStatus = "Ready";
@ -401,8 +452,8 @@ export default class QRScannerDialog extends Vue {
} }
}; };
// Handle both promise and non-promise results // Use instanceof Promise for type narrowing
if (result && typeof result.then === "function") { if (result instanceof Promise) {
result result
.then(processResult) .then(processResult)
.catch((error: Error) => this.handleError(error)) .catch((error: Error) => this.handleError(error))

87
src/services/QRScanner/WebDialogQRScanner.ts

@ -28,26 +28,95 @@ export class WebDialogQRScanner implements QRScannerService {
async requestPermissions(): Promise<boolean> { async requestPermissions(): Promise<boolean> {
try { 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()); stream.getTracks().forEach((track) => track.stop());
return true; return true;
} catch (error) { } catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error requesting camera permissions:", { logger.error("Error requesting camera permissions:", {
error: error instanceof Error ? error.message : String(error), error: wrappedError.message,
stack: error instanceof Error ? error.stack : undefined, 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> { async isSupported(): Promise<boolean> {
// Check for secure context first try {
if (!window.isSecureContext) { // Check for secure context first
logger.warn("Camera access requires HTTPS (secure context)"); 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; return false;
} }
// Then check for camera API support
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
} }
async startScan(): Promise<void> { async startScan(): Promise<void> {

Loading…
Cancel
Save