diff --git a/src/components/QRScanner/QRScannerDialog.vue b/src/components/QRScanner/QRScannerDialog.vue
index 9f2efebe..981f4ca6 100644
--- a/src/components/QRScanner/QRScannerDialog.vue
+++ b/src/components/QRScanner/QRScannerDialog.vue
@@ -92,9 +92,9 @@ interface ScanProps {
},
})
export default class QRScannerDialog extends Vue {
- @Prop({ type: Function, required: true }) onScan!: ScanProps['onScan'];
- @Prop({ type: Function }) onError?: ScanProps['onError'];
- @Prop({ type: Object }) options?: ScanProps['options'];
+ @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;
@@ -132,7 +132,8 @@ export default class QRScannerDialog extends Vue {
await promise;
this.error = null;
} catch (error) {
- const wrappedError = error instanceof Error ? error : new Error(String(error));
+ const wrappedError =
+ error instanceof Error ? error : new Error(String(error));
this.error = wrappedError.message;
if (this.onError) {
this.onError(wrappedError);
@@ -146,7 +147,8 @@ export default class QRScannerDialog extends Vue {
this.onScan(result);
this.close();
} catch (error) {
- const wrappedError = error instanceof Error ? error : new Error(String(error));
+ const wrappedError =
+ error instanceof Error ? error : new Error(String(error));
this.error = wrappedError.message;
if (this.onError) {
this.onError(wrappedError);
diff --git a/src/libs/crypto/index.ts b/src/libs/crypto/index.ts
index f9770cff..378cab01 100644
--- a/src/libs/crypto/index.ts
+++ b/src/libs/crypto/index.ts
@@ -9,6 +9,7 @@ import {
createEndorserJwtForDid,
CONTACT_URL_PATH_ENDORSER_CH_OLD,
CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI,
+ CONTACT_CONFIRM_URL_PATH_TIME_SAFARI,
} from "../../libs/endorserServer";
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
import { logger } from "../../utils/logger";
@@ -104,34 +105,41 @@ export const accessToken = async (did?: string) => {
};
/**
- @return payload of JWT pulled out of any recognized URL path (if any)
+ * Extract JWT from various URL formats
+ * @param jwtUrlText The URL containing the JWT
+ * @returns The extracted JWT or null if not found
*/
export const getContactJwtFromJwtUrl = (jwtUrlText: string) => {
- let jwtText = jwtUrlText;
- const appImportConfirmUrlLoc = jwtText.indexOf(
- CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
- );
- if (appImportConfirmUrlLoc > -1) {
- jwtText = jwtText.substring(
- appImportConfirmUrlLoc +
- CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI.length,
- );
- }
- const appImportOneUrlLoc = jwtText.indexOf(
- CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI,
- );
- if (appImportOneUrlLoc > -1) {
- jwtText = jwtText.substring(
- appImportOneUrlLoc + CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI.length,
- );
- }
- const endorserUrlPathLoc = jwtText.indexOf(CONTACT_URL_PATH_ENDORSER_CH_OLD);
- if (endorserUrlPathLoc > -1) {
- jwtText = jwtText.substring(
- endorserUrlPathLoc + CONTACT_URL_PATH_ENDORSER_CH_OLD.length,
- );
+ try {
+ let jwtText = jwtUrlText;
+
+ // Try to extract JWT from URL paths
+ const paths = [
+ CONTACT_CONFIRM_URL_PATH_TIME_SAFARI,
+ CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
+ CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI,
+ CONTACT_URL_PATH_ENDORSER_CH_OLD,
+ ];
+
+ for (const path of paths) {
+ const pathIndex = jwtText.indexOf(path);
+ if (pathIndex > -1) {
+ jwtText = jwtText.substring(pathIndex + path.length);
+ break;
+ }
+ }
+
+ // Validate JWT format
+ if (!jwtText.match(/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/)) {
+ logger.error("Invalid JWT format in URL:", jwtUrlText);
+ return null;
+ }
+
+ return jwtText;
+ } catch (error) {
+ logger.error("Error extracting JWT from URL:", error);
+ return null;
}
- return jwtText;
};
export const nextDerivationPath = (origDerivPath: string) => {
diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts
index 15497b65..35c6bb24 100644
--- a/src/libs/endorserServer.ts
+++ b/src/libs/endorserServer.ts
@@ -86,6 +86,12 @@ export const CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI = "/contacts?contactJwt=";
*/
export const CONTACT_URL_PATH_ENDORSER_CH_OLD = "/contact?jwt=";
+/**
+ * URL path suffix for contact confirmation
+ * @constant {string}
+ */
+export const CONTACT_CONFIRM_URL_PATH_TIME_SAFARI = "/contact/confirm/";
+
/**
* The prefix for handle IDs, the permanent ID for claims on Endorser
* @constant {string}
diff --git a/src/services/QRScanner/CapacitorQRScanner.ts b/src/services/QRScanner/CapacitorQRScanner.ts
index 4edbd1de..214a3960 100644
--- a/src/services/QRScanner/CapacitorQRScanner.ts
+++ b/src/services/QRScanner/CapacitorQRScanner.ts
@@ -86,15 +86,18 @@ export class CapacitorQRScanner implements QRScannerService {
};
logger.log("Scanner options:", scanOptions);
-
+
// Add listener for barcode scans
- const handle = await BarcodeScanner.addListener('barcodeScanned', (result) => {
- if (this.scanListener) {
- this.scanListener.onScan(result.barcode.rawValue);
- }
- });
+ 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) {
diff --git a/src/services/QRScanner/QRScannerFactory.ts b/src/services/QRScanner/QRScannerFactory.ts
index 216eda91..2edc7602 100644
--- a/src/services/QRScanner/QRScannerFactory.ts
+++ b/src/services/QRScanner/QRScannerFactory.ts
@@ -13,13 +13,18 @@ export class QRScannerFactory {
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',
+ 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 = typeof __IS_MOBILE__ !== 'undefined' ? __IS_MOBILE__ : capacitorNative;
+ const isMobile =
+ typeof __IS_MOBILE__ !== "undefined" ? __IS_MOBILE__ : capacitorNative;
const platform = Capacitor.getPlatform();
logger.log("Platform detection:", {
@@ -37,7 +42,10 @@ export class QRScannerFactory {
// For other platforms, use native if available
const useNative = capacitorNative || isMobile;
- logger.log("Platform decision:", { useNative, reason: useNative ? "capacitorNative/isMobile" : "web" });
+ logger.log("Platform decision:", {
+ useNative,
+ reason: useNative ? "capacitorNative/isMobile" : "web",
+ });
return useNative;
}
@@ -55,7 +63,11 @@ export class QRScannerFactory {
if (isNative) {
logger.log("Using native MLKit scanner");
this.instance = new CapacitorQRScanner();
- } else if (typeof __USE_QR_READER__ !== 'undefined' ? __USE_QR_READER__ : !isNative) {
+ } else if (
+ typeof __USE_QR_READER__ !== "undefined"
+ ? __USE_QR_READER__
+ : !isNative
+ ) {
logger.log("Using web QR scanner");
this.instance = new WebDialogQRScanner();
} else {
diff --git a/src/utils/logger.ts b/src/utils/logger.ts
index 86389c47..3908b28f 100644
--- a/src/utils/logger.ts
+++ b/src/utils/logger.ts
@@ -31,6 +31,17 @@ export const logger = {
logToDb(message + argsString);
}
},
+ info: (message: string, ...args: unknown[]) => {
+ if (
+ process.env.NODE_ENV !== "production" ||
+ process.env.VITE_PLATFORM === "capacitor"
+ ) {
+ // eslint-disable-next-line no-console
+ console.info(message, ...args);
+ const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
+ logToDb(message + argsString);
+ }
+ },
warn: (message: string, ...args: unknown[]) => {
if (
process.env.NODE_ENV !== "production" ||
diff --git a/src/views/ContactQRScanShowView.vue b/src/views/ContactQRScanShowView.vue
index 01720407..204b749f 100644
--- a/src/views/ContactQRScanShowView.vue
+++ b/src/views/ContactQRScanShowView.vue
@@ -78,10 +78,12 @@
Scan Contact Info
-