forked from jsnbuchanan/crowd-funder-for-time-pwa
fix(qr): improve QR scanner implementation and error handling
- Implement robust QR scanner factory with platform detection - Add proper camera permissions to Android manifest - Improve error handling and logging across scanner implementations - Add continuous scanning mode for Capacitor/MLKit scanner - Enhance UI feedback during scanning process - Fix build configuration for proper platform detection - Clean up resources properly in scanner components - Add TypeScript improvements and error wrapping The changes include: - Adding CAMERA permission to AndroidManifest.xml - Setting proper build flags (__IS_MOBILE__, __USE_QR_READER__) - Implementing continuous scanning mode for better UX - Adding proper cleanup of scanner resources - Improving error handling and type safety - Enhancing UI with loading states and error messages
This commit is contained in:
@@ -80,15 +80,21 @@ import { QRScannerOptions } from "@/services/QRScanner/types";
|
||||
import { logger } from "@/utils/logger";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
|
||||
interface ScanProps {
|
||||
onScan: (result: string) => void;
|
||||
onError?: (error: Error) => void;
|
||||
options?: QRScannerOptions;
|
||||
}
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
QrcodeStream,
|
||||
},
|
||||
})
|
||||
export default class QRScannerDialog extends Vue {
|
||||
@Prop({ type: Function, required: true }) onScan!: (result: string) => void;
|
||||
@Prop({ type: Function }) onError?: (error: Error) => void;
|
||||
@Prop({ type: Object }) options?: QRScannerOptions;
|
||||
@Prop({ type: Function, required: true }) onScan!: ScanProps['onScan'];
|
||||
@Prop({ type: Function }) onError?: ScanProps['onError'];
|
||||
@Prop({ type: Object }) options?: ScanProps['options'];
|
||||
|
||||
visible = true;
|
||||
error: string | null = null;
|
||||
@@ -126,11 +132,12 @@ export default class QRScannerDialog extends Vue {
|
||||
await promise;
|
||||
this.error = null;
|
||||
} catch (error) {
|
||||
this.error = error instanceof Error ? error.message : String(error);
|
||||
const wrappedError = error instanceof Error ? error : new Error(String(error));
|
||||
this.error = wrappedError.message;
|
||||
if (this.onError) {
|
||||
this.onError(error instanceof Error ? error : new Error(String(error)));
|
||||
this.onError(wrappedError);
|
||||
}
|
||||
logger.error("Error initializing QR scanner:", error);
|
||||
logger.error("Error initializing QR scanner:", wrappedError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,11 +146,12 @@ export default class QRScannerDialog extends Vue {
|
||||
this.onScan(result);
|
||||
this.close();
|
||||
} catch (error) {
|
||||
this.error = error instanceof Error ? error.message : String(error);
|
||||
const wrappedError = error instanceof Error ? error : new Error(String(error));
|
||||
this.error = wrappedError.message;
|
||||
if (this.onError) {
|
||||
this.onError(error instanceof Error ? error : new Error(String(error)));
|
||||
this.onError(wrappedError);
|
||||
}
|
||||
logger.error("Error handling QR scan result:", error);
|
||||
logger.error("Error handling QR scan result:", wrappedError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,16 @@ import { logger } from "@/utils/logger";
|
||||
export class CapacitorQRScanner implements QRScannerService {
|
||||
private scanListener: ScanListener | null = null;
|
||||
private isScanning = false;
|
||||
private listenerHandles: Array<() => Promise<void>> = [];
|
||||
|
||||
async checkPermissions(): Promise<boolean> {
|
||||
try {
|
||||
const { camera } = await BarcodeScanner.checkPermissions();
|
||||
return camera === "granted";
|
||||
} catch (error) {
|
||||
logger.error("Error checking camera permissions:", error);
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error checking camera permissions:", wrappedError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +35,9 @@ export class CapacitorQRScanner implements QRScannerService {
|
||||
const { camera } = await BarcodeScanner.requestPermissions();
|
||||
return camera === "granted";
|
||||
} catch (error) {
|
||||
logger.error("Error requesting camera permissions:", error);
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error requesting camera permissions:", wrappedError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +47,9 @@ export class CapacitorQRScanner implements QRScannerService {
|
||||
const { supported } = await BarcodeScanner.isSupported();
|
||||
return supported;
|
||||
} catch (error) {
|
||||
logger.error("Error checking scanner support:", error);
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error checking scanner support:", wrappedError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -79,17 +86,23 @@ export class CapacitorQRScanner implements QRScannerService {
|
||||
};
|
||||
|
||||
logger.log("Scanner options:", scanOptions);
|
||||
const result = await BarcodeScanner.scan(scanOptions);
|
||||
logger.log("Scan result:", result);
|
||||
|
||||
if (result.barcodes.length > 0) {
|
||||
this.scanListener?.onScan(result.barcodes[0].rawValue);
|
||||
}
|
||||
|
||||
// Add listener for barcode scans
|
||||
const handle = await BarcodeScanner.addListener('barcodeScanned', (result) => {
|
||||
if (this.scanListener) {
|
||||
this.scanListener.onScan(result.barcode.rawValue);
|
||||
}
|
||||
});
|
||||
this.listenerHandles.push(handle.remove);
|
||||
|
||||
// Start continuous scanning
|
||||
await BarcodeScanner.startScan(scanOptions);
|
||||
} catch (error) {
|
||||
logger.error("Error during QR scan:", error);
|
||||
this.scanListener?.onError?.(error as Error);
|
||||
} finally {
|
||||
this.isScanning = false;
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error during QR scan:", wrappedError);
|
||||
this.scanListener?.onError?.(wrappedError);
|
||||
throw wrappedError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,10 +113,14 @@ export class CapacitorQRScanner implements QRScannerService {
|
||||
|
||||
try {
|
||||
await BarcodeScanner.stopScan();
|
||||
this.isScanning = false;
|
||||
} catch (error) {
|
||||
logger.error("Error stopping QR scan:", error);
|
||||
this.scanListener?.onError?.(error as Error);
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error stopping QR scan:", wrappedError);
|
||||
this.scanListener?.onError?.(wrappedError);
|
||||
throw wrappedError;
|
||||
} finally {
|
||||
this.isScanning = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +129,19 @@ export class CapacitorQRScanner implements QRScannerService {
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
await this.stopScan();
|
||||
this.scanListener = null;
|
||||
try {
|
||||
await this.stopScan();
|
||||
for (const handle of this.listenerHandles) {
|
||||
await handle();
|
||||
}
|
||||
} catch (error) {
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error during cleanup:", wrappedError);
|
||||
throw wrappedError;
|
||||
} finally {
|
||||
this.listenerHandles = [];
|
||||
this.scanListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,15 @@ export class QRScannerFactory {
|
||||
private static instance: QRScannerService | null = null;
|
||||
|
||||
private static isNativePlatform(): boolean {
|
||||
// Debug logging for build flags
|
||||
logger.log("Build flags:", {
|
||||
IS_MOBILE: typeof __IS_MOBILE__ !== 'undefined' ? __IS_MOBILE__ : 'undefined',
|
||||
USE_QR_READER: typeof __USE_QR_READER__ !== 'undefined' ? __USE_QR_READER__ : 'undefined',
|
||||
VITE_PLATFORM: process.env.VITE_PLATFORM,
|
||||
});
|
||||
|
||||
const capacitorNative = Capacitor.isNativePlatform();
|
||||
const isMobile = __IS_MOBILE__;
|
||||
const isMobile = typeof __IS_MOBILE__ !== 'undefined' ? __IS_MOBILE__ : capacitorNative;
|
||||
const platform = Capacitor.getPlatform();
|
||||
|
||||
logger.log("Platform detection:", {
|
||||
@@ -22,12 +29,16 @@ export class QRScannerFactory {
|
||||
userAgent: navigator.userAgent,
|
||||
});
|
||||
|
||||
// Force native scanner on Android/iOS
|
||||
// Always use native scanner on Android/iOS
|
||||
if (platform === "android" || platform === "ios") {
|
||||
logger.log("Using native scanner due to platform:", platform);
|
||||
return true;
|
||||
}
|
||||
|
||||
return capacitorNative || isMobile;
|
||||
// For other platforms, use native if available
|
||||
const useNative = capacitorNative || isMobile;
|
||||
logger.log("Platform decision:", { useNative, reason: useNative ? "capacitorNative/isMobile" : "web" });
|
||||
return useNative;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,19 +51,24 @@ export class QRScannerFactory {
|
||||
`Creating QR scanner for platform: ${isNative ? "native" : "web"}`,
|
||||
);
|
||||
|
||||
if (isNative) {
|
||||
logger.log("Using native MLKit scanner");
|
||||
this.instance = new CapacitorQRScanner();
|
||||
} else if (__USE_QR_READER__) {
|
||||
logger.log("Using web QR scanner");
|
||||
this.instance = new WebDialogQRScanner();
|
||||
} else {
|
||||
throw new Error(
|
||||
"No QR scanner implementation available for this platform",
|
||||
);
|
||||
try {
|
||||
if (isNative) {
|
||||
logger.log("Using native MLKit scanner");
|
||||
this.instance = new CapacitorQRScanner();
|
||||
} else if (typeof __USE_QR_READER__ !== 'undefined' ? __USE_QR_READER__ : !isNative) {
|
||||
logger.log("Using web QR scanner");
|
||||
this.instance = new WebDialogQRScanner();
|
||||
} else {
|
||||
throw new Error(
|
||||
"No QR scanner implementation available for this platform",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error creating QR scanner:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return this.instance!; // We know it's not null here
|
||||
return this.instance!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,8 +76,13 @@ export class QRScannerFactory {
|
||||
*/
|
||||
static async cleanup(): Promise<void> {
|
||||
if (this.instance) {
|
||||
await this.instance.cleanup();
|
||||
this.instance = null;
|
||||
try {
|
||||
await this.instance.cleanup();
|
||||
} catch (error) {
|
||||
logger.error("Error cleaning up QR scanner:", error);
|
||||
} finally {
|
||||
this.instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export class WebDialogQRScanner implements QRScannerService {
|
||||
private dialogComponent: InstanceType<typeof QRScannerDialog> | null = null;
|
||||
private scanListener: ScanListener | null = null;
|
||||
private isScanning = false;
|
||||
private container: HTMLElement | null = null;
|
||||
|
||||
constructor(private options?: QRScannerOptions) {}
|
||||
|
||||
@@ -18,7 +19,9 @@ export class WebDialogQRScanner implements QRScannerService {
|
||||
});
|
||||
return permissions.state === "granted";
|
||||
} catch (error) {
|
||||
logger.error("Error checking camera permissions:", error);
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error checking camera permissions:", wrappedError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +32,9 @@ export class WebDialogQRScanner implements QRScannerService {
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("Error requesting camera permissions:", error);
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error requesting camera permissions:", wrappedError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -47,8 +52,8 @@ export class WebDialogQRScanner implements QRScannerService {
|
||||
this.isScanning = true;
|
||||
|
||||
// Create and mount dialog component
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
this.container = document.createElement("div");
|
||||
document.body.appendChild(this.container);
|
||||
|
||||
this.dialogInstance = createApp(QRScannerDialog, {
|
||||
onScan: (result: string) => {
|
||||
@@ -64,16 +69,18 @@ export class WebDialogQRScanner implements QRScannerService {
|
||||
options: this.options,
|
||||
});
|
||||
|
||||
this.dialogComponent = this.dialogInstance.mount(container).$refs
|
||||
this.dialogComponent = this.dialogInstance.mount(this.container).$refs
|
||||
.dialog as InstanceType<typeof QRScannerDialog>;
|
||||
} catch (error) {
|
||||
this.isScanning = false;
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
if (this.scanListener?.onError) {
|
||||
this.scanListener.onError(
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
);
|
||||
this.scanListener.onError(wrappedError);
|
||||
}
|
||||
logger.error("Error starting scan:", error);
|
||||
logger.error("Error starting scan:", wrappedError);
|
||||
this.cleanupContainer();
|
||||
throw wrappedError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +96,14 @@ export class WebDialogQRScanner implements QRScannerService {
|
||||
if (this.dialogInstance) {
|
||||
this.dialogInstance.unmount();
|
||||
}
|
||||
this.isScanning = false;
|
||||
} catch (error) {
|
||||
logger.error("Error stopping scan:", error);
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
logger.error("Error stopping scan:", wrappedError);
|
||||
throw wrappedError;
|
||||
} finally {
|
||||
this.isScanning = false;
|
||||
this.cleanupContainer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +111,26 @@ export class WebDialogQRScanner implements QRScannerService {
|
||||
this.scanListener = listener;
|
||||
}
|
||||
|
||||
private cleanupContainer(): void {
|
||||
if (this.container && this.container.parentNode) {
|
||||
this.container.parentNode.removeChild(this.container);
|
||||
}
|
||||
this.container = null;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
await this.stopScan();
|
||||
this.dialogComponent = null;
|
||||
this.dialogInstance = null;
|
||||
this.scanListener = null;
|
||||
try {
|
||||
await this.stopScan();
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<br />
|
||||
<span
|
||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md"
|
||||
@click="() => $refs.userNameDialog.open((name) => (givenName = name))"
|
||||
@click="openUserNameDialog"
|
||||
>
|
||||
click here to set it for them.
|
||||
</span>
|
||||
@@ -77,8 +77,19 @@
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="text-4xl text-center font-light pt-6">Scan Contact Info</h1>
|
||||
<qrcode-stream @detect="onScanDetect" @error="onScanError" />
|
||||
<span>
|
||||
<div v-if="isScanning" class="relative aspect-square">
|
||||
<div class="absolute inset-0 border-2 border-blue-500 opacity-50 pointer-events-none"></div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-md mt-4"
|
||||
@click="startScanning"
|
||||
>
|
||||
Start Scanning
|
||||
</button>
|
||||
</div>
|
||||
<span v-if="error" class="text-red-500 block mt-2">{{ error }}</span>
|
||||
<span v-else class="block mt-2">
|
||||
If you do not see a scanning camera window here, check your camera
|
||||
permissions.
|
||||
</span>
|
||||
@@ -90,7 +101,6 @@
|
||||
import { AxiosError } from "axios";
|
||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { QrcodeStream } from "vue-qrcode-reader";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
@@ -110,9 +120,10 @@ import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc";
|
||||
import { retrieveAccountMetadata } from "../libs/util";
|
||||
import { Router } from "vue-router";
|
||||
import { logger } from "../utils/logger";
|
||||
import { QRScannerFactory } from "../services/QRScanner/QRScannerFactory";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
QrcodeStream,
|
||||
QRCodeVue3,
|
||||
QuickNav,
|
||||
UserNameDialog,
|
||||
@@ -128,6 +139,8 @@ export default class ContactQRScanShow extends Vue {
|
||||
hideRegisterPromptOnNewContact = false;
|
||||
isRegistered = false;
|
||||
qrValue = "";
|
||||
isScanning = false;
|
||||
error: string | null = null;
|
||||
|
||||
ETHR_DID_PREFIX = ETHR_DID_PREFIX;
|
||||
|
||||
@@ -150,12 +163,55 @@ export default class ContactQRScanShow extends Vue {
|
||||
account,
|
||||
!!settings.isRegistered,
|
||||
name,
|
||||
settings.profileImageUrl,
|
||||
settings.profileImageUrl || "",
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async startScanning() {
|
||||
try {
|
||||
this.error = null;
|
||||
this.isScanning = true;
|
||||
|
||||
const scanner = QRScannerFactory.getInstance();
|
||||
|
||||
// Check permissions first
|
||||
if (!(await scanner.checkPermissions())) {
|
||||
const granted = await scanner.requestPermissions();
|
||||
if (!granted) {
|
||||
this.error = "Camera permission denied";
|
||||
this.isScanning = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add scan listener
|
||||
scanner.addListener({
|
||||
onScan: this.onScanDetect,
|
||||
onError: this.onScanError
|
||||
});
|
||||
|
||||
// Start scanning
|
||||
await scanner.startScan();
|
||||
} catch (error) {
|
||||
this.error = error instanceof Error ? error.message : String(error);
|
||||
this.isScanning = false;
|
||||
logger.error("Error starting scan:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async stopScanning() {
|
||||
try {
|
||||
const scanner = QRScannerFactory.getInstance();
|
||||
await scanner.stopScan();
|
||||
} catch (error) {
|
||||
logger.error("Error stopping scan:", error);
|
||||
} finally {
|
||||
this.isScanning = false;
|
||||
}
|
||||
}
|
||||
|
||||
danger(message: string, title: string = "Error", timeout = 5000) {
|
||||
this.$notify(
|
||||
{
|
||||
@@ -169,49 +225,37 @@ export default class ContactQRScanShow extends Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param content is the result of a QR scan, an array with one item with a rawValue property
|
||||
* Handle QR code scan result
|
||||
*/
|
||||
// Unfortunately, there are not typescript definitions for the qrcode-stream component yet.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async onScanDetect(content: any) {
|
||||
const url = content[0]?.rawValue;
|
||||
if (url) {
|
||||
async onScanDetect(result: string) {
|
||||
try {
|
||||
let newContact: Contact;
|
||||
try {
|
||||
const jwt = getContactJwtFromJwtUrl(url);
|
||||
if (!jwt) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "No Contact Info",
|
||||
text: "The contact info could not be parsed.",
|
||||
},
|
||||
3000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { payload } = decodeEndorserJwt(jwt);
|
||||
newContact = {
|
||||
did: payload.own.did || payload.iss, // ".own.did" is reliable as of v 0.3.49
|
||||
name: payload.own.name,
|
||||
nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
|
||||
profileImageUrl: payload.own.profileImageUrl,
|
||||
publicKeyBase64: payload.own.publicEncKey,
|
||||
registered: payload.own.registered,
|
||||
};
|
||||
if (!newContact.did) {
|
||||
this.danger("There is no DID.", "Incomplete Contact");
|
||||
return;
|
||||
}
|
||||
if (!isDid(newContact.did)) {
|
||||
this.danger("The DID must begin with 'did:'", "Invalid DID");
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Error parsing QR info:", e);
|
||||
this.danger("Could not parse the QR info.", "Read Error");
|
||||
const jwt = getContactJwtFromJwtUrl(result);
|
||||
if (!jwt) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "No Contact Info",
|
||||
text: "The contact info could not be parsed.",
|
||||
},
|
||||
3000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { payload } = decodeEndorserJwt(jwt);
|
||||
newContact = {
|
||||
did: payload.own.did || payload.iss, // ".own.did" is reliable as of v 0.3.49
|
||||
name: payload.own.name,
|
||||
nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
|
||||
profileImageUrl: payload.own.profileImageUrl,
|
||||
};
|
||||
if (!newContact.did) {
|
||||
this.danger("There is no DID.", "Incomplete Contact");
|
||||
return;
|
||||
}
|
||||
if (!isDid(newContact.did)) {
|
||||
this.danger("The DID must begin with 'did:'", "Invalid DID");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -247,7 +291,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
type: "confirm",
|
||||
title: "Register",
|
||||
text: "Do you want to register them?",
|
||||
onCancel: async (stopAsking: boolean) => {
|
||||
onCancel: async (stopAsking?: boolean) => {
|
||||
if (stopAsking) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
@@ -255,7 +299,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||
}
|
||||
},
|
||||
onNo: async (stopAsking: boolean) => {
|
||||
onNo: async (stopAsking?: boolean) => {
|
||||
if (stopAsking) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
@@ -285,16 +329,12 @@ export default class ContactQRScanShow extends Vue {
|
||||
5000,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Invalid Contact QR Code",
|
||||
text: "No QR code detected with contact information.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
|
||||
// Stop scanning after successful scan
|
||||
await this.stopScanning();
|
||||
} catch (error) {
|
||||
this.error = error instanceof Error ? error.message : String(error);
|
||||
logger.error("Error processing scan result:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,8 +404,8 @@ export default class ContactQRScanShow extends Vue {
|
||||
let userMessage = "There was an error.";
|
||||
const serverError = error as AxiosError;
|
||||
if (serverError) {
|
||||
if (serverError.response?.data?.error?.message) {
|
||||
userMessage = serverError.response.data.error.message;
|
||||
if (serverError.response?.data && typeof serverError.response.data === 'object' && 'message' in serverError.response.data) {
|
||||
userMessage = (serverError.response.data as {message: string}).message;
|
||||
} else if (serverError.message) {
|
||||
userMessage = serverError.message; // Info for the user
|
||||
} else {
|
||||
@@ -387,18 +427,9 @@ export default class ContactQRScanShow extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onScanError(error: any) {
|
||||
logger.error("Scan was invalid:", error);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Invalid Scan",
|
||||
text: "The scan was invalid.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
onScanError(error: Error) {
|
||||
this.error = error.message;
|
||||
logger.error("Scan error:", error);
|
||||
}
|
||||
|
||||
onCopyUrlToClipboard() {
|
||||
@@ -435,5 +466,22 @@ export default class ContactQRScanShow extends Vue {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
openUserNameDialog() {
|
||||
(this.$refs.userNameDialog as any).open((name: string) => {
|
||||
this.givenName = name;
|
||||
});
|
||||
}
|
||||
|
||||
beforeDestroy() {
|
||||
// Clean up scanner when component is destroyed
|
||||
QRScannerFactory.cleanup();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aspect-square {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user