timesafari
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

147 lines
4.3 KiB

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;
}
}
}