Merge branch 'main' into android

This commit is contained in:
Billy Lo 2021-10-17 19:57:15 -04:00
commit e625cadd00
10 changed files with 137 additions and 61 deletions

View File

@ -9,16 +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 GooglePayButton from '@google-pay/button-react';
import {isChrome, isIOS, isIPad13, isMacOs, isSafari, deviceDetect, osName, osVersion, isAndroid} from 'react-device-detect';
import {isIOS, isMacOs, isSafari, osVersion, getUA, browserName, browserVersion} 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';
import { GPayData } from '../src/gpay';
import Router from 'next/router'
@ -30,9 +25,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);
@ -104,7 +96,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);
@ -138,7 +130,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
@ -157,11 +149,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();
}
@ -178,10 +174,11 @@ function Form(): JSX.Element {
// Hide camera view
async function hideCameraView() {
if (globalControls !== undefined) {
if (globalControls) {
globalControls.stop();
}
setIsCameraOpen(false);
_setFileErrorMessages([]);
}
// Show camera view
@ -195,13 +192,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;
}
@ -213,24 +210,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);
}
@ -447,8 +466,13 @@ function Form(): JSX.Element {
// setAddErrorMessage('Sorry. Apple Wallet pass can be added using Safari or Chrome only.');
// setIsDisabledAppleWallet(true);
// }
if (isIOS && (!osVersion.startsWith('15'))) {
setAddErrorMessage('Sorry, iOS 15+ is needed for the Apple Wallet functionality to work with Smart Health Card')
const uaIsiOS15 = getUA.includes('15_');
if (isIOS && ((!osVersion.startsWith('15')) && !uaIsiOS15)) {
const message = `Not iOS15 error: osVersion=${osVersion} UA=${getUA}`;
console.warn(message);
Sentry.captureMessage(message);
setAddErrorMessage(`Sorry, iOS 15+ is needed for the Apple Wallet functionality to work with Smart Health Card (detected iOS ${osVersion}, browser ${browserName} ${browserVersion})`);
setIsDisabledAppleWallet(true);
return;
}
@ -520,6 +544,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"
@ -530,6 +560,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"

View File

@ -34,7 +34,7 @@ function Page(props: PageProps): JSX.Element {
<a href="https://github.com/billylo1/covidpass" className="underline">{t('common:gitHub')}</a>
<a href="https://vaccine-ontario.ca" className="underline">{t('common:returnToMainSite')}</a>
</nav>
<div className="flex pt-4 flex-row space-x-4 justify-center text-md flex-wrap">Last updated: 2021-10-16 (v2.0.5)</div>
<div className="flex pt-4 flex-row space-x-4 justify-center text-md flex-wrap">Last updated: 2021-10-17 (v2.1.1)</div>
</footer>
</main>
</div>
@ -114,10 +114,31 @@ function Page(props: PageProps): JSX.Element {
</table>
<br/>
<br/>
<div id='shc-card-name' style={{fontSize:20, textAlign: 'center'}}></div>
<div style={{fontSize:14, textAlign: 'center'}}>
<span id='shc-card-name' ></span>&nbsp;&nbsp;&nbsp;&nbsp;(<span id='shc-card-dob'></span>)
</div>
<br/>
<br/>
<table style={{textAlign: "center", width: "100%"}}>
<tbody>
<tr>
<td id='shc-card-vaccine-name-1'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-name-2'></td>
</tr>
<tr>
<td id='shc-card-vaccine-date-1'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-date-2'></td>
</tr>
<tr id='extraRow1' hidden>
<td id='shc-card-vaccine-name-3'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-name-4'></td>
</tr>
<tr id='extraRow2' hidden>
<td id='shc-card-vaccine-date-3'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-date-4'></td>
</tr>
</tbody>
</table>
<div id='shc-card-vaccine' style={{width:'63%', display:'block', marginLeft: 'auto', marginRight: 'auto'}}></div>
<div id='shc-qrcode' style={{width:'63%', display:'block', marginLeft: 'auto', marginRight: 'auto'}}></div>
<br/>
<br/>
</div>

View File

@ -1,6 +1,6 @@
{
"name": "grassroots_covidpass",
"version": "2.0.5",
"version": "2.1.1",
"author": "Billy Lo <billy@vaccine-ontario.ca>",
"license": "MIT",
"private": false,

View File

@ -78,11 +78,12 @@ function Index(): JSX.Element {
<Card content={
<div><p>{t('common:subtitle')}</p><br /><p>{t('common:subtitle2')}</p><br />
<b>{displayPassCount}</b><br/><br/>
<b>MAJOR NEW RELEASE! </b>Oct 15 evening update:
<b>MAJOR NEW RELEASE! </b>Oct 17 afternoon update:
<br />
<br />
<ul className="list-decimal list-outside" style={{ marginLeft: '20px' }}>
<li>You can now import the new enhanced receipt from Ontario onto your Apple or Android devices</li>
<li>You can now scan QR codes directly off of paper or a screen with your camera, eliminating the need to upload PDFs or pictures</li>
<li>Support released for importing Ontario, British Columbia, Alberta, Saskatchewan, Nova Scotia, Québec, Yukon, California, New York, and Louisiana SHC QR codes</li>
<li>Support released for importing QR codes from images as well as from PDFs</li>
<li>Support for creating our previous interim QR codes has been removed - now that the official Ontario QR code is being released and the gap is filled, our QR codes are no longer needed</li>

View File

@ -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
addToGooglePay: Add to Google Pay

View File

@ -1,5 +1,5 @@
export class Constants {
public static NAME = 'Vaccination Receipt'
public static NAME = 'Vaccination'
public static PASS_IDENTIFIER = 'pass.ca.vaccineontario.receipt'
public static TEAM_IDENTIFIER = 'F6X27PB5BH'

View File

@ -147,9 +147,7 @@ export class PassData {
// Update our pass name if this is an SHC pass
if (payload.rawData.length > 0) {
// const newPassTitle = `${Constants.NAME}, ${payload.shcReceipt.cardOrigin}`;
const newPassTitle = `${Constants.NAME}`; // hot patch for production for now... string too long to fit in pass
const newPassTitle = `${Constants.NAME}, ${payload.shcReceipt.cardOrigin}`;
this.logoText = newPassTitle;
this.organizationName = newPassTitle;
this.description = newPassTitle;

View File

@ -102,8 +102,23 @@ export class Photo {
body.hidden = false;
document.getElementById('shc-card-name').innerText = results.payload.shcReceipt.name;
document.getElementById('shc-card-dob').innerText = results.payload.shcReceipt.dateOfBirth;
document.getElementById('shc-card-origin').innerText = results.payload.shcReceipt.cardOrigin;
const vaccinations = results.payload.shcReceipt.vaccinations;
const numDisplay = vaccinations.length <= 4 ? vaccinations.length : 4;
for (let i = 0; i < numDisplay; i++) {
document.getElementById(`shc-card-vaccine-name-${i+1}`).innerText = vaccinations[i].vaccineName;
document.getElementById(`shc-card-vaccine-date-${i+1}`).innerText = vaccinations[i].vaccinationDate;
if (i > 1) {
document.getElementById(`shc-card-vaccine-name-${i+1}`).parentElement.hidden = false;
}
}
const qrcode = new Encoder();
qrcode.setEncodingHint(true);

View File

@ -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);

View File

@ -3,7 +3,7 @@ import { Integrations } from '@sentry/tracing';
export const initSentry = () => {
SentryModule.init({
release: 'grassroots_covidpass@2.0.5', // App version. Needs to be manually updated as we go unless we make the build smarter
release: 'grassroots_covidpass@2.1.1', // App version. Needs to be manually updated as we go unless we make the build smarter
dsn: 'https://7120dcf8548c4c5cb148cdde2ed6a778@o1015766.ingest.sentry.io/5981424',
integrations: [
new Integrations.BrowserTracing(),