From 5fea1cf530efae43cccee87da23214a13d9c62a5 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 11 Apr 2025 11:28:14 +0000 Subject: [PATCH] Updates for qr code --- android/.gradle/file-system.probe | Bin 8 -> 8 bytes android/app/capacitor.build.gradle | 1 + android/app/src/main/AndroidManifest.xml | 1 + .../src/main/assets/capacitor.plugins.json | 4 + android/capacitor.settings.gradle | 3 + package-lock.json | 10 + package.json | 10 +- src/services/PlatformService.ts | 14 + .../platforms/CapacitorPlatformService.ts | 51 +- src/services/platforms/WebPlatformService.ts | 27 + src/views/ContactQRScanShowView.vue | 585 +++++++++++++----- test-scripts/generate_test_qr.js | 44 ++ test-scripts/package-lock.json | 308 ++++++++- test-scripts/package.json | 5 +- test_qr.png | Bin 0 -> 10307 bytes vite.config.ts | 83 +-- 16 files changed, 944 insertions(+), 202 deletions(-) create mode 100644 test-scripts/generate_test_qr.js create mode 100644 test_qr.png diff --git a/android/.gradle/file-system.probe b/android/.gradle/file-system.probe index 44390a335b6402f9ddb8b68a72a8e49b763285fe..c35ad2ac8c3fc5f53fda48f47383e7f135253c6c 100644 GIT binary patch literal 8 PcmZQzV4S8bf2$q<1{eY} literal 8 PcmZQzV4Nn^HRTNe2NnXn diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index a54399fa..9a925acf 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -11,6 +11,7 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { implementation project(':capacitor-app') implementation project(':capacitor-camera') + implementation project(':capacitor-clipboard') implementation project(':capacitor-filesystem') implementation project(':capacitor-share') implementation project(':capawesome-capacitor-file-picker') diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 70ac8410..a95eb6e7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -41,4 +41,5 @@ + diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index 30b5ba98..6a10948b 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -7,6 +7,10 @@ "pkg": "@capacitor/camera", "classpath": "com.capacitorjs.plugins.camera.CameraPlugin" }, + { + "pkg": "@capacitor/clipboard", + "classpath": "com.capacitorjs.plugins.clipboard.ClipboardPlugin" + }, { "pkg": "@capacitor/filesystem", "classpath": "com.capacitorjs.plugins.filesystem.FilesystemPlugin" diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 736eac60..77dd6f02 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -8,6 +8,9 @@ project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/ include ':capacitor-camera' project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android') +include ':capacitor-clipboard' +project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacitor/clipboard/android') + include ':capacitor-filesystem' project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android') diff --git a/package-lock.json b/package-lock.json index dd158e30..38916b28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@capacitor/app": "^6.0.0", "@capacitor/camera": "^6.0.0", "@capacitor/cli": "^6.2.0", + "@capacitor/clipboard": "^6.0.2", "@capacitor/core": "^6.2.0", "@capacitor/filesystem": "^6.0.0", "@capacitor/ios": "^6.2.0", @@ -2882,6 +2883,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@capacitor/clipboard": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@capacitor/clipboard/-/clipboard-6.0.2.tgz", + "integrity": "sha512-jQ6UeFra5NP58THNZNb7HtzOZU7cHsjgrbQGVuMTgsK1uTILZpNeh+pfqHbKggba6KaNh5DAsJvEVQGpIR1VBA==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^6.0.0" + } + }, "node_modules/@capacitor/core": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz", diff --git a/package.json b/package.json index 686caad4..78c6b859 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,13 @@ "name": "Time Safari Team" }, "scripts": { - "dev": "vite --config vite.config.dev.mts", + "dev": "vite", "serve": "vite preview", - "build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.mts", - "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src", + "build": "vite build", + "build:mobile": "VITE_PLATFORM=capacitor vite build", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/", "lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", "prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js", "test:all": "npm run test:prerequisites && npm run build && npm run test:web && npm run test:mobile", @@ -47,6 +50,7 @@ "@capacitor/app": "^6.0.0", "@capacitor/camera": "^6.0.0", "@capacitor/cli": "^6.2.0", + "@capacitor/clipboard": "^6.0.2", "@capacitor/core": "^6.2.0", "@capacitor/filesystem": "^6.0.0", "@capacitor/ios": "^6.2.0", diff --git a/src/services/PlatformService.ts b/src/services/PlatformService.ts index 574b1a3a..adfd8012 100644 --- a/src/services/PlatformService.ts +++ b/src/services/PlatformService.ts @@ -98,4 +98,18 @@ export interface PlatformService { * @returns Promise that resolves when the deep link has been handled */ handleDeepLink(url: string): Promise; + + // Clipboard operations + /** + * Writes text to the system clipboard. + * @param text - The text to write to the clipboard + * @returns Promise that resolves when the write is complete + */ + writeToClipboard(text: string): Promise; + + /** + * Reads text from the system clipboard. + * @returns Promise resolving to the clipboard text + */ + readFromClipboard(): Promise; } diff --git a/src/services/platforms/CapacitorPlatformService.ts b/src/services/platforms/CapacitorPlatformService.ts index 9c655eb6..2fb9b8e4 100644 --- a/src/services/platforms/CapacitorPlatformService.ts +++ b/src/services/platforms/CapacitorPlatformService.ts @@ -7,6 +7,7 @@ import { Filesystem, Directory, Encoding } from "@capacitor/filesystem"; import { Camera, CameraResultType, CameraSource } from "@capacitor/camera"; import { Share } from "@capacitor/share"; import { logger } from "../../utils/logger"; +import { Clipboard } from "@capacitor/clipboard"; /** * Platform service implementation for Capacitor (mobile) platform. @@ -318,12 +319,12 @@ export class CapacitorPlatformService implements PlatformService { async writeAndShareFile(fileName: string, content: string): Promise { const timestamp = new Date().toISOString(); const logData = { - action: 'writeAndShareFile', + action: "writeAndShareFile", fileName, contentLength: content.length, timestamp, }; - logger.log('[CapacitorPlatformService]', JSON.stringify(logData, null, 2)); + logger.log("[CapacitorPlatformService]", JSON.stringify(logData, null, 2)); try { const { uri } = await Filesystem.writeFile({ @@ -334,13 +335,16 @@ export class CapacitorPlatformService implements PlatformService { recursive: true, }); - logger.log('[CapacitorPlatformService] File write successful:', { uri, timestamp: new Date().toISOString() }); + logger.log("[CapacitorPlatformService] File write successful:", { + uri, + timestamp: new Date().toISOString(), + }); await Share.share({ - title: 'TimeSafari Backup', - text: 'Here is your backup file.', + title: "TimeSafari Backup", + text: "Here is your backup file.", url: uri, - dialogTitle: 'Share your backup file', + dialogTitle: "Share your backup file", }); } catch (error) { const err = error as Error; @@ -349,7 +353,10 @@ export class CapacitorPlatformService implements PlatformService { stack: err.stack, timestamp: new Date().toISOString(), }; - logger.error('[CapacitorPlatformService] Error writing or sharing file:', JSON.stringify(errLog, null, 2)); + logger.error( + "[CapacitorPlatformService] Error writing or sharing file:", + JSON.stringify(errLog, null, 2), + ); throw new Error(`Failed to write or share file: ${err.message}`); } } @@ -470,4 +477,34 @@ export class CapacitorPlatformService implements PlatformService { // This is just a placeholder for the interface return Promise.resolve(); } + + /** + * Writes text to the system clipboard using Capacitor's Clipboard plugin. + * @param text - The text to write to the clipboard + * @returns Promise that resolves when the write is complete + */ + async writeToClipboard(text: string): Promise { + try { + await Clipboard.write({ + string: text, + }); + } catch (error) { + logger.error("Error writing to clipboard:", error); + throw new Error("Failed to write to clipboard"); + } + } + + /** + * Reads text from the system clipboard using Capacitor's Clipboard plugin. + * @returns Promise resolving to the clipboard text + */ + async readFromClipboard(): Promise { + try { + const { value } = await Clipboard.read(); + return value; + } catch (error) { + logger.error("Error reading from clipboard:", error); + throw new Error("Failed to read from clipboard"); + } + } } diff --git a/src/services/platforms/WebPlatformService.ts b/src/services/platforms/WebPlatformService.ts index 8b911a92..591e8883 100644 --- a/src/services/platforms/WebPlatformService.ts +++ b/src/services/platforms/WebPlatformService.ts @@ -228,4 +228,31 @@ export class WebPlatformService implements PlatformService { // Web platform can handle deep links through URL parameters return Promise.resolve(); } + + /** + * Writes text to the system clipboard using the Web Clipboard API. + * @param text - The text to write to the clipboard + * @returns Promise that resolves when the write is complete + */ + async writeToClipboard(text: string): Promise { + try { + await navigator.clipboard.writeText(text); + } catch (error) { + logger.error("Error writing to clipboard:", error); + throw new Error("Failed to write to clipboard"); + } + } + + /** + * Reads text from the system clipboard using the Web Clipboard API. + * @returns Promise resolving to the clipboard text + */ + async readFromClipboard(): Promise { + try { + return await navigator.clipboard.readText(); + } catch (error) { + logger.error("Error reading from clipboard:", error); + throw new Error("Failed to read from clipboard"); + } + } } diff --git a/src/views/ContactQRScanShowView.vue b/src/views/ContactQRScanShowView.vue index f315e658..5d5563cf 100644 --- a/src/views/ContactQRScanShowView.vue +++ b/src/views/ContactQRScanShowView.vue @@ -77,11 +77,24 @@

Scan Contact Info

- - - If you do not see a scanning camera window here, check your camera - permissions. - + + + +
+ +

+ If you do not see the camera, check your camera permissions. +

+
@@ -91,8 +104,8 @@ 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 { PlatformServiceFactory } from "../services/PlatformServiceFactory"; +import { PlatformService } from "../services/PlatformService"; import QuickNav from "../components/QuickNav.vue"; import UserNameDialog from "../components/UserNameDialog.vue"; import { NotificationIface } from "../constants/app"; @@ -110,9 +123,15 @@ 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 { Camera, CameraResultType, CameraSource } from "@capacitor/camera"; + +// Declare global constants +declare const __USE_QR_READER__: boolean; +declare const __IS_MOBILE__: boolean; + @Component({ components: { - QrcodeStream, + QrcodeStream: __USE_QR_READER__ ? QrcodeStream : null, QRCodeVue3, QuickNav, UserNameDialog, @@ -121,6 +140,11 @@ import { logger } from "../utils/logger"; export default class ContactQRScanShow extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; $router!: Router; + declare $refs: { + userNameDialog: { + open: (callback: (name: string) => void) => void; + }; + }; activeDid = ""; apiServer = ""; @@ -131,6 +155,9 @@ export default class ContactQRScanShow extends Vue { ETHR_DID_PREFIX = ETHR_DID_PREFIX; + private platformService: PlatformService = + PlatformServiceFactory.getInstance(); + async created() { const settings = await retrieveSettingsForActiveAccount(); this.activeDid = settings.activeDid || ""; @@ -144,19 +171,149 @@ export default class ContactQRScanShow extends Vue { if (account) { const name = (settings.firstName || "") + - (settings.lastName ? ` ${settings.lastName}` : ""); // lastName is deprecated, pre v 0.1.3 + (settings.lastName ? ` ${settings.lastName}` : ""); this.qrValue = await generateEndorserJwtUrlForAccount( account, !!settings.isRegistered, name, - settings.profileImageUrl, + settings.profileImageUrl || "", false, ); } + + // Initialize camera with retry logic + if (this.useQRReader) { + await this.initializeCamera(); + } + } + + async initializeCamera(retryCount = 0): Promise { + try { + const capabilities = this.platformService.getCapabilities(); + if (!capabilities.hasCamera) { + this.danger("No camera available on this device.", "Camera Error"); + return; + } + + // Check camera permissions + const hasPermission = await this.checkCameraPermission(); + if (!hasPermission) { + this.danger( + "Camera permission is required to scan QR codes. Please enable camera access in your device settings.", + "Permission Required" + ); + return; + } + + // If we get here, camera should be available + this.$notify( + { + group: "alert", + type: "success", + title: "Camera Ready", + text: "Camera is ready to scan QR codes.", + }, + 3000 + ); + } catch (error) { + logger.error("Error initializing camera:", error); + + // Retry up to 3 times for certain errors + if (retryCount < 3) { + const isPermissionError = error instanceof Error && + (error.message.includes("permission") || + error.message.includes("NotReadableError")); + + if (isPermissionError) { + // Wait before retrying + await new Promise(resolve => setTimeout(resolve, 1000)); + return this.initializeCamera(retryCount + 1); + } + } + + this.danger( + "Failed to initialize camera. Please check your camera permissions and try again.", + "Camera Error" + ); + } + } + + async checkCameraPermission(): Promise { + try { + const capabilities = this.platformService.getCapabilities(); + if (!capabilities.hasCamera) { + return false; + } + + // Try to access camera to check permissions + await this.platformService.takePicture(); + return true; + } catch (error) { + logger.error("Camera permission check failed:", error); + return false; + } + } + + async openMobileCamera(): Promise { + try { + // Check permissions first + const hasPermission = await this.checkCameraPermission(); + if (!hasPermission) { + this.danger( + "Camera permission is required. Please enable camera access in your device settings.", + "Permission Required" + ); + return; + } + + const image = await Camera.getPhoto({ + quality: 90, + allowEditing: false, + resultType: CameraResultType.DataUrl, + source: CameraSource.Camera, + }); + + if (image.dataUrl) { + await this.processImageForQRCode(image.dataUrl); + } + } catch (error) { + logger.error("Error taking picture:", error); + this.danger( + "Failed to access camera. Please check your camera permissions.", + "Camera Error" + ); + } } - danger(message: string, title: string = "Error", timeout = 5000) { + async processImageForQRCode(_imageDataUrl: string) { + try { + // Here you would implement QR code scanning from the image + // For example, using jsQR: + // const image = new Image(); + // image.src = imageDataUrl; + // image.onload = () => { + // const canvas = document.createElement('canvas'); + // const context = canvas.getContext('2d'); + // canvas.width = image.width; + // canvas.height = image.height; + // context.drawImage(image, 0, 0); + // const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + // const code = jsQR(imageData.data, imageData.width, imageData.height); + // if (code) { + // this.onScanDetect([{ rawValue: code.data }]); + // } + // }; + } catch (error) { + logger.error("Error processing image for QR code:", error); + this.danger( + "Failed to process the image. Please try again.", + "Processing Error", + ); + } + } + + danger(message: string, title = "Error", timeout = 5000): void { this.$notify( { group: "alert", @@ -174,131 +331,157 @@ export default class ContactQRScanShow extends Vue { */ // 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) { + async onScanDetect(content: any): Promise { const url = content[0]?.rawValue; - if (url) { - 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"); + if (!url) { + this.danger("No QR code detected. Please try again.", "Scan Error"); + return; + } + + // Validate URL format first + if (!url.startsWith("http://") && !url.startsWith("https://")) { + this.danger( + "Invalid QR code format. Please scan a valid TimeSafari contact QR code.", + "Invalid Format", + ); + return; + } + + let newContact: Contact; + try { + // Extract JWT from URL + const jwt = getContactJwtFromJwtUrl(url); + if (!jwt) { + this.danger( + "Could not extract contact information from the QR code. Please try again.", + "Invalid QR Code", + ); return; } - try { - await db.open(); - await db.contacts.add(newContact); + // Validate JWT format + if ( + !jwt.match(/^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$/) + ) { + this.danger( + "The QR code contains invalid data. Please scan a valid TimeSafari contact QR code.", + "Invalid Data", + ); + return; + } - let addedMessage; - if (this.activeDid) { - await this.setVisibility(newContact, true); - newContact.seesMe = true; // didn't work inside setVisibility - addedMessage = - "They were added, and your activity is visible to them."; - } else { - addedMessage = "They were added."; - } - this.$notify( - { - group: "alert", - type: "success", - title: "Contact Added", - text: addedMessage, - }, - 3000, + const { payload } = decodeEndorserJwt(jwt); + if (!payload) { + this.danger( + "Could not decode the contact information. Please try again.", + "Decode Error", ); + return; + } - if (this.isRegistered) { - if (!this.hideRegisterPromptOnNewContact && !newContact.registered) { - 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; - } - }, - 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) { - logger.error("Error saving contact info:", e); - this.$notify( - { - group: "alert", - type: "danger", - title: "Contact Error", - text: "Could not save contact info. Check if it already exists.", - }, - 5000, + // Validate required fields + if (!payload.own && !payload.iss) { + this.danger( + "Missing required contact information. Please scan a valid TimeSafari contact QR code.", + "Incomplete Data", ); + return; } - } else { + + newContact = { + did: payload.own?.did || payload.iss, + 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( + "Missing contact identifier. Please scan a valid TimeSafari contact QR code.", + "Incomplete Contact", + ); + return; + } + + if (!isDid(newContact.did)) { + this.danger( + "Invalid contact identifier format. The identifier must begin with 'did:'.", + "Invalid Identifier", + ); + return; + } + + await db.open(); + await db.contacts.add(newContact); + + 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."; + } + this.$notify( { group: "alert", - type: "danger", - title: "Invalid Contact QR Code", - text: "No QR code detected with contact information.", + type: "success", + title: "Contact Added", + text: addedMessage, }, - 5000, + 3000, + ); + + if ( + this.isRegistered && + !this.hideRegisterPromptOnNewContact && + !newContact.registered + ) { + 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; + } + }, + 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) { + 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.", + "Processing Error", ); } } - async setVisibility(contact: Contact, visibility: boolean) { + async setVisibility(contact: Contact, visibility: boolean): Promise { const result = await setVisibilityUtil( this.activeDid, this.apiServer, @@ -314,7 +497,7 @@ export default class ContactQRScanShow extends Vue { } } - async register(contact: Contact) { + async register(contact: Contact): Promise { this.$notify( { group: "alert", @@ -364,17 +547,19 @@ 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; + const responseData = serverError.response?.data as { + error?: { message?: string }; + }; + if (responseData?.error?.message) { + userMessage = responseData.error.message; } else if (serverError.message) { - userMessage = serverError.message; // Info for the user + userMessage = serverError.message; } else { userMessage = JSON.stringify(serverError.toJSON()); } } else { userMessage = error as string; } - // Now set that error for the user to see. this.$notify( { group: "alert", @@ -388,7 +573,7 @@ export default class ContactQRScanShow extends Vue { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - onScanError(error: any) { + onScanError(error: any): void { logger.error("Scan was invalid:", error); this.$notify( { @@ -401,39 +586,131 @@ export default class ContactQRScanShow extends Vue { ); } - onCopyUrlToClipboard() { - //this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing - useClipboard() - .copy(this.qrValue) - .then(() => { - // console.log("Contact URL:", this.qrValue); - this.$notify( - { - group: "alert", - type: "toast", - title: "Copied", - text: "Contact URL was copied to clipboard.", - }, - 2000, - ); + async onCopyUrlToClipboard(): Promise { + try { + await this.platformService.writeToClipboard(this.qrValue); + 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"); + } + } + + async onCopyDidToClipboard(): Promise { + 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, + ); + } catch (error) { + logger.error("Error copying to clipboard:", error); + this.danger("Failed to copy to clipboard", "Error"); + } + } + + async copyToClipboard(text: string): Promise { + try { + await this.platformService.writeToClipboard(text); + this.$notify( + { + group: "alert", + type: "success", + title: "Copied to clipboard", + text: "The DID has been copied to your clipboard.", + }, + 3000, + ); + } catch (error) { + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "Failed to copy to clipboard.", + }, + 3000, + ); + } + } + + async requestCameraPermission(): Promise { + try { + const capabilities = this.platformService.getCapabilities(); + if (capabilities.hasCamera) { + try { + await this.platformService.takePicture(); + this.$notify( + { + group: "alert", + type: "success", + title: "Camera Access Granted", + text: "You can now scan QR codes.", + }, + 3000, + ); + } catch (error) { + this.$notify( + { + group: "alert", + type: "danger", + title: "Camera Access Denied", + text: "Please enable camera access in your device settings.", + }, + 5000, + ); + } + } + } catch (error) { + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "Failed to request camera permission.", + }, + 3000, + ); + } + } + + async onCancel(stopAsking?: boolean) { + if (stopAsking) { + await db.settings.update(MASTER_SETTINGS_KEY, { + hideRegisterPromptOnNewContact: stopAsking, }); + this.hideRegisterPromptOnNewContact = stopAsking; + } } - onCopyDidToClipboard() { - //this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing - useClipboard() - .copy(this.activeDid) - .then(() => { - 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, - ); + async onNo(stopAsking?: boolean) { + if (stopAsking) { + await db.settings.update(MASTER_SETTINGS_KEY, { + hideRegisterPromptOnNewContact: stopAsking, }); + this.hideRegisterPromptOnNewContact = stopAsking; + } + } + + get useQRReader(): boolean { + return __USE_QR_READER__; + } + + get isMobile(): boolean { + return __IS_MOBILE__; } } diff --git a/test-scripts/generate_test_qr.js b/test-scripts/generate_test_qr.js new file mode 100644 index 00000000..40cae750 --- /dev/null +++ b/test-scripts/generate_test_qr.js @@ -0,0 +1,44 @@ +const { createJWT, SimpleSigner } = require('did-jwt'); +const { Buffer } = require('buffer'); +const qrcode = require('qrcode'); +const fs = require('fs'); + +// Test account details +const testAccount = { + did: 'did:ethr:0x1234567890123456789012345678901234567890', + privateKey: '0x1234567890123456789012345678901234567890123456789012345678901234', + publicKey: '0x12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678' +}; + +async function generateTestQR() { + // Create contact info payload + const contactInfo = { + iat: Date.now(), + iss: testAccount.did, + own: { + did: testAccount.did, + name: 'Test User', + publicEncKey: Buffer.from(testAccount.publicKey.slice(2), 'hex').toString('base64'), + registered: true, + profileImageUrl: 'https://example.com/profile.jpg' + } + }; + + // Create JWT + const signer = await SimpleSigner(testAccount.privateKey); + const jwt = await createJWT(contactInfo, { + issuer: testAccount.did, + signer + }); + + // Create QR code URL + const url = `https://timesafari.app/contact/confirm/${jwt}`; + + // Generate QR code and save as PNG + const qrCode = await qrcode.toBuffer(url); + fs.writeFileSync('test_qr.png', qrCode); + console.log('QR Code URL:', url); + console.log('QR code saved as test_qr.png'); +} + +generateTestQR().catch(console.error); \ No newline at end of file diff --git a/test-scripts/package-lock.json b/test-scripts/package-lock.json index 64a66cea..ec7ebd80 100644 --- a/test-scripts/package-lock.json +++ b/test-scripts/package-lock.json @@ -12,7 +12,8 @@ "@ethersproject/hdnode": "^5.7.0", "@ethersproject/wallet": "^5.7.0", "axios": "^1.6.2", - "did-jwt": "^6.11.6" + "did-jwt": "^6.11.6", + "qrcode": "^1.5.4" }, "devDependencies": { "@types/node": "^20.10.0", @@ -860,6 +861,30 @@ "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", "license": "MIT" }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -915,12 +940,50 @@ "node": ">= 0.4" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/canonicalize": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.0.0.tgz", "integrity": "sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w==", "license": "Apache-2.0" }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -940,6 +1003,15 @@ "dev": true, "license": "MIT" }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -985,6 +1057,12 @@ "node": ">=0.3.1" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1020,6 +1098,12 @@ "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1065,6 +1149,19 @@ "node": ">= 0.4" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -1109,6 +1206,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1224,12 +1330,33 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "license": "MIT" }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1285,18 +1412,136 @@ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", "license": "(Apache-2.0 AND MIT)" }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", "license": "MIT" }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -1378,6 +1623,67 @@ "dev": true, "license": "MIT" }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/test-scripts/package.json b/test-scripts/package.json index fd57f96d..7ed6434f 100644 --- a/test-scripts/package.json +++ b/test-scripts/package.json @@ -13,11 +13,12 @@ "@ethersproject/hdnode": "^5.7.0", "@ethersproject/wallet": "^5.7.0", "axios": "^1.6.2", - "did-jwt": "^6.11.6" + "did-jwt": "^6.11.6", + "qrcode": "^1.5.4" }, "devDependencies": { "@types/node": "^20.10.0", "ts-node": "^10.9.1", "typescript": "^5.3.2" } -} \ No newline at end of file +} diff --git a/test_qr.png b/test_qr.png new file mode 100644 index 0000000000000000000000000000000000000000..47e51824d0d5edca969778a50df9c8e4b514c47f GIT binary patch literal 10307 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 { + return { + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + 'nostr-tools': path.resolve(__dirname, 'node_modules/nostr-tools'), + 'nostr-tools/nip06': path.resolve(__dirname, 'node_modules/nostr-tools/nip06'), + 'nostr-tools/core': path.resolve(__dirname, 'node_modules/nostr-tools/core'), + stream: 'stream-browserify', + util: 'util', + crypto: 'crypto-browserify' + }, + mainFields: ['module', 'jsnext:main', 'jsnext', 'main'], }, - mainFields: ['module', 'jsnext:main', 'jsnext', 'main'], - }, - optimizeDeps: { - include: ['nostr-tools', 'nostr-tools/nip06', 'nostr-tools/core'], - esbuildOptions: { - define: { - global: 'globalThis' + optimizeDeps: { + include: ['nostr-tools', 'nostr-tools/nip06', 'nostr-tools/core'], + esbuildOptions: { + define: { + global: 'globalThis' + } } - } - }, - build: { - sourcemap: true, - target: 'esnext', - chunkSizeWarningLimit: 1000, - commonjsOptions: { - include: [/node_modules/], - transformMixedEsModules: true }, - rollupOptions: { - external: ['stream', 'util', 'crypto'], - output: { - globals: { - stream: 'stream', - util: 'util', - crypto: 'crypto' + define: { + __USE_QR_READER__: JSON.stringify(!isMobile), + __IS_MOBILE__: JSON.stringify(isMobile), + }, + build: { + sourcemap: true, + target: 'esnext', + chunkSizeWarningLimit: 1000, + commonjsOptions: { + include: [/node_modules/], + transformMixedEsModules: true + }, + rollupOptions: { + external: isMobile ? ['vue-qrcode-reader'] : [], + output: { + globals: { + stream: 'stream', + util: 'util', + crypto: 'crypto' + }, + entryFileNames: `[name]${isMobile ? '-mobile' : ''}.js`, + chunkFileNames: `[name]${isMobile ? '-mobile' : ''}.js`, + assetFileNames: `[name]${isMobile ? '-mobile' : ''}.[ext]` } } } - } + }; }); \ No newline at end of file