forked from trent_larson/crowd-funder-for-time-pwa
Updates for qr code
This commit is contained in:
Binary file not shown.
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
generated
10
package-lock.json
generated
@@ -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
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",
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
danger(message: string, title: string = "Error", timeout = 5000) {
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,60 +331,100 @@ 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
const { payload } = decodeEndorserJwt(jwt);
|
||||
if (!payload) {
|
||||
this.danger(
|
||||
"Could not decode the contact information. Please try again.",
|
||||
"Decode Error",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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; // didn't work inside setVisibility
|
||||
addedMessage =
|
||||
"They were added, and your activity is visible to them.";
|
||||
newContact.seesMe = true;
|
||||
addedMessage = "They were added, and your activity is visible to them.";
|
||||
} else {
|
||||
addedMessage = "They were added.";
|
||||
}
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -238,8 +435,11 @@ export default class ContactQRScanShow extends Vue {
|
||||
3000,
|
||||
);
|
||||
|
||||
if (this.isRegistered) {
|
||||
if (!this.hideRegisterPromptOnNewContact && !newContact.registered) {
|
||||
if (
|
||||
this.isRegistered &&
|
||||
!this.hideRegisterPromptOnNewContact &&
|
||||
!newContact.registered
|
||||
) {
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
@@ -247,7 +447,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
type: "confirm",
|
||||
title: "Register",
|
||||
text: "Do you want to register them?",
|
||||
onCancel: async (stopAsking: boolean) => {
|
||||
onCancel: async (stopAsking?: boolean) => {
|
||||
if (stopAsking) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
@@ -255,7 +455,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||
}
|
||||
},
|
||||
onNo: async (stopAsking: boolean) => {
|
||||
onNo: async (stopAsking?: boolean) => {
|
||||
if (stopAsking) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
@@ -272,33 +472,16 @@ export default class ContactQRScanShow extends Vue {
|
||||
);
|
||||
}, 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,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Invalid Contact QR Code",
|
||||
text: "No QR code detected with contact information.",
|
||||
},
|
||||
5000,
|
||||
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,12 +586,9 @@ 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);
|
||||
async onCopyUrlToClipboard(): Promise<void> {
|
||||
try {
|
||||
await this.platformService.writeToClipboard(this.qrValue);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -416,14 +598,15 @@ export default class ContactQRScanShow extends Vue {
|
||||
},
|
||||
2000,
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error copying to clipboard:", error);
|
||||
this.danger("Failed to copy to clipboard", "Error");
|
||||
}
|
||||
}
|
||||
|
||||
onCopyDidToClipboard() {
|
||||
//this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing
|
||||
useClipboard()
|
||||
.copy(this.activeDid)
|
||||
.then(() => {
|
||||
async onCopyDidToClipboard(): Promise<void> {
|
||||
try {
|
||||
await this.platformService.writeToClipboard(this.activeDid);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -433,7 +616,101 @@ export default class ContactQRScanShow extends Vue {
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
Normal file
44
test-scripts/generate_test_qr.js
Normal file
@@ -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
generated
308
test-scripts/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -13,7 +13,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",
|
||||
|
||||
BIN
test_qr.png
Normal file
BIN
test_qr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -1,12 +1,17 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import path from "path";
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
|
||||
export default defineConfig({
|
||||
// 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: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
'@': 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'),
|
||||
@@ -24,6 +29,10 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
},
|
||||
define: {
|
||||
__USE_QR_READER__: JSON.stringify(!isMobile),
|
||||
__IS_MOBILE__: JSON.stringify(isMobile),
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
target: 'esnext',
|
||||
@@ -33,14 +42,18 @@ export default defineConfig({
|
||||
transformMixedEsModules: true
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['stream', 'util', 'crypto'],
|
||||
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]`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user