docs: consolidate QR code implementation documentation
Merge multiple QR code documentation files into a single comprehensive guide that accurately reflects the current implementation. The consolidated guide: - Combines information from qr-code-implementation-guide.mdc, qr-code-handling-rule.mdc, and camera-implementation.md - Clarifies the relationship between ContactQRScanView and ContactQRScanShowView - Streamlines build configuration documentation - Adds detailed sections on error handling, security, and best practices - Improves organization and readability of implementation details - Removes redundant information while preserving critical details This change improves documentation maintainability and provides a single source of truth for QR code implementation details.
This commit is contained in:
@@ -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
|
||||
284
doc/qr-code-implementation-guide.md
Normal file
284
doc/qr-code-implementation-guide.md
Normal file
@@ -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
|
||||
424
doc/web-push.md
Normal file
424
doc/web-push.md
Normal file
@@ -0,0 +1,424 @@
|
||||
|
||||
# Overivew of Web Push
|
||||
|
||||
Web Push notifications is a web browser messaging protocol defined by the W3C.
|
||||
|
||||
Discussions of this interesting technology are clouded because of a
|
||||
terminological morass.
|
||||
|
||||
To understand how Web Push operates, we need to observe that are three (and
|
||||
potentially four) parties involved. These are:
|
||||
|
||||
1) The user's web browser. Let's call that BROWSER
|
||||
2) The Web Push Service Provider which is operated by the organization
|
||||
controlling the web browser's source code. Here named PROVIDER. An example of a
|
||||
PROVIDER is FCM (Firebase Cloud Messaging) which is owned by Google.
|
||||
3) The Web Application that a user is visiting from their web browser. Let's
|
||||
call this the SERVICE (short for Web Push application service)
|
||||
4) A Custom Web Push Intermediary Service, either third party or self-hosted.
|
||||
Called INTERMEDIARY here. FCM also may fit in this category if the SERVICE
|
||||
has an API key from FCM.]
|
||||
|
||||
The workflow works like this:
|
||||
|
||||
BROWSER visits a website which hosts a SERVICE.
|
||||
|
||||
The SERVICE asks BROWSER for its permission to subscribe to messages coming
|
||||
from the SERVICE.
|
||||
|
||||
The SERVICE will provide context and obtain explicit permission before prompting
|
||||
for notification permission:
|
||||
|
||||
In order to provide this context and explicit permission, a two-step opt-in process
|
||||
first presents the user with a pre-permission dialog box that explains
|
||||
what the notifications are for and why they are useful. This may help reduce the
|
||||
possibility of users clicking "don't allow".
|
||||
|
||||
Now, to explain what happens in Typescript, we can activate a browser's
|
||||
permission dialogue in this manner:
|
||||
|
||||
```typescript
|
||||
function askPermission(): Promise<NotificationPermission> {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const permissionResult = Notification.requestPermission(function(result) {
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
if (permissionResult) {
|
||||
permissionResult.then(resolve, reject);
|
||||
}
|
||||
}).then(function(permissionResult) {
|
||||
if (permissionResult !== 'granted') {
|
||||
throw new Error("We weren't granted permission.");
|
||||
}
|
||||
return permissionResult;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The Notification.permission property indicates the permission level for the
|
||||
current session and returns one of the following string values:
|
||||
|
||||
- 'granted': The user has granted permission for notifications.
|
||||
- 'denied': The user has denied permission for notifications.
|
||||
- 'default': The user has not made a choice yet.
|
||||
|
||||
Once the user has granted permission, the client application registers a service
|
||||
worker using the `ServiceWorkerRegistration` API.
|
||||
|
||||
The `ServiceWorkerRegistration` API is accessible via the browser's `navigator`
|
||||
object and the `navigator.serviceWorker` child object and ultimately directly
|
||||
accessible via the navigator.serviceWorker.register method which also creates
|
||||
the service worker or the navigator.serviceWorker.getRegistration method.
|
||||
|
||||
Once you have a `ServiceWorkerRegistration` object, that object will provide a
|
||||
child object named `pushManager` through which subscription and management of
|
||||
subscriptions may be done.
|
||||
|
||||
Let's go through the `register` method first:
|
||||
|
||||
```javascript
|
||||
navigator.serviceWorker.register('sw.js', { scope: '/' })
|
||||
.then(function(registration) {
|
||||
console.log('Service worker registered successfully:', registration);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log('Service worker registration failed:', error);
|
||||
});
|
||||
```
|
||||
|
||||
The `sw.js` file contains the logic for what a service worker should do.
|
||||
It executes in a separate thread of execution from the web page but provides a
|
||||
means of communicating between itself and the web page via messages.
|
||||
|
||||
Note that there is a scope that can specify what network requests it may
|
||||
intercept.
|
||||
|
||||
The Vue project already has its own service worker but it is possible to
|
||||
create multiple service worker files by registering them on different scopes.
|
||||
|
||||
It is useful architecturally to specify a separate server worker file.
|
||||
|
||||
In the case of web push, the path of the scope only has reference to the domain
|
||||
of the service worker and no relationship to the pathing for the web push
|
||||
server. In order to specify more than one server workers each needs to be on
|
||||
different scope paths!
|
||||
|
||||
Here's a version which can be used for testing locally. Note there can be
|
||||
caching issues in your browser! Incognito is highly recommended.
|
||||
|
||||
sw-dev.ts
|
||||
|
||||
```typescript
|
||||
self.addEventListener('push', function(event: PushEvent) {
|
||||
console.log('Received a push message', event);
|
||||
|
||||
const title = 'Push message';
|
||||
const body = 'The message body';
|
||||
const icon = '/images/icon-192x192.png';
|
||||
const tag = 'simple-push-demo-notification-tag';
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(title, {
|
||||
body: body,
|
||||
icon: icon,
|
||||
tag: tag
|
||||
})
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
vue.config.js
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
pwa: {
|
||||
workboxOptions: {
|
||||
importScripts: ['sw-dev.ts']
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once we have the service worker registered and the ServiceWorkerRegistration is
|
||||
returned, we then have access to a `pushManager` property object. This property
|
||||
allows us to continue with the web push work flow.
|
||||
|
||||
In the next step, BROWSER requests a data structure from SERVICE called a VAPID
|
||||
(Voluntary Application Server Identification) which is the public key from a
|
||||
key-pair.
|
||||
|
||||
The VAPID is a specification used to identify the application server (i.e. the
|
||||
SERVICE server) that is sending push messages through a push PROVIDER. It's an
|
||||
authentication mechanism that allows the server to demonstrate its identity to
|
||||
the push PROVIDER, by use of a public and private key pair. These keys are used
|
||||
by the SERVICE in encrypting messages being sent to the BROWSER, as well as
|
||||
being used by the BROWSER in decrypting the messages coming from the SERVICE.
|
||||
|
||||
The VAPID (Voluntary Application Server Identification) key provides more
|
||||
security and authenticity for web push notifications in the following ways:
|
||||
|
||||
Identifying the Application Server:
|
||||
|
||||
The VAPID key is used to identify the application server that is sending
|
||||
the push notifications. This ensures that the push notifications are
|
||||
authentic and not sent by a malicious third party.
|
||||
|
||||
Encrypting the Messages:
|
||||
|
||||
The VAPID key is used to sign the push notifications sent by the
|
||||
application server, ensuring that they are not tampered with during
|
||||
transmission. This provides an additional layer of security and
|
||||
authenticity for the push notifications.
|
||||
|
||||
Adding Contact Information:
|
||||
|
||||
The VAPID key allows a web application to add contact information to
|
||||
the push messages sent to the browser push service. This enables the
|
||||
push service to contact the application server in case of need or
|
||||
provide additional debug information about the push messages.
|
||||
|
||||
Improving Delivery Rates:
|
||||
|
||||
Using the VAPID key can help improve the overall performance of web push
|
||||
notifications, specifically improving delivery rates. By streamlining the
|
||||
delivery process, the chance of delivery errors along the way is lessened.
|
||||
|
||||
If the BROWSER accepts and grants permission to subscribe to receiving from the
|
||||
SERVICE Web Push messages, then the BROWSER makes a subscription request to
|
||||
PROVIDER which creates and stores a special URL for that BROWSER.
|
||||
|
||||
Here's a bit of code describing the above process:
|
||||
|
||||
```typescript
|
||||
// b64 is the VAPID
|
||||
b64 = 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U';
|
||||
const applicationServerKey = urlBase64ToUint8Array(b64);
|
||||
const options: PushSubscriptionOptions = {
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: applicationServerKey
|
||||
};
|
||||
|
||||
registration.pushManager.subscribe(options)
|
||||
.then(function(subscription) {
|
||||
console.log('Push subscription successful:', subscription);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Push subscription failed:', error);
|
||||
});
|
||||
```
|
||||
|
||||
In this example, the `applicationServerKey` variable contains the VAPID public
|
||||
key, which is converted to a `Uint8Array` using a function such as this:
|
||||
|
||||
```typescript
|
||||
export function toUint8Array(base64String: string, atobFn: typeof atob): Uint8Array {
|
||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||
|
||||
const rawData = atobFn(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
```
|
||||
|
||||
The options object is of type `PushSubscriptionOptions`, which includes the
|
||||
`userVisibleOnly` and `applicationServerKey` (ie VAPID public key) properties.
|
||||
|
||||
options: An object that contains the options used for creating the
|
||||
subscription. This object itself has the following sub-properties:
|
||||
|
||||
applicationServerKey: A public key your push service uses for application
|
||||
server identification. This is normally a Uint8Array.
|
||||
|
||||
userVisibleOnly: A boolean value indicating that the push messages that
|
||||
are sent should be made visible to the user through a notification.
|
||||
This is often set to true.
|
||||
|
||||
The subscribe() method returns a `Promise` that resolves to a `PushSubscription`
|
||||
object containing details of the subscription, such as the endpoint URL and the
|
||||
public key. The returned data would have a form like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoint": "https://some.pushservice.com/some/unique/identifier",
|
||||
"expirationTime": null,
|
||||
"keys": {
|
||||
"p256dh": "some_base64_encoded_string",
|
||||
"auth": "some_other_base64_encoded_string"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
endpoint: A string representing the endpoint URL for the push service. This
|
||||
URL is essentially the push service address to which the push message would
|
||||
be sent for this particular subscription.
|
||||
|
||||
expirationTime: A DOMHighResTimeStamp (which is basically a number or null)
|
||||
representing the subscription's expiration time in milliseconds since
|
||||
01 January, 1970 UTC. This can be null if the subscription never expires.
|
||||
|
||||
The BROWSER will, internally, then use that URL to check for incoming messages
|
||||
by way of the service worker we described earlier. The BROWSER also sends this
|
||||
URL back to SERVICE which will use that URL to send messages to the BROWSER via
|
||||
the PROVIDER.
|
||||
|
||||
Ultimately, the actual internal process of receiving messages varies from BROWSER
|
||||
to BROWSER. Approaches vary from long-polling HTTP connections to WebSockets. A
|
||||
lot of handwaving and voodoo magic. The bottom line is that the BROWSER itself
|
||||
manages the connection to the PROVIDER whilst the SERVICE must send messages
|
||||
via the PROVIDER so that they reach the BROWSER service worker.
|
||||
|
||||
Just to remind us that in our service worker our code for receiving messages
|
||||
will look something like this:
|
||||
|
||||
```typescript
|
||||
self.addEventListener('push', function(event: PushEvent) {
|
||||
console.log('Received a push message', event);
|
||||
|
||||
const title = 'Push message';
|
||||
const body = 'The message body';
|
||||
const icon = '/images/icon-192x192.png';
|
||||
const tag = 'simple-push-demo-notification-tag';
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(title, {
|
||||
body: body,
|
||||
icon: icon,
|
||||
tag: tag
|
||||
})
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
Now to address the issue of receiving notification messages on mobile devices.
|
||||
It should be noted that Web Push messages are only received when BROWSER is
|
||||
open, except in the cases of Chrome and Firefox mobile BROWSERS. In iOS, the
|
||||
mobile application (in our case a PWA) must be added to the Home Screen and
|
||||
permissions must be explicitly granted that allow the application to receive
|
||||
push notifications. Further, with an iOS device the user must enable wake on
|
||||
notification to have their device light-up when it receives a notification
|
||||
(<https://support.apple.com/enus/HT208081>).
|
||||
|
||||
So what about #4? - The INTERMEDIARY. Well, It is possible under very special
|
||||
circumstances to create your own Web Push PROVIDER. The only case I've found so
|
||||
far relates to making an Android Custom ROM. (An Android Custom ROM is a
|
||||
customized version of the Android Operating System.) There are open source
|
||||
IMTERMEDIARY products such as UnifiedPush (<https://unifiedpush.org/>) which can
|
||||
fulfill this role. If you are using iOS you are not permitted to make or use
|
||||
your own custom Web Push PROVIDER. Apple will never allow anyone to do that.
|
||||
Apple has none of its own.
|
||||
|
||||
It is, however, possible to have a sort of proxy working between your SERVICE
|
||||
and FCM (or iOS). Services that mash up various Push notification services (like
|
||||
OneSignal) can perform in the role of such proxies.
|
||||
|
||||
# 4 -The INTERMEDIARY- doesn't appear to be anything we should be spending our time on
|
||||
|
||||
A BROWSER may also remove a subscription. In order to remove a subscription,
|
||||
the registration record must be retrieved from the serviceWorker using
|
||||
`navigator.serviceWorker.ready`. Within the `ready` property is the
|
||||
`pushManager` which has a `getSubscription` method. Once you have the
|
||||
subscription object, you may call the `unsubscribe` method. `unsubscribe` is
|
||||
asynchronnous and returns a boolean true if it is successful in removing the
|
||||
subscription and false if not.
|
||||
|
||||
```typescript
|
||||
async function unsubscribeFromPush() {
|
||||
// Check if the browser supports service workers
|
||||
if ("serviceWorker" in navigator) {
|
||||
// Get the registration object for the service worker
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
|
||||
// Get the existing subscription
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (subscription) {
|
||||
// Unsubscribe
|
||||
const successful = await subscription.unsubscribe();
|
||||
if (successful) {
|
||||
console.log("Successfully unsubscribed from push notifications.");
|
||||
// You can also inform your server to remove this subscription
|
||||
} else {
|
||||
console.log("Failed to unsubscribe from push notifications.");
|
||||
}
|
||||
} else {
|
||||
console.log("No subscription was found.");
|
||||
}
|
||||
} else {
|
||||
console.log("Service workers are not supported by this browser.");
|
||||
}
|
||||
}
|
||||
|
||||
// Unsubscribe from push notifications
|
||||
unsubscribeFromPush().catch((err) => {
|
||||
console.error("An error occurred while unsubscribing from push notifications", err);
|
||||
});
|
||||
```
|
||||
|
||||
NOTE: We could offer an option within the app to "mute" these notifications. This wouldn't turn off the notifications at the browser level, but you could make it so that your Service Worker doesn't display them even if it receives them.
|
||||
|
||||
# NOTIFICATION DIALOG WORKFLOW
|
||||
|
||||
## ON APP FIRST-LAUNCH
|
||||
|
||||
The user is periodically presented with the notification permission dialog that asks them if they want to turn on notifications. User is given 3 choices:
|
||||
|
||||
- "Turn on Notifications": triggers the browser's own notification permission prompt.
|
||||
- "Maybe Later": dismisses the dialog, to reappear at a later instance. (The next time the user launches the app? After X amount of days? A combination of both?)
|
||||
- "Never": dismisses the dialog; app remembers to not automatically present the dialog again.
|
||||
|
||||
## IF THE USER CHOOSES "NEVER"
|
||||
|
||||
The dialog can still be accessed via the Notifications toggle switch in `AccountViewView` (which also tells the user if notifications are turned on or off).
|
||||
|
||||
## TO TEMPORARILY MUTE NOTIFICATIONS
|
||||
|
||||
While notifications are turned on, the user can tap on the Mute Notifications toggle switch in `AccountViewView` (visible only when notifications are turned on) to trigger the Mute Notifications Dialog. User is given the following choices:
|
||||
|
||||
- Several "Mute for X Hour/s" buttons to temporarily mute notifications.
|
||||
- "Mute until I turn it back on" button to indefinitely mute notifications.
|
||||
- "Cancel" to make no changes and dismiss the dialog.
|
||||
|
||||
## TO UNMUTE NOTIFICATIONS
|
||||
|
||||
Simply tap on the Mute Notifications toggle switch in `AccountViewView` to immediately unmute notifications. No dialog needed.
|
||||
|
||||
## TO TURN OFF NOTIFICATIONS
|
||||
|
||||
While notifications are turned on, the user can tap on the App Notifications toggle switch in `AccountViewView` to trigger the Turn Off Notifications Dialog. User is given the following choices:
|
||||
|
||||
- "Turn off Notifications" to fully turn them off (which means the user will need to go through the dialogs agains to turn them back on).
|
||||
- "Leave it On" to make no changes and dismiss the dialog.
|
||||
|
||||
# NOTIFICATION STATES
|
||||
|
||||
- Unpermissioned. Push server cannot send notifications to the user because it does not have permission.
|
||||
This may be the same as when the user gave permission in the past but has since revoked it at the OS or browser
|
||||
level, outside the app. (User can change to Permissioned when the user gives permission.)
|
||||
- Permissioned. (User can change to Unpermissioned via the OS or browser settings.)
|
||||
- Active. (User can change to Muted when the user mutes notifications.)
|
||||
- Muted. (User can change to Active when the user toggles it.)
|
||||
(Turning mute off automatically after some amount of time is not planned in version 1.)
|
||||
|
||||
# TROUBLESHOOTING
|
||||
|
||||
## Desktop
|
||||
|
||||
### Firefox
|
||||
|
||||
Go to `about:debugging` and click on `Inspect` for the service worker.
|
||||
|
||||
### Chrome
|
||||
|
||||
Go to `chrome://inspect/#service-workers` and click on `Inspect` for the service worker.
|
||||
|
||||
## Mobile
|
||||
|
||||
### Android
|
||||
|
||||
### iOS
|
||||
Reference in New Issue
Block a user