Browse Source

Merge branch 'qrcode-reboot' of ssh://173.199.124.46:222/trent_larson/crowd-funder-for-time-pwa into qrcode-reboot

qrcode-reboot
Jose Olarte III 1 day ago
parent
commit
efab9b968c
  1. 177
      .cursor/rules/qr-code-handling-rule.mdc
  2. 533
      .cursor/rules/qr-code-implementation-guide.mdc
  3. 0
      doc/DEEP_LINKS.md
  4. 284
      doc/qr-code-implementation-guide.md
  5. 0
      doc/web-push.md
  6. 507
      docs/camera-implementation.md
  7. 156
      qr-code-implementation-guide.md
  8. 119
      src/services/QRScanner/WebInlineQRScanner.ts
  9. 20
      src/services/QRScanner/types.ts
  10. 116
      src/views/ContactQRScanShowView.vue

177
.cursor/rules/qr-code-handling-rule.mdc

@ -1,177 +0,0 @@
---
description:
globs:
alwaysApply: true
---
# QR Code Handling Rule
## Architecture Overview
The QR code scanning functionality follows a platform-agnostic design using a factory pattern that provides different implementations for web and mobile platforms.
### Core Components
1. **Factory Pattern**
- `QRScannerFactory` - Creates appropriate scanner instance based on platform
- Common interface `QRScannerService` implemented by all scanners
- Platform detection via Capacitor and build flags
2. **Platform-Specific Implementations**
- `CapacitorQRScanner` - Native mobile implementation using MLKit
- `WebInlineQRScanner` - Web browser implementation using MediaDevices API
- `QRScannerDialog.vue` - Shared UI component
## Mobile Implementation (Capacitor)
### Technology Stack
- Uses `@capacitor-mlkit/barcode-scanning` plugin
- Configured in `capacitor.config.ts`
- Native camera access through platform APIs
### Key Features
- Direct camera access via native APIs
- Optimized for mobile performance
- Supports both iOS and Android
- Real-time QR code detection
- Back camera preferred for scanning
### Configuration
```typescript
MLKitBarcodeScanner: {
formats: ['QR_CODE'],
detectorSize: 1.0,
lensFacing: 'back',
googleBarcodeScannerModuleInstallState: true
}
```
### Permissions Handling
1. Check permissions via `BarcodeScanner.checkPermissions()`
2. Request permissions if needed
3. Handle permission states (granted/denied)
4. Graceful fallbacks for permission issues
## Web Implementation
### Technology Stack
- Uses browser's MediaDevices API
- Vue.js components for UI
- EventEmitter for stream management
### Key Features
- Browser-based camera access
- Inline camera preview
- Responsive design
- Cross-browser compatibility
- Progressive enhancement
### Permissions Handling
1. Uses browser's permission API
2. MediaDevices API for camera access
3. Handles secure context requirements
4. Provides user feedback for permission states
## Shared Features
### Error Handling
1. Permission denied scenarios
2. Device compatibility checks
3. Camera access failures
4. QR code validation
5. Network connectivity issues
### User Experience
1. Clear feedback during scanning
2. Loading states
3. Error messages
4. Success confirmations
5. Camera preview
### Security
1. HTTPS requirement for web
2. Permission validation
3. Data validation
4. Safe error handling
## Usage Guidelines
### Platform Detection
```typescript
const isNative = QRScannerFactory.isNativePlatform();
if (isNative) {
// Use native scanner
} else {
// Use web scanner
}
```
### Implementation Example
```typescript
const scanner = QRScannerFactory.getInstance();
await scanner.checkPermissions();
await scanner.startScan();
scanner.addListener({
onScan: (result) => {
// Handle scan result
},
onError: (error) => {
// Handle error
}
});
```
### Best Practices
1. Always check permissions before starting scan
2. Clean up resources after scanning
3. Handle all error cases
4. Provide clear user feedback
5. Test on multiple devices/browsers
## Platform-Specific Notes
### Mobile (Capacitor)
1. Use native camera API when available
2. Handle device rotation
3. Support both front/back cameras
4. Manage system permissions properly
5. Handle app lifecycle events
### Web
1. Check browser compatibility
2. Handle secure context requirement
3. Manage memory usage
4. Clean up MediaStream
5. Handle tab visibility changes
## Testing Requirements
1. Test on multiple devices
2. Verify permission flows
3. Check error handling
4. Validate cleanup
5. Verify cross-platform behavior
## Service Interface
```typescript
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>;
}
interface ScanListener {
onScan: (result: string) => void;
onError?: (error: Error) => void;
}
interface QRScannerOptions {
camera?: "front" | "back";
showPreview?: boolean;
playSound?: boolean;
}

533
.cursor/rules/qr-code-implementation-guide.mdc

@ -1,533 +0,0 @@
---
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

0
docs/DEEP_LINKS.md → doc/DEEP_LINKS.md

284
doc/qr-code-implementation-guide.md

@ -0,0 +1,284 @@
# QR Code Implementation Guide
## Overview
This document describes the QR code scanning and generation implementation in the TimeSafari application. The system uses a platform-agnostic design with specific implementations for web and mobile platforms.
## Architecture
### 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
└── views/
├── ContactQRScanView.vue # Dedicated scanning view
└── ContactQRScanShowView.vue # Combined QR display and scanning view
```
### Core Components
1. **Factory Pattern**
- `QRScannerFactory` - Creates appropriate scanner instance based on platform
- Common interface `QRScannerService` implemented by all scanners
- Platform detection via Capacitor and build flags
2. **Platform-Specific Implementations**
- `CapacitorQRScanner` - Native mobile implementation using MLKit
- `WebInlineQRScanner` - Web browser implementation using MediaDevices API
- `QRScannerDialog.vue` - Shared UI component
3. **View Components**
- `ContactQRScanView` - Dedicated view for scanning QR codes
- `ContactQRScanShowView` - Combined view for displaying and scanning QR codes
## Implementation Details
### Core Interfaces
```typescript
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>;
}
interface ScanListener {
onScan: (result: string) => void;
onError?: (error: Error) => void;
}
interface QRScannerOptions {
camera?: "front" | "back";
showPreview?: boolean;
playSound?: boolean;
}
```
### Platform-Specific Implementations
#### Mobile (Capacitor)
- Uses `@capacitor-mlkit/barcode-scanning`
- Native camera access through platform APIs
- Optimized for mobile performance
- Supports both iOS and Android
- Real-time QR code detection
- Back camera preferred for scanning
Configuration:
```typescript
// capacitor.config.ts
const config: CapacitorConfig = {
plugins: {
MLKitBarcodeScanner: {
formats: ['QR_CODE'],
detectorSize: 1.0,
lensFacing: 'back',
googleBarcodeScannerModuleInstallState: true
}
}
};
```
#### Web
- Uses browser's MediaDevices API
- Vue.js components for UI
- EventEmitter for stream management
- Browser-based camera access
- Inline camera preview
- Responsive design
- Cross-browser compatibility
### View Components
#### ContactQRScanView
- Dedicated view for scanning QR codes
- Full-screen camera interface
- Simple UI focused on scanning
- Used primarily on native platforms
- Streamlined scanning experience
#### ContactQRScanShowView
- Combined view for QR code display and scanning
- Shows user's own QR code
- Handles user registration status
- Provides options to copy contact information
- Platform-specific scanning implementation:
- Native: Button to navigate to ContactQRScanView
- Web: Built-in scanning functionality
### QR Code Workflow
1. **Initiation**
- User selects "Scan QR Code" option
- Platform-specific scanner is initialized
- Camera permissions are verified
- Appropriate scanner component is loaded
2. **Platform-Specific Implementation**
- Web: Uses `qrcode-stream` for real-time scanning
- Native: Uses `@capacitor-mlkit/barcode-scanning`
3. **Scanning Process**
- Camera stream initialization
- Real-time frame analysis
- QR code detection and decoding
- Validation of QR code format
- Processing of contact information
4. **Contact Processing**
- Decryption of contact data
- Validation of user information
- Verification of timestamp
- Check for duplicate contacts
- Processing of shared data
## Build Configuration
### Common Vite Configuration
```typescript
// vite.config.common.mts
export async function createBuildConfig(mode: string) {
const isCapacitor = mode === "capacitor";
return defineConfig({
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)
},
optimizeDeps: {
include: [
'@capacitor-mlkit/barcode-scanning',
'vue-qrcode-reader'
]
}
});
}
```
### Platform-Specific Builds
```json
{
"scripts": {
"build:web": "vite build --config vite.config.web.mts",
"build:capacitor": "vite build --config vite.config.capacitor.mts",
"build:all": "npm run build:web && npm run build:capacitor"
}
}
```
## Error Handling
### Common Error Scenarios
1. No camera found
2. Permission denied
3. Camera in use by another application
4. HTTPS required
5. Browser compatibility issues
6. Invalid QR code format
7. Expired QR codes
8. Duplicate contact attempts
9. Network connectivity issues
### Error Response
- User-friendly error messages
- Troubleshooting tips
- Clear instructions for resolution
- Platform-specific guidance
## Security Considerations
### QR Code Security
- Encryption of contact data
- Timestamp validation
- Version checking
- User verification
- Rate limiting for scans
### Data Protection
- Secure transmission of contact data
- Validation of QR code authenticity
- Prevention of duplicate scans
- Protection against malicious codes
- Secure storage of contact information
## Best Practices
### Camera Access
1. Always check for camera availability
2. Request permissions explicitly
3. Handle all error conditions
4. Provide clear user feedback
5. Implement proper cleanup
### Performance
1. Optimize camera resolution
2. Implement proper resource cleanup
3. Handle camera switching efficiently
4. Manage memory usage
5. Battery usage optimization
### User Experience
1. Clear visual feedback
2. Camera preview
3. Scanning status indicators
4. Error messages
5. Success confirmations
6. Intuitive camera controls
7. Smooth camera switching
8. Responsive UI feedback
## Testing
### Test Scenarios
1. Permission handling
2. Camera switching
3. Error conditions
4. Platform compatibility
5. Performance metrics
6. QR code detection
7. Contact processing
8. Security validation
### Test Environment
- Multiple browsers
- iOS and Android devices
- Various network conditions
- Different camera configurations
## Dependencies
### Key Packages
- `@capacitor-mlkit/barcode-scanning`
- `qrcode-stream`
- `vue-qrcode-reader`
- Platform-specific camera APIs
## Maintenance
### Regular Updates
- Keep dependencies updated
- Monitor platform changes
- Update documentation
- Review security patches
### Performance Monitoring
- Track memory usage
- Monitor camera performance
- Check error rates
- Analyze user feedback

0
web-push.md → doc/web-push.md

507
docs/camera-implementation.md

@ -1,507 +0,0 @@
# Camera Implementation Documentation
## Overview
This document describes how camera functionality is implemented across the TimeSafari application. The application uses cameras for several purposes:
1. QR Code scanning for contact sharing and verification
2. Photo capture for gift records
3. Profile photo management
4. Shared photo handling
5. Image upload and processing
## Components
### QRScannerDialog.vue
Primary component for QR code scanning in web browsers.
**Key Features:**
- Uses `qrcode-stream` for web-based QR scanning
- Supports both front and back cameras
- Provides real-time camera status feedback
- Implements error handling with user-friendly messages
- Includes camera switching functionality
**Camera Access Flow:**
1. Checks for camera API availability
2. Enumerates available video devices
3. Requests camera permissions
4. Initializes camera stream with preferred settings
5. Handles various error conditions with specific messages
### PhotoDialog.vue
Component for photo capture and selection.
**Key Features:**
- Cross-platform photo capture interface
- Image cropping capabilities
- File selection fallback
- Unified interface for different platforms
- Progress feedback during upload
- Comprehensive error handling
**Camera Access Flow:**
1. User initiates photo capture
2. Platform-specific camera access is requested
3. Image is captured or selected
4. Optional cropping is performed
5. Image is processed and uploaded
6. URL is returned to caller
### ImageMethodDialog.vue
Component for selecting image input method.
**Key Features:**
- Multiple input methods (camera, file upload, URL)
- Unified interface for image selection
- Integration with PhotoDialog for processing
- Support for image cropping
- URL-based image handling
**Camera Access Flow:**
1. User selects camera option
2. PhotoDialog is opened for capture
3. Captured image is processed
4. Image is returned to parent component
### SharedPhotoView.vue
Component for handling shared photos.
**Key Features:**
- Processes incoming shared photos
- Options to use photo for gifts or profile
- Image preview and confirmation
- Server upload integration
- Temporary storage management
**Photo Processing Flow:**
1. Photo is shared to application
2. Stored temporarily in IndexedDB
3. User chooses usage (gift/profile)
4. Image is processed accordingly
5. Server upload is performed
### ContactQRScanShowView.vue
Component for QR code scanning in contact sharing.
**Key Features:**
- QR code scanning interface
- Camera controls (start/stop)
- Platform-specific implementations
- Error handling and status feedback
**Camera Access Flow:**
1. User initiates scanning
2. Camera permissions are checked
3. Camera stream is initialized
4. QR codes are detected in real-time
5. Results are processed
## Services
### QRScanner Services
#### WebDialogQRScanner
Web-based implementation of QR scanning.
**Key Methods:**
- `checkPermissions()`: Verifies camera permission status
- `requestPermissions()`: Requests camera access
- `isSupported()`: Checks for camera API support
- Handles various error conditions with specific messages
#### CapacitorQRScanner
Native implementation using Capacitor's MLKit.
**Key Features:**
- Uses `@capacitor-mlkit/barcode-scanning`
- Supports both front and back cameras
- Implements permission management
- Provides continuous scanning capability
### Platform Services
#### WebPlatformService
Web-specific implementation of platform features.
**Camera Capabilities:**
- Uses HTML5 file input with capture attribute for mobile
- Uses getUserMedia API for desktop webcam access
- Falls back to file selection if camera unavailable
- Processes captured images for consistent format
- Handles both mobile and desktop browser environments
#### CapacitorPlatformService
Native implementation using Capacitor.
**Camera Features:**
- Uses `Camera.getPhoto()` for native camera access
- Supports image editing
- Configures high-quality image capture
- Handles base64 image processing
- Provides native camera UI
#### ElectronPlatformService
Desktop implementation (currently unimplemented).
**Status:**
- Camera functionality not yet implemented
- Planned to use Electron's media APIs
- Will support desktop camera access
## Camera Usage Scenarios
### Gift Photo Capture
**Implementation:**
- Uses PhotoDialog for capture/selection
- Supports multiple input methods
- Optional image cropping
- Server upload with authentication
- Integration with gift records
**Flow:**
1. User initiates photo capture from gift details
2. ImageMethodDialog presents input options
3. PhotoDialog handles capture/selection
4. Image is processed and uploaded
5. URL is attached to gift record
### Profile Photo Management
**Implementation:**
- Uses same PhotoDialog component
- Enforces square aspect ratio
- Requires image cropping
- Updates user profile settings
- Handles profile image updates
**Flow:**
1. User initiates profile photo update
2. PhotoDialog opens with cropping enabled
3. Image is captured/selected
4. User crops to square aspect ratio
5. Image is uploaded and profile updated
### Shared Photo Processing
**Implementation:**
- Handles incoming shared photos
- Temporary storage in IndexedDB
- Options for photo usage
- Server upload integration
- Cleanup after processing
**Flow:**
1. Photo is shared to application
2. Stored temporarily in IndexedDB
3. SharedPhotoView presents options
4. User chooses usage (gift/profile)
5. Image is processed accordingly
### QR Code Scanning
**Implementation:**
- Platform-specific scanning components
- Real-time camera feed processing
- QR code detection and validation
- Contact information processing
- Error handling and retry
**Flow:**
1. User initiates QR scanning
2. Camera permissions are checked
3. Camera stream is initialized
4. QR codes are detected
5. Contact information is processed
### QR Code Workflow
**Implementation Details:**
The QR code scanning workflow is implemented across multiple components and services to provide a seamless experience for contact sharing and verification. The system supports both web-based and native implementations through platform-specific services.
#### QR Code Generation
**Contact QR Codes:**
- Generated using `qrcode.vue` component
- Contains encrypted contact information
- Includes user ID and verification data
- Supports offline sharing
- Implements error correction
**QR Code Format:**
```json
{
"type": "contact",
"userId": "encrypted_user_id",
"timestamp": "creation_time",
"version": "qr_code_version",
"data": "encrypted_contact_data"
}
```
#### QR Code Scanning Workflow
**1. Initiation:**
- User selects "Scan QR Code" option
- Platform-specific scanner is initialized
- Camera permissions are verified
- Appropriate scanner component is loaded
**2. Platform-Specific Implementation:**
*Web Implementation:*
- Uses `qrcode-stream` for real-time scanning
- Supports both front and back cameras
- Implements continuous scanning
- Provides visual feedback for scanning status
- Handles browser compatibility issues
*Native Implementation (Capacitor):*
- Uses `@capacitor-mlkit/barcode-scanning`
- Leverages native camera capabilities
- Provides optimized scanning performance
- Supports multiple barcode formats
- Implements native permission handling
**3. Scanning Process:**
- Camera stream is initialized
- Real-time frame analysis begins
- QR codes are detected and decoded
- Validation of QR code format
- Processing of contact information
**4. Contact Processing:**
- Decryption of contact data
- Validation of user information
- Verification of timestamp
- Check for duplicate contacts
- Processing of shared data
**5. Error Handling:**
- Invalid QR code format
- Expired QR codes
- Duplicate contact attempts
- Network connectivity issues
- Permission denials
- Camera access problems
**6. Success Flow:**
- Contact information is extracted
- User is prompted for confirmation
- Contact is added to user's list
- Success notification is displayed
- Camera resources are cleaned up
#### Security Measures
**QR Code Security:**
- Encryption of contact data
- Timestamp validation
- Version checking
- User verification
- Rate limiting for scans
**Data Protection:**
- Secure transmission of contact data
- Validation of QR code authenticity
- Prevention of duplicate scans
- Protection against malicious codes
- Secure storage of contact information
#### User Experience
**Scanning Interface:**
- Clear visual feedback
- Camera preview
- Scanning status indicators
- Error messages
- Success confirmations
**Accessibility:**
- Support for different screen sizes
- Clear instructions
- Error recovery options
- Alternative input methods
- Offline capability
#### Performance Considerations
**Optimization:**
- Efficient camera resource usage
- Quick QR code detection
- Minimal processing overhead
- Battery usage optimization
- Memory management
**Platform-Specific Optimizations:**
- Web: Optimized for browser performance
- Native: Leverages device capabilities
- Desktop: Efficient resource usage
- Mobile: Battery and performance balance
## Platform-Specific Considerations
### iOS
- Requires `NSCameraUsageDescription` in Info.plist
- Supports both front and back cameras
- Implements proper permission handling
- Uses native camera UI through Capacitor
- Handles photo library access
### Android
- Requires camera permissions in manifest
- Supports both front and back cameras
- Handles permission requests through Capacitor
- Uses native camera UI
- Manages photo library access
### Web
- Requires HTTPS for camera access
- Implements fallback mechanisms
- Handles browser compatibility issues
- Uses getUserMedia API on desktop
- Uses file input with capture on mobile
- Supports multiple input methods
## Error Handling
### Common Error Scenarios
1. No camera found
2. Permission denied
3. Camera in use by another application
4. HTTPS required
5. Browser compatibility issues
6. Upload failures
7. Image processing errors
### Error Response
- User-friendly error messages
- Troubleshooting tips
- Clear instructions for resolution
- Platform-specific guidance
- Graceful fallbacks
## Security Considerations
### Permission Management
- Explicit permission requests
- Permission state tracking
- Graceful handling of denied permissions
- Platform-specific permission handling
- Secure permission storage
### Data Handling
- Secure image processing
- Proper cleanup of camera resources
- No persistent storage of camera data
- Secure server upload
- Temporary storage management
## Best Practices
### Camera Access
1. Always check for camera availability
2. Request permissions explicitly
3. Handle all error conditions
4. Provide clear user feedback
5. Implement proper cleanup
6. Use platform-specific optimizations
### Performance
1. Optimize camera resolution
2. Implement proper resource cleanup
3. Handle camera switching efficiently
4. Manage memory usage
5. Optimize image processing
6. Handle upload efficiently
### User Experience
1. Clear status indicators
2. Intuitive camera controls
3. Helpful error messages
4. Smooth camera switching
5. Responsive UI feedback
6. Platform-appropriate UI
## Future Improvements
### Planned Enhancements
1. Implement Electron camera support
2. Add advanced camera features
3. Improve error handling
4. Enhance user feedback
5. Optimize performance
6. Add image compression options
### Known Issues
1. Electron camera implementation pending
2. Some browser compatibility limitations
3. Platform-specific quirks to address
4. Mobile browser camera access limitations
5. Image upload performance on slow connections
## Dependencies
### Key Packages
- `@capacitor-mlkit/barcode-scanning`
- `qrcode-stream`
- `vue-picture-cropper`
- `@capacitor/camera`
- Platform-specific camera APIs
## Testing
### Test Scenarios
1. Permission handling
2. Camera switching
3. Error conditions
4. Platform compatibility
5. Performance metrics
6. Upload scenarios
7. Image processing
### Test Environment
- Multiple browsers
- iOS and Android devices
- Desktop platforms
- Various network conditions
- Different camera configurations

156
qr-code-implementation-guide.md

@ -1,156 +0,0 @@
## Build Configuration
### Common Vite Configuration
```typescript
// vite.config.common.mts
export async function createBuildConfig(mode: string) {
const isCapacitor = mode === "capacitor";
return defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'vue-facing-decorator']
}
}
}
},
define: {
__USE_QR_READER__: JSON.stringify(!isCapacitor),
__IS_MOBILE__: JSON.stringify(isCapacitor),
},
optimizeDeps: {
include: [
'@capacitor-mlkit/barcode-scanning',
'vue-qrcode-reader'
]
},
resolve: {
alias: {
'@capacitor/app': path.resolve(__dirname, 'node_modules/@capacitor/app')
}
}
});
}
```
### Web-Specific Configuration
```typescript
// vite.config.web.mts
import { defineConfig, mergeConfig } from "vite";
import { createBuildConfig } from "./vite.config.common.mts";
export default defineConfig(async () => {
const baseConfig = await createBuildConfig('web');
return mergeConfig(baseConfig, {
define: {
__USE_QR_READER__: true,
__IS_MOBILE__: false,
}
});
});
```
### Capacitor-Specific Configuration
```typescript
// vite.config.capacitor.mts
import { defineConfig, mergeConfig } from "vite";
import { createBuildConfig } from "./vite.config.common.mts";
export default defineConfig(async () => {
const baseConfig = await createBuildConfig('capacitor');
return mergeConfig(baseConfig, {
define: {
__USE_QR_READER__: false,
__IS_MOBILE__: true,
},
build: {
rollupOptions: {
external: ['vue-qrcode-reader'], // Exclude web QR reader from mobile builds
output: {
entryFileNames: '[name]-mobile.js',
chunkFileNames: '[name]-mobile.js',
assetFileNames: '[name]-mobile.[ext]'
}
}
}
});
});
```
### Build Scripts
Add these scripts to your `package.json`:
```json
{
"scripts": {
"build:web": "vite build --config vite.config.web.mts",
"build:capacitor": "vite build --config vite.config.capacitor.mts",
"build:all": "npm run build:web && npm run build:capacitor"
}
}
```
### Environment Variables
Create a `.env` file:
```bash
# QR Scanner Configuration
VITE_QR_SCANNER_ENABLED=true
VITE_DEFAULT_CAMERA=back
```
### Build Process
1. **Web Build**
```bash
npm run build:web
```
This will:
- Include vue-qrcode-reader
- Set __USE_QR_READER__ to true
- Set __IS_MOBILE__ to false
- Build for web browsers
2. **Capacitor Build**
```bash
npm run build:capacitor
```
This will:
- Exclude vue-qrcode-reader
- Set __USE_QR_READER__ to false
- Set __IS_MOBILE__ to true
- Build for mobile platforms
3. **Build Both**
```bash
npm run build:all
```
### Important Notes
1. **Dependencies**
- Ensure all QR-related dependencies are properly listed in package.json
- Use exact versions to avoid compatibility issues
- Consider using peer dependencies for shared libraries
2. **Bundle Size**
- Web build includes vue-qrcode-reader (~100KB)
- Mobile build includes @capacitor-mlkit/barcode-scanning (~50KB)
- Consider using dynamic imports for lazy loading
3. **Platform Detection**
- Build flags determine which implementation to use
- Runtime checks provide fallback options
- Environment variables can override defaults
4. **Performance**
- Mobile builds optimize for native performance
- Web builds include necessary polyfills
- Chunk splitting improves load times
5. **Debugging**
- Source maps are enabled for development
- Build artifacts are properly named for identification
- Console logs help track initialization

119
src/services/QRScanner/WebInlineQRScanner.ts

@ -1,4 +1,4 @@
import { QRScannerService, ScanListener, QRScannerOptions } from "./types";
import { QRScannerService, ScanListener, QRScannerOptions, CameraState, CameraStateListener } from "./types";
import { logger } from "@/utils/logger";
import { EventEmitter } from "events";
import jsQR from "jsqr";
@ -21,6 +21,9 @@ export class WebInlineQRScanner implements QRScannerService {
private readonly TARGET_FPS = 15; // Target 15 FPS for scanning
private readonly FRAME_INTERVAL = 1000 / 15; // ~67ms between frames
private lastFrameTime = 0;
private cameraStateListeners: Set<CameraStateListener> = new Set();
private currentState: CameraState = 'off';
private currentStateMessage?: string;
constructor(private options?: QRScannerOptions) {
// Generate a short random ID for this scanner instance
@ -43,8 +46,35 @@ export class WebInlineQRScanner implements QRScannerService {
);
}
private updateCameraState(state: CameraState, message?: string) {
this.currentState = state;
this.currentStateMessage = message;
this.cameraStateListeners.forEach(listener => {
try {
listener.onStateChange(state, message);
logger.info(`[WebInlineQRScanner:${this.id}] Camera state changed to: ${state}`, {
state,
message,
});
} catch (error) {
logger.error(`[WebInlineQRScanner:${this.id}] Error in camera state listener:`, error);
}
});
}
addCameraStateListener(listener: CameraStateListener): void {
this.cameraStateListeners.add(listener);
// Immediately notify the new listener of current state
listener.onStateChange(this.currentState, this.currentStateMessage);
}
removeCameraStateListener(listener: CameraStateListener): void {
this.cameraStateListeners.delete(listener);
}
async checkPermissions(): Promise<boolean> {
try {
this.updateCameraState('initializing', 'Checking camera permissions...');
logger.error(
`[WebInlineQRScanner:${this.id}] Checking camera permissions...`,
);
@ -55,7 +85,9 @@ export class WebInlineQRScanner implements QRScannerService {
`[WebInlineQRScanner:${this.id}] Permission state:`,
permissions.state,
);
return permissions.state === "granted";
const granted = permissions.state === "granted";
this.updateCameraState(granted ? 'ready' : 'permission_denied');
return granted;
} catch (error) {
logger.error(
`[WebInlineQRScanner:${this.id}] Error checking camera permissions:`,
@ -64,12 +96,14 @@ export class WebInlineQRScanner implements QRScannerService {
stack: error instanceof Error ? error.stack : undefined,
},
);
this.updateCameraState('error', 'Error checking camera permissions');
return false;
}
}
async requestPermissions(): Promise<boolean> {
try {
this.updateCameraState('initializing', 'Requesting camera permissions...');
logger.error(
`[WebInlineQRScanner:${this.id}] Requesting camera permissions...`,
);
@ -107,10 +141,8 @@ export class WebInlineQRScanner implements QRScannerService {
},
});
logger.error(
`[WebInlineQRScanner:${this.id}] Camera stream obtained successfully`,
);
this.updateCameraState('ready', 'Camera permissions granted');
// Stop the test stream immediately
stream.getTracks().forEach((track) => {
logger.error(`[WebInlineQRScanner:${this.id}] Stopping test track:`, {
@ -122,36 +154,20 @@ export class WebInlineQRScanner implements QRScannerService {
});
return true;
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error(
`[WebInlineQRScanner:${this.id}] Error requesting camera permissions:`,
{
error: wrappedError.message,
stack: wrappedError.stack,
name: wrappedError.name,
},
);
// Provide more specific error messages
if (
wrappedError.name === "NotFoundError" ||
wrappedError.name === "DevicesNotFoundError"
) {
const wrappedError = error instanceof Error ? error : new Error(String(error));
// Update state based on error type
if (wrappedError.name === "NotFoundError" || wrappedError.name === "DevicesNotFoundError") {
this.updateCameraState('not_found', 'No camera found on this device');
throw new Error("No camera found on this device");
} else if (
wrappedError.name === "NotAllowedError" ||
wrappedError.name === "PermissionDeniedError"
) {
throw new Error(
"Camera access denied. Please grant camera permission and try again",
);
} else if (
wrappedError.name === "NotReadableError" ||
wrappedError.name === "TrackStartError"
) {
} else if (wrappedError.name === "NotAllowedError" || wrappedError.name === "PermissionDeniedError") {
this.updateCameraState('permission_denied', 'Camera access denied');
throw new Error("Camera access denied. Please grant camera permission and try again");
} else if (wrappedError.name === "NotReadableError" || wrappedError.name === "TrackStartError") {
this.updateCameraState('in_use', 'Camera is in use by another application');
throw new Error("Camera is in use by another application");
} else {
this.updateCameraState('error', wrappedError.message);
throw new Error(`Camera error: ${wrappedError.message}`);
}
}
@ -390,6 +406,7 @@ export class WebInlineQRScanner implements QRScannerService {
this.isScanning = true;
this.scanAttempts = 0;
this.lastScanTime = Date.now();
this.updateCameraState('initializing', 'Starting camera...');
logger.error(`[WebInlineQRScanner:${this.id}] Starting scan`);
// Get camera stream
@ -404,6 +421,8 @@ export class WebInlineQRScanner implements QRScannerService {
},
});
this.updateCameraState('active', 'Camera is active');
logger.error(`[WebInlineQRScanner:${this.id}] Camera stream obtained:`, {
tracks: this.stream.getTracks().map((t) => ({
kind: t.kind,
@ -429,13 +448,15 @@ export class WebInlineQRScanner implements QRScannerService {
this.scanQRCode();
} catch (error) {
this.isScanning = false;
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error(`[WebInlineQRScanner:${this.id}] Error starting scan:`, {
error: wrappedError.message,
stack: wrappedError.stack,
name: wrappedError.name,
});
const wrappedError = error instanceof Error ? error : new Error(String(error));
// Update state based on error type
if (wrappedError.name === "NotReadableError" || wrappedError.name === "TrackStartError") {
this.updateCameraState('in_use', 'Camera is in use by another application');
} else {
this.updateCameraState('error', wrappedError.message);
}
if (this.scanListener?.onError) {
this.scanListener.onError(wrappedError);
}
@ -492,14 +513,9 @@ export class WebInlineQRScanner implements QRScannerService {
`[WebInlineQRScanner:${this.id}] Stream stopped event emitted`,
);
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error(`[WebInlineQRScanner:${this.id}] Error stopping scan:`, {
error: wrappedError.message,
stack: wrappedError.stack,
name: wrappedError.name,
});
throw wrappedError;
logger.error(`[WebInlineQRScanner:${this.id}] Error stopping scan:`, error);
this.updateCameraState('error', 'Error stopping camera');
throw error;
} finally {
this.isScanning = false;
logger.error(`[WebInlineQRScanner:${this.id}] Scan stopped successfully`);
@ -541,10 +557,9 @@ export class WebInlineQRScanner implements QRScannerService {
`[WebInlineQRScanner:${this.id}] Cleanup completed successfully`,
);
} catch (error) {
logger.error(`[WebInlineQRScanner:${this.id}] Error during cleanup:`, {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
logger.error(`[WebInlineQRScanner:${this.id}] Error during cleanup:`, error);
this.updateCameraState('error', 'Error during cleanup');
throw error;
}
}
}

20
src/services/QRScanner/types.ts

@ -22,6 +22,20 @@ export interface QRScannerOptions {
playSound?: boolean;
}
export type CameraState =
| 'initializing' // Camera is being initialized
| 'ready' // Camera is ready to use
| 'active' // Camera is actively streaming
| 'in_use' // Camera is in use by another application
| 'permission_denied' // Camera permission was denied
| 'not_found' // No camera found on device
| 'error' // Generic error state
| 'off'; // Camera is off/stopped
export interface CameraStateListener {
onStateChange: (state: CameraState, message?: string) => void;
}
/**
* Interface for QR scanner service implementations
*/
@ -44,6 +58,12 @@ export interface QRScannerService {
/** Add a listener for scan events */
addListener(listener: ScanListener): void;
/** Add a listener for camera state changes */
addCameraStateListener(listener: CameraStateListener): void;
/** Remove a camera state listener */
removeCameraStateListener(listener: CameraStateListener): void;
/** Clean up scanner resources */
cleanup(): Promise<void>;
}

116
src/views/ContactQRScanShowView.vue

@ -88,7 +88,7 @@
class="absolute top-0 left-0 right-0 bg-black bg-opacity-50 text-white text-sm text-center py-2 z-10"
>
<div
v-if="isInitializing"
v-if="cameraState === 'initializing'"
class="flex items-center justify-center space-x-2"
>
<svg
@ -112,10 +112,10 @@
3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<span>{{ initializationStatus }}</span>
<span>{{ cameraStateMessage || 'Initializing camera...' }}</span>
</div>
<p
v-else-if="isScanning"
v-else-if="cameraState === 'active'"
class="flex items-center justify-center space-x-2"
>
<span
@ -125,8 +125,14 @@
</p>
<p v-else-if="error" class="text-red-400">Error: {{ error }}</p>
<p v-else class="flex items-center justify-center space-x-2">
<span class="inline-block w-2 h-2 bg-blue-500 rounded-full"></span>
<span>Ready to scan</span>
<span :class="{
'inline-block w-2 h-2 rounded-full': true,
'bg-green-500': cameraState === 'ready',
'bg-yellow-500': cameraState === 'in_use',
'bg-red-500': cameraState === 'error' || cameraState === 'permission_denied' || cameraState === 'not_found',
'bg-blue-500': cameraState === 'off'
}"></span>
<span>{{ cameraStateMessage || 'Ready to scan' }}</span>
</p>
</div>
@ -202,6 +208,7 @@ import { retrieveAccountMetadata } from "../libs/util";
import { Router } from "vue-router";
import { logger } from "../utils/logger";
import { QRScannerFactory } from "@/services/QRScanner/QRScannerFactory";
import { CameraState } from "@/services/QRScanner/types";
interface QRScanResult {
rawValue?: string;
@ -239,7 +246,8 @@ export default class ContactQRScanShow extends Vue {
initializationStatus = "Initializing camera...";
useQRReader = __USE_QR_READER__;
preferredCamera: "user" | "environment" = "environment";
cameraStatus = "Initializing";
cameraState: CameraState = 'off';
cameraStateMessage?: string;
ETHR_DID_PREFIX = ETHR_DID_PREFIX;
@ -303,65 +311,87 @@ export default class ContactQRScanShow extends Vue {
try {
this.error = null;
this.isScanning = true;
this.isInitializing = true;
this.initializationStatus = "Initializing camera...";
this.lastScannedValue = "";
this.lastScanTime = 0;
const scanner = QRScannerFactory.getInstance();
// Check if scanning is supported first
if (!(await scanner.isSupported())) {
this.error =
"Camera access requires HTTPS. Please use a secure connection.";
// Add camera state listener
scanner.addCameraStateListener({
onStateChange: (state, message) => {
this.cameraState = state;
this.cameraStateMessage = message;
// Update UI based on camera state
switch (state) {
case 'in_use':
this.error = "Camera is in use by another application";
this.isScanning = false;
this.isInitializing = false;
this.$notify(
{
group: "alert",
type: "warning",
title: "HTTPS Required",
text: "Camera access requires a secure (HTTPS) connection",
title: "Camera in Use",
text: "Please close other applications using the camera and try again",
},
5000,
);
return;
}
// Check permissions first
if (!(await scanner.checkPermissions())) {
this.initializationStatus = "Requesting camera permission...";
const granted = await scanner.requestPermissions();
if (!granted) {
break;
case 'permission_denied':
this.error = "Camera permission denied";
this.isScanning = false;
this.isInitializing = false;
// Show notification for better visibility
this.$notify(
{
group: "alert",
type: "warning",
title: "Camera Access Required",
text: "Camera permission denied",
text: "Please grant camera permission to scan QR codes",
},
5000,
);
return;
}
}
// For native platforms, use the scanner service
scanner.addListener({
onScan: this.onScanDetect,
onError: this.onScanError,
break;
case 'not_found':
this.error = "No camera found";
this.isScanning = false;
this.$notify(
{
group: "alert",
type: "warning",
title: "No Camera",
text: "No camera was found on this device",
},
5000,
);
break;
case 'error':
this.error = this.cameraStateMessage || "Camera error";
this.isScanning = false;
break;
}
},
});
// Check if scanning is supported first
if (!(await scanner.isSupported())) {
this.error = "Camera access requires HTTPS. Please use a secure connection.";
this.isScanning = false;
this.$notify(
{
group: "alert",
type: "warning",
title: "HTTPS Required",
text: "Camera access requires a secure (HTTPS) connection",
},
5000,
);
return;
}
// Start scanning
await scanner.startScan();
} catch (error) {
this.error = error instanceof Error ? error.message : String(error);
this.isScanning = false;
this.isInitializing = false;
logger.error("Error starting scan:", {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
@ -828,12 +858,12 @@ export default class ContactQRScanShow extends Vue {
try {
await promise;
this.isInitializing = false;
this.cameraStatus = "Ready";
this.cameraState = "ready";
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
this.error = wrappedError.message;
this.cameraStatus = "Error";
this.cameraState = "error";
this.isInitializing = false;
logger.error("Error during QR scanner initialization:", {
error: wrappedError.message,
@ -843,17 +873,17 @@ export default class ContactQRScanShow extends Vue {
}
onCameraOn(): void {
this.cameraStatus = "Active";
this.cameraState = "active";
this.isInitializing = false;
}
onCameraOff(): void {
this.cameraStatus = "Off";
this.cameraState = "off";
}
onDetect(result: unknown): void {
this.isScanning = true;
this.cameraStatus = "Detecting";
this.cameraState = "detecting";
try {
let rawValue: string | undefined;
if (
@ -874,7 +904,7 @@ export default class ContactQRScanShow extends Vue {
this.handleError(error);
} finally {
this.isScanning = false;
this.cameraStatus = "Active";
this.cameraState = "active";
}
}
@ -897,12 +927,12 @@ export default class ContactQRScanShow extends Vue {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
this.error = wrappedError.message;
this.cameraStatus = "Error";
this.cameraState = "error";
}
onError(error: Error): void {
this.error = error.message;
this.cameraStatus = "Error";
this.cameraState = "error";
logger.error("QR code scan error:", {
error: error.message,
stack: error.stack,

Loading…
Cancel
Save