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.
 
 
 
 
 
 

155 lines
3.9 KiB

import {
BarcodeScanner,
BarcodeFormat,
LensFacing,
ScanResult,
} from "@capacitor-mlkit/barcode-scanning";
import type { QRScannerService, ScanListener } 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() {
try {
const { camera } = await BarcodeScanner.checkPermissions();
return camera === "granted";
} catch (error) {
logger.error("Error checking camera permissions:", error);
return false;
}
}
async requestPermissions() {
try {
const { camera } = await BarcodeScanner.requestPermissions();
return camera === "granted";
} catch (error) {
logger.error("Error requesting camera permissions:", error);
return false;
}
}
async isSupported() {
try {
const { supported } = await BarcodeScanner.isSupported();
return supported;
} catch (error) {
logger.error("Error checking barcode scanner support:", error);
return false;
}
}
async startScan() {
if (this.isScanning) {
logger.warn("Scanner is already active");
return;
}
try {
// First register listeners before starting scan
await this.registerListeners();
this.isScanning = true;
await BarcodeScanner.startScan({
formats: [BarcodeFormat.QrCode],
lensFacing: LensFacing.Back,
});
} catch (error) {
// Ensure cleanup on error
this.isScanning = false;
await this.removeListeners();
logger.error("Error starting barcode scan:", error);
throw error;
}
}
async stopScan() {
if (!this.isScanning) {
return;
}
try {
// First stop the scan
await BarcodeScanner.stopScan();
} catch (error) {
logger.error("Error stopping barcode scan:", error);
} finally {
// Always cleanup state even if stop fails
this.isScanning = false;
await this.removeListeners();
}
}
private async registerListeners() {
// Clear any existing listeners first
await this.removeListeners();
const scanHandle = await BarcodeScanner.addListener(
"barcodesScanned",
(result: ScanResult) => {
if (result.barcodes.length > 0) {
const barcode = result.barcodes[0];
if (barcode.rawValue && this.scanListener) {
this.scanListener.onScan(barcode.rawValue);
}
}
},
);
const errorHandle = await BarcodeScanner.addListener(
"scanError",
(error) => {
logger.error("Scan error:", error);
if (this.scanListener?.onError) {
this.scanListener.onError(
new Error(error.message || "Unknown scan error"),
);
}
},
);
this.listenerHandles.push(
async () => await scanHandle.remove(),
async () => await errorHandle.remove(),
);
}
private async removeListeners() {
try {
// Remove all registered listener handles
await Promise.all(this.listenerHandles.map((handle) => handle()));
this.listenerHandles = [];
} catch (error) {
logger.error("Error removing listeners:", error);
}
}
addListener(listener: ScanListener) {
if (this.scanListener) {
logger.warn("Scanner listener already exists, removing old listener");
this.cleanup();
}
this.scanListener = listener;
}
async cleanup() {
try {
// Stop scan first if active
if (this.isScanning) {
await this.stopScan();
}
// Remove listeners
await this.removeListeners();
// Clear state
this.scanListener = null;
this.isScanning = false;
} catch (error) {
logger.error("Error during cleanup:", error);
}
}
}