Browse Source

Updates for qr code

qrcode-capacitor
Matthew Raymer 2 months ago
parent
commit
5fea1cf530
  1. BIN
      android/.gradle/file-system.probe
  2. 1
      android/app/capacitor.build.gradle
  3. 1
      android/app/src/main/AndroidManifest.xml
  4. 4
      android/app/src/main/assets/capacitor.plugins.json
  5. 3
      android/capacitor.settings.gradle
  6. 10
      package-lock.json
  7. 10
      package.json
  8. 14
      src/services/PlatformService.ts
  9. 51
      src/services/platforms/CapacitorPlatformService.ts
  10. 27
      src/services/platforms/WebPlatformService.ts
  11. 585
      src/views/ContactQRScanShowView.vue
  12. 44
      test-scripts/generate_test_qr.js
  13. 308
      test-scripts/package-lock.json
  14. 5
      test-scripts/package.json
  15. BIN
      test_qr.png
  16. 83
      vite.config.ts

BIN
android/.gradle/file-system.probe

Binary file not shown.

1
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')

1
android/app/src/main/AndroidManifest.xml

@ -41,4 +41,5 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
</manifest>

4
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"

3
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')

10
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",

10
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",

14
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<void>;
// 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<void>;
/**
* Reads text from the system clipboard.
* @returns Promise resolving to the clipboard text
*/
readFromClipboard(): Promise<string>;
}

51
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<void> {
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<void> {
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<string> {
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");
}
}
}

27
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<void> {
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<string> {
try {
return await navigator.clipboard.readText();
} catch (error) {
logger.error("Error reading from clipboard:", error);
throw new Error("Failed to read from clipboard");
}
}
}

585
src/views/ContactQRScanShowView.vue

@ -77,11 +77,24 @@
<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>
If you do not see a scanning camera window here, check your camera
permissions.
</span>
<!-- Web QR Code Scanner -->
<qrcode-stream
v-if="useQRReader"
@detect="onScanDetect"
@error="onScanError"
/>
<!-- Mobile Camera Button -->
<div v-else class="mt-4">
<button
class="bg-blue-500 text-white px-4 py-2 rounded-md"
@click="openMobileCamera"
>
Open Camera
</button>
<p class="mt-2 text-sm text-gray-600">
If you do not see the camera, check your camera permissions.
</p>
</div>
</div>
</section>
</template>
@ -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<void> {
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<boolean> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<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,
);
} catch (error) {
logger.error("Error copying to clipboard:", error);
this.danger("Failed to copy to clipboard", "Error");
}
}
async copyToClipboard(text: string): Promise<void> {
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<void> {
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__;
}
}
</script>

44
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);

308
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",

5
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"
}
}
}

BIN
test_qr.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

83
vite.config.ts

@ -1,46 +1,59 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import { fileURLToPath, URL } from 'node:url';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'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'
// Determine if we're building for mobile based on environment variable
const isMobile = process.env.VITE_PLATFORM === 'capacitor';
export default defineConfig(({ mode }) => {
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]`
}
}
}
}
};
});
Loading…
Cancel
Save