@ -13,7 +13,7 @@
>
< div >
< h3 class = "text-lg font-medium text-gray-900" > Scan QR Code < / h3 >
< span class = "text-xs text-gray-500" > v1 .1 .0 build 00000 < / span >
< span class = "text-xs text-gray-500" > v1 .1 .0 < / span >
< / div >
< button
class = "text-gray-400 hover:text-gray-500"
@ -67,8 +67,7 @@
< path
class = "opacity-75"
fill = "currentColor"
d = " M4 12 a8 8 0 018 - 8 V0C5 .373 0 0 5.373 0 12 h4zm2 5.291 A7 .962 7.962 0 014 12 H0c0
3.042 1.135 5.824 3 7.938 l3 - 2.647 z "
d = "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
> < / path >
< / svg >
< span > { { initializationStatus } } < / span >
@ -138,8 +137,7 @@
stroke - linecap = "round"
stroke - linejoin = "round"
stroke - width = "2"
d = " M3 9 a2 2 0 012 - 2 h .93 a2 2 0 001.664 - .89 l .812 - 1.22 A2 2 0 0110.07 4 h3 .86 a2 2 0
011.664 .89 l .812 1.22 A2 2 0 0018.07 7 H19a2 2 0 012 2 v9a2 2 0 01 - 2 2 H5a2 2 0 01 - 2 - 2 V9z "
d = "M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
/ >
< path
stroke - linecap = "round"
@ -164,6 +162,19 @@
< / div >
< / div >
<!-- Error Banner -- >
< div v-if ="error" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert" >
< strong class = "font-bold" > Camera Error : < / strong >
< span class = "block sm:inline" > { { error } } < / span >
< ul class = "mt-2 text-sm text-red-600 list-disc list-inside" >
< li v-if ="error.includes('No camera found')" > Check if your device has a camera and it is enabled. < / li >
< li v-if ="error.includes('denied')" > Allow camera access in your browser settings and reload the page. < / li >
< li v-if ="error.includes('in use')" > Close other applications that may be using the camera. < / li >
< li v-if ="error.includes('HTTPS')" > Ensure you are using a secure ( HTTPS ) connection. < / li >
< li v-if ="!error.includes('No camera found') && !error.includes('denied') && !error.includes('in use') && !error.includes('HTTPS')" > Try refreshing the page or using a different browser / device. < / li >
< / ul >
< / div >
<!-- Footer -- >
< div class = "p-4 border-t border-gray-200" >
< div class = "flex flex-col space-y-4" >
@ -197,14 +208,6 @@
< / div >
< / div >
< / div >
< div v-if ="debugMessage" class="bg-yellow-200 text-black p-2 m-2 rounded" >
{ { debugMessage } }
< / div >
< div class = "bg-red-200 text-black p-2 m-2 rounded" >
DEBUG PANEL : If you see this , the template is updating .
< / div >
< / div >
< / div >
< / template >
@ -259,7 +262,6 @@ export default class QRScannerDialog extends Vue {
preferredCamera : "user" | "environment" = "environment" ;
initializationStatus = "Checking camera access..." ;
cameraStatus = "Initializing" ;
debugMessage = "" ;
created ( ) {
logger . log ( "QRScannerDialog platform detection:" , {
@ -282,45 +284,105 @@ export default class QRScannerDialog extends Vue {
}
}
async onInit ( promise : Promise < void > ) {
alert ( "onInit called" ) ;
this . debugMessage = "onInit called" ;
let timeoutHit = false ;
const timeout = setTimeout ( ( ) => {
timeoutHit = true ;
this . isInitializing = false ;
this . cameraStatus = "Ready (timeout fallback)" ;
this . initializationStatus = "Camera ready (fallback)" ;
alert ( "Timeout fallback triggered" ) ;
this . debugMessage = "Timeout fallback triggered" ;
} , 4000 ) ;
async onInit ( promise : Promise < void > ) : Promise < void > {
if ( this . isNativePlatform ) {
logger . log ( "Closing QR dialog on native platform" ) ;
this . $nextTick ( ( ) => this . close ( ) ) ;
return ;
}
this . isInitializing = true ;
this . error = null ;
this . initializationStatus = "Checking camera access..." ;
try {
await promise ;
if ( ! timeoutHit ) {
clearTimeout ( timeout ) ;
this . isInitializing = false ;
this . cameraStatus = "Ready" ;
alert ( "Promise resolved before timeout" ) ;
this . debugMessage = "Promise resolved before timeout" ;
/ / 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 ) {
throw new Error (
"Camera API not available. Please ensure you're using HTTPS." ,
) ;
}
} catch ( error ) {
clearTimeout ( timeout ) ;
alert ( "Promise rejected: " + ( error instanceof Error ? error . message : error ) ) ;
this . debugMessage = "Promise rejected: " + ( error instanceof Error ? error . message : error ) ;
if ( error instanceof Error ) {
this . error = error . message ;
this . cameraStatus = "Error" ;
if ( this . onError ) {
this . onError ( error ) ;
/ / 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 videoDevices = devices . filter ( device => device . kind === 'videoinput' ) ;
if ( videoDevices . length === 0 ) {
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 }
}
}
logger . error ( "Error initializing QR scanner:" , {
error : error . message ,
stack : error . stack ,
} ) ;
/ / 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..." ;
try {
const stream = await navigator . mediaDevices . getUserMedia ( {
video : {
facingMode : this . preferredCamera ,
width : { ideal : 1280 } ,
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 ( ) ) ;
this . initializationStatus = "Camera permission granted..." ;
logger . log ( "Camera permission granted" ) ;
} catch ( permissionError ) {
const error = permissionError as Error ;
logger . error ( "Camera permission error:" , {
name : error . name ,
type : error . constructor . name ,
messag e: error . messag e,
} ) ;
if ( error . name === "NotAllowedError" || error . name === "PermissionDeniedError" ) {
throw new Error (
"Camera access denied. Please grant camera permission and try again." ,
) ;
} else if ( error . name === "NotFoundError" || error . name === "DevicesNotFoundError" ) {
throw new Error (
"No camera found. Please ensure your device has a camera." ,
) ;
} else if ( error . name === "NotReadableError" || error . name === "TrackStartError" ) {
throw new Error ( "Camera is in use by another application." ) ;
} else {
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..." ;
logger . log ( "Initializing QR scanner..." ) ;
await promise ;
this . isInitializing = false ;
this . cameraStatus = "Ready" ;
logger . log ( "QR scanner initialized successfully" ) ;
} catch ( error ) {
const wrappedError = error instanceof Error ? error : new Error ( String ( error ) ) ;
this . error = wrappedError . message ;
this . cameraStatus = "Error" ;
if ( this . onError ) {
this . onError ( wrappedError ) ;
}
logger . error ( "Error initializing QR scanner:" , {
error : wrappedError . message ,
stack : wrappedError . stack ,
name : wrappedError . name ,
type : wrappedError . constructor . name ,
} ) ;
} finally {
this . isInitializing = false ;
}
@ -353,8 +415,8 @@ export default class QRScannerDialog extends Vue {
}
} ;
/ / H a n d l e b o t h p r o m i s e a n d n o n - p r o m i s e r e s u l t s
if ( result && typeof result . then === "function" ) {
/ / 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 ) {
result
. then ( processResult )
. catch ( ( error : Error ) => this . handleError ( error ) )