refactor(scanner): improve barcode scanner initialization and error handling
- Fix scanner listener initialization in class component * Replace Vue ref with direct class property for scanListener * Remove unnecessary .value accesses throughout the code - Enhance error handling and cleanup * Add proper cleanup on scanner errors * Add cleanup in stopScanning method * Improve error message formatting * Add error handling in handleScanResult - Improve state management * Move state updates into try/catch blocks * Add finally block to reset state after scan processing * Better handling of processing states during scanning - Add comprehensive logging * Log each step of scanner initialization * Add detailed error logging * Format object logs to prevent [object Object] output - Code style improvements * Fix indentation throughout the file * Consistent error handling patterns * Better code organization and readability
This commit is contained in:
@@ -137,7 +137,7 @@ import {
|
||||
BarcodeScanner,
|
||||
type ScanResult,
|
||||
} from "@capacitor-mlkit/barcode-scanning";
|
||||
import { ref, type Ref, reactive } from "vue";
|
||||
import { ref, reactive } from "vue";
|
||||
|
||||
// Declare global constants
|
||||
declare const __USE_QR_READER__: boolean;
|
||||
@@ -241,7 +241,7 @@ export default class ContactQRScanShowView extends Vue {
|
||||
private isCapturingPhoto = false;
|
||||
private appStateListener?: { remove: () => Promise<void> };
|
||||
|
||||
private scanListener: Ref<PluginListenerHandle | null> = ref(null);
|
||||
private scanListener: PluginListenerHandle | null = null;
|
||||
private state = reactive<AppState>({
|
||||
isProcessing: false,
|
||||
processingStatus: "",
|
||||
@@ -307,7 +307,17 @@ export default class ContactQRScanShowView extends Vue {
|
||||
this.appStateListener = await App.addListener(
|
||||
"appStateChange",
|
||||
(state: AppStateChangeEvent) => {
|
||||
logger.log("App state changed:", state);
|
||||
const stateInfo = {
|
||||
isActive: state.isActive,
|
||||
timestamp: new Date().toISOString(),
|
||||
cameraActive: this.cameraActive,
|
||||
scannerState: {
|
||||
...this.state.scannerState,
|
||||
// Convert complex objects to strings to avoid [object Object]
|
||||
error: this.state.scannerState.error?.toString() || null,
|
||||
},
|
||||
};
|
||||
logger.log("App state changed:", JSON.stringify(stateInfo, null, 2));
|
||||
if (!state.isActive) {
|
||||
this.cleanupCamera();
|
||||
}
|
||||
@@ -316,15 +326,31 @@ export default class ContactQRScanShowView extends Vue {
|
||||
|
||||
// Add pause listener
|
||||
await App.addListener("pause", () => {
|
||||
logger.log("App paused");
|
||||
const pauseInfo = {
|
||||
timestamp: new Date().toISOString(),
|
||||
cameraActive: this.cameraActive,
|
||||
scannerState: {
|
||||
...this.state.scannerState,
|
||||
error: this.state.scannerState.error?.toString() || null,
|
||||
},
|
||||
isProcessing: this.state.isProcessing,
|
||||
};
|
||||
logger.log("App paused:", JSON.stringify(pauseInfo, null, 2));
|
||||
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
|
||||
const resumeInfo = {
|
||||
timestamp: new Date().toISOString(),
|
||||
cameraActive: this.cameraActive,
|
||||
scannerState: {
|
||||
...this.state.scannerState,
|
||||
error: this.state.scannerState.error?.toString() || null,
|
||||
},
|
||||
isProcessing: this.state.isProcessing,
|
||||
};
|
||||
logger.log("App resumed:", JSON.stringify(resumeInfo, null, 2));
|
||||
});
|
||||
|
||||
logger.log("App lifecycle listeners setup complete");
|
||||
@@ -340,7 +366,7 @@ export default class ContactQRScanShowView extends Vue {
|
||||
await db.open();
|
||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||
if (settings) {
|
||||
this.hideRegisterPromptOnNewContact =
|
||||
this.hideRegisterPromptOnNewContact =
|
||||
settings.hideRegisterPromptOnNewContact || false;
|
||||
}
|
||||
logger.log("Initial data loaded successfully");
|
||||
@@ -436,41 +462,59 @@ export default class ContactQRScanShowView extends Vue {
|
||||
try {
|
||||
this.state.isProcessing = true;
|
||||
this.state.processingStatus = "Starting camera...";
|
||||
logger.log("Opening mobile camera - starting initialization");
|
||||
|
||||
// Check current permission status
|
||||
const status = await BarcodeScanner.checkPermissions();
|
||||
logger.log("Camera permission status:", JSON.stringify(status, null, 2));
|
||||
|
||||
if (status.camera !== "granted") {
|
||||
// Request permission if not granted
|
||||
logger.log("Requesting camera permissions...");
|
||||
const permissionStatus = await BarcodeScanner.requestPermissions();
|
||||
if (permissionStatus.camera !== "granted") {
|
||||
throw new Error("Camera permission not granted");
|
||||
}
|
||||
logger.log(
|
||||
"Camera permission granted:",
|
||||
JSON.stringify(permissionStatus, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
// Remove any existing listener first
|
||||
try {
|
||||
if (this.scanListener) {
|
||||
logger.log("Removing existing barcode listener");
|
||||
await this.scanListener.remove();
|
||||
this.scanListener = null;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error removing existing listener:", error);
|
||||
// Continue with setup even if removal fails
|
||||
}
|
||||
|
||||
// Set up the listener before starting the scan
|
||||
try {
|
||||
const listener = await BarcodeScanner.addListener(
|
||||
"barcodesScanned",
|
||||
async (result: ScanResult) => {
|
||||
if (result.barcodes && result.barcodes.length > 0) {
|
||||
this.state.processingDetails = `Processing QR code: ${result.barcodes[0].rawValue}`;
|
||||
await this.handleScanResult(result.barcodes[0].rawValue);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Only set the listener if we successfully got one
|
||||
if (listener) {
|
||||
this.scanListener.value = listener;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error setting up barcode listener:", error);
|
||||
throw new Error("Failed to initialize barcode scanner");
|
||||
}
|
||||
logger.log("Setting up new barcode listener");
|
||||
this.scanListener = await BarcodeScanner.addListener(
|
||||
"barcodesScanned",
|
||||
async (result: ScanResult) => {
|
||||
logger.log(
|
||||
"Barcode scan result received:",
|
||||
JSON.stringify(result, null, 2),
|
||||
);
|
||||
if (result.barcodes && result.barcodes.length > 0) {
|
||||
this.state.processingDetails = `Processing QR code: ${result.barcodes[0].rawValue}`;
|
||||
await this.handleScanResult(result.barcodes[0].rawValue);
|
||||
}
|
||||
},
|
||||
);
|
||||
logger.log("Barcode listener setup complete");
|
||||
|
||||
// Start the scanner
|
||||
logger.log("Starting barcode scanner");
|
||||
await BarcodeScanner.startScan();
|
||||
logger.log("Barcode scanner started successfully");
|
||||
|
||||
this.state.isProcessing = false;
|
||||
this.state.processingStatus = "";
|
||||
} catch (error) {
|
||||
@@ -481,19 +525,37 @@ export default class ContactQRScanShowView extends Vue {
|
||||
this.showError(
|
||||
error instanceof Error ? error.message : "Failed to open camera",
|
||||
);
|
||||
|
||||
// Cleanup on error
|
||||
try {
|
||||
if (this.scanListener) {
|
||||
await this.scanListener.remove();
|
||||
this.scanListener = null;
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
logger.error("Error during cleanup:", cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleScanResult(rawValue: string) {
|
||||
this.state.isProcessing = true;
|
||||
this.state.processingStatus = "Processing QR code...";
|
||||
this.state.processingDetails = `Scanned value: ${rawValue}`;
|
||||
try {
|
||||
this.state.isProcessing = true;
|
||||
this.state.processingStatus = "Processing QR code...";
|
||||
this.state.processingDetails = `Scanned value: ${rawValue}`;
|
||||
|
||||
// Stop scanning before processing
|
||||
await this.stopScanning();
|
||||
|
||||
// Process the scan result
|
||||
await this.onScanDetect({ rawValue });
|
||||
} catch (error) {
|
||||
logger.error("Error handling scan result:", error);
|
||||
this.showError("Failed to process scan result");
|
||||
} finally {
|
||||
this.state.isProcessing = false;
|
||||
this.state.processingStatus = "";
|
||||
this.state.processingDetails = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,11 +615,11 @@ export default class ContactQRScanShowView extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
let newContact: Contact;
|
||||
try {
|
||||
let newContact: Contact;
|
||||
try {
|
||||
// Extract JWT from URL
|
||||
const jwt = getContactJwtFromJwtUrl(url);
|
||||
if (!jwt) {
|
||||
const jwt = getContactJwtFromJwtUrl(url);
|
||||
if (!jwt) {
|
||||
this.danger(
|
||||
"Could not extract contact information from the QR code. Please try again.",
|
||||
"Invalid QR Code",
|
||||
@@ -572,11 +634,11 @@ export default class ContactQRScanShowView extends Vue {
|
||||
this.danger(
|
||||
"The QR code contains invalid data. Please scan a valid TimeSafari contact QR code.",
|
||||
"Invalid Data",
|
||||
);
|
||||
return;
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { payload } = decodeEndorserJwt(jwt);
|
||||
const { payload } = decodeEndorserJwt(jwt);
|
||||
if (!payload) {
|
||||
this.danger(
|
||||
"Could not decode the contact information. Please try again.",
|
||||
@@ -594,7 +656,7 @@ export default class ContactQRScanShowView extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
newContact = {
|
||||
newContact = {
|
||||
did: payload.own?.did || payload.iss,
|
||||
name: payload.own?.name,
|
||||
nextPubKeyHashB64: payload.own?.nextPublicEncKeyHash,
|
||||
@@ -603,15 +665,15 @@ export default class ContactQRScanShowView extends Vue {
|
||||
registered: payload.own?.registered,
|
||||
};
|
||||
|
||||
if (!newContact.did) {
|
||||
if (!newContact.did) {
|
||||
this.danger(
|
||||
"Missing contact identifier. Please scan a valid TimeSafari contact QR code.",
|
||||
"Incomplete Contact",
|
||||
);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDid(newContact.did)) {
|
||||
if (!isDid(newContact.did)) {
|
||||
this.danger(
|
||||
"Invalid contact identifier format. The identifier must begin with 'did:'.",
|
||||
"Invalid Identifier",
|
||||
@@ -619,66 +681,66 @@ export default class ContactQRScanShowView extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.open();
|
||||
await db.contacts.add(newContact);
|
||||
await db.open();
|
||||
await db.contacts.add(newContact);
|
||||
|
||||
let addedMessage;
|
||||
if (this.activeDid) {
|
||||
await this.setVisibility(newContact, true);
|
||||
let addedMessage;
|
||||
if (this.activeDid) {
|
||||
await this.setVisibility(newContact, true);
|
||||
newContact.seesMe = true;
|
||||
addedMessage = "They were added, and your activity is visible to them.";
|
||||
} else {
|
||||
addedMessage = "They were added.";
|
||||
}
|
||||
} else {
|
||||
addedMessage = "They were added.";
|
||||
}
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Contact Added",
|
||||
text: addedMessage,
|
||||
},
|
||||
3000,
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Contact Added",
|
||||
text: addedMessage,
|
||||
},
|
||||
3000,
|
||||
);
|
||||
|
||||
if (
|
||||
this.isRegistered &&
|
||||
!this.hideRegisterPromptOnNewContact &&
|
||||
!newContact.registered
|
||||
) {
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Register",
|
||||
text: "Do you want to register them?",
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Register",
|
||||
text: "Do you want to register them?",
|
||||
onCancel: async (stopAsking?: boolean) => {
|
||||
if (stopAsking) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
});
|
||||
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||
}
|
||||
},
|
||||
if (stopAsking) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
});
|
||||
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||
}
|
||||
},
|
||||
onNo: async (stopAsking?: boolean) => {
|
||||
if (stopAsking) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
});
|
||||
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||
}
|
||||
},
|
||||
onYes: async () => {
|
||||
await this.register(newContact);
|
||||
},
|
||||
promptToStopAsking: true,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}, 500);
|
||||
}
|
||||
} catch (e) {
|
||||
if (stopAsking) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
});
|
||||
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||
}
|
||||
},
|
||||
onYes: async () => {
|
||||
await this.register(newContact);
|
||||
},
|
||||
promptToStopAsking: true,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}, 500);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Error processing QR code:", e);
|
||||
this.danger(
|
||||
"Could not process the QR code. Please make sure you're scanning a valid TimeSafari contact QR code.",
|
||||
@@ -795,15 +857,15 @@ export default class ContactQRScanShowView extends Vue {
|
||||
async onCopyUrlToClipboard(): Promise<void> {
|
||||
try {
|
||||
await this.platformService.writeToClipboard(this.qrValue);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Copied",
|
||||
text: "Contact URL was copied to clipboard.",
|
||||
},
|
||||
2000,
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Copied",
|
||||
text: "Contact URL was copied to clipboard.",
|
||||
},
|
||||
2000,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Error copying to clipboard:", error);
|
||||
this.danger("Failed to copy to clipboard", "Error");
|
||||
@@ -813,15 +875,15 @@ export default class ContactQRScanShowView extends Vue {
|
||||
async onCopyDidToClipboard(): Promise<void> {
|
||||
try {
|
||||
await this.platformService.writeToClipboard(this.activeDid);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Copied",
|
||||
text: "Your DID was copied to the clipboard. Have them paste it in the box on their 'People' screen to add you.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Copied",
|
||||
text: "Your DID was copied to the clipboard. Have them paste it in the box on their 'People' screen to add you.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Error copying to clipboard:", error);
|
||||
this.danger("Failed to copy to clipboard", "Error");
|
||||
@@ -880,6 +942,13 @@ export default class ContactQRScanShowView extends Vue {
|
||||
|
||||
async stopScanning() {
|
||||
try {
|
||||
// Remove the listener first
|
||||
if (this.scanListener) {
|
||||
await this.scanListener.remove();
|
||||
this.scanListener = null;
|
||||
}
|
||||
|
||||
// Stop the scanner
|
||||
await BarcodeScanner.stopScan();
|
||||
this.state.scannerState.processingStatus = "Scan stopped";
|
||||
this.state.scannerState.isProcessing = false;
|
||||
@@ -889,6 +958,7 @@ export default class ContactQRScanShowView extends Vue {
|
||||
error instanceof Error ? error.message : String(error);
|
||||
this.state.scannerState.error = `Error stopping scan: ${errorMessage}`;
|
||||
this.state.scannerState.isProcessing = false;
|
||||
logger.error("Error stopping scanner:", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user