mirror of
https://github.com/covidpass-org/covidpass.git
synced 2025-02-22 22:47:44 +01:00
1.7
This commit is contained in:
parent
44dcbfef80
commit
37d8ed99e4
@ -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.
|
||||
|
3
build.sh
3
build.sh
@ -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
|
||||
|
@ -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 (
|
||||
|
@ -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
7
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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}
|
||||
];
|
||||
|
||||
|
||||
|
@ -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
|
176
src/pass.ts
176
src/pass.ts
@ -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,102 +81,106 @@ export class PassData {
|
||||
static async generatePass(payloadBody: PayloadBody): Promise<Buffer> {
|
||||
|
||||
// Create Payload
|
||||
try {
|
||||
const payload: Payload = new Payload(payloadBody);
|
||||
|
||||
const payload: Payload = new Payload(payloadBody);
|
||||
payload.serialNumber = uuid4();
|
||||
|
||||
payload.serialNumber = uuid4();
|
||||
// register record
|
||||
|
||||
// register record
|
||||
const clonedReceipt = Object.assign({}, payload.receipt);
|
||||
delete clonedReceipt.name;
|
||||
delete clonedReceipt.dateOfBirth;
|
||||
clonedReceipt["serialNumber"] = payload.serialNumber;
|
||||
clonedReceipt["type"] = 'applewallet';
|
||||
|
||||
const clonedReceipt = Object.assign({}, payload.receipt);
|
||||
delete clonedReceipt.name;
|
||||
delete clonedReceipt.dateOfBirth;
|
||||
clonedReceipt["serialNumber"] = payload.serialNumber;
|
||||
clonedReceipt["type"] = 'applewallet';
|
||||
|
||||
let requestOptions = {
|
||||
method: 'POST', // *GET, POST, PUT, DELETE, etc.
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(clonedReceipt) // body data type must match "Content-Type" header
|
||||
}
|
||||
|
||||
console.log('registering ' + JSON.stringify(clonedReceipt, null, 2));
|
||||
const configResponse = await fetch('/api/config')
|
||||
const verifierHost = (await configResponse.json()).verifierHost
|
||||
|
||||
// const verifierHost = 'https://verifier.vaccine-ontario.ca';
|
||||
|
||||
const response = await fetch(`${verifierHost}/register`, requestOptions);
|
||||
const responseJson = await response.json();
|
||||
|
||||
console.log(JSON.stringify(responseJson,null,2));
|
||||
|
||||
if (responseJson["result"] != 'OK')
|
||||
return Promise.reject();
|
||||
|
||||
// Create QR Code Object
|
||||
const qrCode: QrCode = {
|
||||
message: `${verifierHost}/verify?serialNumber=${payload.serialNumber}&vaccineName=${payload.receipt.vaccineName}&vaccinationDate=${payload.receipt.vaccinationDate}&organization=${payload.receipt.organization}&dose=${payload.receipt.numDoses}`,
|
||||
format: QrFormat.PKBarcodeFormatQR,
|
||||
messageEncoding: Encoding.iso88591,
|
||||
// altText : payload.rawData
|
||||
|
||||
}
|
||||
|
||||
// Create pass data
|
||||
const pass: PassData = new PassData(payload, qrCode);
|
||||
|
||||
// Create new zip
|
||||
const zip = [] as { path: string; data: Buffer | string }[];
|
||||
|
||||
// Adding required fields
|
||||
|
||||
// console.log(pass);
|
||||
|
||||
// Create pass.json
|
||||
const passJson = JSON.stringify(pass);
|
||||
|
||||
// Add pass.json to zip
|
||||
zip.push({path: 'pass.json', data: Buffer.from(passJson)});
|
||||
|
||||
// Add Images to zip
|
||||
zip.push({path: 'icon.png', data: payload.img1x})
|
||||
zip.push({path: 'icon@2x.png', data: payload.img2x})
|
||||
zip.push({path: 'logo.png', data: payload.img1x})
|
||||
zip.push({path: 'logo@2x.png', data: payload.img2x})
|
||||
|
||||
// Adding manifest
|
||||
// Construct manifest
|
||||
const manifestJson = JSON.stringify(
|
||||
zip.reduce(
|
||||
(res, {path, data}) => {
|
||||
res[path] = PassData.getBufferHash(data);
|
||||
return res;
|
||||
let requestOptions = {
|
||||
method: 'POST', // *GET, POST, PUT, DELETE, etc.
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
{},
|
||||
),
|
||||
);
|
||||
body: JSON.stringify(clonedReceipt) // body data type must match "Content-Type" header
|
||||
}
|
||||
|
||||
// console.log(manifestJson);
|
||||
console.log('registering ' + JSON.stringify(clonedReceipt, null, 2));
|
||||
const configResponse = await fetch('/api/config')
|
||||
const verifierHost = (await configResponse.json()).verifierHost
|
||||
|
||||
// Add Manifest JSON to zip
|
||||
zip.push({path: 'manifest.json', data: Buffer.from(manifestJson)});
|
||||
// const verifierHost = 'https://verifier.vaccine-ontario.ca';
|
||||
|
||||
// Create pass hash
|
||||
const passHash = PassData.getBufferHash(Buffer.from(passJson));
|
||||
const response = await fetch(`${verifierHost}/register`, requestOptions);
|
||||
const responseJson = await response.json();
|
||||
|
||||
// Sign hash with server
|
||||
const manifestSignature = await PassData.signWithRemote({
|
||||
PassJsonHash: passHash,
|
||||
useBlackVersion: false,
|
||||
});
|
||||
console.log(JSON.stringify(responseJson,null,2));
|
||||
|
||||
// Add signature to zip
|
||||
zip.push({path: 'signature', data: Buffer.from(manifestSignature)});
|
||||
if (responseJson["result"] != 'OK')
|
||||
return Promise.reject();
|
||||
|
||||
return createZip(zip);
|
||||
// Create QR Code Object
|
||||
const qrCode: QrCode = {
|
||||
message: `${verifierHost}/verify?serialNumber=${payload.serialNumber}&vaccineName=${payload.receipt.vaccineName}&vaccinationDate=${payload.receipt.vaccinationDate}&organization=${payload.receipt.organization}&dose=${payload.receipt.numDoses}`,
|
||||
format: QrFormat.PKBarcodeFormatQR,
|
||||
messageEncoding: Encoding.iso88591,
|
||||
// altText : payload.rawData
|
||||
|
||||
}
|
||||
|
||||
// Create pass data
|
||||
const pass: PassData = new PassData(payload, qrCode);
|
||||
|
||||
// Create new zip
|
||||
const zip = [] as { path: string; data: Buffer | string }[];
|
||||
|
||||
// Adding required fields
|
||||
|
||||
// console.log(pass);
|
||||
|
||||
// Create pass.json
|
||||
const passJson = JSON.stringify(pass);
|
||||
|
||||
// Add pass.json to zip
|
||||
zip.push({path: 'pass.json', data: Buffer.from(passJson)});
|
||||
|
||||
// Add Images to zip
|
||||
zip.push({path: 'icon.png', data: payload.img1x})
|
||||
zip.push({path: 'icon@2x.png', data: payload.img2x})
|
||||
zip.push({path: 'logo.png', data: payload.img1x})
|
||||
zip.push({path: 'logo@2x.png', data: payload.img2x})
|
||||
|
||||
// Adding manifest
|
||||
// Construct manifest
|
||||
const manifestJson = JSON.stringify(
|
||||
zip.reduce(
|
||||
(res, {path, data}) => {
|
||||
res[path] = PassData.getBufferHash(data);
|
||||
return res;
|
||||
},
|
||||
{},
|
||||
),
|
||||
);
|
||||
|
||||
// console.log(manifestJson);
|
||||
|
||||
// Add Manifest JSON to zip
|
||||
zip.push({path: 'manifest.json', data: Buffer.from(manifestJson)});
|
||||
|
||||
// Create pass hash
|
||||
const passHash = PassData.getBufferHash(Buffer.from(passJson));
|
||||
|
||||
// Sign hash with server
|
||||
const manifestSignature = await PassData.signWithRemote({
|
||||
PassJsonHash: passHash,
|
||||
useBlackVersion: false,
|
||||
});
|
||||
|
||||
// Add signature to zip
|
||||
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) {
|
||||
|
125
src/photo.ts
125
src/photo.ts
@ -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,78 +38,82 @@ export class Photo {
|
||||
static async generatePass(payloadBody: PayloadBody): Promise<Blob> {
|
||||
|
||||
// Create Payload
|
||||
try {
|
||||
const payload: Payload = new Payload(payloadBody);
|
||||
|
||||
const payload: Payload = new Payload(payloadBody);
|
||||
payload.serialNumber = uuid4();
|
||||
|
||||
payload.serialNumber = uuid4();
|
||||
// register record
|
||||
|
||||
// register record
|
||||
const clonedReceipt = Object.assign({}, payload.receipt);
|
||||
delete clonedReceipt.name;
|
||||
delete clonedReceipt.dateOfBirth;
|
||||
clonedReceipt["serialNumber"] = payload.serialNumber;
|
||||
clonedReceipt["type"] = 'photo';
|
||||
|
||||
const clonedReceipt = Object.assign({}, payload.receipt);
|
||||
delete clonedReceipt.name;
|
||||
delete clonedReceipt.dateOfBirth;
|
||||
clonedReceipt["serialNumber"] = payload.serialNumber;
|
||||
clonedReceipt["type"] = 'photo';
|
||||
let requestOptions = {
|
||||
method: 'POST', // *GET, POST, PUT, DELETE, etc.
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(clonedReceipt) // body data type must match "Content-Type" header
|
||||
}
|
||||
|
||||
let requestOptions = {
|
||||
method: 'POST', // *GET, POST, PUT, DELETE, etc.
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(clonedReceipt) // body data type must match "Content-Type" header
|
||||
}
|
||||
console.log('registering ' + JSON.stringify(clonedReceipt, null, 2));
|
||||
const configResponse = await fetch('/api/config')
|
||||
const verifierHost = (await configResponse.json()).verifierHost
|
||||
|
||||
console.log('registering ' + JSON.stringify(clonedReceipt, null, 2));
|
||||
const configResponse = await fetch('/api/config')
|
||||
const verifierHost = (await configResponse.json()).verifierHost
|
||||
// const verifierHost = 'https://verifier.vaccine-ontario.ca';
|
||||
|
||||
// const verifierHost = 'https://verifier.vaccine-ontario.ca';
|
||||
const response = await fetch(`${verifierHost}/register`, requestOptions);
|
||||
const responseJson = await response.json();
|
||||
|
||||
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();
|
||||
|
||||
if (responseJson["result"] != 'OK')
|
||||
// Create QR Code Object
|
||||
const qrCode: QrCode = {
|
||||
message: `${verifierHost}/verify?serialNumber=${payload.serialNumber}&vaccineName=${payload.receipt.vaccineName}&vaccinationDate=${payload.receipt.vaccinationDate}&organization=${payload.receipt.organization}&dose=${payload.receipt.numDoses}`,
|
||||
format: QrFormat.PKBarcodeFormatQR,
|
||||
messageEncoding: Encoding.iso88591,
|
||||
// altText : payload.rawData
|
||||
|
||||
}
|
||||
|
||||
// Create photo
|
||||
// const photo: Photo = new Photo(payload, qrCode);
|
||||
|
||||
// const body = domTree.getElementById('main');
|
||||
const body = document.getElementById('pass-image');
|
||||
body.hidden = false;
|
||||
|
||||
if (payload.receipt.numDoses > 1)
|
||||
body.style.backgroundColor = 'green';
|
||||
else
|
||||
body.style.backgroundColor = 'orangered';
|
||||
|
||||
const vaccineName = payload.receipt.vaccineName;
|
||||
const vaccineNameProper = vaccineName.charAt(0) + vaccineName.substr(1).toLowerCase();
|
||||
const doseVaccine = "Dose " + String(payload.receipt.numDoses) + ": " + vaccineNameProper;
|
||||
|
||||
document.getElementById('vaccineName').innerText = doseVaccine;
|
||||
document.getElementById('vaccinationDate').innerText = payload.receipt.vaccinationDate;
|
||||
document.getElementById('organization').innerText = payload.receipt.organization;
|
||||
document.getElementById('name').innerText = payload.receipt.name;
|
||||
|
||||
const codeWriter = new BrowserQRCodeSvgWriter();
|
||||
const svg = codeWriter.write(qrCode.message,200,200);
|
||||
svg.setAttribute('style','background-color: white');
|
||||
document.getElementById('qrcode').appendChild(svg);
|
||||
|
||||
const blobPromise = toBlob(body);
|
||||
return blobPromise;
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
return Promise.reject();
|
||||
|
||||
// Create QR Code Object
|
||||
const qrCode: QrCode = {
|
||||
message: `${verifierHost}/verify?serialNumber=${payload.serialNumber}&vaccineName=${payload.receipt.vaccineName}&vaccinationDate=${payload.receipt.vaccinationDate}&organization=${payload.receipt.organization}&dose=${payload.receipt.numDoses}`,
|
||||
format: QrFormat.PKBarcodeFormatQR,
|
||||
messageEncoding: Encoding.iso88591,
|
||||
// altText : payload.rawData
|
||||
|
||||
}
|
||||
|
||||
// Create photo
|
||||
// const photo: Photo = new Photo(payload, qrCode);
|
||||
|
||||
// const body = domTree.getElementById('main');
|
||||
const body = document.getElementById('pass-image');
|
||||
body.hidden = false;
|
||||
|
||||
if (payload.receipt.numDoses > 1)
|
||||
body.style.backgroundColor = 'green';
|
||||
else
|
||||
body.style.backgroundColor = 'orangered';
|
||||
|
||||
const vaccineName = payload.receipt.vaccineName;
|
||||
const vaccineNameProper = vaccineName.charAt(0) + vaccineName.substr(1).toLowerCase();
|
||||
const doseVaccine = "Dose " + String(payload.receipt.numDoses) + ": " + vaccineNameProper;
|
||||
|
||||
document.getElementById('vaccineName').innerText = doseVaccine;
|
||||
document.getElementById('vaccinationDate').innerText = payload.receipt.vaccinationDate;
|
||||
document.getElementById('organization').innerText = payload.receipt.organization;
|
||||
document.getElementById('name').innerText = payload.receipt.name;
|
||||
|
||||
const codeWriter = new BrowserQRCodeSvgWriter();
|
||||
const svg = codeWriter.write(qrCode.message,200,200);
|
||||
svg.setAttribute('style','background-color: white');
|
||||
document.getElementById('qrcode').appendChild(svg);
|
||||
|
||||
const blobPromise = toBlob(body);
|
||||
return blobPromise;
|
||||
}
|
||||
|
||||
private constructor(payload: Payload, qrCode: QrCode) {
|
||||
|
@ -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,40 +72,45 @@ async function loadPDF(signedPdfBuffer : ArrayBuffer): Promise<any> {
|
||||
|
||||
async function getPdfDetails(fileBuffer: ArrayBuffer): Promise<Receipt> {
|
||||
|
||||
const typedArray = new Uint8Array(fileBuffer);
|
||||
let loadingTask = PdfJS.getDocument(typedArray);
|
||||
try {
|
||||
const typedArray = new Uint8Array(fileBuffer);
|
||||
let loadingTask = PdfJS.getDocument(typedArray);
|
||||
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
// Load last PDF page
|
||||
const pageNumber = pdfDocument.numPages;
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
// Load last PDF page
|
||||
const pageNumber = pdfDocument.numPages;
|
||||
|
||||
const pdfPage = await pdfDocument.getPage(pageNumber);
|
||||
const content = await pdfPage.getTextContent();
|
||||
const numItems = content.items.length;
|
||||
let name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization;
|
||||
const pdfPage = await pdfDocument.getPage(pageNumber);
|
||||
const content = await pdfPage.getTextContent();
|
||||
const numItems = content.items.length;
|
||||
let name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization;
|
||||
|
||||
for (let i = 0; i < numItems; i++) {
|
||||
let item = content.items[i] as TextItem;
|
||||
const value = item.str;
|
||||
if (value.includes('Name / Nom'))
|
||||
name = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('Date:')) {
|
||||
vaccinationDate = (content.items[i+1] as TextItem).str;
|
||||
vaccinationDate = vaccinationDate.split(',')[0];
|
||||
for (let i = 0; i < numItems; i++) {
|
||||
let item = content.items[i] as TextItem;
|
||||
const value = item.str;
|
||||
if (value.includes('Name / Nom'))
|
||||
name = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('Date:')) {
|
||||
vaccinationDate = (content.items[i+1] as TextItem).str;
|
||||
vaccinationDate = vaccinationDate.split(',')[0];
|
||||
}
|
||||
if (value.includes('Product name')) {
|
||||
vaccineName = (content.items[i+1] as TextItem).str;
|
||||
vaccineName = vaccineName.split(' ')[0];
|
||||
}
|
||||
if (value.includes('Date of birth'))
|
||||
dateOfBirth = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('Authorized organization'))
|
||||
organization = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('You have received'))
|
||||
numDoses = Number(value.split(' ')[3]);
|
||||
}
|
||||
if (value.includes('Product name')) {
|
||||
vaccineName = (content.items[i+1] as TextItem).str;
|
||||
vaccineName = vaccineName.split(' ')[0];
|
||||
}
|
||||
if (value.includes('Date of birth'))
|
||||
dateOfBirth = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('Authorized organization'))
|
||||
organization = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('You have received'))
|
||||
numDoses = Number(value.split(' ')[3]);
|
||||
}
|
||||
const receipt = new Receipt(name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization);
|
||||
const receipt = new Receipt(name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization);
|
||||
|
||||
return Promise.resolve(receipt);
|
||||
return Promise.resolve(receipt);
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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');
|
||||
|
||||
;};
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user