forked from jsnbuchanan/crowd-funder-for-time-pwa
- 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.
151 lines
4.3 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|