import {saveAs} from 'file-saver'; import React, {FormEvent, useEffect, useRef, useState} from "react"; import {BrowserQRCodeReader, IScannerControls} from "@zxing/browser"; import {Result} from "@zxing/library"; import {useTranslation} from 'next-i18next'; import Link from 'next/link'; import Card from "./Card"; import Alert from "./Alert"; import Check from './Check'; import {PayloadBody} from "../src/payload"; import {getPayloadBodyFromFile} 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 * as Sentry from '@sentry/react'; import { counterReset } from 'html2canvas/dist/types/css/property-descriptors/counter-reset'; function Form(): JSX.Element { const {t} = useTranslation(['index', 'errors', 'common']); // Whether camera is open or not const [isCameraOpen, setIsCameraOpen] = useState(false); // Currently selected color const [selectedColor, setSelectedColor] = useState(COLORS.WHITE); // Global camera controls const [globalControls, setGlobalControls] = useState(undefined); // Currently selected QR Code / File. Only one of them is set. const [qrCode, setQrCode] = useState(undefined); const [file, setFile] = useState(undefined); const [errorMessage, _setErrorMessage] = useState(undefined); const [loading, setLoading] = useState(false); const [passCount, setPassCount] = useState(''); const [generated, setGenerated] = useState(false); // this flag represents the file has been used to generate a pass const hitcountHost = 'https://stats.vaccine-ontario.ca'; useEffect(() => { if (passCount.length == 0) { getPassCount(); } }, []); const getPassCount = async () => { const hitCount = await getHitCount(); console.log(`hitcount = ${hitCount}`); setPassCount(hitCount); }; async function getHitCount() { try { const request = `${hitcountHost}/nocount?url=pass.vaccine-ontario.ca`; let response = await fetch(request); const counter = await response.text(); console.log('getHitCount returns ' + counter); return Promise.resolve(counter); } catch (e) { console.error(e); return Promise.reject(e); } } // Check if there is a translation and replace message accordingly const setErrorMessage = (message: string) => { if (message == undefined) { _setErrorMessage(undefined); return; } const translation = t('errors:'.concat(message)); _setErrorMessage(translation !== message ? translation : message); }; // File Input ref const inputFile = useRef(undefined) // Add event listener to listen for file change events useEffect(() => { if (inputFile && inputFile.current) { inputFile.current.addEventListener('input', () => { let selectedFile = inputFile.current.files[0]; if (selectedFile !== undefined) { setQrCode(undefined); setFile(selectedFile); setGenerated(false); } }); } checkBrowserType(); }, [inputFile]) // Show file Dialog async function showFileDialog() { inputFile.current.click(); } async function gotoOntarioHealth() { window.location.href = 'https://covid19.ontariohealth.ca'; } // Hide camera view async function hideCameraView() { if (globalControls !== undefined) { globalControls.stop(); } setIsCameraOpen(false); } // Show camera view async function showCameraView() { // Create new QR Code Reader const codeReader = new BrowserQRCodeReader(); // Needs to be called before any camera can be accessed let deviceList: MediaDeviceInfo[]; try { deviceList = await BrowserQRCodeReader.listVideoInputDevices(); } catch (e) { setErrorMessage('noCameraAccess'); return; } // Check if camera device is present if (deviceList.length == 0) { setErrorMessage("noCameraFound"); return; } // Get preview Element to show camera stream const previewElem: HTMLVideoElement = document.querySelector('#cameraPreview'); // Set Global controls setGlobalControls( // Start decoding from video device await codeReader.decodeFromVideoDevice(undefined, previewElem, (result, error, controls) => { if (result !== undefined) { setQrCode(result); setFile(undefined); controls.stop(); // Reset setGlobalControls(undefined); setIsCameraOpen(false); } if (error !== undefined) { setErrorMessage(error.message); } } ) ); setIsCameraOpen(true); } async function incrementCount() { try { if (typeof generated == undefined || !generated) { const request = `${hitcountHost}/count?url=pass.vaccine-ontario.ca`; console.log(request); let response = await fetch(request); console.log(request); const counter = await response.text(); // response count is not used intentionally so it always goes up by 1 only even if the server has changed let newPasscount = Number(passCount) + 1; console.log(counter); setPassCount(counter); setGenerated(true); console.log(`new PassCount = ${newPasscount}`); } } catch (e) { console.error(e); return Promise.reject(e); } } // Add Pass to wallet async function addToWallet(event: FormEvent) { event.preventDefault(); setLoading(true); if (!file && !qrCode) { setErrorMessage('noFileOrQrCode') setLoading(false); return; } const color = selectedColor; let payloadBody: PayloadBody; try { if (file) { console.log('> generatePass'); payloadBody = await getPayloadBodyFromFile(file); await incrementCount(); let pass = await PassData.generatePass(payloadBody); const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"}); saveAs(passBlob, 'covid.pkpass'); setLoading(false); } } catch (e) { console.error(e); setErrorMessage(e.message); Sentry.captureException(e); setLoading(false); } } //TODO: merge with addToWallet for common flow async function saveAsPhoto() { setLoading(true); if (!file && !qrCode) { setErrorMessage('noFileOrQrCode') setLoading(false); return; } let payloadBody: PayloadBody; try { payloadBody = await getPayloadBodyFromFile(file); await incrementCount(); let photoBlob = await Photo.generatePass(payloadBody); saveAs(photoBlob, 'pass.png'); // need to clean up const qrcodeElement = document.getElementById('qrcode'); const svg = qrcodeElement.firstChild; qrcodeElement.removeChild(svg); const body = document.getElementById('pass-image'); body.hidden = true; setLoading(false); } catch (e) { Sentry.captureException(e); setErrorMessage(e.message); setLoading(false); } } async function checkBrowserType() { if (isIPad13) { setErrorMessage('Sorry. Apple does not support the use of Wallet on iPad. Please use iPhone/Safari.'); document.getElementById('download').setAttribute('disabled','true'); } if (!isSafari && !isChrome) { setErrorMessage('Sorry. Apple Wallet pass can be added using Safari or Chrome only.'); document.getElementById('download').setAttribute('disabled','true'); } if (isIOS && (!osVersion.includes('13') && !osVersion.includes('14') && !osVersion.includes('15'))) { setErrorMessage('Sorry, iOS 13+ is needed for the Apple Wallet functionality to work') document.getElementById('download').setAttribute('disabled','true') } if (isIOS && !isSafari) { setErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS') document.getElementById('download').setAttribute('disabled','true') } } return (

{t('index:visit')}  {t('index:ontarioHealth')}   {t('index:downloadSignedPDF')}

}/>

{t('index:selectCertificateDescription')}

{t('index:selectCertificateReminder')}

{(qrCode || file) &&
{ qrCode && t('index:foundQrCode') } { file && file.name }
} }/>

{t('index:dataPrivacyDescription')} {/* {t('index:privacyPolicy')} . */}

    {passCount && } {/* */}
    
}/> { errorMessage && setErrorMessage(undefined)}/> } ) } export default Form;