From 54c09b8f7db2023cf56f3744ced50980e4280563 Mon Sep 17 00:00:00 2001 From: aineo <124525926+aineo@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:59:03 +0300 Subject: [PATCH 1/2] feat: No camera access or video stream info in modal --- src/components/QrcodeScannerDialog.vue | 76 +++++++++++++++++++------- src/lib/zxing/index.ts | 30 +++++++--- src/locales/de.json | 4 ++ src/locales/en.json | 4 ++ src/locales/ru.json | 4 ++ src/locales/zh.json | 4 ++ 6 files changed, 93 insertions(+), 29 deletions(-) diff --git a/src/components/QrcodeScannerDialog.vue b/src/components/QrcodeScannerDialog.vue index ac7f13fa5..dc1fa8678 100644 --- a/src/components/QrcodeScannerDialog.vue +++ b/src/components/QrcodeScannerDialog.vue @@ -46,21 +46,42 @@ - + -

- {{ $t('scan.no_camera_found') }} -

-

- {{ $t('scan.connect_camera') }} -

+ + +
@@ -91,7 +112,7 @@ const classes = { cameraSelect: `${className}__camera-select` } -type CameraStatus = 'waiting' | 'active' | 'nocamera' +type CameraStatus = 'waiting' | 'active' | 'nocamera' | 'noaccess' | 'nostream' export default defineComponent({ props: { @@ -110,6 +131,7 @@ export default defineComponent({ const scanner = ref(null) const cameras = ref([]) const currentCamera = ref(null) + const noStreamDetails = ref(null) const scannerControls = ref(null) const show = computed({ @@ -131,10 +153,7 @@ export default defineComponent({ cameras.value = await scanner.value.getCameras() } catch (error) { cameraStatus.value = 'nocamera' - store.dispatch('snackbar/show', { - message: t('scan.something_wrong') - }) - console.error(error) + onError(error as string) } } @@ -149,6 +168,13 @@ export default defineComponent({ show.value = false } + const onError = (error: string) => { + store.dispatch('snackbar/show', { + message: t('scan.something_wrong') + }) + console.error(error) + } + watch(cameras, (cameras) => { if (cameras.length > 0) { currentCamera.value = cameras.length >= 2 ? 1 : 0 @@ -160,15 +186,24 @@ export default defineComponent({ }) watch(currentCamera, () => { - void scanner.value?.start(currentCamera.value, (result, _, controls) => { - if (result) { - onScan(result.getText()) // text is private field for zxing/browser - } + void scanner.value?.start( + currentCamera.value, + (result, _, controls) => { + if (result) onScan(result.getText()) // text is private field for zxing/browser - if (controls) { - scannerControls.value = controls + if (controls) scannerControls.value = controls + }, + (error) => { + if (error.name === 'NotAllowedError') { + cameraStatus.value = 'noaccess' + } else { + cameraStatus.value = 'nostream' + noStreamDetails.value = error + } + + onError(error) } - }) + ) }) onMounted(() => { @@ -184,6 +219,7 @@ export default defineComponent({ cameraStatus, classes, currentCamera, + noStreamDetails, props, show, videoElement diff --git a/src/lib/zxing/index.ts b/src/lib/zxing/index.ts index af823598f..141c1c1e8 100644 --- a/src/lib/zxing/index.ts +++ b/src/lib/zxing/index.ts @@ -6,6 +6,9 @@ type DecodeContinuouslyCallback = ( error?: Exception, controls?: IScannerControls ) => void + +type ModalErrorCallback = (error: any) => void + export class Scanner { cameraStream!: MediaStream codeReader!: BrowserQRCodeReader @@ -20,19 +23,28 @@ export class Scanner { this.codeReader = new BrowserQRCodeReader() } - async start(currentCamera: number | null, decodeCallback: DecodeContinuouslyCallback) { + async start( + currentCamera: number | null, + decodeCallback: DecodeContinuouslyCallback, + modalCallback: ModalErrorCallback, + ) { // Stop all tracks from media stream before camera changing if (this.cameraStream) this.stopVideoTracks() - // Request new media stream and attach to video element as source object - const facingMode = currentCamera === 1 ? 'environment' : 'user' - this.cameraStream = await navigator.mediaDevices.getUserMedia({ - video: { facingMode }, - audio: false - }) - this.videoElement.srcObject = this.cameraStream + try { + // Request new media stream and attach to video element as source object + const facingMode = currentCamera === 1 ? 'environment' : 'user' + this.cameraStream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode }, + audio: false + }) + this.videoElement.srcObject = this.cameraStream - return this.codeReader.decodeFromVideoElement(this.videoElement, decodeCallback) + this.codeReader.decodeFromVideoElement(this.videoElement, decodeCallback) + } catch (error) { + // If something went wrong, show the reason in modal window + modalCallback(error) + } } async getCameras() { diff --git a/src/locales/de.json b/src/locales/de.json index 24be2d500..c15cefb79 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -169,6 +169,7 @@ "camera_button": "Camera", "close_button": "Close", "connect_camera": "Connect the camera and try again", + "grant_camera_permissions": "Erteilen Sie Kamera Berechtigungen in den Browsereinstellungen und versuchen Sie es erneut", "hold_your_device": "Hold your device steady for 2-3 seconds towards the QR code you want to scan", "login": { "modal_header": "Scan QR code with a passphrase" @@ -176,7 +177,10 @@ "new-chat": { "modal_header": "Scan QR code with ADM wallet address" }, + "no_camera_access": "Kein zugriff auf die kamera", "no_camera_found": "No camera found", + "no_camera_stream": "Kein Video Stream von Kamera", + "no_stream_details": "Einzelheiten:
{ noStreamDetails }", "something_wrong": "Unexpected error. Check the adm console application", "waiting_camera": "Waiting camera" }, diff --git a/src/locales/en.json b/src/locales/en.json index 24747b505..13f5d7511 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -249,6 +249,7 @@ "camera_button": "Camera", "close_button": "Close", "connect_camera": "Switch on camera and try again", + "grant_camera_permissions": "Grant camera permissions in browser settings and try again", "hold_your_device": "Hold your device steady for 2-3 seconds to scan the QR code", "login": { "modal_header": "Scan the QR code containing a passphrase" @@ -256,7 +257,10 @@ "new-chat": { "modal_header": "Scan the QR code contain an ADAMANT address" }, + "no_camera_access": "No camera access", "no_camera_found": "No camera found", + "no_camera_stream": "No camera video stream", + "no_stream_details": "Details:
{ noStreamDetails }", "something_wrong": "Something went wrong", "waiting_camera": "Waiting for camera…" }, diff --git a/src/locales/ru.json b/src/locales/ru.json index e49274d50..47d8fd699 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -250,6 +250,7 @@ "camera_button": "Камера", "close_button": "Закрыть", "connect_camera": "Подключите камеру и попробуйте снова", + "grant_camera_permissions": "Предоставьте разрешения камере в настройках браузера и попробуйте снова", "hold_your_device": "Наведите камеру на 2-3 секунды на QR-код", "login": { "modal_header": "Отсканируйте QR-код с парольной фразой" @@ -257,7 +258,10 @@ "new-chat": { "modal_header": "Отсканируйте QR-код с адресом АДАМАНТа" }, + "no_camera_access": "Нет доступа к камере", "no_camera_found": "Камера не найдена", + "no_camera_stream": "Нет видеопотока с камеры", + "no_stream_details": "Подробности:
{ noStreamDetails }", "something_wrong": "Что-то пошло не так", "waiting_camera": "Включаю камеру…" }, diff --git a/src/locales/zh.json b/src/locales/zh.json index 642e19ed5..daea0afb0 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -249,6 +249,7 @@ "camera_button": "相机", "close_button": "关闭", "connect_camera": "打开相机,再试一次", + "grant_camera_permissions": "在浏览器设置中授予相机权限并重试", "hold_your_device": "保持设备稳定 2-3 秒以扫描二维码", "login": { "modal_header": "扫描包含密码短语的二维码" @@ -256,7 +257,10 @@ "new-chat": { "modal_header": "扫描包含ADAMANT地址的二维码" }, + "no_camera_access": "没有相机访问权限", "no_camera_found": "未找到摄像头", + "no_camera_stream": "没有来自摄像头的视频流", + "no_stream_details": "详情:
{ noStreamDetails }", "something_wrong": "出了问题", "waiting_camera": "正在等待相机…" }, From ac715e4dfc79f594b6b2ffdbb2ce8d15e3c801b1 Mon Sep 17 00:00:00 2001 From: aineo <124525926+aineo@users.noreply.github.com> Date: Fri, 19 Jul 2024 19:09:57 +0300 Subject: [PATCH 2/2] chore: camera error typings --- src/components/QrcodeScannerDialog.vue | 6 +++--- src/lib/zxing/index.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/QrcodeScannerDialog.vue b/src/components/QrcodeScannerDialog.vue index dc1fa8678..73a67daea 100644 --- a/src/components/QrcodeScannerDialog.vue +++ b/src/components/QrcodeScannerDialog.vue @@ -153,7 +153,7 @@ export default defineComponent({ cameras.value = await scanner.value.getCameras() } catch (error) { cameraStatus.value = 'nocamera' - onError(error as string) + onError(error as Error) } } @@ -168,7 +168,7 @@ export default defineComponent({ show.value = false } - const onError = (error: string) => { + const onError = (error: Error) => { store.dispatch('snackbar/show', { message: t('scan.something_wrong') }) @@ -198,7 +198,7 @@ export default defineComponent({ cameraStatus.value = 'noaccess' } else { cameraStatus.value = 'nostream' - noStreamDetails.value = error + noStreamDetails.value = `${error.name} ${error.message}` } onError(error) diff --git a/src/lib/zxing/index.ts b/src/lib/zxing/index.ts index 141c1c1e8..49feace16 100644 --- a/src/lib/zxing/index.ts +++ b/src/lib/zxing/index.ts @@ -7,7 +7,7 @@ type DecodeContinuouslyCallback = ( controls?: IScannerControls ) => void -type ModalErrorCallback = (error: any) => void +type ModalErrorCallback = (error: Error) => void export class Scanner { cameraStream!: MediaStream @@ -26,7 +26,7 @@ export class Scanner { async start( currentCamera: number | null, decodeCallback: DecodeContinuouslyCallback, - modalCallback: ModalErrorCallback, + modalCallback: ModalErrorCallback ) { // Stop all tracks from media stream before camera changing if (this.cameraStream) this.stopVideoTracks() @@ -42,8 +42,8 @@ export class Scanner { this.codeReader.decodeFromVideoElement(this.videoElement, decodeCallback) } catch (error) { - // If something went wrong, show the reason in modal window - modalCallback(error) + // If something went wrong, show the reason in the modal window + modalCallback(error as Error) } }