@ -228,11 +228,18 @@
>
>
Cancel
Cancel
< / button >
< / button >
< button
class = "px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600"
@ click = "copyLogs"
>
Copy Debug Logs
< / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< div v-if ="errorMessage" class="error-message" > {{ errorMessage }} < / div >
< / template >
< / template >
< script lang = "ts" >
< script lang = "ts" >
@ -241,11 +248,13 @@ import { QrcodeStream } from "vue-qrcode-reader";
import { QRScannerOptions } from "@/services/QRScanner/types" ;
import { QRScannerOptions } from "@/services/QRScanner/types" ;
import { logger } from "@/utils/logger" ;
import { logger } from "@/utils/logger" ;
import { Capacitor } from "@capacitor/core" ;
import { Capacitor } from "@capacitor/core" ;
import { logCollector } from "@/utils/LogCollector" ;
interface ScanProps {
interface ScanProps {
onScan : ( result : string ) => void ;
onScan : ( result : string ) => void ;
onError ? : ( error : Error ) => void ;
onError ? : ( error : Error ) => void ;
options ? : QRScannerOptions ;
options ? : QRScannerOptions ;
onClose ? : ( ) => void ;
}
}
interface DetectionResult {
interface DetectionResult {
@ -267,6 +276,7 @@ export default class QRScannerDialog extends Vue {
@ Prop ( { type : Function , required : true } ) onScan ! : ScanProps [ "onScan" ] ;
@ Prop ( { type : Function , required : true } ) onScan ! : ScanProps [ "onScan" ] ;
@ Prop ( { type : Function } ) onError ? : ScanProps [ "onError" ] ;
@ Prop ( { type : Function } ) onError ? : ScanProps [ "onError" ] ;
@ Prop ( { type : Object } ) options ? : ScanProps [ "options" ] ;
@ Prop ( { type : Object } ) options ? : ScanProps [ "options" ] ;
@ Prop ( { type : Function } ) onClose ? : ScanProps [ "onClose" ] ;
/ / V e r s i o n
/ / V e r s i o n
readonly version = "1.1.0" ;
readonly version = "1.1.0" ;
@ -285,8 +295,28 @@ export default class QRScannerDialog extends Vue {
preferredCamera : "user" | "environment" = "environment" ;
preferredCamera : "user" | "environment" = "environment" ;
initializationStatus = "Checking camera access..." ;
initializationStatus = "Checking camera access..." ;
cameraStatus = "Initializing" ;
cameraStatus = "Initializing" ;
errorMessage = "" ;
created ( ) {
created ( ) {
console . log ( '[QRScannerDialog] created' ) ;
console . log ( '[QRScannerDialog] Props received:' , {
onScan : typeof this . onScan ,
onError : typeof this . onError ,
options : this . options ,
onClose : typeof this . onClose ,
} ) ;
console . log ( '[QRScannerDialog] Initial state:' , {
visible : this . visible ,
error : this . error ,
useQRReader : this . useQRReader ,
isNativePlatform : this . isNativePlatform ,
isInitializing : this . isInitializing ,
isScanning : this . isScanning ,
preferredCamera : this . preferredCamera ,
initializationStatus : this . initializationStatus ,
cameraStatus : this . cameraStatus ,
errorMessage : this . errorMessage ,
} ) ;
logger . log ( "QRScannerDialog platform detection:" , {
logger . log ( "QRScannerDialog platform detection:" , {
capacitorNative : Capacitor . isNativePlatform ( ) ,
capacitorNative : Capacitor . isNativePlatform ( ) ,
isMobile : __IS_MOBILE__ ,
isMobile : __IS_MOBILE__ ,
@ -299,60 +329,68 @@ export default class QRScannerDialog extends Vue {
navigator . mediaDevices && navigator . mediaDevices . getUserMedia
navigator . mediaDevices && navigator . mediaDevices . getUserMedia
) ,
) ,
} ) ;
} ) ;
/ / I f o n n a t i v e p l a t f o r m , c l o s e i m m e d i a t e l y a n d d o n ' t i n i t i a l i z e w e b s c a n n e r
if ( this . isNativePlatform ) {
if ( this . isNativePlatform ) {
logger . log ( "Closing QR dialog on native platform" ) ;
logger . log ( "Closing QR dialog on native platform" ) ;
this . $nextTick ( ( ) => this . close ( ) ) ;
this . $nextTick ( ( ) => this . close ( ) ) ;
}
}
}
}
mounted ( ) {
console . log ( '[QRScannerDialog] mounted' ) ;
/ / T i m e r t o w a r n i f n o Q R c o d e d e t e c t e d a f t e r 1 0 s e c o n d s
this . _scanTimeout = setTimeout ( ( ) => {
if ( ! this . isScanning ) {
console . warn ( '[QRScannerDialog] No QR code detected after 10 seconds' ) ;
}
} , 10000 ) ;
/ / P e r i o d i c t i m e r t o l o g w a i t i n g s t a t u s e v e r y 5 s e c o n d s
this . _waitingInterval = setInterval ( ( ) => {
if ( ! this . isScanning && this . cameraStatus === 'Active' ) {
console . log ( '[QRScannerDialog] Still waiting for QR code detection...' ) ;
}
} , 5000 ) ;
console . log ( '[QRScannerDialog] Waiting interval started' ) ;
}
beforeUnmount ( ) {
if ( this . _scanTimeout ) {
clearTimeout ( this . _scanTimeout ) ;
console . log ( '[QRScannerDialog] Scan timeout cleared' ) ;
}
if ( this . _waitingInterval ) {
clearInterval ( this . _waitingInterval ) ;
console . log ( '[QRScannerDialog] Waiting interval cleared' ) ;
}
console . log ( '[QRScannerDialog] beforeUnmount' ) ;
}
async onInit ( promise : Promise < void > ) : Promise < void > {
async onInit ( promise : Promise < void > ) : Promise < void > {
console . log ( '[QRScannerDialog] onInit called' ) ;
if ( this . isNativePlatform ) {
if ( this . isNativePlatform ) {
logger . log ( "Closing QR dialog on native platform" ) ;
logger . log ( "Closing QR dialog on native platform" ) ;
this . $nextTick ( ( ) => this . close ( ) ) ;
this . $nextTick ( ( ) => this . close ( ) ) ;
return ;
return ;
}
}
this . isInitializing = true ;
this . isInitializing = true ;
console . log ( '[QRScannerDialog] isInitializing set to' , this . isInitializing ) ;
this . error = null ;
this . error = null ;
this . initializationStatus = "Checking camera access..." ;
this . initializationStatus = "Checking camera access..." ;
console . log ( '[QRScannerDialog] initializationStatus set to' , this . initializationStatus ) ;
try {
try {
/ / F i r s t c h e c k i f m e d i a D e v i c e s A P I i s a v a i l a b l e
if ( ! navigator . mediaDevices ) {
if ( ! navigator . mediaDevices ) {
console . log ( '[QRScannerDialog] Camera API not available' ) ;
throw new Error (
throw new Error (
"Camera API not available. Please ensure you're using HTTPS." ,
"Camera API not available. Please ensure you're using HTTPS." ,
) ;
) ;
}
}
/ / C h e c k f o r v i d e o d e v i c e s
const devices = await navigator . mediaDevices . enumerateDevices ( ) ;
const devices = await navigator . mediaDevices . enumerateDevices ( ) ;
const videoDevices = devices . filter (
const videoDevices = devices . filter ( ( device ) => device . kind === "videoinput" ) ;
( device ) => device . kind === "videoinput" ,
console . log ( '[QRScannerDialog] videoDevices found:' , videoDevices . length ) ;
) ;
if ( videoDevices . length === 0 ) {
if ( videoDevices . length === 0 ) {
throw new Error ( "No camera found on this device" ) ;
throw new Error ( "No camera found on this device" ) ;
}
}
logger . log ( "Starting QR scanner initialization..." , {
mediaDevices : ! ! navigator . mediaDevices ,
getUserMedia : ! ! (
navigator . mediaDevices && navigator . mediaDevices . getUserMedia
) ,
videoDevices : videoDevices . length ,
constraints : {
video : {
facingMode : this . preferredCamera ,
width : { ideal : 1280 } ,
height : { ideal : 720 } ,
} ,
} ,
} ) ;
/ / E x p l i c i t l y r e q u e s t c a m e r a p e r m i s s i o n f i r s t
this . initializationStatus = "Requesting camera permission..." ;
this . initializationStatus = "Requesting camera permission..." ;
console . log ( '[QRScannerDialog] initializationStatus set to' , this . initializationStatus ) ;
try {
try {
const stream = await navigator . mediaDevices . getUserMedia ( {
const stream = await navigator . mediaDevices . getUserMedia ( {
video : {
video : {
@ -361,19 +399,12 @@ export default class QRScannerDialog extends Vue {
height : { ideal : 720 } ,
height : { ideal : 720 } ,
} ,
} ,
} ) ;
} ) ;
/ / S t o p t h e t e s t s t r e a m i m m e d i a t e l y
stream . getTracks ( ) . forEach ( ( track ) => track . stop ( ) ) ;
stream . getTracks ( ) . forEach ( ( track ) => track . stop ( ) ) ;
this . initializationStatus = "Camera permission granted..." ;
this . initializationStatus = "Camera permission granted..." ;
logg er . log ( "Camera permission granted" ) ;
conso le. log ( '[QRScannerDialog] initializationStatus set to' , this . initializationStatus ) ;
} catch ( permissionError ) {
} catch ( permissionError ) {
const error = permissionError as Error ;
const error = permissionError as Error ;
logger . error ( "Camera permission error:" , {
console . log ( '[QRScannerDialog] Camera permission error:' , error . name , error . message ) ;
name : error . name ,
message : error . message ,
} ) ;
if (
if (
error . name === "NotAllowedError" ||
error . name === "NotAllowedError" ||
error . name === "PermissionDeniedError"
error . name === "PermissionDeniedError"
@ -397,62 +428,79 @@ export default class QRScannerDialog extends Vue {
throw new Error ( ` Camera error: ${ error . message } ` ) ;
throw new Error ( ` Camera error: ${ error . message } ` ) ;
}
}
}
}
/ / N o w i n i t i a l i z e t h e Q R s c a n n e r
this . initializationStatus = "Starting QR scanner..." ;
this . initializationStatus = "Starting QR scanner..." ;
logg er . log ( "Initializing QR scanner..." ) ;
console . log ( '[QRScannerDialog] initializationStatus set to' , this . initializationStatus ) ;
await promise ;
await promise ;
this . isInitializing = false ;
this . isInitializing = false ;
this . cameraStatus = "Ready" ;
this . cameraStatus = "Ready" ;
logger . log ( "QR scanner initialized successfully" ) ;
console . log ( '[QRScannerDialog] QR scanner initialized successfully' ) ;
console . log ( '[QRScannerDialog] cameraStatus set to' , this . cameraStatus ) ;
} catch ( error ) {
} catch ( error ) {
const wrappedError =
const wrappedError = error instanceof Error ? error : new Error ( String ( error ) ) ;
error instanceof Error ? error : new Error ( String ( error ) ) ;
this . error = wrappedError . message ;
this . error = wrappedError . message ;
this . cameraStatus = "Error" ;
this . cameraStatus = "Error" ;
console . log ( '[QRScannerDialog] Error initializing QR scanner:' , wrappedError . message ) ;
console . log ( '[QRScannerDialog] cameraStatus set to' , this . cameraStatus ) ;
if ( this . onError ) {
if ( this . onError ) {
this . onError ( wrappedError ) ;
this . onError ( wrappedError ) ;
}
}
logger . error ( "Error initializing QR scanner:" , {
error : wrappedError . message ,
stack : wrappedError . stack ,
name : wrappedError . name ,
type : wrappedError . constructor . name ,
} ) ;
} finally {
} finally {
this . isInitializing = false ;
this . isInitializing = false ;
console . log ( '[QRScannerDialog] isInitializing set to' , this . isInitializing ) ;
}
}
}
}
onCameraOn ( ) : void {
onCameraOn ( ) : void {
this . cameraStatus = "Active" ;
this . cameraStatus = "Active" ;
logger . log ( "Camera turned on successfully" ) ;
console . log ( '[QRScannerDialog] Camera turned on successfully' ) ;
console . log ( '[QRScannerDialog] cameraStatus set to' , this . cameraStatus ) ;
}
}
onCameraOff ( ) : void {
onCameraOff ( ) : void {
this . cameraStatus = "Off" ;
this . cameraStatus = "Off" ;
logger . log ( "Camera turned off" ) ;
console . log ( '[QRScannerDialog] Camera turned off' ) ;
console . log ( '[QRScannerDialog] cameraStatus set to' , this . cameraStatus ) ;
}
}
onDetect ( result : DetectionResult | Promise < DetectionResult > ) : void {
onDetect ( result : DetectionResult | Promise < DetectionResult > ) : void {
const ts = new Date ( ) . toISOString ( ) ;
console . log ( ` [QRScannerDialog] onDetect called at ${ ts } with ` , result ) ;
this . isScanning = true ;
this . isScanning = true ;
this . cameraStatus = "Detecting" ;
this . cameraStatus = "Detecting" ;
logger . log ( "QR code detected, processing..." ) ;
console . log ( '[QRScannerDialog] isScanning set to' , this . isScanning ) ;
console . log ( '[QRScannerDialog] cameraStatus set to' , this . cameraStatus ) ;
/ / H a n d l e b o t h p r o m i s e a n d d i r e c t v a l u e c a s e s
const processResult = ( detection : DetectionResult | DetectionResult [ ] ) => {
const processResult = ( detection : DetectionResult ) => {
try {
console . log ( ` [QRScannerDialog] onDetect exit at ${ new Date ( ) . toISOString ( ) } with detection: ` , detection ) ;
/ / F a l l b a c k : I f d e t e c t i o n i s a n a r r a y , c h e c k t h e f i r s t e l e m e n t
let rawValue : string | undefined ;
if ( Array . isArray ( detection ) && detection . length > 0 && 'rawValue' in detection [ 0 ] ) {
rawValue = detection [ 0 ] . rawValue ;
} else if ( detection && typeof detection === 'object' && 'rawValue' in detection && detection . rawValue ) {
rawValue = ( detection as any ) . rawValue ;
}
if ( rawValue ) {
console . log ( '[QRScannerDialog] Fallback: Detected rawValue, treating as scan:' , rawValue ) ;
this . isInitializing = false ;
this . initializationStatus = 'QR code captured!' ;
this . onScan ( rawValue ) ;
try {
try {
logger . log ( "QR code processed successfully:" , detection ) ;
console . log ( '[QRScannerDialog] About to call close() after scan' ) ;
this . close ( ) ;
console . log ( '[QRScannerDialog] close() called successfully after scan' ) ;
} catch ( err ) {
console . error ( '[QRScannerDialog] Error calling close():' , err ) ;
}
}
} catch ( error ) {
} catch ( error ) {
this . handleError ( error ) ;
this . handleError ( error ) ;
} finally {
} finally {
this . isScanning = false ;
this . isScanning = false ;
this . cameraStatus = "Active" ;
this . cameraStatus = "Active" ;
console . log ( '[QRScannerDialog] isScanning set to' , this . isScanning ) ;
console . log ( '[QRScannerDialog] cameraStatus set to' , this . cameraStatus ) ;
}
}
} ;
} ;
/ / U s e i n s t a n c e o f P r o m i s e f o r t y p e n a r r o w i n g
if ( result instanceof Promise ) {
if ( result instanceof Promise ) {
result
result
. then ( processResult )
. then ( processResult )
@ -460,6 +508,8 @@ export default class QRScannerDialog extends Vue {
. finally ( ( ) => {
. finally ( ( ) => {
this . isScanning = false ;
this . isScanning = false ;
this . cameraStatus = "Active" ;
this . cameraStatus = "Active" ;
console . log ( '[QRScannerDialog] isScanning set to' , this . isScanning ) ;
console . log ( '[QRScannerDialog] cameraStatus set to' , this . cameraStatus ) ;
} ) ;
} ) ;
} else {
} else {
processResult ( result ) ;
processResult ( result ) ;
@ -467,50 +517,104 @@ export default class QRScannerDialog extends Vue {
}
}
private handleError ( error : unknown ) : void {
private handleError ( error : unknown ) : void {
const wrappedError =
const wrappedError = error instanceof Error ? error : new Error ( String ( error ) ) ;
error instanceof Error ? error : new Error ( String ( error ) ) ;
this . error = wrappedError . message ;
this . error = wrappedError . message ;
this . cameraStatus = "Error" ;
this . cameraStatus = "Error" ;
console . log ( '[QRScannerDialog] handleError:' , wrappedError . message ) ;
console . log ( '[QRScannerDialog] cameraStatus set to' , this . cameraStatus ) ;
if ( this . onError ) {
if ( this . onError ) {
this . onError ( wrappedError ) ;
this . onError ( wrappedError ) ;
}
}
logger . error ( "QR scanner error:" , {
error : wrappedError . message ,
stack : wrappedError . stack ,
name : wrappedError . name ,
type : wrappedError . constructor . name ,
} ) ;
}
}
onDecode ( result : string ) : void {
onDecode ( result : string ) : void {
logger . log ( "QR code decoded:" , result ) ;
const ts = new Date ( ) . toISOString ( ) ;
console . log ( ` [QRScannerDialog] onDecode called at ${ ts } with result: ` , result ) ;
try {
try {
this . isInitializing = false ;
this . initializationStatus = 'QR code captured!' ;
console . log ( '[QRScannerDialog] UI state updated after scan: isInitializing set to' , this . isInitializing , ', initializationStatus set to' , this . initializationStatus ) ;
this . onScan ( result ) ;
this . onScan ( result ) ;
this . close ( ) ;
this . close ( ) ;
console . log ( ` [QRScannerDialog] onDecode exit at ${ new Date ( ) . toISOString ( ) } ` ) ;
} catch ( error ) {
} catch ( error ) {
this . handleError ( error ) ;
this . handleError ( error ) ;
}
}
}
}
toggleCamera ( ) : void {
toggleCamera ( ) : void {
this . preferredCamera =
const prevCamera = this . preferredCamera ;
this . preferredCamera === "user" ? "environment" : "user" ;
this . preferredCamera = this . preferredCamera === "user" ? "environment" : "user" ;
console . log ( '[QRScannerDialog] toggleCamera from' , prevCamera , 'to' , this . preferredCamera ) ;
console . log ( '[QRScannerDialog] preferredCamera set to' , this . preferredCamera ) ;
}
}
retryScanning ( ) : void {
retryScanning ( ) : void {
console . log ( '[QRScannerDialog] retryScanning called' ) ;
this . error = null ;
this . error = null ;
this . isInitializing = true ;
this . isInitializing = true ;
/ / T h e Q R s c a n n e r c o m p o n e n t w i l l a u t o m a t i c a l l y r e i n i t i a l i z e
console . log ( '[QRScannerDialog] isInitializing set to' , this . isInitializing ) ;
console . log ( '[QRScannerDialog] Scanning re-initialized' ) ;
}
}
async close ( ) : Promise < void > {
close = async ( ) : Promise < void > = > {
logg er . log ( "Closing QR scanner dialog" ) ;
conso le. log ( '[QRScannerDialog] close called' ) ;
this . visible = false ;
this . visible = false ;
console . log ( '[QRScannerDialog] visible set to' , this . visible ) ;
/ / N o t i f y p a r e n t / s e r v i c e
if ( typeof this . onClose === 'function' ) {
console . log ( '[QRScannerDialog] Calling onClose prop' ) ;
this . onClose ( ) ;
}
await this . $nextTick ( ) ;
await this . $nextTick ( ) ;
if ( this . $el && this . $el . parentNode ) {
if ( this . $el && this . $el . parentNode ) {
this . $el . parentNode . removeChild ( this . $el ) ;
this . $el . parentNode . removeChild ( this . $el ) ;
console . log ( '[QRScannerDialog] Dialog element removed from DOM' ) ;
} else {
console . log ( '[QRScannerDialog] Dialog element NOT removed from DOM' ) ;
}
}
onScanDetect ( promisedResult ) {
const ts = new Date ( ) . toISOString ( ) ;
console . log ( ` [QRScannerDialog] onScanDetect called at ${ ts } with ` , promisedResult ) ;
promisedResult
. then ( ( result ) => {
console . log ( ` [QRScannerDialog] onScanDetect exit at ${ new Date ( ) . toISOString ( ) } with result: ` , result ) ;
this . onScan ( result ) ;
} )
. catch ( ( error ) => {
console . error ( ` [QRScannerDialog] onScanDetect error at ${ new Date ( ) . toISOString ( ) } : ` , error ) ;
this . errorMessage = error . message || 'Scan error' ;
if ( this . onError ) this . onError ( error ) ;
} ) ;
}
onScanError ( error ) {
const ts = new Date ( ) . toISOString ( ) ;
console . error ( ` [QRScannerDialog] onScanError called at ${ ts } : ` , error ) ;
this . errorMessage = error . message || 'Camera error' ;
if ( this . onError ) this . onError ( error ) ;
}
async startMobileScan ( ) {
try {
console . log ( '[QRScannerDialog] startMobileScan called' ) ;
const scanner = QRScannerFactory . getInstance ( ) ;
await scanner . startScan ( ) ;
} catch ( error ) {
console . error ( '[QRScannerDialog] Error starting mobile scan:' , error ) ;
if ( this . onError ) this . onError ( error ) ;
}
}
async copyLogs ( ) {
console . log ( '[QRScannerDialog] copyLogs called' ) ;
try {
await navigator . clipboard . writeText ( logCollector . getLogs ( ) ) ;
alert ( 'Logs copied to clipboard!' ) ;
} catch ( e ) {
alert ( 'Failed to copy logs: ' + ( e instanceof Error ? e . message : e ) ) ;
}
}
}
}
}
}