From 74b9caa94fcda3a6acb3a1bc66b4a7f1d7fd8ce0 Mon Sep 17 00:00:00 2001 From: Matt Raymer Date: Wed, 7 May 2025 01:57:18 -0400 Subject: [PATCH] chore: updates for qr code reader rules, linting, and cleanup --- .../rules/architectural_decision_record.mdc | 102 ++- .cursor/rules/qr-code-handling-rule.mdc | 49 +- .../rules/qr-code-implementation-guide.mdc | 481 ++++++------ package-lock.json | 447 +++++------ src/components/QRScanner/QRScannerDialog.vue | 722 ------------------ src/services/QRScanner/QRScannerService.ts | 15 - src/services/QRScanner/WebDialogQRScanner.ts | 263 ------- src/views/ContactQRScanShowView.vue | 15 +- 8 files changed, 583 insertions(+), 1511 deletions(-) delete mode 100644 src/components/QRScanner/QRScannerDialog.vue delete mode 100644 src/services/QRScanner/QRScannerService.ts delete mode 100644 src/services/QRScanner/WebDialogQRScanner.ts diff --git a/.cursor/rules/architectural_decision_record.mdc b/.cursor/rules/architectural_decision_record.mdc index c037cce8..06ed4d9f 100644 --- a/.cursor/rules/architectural_decision_record.mdc +++ b/.cursor/rules/architectural_decision_record.mdc @@ -9,15 +9,33 @@ alwaysApply: true | Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) | PyWebView (Desktop) | |---------|-----------|-------------------|-------------------|-------------------| -| QR Code Scanning | vue-qrcode-reader | @capacitor-mlkit/barcode-scanning | Not Implemented | Not Implemented | +| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented | Not Implemented | | Deep Linking | URL Parameters | App URL Open Events | Not Implemented | Not Implemented | | File System | Limited (Browser API) | Capacitor Filesystem | Electron fs | PyWebView Python Bridge | | Camera Access | MediaDevices API | Capacitor Camera | Not Implemented | Not Implemented | | Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks | process.env checks | -## 2. Build Configuration Structure +## 2. Project Structure -### 2.1 Entry Points +### 2.1 Core Directories +``` +src/ +├── components/ # Vue components +├── services/ # Platform services and business logic +├── views/ # Page components +├── router/ # Vue router configuration +├── types/ # TypeScript type definitions +├── utils/ # Utility functions +├── lib/ # Core libraries +├── platforms/ # Platform-specific implementations +├── electron/ # Electron-specific code +├── constants/ # Application constants +├── db/ # Database related code +├── interfaces/ # TypeScript interfaces +└── assets/ # Static assets +``` + +### 2.2 Entry Points ``` src/ ├── main.ts # Base entry @@ -28,19 +46,35 @@ src/ └── main.web.ts # Web/PWA entry ``` -### 2.2 Build Configurations +### 2.3 Build Configurations ``` root/ ├── vite.config.common.mts # Shared config ├── vite.config.capacitor.mts # Mobile build ├── vite.config.electron.mts # Electron build ├── vite.config.pywebview.mts # PyWebView build -└── vite.config.web.mts # Web/PWA build +├── vite.config.web.mts # Web/PWA build +└── vite.config.utils.mts # Build utilities ``` -## 3. Platform Service Architecture +## 3. Service Architecture + +### 3.1 Service Organization +``` +services/ +├── QRScanner/ # QR code scanning service +│ ├── WebInlineQRScanner.ts +│ └── interfaces.ts +├── platforms/ # Platform-specific services +│ ├── WebPlatformService.ts +│ ├── CapacitorPlatformService.ts +│ ├── ElectronPlatformService.ts +│ └── PyWebViewPlatformService.ts +└── factory/ # Service factories + └── PlatformServiceFactory.ts +``` -### 3.1 Service Factory Pattern +### 3.2 Service Factory Pattern ```typescript // PlatformServiceFactory.ts export class PlatformServiceFactory { @@ -56,40 +90,34 @@ export class PlatformServiceFactory { } ``` -### 3.2 Platform-Specific Implementations -``` -services/platforms/ -├── WebPlatformService.ts -├── CapacitorPlatformService.ts -├── ElectronPlatformService.ts -└── PyWebViewPlatformService.ts -``` - ## 4. Feature Implementation Guidelines ### 4.1 QR Code Scanning -1. **Factory Pattern** +1. **Service Interface** ```typescript -export class QRScannerFactory { - static getInstance(): QRScannerService { - if (__IS_MOBILE__ || Capacitor.isNativePlatform()) { - return new CapacitorQRScanner(); - } else if (__USE_QR_READER__) { - return new WebDialogQRScanner(); - } - throw new Error("No QR scanner implementation available"); - } +interface QRScannerService { + checkPermissions(): Promise; + requestPermissions(): Promise; + isSupported(): Promise; + startScan(): Promise; + stopScan(): Promise; + addListener(listener: ScanListener): void; + onStream(callback: (stream: MediaStream | null) => void): void; + cleanup(): Promise; } ``` 2. **Platform-Specific Implementation** ```typescript -// Example for Capacitor -export class CapacitorQRScanner implements QRScannerService { - async startScan(options?: QRScannerOptions): Promise { - // Platform-specific implementation - } +// WebInlineQRScanner.ts +export class WebInlineQRScanner implements QRScannerService { + private scanListener: ScanListener | null = null; + private isScanning = false; + private stream: MediaStream | null = null; + private events = new EventEmitter(); + + // Implementation of interface methods } ``` @@ -202,6 +230,8 @@ if (process.env.VITE_PLATFORM === 'capacitor') { - Use platform-specific directories for unique implementations - Share common code through service interfaces - Implement feature detection before using platform capabilities +- Keep platform-specific code isolated in dedicated directories +- Use TypeScript interfaces for cross-platform compatibility ### 8.2 Platform Detection ```typescript @@ -219,6 +249,7 @@ if (capabilities.hasCamera) { 3. Use factory pattern for instantiation 4. Implement graceful fallbacks 5. Add comprehensive error handling +6. Use dependency injection for better testability ## 9. Dependency Management @@ -228,7 +259,7 @@ if (capabilities.hasCamera) { "dependencies": { "@capacitor/core": "^6.2.0", "electron": "^33.2.1", - "vue-qrcode-reader": "^5.5.3" + "vue": "^3.4.0" } } ``` @@ -253,8 +284,9 @@ async checkPermissions(): Promise { ``` ### 10.2 Data Storage -- Use platform-appropriate storage mechanisms -- Implement encryption for sensitive data -- Handle permissions appropriately +- Use secure storage mechanisms for sensitive data +- Implement proper encryption for stored data +- Follow platform-specific security guidelines +- Regular security audits and updates This document should be updated as new features are added or platform-specific implementations change. Regular reviews ensure it remains current with the codebase. diff --git a/.cursor/rules/qr-code-handling-rule.mdc b/.cursor/rules/qr-code-handling-rule.mdc index fc949509..d78e2e28 100644 --- a/.cursor/rules/qr-code-handling-rule.mdc +++ b/.cursor/rules/qr-code-handling-rule.mdc @@ -14,11 +14,11 @@ The QR code scanning functionality follows a platform-agnostic design using a fa 1. **Factory Pattern** - `QRScannerFactory` - Creates appropriate scanner instance based on platform - Common interface `QRScannerService` implemented by all scanners -- Platform detection via Vite config flags: `__USE_QR_READER__` and `__IS_MOBILE__` +- Platform detection via Capacitor and build flags 2. **Platform-Specific Implementations** -- `CapacitorQRScanner` - Native mobile implementation -- `WebDialogQRScanner` - Web browser implementation +- `CapacitorQRScanner` - Native mobile implementation using MLKit +- `WebInlineQRScanner` - Web browser implementation using MediaDevices API - `QRScannerDialog.vue` - Shared UI component ## Mobile Implementation (Capacitor) @@ -54,13 +54,13 @@ MLKitBarcodeScanner: { ## Web Implementation ### Technology Stack -- Uses `vue-qrcode-reader` library -- Browser's MediaDevices API -- Vue.js dialog component +- Uses browser's MediaDevices API +- Vue.js components for UI +- EventEmitter for stream management ### Key Features - Browser-based camera access -- Fallback UI for unsupported browsers +- Inline camera preview - Responsive design - Cross-browser compatibility - Progressive enhancement @@ -97,9 +97,10 @@ MLKitBarcodeScanner: { ### Platform Detection ```typescript -if (__IS_MOBILE__ || Capacitor.isNativePlatform()) { +const isNative = QRScannerFactory.isNativePlatform(); +if (isNative) { // Use native scanner -} else if (__USE_QR_READER__) { +} else { // Use web scanner } ``` @@ -112,6 +113,9 @@ await scanner.startScan(); scanner.addListener({ onScan: (result) => { // Handle scan result + }, + onError: (error) => { + // Handle error } }); ``` @@ -145,4 +149,29 @@ scanner.addListener({ 2. Verify permission flows 3. Check error handling 4. Validate cleanup -5. Verify cross-platform behavior \ No newline at end of file +5. Verify cross-platform behavior + +## Service Interface + +```typescript +interface QRScannerService { + checkPermissions(): Promise; + requestPermissions(): Promise; + isSupported(): Promise; + startScan(options?: QRScannerOptions): Promise; + stopScan(): Promise; + addListener(listener: ScanListener): void; + onStream(callback: (stream: MediaStream | null) => void): void; + cleanup(): Promise; +} + +interface ScanListener { + onScan: (result: string) => void; + onError?: (error: Error) => void; +} + +interface QRScannerOptions { + camera?: "front" | "back"; + showPreview?: boolean; + playSound?: boolean; +} \ No newline at end of file diff --git a/.cursor/rules/qr-code-implementation-guide.mdc b/.cursor/rules/qr-code-implementation-guide.mdc index 0f0a9b21..fd488f96 100644 --- a/.cursor/rules/qr-code-implementation-guide.mdc +++ b/.cursor/rules/qr-code-implementation-guide.mdc @@ -9,19 +9,16 @@ alwaysApply: true ``` src/ -├── components/ -│ └── QRScanner/ -│ ├── types.ts -│ ├── factory.ts -│ ├── CapacitorScanner.ts -│ ├── WebDialogScanner.ts -│ └── QRScannerDialog.vue ├── services/ │ └── QRScanner/ -│ ├── types.ts -│ ├── QRScannerFactory.ts -│ ├── CapacitorQRScanner.ts -│ └── WebDialogQRScanner.ts +│ ├── 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 @@ -33,13 +30,20 @@ export interface ScanListener { onError?: (error: Error) => void; } +export interface QRScannerOptions { + camera?: "front" | "back"; + showPreview?: boolean; + playSound?: boolean; +} + export interface QRScannerService { checkPermissions(): Promise; requestPermissions(): Promise; isSupported(): Promise; - startScan(): Promise; + startScan(options?: QRScannerOptions): Promise; stopScan(): Promise; addListener(listener: ScanListener): void; + onStream(callback: (stream: MediaStream | null) => void): void; cleanup(): Promise; } ``` @@ -48,18 +52,17 @@ export interface QRScannerService { ### Vite Configuration ```typescript -// vite.config.ts -export default defineConfig({ - define: { - __USE_QR_READER__: JSON.stringify(!isMobile), - __IS_MOBILE__: JSON.stringify(isMobile), - }, - build: { - rollupOptions: { - external: isMobile ? ['vue-qrcode-reader'] : [], +// 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 @@ -81,7 +84,7 @@ const config: CapacitorConfig = { 1. **Install Dependencies** ```bash -npm install @capacitor-mlkit/barcode-scanning vue-qrcode-reader +npm install @capacitor-mlkit/barcode-scanning ``` 2. **Create Core Types** @@ -93,20 +96,34 @@ Create the interface files as shown above. 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) { - if (__IS_MOBILE__ || Capacitor.isNativePlatform()) { + const isNative = this.isNativePlatform(); + + if (isNative) { this.instance = new CapacitorQRScanner(); - } else if (__USE_QR_READER__) { - this.instance = new WebDialogQRScanner(); } else { - throw new Error('No QR scanner implementation available'); + this.instance = new WebInlineQRScanner(); } } - return this.instance; + return this.instance!; } - static async cleanup() { + static async cleanup(): Promise { if (this.instance) { await this.instance.cleanup(); this.instance = null; @@ -122,65 +139,104 @@ export class CapacitorQRScanner implements QRScannerService { private scanListener: ScanListener | null = null; private isScanning = false; private listenerHandles: Array<() => Promise> = []; + private cleanupPromise: Promise | null = null; - async checkPermissions() { + async checkPermissions(): Promise { try { const { camera } = await BarcodeScanner.checkPermissions(); - return camera === 'granted'; + return camera === "granted"; } catch (error) { - logger.error('Error checking camera permissions:', error); + logger.error("Error checking camera permissions:", error); return false; } } - async requestPermissions() { + async requestPermissions(): Promise { try { + if (await this.checkPermissions()) { + return true; + } const { camera } = await BarcodeScanner.requestPermissions(); - return camera === 'granted'; + return camera === "granted"; } catch (error) { - logger.error('Error requesting camera permissions:', error); + logger.error("Error requesting camera permissions:", error); return false; } } - async isSupported() { - return Capacitor.isNativePlatform(); + async isSupported(): Promise { + try { + const { supported } = await BarcodeScanner.isSupported(); + return supported; + } catch (error) { + logger.error("Error checking scanner support:", error); + return false; + } } - async startScan() { + async startScan(options?: QRScannerOptions): Promise { if (this.isScanning) return; - this.isScanning = true; + if (this.cleanupPromise) { + await this.cleanupPromise; + } try { - await BarcodeScanner.startScan(); + 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() { + async stopScan(): Promise { if (!this.isScanning) return; this.isScanning = false; try { await BarcodeScanner.stopScan(); } catch (error) { - logger.error('Error stopping scan:', error); + logger.error("Error stopping scan:", error); + throw error; } } - addListener(listener: ScanListener) { + addListener(listener: ScanListener): void { this.scanListener = listener; - const handle = BarcodeScanner.addListener('barcodeScanned', (result) => { - if (this.scanListener) { - this.scanListener.onScan(result.barcode); - } - }); - this.listenerHandles.push(handle.remove); } - async cleanup() { + onStream(callback: (stream: MediaStream | null) => void): void { + // No-op for native scanner + callback(null); + } + + async cleanup(): Promise { await this.stopScan(); for (const handle of this.listenerHandles) { await handle(); @@ -193,31 +249,40 @@ export class CapacitorQRScanner implements QRScannerService { 5. **Implement Web Scanner** ```typescript -// WebDialogQRScanner.ts -export class WebDialogQRScanner implements QRScannerService { - private dialogInstance: App | null = null; - private dialogComponent: InstanceType | null = null; +// 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 { try { const permissions = await navigator.permissions.query({ - name: 'camera' as PermissionName + name: "camera" as PermissionName, }); - return permissions.state === 'granted'; + return permissions.state === "granted"; } catch (error) { - logger.error('Error checking camera permissions:', error); + logger.error("Error checking camera permissions:", error); return false; } } async requestPermissions(): Promise { try { - const stream = await navigator.mediaDevices.getUserMedia({ video: true }); + 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); + logger.error("Error requesting camera permissions:", error); return false; } } @@ -226,208 +291,150 @@ export class WebDialogQRScanner implements QRScannerService { return 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices; } - async startScan() { - if (this.dialogInstance) return; + async startScan(): Promise { + if (this.isScanning) return; - const container = document.createElement('div'); - document.body.appendChild(container); + 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; + } + } - this.dialogInstance = createApp(QRScannerDialog, { - onScan: (result: string) => { - if (this.scanListener) { - this.scanListener.onScan(result); - } - }, - onError: (error: Error) => { - if (this.scanListener?.onError) { - this.scanListener.onError(error); - } - }, - onClose: () => { - this.cleanup(); - } - }); + async stopScan(): Promise { + if (!this.isScanning) return; - this.dialogComponent = this.dialogInstance.mount(container) as InstanceType; + 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; + } } - async stopScan() { - await this.cleanup(); + addListener(listener: ScanListener): void { + this.scanListener = listener; } - addListener(listener: ScanListener) { - this.scanListener = listener; + onStream(callback: (stream: MediaStream | null) => void): void { + this.events.on("stream", callback); } - async cleanup() { - if (this.dialogInstance) { - this.dialogInstance.unmount(); - this.dialogInstance = null; - this.dialogComponent = null; + async cleanup(): Promise { + try { + await this.stopScan(); + this.events.removeAllListeners(); + } catch (error) { + logger.error("Error during cleanup:", error); } } } ``` -6. **Create Dialog Component** -```vue - - - - - - -``` - -## Usage Example + // Add scan listener + scanner.addListener({ + onScan: (result) => { + console.log('QR Code scanned:', result); + }, + onError: (error) => { + console.error('Scan error:', error); + } + }); -```typescript -// In your component -async function scanQRCode() { - const scanner = QRScannerFactory.getInstance(); - - if (!(await scanner.checkPermissions())) { - const granted = await scanner.requestPermissions(); - if (!granted) { - throw new Error('Camera permission denied'); - } - } + // Start scanning + await scanner.startScan({ + camera: 'back', + showPreview: true + }); - scanner.addListener({ - onScan: (result) => { - console.log('Scanned:', result); - }, - onError: (error) => { - console.error('Scan error:', error); + // 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); } - }); - - await scanner.startScan(); -} + }, -// Cleanup when done -onUnmounted(() => { - QRScannerFactory.cleanup(); + 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) diff --git a/package-lock.json b/package-lock.json index 097fc965..87f1beac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -193,9 +193,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz", - "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", "devOptional": true, "engines": { "node": ">=6.9.0" @@ -269,12 +269,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz", - "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "devOptional": true, "dependencies": { - "@babel/compat-data": "^7.27.1", + "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -638,9 +638,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", - "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "dependencies": { "@babel/types": "^7.27.1" }, @@ -1636,13 +1636,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.1.tgz", - "integrity": "sha512-/sSliVc9gHE20/7D5qsdGlq7RG5NCDTWsAhyqzGuq174EtWJoGzIu1BQ7G56eDsTcy1jseBZwv50olSdXOlGuA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz", + "integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==", "devOptional": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", "@babel/plugin-transform-parameters": "^7.27.1" }, "engines": { @@ -2100,13 +2101,13 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.1.tgz", - "integrity": "sha512-TZ5USxFpLgKDpdEt8YWBR7p6g+bZo6sHaXLqP2BY/U0acaoI8FTVflcYCr/v94twM1C5IWFdZ/hscq9WjUeLXA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", + "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", + "@babel/compat-data": "^7.27.2", + "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", @@ -2148,7 +2149,7 @@ "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1", @@ -2256,13 +2257,13 @@ } }, "node_modules/@babel/template": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz", - "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "devOptional": true, "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.1", + "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" }, "engines": { @@ -4810,26 +4811,26 @@ } }, "node_modules/@expo/cli": { - "version": "0.24.11", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.24.11.tgz", - "integrity": "sha512-bQtXdonOgg2OgPjHd7D5IkiPObKyiLs+HVM2A1VFV1pOT/8kc2kF/I4lN/Y5uce03FC8v0VRv6rKrDQPlTVWlg==", + "version": "0.24.12", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.24.12.tgz", + "integrity": "sha512-MHCIq5jE6uWG26z7SQjUGxXrggxrooKqaGLTz2Vktr5NPkqRc0HBRKi3Rzd4zH5Y902/p18itTQBgvYOrMHt/g==", "optional": true, "peer": true, "dependencies": { "@0no-co/graphql.web": "^1.0.8", "@babel/runtime": "^7.20.0", "@expo/code-signing-certificates": "^0.0.5", - "@expo/config": "~11.0.8", + "@expo/config": "~11.0.9", "@expo/config-plugins": "~10.0.2", "@expo/devcert": "^1.1.2", "@expo/env": "~1.0.5", "@expo/image-utils": "^0.7.4", "@expo/json-file": "^9.1.4", - "@expo/metro-config": "~0.20.13", + "@expo/metro-config": "~0.20.14", "@expo/osascript": "^2.2.4", "@expo/package-manager": "^1.8.4", "@expo/plist": "^0.3.4", - "@expo/prebuild-config": "^9.0.5", + "@expo/prebuild-config": "^9.0.6", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.3.0", @@ -5215,15 +5216,15 @@ } }, "node_modules/@expo/config": { - "version": "11.0.8", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-11.0.8.tgz", - "integrity": "sha512-udLrpW4SvXUwF+ntJ0RzEjRbFoSS7Tr/rMrvhfISHWGbcZ09+c+QkI0O8y1sEBWQDpI/IlC9REPqGm5b7HweDw==", + "version": "11.0.9", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-11.0.9.tgz", + "integrity": "sha512-Rm2nnuwvPFBPmK0qlzx1DyGFcDq1KgahvdnYRuCYGDwOxUrf+cqYnj/K7cHijC1sBpp8fw550NVKMoLCsOodjw==", "optional": true, "peer": true, "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~10.0.2", - "@expo/config-types": "^53.0.3", + "@expo/config-types": "^53.0.4", "@expo/json-file": "^9.1.4", "deepmerge": "^4.3.1", "getenv": "^1.0.0", @@ -5366,9 +5367,9 @@ } }, "node_modules/@expo/config-types": { - "version": "53.0.3", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-53.0.3.tgz", - "integrity": "sha512-V1e6CiM4TXtGxG/W2Msjp/QOx/vikLo5IUGMvEMjgAglBfGYx3PXfqsUb5aZDt6kqA3bDDwFuZoS5vNm/SYwSg==", + "version": "53.0.4", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-53.0.4.tgz", + "integrity": "sha512-0s+9vFx83WIToEr0Iwy4CcmiUXa5BgwBmEjylBB2eojX5XAMm9mJvw9KpjAb8m7zq2G0Q6bRbeufkzgbipuNQg==", "optional": true, "peer": true }, @@ -5617,9 +5618,9 @@ } }, "node_modules/@expo/metro-config": { - "version": "0.20.13", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.20.13.tgz", - "integrity": "sha512-yyhyBBX2HaqFpuGq8r73d9eB1nJeUWDrNDrPANWuXNwfM/fd5pCT1GXmlRe4CWPQ4dPOlYnBIyrEn5c2FI5J4w==", + "version": "0.20.14", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.20.14.tgz", + "integrity": "sha512-tYDDubuZycK+NX00XN7BMu73kBur/evOPcKfxc+UBeFfgN2EifOITtdwSUDdRsbtJ2OnXwMY1HfRUG3Lq3l4cw==", "optional": true, "peer": true, "dependencies": { @@ -5627,7 +5628,7 @@ "@babel/generator": "^7.20.5", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", - "@expo/config": "~11.0.8", + "@expo/config": "~11.0.9", "@expo/env": "~1.0.5", "@expo/json-file": "~9.1.4", "@expo/spawn-async": "^1.7.2", @@ -5992,15 +5993,15 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.5.tgz", - "integrity": "sha512-oiSVU5ePu9lsOvn5p4xplqjzPlcZHzKYwzuonTa9GCH1GxcOEIBsvMVQiHBXHtqvgV2dztjm34kdXV//+9jtCA==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.6.tgz", + "integrity": "sha512-HDTdlMkTQZ95rd6EpvuLM+xkZV03yGLc38FqI37qKFLJtUN1WnYVaWsuXKoljd1OrVEVsHe6CfqKwaPZ52D56Q==", "optional": true, "peer": true, "dependencies": { - "@expo/config": "~11.0.7", + "@expo/config": "~11.0.9", "@expo/config-plugins": "~10.0.2", - "@expo/config-types": "^53.0.3", + "@expo/config-types": "^53.0.4", "@expo/image-utils": "^0.7.4", "@expo/json-file": "^9.1.4", "@react-native/normalize-colors": "0.79.2", @@ -7608,9 +7609,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", - "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz", + "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==", "cpu": [ "arm" ], @@ -7621,9 +7622,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", - "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz", + "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==", "cpu": [ "arm64" ], @@ -7634,9 +7635,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", - "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz", + "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==", "cpu": [ "arm64" ], @@ -7647,9 +7648,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", - "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz", + "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==", "cpu": [ "x64" ], @@ -7660,9 +7661,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", - "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz", + "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==", "cpu": [ "arm64" ], @@ -7673,9 +7674,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", - "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz", + "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==", "cpu": [ "x64" ], @@ -7686,9 +7687,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", - "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz", + "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==", "cpu": [ "arm" ], @@ -7699,9 +7700,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", - "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz", + "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==", "cpu": [ "arm" ], @@ -7712,9 +7713,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", - "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz", + "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==", "cpu": [ "arm64" ], @@ -7725,9 +7726,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", - "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz", + "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==", "cpu": [ "arm64" ], @@ -7738,9 +7739,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", - "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz", + "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==", "cpu": [ "loong64" ], @@ -7751,9 +7752,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", - "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz", + "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==", "cpu": [ "ppc64" ], @@ -7764,9 +7765,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", - "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz", + "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==", "cpu": [ "riscv64" ], @@ -7777,9 +7778,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", - "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz", + "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==", "cpu": [ "riscv64" ], @@ -7790,9 +7791,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", - "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz", + "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==", "cpu": [ "s390x" ], @@ -7803,9 +7804,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", - "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz", + "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==", "cpu": [ "x64" ], @@ -7816,9 +7817,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", - "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz", + "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==", "cpu": [ "x64" ], @@ -7829,9 +7830,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", - "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz", + "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==", "cpu": [ "arm64" ], @@ -7842,9 +7843,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", - "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz", + "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==", "cpu": [ "ia32" ], @@ -7855,9 +7856,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", - "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz", + "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==", "cpu": [ "x64" ], @@ -8762,9 +8763,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.17.40", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.40.tgz", - "integrity": "sha512-XNlderXNxSooRdgQFCX2aYoRtHhbUK86Iogm4T7c+pWHbYfVz5frT8ywZ94kXoMjC0f7EReLRiM0tGNtcxXOIA==", + "version": "20.17.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.43.tgz", + "integrity": "sha512-DnDEcDUnVAUYSa7U03QvrXbj1MZj00xoyi/a3lRGkR/c7BFUnqv+OY9EUphMqXUKdZJEOmuzu2mm+LmCisnPow==", "dependencies": { "undici-types": "~6.19.2" } @@ -14553,9 +14554,9 @@ } }, "node_modules/ethers": { - "version": "6.13.7", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.7.tgz", - "integrity": "sha512-qbaJ0uIrjh+huP1Lad2f2QtzW5dcqSVjIzVH6yWB4dKoMuj2WqYz5aMeeQTCNpAKgTJBM5J9vcc2cYJ23UAimQ==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.0.tgz", + "integrity": "sha512-KgHwltNSMdbrGWEyKkM0Rt2s+u1nDH/5BVDQakLinzGEJi4bWindBzZSCC4gKsbZjwDTI6ex/8suR9Ihbmz4IQ==", "funding": [ { "type": "individual", @@ -14738,26 +14739,26 @@ } }, "node_modules/expo": { - "version": "53.0.7", - "resolved": "https://registry.npmjs.org/expo/-/expo-53.0.7.tgz", - "integrity": "sha512-ghX529ZG/PnDtSQTzcl3qtt6/i9ktW1Ie8BE5u936MWCiPMwydxzZ/bilM3XlckLqKEsGsqmmpA1eVcWxkm1Ow==", + "version": "53.0.8", + "resolved": "https://registry.npmjs.org/expo/-/expo-53.0.8.tgz", + "integrity": "sha512-5CQWayZFDKif++HwfI6ysRNfePYH3MOEZw5edQStQyoL2MehzlasZoICSYHzqptMdMFSt2RTM5Tqgn8L4wYmVg==", "optional": true, "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.24.11", - "@expo/config": "~11.0.8", + "@expo/cli": "0.24.12", + "@expo/config": "~11.0.9", "@expo/config-plugins": "~10.0.2", "@expo/fingerprint": "0.12.4", - "@expo/metro-config": "0.20.13", + "@expo/metro-config": "0.20.14", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~13.1.11", "expo-asset": "~11.1.5", - "expo-constants": "~17.1.5", + "expo-constants": "~17.1.6", "expo-file-system": "~18.1.9", "expo-font": "~13.3.1", "expo-keep-awake": "~14.1.4", - "expo-modules-autolinking": "2.1.9", + "expo-modules-autolinking": "2.1.10", "expo-modules-core": "2.3.12", "react-native-edge-to-edge": "1.6.0", "whatwg-url-without-unicode": "8.0.0-3" @@ -14803,13 +14804,13 @@ } }, "node_modules/expo-constants": { - "version": "17.1.5", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.5.tgz", - "integrity": "sha512-9kjfQjVG6RgBQjFOo7LewxuZgTnYufXPuqpF00Ju5q2dAFW9Eh1SyJpFxbt7KoN+Wwu0hcIr/nQ0lPQugkg07Q==", + "version": "17.1.6", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.6.tgz", + "integrity": "sha512-q5mLvJiLtPcaZ7t2diSOlQ2AyxIO8YMVEJsEfI/ExkGj15JrflNQ7CALEW6IF/uNae/76qI/XcjEuuAyjdaCNw==", "optional": true, "peer": true, "dependencies": { - "@expo/config": "~11.0.7", + "@expo/config": "~11.0.9", "@expo/env": "~1.0.5" }, "peerDependencies": { @@ -14927,9 +14928,9 @@ } }, "node_modules/expo/node_modules/expo-modules-autolinking": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.9.tgz", - "integrity": "sha512-54InfnWy1BR54IDZoawqdFAaF2lyLHe9J+2dZ7y91/36jVpBtAval39ZKt2IISFJZ7TVglsojl4P5BDcDGcvjQ==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.10.tgz", + "integrity": "sha512-k93fzoszrYTKbZ51DSVnewYIGUV6Gi22Su8qySXPFJEfvtDs2NUUNRHBZNKgLHvwc6xPzVC5j7JYbrpXNuY44A==", "optional": true, "peer": true, "dependencies": { @@ -19006,9 +19007,9 @@ "dev": true }, "node_modules/metro": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.82.2.tgz", - "integrity": "sha512-hOBd4O4Cn/tLf3jz7IjSgD/A66MqMzgZuyF1I/pmNwYcY3q3j2vbh7Fa09KIbvUq5Yz7BewU356XboaEtEXPgA==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.82.3.tgz", + "integrity": "sha512-EfSLtuUmfsGk3znJ+zoN8cRLniQo3W1wyA+nJMfpTLdENfbbPnGRTwmKhzRcJIUh9jgkrrF4oRQ5shLtQ2DsUw==", "optional": true, "peer": true, "dependencies": { @@ -19033,18 +19034,18 @@ "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.82.2", - "metro-cache": "0.82.2", - "metro-cache-key": "0.82.2", - "metro-config": "0.82.2", - "metro-core": "0.82.2", - "metro-file-map": "0.82.2", - "metro-resolver": "0.82.2", - "metro-runtime": "0.82.2", - "metro-source-map": "0.82.2", - "metro-symbolicate": "0.82.2", - "metro-transform-plugins": "0.82.2", - "metro-transform-worker": "0.82.2", + "metro-babel-transformer": "0.82.3", + "metro-cache": "0.82.3", + "metro-cache-key": "0.82.3", + "metro-config": "0.82.3", + "metro-core": "0.82.3", + "metro-file-map": "0.82.3", + "metro-resolver": "0.82.3", + "metro-runtime": "0.82.3", + "metro-source-map": "0.82.3", + "metro-symbolicate": "0.82.3", + "metro-transform-plugins": "0.82.3", + "metro-transform-worker": "0.82.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", @@ -19061,9 +19062,9 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.82.2.tgz", - "integrity": "sha512-c2gesA7/B4dovPmmYC2HziNXb4XFG3YkQ9FjEzwRnR6KH2hT7nJn6mkcri1h85r3sMttpnmoBuZ8WDz980Zhlw==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.82.3.tgz", + "integrity": "sha512-eC0f1MSA8rg7VoNDCYMIAIe5AEgYBskh5W8rIa4RGRdmEOsGlXbAV0AWMYoA7NlIALW/S9b10AcdIwD3n1e50w==", "optional": true, "peer": true, "dependencies": { @@ -19094,25 +19095,25 @@ } }, "node_modules/metro-cache": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.82.2.tgz", - "integrity": "sha512-MxY4xvPKuE68NYpKJjH8YvVVugDL2QcuTracHsV5/30ZIaRr0v1QuAX5vt45OCQDQQWeh1rDv3E4JB6AbIvnZQ==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.82.3.tgz", + "integrity": "sha512-9zKhicA5GENROeP+iXku1NrI8FegtwEg3iPXHGixkm1Yppkbwsy/3lSHSiJZoT6GkZmxUDjN6sQ5QQ+/p72Msw==", "optional": true, "peer": true, "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", - "metro-core": "0.82.2" + "metro-core": "0.82.3" }, "engines": { "node": ">=18.18" } }, "node_modules/metro-cache-key": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.82.2.tgz", - "integrity": "sha512-lfjC9zzSri+rS7lkoCh04LniFga8JQVUqSuscD9KraIm9zRzwIwvaMx8V6Oogiezs+FAJUOSnVNhHcHc9l8H2Q==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.82.3.tgz", + "integrity": "sha512-dDLTUOJ7YYqGog9kR55InchwnkkHuxBXD765J3hQVWWPCy6xO9uZXZYGX1Y/tIMV8U7Ho1Sve0V13n5rFajrRQ==", "optional": true, "peer": true, "dependencies": { @@ -19123,9 +19124,9 @@ } }, "node_modules/metro-config": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.82.2.tgz", - "integrity": "sha512-0dG3qCFLoE3ddNexAxSLJ7FbGjEbwUjDNOgYeCLoPSkKB01k5itvvr2HFfl2HisOCfLcpjpVzF5NtB/O71lxfA==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.82.3.tgz", + "integrity": "sha512-GRG9sBkPvrGXD/Wu3RdEDuWg5NDixF9t0c6Zz9kZ9Aa/aQY+m85JgaCI5HYEV+UzVC/IUFFSpJiMfzQRicppLw==", "optional": true, "peer": true, "dependencies": { @@ -19133,34 +19134,34 @@ "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", - "metro": "0.82.2", - "metro-cache": "0.82.2", - "metro-core": "0.82.2", - "metro-runtime": "0.82.2" + "metro": "0.82.3", + "metro-cache": "0.82.3", + "metro-core": "0.82.3", + "metro-runtime": "0.82.3" }, "engines": { "node": ">=18.18" } }, "node_modules/metro-core": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.82.2.tgz", - "integrity": "sha512-d2XMkWbRh6PdPV1OZ8OyUyDWrtEbQ1m5ASpKtemLPbujfoE4RlwFZdl4ljfBNVVZ1s0z7tgsSFwKMyTeXgjtSg==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.82.3.tgz", + "integrity": "sha512-JQZDdXo3hyLl1pqVT4IKEwcBK+3f11qFXeCjQ1hjVpjMwQLOqSM02J7NC/4DNSBt+qWBxWj6R5Jphcc7+9AEWw==", "optional": true, "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.82.2" + "metro-resolver": "0.82.3" }, "engines": { "node": ">=18.18" } }, "node_modules/metro-file-map": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.82.2.tgz", - "integrity": "sha512-pax0WA80eRH096YO0kwox+ZD5im3V0Vswr2x1YqdMcZVWlr6uwXgQdo9q+mpcvJ1k77J+hmY5HIg71bqrUptVg==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.82.3.tgz", + "integrity": "sha512-o4wtloAge85MZl85F87FT59R/4tn5GvCvLfYcnzzDB20o2YX9AMxZqswrGMaei/GbD/Win5FrLF/Iq8oetcByA==", "optional": true, "peer": true, "dependencies": { @@ -19204,9 +19205,9 @@ "peer": true }, "node_modules/metro-minify-terser": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.82.2.tgz", - "integrity": "sha512-+nveaEdQUvsoi0OSr4Cp+btevZsg2DKsu8kUJsvyLIcRRFPUw9CwzF3V2cA5b55DY5LcIJyAcZf4D9ARKfoilQ==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.82.3.tgz", + "integrity": "sha512-/3FasOULfHq1P0KPNFy5y28Th5oknPSwEbt9JELVBMAPhUnLqQkCLr4M+RQzKG3aEQN1/mEqenWApFCkk6Nm/Q==", "optional": true, "peer": true, "dependencies": { @@ -19218,9 +19219,9 @@ } }, "node_modules/metro-resolver": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.82.2.tgz", - "integrity": "sha512-Who2hGzq2aCGSsBaQBU0L3SADiy/kj/gv0coujNWziRY4SKq7ECKzWqtVk1JlEF7IGXDDRDxEgFuLmPV6mZGVQ==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.82.3.tgz", + "integrity": "sha512-pdib7UrOM04j/RjWmaqmjjWRiuCbpA8BdUSuXzvBaK0QlNzHkRRDv6kiOGxgQ+UgG+KdbPcJktsW9olqiDhf9w==", "optional": true, "peer": true, "dependencies": { @@ -19231,9 +19232,9 @@ } }, "node_modules/metro-runtime": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.82.2.tgz", - "integrity": "sha512-gEcb2AfDs3GRs2SFjtEmG0k61B/cZEVCbh6cSmkjJpyHr+VRjw77MnDpX9AUcJYa4bCT63E7IEySOMM0Z8p87g==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.82.3.tgz", + "integrity": "sha512-J4SrUUsBy9ire8I2sFuXN5MzPmuBHlx1bjvAjdoo1ecpH2mtS3ubRqVnMotBxuK5+GhrbW0mtg5/46PVXy26cw==", "optional": true, "peer": true, "dependencies": { @@ -19245,9 +19246,9 @@ } }, "node_modules/metro-source-map": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.82.2.tgz", - "integrity": "sha512-S26xPdz1/EeAY0HqaPXfny8CeiY0Dvl4sBLQiXGXhoES4gUDAuMhA1tioKrv5F+x68Sod8cp8Js6EGqbMXeqMA==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.82.3.tgz", + "integrity": "sha512-gz7wfjz23rit6ePQ7NKE9x+VOWGKm54vli4wbphR9W+3y0bh6Ad7T0BGH9DUzRAnOnOorewrVEqFmT24mia5sg==", "optional": true, "peer": true, "dependencies": { @@ -19256,9 +19257,9 @@ "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.82.2", + "metro-symbolicate": "0.82.3", "nullthrows": "^1.1.1", - "ob1": "0.82.2", + "ob1": "0.82.3", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -19277,15 +19278,15 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.82.2.tgz", - "integrity": "sha512-iheanMnOMned6gjt6sKSfU5AoNyV6pJyQAWydwuHcjhGpa/kiAM0kKmw23qHejELK89Yw8HDZ3Fd/5l1jxpFVA==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.82.3.tgz", + "integrity": "sha512-WZKhR+QGbwkOLWP1z58Y7BFWUqLVDEEPsSQ5UI5+OWQDAwdtsPU9+sSNoJtD5qRU9qrB2XewQE3lJ2EQRRFJew==", "optional": true, "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.82.2", + "metro-source-map": "0.82.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" @@ -19308,9 +19309,9 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.82.2.tgz", - "integrity": "sha512-kEveuEVxghTEXkDiyY0MT5QRqei092KJG46nduo0VghFgI6QFodbAjFit1ULyWsn2VOTGSUDJ3VgHBMy7MaccA==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.82.3.tgz", + "integrity": "sha512-s1gVrkhczwMbxZLRSLCJ16K/4Sqx5IhO4sWlL6j0jlIEs1/Drn3JrkUUdQTtgmJS8SBpxmmB66cw7wnz751dVg==", "optional": true, "peer": true, "dependencies": { @@ -19326,9 +19327,9 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.82.2.tgz", - "integrity": "sha512-MJQNz6cGjqewCRqFmPrsHu6Oe93v2B6zgHkrNxQ6XdPMJz5VHD33m8q+8UsNJOH8wUMoRu5JmYtuUTIVIFxh2A==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.82.3.tgz", + "integrity": "sha512-z5Y7nYlSlLAEhjFi73uEJh69G5IC6HFZmXFcrxnY+JNlsjT2r0GgsDF4WaQGtarAIt5NP88V8983/PedwNfEcw==", "optional": true, "peer": true, "dependencies": { @@ -19337,13 +19338,13 @@ "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "metro": "0.82.2", - "metro-babel-transformer": "0.82.2", - "metro-cache": "0.82.2", - "metro-cache-key": "0.82.2", - "metro-minify-terser": "0.82.2", - "metro-source-map": "0.82.2", - "metro-transform-plugins": "0.82.2", + "metro": "0.82.3", + "metro-babel-transformer": "0.82.3", + "metro-cache": "0.82.3", + "metro-cache-key": "0.82.3", + "metro-minify-terser": "0.82.3", + "metro-source-map": "0.82.3", + "metro-transform-plugins": "0.82.3", "nullthrows": "^1.1.1" }, "engines": { @@ -20805,9 +20806,9 @@ "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" }, "node_modules/ob1": { - "version": "0.82.2", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.82.2.tgz", - "integrity": "sha512-sfUaYpjkAdHgu8cXLAyWXO98jW1EUOStTDNslfC9eb3tBLExe67PRqh09J0xdD6AlFKHFGTvXPbuHGvlrZNJNA==", + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.82.3.tgz", + "integrity": "sha512-8/SeymYlPMVODpCATHqm+X8eiuvD1GsKVa11n688V4GGgjrM3CRvrbtrYBs4t89LJDkv5CwGYPdqayuY0DmTTA==", "optional": true, "peer": true, "dependencies": { @@ -23221,9 +23222,9 @@ } }, "node_modules/rollup": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", - "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", + "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==", "dev": true, "dependencies": { "@types/estree": "1.0.7" @@ -23236,26 +23237,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.1", - "@rollup/rollup-android-arm64": "4.40.1", - "@rollup/rollup-darwin-arm64": "4.40.1", - "@rollup/rollup-darwin-x64": "4.40.1", - "@rollup/rollup-freebsd-arm64": "4.40.1", - "@rollup/rollup-freebsd-x64": "4.40.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", - "@rollup/rollup-linux-arm-musleabihf": "4.40.1", - "@rollup/rollup-linux-arm64-gnu": "4.40.1", - "@rollup/rollup-linux-arm64-musl": "4.40.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-musl": "4.40.1", - "@rollup/rollup-linux-s390x-gnu": "4.40.1", - "@rollup/rollup-linux-x64-gnu": "4.40.1", - "@rollup/rollup-linux-x64-musl": "4.40.1", - "@rollup/rollup-win32-arm64-msvc": "4.40.1", - "@rollup/rollup-win32-ia32-msvc": "4.40.1", - "@rollup/rollup-win32-x64-msvc": "4.40.1", + "@rollup/rollup-android-arm-eabi": "4.40.2", + "@rollup/rollup-android-arm64": "4.40.2", + "@rollup/rollup-darwin-arm64": "4.40.2", + "@rollup/rollup-darwin-x64": "4.40.2", + "@rollup/rollup-freebsd-arm64": "4.40.2", + "@rollup/rollup-freebsd-x64": "4.40.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", + "@rollup/rollup-linux-arm-musleabihf": "4.40.2", + "@rollup/rollup-linux-arm64-gnu": "4.40.2", + "@rollup/rollup-linux-arm64-musl": "4.40.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-musl": "4.40.2", + "@rollup/rollup-linux-s390x-gnu": "4.40.2", + "@rollup/rollup-linux-x64-gnu": "4.40.2", + "@rollup/rollup-linux-x64-musl": "4.40.2", + "@rollup/rollup-win32-arm64-msvc": "4.40.2", + "@rollup/rollup-win32-ia32-msvc": "4.40.2", + "@rollup/rollup-win32-x64-msvc": "4.40.2", "fsevents": "~2.3.2" } }, diff --git a/src/components/QRScanner/QRScannerDialog.vue b/src/components/QRScanner/QRScannerDialog.vue deleted file mode 100644 index d7de3f69..00000000 --- a/src/components/QRScanner/QRScannerDialog.vue +++ /dev/null @@ -1,722 +0,0 @@ - - - - - - diff --git a/src/services/QRScanner/QRScannerService.ts b/src/services/QRScanner/QRScannerService.ts deleted file mode 100644 index 619fa282..00000000 --- a/src/services/QRScanner/QRScannerService.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface QRScannerListener { - onScan: (result: string) => void; - onError: (error: Error) => void; -} - -export interface QRScannerService { - checkPermissions(): Promise; - requestPermissions(): Promise; - isSupported(): Promise; - startScan(): Promise; - stopScan(): Promise; - addListener(listener: QRScannerListener): void; - cleanup(): Promise; - onStream(callback: (stream: MediaStream | null) => void): void; -} diff --git a/src/services/QRScanner/WebDialogQRScanner.ts b/src/services/QRScanner/WebDialogQRScanner.ts deleted file mode 100644 index 63be67d8..00000000 --- a/src/services/QRScanner/WebDialogQRScanner.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { createApp, App } from "vue"; -import { QRScannerService, ScanListener, QRScannerOptions } from "./types"; -import QRScannerDialog from "@/components/QRScanner/QRScannerDialog.vue"; -import { logger } from "@/utils/logger"; - -export class WebDialogQRScanner implements QRScannerService { - private dialogInstance: App | null = null; - private dialogComponent: InstanceType | null = null; - private scanListener: ScanListener | null = null; - private isScanning = false; - private container: HTMLElement | null = null; - private sessionId: number | null = null; - private failsafeTimeout: unknown = null; - - constructor(private options?: QRScannerOptions) {} - - async checkPermissions(): Promise { - try { - logger.log("[QRScanner] Checking camera permissions..."); - const permissions = await navigator.permissions.query({ - name: "camera" as PermissionName, - }); - logger.log("[QRScanner] Permission state:", permissions.state); - return permissions.state === "granted"; - } catch (error) { - logger.error("[QRScanner] Error checking camera permissions:", error); - return false; - } - } - - async requestPermissions(): Promise { - try { - // First check if we have any video devices - const devices = await navigator.mediaDevices.enumerateDevices(); - const videoDevices = devices.filter( - (device) => device.kind === "videoinput", - ); - - if (videoDevices.length === 0) { - logger.error("No video devices found"); - throw new Error("No camera found on this device"); - } - - // Try to get a stream with specific constraints - const stream = await navigator.mediaDevices.getUserMedia({ - video: { - facingMode: "environment", - width: { ideal: 1280 }, - height: { ideal: 720 }, - }, - }); - - // Stop the test stream immediately - stream.getTracks().forEach((track) => track.stop()); - return true; - } catch (error) { - const wrappedError = - error instanceof Error ? error : new Error(String(error)); - logger.error("Error requesting camera permissions:", { - error: wrappedError.message, - stack: wrappedError.stack, - name: wrappedError.name, - }); - - // Provide more specific error messages - if ( - wrappedError.name === "NotFoundError" || - wrappedError.name === "DevicesNotFoundError" - ) { - 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" - ) { - throw new Error("Camera is in use by another application"); - } else { - throw new Error(`Camera error: ${wrappedError.message}`); - } - } - } - - async isSupported(): Promise { - try { - // Check for secure context first - if (!window.isSecureContext) { - logger.warn("Camera access requires HTTPS (secure context)"); - return false; - } - - // Check for camera API support - if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { - logger.warn("Camera API not supported in this browser"); - return false; - } - - // Check if we have any video devices - const devices = await navigator.mediaDevices.enumerateDevices(); - const hasVideoDevices = devices.some( - (device) => device.kind === "videoinput", - ); - - if (!hasVideoDevices) { - logger.warn("No video devices found"); - return false; - } - - return true; - } catch (error) { - logger.error("Error checking camera support:", { - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }); - return false; - } - } - - async startScan(): Promise { - if (this.isScanning) { - return; - } - - try { - this.isScanning = true; - this.sessionId = Date.now(); - logger.log( - `[WebDialogQRScanner] Opening dialog, session: ${this.sessionId}`, - ); - - // Create and mount dialog component - this.container = document.createElement("div"); - document.body.appendChild(this.container); - - this.dialogInstance = createApp(QRScannerDialog, { - onScan: (result: string) => { - if (this.scanListener) { - this.scanListener.onScan(result); - } - }, - onError: (error: Error) => { - if (this.scanListener?.onError) { - this.scanListener.onError(error); - } - }, - onClose: () => { - logger.log( - `[WebDialogQRScanner] onClose received from dialog, session: ${this.sessionId}`, - ); - this.stopScan("dialog onClose"); - }, - options: this.options, - sessionId: this.sessionId, - }); - - this.dialogComponent = this.dialogInstance.mount( - this.container, - ) as InstanceType; - - // Failsafe: force cleanup after 60s if dialog is still open - this.failsafeTimeout = setTimeout(() => { - if (this.isScanning) { - logger.warn( - `[WebDialogQRScanner] Failsafe triggered, forcing cleanup for session: ${this.sessionId}`, - ); - this.stopScan("failsafe timeout"); - } - }, 60000); - logger.log( - `[WebDialogQRScanner] Failsafe timeout set for session: ${this.sessionId}`, - ); - } catch (error) { - this.isScanning = false; - const wrappedError = - error instanceof Error ? error : new Error(String(error)); - if (this.scanListener?.onError) { - this.scanListener.onError(wrappedError); - } - logger.error("Error starting scan:", wrappedError); - this.cleanupContainer(); - throw wrappedError; - } - } - - async stopScan(reason: string = "manual"): Promise { - if (!this.isScanning) { - return; - } - - try { - logger.log( - `[WebDialogQRScanner] stopScan called, reason: ${reason}, session: ${this.sessionId}`, - ); - if (this.dialogComponent) { - await this.dialogComponent.close(); - logger.log( - `[WebDialogQRScanner] dialogComponent.close() called, session: ${this.sessionId}`, - ); - } - if (this.dialogInstance) { - this.dialogInstance.unmount(); - logger.log( - `[WebDialogQRScanner] dialogInstance.unmount() called, session: ${this.sessionId}`, - ); - } - } catch (error) { - const wrappedError = - error instanceof Error ? error : new Error(String(error)); - logger.error("Error stopping scan:", wrappedError); - throw wrappedError; - } finally { - this.isScanning = false; - if (this.failsafeTimeout) { - clearTimeout(this.failsafeTimeout); - this.failsafeTimeout = null; - logger.log( - `[WebDialogQRScanner] Failsafe timeout cleared, session: ${this.sessionId}`, - ); - } - this.cleanupContainer(); - } - } - - addListener(listener: ScanListener): void { - this.scanListener = listener; - } - - private cleanupContainer(): void { - if (this.container && this.container.parentNode) { - this.container.parentNode.removeChild(this.container); - logger.log( - `[WebDialogQRScanner] Dialog container removed from DOM, session: ${this.sessionId}`, - ); - } else { - logger.log( - `[WebDialogQRScanner] Dialog container NOT removed from DOM, session: ${this.sessionId}`, - ); - } - this.container = null; - } - - async cleanup(): Promise { - try { - await this.stopScan("cleanup"); - } catch (error) { - const wrappedError = - error instanceof Error ? error : new Error(String(error)); - logger.error("Error during cleanup:", wrappedError); - throw wrappedError; - } finally { - this.dialogComponent = null; - this.dialogInstance = null; - this.scanListener = null; - this.cleanupContainer(); - this.sessionId = null; - } - } -} diff --git a/src/views/ContactQRScanShowView.vue b/src/views/ContactQRScanShowView.vue index 6439973e..04c4e252 100644 --- a/src/views/ContactQRScanShowView.vue +++ b/src/views/ContactQRScanShowView.vue @@ -75,8 +75,13 @@
-

Scan Contact Info

-
+

+ Scan Contact Info +

+
Position QR code in the frame

-

- Error: {{ error }} -

+

Error: {{ error }}

Ready to scan @@ -157,7 +160,7 @@

-
+