forked from jsnbuchanan/crowd-funder-for-time-pwa
refactor(qr): improve QR scanner robustness and lifecycle management
- Add cleanup promise to prevent concurrent cleanup operations - Add proper component lifecycle tracking with isMounted flag - Add isCleaningUp flag to prevent operations during cleanup - Add debug level logging for better diagnostics - Add structured error logging with stack traces - Add proper error handling in component initialization - Add proper cleanup of event listeners and camera resources - Add proper handling of app pause/resume events - Add proper error boundaries around camera operations - Improve error message formatting and consistency The QR scanner now properly handles lifecycle events, cleans up resources, and provides better error diagnostics. This improves reliability on mobile devices and prevents potential memory leaks.
This commit is contained in:
@@ -160,32 +160,50 @@ export default class ContactQRScanShow extends Vue {
|
||||
private lastScanTime: number = 0;
|
||||
private readonly SCAN_DEBOUNCE_MS = 2000; // Prevent duplicate scans within 2 seconds
|
||||
|
||||
// Add cleanup tracking
|
||||
private isCleaningUp = false;
|
||||
private isMounted = false;
|
||||
|
||||
async created() {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.givenName = settings.firstName || "";
|
||||
this.hideRegisterPromptOnNewContact =
|
||||
!!settings.hideRegisterPromptOnNewContact;
|
||||
this.isRegistered = !!settings.isRegistered;
|
||||
try {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.givenName = settings.firstName || "";
|
||||
this.hideRegisterPromptOnNewContact = !!settings.hideRegisterPromptOnNewContact;
|
||||
this.isRegistered = !!settings.isRegistered;
|
||||
|
||||
const account = await retrieveAccountMetadata(this.activeDid);
|
||||
if (account) {
|
||||
const name =
|
||||
(settings.firstName || "") +
|
||||
(settings.lastName ? ` ${settings.lastName}` : ""); // lastName is deprecated, pre v 0.1.3
|
||||
|
||||
this.qrValue = await generateEndorserJwtUrlForAccount(
|
||||
account,
|
||||
!!settings.isRegistered,
|
||||
name,
|
||||
settings.profileImageUrl || "",
|
||||
false,
|
||||
);
|
||||
const account = await retrieveAccountMetadata(this.activeDid);
|
||||
if (account) {
|
||||
const name = (settings.firstName || "") + (settings.lastName ? ` ${settings.lastName}` : "");
|
||||
this.qrValue = await generateEndorserJwtUrlForAccount(
|
||||
account,
|
||||
!!settings.isRegistered,
|
||||
name,
|
||||
settings.profileImageUrl || "",
|
||||
false,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error initializing component:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Initialization Error",
|
||||
text: "Failed to initialize QR scanner. Please try again.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async startScanning() {
|
||||
if (this.isCleaningUp) {
|
||||
logger.debug("Cannot start scanning during cleanup");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.error = null;
|
||||
this.isScanning = true;
|
||||
@@ -215,7 +233,10 @@ export default class ContactQRScanShow extends Vue {
|
||||
} catch (error) {
|
||||
this.error = error instanceof Error ? error.message : String(error);
|
||||
this.isScanning = false;
|
||||
logger.error("Error starting scan:", error);
|
||||
logger.error("Error starting scan:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,11 +244,35 @@ export default class ContactQRScanShow extends Vue {
|
||||
try {
|
||||
const scanner = QRScannerFactory.getInstance();
|
||||
await scanner.stopScan();
|
||||
} catch (error) {
|
||||
logger.error("Error stopping scan:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
} finally {
|
||||
this.isScanning = false;
|
||||
this.lastScannedValue = "";
|
||||
this.lastScanTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
async cleanupScanner() {
|
||||
if (this.isCleaningUp) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isCleaningUp = true;
|
||||
try {
|
||||
logger.info("Cleaning up QR scanner resources");
|
||||
await this.stopScanning();
|
||||
await QRScannerFactory.cleanup();
|
||||
} catch (error) {
|
||||
logger.error("Error stopping scan:", error);
|
||||
logger.error("Error during scanner cleanup:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
} finally {
|
||||
this.isCleaningUp = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,10 +542,32 @@ export default class ContactQRScanShow extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
// Lifecycle hooks
|
||||
mounted() {
|
||||
this.isMounted = true;
|
||||
document.addEventListener("pause", this.handleAppPause);
|
||||
document.addEventListener("resume", this.handleAppResume);
|
||||
}
|
||||
|
||||
beforeDestroy() {
|
||||
logger.info("Cleaning up QR scanner resources");
|
||||
this.stopScanning(); // Ensure scanner is stopped
|
||||
QRScannerFactory.cleanup();
|
||||
this.isMounted = false;
|
||||
document.removeEventListener("pause", this.handleAppPause);
|
||||
document.removeEventListener("resume", this.handleAppResume);
|
||||
this.cleanupScanner();
|
||||
}
|
||||
|
||||
async handleAppPause() {
|
||||
if (!this.isMounted) return;
|
||||
|
||||
logger.info("App paused, stopping scanner");
|
||||
await this.stopScanning();
|
||||
}
|
||||
|
||||
handleAppResume() {
|
||||
if (!this.isMounted) return;
|
||||
|
||||
logger.info("App resumed, scanner can be restarted by user");
|
||||
this.isScanning = false;
|
||||
}
|
||||
|
||||
async addNewContact(contact: Contact) {
|
||||
@@ -581,28 +648,6 @@ export default class ContactQRScanShow extends Vue {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add pause/resume handlers for mobile
|
||||
mounted() {
|
||||
document.addEventListener("pause", this.handleAppPause);
|
||||
document.addEventListener("resume", this.handleAppResume);
|
||||
}
|
||||
|
||||
beforeUnmount() {
|
||||
document.removeEventListener("pause", this.handleAppPause);
|
||||
document.removeEventListener("resume", this.handleAppResume);
|
||||
}
|
||||
|
||||
handleAppPause() {
|
||||
logger.info("App paused, stopping scanner");
|
||||
this.stopScanning();
|
||||
}
|
||||
|
||||
handleAppResume() {
|
||||
logger.info("App resumed, scanner can be restarted by user");
|
||||
// Don't auto-restart scanning - let user initiate it
|
||||
this.isScanning = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -611,3 +656,4 @@ export default class ContactQRScanShow extends Vue {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user