This commit is contained in:
Billy Lo 2021-09-14 20:33:27 -04:00
parent 44dcbfef80
commit 37d8ed99e4
13 changed files with 252 additions and 215 deletions

View File

@ -82,6 +82,10 @@ Finally, the following steps happen locally in your browser:
* Assembling the signed pass file out of the incomplete file generated locally and the signature
* Saving the file on your device
# Logging
* Sentry.io is used. Please put your DSN into your environment variable SENTRY_DSN at runtime to activate it.
# Credits
The idea for this web app originated from the [solution of an Austrian web developer](https://coronapass.fabianpimminger.com), which only works for Austrian certificates at the moment.

View File

@ -1,2 +1,5 @@
docker build . -t covidpass -t gcr.io/broadcast2patients/covidpass
docker push gcr.io/broadcast2patients/covidpass
gcloud config set project broadcast2patients
gcloud config set run/region us-east1
gcloud run deploy covidpass --image gcr.io/broadcast2patients/covidpass:latest --platform managed

View File

@ -14,7 +14,8 @@ import {PassData} from "../src/pass";
import {Photo} from "../src/photo";
import {COLORS} from "../src/colors";
import Colors from './Colors';
import {isIOS, isMacOs, isSafari} from 'react-device-detect';
import {isChrome, isIOS, isIPad13, isMacOs, isSafari, deviceDetect, osName, osVersion} from 'react-device-detect';
import * as Sentry from '@sentry/react';
function Form(): JSX.Element {
@ -61,7 +62,7 @@ function Form(): JSX.Element {
}
});
}
// checkBrowserType();
checkBrowserType();
}, [inputFile])
// Show file Dialog
@ -137,12 +138,6 @@ function Form(): JSX.Element {
event.preventDefault();
setLoading(true);
if (navigator.userAgent.match('CriOS')) {
setErrorMessage('safariSupportOnly');
setLoading(false);
return;
}
if (!file && !qrCode) {
setErrorMessage('noFileOrQrCode')
setLoading(false);
@ -167,6 +162,7 @@ function Form(): JSX.Element {
} catch (e) {
setErrorMessage(e.message);
Sentry.captureException(e);
setLoading(false);
}
}
@ -197,6 +193,7 @@ function Form(): JSX.Element {
setLoading(false);
} catch (e) {
Sentry.captureException(e);
setErrorMessage(e.message);
setLoading(false);
}
@ -204,13 +201,22 @@ function Form(): JSX.Element {
async function checkBrowserType() {
if (isMacOs || (isIOS && isSafari)) {
document.getElementById('download').hidden = false;
} else {
document.getElementById('download').hidden = true;
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 (Your version is ${osVersion})`);
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 (

View File

@ -28,13 +28,13 @@ function Page(props: PageProps): JSX.Element {
<nav className="nav flex pt-4 flex-row space-x-4 justify-center text-md font-bold flex-wrap">
{<Link href="/faq"><a className="underline">{t('common:faq')}</a></Link>}
<a href="https://www.youtube.com/watch?v=AIrG5Qbjptg" className="underline">{t('index:demo')}</a>
<a href="https://twitter.com/grassroots_team" className="underline">{t('index:whatsnew')}</a>
<a href="mailto:grassroots@vaccine-ontario.ca" className="underline">{t('common:contact')}</a>
<a href="https://verifier.vaccine-ontario.ca" className="underline">{t('common:gotoVerifier')}</a>
<a href="https://vaccine-ontario.ca" className="underline">{t('common:returnToMainSite')}</a>
{/* <a href="https://github.com/billylo1/covidpass" className="hover:underline">{t('common:gitHub')}</a> */}
</nav>
<div className="flex pt-4 flex-row space-x-4 justify-center text-md flex-wrap">Last updated: 2021-09-09 (v1.6)</div>
<div className="flex pt-4 flex-row space-x-4 justify-center text-md flex-wrap">Last updated: 2021-09-14 (v1.7)</div>
</footer>
</main>
</div>

7
package-lock.json generated
View File

@ -1,11 +1,12 @@
{
"name": "covidpass",
"version": "0.1.0",
"name": "grassroots_covidpass",
"version": "1.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.1.0",
"name": "grassroots_covidpass",
"version": "1.4.0",
"dependencies": {
"@headlessui/react": "^1.3.0",
"@ninja-labs/verify-pdf": "^0.3.9",

View File

@ -1,8 +1,8 @@
{
"name": "covidpass",
"version": "0.1.0",
"author": "Marvin Sextro <marvin.sextro@gmail.com>",
"private": true,
"name": "grassroots_covidpass",
"version": "1.7.0",
"author": "Billy Lo <billy@vaccine-ontario.ca>",
"private": false,
"scripts": {
"dev": "next dev",
"build": "next build",

View File

@ -7,15 +7,20 @@ import Card from '../components/Card'
function Faq(): JSX.Element {
const { t } = useTranslation(['common', 'index', 'faq']);
const questionList = [
{description: 'Which version of iOS does this support?', answer: 'iOS 14.0 is the minimum at the moment. We are looking for adjustments for older iOSes, but it will take a bit of time.', key: 1},
{description: 'Why have we taken time to build this?', answer: 'Gives Ontarians/organizations something very easy to verify vaccination status.', key: 2},
{description: 'Which version of iOS does this support?', answer: 'iOS 13.7 is the minimum at the moment. We are looking for adjustments for older iOSes, but it will take a bit of time.', key: 1},
{description: 'Why have we taken time to build this?', answer: 'Gives Ontarians/organizations something easy to use (volunteered-developed, unofficial) until the official provincial app comes out in October.', key: 2},
{description: 'Who made this?', answer: 'The same group of volunteers (Billy Lo, Ryan Slobojan, Evert Timberg, Jason Liu, Anujan Mathisekaran, Lisa Discepola, Samantha Finn, Madison Pearce) who created the all-in-one vaccine appointment finding tool at vaccine-ontario.ca.', key: 3},
{description: 'How is the data on my vaccination receipt processed?', answer: 'It checks the receipt for an official signature from the province. If present, the receipt data is converted into Apple\'s format and then added into your iOS Wallet app.', key: 4},
{description: 'How can organizations validate the QR code?', answer: 'Just aim your standard camera app (iPhone/Android) at the code, and it will bring up a web page that shows the verification result.', key: 5},
{description: 'Is this free and private?', answer: 'Similar to VaxHunters, this is not backed by any commerical businesses. Just volunteers trying to do our part to help the community.', key: 6},
{description: 'Should I use the official provincial apps when they come out on 22nd October?', answer: 'YES. This is an extra avenue intended to augment the official efforts.', key: 7},
{description: 'Do you have plans for Android support?', answer: 'Yes. We are working with Google to gain access to the APIs required.', key: 8},
{description: 'I have more questions. Can you help?', answer: 'Sure. Just email us at grassroots@vaccine-ontario.ca.', key: 9}
{description: 'Should I use the official provincial apps when they come out on 22nd October?', answer: 'YES. Once the official QR code from the province is available, you will also be able to refresh what\'s in your Apple Wallet as well. More details will follow.', key: 4},
{description: 'How is the data on my vaccination receipt processed?', answer: 'It checks the receipt for an official signature from the province. If present, the receipt data is converted into Apple\'s format and then added into your iOS Wallet app.', key: 5},
{description: 'How can organizations validate this QR code?', answer: 'Just aim your standard camera app (iPhone/Android) at the code, and it will bring up a web page that shows the verification result.', key: 6},
{description: 'Is this free and private?', answer: 'Similar to VaxHuntersCanada, there are no commerical interests. Just volunteers trying to do our part to help the community.', key: 7},
{description: 'Do you have plans for Android support?', answer: 'Yes. We are working with Google to gain access to the APIs required. Meanwhile, you can also use this tool to download an Apple Wallet pass and import that into Google Pay Wallet using apps such as Pass2Pay.', key: 8},
{description: 'How about BC, Quebec and Alberta?', answer: 'We will be investigating BC shortly. If you are interested in contributing, email us at grassroots@vaccine-ontario.ca', key: 9},
{description: 'How can I stay up-to-date on your progress?', answer: 'We will post regular updates on Twitter @grassroots_team.', key: 10},
{description: 'I only have an emailed receipt (e.g. Red/White OHIP card users). Can I still use this tool?', answer: 'Not right now unfortunately. But we expect to be able to support that after the official app is released on 22 Oct.', key: 11},
{description: 'What does the colour of the Apple Wallet pass mean?', answer: 'Dose 1 is shown as Orange; dose 2+ in green for easy differentiation without reading the text.', key: 12},
{description: 'How about Apple Watch?', answer: 'If you have iCloud sync enabled, you will see the pass on the watch too.', key: 13},
{description: 'I have more questions. Can you please help me?', answer: 'Sure. Just email us at grassroots@vaccine-ontario.ca.', key: 14}
];

View File

@ -34,4 +34,5 @@ qrCode: QR code is for verification only, with no personal info.
openSourceTransparent: Source code is free and open for re-use/contributions on GitHub.
verifierLink: QR code verifier available at https://verifier.vaccine-ontario.ca
demo: Video Demo
whatsnew: What's New
#hostedInEU: Hosted in the EU

View File

@ -3,7 +3,7 @@ import {v4 as uuid4} from 'uuid';
import {Constants} from "./constants";
import {Payload, PayloadBody, PassDictionary} from "./payload";
import {ValueSets} from "./value_sets";
import * as Sentry from '@sentry/react';
const crypto = require('crypto')
@ -81,7 +81,7 @@ export class PassData {
static async generatePass(payloadBody: PayloadBody): Promise<Buffer> {
// Create Payload
try {
const payload: Payload = new Payload(payloadBody);
payload.serialNumber = uuid4();
@ -177,6 +177,10 @@ export class PassData {
zip.push({path: 'signature', data: Buffer.from(manifestSignature)});
return createZip(zip);
} catch (e) {
Sentry.captureException(e);
return Promise.reject();
}
}
private constructor(payload: Payload, qrCode: QrCode) {

View File

@ -3,6 +3,7 @@ import {Payload, PayloadBody} from "./payload";
import {v4 as uuid4} from 'uuid';
import {BrowserQRCodeSvgWriter} from "@zxing/browser";
import { toPng, toJpeg, toBlob, toPixelData, toSvg } from 'html-to-image';
import * as Sentry from '@sentry/react';
enum QrFormat {
PKBarcodeFormatQR = 'PKBarcodeFormatQR',
@ -37,7 +38,7 @@ export class Photo {
static async generatePass(payloadBody: PayloadBody): Promise<Blob> {
// Create Payload
try {
const payload: Payload = new Payload(payloadBody);
payload.serialNumber = uuid4();
@ -109,6 +110,10 @@ export class Photo {
const blobPromise = toBlob(body);
return blobPromise;
} catch (e) {
Sentry.captureException(e);
return Promise.reject();
}
}
private constructor(payload: Payload, qrCode: QrCode) {

View File

@ -59,15 +59,11 @@ async function loadPDF(signedPdfBuffer : ArrayBuffer): Promise<any> {
} catch (e) {
console.error(e);
Sentry.captureException(e);
if (e.message.includes('Failed to locate ByteRange')) {
e.message = 'Sorry. Selected PDF file is not digitally signed. Please download official copy from Step 1 and retry. Thanks.'
}
else if (e.message.includes('arrayBuffer')) {
e.message = 'Sorry. The tool currently requires iOS 14.2+. If possible, please upgrade. We are looking for workarounds, but it will take some time.'
} else {
Sentry.captureException(e);
}
return Promise.reject(e);
}
@ -76,6 +72,7 @@ async function loadPDF(signedPdfBuffer : ArrayBuffer): Promise<any> {
async function getPdfDetails(fileBuffer: ArrayBuffer): Promise<Receipt> {
try {
const typedArray = new Uint8Array(fileBuffer);
let loadingTask = PdfJS.getDocument(typedArray);
@ -111,5 +108,9 @@ async function getPdfDetails(fileBuffer: ArrayBuffer): Promise<Receipt> {
const receipt = new Receipt(name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization);
return Promise.resolve(receipt);
} catch (e) {
Sentry.captureException(e);
return Promise.reject();
}
}

View File

@ -3,10 +3,12 @@ import { Integrations } from '@sentry/tracing';
export const initSentry = () => {
SentryModule.init({
dsn: 'https://eQQAQX2SsudxcM@o997324.ingest.sentry.io/5955697',
release: 'grassroots_covidpass@1.4.0', // App version. Needs to be manually updated as we go unless we make the build smarter
release: 'grassroots_covidpass@1.7.0', // App version. Needs to be manually updated as we go unless we make the build smarter
integrations: [
new Integrations.BrowserTracing(),
],
attachStacktrace: true
});
console.log('sentry initialized');
;};

View File

@ -1456,6 +1456,11 @@
"resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
"version" "1.0.0"
"fsevents@~2.3.1", "fsevents@~2.3.2":
"integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="
"resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
"version" "2.3.2"
"function-bind@^1.1.1":
"integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
"resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"