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> = []; async checkPermissions(): Promise { 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 { 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 { 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 { 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 { 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 { 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; } } }