@ -109,26 +109,99 @@ import { PlatformService } from "../services/PlatformService";
import QuickNav from "../components/QuickNav.vue" ;
import QuickNav from "../components/QuickNav.vue" ;
import UserNameDialog from "../components/UserNameDialog.vue" ;
import UserNameDialog from "../components/UserNameDialog.vue" ;
import { NotificationIface } from "../constants/app" ;
import { NotificationIface } from "../constants/app" ;
import { db , retrieveSettingsForActiveAccount } from "../db/index" ;
import { db } from "../db/index" ;
import { Contact } from "../db/tables/contacts" ;
import { Contact } from "../db/tables/contacts" ;
import { MASTER_SETTINGS_KEY } from "../db/tables/settings" ;
import { MASTER_SETTINGS_KEY } from "../db/tables/settings" ;
import { getContactJwtFromJwtUrl } from "../libs/crypto" ;
import { getContactJwtFromJwtUrl } from "../libs/crypto" ;
import {
import {
generateEndorserJwtUrlForAccount ,
isDid ,
isDid ,
register ,
register ,
setVisibilityUtil ,
setVisibilityUtil ,
} from "../libs/endorserServer" ;
} from "../libs/endorserServer" ;
import { decodeEndorserJwt , ETHR_DID_PREFIX } from "../libs/crypto/vc" ;
import { decodeEndorserJwt , ETHR_DID_PREFIX } from "../libs/crypto/vc" ;
import { retrieveAccountMetadata } from "../libs/util" ;
import { Router } from "vue-router" ;
import { Router } from "vue-router" ;
import { logger } from "../utils/logger" ;
import { logger } from "../utils/logger" ;
import { Camera , CameraResultType , CameraSource } from "@capacitor/camera" ;
import { Camera , CameraResultType , CameraSource , ImageOptions , CameraPermissionState } from '@capacitor/camera' ;
import { App } from '@capacitor/app' ;
import jsQR from "jsqr" ;
/ / D e c l a r e g l o b a l c o n s t a n t s
/ / D e c l a r e g l o b a l c o n s t a n t s
declare const __USE_QR_READER__ : boolean ;
declare const __USE_QR_READER__ : boolean ;
declare const __IS_MOBILE__ : boolean ;
declare const __IS_MOBILE__ : boolean ;
/ / D e f i n e a l l p o s s i b l e c a m e r a s t a t e s
type CameraState =
| 'initializing'
| 'ready'
| 'error'
| 'checking_permissions'
| 'no_camera_capability'
| 'permission_status_checked'
| 'requesting_permission'
| 'permission_requested'
| 'permission_check_error'
| 'camera_initialized'
| 'camera_error'
| 'opening_camera'
| 'capture_already_in_progress'
| 'photo_captured'
| 'processing_photo'
| 'no_image_data'
| 'capture_error'
| 'user_cancelled'
| 'permission_error'
| 'hardware_unavailable'
| 'capture_completed'
| 'cleanup'
| 'processing_completed' ;
/ / D e f i n e a l l p o s s i b l e Q R p r o c e s s i n g s t a t e s
type QRProcessingState =
| 'processing_image'
| 'qr_code_detected'
| 'no_qr_code_found'
| 'processing_error' ;
interface CameraStateHistoryEntry {
state : CameraState | QRProcessingState ;
timestamp : number ;
details ? : Record < string , unknown > ;
}
/ / C u s t o m A p p S t a t e t y p e t o m a t c h o u r n e e d s
interface CustomAppState {
state : 'active' | 'inactive' | 'background' | 'foreground' ;
}
interface AppStateChangeEvent {
isActive : boolean ;
}
/ / D e f i n e w o r k e r m e s s a g e t y p e s
interface QRCodeResult {
data : string ;
location : {
topLeftCorner : { x : number ; y : number } ;
topRightCorner : { x : number ; y : number } ;
bottomRightCorner : { x : number ; y : number } ;
bottomLeftCorner : { x : number ; y : number } ;
} ;
}
interface WorkerSuccessMessage {
success : true ;
code : QRCodeResult ;
}
interface WorkerErrorMessage {
success : false ;
error : string ;
}
type WorkerMessage = WorkerSuccessMessage | WorkerErrorMessage ;
@ Component ( {
@ Component ( {
components : {
components : {
QrcodeStream : __USE_QR_READER__ ? QrcodeStream : null ,
QrcodeStream : __USE_QR_READER__ ? QrcodeStream : null ,
@ -158,158 +231,339 @@ export default class ContactQRScanShow extends Vue {
private platformService : PlatformService =
private platformService : PlatformService =
PlatformServiceFactory . getInstance ( ) ;
PlatformServiceFactory . getInstance ( ) ;
private cameraActive = false ;
private lastCameraState : CameraState | QRProcessingState = 'initializing' ;
private cameraStateHistory : CameraStateHistoryEntry [ ] = [ ] ;
private readonly STATE_HISTORY_LIMIT = 20 ;
private isCapturingPhoto = false ;
private appStateListener ? : { remove : ( ) => Promise < void > } ;
async created ( ) {
async created ( ) {
const settings = await retrieveSettingsForActiveAccount ( ) ;
logger . log ( 'ContactQRScanShow component created' ) ;
this . activeDid = settings . activeDid || "" ;
try {
this . apiServer = settings . apiServer || "" ;
/ / R e m o v e a n y e x i s t i n g l i s t e n e r s f i r s t
this . givenName = settings . firstName || "" ;
await App . removeAllListeners ( ) ;
this . hideRegisterPromptOnNewContact =
! ! settings . hideRegisterPromptOnNewContact ;
this . isRegistered = ! ! settings . isRegistered ;
const account = await retrieveAccountMetadata ( this . activeDid ) ;
if ( account ) {
const name =
( settings . firstName || "" ) +
( settings . lastName ? ` ${ settings . lastName } ` : "" ) ;
this . qrValue = await generateEndorserJwtUrlForAccount (
account ,
! ! settings . isRegistered ,
name ,
settings . profileImageUrl || "" ,
false ,
) ;
}
/ / I n i t i a l i z e c a m e r a w i t h r e t r y l o g i c
/ / A d d a p p s t a t e l i s t e n e r s
if ( this . useQRReader ) {
const stateListener = await App . addListener ( 'appStateChange' , ( state : AppStateChangeEvent ) => {
await this . initializeCamera ( ) ;
logger . log ( 'App state changed:' , state ) ;
if ( ! state . isActive && this . cameraActive ) {
this . cleanupCamera ( ) ;
}
} ) ;
this . appStateListener = stateListener ;
await App . addListener ( 'pause' , ( ) => {
logger . log ( 'App paused' ) ;
if ( this . cameraActive ) {
this . cleanupCamera ( ) ;
}
} ) ;
await App . addListener ( 'resume' , ( ) => {
logger . log ( 'App resumed' ) ;
if ( this . cameraActive ) {
this . initializeCamera ( ) ;
}
} ) ;
/ / L o a d i n i t i a l d a t a
await this . loadInitialData ( ) ;
logger . log ( 'ContactQRScanShow initialization complete' ) ;
} catch ( error ) {
logger . error ( 'Failed to initialize ContactQRScanShow:' , error ) ;
this . showError ( 'Failed to initialize. Please try again.' ) ;
}
}
}
}
async initializeCamera ( retryCount = 0 ) : Promise < void > {
private async loadInitialData ( ) {
try {
try {
const capabilities = this . platformService . getCapabilities ( ) ;
/ / L o a d s e t t i n g s f r o m D B
if ( ! capabilities . hasCamera ) {
await db . open ( ) ;
this . danger ( "No camera available on this device." , "Camera Error" ) ;
const settings = await db . settings . get ( MASTER_SETTINGS_KEY ) ;
return ;
if ( settings ) {
this . hideRegisterPromptOnNewContact = settings . hideRegisterPromptOnNewContact || false ;
}
}
logger . log ( 'Initial data loaded successfully' ) ;
} catch ( error ) {
logger . error ( 'Failed to load initial data:' , error ) ;
throw error ;
}
}
/ / C h e c k c a m e r a p e r m i s s i o n s
private cleanupCamera ( ) {
const hasPermission = await this . checkCameraPermission ( ) ;
try {
if ( ! hasPermission ) {
this . cameraActive = false ;
this . danger (
this . isCapturingPhoto = false ;
"Camera permission is required to scan QR codes. Please enable camera access in your device settings." ,
this . addCameraState ( 'cleanup' ) ;
"Permission Required"
logger . log ( 'Camera cleaned up successfully' ) ;
) ;
} catch ( error ) {
return ;
logger . error ( 'Error during camera cleanup:' , error ) ;
}
}
private addCameraState ( state : CameraState | QRProcessingState , details ? : Record < string , unknown > ) {
const entry : CameraStateHistoryEntry = {
state ,
timestamp : Date . now ( ) ,
details
} ;
this . cameraStateHistory . push ( entry ) ;
if ( this . cameraStateHistory . length > this . STATE_HISTORY_LIMIT ) {
this . cameraStateHistory . shift ( ) ;
}
/ / E n h a n c e d l o g g i n g w i t h b e t t e r d e t a i l s
logger . log ( 'Camera state transition:' , {
state ,
details : {
... details ,
cameraActive : this . cameraActive ,
isCapturingPhoto : this . isCapturingPhoto ,
historyLength : this . cameraStateHistory . length
}
}
} ) ;
/ / I f w e g e t h e r e , c a m e r a s h o u l d b e a v a i l a b l e
this . lastCameraState = state ;
this . $notify (
}
{
group : "alert" ,
beforeDestroy ( ) {
type : "success" ,
logger . log ( 'ContactQRScanShow component being destroyed, initiating cleanup' , {
title : "Camera Ready" ,
cameraActive : this . cameraActive ,
text : "Camera is ready to scan QR codes." ,
lastState : this . lastCameraState ,
} ,
stateHistory : this . cameraStateHistory
3000
} ) ;
) ;
} catch ( error ) {
/ / R e m o v e a l l a p p l i f e c y c l e l i s t e n e r s
logger . error ( "Error initializing camera:" , error ) ;
const cleanupListeners = async ( ) => {
if ( this . appStateListener ) {
/ / R e t r y u p t o 3 t i m e s f o r c e r t a i n e r r o r s
try {
if ( retryCount < 3 ) {
await this . appStateListener . remove ( ) ;
const isPermissionError = error instanceof Error &&
logger . log ( 'App state change listener removed successfully' ) ;
( error . message . includes ( "permission" ) ||
} catch ( error ) {
error . message . includes ( "NotReadableError" ) ) ;
logger . error ( 'Error removing app state change listener:' , error ) ;
if ( isPermissionError ) {
/ / W a i t b e f o r e r e t r y i n g
await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
return this . initializeCamera ( retryCount + 1 ) ;
}
}
}
}
this . danger (
try {
"Failed to initialize camera. Please check your camera permissions and try again." ,
await App . removeAllListeners ( ) ;
"Camera Error"
logger . log ( 'All app listeners removed successfully' ) ;
) ;
} catch ( error ) {
}
logger . error ( 'Error removing all app listeners:' , error ) ;
}
} ;
/ / C l e a n u p e v e r y t h i n g
Promise . all ( [
cleanupListeners ( ) ,
this . cleanupCamera ( )
] ) . catch ( error => {
logger . error ( 'Error during component cleanup:' , error ) ;
} ) ;
}
}
async checkCameraPermission ( ) : Promise < boolean > {
private async handleQRCodeResult ( data : string ) : Promise < void > {
try {
try {
const capabilities = this . platformService . getCapabilities ( ) ;
await this . onScanDetect ( [ { rawValue : data } ] ) ;
if ( ! capabilities . hasCamera ) {
return false ;
}
/ / T r y t o a c c e s s c a m e r a t o c h e c k p e r m i s s i o n s
await this . platformService . takePicture ( ) ;
return true ;
} catch ( error ) {
} catch ( error ) {
logg er . error ( "Camera permission check failed:" , error ) ;
console . error ( 'Failed to handle QR code result:' , error ) ;
return false ;
this . showError ( 'Failed to process QR code data.' ) ;
}
}
}
}
async openMobileCamera ( ) : Promise < void > {
async initializeCamera ( retryCount = 0 ) : Promise < void > {
if ( this . cameraActive ) {
console . log ( 'Camera already active, skipping initialization' ) ;
return ;
}
try {
try {
/ / C h e c k p e r m i s s i o n s f i r s t
console . log ( 'Initializing camera...' , { retryCount } ) ;
const hasPermission = await this . checkCameraPermission ( ) ;
if ( ! hasPermission ) {
/ / C h e c k c a m e r a p e r m i s s i o n s f i r s t
this . danger (
const permissionStatus = await this . checkCameraPermission ( ) ;
"Camera permission is required. Please enable camera access in your device settings." ,
if ( permissionStatus . camera !== 'granted' ) {
"Permission Required"
throw new Error ( 'Camera permission not granted' ) ;
) ;
return ;
}
}
const image = await Camera . getPhoto ( {
this . cameraActive = true ;
/ / C o n f i g u r e c a m e r a o p t i o n s
const cameraOptions : ImageOptions = {
quality : 90 ,
quality : 90 ,
allowEditing : false ,
allowEditing : false ,
resultType : CameraResultType . DataUrl ,
resultType : CameraResultType . DataUrl ,
source : CameraSource . Camera ,
source : CameraSource . Camera
} ) ;
} ;
if ( image . dataUrl ) {
console . log ( 'Opening camera with options:' , cameraOptions ) ;
await this . processImageForQRCode ( image . dataUrl ) ;
const image = await Camera . getPhoto ( cameraOptions ) ;
if ( ! image || ! image . dataUrl ) {
throw new Error ( 'Failed to capture photo: No image data received' ) ;
}
}
console . log ( 'Photo captured successfully, processing image...' ) ;
await this . processImageForQRCode ( image . dataUrl ) ;
} catch ( error ) {
} catch ( error ) {
logger . error ( "Error taking picture:" , error ) ;
this . cameraActive = false ;
this . danger (
console . error ( 'Camera initialization failed:' , error instanceof Error ? error . message : String ( error ) ) ;
"Failed to access camera. Please check your camera permissions." ,
"Camera Error"
/ / H a n d l e u s e r c a n c e l l a t i o n s e p a r a t e l y
) ;
if ( error instanceof Error && error . message . includes ( 'User cancelled photos app' ) ) {
console . log ( 'User cancelled photo capture' ) ;
return ;
}
/ / H a n d l e r e t r y l o g i c
if ( retryCount < 2 ) {
console . log ( 'Retrying camera initialization...' ) ;
await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
return this . initializeCamera ( retryCount + 1 ) ;
}
this . showError ( 'Failed to initialize camera. Please try again.' ) ;
}
}
}
}
async processImageForQRCode ( _imageDataUrl : string ) {
async checkCameraPermission ( ) : Promise < { camera : CameraPermissionState } > {
try {
try {
/ / H e r e y o u w o u l d i m p l e m e n t Q R c o d e s c a n n i n g f r o m t h e i m a g e
this . addCameraState ( 'checking_permissions' ) ;
/ / F o r e x a m p l e , u s i n g j s Q R :
const capabilities = this . platformService . getCapabilities ( ) ;
/ / c o n s t i m a g e = n e w I m a g e ( ) ;
if ( ! capabilities . hasCamera ) {
/ / i m a g e . s r c = i m a g e D a t a U r l ;
this . addCameraState ( 'no_camera_capability' ) ;
/ / i m a g e . o n l o a d = ( ) = > {
return { camera : 'denied' as CameraPermissionState } ;
/ / c o n s t c a n v a s = d o c u m e n t . c r e a t e E l e m e n t ( ' c a n v a s ' ) ;
}
/ / c o n s t c o n t e x t = c a n v a s . g e t C o n t e x t ( ' 2 d ' ) ;
/ / c a n v a s . w i d t h = i m a g e . w i d t h ;
const permissionStatus = await Camera . checkPermissions ( ) ;
/ / c a n v a s . h e i g h t = i m a g e . h e i g h t ;
this . addCameraState ( 'permission_status_checked' ) ;
/ / c o n t e x t . d r a w I m a g e ( i m a g e , 0 , 0 ) ;
/ / c o n s t i m a g e D a t a = c o n t e x t . g e t I m a g e D a t a ( 0 , 0 , c a n v a s . w i d t h , c a n v a s . h e i g h t ) ;
if ( permissionStatus . camera === 'prompt' ) {
/ / c o n s t c o d e = j s Q R ( i m a g e D a t a . d a t a , i m a g e D a t a . w i d t h , i m a g e D a t a . h e i g h t ) ;
this . addCameraState ( 'requesting_permission' ) ;
/ / i f ( c o d e ) {
const requestResult = await Camera . requestPermissions ( ) ;
/ / t h i s . o n S c a n D e t e c t ( [ { r a w V a l u e : c o d e . d a t a } ] ) ;
this . addCameraState ( 'permission_requested' ) ;
/ / }
return requestResult ;
/ / } ;
}
return permissionStatus ;
} catch ( error ) {
} catch ( error ) {
logger . error ( "Error processing image for QR code:" , error ) ;
this . addCameraState ( 'permission_check_error' ) ;
this . danger (
return { camera : 'denied' as CameraPermissionState } ;
"Failed to process the image. Please try again." ,
}
"Processing Error" ,
}
) ;
async processImageForQRCode ( imageDataUrl : string ) : Promise < void > {
try {
logger . log ( 'Starting QR code processing' ) ;
/ / C r e a t e w o r k e r f o r i m a g e p r o c e s s i n g
const worker = new Worker ( URL . createObjectURL ( new Blob ( [ `
self . onmessage = async function ( e ) {
const { imageData , width , height } = e . data ;
try {
/ / I m p o r t j s Q R i n t h e w o r k e r
importScripts ( '${window.location.origin}/assets/jsqr.js' ) ;
const code = self . jsQR ( imageData , width , height , {
inversionAttempts : "dontInvert"
} ) ;
self . postMessage ( { success : true , code } ) ;
} catch ( error ) {
self . postMessage ( { success : false , error : error . message } ) ;
}
} ;
` ], { type: 'text/javascript' })));
const image = new Image ( ) ;
image . crossOrigin = 'anonymous' ;
image . src = imageDataUrl ;
await new Promise ( ( resolve , reject ) => {
const timeout = setTimeout ( ( ) => reject ( new Error ( 'Image load timeout' ) ) , 5000 ) ;
image . onload = ( ) => {
clearTimeout ( timeout ) ;
resolve ( undefined ) ;
} ;
image . onerror = ( ) => {
clearTimeout ( timeout ) ;
reject ( new Error ( 'Failed to load image' ) ) ;
} ;
} ) ;
logger . log ( 'Image loaded, creating canvas...' ) ;
const canvas = document . createElement ( 'canvas' ) ;
const maxDimension = 1024 ; / / L i m i t i m a g e s i z e f o r b e t t e r p e r f o r m a n c e
/ / S c a l e d o w n i m a g e i f n e e d e d w h i l e m a i n t a i n i n g a s p e c t r a t i o
let width = image . naturalWidth || 800 ;
let height = image . naturalHeight || 600 ;
if ( width > maxDimension || height > maxDimension ) {
if ( width > height ) {
height = Math . floor ( height * ( maxDimension / width ) ) ;
width = maxDimension ;
} else {
width = Math . floor ( width * ( maxDimension / height ) ) ;
height = maxDimension ;
}
}
canvas . width = width ;
canvas . height = height ;
const ctx = canvas . getContext ( '2d' ) ;
if ( ! ctx ) {
throw new Error ( 'Failed to get canvas context' ) ;
}
/ / D r a w i m a g e m a i n t a i n i n g o r i e n t a t i o n
ctx . save ( ) ;
ctx . drawImage ( image , 0 , 0 , width , height ) ;
ctx . restore ( ) ;
const imageData = ctx . getImageData ( 0 , 0 , width , height ) ;
logger . log ( 'Processing image data for QR code...' , {
width ,
height ,
dataLength : imageData . data . length
} ) ;
/ / P r o c e s s Q R c o d e i n w o r k e r
const result = await new Promise < QRCodeResult > ( ( resolve , reject ) => {
worker . onmessage = ( e : MessageEvent < WorkerMessage > ) => {
if ( e . data . success ) {
resolve ( e . data . code ) ;
} else {
reject ( new Error ( e . data . error ) ) ;
}
} ;
worker . onerror = reject ;
worker . postMessage ( {
imageData : imageData . data ,
width : imageData . width ,
height : imageData . height
} ) ;
} ) ;
worker . terminate ( ) ;
if ( result ) {
logger . log ( 'QR code found:' , { data : result . data } ) ;
await this . handleQRCodeResult ( result . data ) ;
} else {
logger . log ( 'No QR code found in image' ) ;
this . showError ( 'No QR code found. Please try again.' ) ;
}
} catch ( error ) {
logger . error ( 'QR code processing failed:' , error ) ;
this . showError ( 'Failed to process QR code. Please try again.' ) ;
} finally {
this . cameraActive = false ;
this . isCapturingPhoto = false ;
this . addCameraState ( 'processing_completed' ) ;
}
}
}
}
@ -347,11 +601,11 @@ export default class ContactQRScanShow extends Vue {
return ;
return ;
}
}
let newContact : Contact ;
let newContact : Contact ;
try {
try {
/ / E x t r a c t J W T f r o m U R L
/ / E x t r a c t J W T f r o m U R L
const jwt = getContactJwtFromJwtUrl ( url ) ;
const jwt = getContactJwtFromJwtUrl ( url ) ;
if ( ! jwt ) {
if ( ! jwt ) {
this . danger (
this . danger (
"Could not extract contact information from the QR code. Please try again." ,
"Could not extract contact information from the QR code. Please try again." ,
"Invalid QR Code" ,
"Invalid QR Code" ,
@ -366,11 +620,11 @@ export default class ContactQRScanShow extends Vue {
this . danger (
this . danger (
"The QR code contains invalid data. Please scan a valid TimeSafari contact QR code." ,
"The QR code contains invalid data. Please scan a valid TimeSafari contact QR code." ,
"Invalid Data" ,
"Invalid Data" ,
) ;
) ;
return ;
return ;
}
}
const { payload } = decodeEndorserJwt ( jwt ) ;
const { payload } = decodeEndorserJwt ( jwt ) ;
if ( ! payload ) {
if ( ! payload ) {
this . danger (
this . danger (
"Could not decode the contact information. Please try again." ,
"Could not decode the contact information. Please try again." ,
@ -388,7 +642,7 @@ export default class ContactQRScanShow extends Vue {
return ;
return ;
}
}
newContact = {
newContact = {
did : payload . own ? . did || payload . iss ,
did : payload . own ? . did || payload . iss ,
name : payload . own ? . name ,
name : payload . own ? . name ,
nextPubKeyHashB64 : payload . own ? . nextPublicEncKeyHash ,
nextPubKeyHashB64 : payload . own ? . nextPublicEncKeyHash ,
@ -397,15 +651,15 @@ export default class ContactQRScanShow extends Vue {
registered : payload . own ? . registered ,
registered : payload . own ? . registered ,
} ;
} ;
if ( ! newContact . did ) {
if ( ! newContact . did ) {
this . danger (
this . danger (
"Missing contact identifier. Please scan a valid TimeSafari contact QR code." ,
"Missing contact identifier. Please scan a valid TimeSafari contact QR code." ,
"Incomplete Contact" ,
"Incomplete Contact" ,
) ;
) ;
return ;
return ;
}
}
if ( ! isDid ( newContact . did ) ) {
if ( ! isDid ( newContact . did ) ) {
this . danger (
this . danger (
"Invalid contact identifier format. The identifier must begin with 'did:'." ,
"Invalid contact identifier format. The identifier must begin with 'did:'." ,
"Invalid Identifier" ,
"Invalid Identifier" ,
@ -413,66 +667,66 @@ export default class ContactQRScanShow extends Vue {
return ;
return ;
}
}
await db . open ( ) ;
await db . open ( ) ;
await db . contacts . add ( newContact ) ;
await db . contacts . add ( newContact ) ;
let addedMessage ;
let addedMessage ;
if ( this . activeDid ) {
if ( this . activeDid ) {
await this . setVisibility ( newContact , true ) ;
await this . setVisibility ( newContact , true ) ;
newContact . seesMe = true ;
newContact . seesMe = true ;
addedMessage = "They were added, and your activity is visible to them." ;
addedMessage = "They were added, and your activity is visible to them." ;
} else {
} else {
addedMessage = "They were added." ;
addedMessage = "They were added." ;
}
}
this . $notify (
this . $notify (
{
{
group : "alert" ,
group : "alert" ,
type : "success" ,
type : "success" ,
title : "Contact Added" ,
title : "Contact Added" ,
text : addedMessage ,
text : addedMessage ,
} ,
} ,
3000 ,
3000 ,
) ;
) ;
if (
if (
this . isRegistered &&
this . isRegistered &&
! this . hideRegisterPromptOnNewContact &&
! this . hideRegisterPromptOnNewContact &&
! newContact . registered
! newContact . registered
) {
) {
setTimeout ( ( ) => {
setTimeout ( ( ) => {
this . $notify (
this . $notify (
{
{
group : "modal" ,
group : "modal" ,
type : "confirm" ,
type : "confirm" ,
title : "Register" ,
title : "Register" ,
text : "Do you want to register them?" ,
text : "Do you want to register them?" ,
onCancel : async ( stopAsking ? : boolean ) => {
onCancel : async ( stopAsking ? : boolean ) => {
if ( stopAsking ) {
if ( stopAsking ) {
await db . settings . update ( MASTER_SETTINGS_KEY , {
await db . settings . update ( MASTER_SETTINGS_KEY , {
hideRegisterPromptOnNewContact : stopAsking ,
hideRegisterPromptOnNewContact : stopAsking ,
} ) ;
} ) ;
this . hideRegisterPromptOnNewContact = stopAsking ;
this . hideRegisterPromptOnNewContact = stopAsking ;
}
}
} ,
} ,
onNo : async ( stopAsking ? : boolean ) => {
onNo : async ( stopAsking ? : boolean ) => {
if ( stopAsking ) {
if ( stopAsking ) {
await db . settings . update ( MASTER_SETTINGS_KEY , {
await db . settings . update ( MASTER_SETTINGS_KEY , {
hideRegisterPromptOnNewContact : stopAsking ,
hideRegisterPromptOnNewContact : stopAsking ,
} ) ;
} ) ;
this . hideRegisterPromptOnNewContact = stopAsking ;
this . hideRegisterPromptOnNewContact = stopAsking ;
}
}
} ,
} ,
onYes : async ( ) => {
onYes : async ( ) => {
await this . register ( newContact ) ;
await this . register ( newContact ) ;
} ,
} ,
promptToStopAsking : true ,
promptToStopAsking : true ,
} ,
} ,
- 1 ,
- 1 ,
) ;
) ;
} , 500 ) ;
} , 500 ) ;
}
}
} catch ( e ) {
} catch ( e ) {
logger . error ( "Error processing QR code:" , e ) ;
logger . error ( "Error processing QR code:" , e ) ;
this . danger (
this . danger (
"Could not process the QR code. Please make sure you're scanning a valid TimeSafari contact QR code." ,
"Could not process the QR code. Please make sure you're scanning a valid TimeSafari contact QR code." ,
@ -589,15 +843,15 @@ export default class ContactQRScanShow extends Vue {
async onCopyUrlToClipboard ( ) : Promise < void > {
async onCopyUrlToClipboard ( ) : Promise < void > {
try {
try {
await this . platformService . writeToClipboard ( this . qrValue ) ;
await this . platformService . writeToClipboard ( this . qrValue ) ;
this . $notify (
this . $notify (
{
{
group : "alert" ,
group : "alert" ,
type : "toast" ,
type : "toast" ,
title : "Copied" ,
title : "Copied" ,
text : "Contact URL was copied to clipboard." ,
text : "Contact URL was copied to clipboard." ,
} ,
} ,
2000 ,
2000 ,
) ;
) ;
} catch ( error ) {
} catch ( error ) {
logger . error ( "Error copying to clipboard:" , error ) ;
logger . error ( "Error copying to clipboard:" , error ) ;
this . danger ( "Failed to copy to clipboard" , "Error" ) ;
this . danger ( "Failed to copy to clipboard" , "Error" ) ;
@ -607,15 +861,15 @@ export default class ContactQRScanShow extends Vue {
async onCopyDidToClipboard ( ) : Promise < void > {
async onCopyDidToClipboard ( ) : Promise < void > {
try {
try {
await this . platformService . writeToClipboard ( this . activeDid ) ;
await this . platformService . writeToClipboard ( this . activeDid ) ;
this . $notify (
this . $notify (
{
{
group : "alert" ,
group : "alert" ,
type : "info" ,
type : "info" ,
title : "Copied" ,
title : "Copied" ,
text : "Your DID was copied to the clipboard. Have them paste it in the box on their 'People' screen to add you." ,
text : "Your DID was copied to the clipboard. Have them paste it in the box on their 'People' screen to add you." ,
} ,
} ,
5000 ,
5000 ,
) ;
) ;
} catch ( error ) {
} catch ( error ) {
logger . error ( "Error copying to clipboard:" , error ) ;
logger . error ( "Error copying to clipboard:" , error ) ;
this . danger ( "Failed to copy to clipboard" , "Error" ) ;
this . danger ( "Failed to copy to clipboard" , "Error" ) ;
@ -712,5 +966,96 @@ export default class ContactQRScanShow extends Vue {
get isMobile ( ) : boolean {
get isMobile ( ) : boolean {
return __IS_MOBILE__ ;
return __IS_MOBILE__ ;
}
}
private showError ( message : string ) {
/ / I m p l e m e n t t h e l o g i c t o s h o w a u s e r - f r i e n d l y e r r o r m e s s a g e t o t h e u s e r
console . error ( message ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : message ,
} ,
5000 ,
) ;
}
async openMobileCamera ( ) {
if ( this . isCapturingPhoto ) {
this . addCameraState ( 'capture_already_in_progress' ) ;
logger . warn ( 'Camera capture already in progress, ignoring request' ) ;
return ;
}
try {
this . isCapturingPhoto = true ;
this . addCameraState ( 'opening_camera' ) ;
const config = {
quality : 90 ,
allowEditing : false ,
resultType : CameraResultType . DataUrl ,
source : CameraSource . Camera
} ;
logger . log ( 'Camera configuration:' , config ) ;
const image = await Camera . getPhoto ( config ) ;
this . addCameraState ( 'photo_captured' , {
hasDataUrl : ! ! image . dataUrl ,
dataUrlLength : image . dataUrl ? . length ,
format : image . format ,
saved : image . saved ,
webPath : image . webPath ,
base64String : image . dataUrl ? . substring ( 0 , 50 ) + '...' / / L o g f i r s t 5 0 c h a r s o f b a s e 6 4
} ) ;
if ( image . dataUrl ) {
this . addCameraState ( 'processing_photo' ) ;
await this . processImageForQRCode ( image . dataUrl ) ;
} else {
this . addCameraState ( 'no_image_data' ) ;
logger . error ( 'Camera returned no image data' ) ;
this . $notify ( {
type : 'error' ,
title : 'Camera Error' ,
text : 'No image was captured. Please try again.' ,
group : 'qr-scanner'
} ) ;
}
} catch ( error ) {
this . addCameraState ( 'capture_error' , {
error ,
errorName : error instanceof Error ? error . name : 'Unknown' ,
errorMessage : error instanceof Error ? error . message : String ( error ) ,
errorStack : error instanceof Error ? error . stack : undefined
} ) ;
if ( error instanceof Error ) {
if ( error . message . includes ( 'User cancelled photos app' ) ) {
logger . log ( 'User cancelled photo capture' ) ;
this . addCameraState ( 'user_cancelled' ) ;
} else if ( error . message . includes ( 'permission' ) ) {
logger . error ( 'Camera permission error during capture' ) ;
this . addCameraState ( 'permission_error' ) ;
} else if ( error . message . includes ( 'Camera is not available' ) ) {
logger . error ( 'Camera hardware not available' ) ;
this . addCameraState ( 'hardware_unavailable' ) ;
}
}
this . $notify ( {
type : 'error' ,
title : 'Camera Error' ,
text : 'Failed to capture photo. Please check camera permissions and try again.' ,
group : 'qr-scanner'
} ) ;
} finally {
this . isCapturingPhoto = false ;
this . addCameraState ( 'capture_completed' ) ;
}
}
}
}
< / script >
< / script >