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