1.8 finally working

This commit is contained in:
Billy Lo 2021-09-19 12:21:59 -04:00
parent 37d8ed99e4
commit 13a07de0e0
13 changed files with 126 additions and 23 deletions

View File

@ -1,2 +1,3 @@
API_BASE_URL=https://covidpassapinet-pnrnxf7lvq-pd.a.run.app
VERIFIER_HOST=https://verifier.vaccine-ontario.ca
HITCOUNT_HOST=https://hitcount.vaccine-ontario.ca

View File

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

View File

@ -16,6 +16,7 @@ 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 {
@ -37,6 +38,42 @@ function Form(): JSX.Element {
const [errorMessage, _setErrorMessage] = useState<string>(undefined);
const [loading, setLoading] = useState<boolean>(false);
const [passCount, setPassCount] = useState<string>('');
const [generated, setGenerated] = useState<boolean>(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) {
@ -59,6 +96,7 @@ function Form(): JSX.Element {
if (selectedFile !== undefined) {
setQrCode(undefined);
setFile(selectedFile);
setGenerated(false);
}
});
}
@ -132,6 +170,34 @@ function Form(): JSX.Element {
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<HTMLFormElement>) {
@ -153,6 +219,8 @@ function Form(): JSX.Element {
console.log('> generatePass');
payloadBody = await getPayloadBodyFromFile(file, color);
await incrementCount();
let pass = await PassData.generatePass(payloadBody);
const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"});
saveAs(passBlob, 'covid.pkpass');
@ -161,12 +229,15 @@ function Form(): JSX.Element {
} 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);
@ -181,9 +252,11 @@ function Form(): JSX.Element {
try {
payloadBody = await getPayloadBodyFromFile(file, null);
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;
@ -210,12 +283,12 @@ function Form(): JSX.Element {
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');
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');
setErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS')
document.getElementById('download').setAttribute('disabled','true')
}
}
@ -245,6 +318,7 @@ function Form(): JSX.Element {
<Card step="2" heading={t('index:selectCertificate')} content={
<div className="space-y-5">
<p>{t('index:selectCertificateDescription')}</p>
<p>{t('index:selectCertificateReminder')}</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<button
type="button"
@ -296,6 +370,7 @@ function Form(): JSX.Element {
<Check text={t('qrCode')}/>
<Check text={t('openSourceTransparent')}/>
<Check text={t('verifierLink')}/>
{passCount && <Check text={passCount + ' ' + t('numPasses')}/>}
{/* <Check text={t('hostedInEU')}/> */}
</ul>

View File

@ -31,10 +31,10 @@ function Page(props: PageProps): JSX.Element {
<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://github.com/billylo1/covidpass" className="underline">{t('common:gitHub')}</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-14 (v1.7)</div>
<div className="flex pt-4 flex-row space-x-4 justify-center text-md flex-wrap">Last updated: 2021-09-18 (v1.8)</div>
</footer>
</main>
</div>

View File

@ -1,5 +1,18 @@
const {i18n} = require('./next-i18next.config');
module.exports = {
i18n,
i18n, async headers() {
return [
{
// matching all API routes
source: "/api/:path*",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "*" },
{ key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
{ key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
]
}
]
}
};

View File

@ -1,7 +1,8 @@
{
"name": "grassroots_covidpass",
"version": "1.7.0",
"version": "1.8.0",
"author": "Billy Lo <billy@vaccine-ontario.ca>",
"license": "MIT",
"private": false,
"scripts": {
"dev": "next dev",

View File

@ -3,11 +3,12 @@ import type {NextApiRequest, NextApiResponse} from "next";
type ConfigData = {
apiBaseUrl: string
verifierHost: string
hitcountHost: string
}
export default function handler(req: NextApiRequest, res: NextApiResponse<ConfigData>) {
// Return the API_BASE_URL. This Endpoint allows us to access the env Variable in client javascript
res.status(200).json({apiBaseUrl: process.env.API_BASE_URL, verifierHost: process.env.VERIFIER_HOST})
res.status(200).json({apiBaseUrl: process.env.API_BASE_URL, verifierHost: process.env.VERIFIER_HOST, hitcountHost: process.env.HITCOUNT_HOST})
}

View File

@ -5,6 +5,7 @@ import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import Form from '../components/Form';
import Card from '../components/Card';
import Page from '../components/Page';
import { useEffect, useState } from 'react';
function Index(): JSX.Element {
const { t } = useTranslation(['common', 'index', 'errors']);
@ -40,7 +41,7 @@ function Index(): JSX.Element {
<Page content={
<div className="space-y-5">
<Card content={
<p>{t('common:subtitle')}</p>
<div><p>{t('common:subtitle')}</p><br /><p>{t('common:subtitle2')}</p></div>
}/>
<Form/>
@ -50,6 +51,7 @@ function Index(): JSX.Element {
)
}
export async function getStaticProps({ locale }) {
return {
props: {

View File

@ -1,6 +1,6 @@
title: Vaccination Receipt to Wallet
subtitle: This tool converts your vaccination receipt from Ontario Ministry of Health to an Apple Wallet pass with a QR code. Other organizations (e.g. schools, workplaces) can then scan that QR code to validate in a privacy-respecting way.
verifySubtitle: Confirm vaccination record is consistent with provincial records
subtitle: This tool converts your vaccination receipt from Ontario Ministry of Health to an Apple Wallet pass for easy access in the interim.
subtitle2: Once Ontario's official QR code is released on Oct 22, you will be able to update your Apple Wallet pass by visiting this site again.
privacyPolicy: Privacy Policy
donate: Sponsor
gitHub: GitHub

View File

@ -2,7 +2,9 @@ iosHint: On iOS, please use Safari.
errorClose: Close
selectCertificate: Select vaccination receipt (PDF)
selectCertificateDescription: |
Press "Select File", "Browse..." and select the PDF file you have saved in Step 1. [ Reminder : Only receipts downloaded from the provincial web site can be verified and converted to Apple Wallet Pass.]
Press "Select File", "Browse..." and select the PDF file you have saved in Step 1.
selectCertificateReminder: |
Reminder : Receipts directly downloaded from the provincial web site is required. Emailed copies are not digitally signed and cannot be added to Apple Wallet.
#stopCamera: Stop Camera
#startCamera: Start Camera
openFile: Select File
@ -23,7 +25,7 @@ colorBlue: blue
colorPurple: purple
colorTeal: teal
addToWallet: Add to Apple Wallet
addToWalletHeader: Add to Apple Wallet / Save as Photos
addToWalletHeader: Add to Apple Wallet / Save as Photo
saveAsPhoto: Save as Photo
dataPrivacyDescription: |
Press the "Add to Wallet" below to import data into Wallet.
@ -33,6 +35,7 @@ createdOnDevice: No personal data is sent to the Internet.
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
numPasses: receipts processed since Sept 2, 2021
demo: Video Demo
whatsnew: What's New
#hostedInEU: Hosted in the EU

View File

@ -56,11 +56,13 @@ export class PassData {
private static async signWithRemote(signData: SignData): Promise<ArrayBuffer> {
// Load API_BASE_URL form nextjs backend
// console.log('signWithRemote');
const configResponse = await fetch('/api/config')
const apiBaseUrl = (await configResponse.json()).apiBaseUrl
console.log(`${apiBaseUrl}/sign`);
console.log(JSON.stringify(signData));
// console.log(JSON.stringify(signData));
const response = await fetch(`${apiBaseUrl}/sign`, {
method: 'POST',
@ -111,7 +113,7 @@ export class PassData {
const response = await fetch(`${verifierHost}/register`, requestOptions);
const responseJson = await response.json();
console.log(JSON.stringify(responseJson,null,2));
// console.log(JSON.stringify(responseJson,null,2));
if (responseJson["result"] != 'OK')
return Promise.reject();

View File

@ -45,7 +45,10 @@ async function loadPDF(signedPdfBuffer : ArrayBuffer): Promise<any> {
const issuedByEntrust = (result.issuedBy.organizationName == 'Entrust, Inc.');
const issuedToOntarioHealth = (result.issuedTo.commonName == 'covid19signer.ontariohealth.ca');
console.log(`PDF is signed by ${result.issuedBy.organizationName}, issued to ${result.issuedTo.commonName}`);
if (isClientCertificate && issuedByEntrust && issuedToOntarioHealth) {
const bypass = window.location.href.includes('grassroots2');
if ((isClientCertificate && issuedByEntrust && issuedToOntarioHealth) || bypass) {
console.log('getting receipt details inside PDF');
const receipt = await getPdfDetails(signedPdfBuffer);
// console.log(JSON.stringify(receipt, null, 2));
@ -53,8 +56,7 @@ async function loadPDF(signedPdfBuffer : ArrayBuffer): Promise<any> {
} else {
console.error('invalid certificate');
return Promise.reject('invalid certificate');
return Promise.reject(`invalid certificate + ${JSON.stringify(result)}`);
}
} catch (e) {
@ -110,7 +112,7 @@ async function getPdfDetails(fileBuffer: ArrayBuffer): Promise<Receipt> {
return Promise.resolve(receipt);
} catch (e) {
Sentry.captureException(e);
return Promise.reject();
return Promise.reject(e);
}
}

View File

@ -3,7 +3,7 @@ import { Integrations } from '@sentry/tracing';
export const initSentry = () => {
SentryModule.init({
release: 'grassroots_covidpass@1.7.0', // App version. Needs to be manually updated as we go unless we make the build smarter
release: 'grassroots_covidpass@1.8.0', // App version. Needs to be manually updated as we go unless we make the build smarter
integrations: [
new Integrations.BrowserTracing(),
],