From 18f034acd3ff98d0d9f1f2b4bd926f0ddfee104a Mon Sep 17 00:00:00 2001 From: Kitteh Date: Sat, 24 Apr 2021 11:19:21 +0100 Subject: [PATCH] Add QR scanner to Unseal page. Closes #22. --- package.json | 2 + src/pages/Unseal.js | 163 +++++++++++++++++++++++++++++++++-------- src/scss/uikit.scss | 1 + src/translations/en.js | 3 + 4 files changed, 138 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index db4ae18..aceeb42 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "mini-css-extract-plugin": "^1.4.1", "node-sass": "^5.0.0", "prismjs": "^1.23.0", + "qr-scanner": "^1.2.0", + "raw-loader": "^4.0.2", "sass-loader": "^11.0.1", "uikit": "^3.6.19", "webpack": "^5.32.0", diff --git a/src/pages/Unseal.js b/src/pages/Unseal.js index e2dd28a..092577a 100644 --- a/src/pages/Unseal.js +++ b/src/pages/Unseal.js @@ -2,14 +2,103 @@ import { Page } from "../types/Page.js"; import { submitUnsealKey, getSealStatus } from "../api.js"; import { setPageContent, setErrorText, changePage } from "../pageUtils.js"; import { makeElement } from "../htmlUtils.js"; +import { Margin } from "../elements/Margin.js"; import { MarginInline } from "../elements/MarginInline.js"; import i18next from 'i18next'; +import QrScanner from 'qr-scanner'; + +import qrScannerWorkerSource from '!!raw-loader!qr-scanner/qr-scanner-worker.min.js'; +QrScanner.WORKER_PATH = URL.createObjectURL(new Blob([qrScannerWorkerSource])); + +const UnsealInputModes = { + FORM_INPUT: "FORM_INPUT", + QR_INPUT: "QR_INPUT" +} export class UnsealPage extends Page { constructor() { super(); + //this.mode = UnsealInputModes.QR_INPUT; + this.mode = UnsealInputModes.FORM_INPUT; } + cleanup() { + this.deinitWebcam() + clearInterval(this.refresher); + } + + deinitWebcam() { + try { + this.stream.getTracks().forEach(function (track) { + track.stop(); + }); + } catch { + + } + + } + + makeRefresher() { + this.refresher = setInterval(async function () { + this.updateSealProgress(await getSealStatus()); + }.bind(this), 1000); + } + async render() { + this.unsealProgress = makeElement({ + tag: "progress", + class: "uk-progress", + attributes: { value: "0", max: "0" } + }); + this.unsealProgressText = makeElement({ + tag: "p", + text: i18next.t("unseal_keys_progress", { progress: "0", keys_needed: "0" }), + }); + this.unsealInputContent = makeElement({ + tag: "div" + }) + setPageContent(makeElement({ + tag: "div", + children: [ + this.unsealProgress, + makeElement({ + tag: "p", + id: "errorText", + class: ["uk-text-danger", "uk-margin-top"] + }), + this.unsealProgressText, + this.unsealInputContent + ] + })); + this.switchInputMode(this.mode); + this.updateSealProgress(await getSealStatus()); + this.makeRefresher(); + } + + setButtons(method) { + let newMethod; + let buttonText; + newMethod = method == UnsealInputModes.FORM_INPUT ? UnsealInputModes.QR_INPUT : UnsealInputModes.FORM_INPUT; + buttonText = newMethod == UnsealInputModes.FORM_INPUT ? i18next.t("unseal_input_btn") : i18next.t("unseal_qr_btn"); + this.unsealInputContent.appendChild(makeElement({ + tag: "button", + class: ["uk-button", "uk-button-primary"], + text: buttonText, + onclick: () => { + this.switchInputMode(newMethod); + } + })) + } + + + switchInputMode(method) { + this.deinitWebcam(); + this.unsealInputContent.querySelectorAll('*').forEach(n => n.remove()) + if (method == UnsealInputModes.FORM_INPUT) this.makeUnsealForm(); + if (method == UnsealInputModes.QR_INPUT) this.makeQRInput(); + this.setButtons(method); + } + + makeUnsealForm() { this.unsealKeyForm = makeElement({ tag: "form", children: [ @@ -30,36 +119,44 @@ export class UnsealPage extends Page { })), ] }); - - this.unsealProgress = makeElement({ - tag: "progress", - class: "uk-progress", - attributes: { value: "0", max: "0" } - }); - this.unsealProgressText = makeElement({ - tag: "p", - text: i18next.t("unseal_keys_progress", { progress: "0", keys_needed: "0" }), - }); - - setPageContent(makeElement({ - tag: "div", - children: [ - this.unsealProgress, - makeElement({ - tag: "p", - id: "errorText", - class: ["uk-text-danger", "uk-margin-top"] - }), - this.unsealProgressText, - this.unsealKeyForm - ] - })); + this.unsealInputContent.appendChild(this.unsealKeyForm); this.unsealKeyForm.addEventListener("submit", function (e) { e.preventDefault(); - pageState.currentPage.handleKeySubmit(); - }); - this.updateSealProgress(await getSealStatus()); + this.handleKeySubmit(); + }.bind(this)); } + + async makeQRInput() { + let webcamVideo = makeElement({ + tag: "video" + }) + + let QRInput = makeElement({ + tag: "div", + children: [ + Margin(webcamVideo), + ] + }) + this.unsealInputContent.appendChild(QRInput); + + + this.stream = await navigator.mediaDevices.getUserMedia({ + video: { + facingMode: 'environment', + }, + audio: false, + }); + webcamVideo.srcObject = this.stream; + let lastSeenCode = ""; + const qrScanner = new QrScanner(webcamVideo, function (code) { + if (lastSeenCode == code) return; + lastSeenCode = code; + this.submitKey(code); + console.log('decoded qr code:', code) + }.bind(this)); + qrScanner.start(); + } + updateSealProgress(data) { let progress = data.progress; let keysNeeded = data.t; @@ -77,10 +174,8 @@ export class UnsealPage extends Page { } } - async handleKeySubmit() { - let formData = new FormData(this.unsealKeyForm); - - submitUnsealKey(formData.get("key")).then(_ => { + submitKey(key) { + submitUnsealKey(key).then(_ => { getSealStatus().then(data => { this.updateSealProgress(data); }); @@ -88,6 +183,12 @@ export class UnsealPage extends Page { setErrorText(e.message); }); } + + async handleKeySubmit() { + let formData = new FormData(this.unsealKeyForm); + + this.submitKey(formData.get("key")) + } get name() { return i18next.t("unseal_vault_text"); } diff --git a/src/scss/uikit.scss b/src/scss/uikit.scss index e1e464e..a721315 100644 --- a/src/scss/uikit.scss +++ b/src/scss/uikit.scss @@ -18,6 +18,7 @@ $global-warning-background: #d08770; $global-danger-background: #bf616a; $button-primary-background: #5e81ac; +$progress-bar-background: #5e81ac; // Keep these in same order as https://github.com/uikit/uikit/blob/develop/src/less/components/_import.less @import "uikit/src/scss/variables.scss"; diff --git a/src/translations/en.js b/src/translations/en.js index 363a884..eb87019 100644 --- a/src/translations/en.js +++ b/src/translations/en.js @@ -26,6 +26,9 @@ module.exports = { // Unseal Page "unseal_vault_text": "Unseal the Vault", "submit_key_btn": "Submit Key", + "unseal_input_btn": "Switch to Manual Key Input", + "unseal_qr_btn": "Switch to QR Key Input", + "key_input_placeholder": "Key", "unseal_keys_progress": "Keys: {{progress}}/{{keys_needed}}",