import { createApp, App } from "vue"; import { QRScannerService, ScanListener, QRScannerOptions } from "./types"; import QRScannerDialog from "@/components/QRScanner/QRScannerDialog.vue"; import { logger } from "@/utils/logger"; export class WebDialogQRScanner implements QRScannerService { private dialogInstance: App | null = null; private dialogComponent: InstanceType | null = null; private scanListener: ScanListener | null = null; private isScanning = false; private container: HTMLElement | null = null; constructor(private options?: QRScannerOptions) {} async checkPermissions(): Promise { try { const permissions = await navigator.permissions.query({ name: "camera" as PermissionName, }); return permissions.state === "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 { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); stream.getTracks().forEach((track) => track.stop()); return true; } catch (error) { const wrappedError = error instanceof Error ? error : new Error(String(error)); logger.error("Error requesting camera permissions:", wrappedError); return false; } } async isSupported(): Promise { return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia); } async startScan(): Promise { if (this.isScanning) { return; } try { this.isScanning = true; // Create and mount dialog component this.container = document.createElement("div"); document.body.appendChild(this.container); this.dialogInstance = createApp(QRScannerDialog, { onScan: (result: string) => { if (this.scanListener) { this.scanListener.onScan(result); } }, onError: (error: Error) => { if (this.scanListener?.onError) { this.scanListener.onError(error); } }, options: this.options, }); this.dialogComponent = this.dialogInstance.mount(this.container).$refs .dialog as InstanceType; } catch (error) { this.isScanning = false; const wrappedError = error instanceof Error ? error : new Error(String(error)); if (this.scanListener?.onError) { this.scanListener.onError(wrappedError); } logger.error("Error starting scan:", wrappedError); this.cleanupContainer(); throw wrappedError; } } async stopScan(): Promise { if (!this.isScanning) { return; } try { if (this.dialogComponent) { await this.dialogComponent.close(); } if (this.dialogInstance) { this.dialogInstance.unmount(); } } catch (error) { const wrappedError = error instanceof Error ? error : new Error(String(error)); logger.error("Error stopping scan:", wrappedError); throw wrappedError; } finally { this.isScanning = false; this.cleanupContainer(); } } addListener(listener: ScanListener): void { this.scanListener = listener; } private cleanupContainer(): void { if (this.container && this.container.parentNode) { this.container.parentNode.removeChild(this.container); } this.container = null; } async cleanup(): Promise { try { await this.stopScan(); } catch (error) { const wrappedError = error instanceof Error ? error : new Error(String(error)); logger.error("Error during cleanup:", wrappedError); throw wrappedError; } finally { this.dialogComponent = null; this.dialogInstance = null; this.scanListener = null; this.cleanupContainer(); } } }