Added direct camera-scan support for creating QR codes
* SHC QR codes can now be scanned directly from the camera, eliminating the need for error-prone screenshotting or photos - the scanner will auto-detect an SHC QR code and proceed to the next step
This commit is contained in:
parent
b179c19e3d
commit
8e83c65f6c
|
@ -9,15 +9,11 @@ import Card from "./Card";
|
|||
import Alert from "./Alert";
|
||||
import Check from './Check';
|
||||
import {PayloadBody} from "../src/payload";
|
||||
import {getPayloadBodyFromFile} from "../src/process";
|
||||
import {getPayloadBodyFromFile, processSHCCode} from "../src/process";
|
||||
import {PassData} from "../src/pass";
|
||||
import {Photo} from "../src/photo";
|
||||
import {COLORS} from "../src/colors";
|
||||
import Colors from './Colors';
|
||||
import {isChrome, isIOS, isIPad13, isMacOs, isSafari, deviceDetect, osName, osVersion} from 'react-device-detect';
|
||||
import {isIOS, isMacOs, isSafari, osVersion} from 'react-device-detect';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { counterReset } from 'html2canvas/dist/types/css/property-descriptors/counter-reset';
|
||||
import { color } from 'html2canvas/dist/types/css/types/color';
|
||||
import Bullet from './Bullet';
|
||||
|
||||
|
||||
|
@ -27,9 +23,6 @@ function Form(): JSX.Element {
|
|||
// Whether camera is open or not
|
||||
const [isCameraOpen, setIsCameraOpen] = useState<boolean>(false);
|
||||
|
||||
// Currently selected color
|
||||
const [selectedColor, setSelectedColor] = useState<COLORS>(COLORS.WHITE);
|
||||
|
||||
// Currently selected dose
|
||||
const [selectedDose, setSelectedDose] = useState<number>(2);
|
||||
|
||||
|
@ -98,7 +91,7 @@ function Form(): JSX.Element {
|
|||
if (inputFile && inputFile.current) {
|
||||
inputFile.current.addEventListener('change', () => {
|
||||
let selectedFile = inputFile.current.files[0];
|
||||
if (selectedFile !== undefined) {
|
||||
if (selectedFile) {
|
||||
setFileLoading(true);
|
||||
setQrCode(undefined);
|
||||
setPayloadBody(undefined);
|
||||
|
@ -132,7 +125,7 @@ function Form(): JSX.Element {
|
|||
} catch (e) {
|
||||
setFile(file);
|
||||
setFileLoading(false);
|
||||
if (e != undefined) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
|
||||
// Don't report known errors to Sentry
|
||||
|
@ -151,11 +144,15 @@ function Form(): JSX.Element {
|
|||
setFileErrorMessage("Unexpected error. Sorry.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Show file Dialog
|
||||
async function showFileDialog() {
|
||||
hideCameraView();
|
||||
|
||||
// Clear out any currently-selected files
|
||||
inputFile.current.value = '';
|
||||
|
||||
inputFile.current.click();
|
||||
}
|
||||
|
||||
|
@ -172,10 +169,11 @@ function Form(): JSX.Element {
|
|||
|
||||
// Hide camera view
|
||||
async function hideCameraView() {
|
||||
if (globalControls !== undefined) {
|
||||
if (globalControls) {
|
||||
globalControls.stop();
|
||||
}
|
||||
setIsCameraOpen(false);
|
||||
_setFileErrorMessages([]);
|
||||
}
|
||||
|
||||
// Show camera view
|
||||
|
@ -189,13 +187,13 @@ function Form(): JSX.Element {
|
|||
try {
|
||||
deviceList = await BrowserQRCodeReader.listVideoInputDevices();
|
||||
} catch (e) {
|
||||
setAddErrorMessage('noCameraAccess');
|
||||
setFileErrorMessage('noCameraAccess');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if camera device is present
|
||||
if (deviceList.length == 0) {
|
||||
setAddErrorMessage("noCameraFound");
|
||||
setFileErrorMessage("noCameraFound");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -207,24 +205,46 @@ function Form(): JSX.Element {
|
|||
// Start decoding from video device
|
||||
await codeReader.decodeFromVideoDevice(undefined,
|
||||
previewElem,
|
||||
(result, error, controls) => {
|
||||
if (result !== undefined) {
|
||||
setQrCode(result);
|
||||
setFile(undefined);
|
||||
async (result, error, controls) => {
|
||||
if (result) {
|
||||
const qrCode = result.getText();
|
||||
// Check if this was a valid SHC QR code - if it was not, display an error
|
||||
if (!qrCode.startsWith('shc:/')) {
|
||||
setFileErrorMessage('The scanned QR code was not a valid Smart Health Card QR code!');
|
||||
} else {
|
||||
_setFileErrorMessages([]);
|
||||
setQrCode(result);
|
||||
setFile(undefined);
|
||||
setPayloadBody(undefined);
|
||||
setShowDoseOption(false);
|
||||
setGenerated(false);
|
||||
checkBrowserType();
|
||||
|
||||
const payloadBody = await processSHCCode(qrCode);
|
||||
|
||||
setPayloadBody(payloadBody);
|
||||
|
||||
controls.stop();
|
||||
|
||||
// Reset
|
||||
setGlobalControls(undefined);
|
||||
setIsCameraOpen(false);
|
||||
controls.stop();
|
||||
|
||||
// Reset
|
||||
setGlobalControls(undefined);
|
||||
setIsCameraOpen(false);
|
||||
}
|
||||
}
|
||||
if (error !== undefined) {
|
||||
setAddErrorMessage(error.message);
|
||||
if (error) {
|
||||
setFileErrorMessage(error.message);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
setQrCode(undefined);
|
||||
setPayloadBody(undefined);
|
||||
setFile(undefined);
|
||||
setShowDoseOption(false);
|
||||
setGenerated(false);
|
||||
_setFileErrorMessages([]);
|
||||
|
||||
setIsCameraOpen(true);
|
||||
}
|
||||
|
||||
|
@ -442,6 +462,12 @@ function Form(): JSX.Element {
|
|||
className="focus:outline-none h-20 bg-green-600 hover:bg-gray-700 text-white font-semibold rounded-md">
|
||||
{t('index:openFile')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={isCameraOpen ? hideCameraView : showCameraView}
|
||||
className="focus:outline-none h-20 bg-green-600 hover:bg-gray-700 text-white font-semibold rounded-md">
|
||||
{isCameraOpen ? t('index:stopCamera') : t('index:startCamera')}
|
||||
</button>
|
||||
<div id="spin" className={fileLoading ? undefined : "hidden"}>
|
||||
<svg className="animate-spin h-5 w-5 ml-4" viewBox="0 0 24 24">
|
||||
<circle className="opacity-0" cx="12" cy="12" r="10" stroke="currentColor"
|
||||
|
@ -452,6 +478,8 @@ function Form(): JSX.Element {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<video id="cameraPreview"
|
||||
className={`${isCameraOpen ? undefined : "hidden"} rounded-md w-full`}/>
|
||||
<input type='file'
|
||||
id='file'
|
||||
accept="application/pdf,.png,.jpg,.jpeg,.gif,.webp"
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
iosHint: On iOS, please use Safari
|
||||
errorClose: Close
|
||||
selectCertificate: Select vaccination receipt (PDF or image)
|
||||
selectCertificate: Select or scan vaccination receipt (PDF or image)
|
||||
selectCertificateDescription: |
|
||||
Press "Select File", "Browse..." and select the PDF file or picture you have saved in Step 1
|
||||
#stopCamera: Stop Camera
|
||||
#startCamera: Start Camera
|
||||
You can scan the QR code with your camera, or press "Select File", "Browse..." and select the PDF file or picture you have saved in Step 1
|
||||
stopCamera: Stop Camera
|
||||
startCamera: Start Camera
|
||||
openFile: Select File
|
||||
#foundQrCode: Found QR Code!
|
||||
foundQrCode: Found valid Smart Health Card QR Code!
|
||||
downloadReceipt: Download or take a picture of your QR code
|
||||
visit: If you are in Ontario, please visit
|
||||
ontarioHealth: Ontario Ministry of Health
|
||||
gotoOntarioHealth: Go to Ontario Ministry of Health
|
||||
downloadSignedPDF: and enter your information to display your official vaccination receipt. Press the Share Icon at the bottom, "Save As Files" to store it onto your iPhone. You can also take a picture or screenshot of your QR code with your phone (please make sure the picture is good-quality, is not blurry, and captures ALL of the QR code!)
|
||||
downloadSignedPDF: and enter your information to display your official vaccination receipt. Press the Share Icon at the bottom, "Save As Files" to store it onto your iPhone. You can also take a picture or screenshot of your QR code with your phone (please make sure the picture is good-quality, is not blurry, and captures ALL of the QR code!), or scan it directly with your camera below in step 2
|
||||
reminderNotToRepeat: If you have completed this step before, simply proceed to Step 2
|
||||
addToWallet: Add to Apple Wallet
|
||||
addToWalletHeader: Add to Apple Wallet / Save as Photo
|
||||
|
|
|
@ -129,6 +129,31 @@ async function getImageDataFromPdf(fileBuffer: ArrayBuffer): Promise<ImageData[]
|
|||
return Promise.resolve(retArray);
|
||||
}
|
||||
|
||||
export async function processSHCCode(shcQrCode : string) : Promise<PayloadBody> {
|
||||
console.log('processSHCCode');
|
||||
|
||||
try {
|
||||
// We found a QR code of some kind - start analyzing now
|
||||
const jws = getScannedJWS(shcQrCode);
|
||||
const decoded = await decodeJWS(jws);
|
||||
|
||||
//console.log(decoded);
|
||||
|
||||
const verified = verifyJWS(jws, decoded.iss);
|
||||
|
||||
if (verified) {
|
||||
const shcReceipt = Decode.decodedStringToReceipt(decoded);
|
||||
//console.log(shcReceipt);
|
||||
return Promise.resolve({receipts: null, shcReceipt, rawData: shcQrCode});
|
||||
} else {
|
||||
// If we got here, we found an SHC which was not verifiable. Consider it fatal and stop processing.
|
||||
return Promise.reject(`Issuer ${decoded.iss} cannot be verified.`);
|
||||
}
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function processSHC(allImageData : ImageData[]) : Promise<PayloadBody> {
|
||||
|
||||
console.log('processSHC');
|
||||
|
@ -144,23 +169,7 @@ async function processSHC(allImageData : ImageData[]) : Promise<PayloadBody> {
|
|||
|
||||
if (code) {
|
||||
try {
|
||||
// We found a QR code of some kind - start analyzing now
|
||||
const rawData = code.data;
|
||||
const jws = getScannedJWS(rawData);
|
||||
const decoded = await decodeJWS(jws);
|
||||
|
||||
//console.log(decoded);
|
||||
|
||||
const verified = verifyJWS(jws, decoded.iss);
|
||||
|
||||
if (verified) {
|
||||
const shcReceipt = Decode.decodedStringToReceipt(decoded);
|
||||
//console.log(shcReceipt);
|
||||
return Promise.resolve({receipts: null, shcReceipt, rawData});
|
||||
} else {
|
||||
// If we got here, we found an SHC which was not verifiable. Consider it fatal and stop processing.
|
||||
return Promise.reject(`Issuer ${decoded.iss} cannot be verified.`);
|
||||
}
|
||||
return await processSHCCode(code.data);
|
||||
} catch (e) {
|
||||
// We blew up during processing - log it and move on to the next page
|
||||
console.log(e);
|
||||
|
|
Loading…
Reference in New Issue