WIP: Enhance QR scanner initialization and error handling
- Add visual processing overlay with status feedback - Implement robust worker initialization with retry mechanism - Add detailed error tracking and logging - Improve image processing with proper cleanup - Handle worker lifecycle and state management - Add proper module context simulation for jsQR initialization Still investigating jsQR initialization failure in worker context.
This commit is contained in:
@@ -2,6 +2,15 @@
|
||||
<QuickNav selected="Profile" />
|
||||
<!-- CONTENT -->
|
||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||
<!-- Processing Overlay -->
|
||||
<div v-if="isProcessing" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white rounded-lg p-6 text-center">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
|
||||
<p class="text-lg font-semibold">{{ processingStatus }}</p>
|
||||
<p class="text-sm text-gray-600 mt-2">{{ processingDetails }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<div class="mb-8">
|
||||
<!-- Back -->
|
||||
@@ -202,6 +211,20 @@ interface WorkerErrorMessage {
|
||||
|
||||
type WorkerMessage = WorkerSuccessMessage | WorkerErrorMessage;
|
||||
|
||||
interface WorkerResult {
|
||||
success: boolean;
|
||||
code?: {
|
||||
data: string;
|
||||
location: {
|
||||
topLeft: { x: number; y: number };
|
||||
topRight: { x: number; y: number };
|
||||
bottomLeft: { x: number; y: number };
|
||||
bottomRight: { x: number; y: number };
|
||||
};
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
QrcodeStream: __USE_QR_READER__ ? QrcodeStream : null,
|
||||
@@ -238,34 +261,23 @@ export default class ContactQRScanShow extends Vue {
|
||||
private isCapturingPhoto = false;
|
||||
private appStateListener?: { remove: () => Promise<void> };
|
||||
|
||||
private isProcessing = false;
|
||||
private processingStatus = '';
|
||||
private processingDetails = '';
|
||||
|
||||
private worker: Worker | null = null;
|
||||
private workerInitialized = false;
|
||||
private initializationAttempts = 0;
|
||||
private readonly MAX_INITIALIZATION_ATTEMPTS = 3;
|
||||
|
||||
async created() {
|
||||
logger.log('ContactQRScanShow component created');
|
||||
try {
|
||||
// Remove any existing listeners first
|
||||
await App.removeAllListeners();
|
||||
await this.cleanupAppListeners();
|
||||
|
||||
// Add app state listeners
|
||||
const stateListener = await App.addListener('appStateChange', (state: AppStateChangeEvent) => {
|
||||
logger.log('App state changed:', state);
|
||||
if (!state.isActive && this.cameraActive) {
|
||||
this.cleanupCamera();
|
||||
}
|
||||
});
|
||||
this.appStateListener = stateListener;
|
||||
|
||||
await App.addListener('pause', () => {
|
||||
logger.log('App paused');
|
||||
if (this.cameraActive) {
|
||||
this.cleanupCamera();
|
||||
}
|
||||
});
|
||||
|
||||
await App.addListener('resume', () => {
|
||||
logger.log('App resumed');
|
||||
if (this.cameraActive) {
|
||||
this.initializeCamera();
|
||||
}
|
||||
});
|
||||
await this.setupAppLifecycleListeners();
|
||||
|
||||
// Load initial data
|
||||
await this.loadInitialData();
|
||||
@@ -277,6 +289,50 @@ export default class ContactQRScanShow extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanupAppListeners(): Promise<void> {
|
||||
try {
|
||||
if (this.appStateListener) {
|
||||
await this.appStateListener.remove();
|
||||
this.appStateListener = undefined;
|
||||
}
|
||||
await App.removeAllListeners();
|
||||
logger.log('App listeners cleaned up successfully');
|
||||
} catch (error) {
|
||||
logger.error('Error cleaning up app listeners:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async setupAppLifecycleListeners(): Promise<void> {
|
||||
try {
|
||||
// Add app state change listener
|
||||
this.appStateListener = await App.addListener('appStateChange', (state: AppStateChangeEvent) => {
|
||||
logger.log('App state changed:', state);
|
||||
if (!state.isActive) {
|
||||
this.cleanupCamera();
|
||||
}
|
||||
});
|
||||
|
||||
// Add pause listener
|
||||
await App.addListener('pause', () => {
|
||||
logger.log('App paused');
|
||||
this.cleanupCamera();
|
||||
});
|
||||
|
||||
// Add resume listener
|
||||
await App.addListener('resume', () => {
|
||||
logger.log('App resumed');
|
||||
// Don't automatically reinitialize camera on resume
|
||||
// Let user explicitly request camera access again
|
||||
});
|
||||
|
||||
logger.log('App lifecycle listeners setup complete');
|
||||
} catch (error) {
|
||||
logger.error('Error setting up app lifecycle listeners:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadInitialData() {
|
||||
try {
|
||||
// Load settings from DB
|
||||
@@ -292,22 +348,34 @@ export default class ContactQRScanShow extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupCamera() {
|
||||
private async cleanupCamera() {
|
||||
try {
|
||||
this.cameraActive = false;
|
||||
this.isCapturingPhoto = false;
|
||||
this.addCameraState('cleanup');
|
||||
logger.log('Camera cleaned up successfully');
|
||||
if (this.cameraActive) {
|
||||
this.cameraActive = false;
|
||||
this.isCapturingPhoto = false;
|
||||
this.addCameraState('cleanup');
|
||||
logger.log('Camera cleaned up successfully');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error during camera cleanup:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private addCameraState(state: CameraState | QRProcessingState, details?: Record<string, unknown>) {
|
||||
// Prevent duplicate state transitions
|
||||
if (this.lastCameraState === state) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry: CameraStateHistoryEntry = {
|
||||
state,
|
||||
timestamp: Date.now(),
|
||||
details
|
||||
details: {
|
||||
...details,
|
||||
cameraActive: this.cameraActive,
|
||||
isCapturingPhoto: this.isCapturingPhoto,
|
||||
isProcessing: this.isProcessing
|
||||
}
|
||||
};
|
||||
|
||||
this.cameraStateHistory.push(entry);
|
||||
@@ -316,17 +384,11 @@ export default class ContactQRScanShow extends Vue {
|
||||
this.cameraStateHistory.shift();
|
||||
}
|
||||
|
||||
// Enhanced logging with better details
|
||||
logger.log('Camera state transition:', {
|
||||
state,
|
||||
details: {
|
||||
...details,
|
||||
cameraActive: this.cameraActive,
|
||||
isCapturingPhoto: this.isCapturingPhoto,
|
||||
historyLength: this.cameraStateHistory.length
|
||||
}
|
||||
this.logWithDetails('Camera state transition', {
|
||||
state: entry.state,
|
||||
timestamp: entry.timestamp,
|
||||
details: entry.details
|
||||
});
|
||||
|
||||
this.lastCameraState = state;
|
||||
}
|
||||
|
||||
@@ -363,6 +425,11 @@ export default class ContactQRScanShow extends Vue {
|
||||
]).catch(error => {
|
||||
logger.error('Error during component cleanup:', error);
|
||||
});
|
||||
|
||||
if (this.worker) {
|
||||
this.worker.terminate();
|
||||
this.worker = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleQRCodeResult(data: string): Promise<void> {
|
||||
@@ -456,114 +523,376 @@ export default class ContactQRScanShow extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async processImageForQRCode(imageDataUrl: string): Promise<void> {
|
||||
private async initializeWorker(): Promise<void> {
|
||||
try {
|
||||
logger.log('Starting QR code processing');
|
||||
if (this.initializationAttempts >= this.MAX_INITIALIZATION_ATTEMPTS) {
|
||||
throw new Error('Maximum worker initialization attempts reached');
|
||||
}
|
||||
|
||||
// Create worker for image processing
|
||||
const worker = new Worker(URL.createObjectURL(new Blob([`
|
||||
self.onmessage = async function(e) {
|
||||
this.initializationAttempts++;
|
||||
this.setProcessingStatus('Initializing', `Setting up QR scanner (attempt ${this.initializationAttempts})...`);
|
||||
logger.log('Starting worker initialization', { attempt: this.initializationAttempts });
|
||||
|
||||
// Import and wait for jsQR to be fully loaded
|
||||
logger.log('Loading jsQR module...');
|
||||
const jsQRModule = await import('jsqr');
|
||||
if (!jsQRModule?.default) {
|
||||
throw new Error('Failed to load jsQR module');
|
||||
}
|
||||
logger.log('jsQR module loaded successfully');
|
||||
|
||||
// Create worker with inline code and bundled jsQR
|
||||
const workerCode = `
|
||||
let jsQRInitialized = false;
|
||||
let initializationError = null;
|
||||
|
||||
try {
|
||||
// Get the raw jsQR function code
|
||||
const jsQRCode = ${jsQRModule.default.toString()};
|
||||
|
||||
// Create a proper module-like environment
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
self: self,
|
||||
window: self, // Some modules expect window to be defined
|
||||
global: self // Some modules expect global to be defined
|
||||
};
|
||||
|
||||
// Create a function that will run in the proper context
|
||||
const initFunction = new Function(
|
||||
'module', 'exports', 'self', 'window', 'global',
|
||||
jsQRCode
|
||||
);
|
||||
|
||||
// Execute the function in our context
|
||||
initFunction.call(
|
||||
context,
|
||||
context.module,
|
||||
context.exports,
|
||||
context.self,
|
||||
context.window,
|
||||
context.global
|
||||
);
|
||||
|
||||
// Get the jsQR function - it might be exported directly or as default
|
||||
self.jsQR = context.module.exports.default || context.module.exports;
|
||||
|
||||
// Verify the function works
|
||||
if (typeof self.jsQR === 'function') {
|
||||
// Test with a small dummy image
|
||||
const testData = new Uint8ClampedArray(4 * 4 * 4);
|
||||
self.jsQR(testData, 4, 4, { inversionAttempts: "dontInvert" });
|
||||
|
||||
jsQRInitialized = true;
|
||||
self.postMessage({ initialized: true });
|
||||
} else {
|
||||
throw new Error('jsQR is not a function after initialization');
|
||||
}
|
||||
} catch (error) {
|
||||
initializationError = error;
|
||||
console.error('Worker initialization error:', error);
|
||||
self.postMessage({
|
||||
error: 'Failed to initialize jsQR: ' + error.message,
|
||||
details: {
|
||||
errorType: error.name,
|
||||
errorStack: error.stack,
|
||||
jsQRType: typeof self.jsQR
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.onmessage = function(e) {
|
||||
if (!jsQRInitialized) {
|
||||
self.postMessage({
|
||||
success: false,
|
||||
error: 'QR scanner not initialized',
|
||||
details: {
|
||||
initializationError: initializationError ? initializationError.message : 'Unknown error',
|
||||
jsQRType: typeof self.jsQR
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { imageData, width, height } = e.data;
|
||||
try {
|
||||
// Import jsQR in the worker
|
||||
importScripts('${window.location.origin}/assets/jsqr.js');
|
||||
const code = self.jsQR(imageData, width, height, {
|
||||
inversionAttempts: "dontInvert"
|
||||
const uint8Array = new Uint8ClampedArray(imageData);
|
||||
|
||||
// Try normal orientation
|
||||
let code = self.jsQR(uint8Array, width, height, {
|
||||
inversionAttempts: "attemptBoth"
|
||||
});
|
||||
self.postMessage({ success: true, code });
|
||||
|
||||
if (!code) {
|
||||
// Try rotated 90 degrees
|
||||
const rotated = rotateImageData(new ImageData(uint8Array, width, height), width, height);
|
||||
if (rotated) {
|
||||
code = self.jsQR(new Uint8ClampedArray(rotated.data), rotated.width, rotated.height, {
|
||||
inversionAttempts: "attemptBoth"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.postMessage(code ? { success: true, code } : { success: false, error: 'No QR code found' });
|
||||
} catch (error) {
|
||||
self.postMessage({ success: false, error: error.message });
|
||||
console.error('QR processing error:', error);
|
||||
self.postMessage({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
details: {
|
||||
errorType: error.name,
|
||||
errorStack: error instanceof Error ? error.stack : undefined,
|
||||
jsQRType: typeof self.jsQR
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
`], { type: 'text/javascript' })));
|
||||
|
||||
const image = new Image();
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.src = imageDataUrl;
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('Image load timeout')), 5000);
|
||||
image.onload = () => {
|
||||
clearTimeout(timeout);
|
||||
resolve(undefined);
|
||||
};
|
||||
image.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error('Failed to load image'));
|
||||
};
|
||||
});
|
||||
function rotateImageData(imageData, width, height) {
|
||||
const canvas = new OffscreenCanvas(height, width);
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return null;
|
||||
|
||||
logger.log('Image loaded, creating canvas...');
|
||||
const canvas = document.createElement('canvas');
|
||||
const maxDimension = 1024; // Limit image size for better performance
|
||||
|
||||
// Scale down image if needed while maintaining aspect ratio
|
||||
let width = image.naturalWidth || 800;
|
||||
let height = image.naturalHeight || 600;
|
||||
if (width > maxDimension || height > maxDimension) {
|
||||
if (width > height) {
|
||||
height = Math.floor(height * (maxDimension / width));
|
||||
width = maxDimension;
|
||||
} else {
|
||||
width = Math.floor(width * (maxDimension / height));
|
||||
height = maxDimension;
|
||||
const newImageData = ctx.createImageData(height, width);
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const srcIndex = (y * width + x) * 4;
|
||||
const destIndex = ((width - x - 1) * height + y) * 4;
|
||||
newImageData.data[destIndex] = imageData.data[srcIndex];
|
||||
newImageData.data[destIndex + 1] = imageData.data[srcIndex + 1];
|
||||
newImageData.data[destIndex + 2] = imageData.data[srcIndex + 2];
|
||||
newImageData.data[destIndex + 3] = imageData.data[srcIndex + 3];
|
||||
}
|
||||
}
|
||||
return { data: newImageData.data, width: height, height: width };
|
||||
}
|
||||
`;
|
||||
|
||||
// Terminate existing worker if it exists
|
||||
if (this.worker) {
|
||||
logger.log('Terminating existing worker');
|
||||
this.worker.terminate();
|
||||
this.worker = null;
|
||||
this.workerInitialized = false;
|
||||
}
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
// Create blob and worker
|
||||
logger.log('Creating worker blob and URL');
|
||||
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
||||
const workerUrl = URL.createObjectURL(blob);
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('Failed to get canvas context');
|
||||
}
|
||||
|
||||
// Draw image maintaining orientation
|
||||
ctx.save();
|
||||
ctx.drawImage(image, 0, 0, width, height);
|
||||
ctx.restore();
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, width, height);
|
||||
logger.log('Creating new worker');
|
||||
this.worker = new Worker(workerUrl);
|
||||
|
||||
logger.log('Processing image data for QR code...', {
|
||||
width,
|
||||
height,
|
||||
dataLength: imageData.data.length
|
||||
});
|
||||
// Wait for worker to be ready with increased timeout
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
if (!this.worker) {
|
||||
reject(new Error('Worker failed to initialize'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Process QR code in worker
|
||||
const result = await new Promise<QRCodeResult>((resolve, reject) => {
|
||||
worker.onmessage = (e: MessageEvent<WorkerMessage>) => {
|
||||
if (e.data.success) {
|
||||
resolve(e.data.code);
|
||||
} else {
|
||||
logger.log('Setting up worker message handlers');
|
||||
const timeoutId = setTimeout(() => {
|
||||
logger.error('Worker initialization timed out after 10 seconds');
|
||||
if (this.worker) {
|
||||
this.worker.terminate();
|
||||
this.worker = null;
|
||||
}
|
||||
reject(new Error('Worker initialization timed out'));
|
||||
}, 10000);
|
||||
|
||||
this.worker.onmessage = (e) => {
|
||||
logger.log('Received worker message:', e.data);
|
||||
if (e.data?.initialized) {
|
||||
clearTimeout(timeoutId);
|
||||
this.workerInitialized = true;
|
||||
resolve();
|
||||
} else if (e.data?.error) {
|
||||
clearTimeout(timeoutId);
|
||||
reject(new Error(e.data.error));
|
||||
}
|
||||
};
|
||||
worker.onerror = reject;
|
||||
worker.postMessage({
|
||||
imageData: imageData.data,
|
||||
width: imageData.width,
|
||||
height: imageData.height
|
||||
});
|
||||
|
||||
this.worker.onerror = (error) => {
|
||||
logger.error('Worker error during initialization:', error);
|
||||
clearTimeout(timeoutId);
|
||||
reject(new Error(`Worker error: ${error.message}`));
|
||||
};
|
||||
});
|
||||
|
||||
worker.terminate();
|
||||
|
||||
if (result) {
|
||||
logger.log('QR code found:', { data: result.data });
|
||||
await this.handleQRCodeResult(result.data);
|
||||
} else {
|
||||
logger.log('No QR code found in image');
|
||||
this.showError('No QR code found. Please try again.');
|
||||
}
|
||||
// Clean up the URL after worker is initialized
|
||||
URL.revokeObjectURL(workerUrl);
|
||||
logger.log('Worker initialized successfully');
|
||||
this.setProcessingStatus('Ready', 'QR scanner initialized');
|
||||
|
||||
} catch (error) {
|
||||
logger.error('QR code processing failed:', error);
|
||||
this.showError('Failed to process QR code. Please try again.');
|
||||
logger.error('Failed to initialize worker:', error);
|
||||
this.setProcessingStatus('Error', `Failed to initialize QR scanner: ${error instanceof Error ? error.message : String(error)}`);
|
||||
|
||||
if (this.worker) {
|
||||
this.worker.terminate();
|
||||
this.worker = null;
|
||||
}
|
||||
this.workerInitialized = false;
|
||||
|
||||
// Show appropriate error message based on attempt count
|
||||
if (this.initializationAttempts >= this.MAX_INITIALIZATION_ATTEMPTS) {
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "error",
|
||||
title: "QR Scanner Error",
|
||||
text: "Failed to initialize QR scanner after multiple attempts. Please try again later.",
|
||||
});
|
||||
} else {
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "QR Scanner Warning",
|
||||
text: "Failed to initialize QR scanner. Retrying...",
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadImage(imageDataUrl: string): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
|
||||
const loadTimeout = setTimeout(() => {
|
||||
img.src = '';
|
||||
reject(new Error('Image load timeout'));
|
||||
}, 5000);
|
||||
|
||||
img.onload = () => {
|
||||
clearTimeout(loadTimeout);
|
||||
resolve(img);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
clearTimeout(loadTimeout);
|
||||
reject(new Error('Failed to load image'));
|
||||
};
|
||||
|
||||
img.src = imageDataUrl;
|
||||
});
|
||||
}
|
||||
|
||||
private getImageData(img: HTMLImageElement): { imageData: ImageData; width: number; height: number } {
|
||||
const MAX_IMAGE_DIMENSION = 1024;
|
||||
const aspectRatio = img.naturalWidth / img.naturalHeight;
|
||||
|
||||
// Calculate dimensions maintaining aspect ratio
|
||||
let width = MAX_IMAGE_DIMENSION;
|
||||
let height = MAX_IMAGE_DIMENSION;
|
||||
|
||||
if (aspectRatio > 1) {
|
||||
height = Math.round(width / aspectRatio);
|
||||
} else {
|
||||
width = Math.round(height * aspectRatio);
|
||||
}
|
||||
|
||||
const canvas = new OffscreenCanvas(width, height);
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
if (!ctx) {
|
||||
throw new Error('Failed to get canvas context');
|
||||
}
|
||||
|
||||
// Clear canvas and ensure proper rendering
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, width, height);
|
||||
return { imageData, width, height };
|
||||
}
|
||||
|
||||
private async processImageForQRCode(imageDataUrl: string): Promise<void> {
|
||||
try {
|
||||
if (!this.worker || !this.workerInitialized) {
|
||||
this.setProcessingStatus('Initializing', 'Setting up QR scanner...');
|
||||
try {
|
||||
await this.initializeWorker();
|
||||
} catch (error) {
|
||||
this.setProcessingStatus('Error', 'QR scanner initialization failed');
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "error",
|
||||
title: "QR Scanner Error",
|
||||
text: "Failed to initialize QR scanner. Please try again.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.isProcessing = true;
|
||||
this.setProcessingStatus('Processing', 'Loading image...');
|
||||
|
||||
try {
|
||||
const img = await this.loadImage(imageDataUrl);
|
||||
const { imageData, width, height } = this.getImageData(img);
|
||||
|
||||
this.setProcessingStatus('Processing', 'Analyzing image for QR code...');
|
||||
|
||||
const result = await new Promise<WorkerResult>((resolve, reject) => {
|
||||
if (!this.worker) {
|
||||
reject(new Error('Worker not initialized'));
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.setProcessingStatus('Error', 'QR code processing timed out');
|
||||
reject(new Error('QR code processing timed out'));
|
||||
}, 30000);
|
||||
|
||||
this.worker.onmessage = (e: MessageEvent<WorkerResult>) => {
|
||||
clearTimeout(timeoutId);
|
||||
resolve(e.data);
|
||||
};
|
||||
|
||||
this.worker.onerror = (error) => {
|
||||
clearTimeout(timeoutId);
|
||||
this.setProcessingStatus('Error', `Processing error: ${error.message}`);
|
||||
reject(error);
|
||||
};
|
||||
|
||||
this.setProcessingStatus('Processing', 'Scanning for QR code...');
|
||||
this.worker.postMessage({
|
||||
imageData: Array.from(imageData.data),
|
||||
width,
|
||||
height
|
||||
});
|
||||
});
|
||||
|
||||
if (result.success && result.code) {
|
||||
this.setProcessingStatus('Success', 'QR code found!');
|
||||
await this.handleQRCodeResult(result.code.data);
|
||||
} else {
|
||||
this.setProcessingStatus('Error', result.error || 'No QR code found');
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "No QR Code Found",
|
||||
text: "Please make sure the QR code is clearly visible and try again.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('QR processing error:', error);
|
||||
this.setProcessingStatus('Error', `Failed to process QR code: ${error instanceof Error ? error.message : String(error)}`);
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "error",
|
||||
title: "Processing Error",
|
||||
text: "Failed to process the image. Please try again.",
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
this.cameraActive = false;
|
||||
this.isProcessing = false;
|
||||
this.isCapturingPhoto = false;
|
||||
this.addCameraState('processing_completed');
|
||||
this.addCameraState('capture_completed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,9 +938,9 @@ export default class ContactQRScanShow extends Vue {
|
||||
this.danger(
|
||||
"Could not extract contact information from the QR code. Please try again.",
|
||||
"Invalid QR Code",
|
||||
);
|
||||
return;
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate JWT format
|
||||
if (
|
||||
@@ -1057,5 +1386,34 @@ export default class ContactQRScanShow extends Vue {
|
||||
this.addCameraState('capture_completed');
|
||||
}
|
||||
}
|
||||
|
||||
private setProcessingStatus(status: string, details = '') {
|
||||
this.isProcessing = status !== 'Error' && status !== 'Success';
|
||||
this.processingStatus = status;
|
||||
this.processingDetails = details;
|
||||
|
||||
// Log the status change
|
||||
this.logWithDetails('Processing status changed', {
|
||||
status,
|
||||
details,
|
||||
isProcessing: this.isProcessing
|
||||
});
|
||||
}
|
||||
|
||||
private logWithDetails(message: string, details?: Record<string, unknown>) {
|
||||
const formattedDetails = details ?
|
||||
'\n' + JSON.stringify(details, (key, value) => {
|
||||
if (value instanceof Error) {
|
||||
return {
|
||||
message: value.message,
|
||||
stack: value.stack,
|
||||
name: value.name
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}, 2) : '';
|
||||
|
||||
logger.log(`${message}${formattedDetails}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user