Files
crowd-funder-from-jason/src/services/QRScanner/CapacitorQRScanner.ts
Matthew Raymer 30e448faf8 refactor(qr): improve QR code scanning robustness and error handling
- Enhance JWT extraction with unified path handling and validation
- Add debouncing to prevent duplicate scans
- Improve error handling and logging throughout QR flow
- Add proper TypeScript interfaces for QR scan results
- Implement mobile app lifecycle handlers (pause/resume)
- Enhance logging with structured data and consistent levels
- Clean up scanner resources properly on component destroy
- Split contact handling into separate method for better organization
- Add proper type for UserNameDialog ref

This commit improves the reliability and maintainability of the QR code
scanning functionality while adding better error handling and logging.
2025-04-22 11:04:56 +00:00

151 lines
4.3 KiB
TypeScript

import {
BarcodeScanner,
BarcodeFormat,
StartScanOptions,
LensFacing,
} from "@capacitor-mlkit/barcode-scanning";
import { QRScannerService, ScanListener, QRScannerOptions } from "./types";
import { logger } from "@/utils/logger";
export class CapacitorQRScanner implements QRScannerService {
private scanListener: ScanListener | null = null;
private isScanning = false;
private listenerHandles: Array<() => Promise<void>> = [];
async checkPermissions(): Promise<boolean> {
try {
const { camera } = await BarcodeScanner.checkPermissions();
return camera === "granted";
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error checking camera permissions:", wrappedError);
return false;
}
}
async requestPermissions(): Promise<boolean> {
try {
// First check if we already have permissions
if (await this.checkPermissions()) {
return true;
}
// Request permissions if we don't have them
const { camera } = await BarcodeScanner.requestPermissions();
return camera === "granted";
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error requesting camera permissions:", wrappedError);
return false;
}
}
async isSupported(): Promise<boolean> {
try {
const { supported } = await BarcodeScanner.isSupported();
return supported;
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error checking scanner support:", wrappedError);
return false;
}
}
async startScan(options?: QRScannerOptions): Promise<void> {
if (this.isScanning) {
return;
}
try {
// Ensure we have permissions before starting
logger.log("Checking camera permissions...");
if (!(await this.checkPermissions())) {
logger.log("Requesting camera permissions...");
const granted = await this.requestPermissions();
if (!granted) {
throw new Error("Camera permission denied");
}
}
// Check if scanning is supported
logger.log("Checking scanner support...");
if (!(await this.isSupported())) {
throw new Error("QR scanning not supported on this device");
}
logger.log("Starting MLKit scanner...");
this.isScanning = true;
const scanOptions: StartScanOptions = {
formats: [BarcodeFormat.QrCode],
lensFacing:
options?.camera === "front" ? LensFacing.Front : LensFacing.Back,
};
logger.log("Scanner options:", scanOptions);
// Add listener for barcode scans
const handle = await BarcodeScanner.addListener(
"barcodeScanned",
(result) => {
if (this.scanListener) {
this.scanListener.onScan(result.barcode.rawValue);
}
},
);
this.listenerHandles.push(handle.remove);
// Start continuous scanning
await BarcodeScanner.startScan(scanOptions);
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error during QR scan:", wrappedError);
this.scanListener?.onError?.(wrappedError);
throw wrappedError;
}
}
async stopScan(): Promise<void> {
if (!this.isScanning) {
return;
}
try {
await BarcodeScanner.stopScan();
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error stopping QR scan:", wrappedError);
this.scanListener?.onError?.(wrappedError);
throw wrappedError;
} finally {
this.isScanning = false;
}
}
addListener(listener: ScanListener): void {
this.scanListener = listener;
}
async cleanup(): Promise<void> {
try {
await this.stopScan();
for (const handle of this.listenerHandles) {
await handle();
}
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error during cleanup:", wrappedError);
throw wrappedError;
} finally {
this.listenerHandles = [];
this.scanListener = null;
}
}
}