diff --git a/components/Card.js b/components/Card.js index 59d9b2f..059f250 100644 --- a/components/Card.js +++ b/components/Card.js @@ -1,22 +1,24 @@ export default Card -function Card({ heading, content, step }) { - return ( -
- {step && -
-
-

- {step} -

-
-
- {heading} -
-
} -
- {content} -
-
- ) +function Card({heading, content, step}) { + return ( +
+ { + step && +
+
+

+ {step} +

+
+
+ {heading} +
+
+ } +
+ {content} +
+
+ ) } \ No newline at end of file diff --git a/components/Form.js b/components/Form.js index 557575f..2ae7992 100644 --- a/components/Form.js +++ b/components/Form.js @@ -1,192 +1,304 @@ -import jsQR from "jsqr" import {saveAs} from 'file-saver' - +import {BrowserQRCodeReader} from '@zxing/browser' +import React, {useEffect, useRef, useState} from "react" import {decodeData} from "../src/decode" -import { processJpeg, processPng, processPdf } from "../src/process" +import {processPdf, processPng} from "../src/process" import {createPass} from "../src/pass" import Card from "../components/Card" import Alert from "../components/Alert" +import jsQR from "jsqr"; export default Form function Form() { - function readFileAsync(file) { - return new Promise((resolve, reject) => { - let reader = new FileReader(); - - reader.onload = () => { - resolve(reader.result); - }; - - reader.onerror = reject; - - reader.readAsArrayBuffer(file); - }) - } + function readFileAsync(file) { + return new Promise((resolve, reject) => { + let reader = new FileReader(); - const error = function(heading, message) { - const alert = document.getElementById('alert') - alert.setAttribute('style', null) - - document.getElementById('heading').innerHTML = heading - document.getElementById('message').innerHTML = message + reader.onload = () => { + resolve(reader.result); + }; - document.getElementById('spin').style.display = 'none' - } + reader.onerror = reject; - const processFile = async function() { - document.getElementById('spin').style.display = 'block' - - const file = document.getElementById('file').files[0] - const fileBuffer = await readFileAsync(file) - - let imageData - - switch (file.type) { - case 'application/pdf': - console.log('pdf') - imageData = await processPdf(fileBuffer) - break - case 'image/jpeg': - console.log('jpeg') - imageData = await processJpeg(fileBuffer) - break - case 'image/png': - console.log('png') - imageData = await processPng(fileBuffer) - break - default: - error('Error', 'Invalid file type') - return + reader.readAsArrayBuffer(file); + }) } - let code = jsQR(imageData.data, imageData.width, imageData.height, { - inversionAttempts: 'dontInvert', - }) + const error = function (heading, message) { + const alert = document.getElementById('alert') + alert.setAttribute('style', null) - if (code) { - const rawData = code.data - let decoded + document.getElementById('heading').innerHTML = heading + document.getElementById('message').innerHTML = message - try { - decoded = decodeData(rawData) - } catch (error) { - error('Invalid QR code found', 'Make sure that you picked the correct PDF') - } - - return {decoded: decoded, raw: rawData} - } else { - error('No QR code found', 'Try scanning the PDF again') - } - } - - const addToWallet = async function(event) { - event.preventDefault() - - let result - - try { - result = await processFile() - } catch { - error('Error:', 'Could not extract QR code data from PDF') + document.getElementById('spin').style.display = 'none' } - if (typeof result === 'undefined') { - return - } - - const color = document.getElementById('color').value - - try { - const pass = await createPass( - { - decoded: result.decoded, - raw: result.raw, - color: color + const processFile = async function () { + console.log(qrCode) + console.log(file) + if (!qrCode && !file) { + error("Error", "Please capture a QR Code or select a file to scan"); + return; } - ) - - if (!pass) { - error('Error:', "Something went wrong.") - } else { - const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"}); - saveAs(passBlob, 'covid.pkpass') - } - } catch (e) { - error('Error:', e.message) - } finally { - document.getElementById('spin').style.display = 'none' - } - } - return ( -
-
addToWallet(e)}> - -

- Please select the certificate screenshot or (scanned) PDF page, which you received from your doctor, pharmacy, vaccination centre or online. Note that taking a picture does not work on most devices yet. -

- -
- } /> - -

- Pick a background color for your pass. -

-
- -
- -
-
- - } /> - -

- Data privacy is of special importance when processing health-related data. - In order for you to make an informed decision, please read the Privacy Policy. -

- -
- -
- - - - -
-
- - } /> - - - - - ) + document.getElementById('spin').style.display = 'block' + + let rawData; + + if (file) { + let imageData + const fileBuffer = await readFileAsync(file) + + switch (file.type) { + case 'application/pdf': + console.log('pdf') + imageData = await processPdf(fileBuffer) + break + case 'image/png': + console.log('png') + imageData = await processPng(fileBuffer) + break + default: + error('Error', 'Invalid file type') + return + } + + let code = jsQR(imageData.data, imageData.width, imageData.height, { + inversionAttempts: 'dontInvert', + }) + + rawData = code.data; + + } else { + rawData = qrCode.getText() + } + + if (rawData) { + let decoded + + try { + decoded = decodeData(rawData) + } catch (e) { + error('Invalid QR code found', 'Try another method to select your certificate') + return; + } + + return {decoded: decoded, raw: rawData} + } else { + error('No QR code found', 'Try another method to select your certificate') + } + } + + const addToWallet = async function (event) { + event.preventDefault() + + let result + + try { + result = await processFile() + } catch (e) { + error('Error:', 'Could not extract QR code data from certificate') + } + + if (typeof result === 'undefined') { + return + } + + const color = document.getElementById('color').value + + try { + const pass = await createPass( + { + decoded: result.decoded, + raw: result.raw, + color: color + } + ) + + if (!pass) { + error('Error:', "Something went wrong.") + } else { + const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"}); + saveAs(passBlob, 'covid.pkpass') + } + } catch (e) { + error('Error:', e.message) + } finally { + document.getElementById('spin').style.display = 'none' + } + } + + const [isCameraOpen, setIsCameraOpen] = useState(false); + const [globalControls, setGlobalControls] = useState(undefined); + const [qrCode, setQrCode] = useState(undefined); + const [file, setFile] = useState(undefined); + + const inputFile = useRef(undefined) + + useEffect(() => { + if (inputFile && inputFile.current) { + inputFile.current.addEventListener('input', () => { + let selectedFile = inputFile.current.files[0]; + if (selectedFile !== undefined) { + setQrCode(undefined); + setFile(selectedFile); + } + }); + } + }, [inputFile]) + + async function showFileDialog() { + inputFile.current.click(); + + } + + + async function hideCameraView() { + if (globalControls !== undefined) { + globalControls.stop(); + } + setIsCameraOpen(false); + } + + async function showCameraView() { + const codeReader = new BrowserQRCodeReader(); + + // Needs to be called before any camera can be accessed + await BrowserQRCodeReader.listVideoInputDevices(); + + // Get preview Element to show camera stream + const previewElem = document.querySelector('#cameraPreview'); + + setGlobalControls(await codeReader.decodeFromVideoDevice(undefined, previewElem, (result, error, controls) => { + + if (result !== undefined) { + setQrCode(result); + setFile(undefined); + + controls.stop(); + + // Reset + setGlobalControls(undefined); + setIsCameraOpen(false); + } + })); + + setIsCameraOpen(true); + } + + return ( +
+
addToWallet(e)}> + +

+ Please select the certificate screenshot or (scanned) PDF page, which you received from your + doctor, pharmacy, vaccination centre or online. Note that taking a picture does not work on + most devices yet. +

+
+ + +
+ +
+ }/> + +

+ Pick a background color for your pass. +

+
+ +
+ + + +
+
+ + }/> + +

+ Data privacy is of special importance when processing health-related data. + In order for you to make an informed decision, please read the Privacy + Policy. +

+ +
+ +
+ + + + +
+
+ + }/> + + + + + ) } diff --git a/package-lock.json b/package-lock.json index c1412d0..90de842 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "version": "0.1.0", "dependencies": { + "@zxing/browser": "^0.0.9", "base45-js": "^1.0.1", "cbor-js": "^0.1.0", "do-not-zip": "^1.0.0", @@ -435,6 +436,38 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "node_modules/@zxing/browser": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.0.9.tgz", + "integrity": "sha512-yqYz/FHQXbUnfSK+oeDXUa4ezC/qdXkVpqEFAnxM7LqIWvNWEQyUpdaTr0X6MGtIcP0Smakj5D8J7/l276plBw==", + "optionalDependencies": { + "@zxing/text-encoding": "^0.9.0" + }, + "peerDependencies": { + "@zxing/library": "^0.18.5" + } + }, + "node_modules/@zxing/library": { + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.18.6.tgz", + "integrity": "sha512-bulZ9JHoLFd9W36pi+7e7DnEYNJhljYjZ1UTsKPOoLMU3qtC+REHITeCRNx40zTRJZx18W5TBRXt5pq2Uopjsw==", + "peer": true, + "dependencies": { + "ts-custom-error": "^3.0.0" + }, + "engines": { + "node": ">= 10.4.0" + }, + "optionalDependencies": { + "@zxing/text-encoding": "~0.9.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -4012,6 +4045,15 @@ "punycode": "^2.1.0" } }, + "node_modules/ts-custom-error": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.2.0.tgz", + "integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A==", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -4730,6 +4772,30 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "@zxing/browser": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.0.9.tgz", + "integrity": "sha512-yqYz/FHQXbUnfSK+oeDXUa4ezC/qdXkVpqEFAnxM7LqIWvNWEQyUpdaTr0X6MGtIcP0Smakj5D8J7/l276plBw==", + "requires": { + "@zxing/text-encoding": "^0.9.0" + } + }, + "@zxing/library": { + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.18.6.tgz", + "integrity": "sha512-bulZ9JHoLFd9W36pi+7e7DnEYNJhljYjZ1UTsKPOoLMU3qtC+REHITeCRNx40zTRJZx18W5TBRXt5pq2Uopjsw==", + "peer": true, + "requires": { + "@zxing/text-encoding": "~0.9.0", + "ts-custom-error": "^3.0.0" + } + }, + "@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -7485,6 +7551,12 @@ "punycode": "^2.1.0" } }, + "ts-custom-error": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.2.0.tgz", + "integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A==", + "peer": true + }, "ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", diff --git a/package.json b/package.json index 5b5f509..f605ce5 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "start": "next start" }, "dependencies": { + "@zxing/browser": "^0.0.9", "base45-js": "^1.0.1", "cbor-js": "^0.1.0", "do-not-zip": "^1.0.0", diff --git a/src/process.js b/src/process.js index df63968..bc640da 100644 --- a/src/process.js +++ b/src/process.js @@ -1,4 +1,3 @@ -import jpeg from 'jpeg-js' import {PNG} from 'pngjs' import * as PdfJS from 'pdfjs-dist' @@ -42,11 +41,6 @@ export async function processPdf(file) { return ctx.getImageData(0, 0, canvas.width, canvas.height) } -// Processes a JPEG File and returns it as ImageData -export async function processJpeg(file) { - return jpeg.decode(file) -} - // Processes a PNG File and returns it as ImageData export async function processPng(file) { return new Promise(async (resolve, reject) => {