---
description: 
globs: 
alwaysApply: true
---
# QR Code Implementation Guide

## Directory Structure

```
src/
├── services/
│   └── QRScanner/
│       ├── types.ts              # Core interfaces and types
│       ├── QRScannerFactory.ts   # Factory for creating scanner instances
│       ├── CapacitorQRScanner.ts # Mobile implementation using MLKit
│       ├── WebInlineQRScanner.ts # Web implementation using MediaDevices API
│       └── interfaces.ts         # Additional interfaces
├── components/
│   └── QRScanner/
│       └── QRScannerDialog.vue   # Shared UI component
```

## Core Interfaces

```typescript
// types.ts
export interface ScanListener {
  onScan: (result: string) => void;
  onError?: (error: Error) => void;
}

export interface QRScannerOptions {
  camera?: "front" | "back";
  showPreview?: boolean;
  playSound?: boolean;
}

export interface QRScannerService {
  checkPermissions(): Promise<boolean>;
  requestPermissions(): Promise<boolean>;
  isSupported(): Promise<boolean>;
  startScan(options?: QRScannerOptions): Promise<void>;
  stopScan(): Promise<void>;
  addListener(listener: ScanListener): void;
  onStream(callback: (stream: MediaStream | null) => void): void;
  cleanup(): Promise<void>;
}
```

## Configuration Files

### Vite Configuration
```typescript
// vite.config.common.mts
export function createBuildConfig(mode: string) {
  return {
    define: {
      'process.env.VITE_PLATFORM': JSON.stringify(mode),
      'process.env.VITE_PWA_ENABLED': JSON.stringify(!isNative),
      __IS_MOBILE__: JSON.stringify(isCapacitor),
      __USE_QR_READER__: JSON.stringify(!isCapacitor)
    }
  };
}
```

### Capacitor Configuration
```typescript
// capacitor.config.ts
const config: CapacitorConfig = {
  plugins: {
    MLKitBarcodeScanner: {
      formats: ['QR_CODE'],
      detectorSize: 1.0,
      lensFacing: 'back',
      googleBarcodeScannerModuleInstallState: true
    }
  }
};
```

## Implementation Steps

1. **Install Dependencies**
```bash
npm install @capacitor-mlkit/barcode-scanning
```

2. **Create Core Types**
Create the interface files as shown above.

3. **Implement Factory**
```typescript
// QRScannerFactory.ts
export class QRScannerFactory {
  private static instance: QRScannerService | null = null;

  private static isNativePlatform(): boolean {
    const capacitorNative = Capacitor.isNativePlatform();
    const isMobile = typeof __IS_MOBILE__ !== "undefined" ? __IS_MOBILE__ : capacitorNative;
    const platform = Capacitor.getPlatform();

    // Always use native scanner on Android/iOS
    if (platform === "android" || platform === "ios") {
      return true;
    }

    // For other platforms, use native if available
    return capacitorNative || isMobile;
  }

  static getInstance(): QRScannerService {
    if (!this.instance) {
      const isNative = this.isNativePlatform();
      
      if (isNative) {
        this.instance = new CapacitorQRScanner();
      } else {
        this.instance = new WebInlineQRScanner();
      }
    }
    return this.instance!;
  }

  static async cleanup(): Promise<void> {
    if (this.instance) {
      await this.instance.cleanup();
      this.instance = null;
    }
  }
}
```

4. **Implement Mobile Scanner**
```typescript
// CapacitorQRScanner.ts
export class CapacitorQRScanner implements QRScannerService {
  private scanListener: ScanListener | null = null;
  private isScanning = false;
  private listenerHandles: Array<() => Promise<void>> = [];
  private cleanupPromise: Promise<void> | null = null;

  async checkPermissions(): Promise<boolean> {
    try {
      const { camera } = await BarcodeScanner.checkPermissions();
      return camera === "granted";
    } catch (error) {
      logger.error("Error checking camera permissions:", error);
      return false;
    }
  }

  async requestPermissions(): Promise<boolean> {
    try {
      if (await this.checkPermissions()) {
        return true;
      }
      const { camera } = await BarcodeScanner.requestPermissions();
      return camera === "granted";
    } catch (error) {
      logger.error("Error requesting camera permissions:", error);
      return false;
    }
  }

  async isSupported(): Promise<boolean> {
    try {
      const { supported } = await BarcodeScanner.isSupported();
      return supported;
    } catch (error) {
      logger.error("Error checking scanner support:", error);
      return false;
    }
  }

  async startScan(options?: QRScannerOptions): Promise<void> {
    if (this.isScanning) return;
    if (this.cleanupPromise) {
      await this.cleanupPromise;
    }

    try {
      if (!(await this.checkPermissions())) {
        const granted = await this.requestPermissions();
        if (!granted) {
          throw new Error("Camera permission denied");
        }
      }

      if (!(await this.isSupported())) {
        throw new Error("QR scanning not supported on this device");
      }

      this.isScanning = true;

      const scanOptions: StartScanOptions = {
        formats: [BarcodeFormat.QrCode],
        lensFacing: options?.camera === "front" ? LensFacing.Front : LensFacing.Back,
      };

      const handle = await BarcodeScanner.addListener("barcodeScanned", (result) => {
        if (this.scanListener && result.barcode?.rawValue) {
          this.scanListener.onScan(result.barcode.rawValue);
        }
      });
      this.listenerHandles.push(handle.remove);

      await BarcodeScanner.startScan(scanOptions);
    } catch (error) {
      this.isScanning = false;
      await this.cleanup();
      this.scanListener?.onError?.(error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async stopScan(): Promise<void> {
    if (!this.isScanning) return;
    this.isScanning = false;

    try {
      await BarcodeScanner.stopScan();
    } catch (error) {
      logger.error("Error stopping scan:", error);
      throw error;
    }
  }

  addListener(listener: ScanListener): void {
    this.scanListener = listener;
  }

  onStream(callback: (stream: MediaStream | null) => void): void {
    // No-op for native scanner
    callback(null);
  }

  async cleanup(): Promise<void> {
    await this.stopScan();
    for (const handle of this.listenerHandles) {
      await handle();
    }
    this.listenerHandles = [];
    this.scanListener = null;
  }
}
```

5. **Implement Web Scanner**
```typescript
// WebInlineQRScanner.ts
export class WebInlineQRScanner implements QRScannerService {
  private scanListener: ScanListener | null = null;
  private isScanning = false;
  private stream: MediaStream | null = null;
  private events = new EventEmitter();

  constructor(private options?: QRScannerOptions) {}

  async checkPermissions(): Promise<boolean> {
    try {
      const permissions = await navigator.permissions.query({
        name: "camera" as PermissionName,
      });
      return permissions.state === "granted";
    } catch (error) {
      logger.error("Error checking camera permissions:", error);
      return false;
    }
  }

  async requestPermissions(): Promise<boolean> {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: {
          facingMode: "environment",
          width: { ideal: 1280 },
          height: { ideal: 720 },
        },
      });
      stream.getTracks().forEach(track => track.stop());
      return true;
    } catch (error) {
      logger.error("Error requesting camera permissions:", error);
      return false;
    }
  }

  async isSupported(): Promise<boolean> {
    return 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices;
  }

  async startScan(): Promise<void> {
    if (this.isScanning) return;

    try {
      this.isScanning = true;
      this.stream = await navigator.mediaDevices.getUserMedia({
        video: {
          facingMode: "environment",
          width: { ideal: 1280 },
          height: { ideal: 720 },
        },
      });
      this.events.emit("stream", this.stream);
    } catch (error) {
      this.isScanning = false;
      const wrappedError = error instanceof Error ? error : new Error(String(error));
      this.scanListener?.onError?.(wrappedError);
      throw wrappedError;
    }
  }

  async stopScan(): Promise<void> {
    if (!this.isScanning) return;

    try {
      if (this.stream) {
        this.stream.getTracks().forEach(track => track.stop());
        this.stream = null;
      }
      this.events.emit("stream", null);
    } catch (error) {
      logger.error("Error stopping scan:", error);
      throw error;
    } finally {
      this.isScanning = false;
    }
  }

  addListener(listener: ScanListener): void {
    this.scanListener = listener;
  }

  onStream(callback: (stream: MediaStream | null) => void): void {
    this.events.on("stream", callback);
  }

  async cleanup(): Promise<void> {
    try {
      await this.stopScan();
      this.events.removeAllListeners();
    } catch (error) {
      logger.error("Error during cleanup:", error);
    }
  }
}
```

## Usage Example

```typescript
// Example usage in a Vue component
import { QRScannerFactory } from '@/services/QRScanner/QRScannerFactory';

export default defineComponent({
  async mounted() {
    const scanner = QRScannerFactory.getInstance();
    
    try {
      // Check and request permissions
      if (!(await scanner.checkPermissions())) {
        const granted = await scanner.requestPermissions();
        if (!granted) {
          throw new Error('Camera permission denied');
        }
      }

      // Add scan listener
      scanner.addListener({
        onScan: (result) => {
          console.log('QR Code scanned:', result);
        },
        onError: (error) => {
          console.error('Scan error:', error);
        }
      });

      // Start scanning
      await scanner.startScan({
        camera: 'back',
        showPreview: true
      });

      // Handle stream for preview
      scanner.onStream((stream) => {
        if (stream) {
          // Update video element with stream
          this.videoElement.srcObject = stream;
        }
      });
    } catch (error) {
      console.error('Failed to start scanner:', error);
    }
  },

  async beforeUnmount() {
    // Clean up scanner
    await QRScannerFactory.cleanup();
  }
});
```

## Best Practices

1. **Error Handling**
   - Always implement error handlers in scan listeners
   - Handle permission denials gracefully
   - Provide user feedback for errors
   - Clean up resources on errors

2. **Resource Management**
   - Always call cleanup when done
   - Stop camera streams properly
   - Remove event listeners
   - Handle component unmounting

3. **Performance**
   - Use appropriate camera resolution
   - Clean up resources promptly
   - Handle platform-specific optimizations
   - Monitor memory usage

4. **Security**
   - Require HTTPS for web implementation
   - Validate scanned data
   - Handle permissions properly
   - Sanitize user input

5. **Testing**
   - Test on multiple devices
   - Verify permission flows
   - Check error scenarios
   - Validate cleanup
   - Test cross-platform behavior

## Platform-Specific Notes

### Mobile (Capacitor)
- Uses MLKit for optimal performance
- Handles native permissions
- Supports both iOS and Android
- Uses back camera by default
- Handles device rotation
- Provides native UI for scanning

### Web
- Uses MediaDevices API
- Requires HTTPS for camera access
- Handles browser compatibility
- Manages memory and resources
- Provides fallback UI
- Uses vue-qrcode-reader for web scanning

## Testing

1. **Unit Tests**
- Test factory pattern
- Test platform detection
- Test error handling
- Test cleanup procedures
- Test permission flows

2. **Integration Tests**
- Test camera access
- Test QR code detection
- Test cross-platform behavior
- Test UI components
- Test error scenarios

3. **E2E Tests**
- Test complete scanning flow
- Test permission handling
- Test cross-platform compatibility
- Test error recovery
- Test cleanup procedures

## Best Practices

1. **Error Handling**
- Always handle permission errors gracefully
- Provide clear error messages to users
- Implement proper cleanup on errors
- Log errors for debugging

2. **Performance**
- Clean up resources when not in use
- Handle device rotation properly
- Optimize camera usage
- Manage memory efficiently

3. **Security**
- Request minimum required permissions
- Handle sensitive data securely
- Validate scanned data
- Implement proper cleanup

4. **User Experience**
- Provide clear feedback
- Handle edge cases gracefully
- Support both platforms seamlessly
- Implement proper loading states

## Troubleshooting

1. **Common Issues**
- Camera permissions denied
- Device not supported
- Scanner not working
- Memory leaks
- UI glitches

2. **Solutions**
- Check permissions
- Verify device support
- Debug scanner implementation
- Monitor memory usage
- Test UI components

## Maintenance

1. **Regular Updates**
- Keep dependencies updated
- Monitor platform changes
- Update documentation
- Review security patches

2. **Performance Monitoring**
- Track memory usage
- Monitor camera performance
- Check error rates
- Analyze user feedback