Merge pull request #30 from billylo1/dynamic-receipt-handling

new receipt handling
This commit is contained in:
Ryan Slobojan 2021-09-29 11:56:47 -04:00 committed by GitHub
commit 6fa67cd1e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 195 additions and 102 deletions

View File

@ -30,31 +30,48 @@ function Form(): JSX.Element {
// Currently selected color // Currently selected color
const [selectedColor, setSelectedColor] = useState<COLORS>(COLORS.WHITE); const [selectedColor, setSelectedColor] = useState<COLORS>(COLORS.WHITE);
// Currently selected dose
const [selectedDose, setSelectedDose] = useState<number>(2);
// Global camera controls // Global camera controls
const [globalControls, setGlobalControls] = useState<IScannerControls>(undefined); const [globalControls, setGlobalControls] = useState<IScannerControls>(undefined);
// Currently selected QR Code / File. Only one of them is set. // Currently selected QR Code / File. Only one of them is set.
const [qrCode, setQrCode] = useState<Result>(undefined); const [qrCode, setQrCode] = useState<Result>(undefined);
const [file, setFile] = useState<File>(undefined); const [file, setFile] = useState<File>(undefined);
const [payloadBody, setPayloadBody] = useState<PayloadBody>(undefined);
const [loading, setLoading] = useState<boolean>(false); const [saveLoading, setSaveLoading] = useState<boolean>(false);
const [fileLoading, setFileLoading] = useState<boolean>(false);
const [generated, setGenerated] = useState<boolean>(false); // this flag represents the file has been used to generate a pass const [generated, setGenerated] = useState<boolean>(false); // this flag represents the file has been used to generate a pass
const [isDisabledAppleWallet, setIsDisabledAppleWallet] = useState<boolean>(false); const [isDisabledAppleWallet, setIsDisabledAppleWallet] = useState<boolean>(false);
const [errorMessages, _setErrorMessages] = useState<Array<string>>([]); const [addErrorMessages, _setAddErrorMessages] = useState<Array<string>>([]);
const [fileErrorMessages, _setFileErrorMessages] = useState<Array<string>>([]);
const [showDoseOption, setShowDoseOption] = useState<boolean>(false);
// const [warningMessages, _setWarningMessages] = useState<Array<string>>([]); // const [warningMessages, _setWarningMessages] = useState<Array<string>>([]);
const hitcountHost = 'https://stats.vaccine-ontario.ca'; const hitcountHost = 'https://stats.vaccine-ontario.ca';
// Check if there is a translation and replace message accordingly // Check if there is a translation and replace message accordingly
const setErrorMessage = (message: string) => { const setAddErrorMessage = (message: string) => {
if (!message) { if (!message) {
return; return;
} }
const translation = t('errors:'.concat(message)); const translation = t('errors:'.concat(message));
_setErrorMessages(Array.from(new Set([...errorMessages, translation !== message ? translation : message]))); _setAddErrorMessages(Array.from(new Set([...addErrorMessages, translation !== message ? translation : message])));
};
const setFileErrorMessage = (message: string) => {
if (!message) {
return;
}
const translation = t('errors:'.concat(message));
_setFileErrorMessages(Array.from(new Set([...addErrorMessages, translation !== message ? translation : message])));
}; };
// const setWarningMessage = (message: string) => { // const setWarningMessage = (message: string) => {
@ -66,9 +83,11 @@ function Form(): JSX.Element {
// _setWarningMessages(Array.from(new Set([...warningMessages, translation !== message ? translation : message]))); // _setWarningMessages(Array.from(new Set([...warningMessages, translation !== message ? translation : message])));
// } // }
const deleteErrorMessage = (message: string) =>{ const deleteAddErrorMessage = (message: string) =>{
console.log(errorMessages) _setAddErrorMessages(addErrorMessages.filter(item => item !== message))
_setErrorMessages(errorMessages.filter(item => item !== message)) }
const deleteFileErrorMessage = (message: string) =>{
_setFileErrorMessages(addErrorMessages.filter(item => item !== message))
} }
// File Input ref // File Input ref
@ -80,17 +99,55 @@ function Form(): JSX.Element {
inputFile.current.addEventListener('input', () => { inputFile.current.addEventListener('input', () => {
let selectedFile = inputFile.current.files[0]; let selectedFile = inputFile.current.files[0];
if (selectedFile !== undefined) { if (selectedFile !== undefined) {
setFileLoading(true);
setQrCode(undefined); setQrCode(undefined);
setFile(selectedFile); setPayloadBody(undefined);
setFile(undefined);
setShowDoseOption(false);
setGenerated(false); setGenerated(false);
deleteErrorMessage(t('errors:'.concat('noFileOrQrCode'))); deleteAddErrorMessage(t('errors:'.concat('noFileOrQrCode')));
_setFileErrorMessages([]);
checkBrowserType(); checkBrowserType();
getPayload(selectedFile);
} }
}); });
} }
checkBrowserType(); checkBrowserType();
}, [inputFile]) }, [inputFile])
async function getPayload(file){
try {
const payload = await getPayloadBodyFromFile(file, COLORS.GREEN);
setPayloadBody(payload);
setFileLoading(false);
setFile(file);
if (Object.keys(payload.receipts).length === 1) {
setSelectedDose(parseInt(Object.keys(payload.receipts)[0]));
}else{
setShowDoseOption(true);
}
} catch (e) {
setFile(file);
setFileLoading(false);
if (e != undefined) {
console.error(e);
Sentry.captureException(e);
if (e.message != undefined) {
setFileErrorMessage(e.message);
} else {
setFileErrorMessage("Unable to continue.");
}
} else {
setFileErrorMessage("Unexpected error. Sorry.");
}
}
}
// Show file Dialog // Show file Dialog
async function showFileDialog() { async function showFileDialog() {
inputFile.current.click(); inputFile.current.click();
@ -124,13 +181,13 @@ function Form(): JSX.Element {
try { try {
deviceList = await BrowserQRCodeReader.listVideoInputDevices(); deviceList = await BrowserQRCodeReader.listVideoInputDevices();
} catch (e) { } catch (e) {
setErrorMessage('noCameraAccess'); setAddErrorMessage('noCameraAccess');
return; return;
} }
// Check if camera device is present // Check if camera device is present
if (deviceList.length == 0) { if (deviceList.length == 0) {
setErrorMessage("noCameraFound"); setAddErrorMessage("noCameraFound");
return; return;
} }
@ -154,7 +211,7 @@ function Form(): JSX.Element {
setIsCameraOpen(false); setIsCameraOpen(false);
} }
if (error !== undefined) { if (error !== undefined) {
setErrorMessage(error.message); setAddErrorMessage(error.message);
} }
} }
) )
@ -186,40 +243,33 @@ function Form(): JSX.Element {
async function addToWallet(event: FormEvent<HTMLFormElement>) { async function addToWallet(event: FormEvent<HTMLFormElement>) {
event.preventDefault(); event.preventDefault();
setLoading(true); setSaveLoading(true);
if (!file && !qrCode) { if (!file && !qrCode) {
setErrorMessage('noFileOrQrCode') setAddErrorMessage('noFileOrQrCode')
setLoading(false); setSaveLoading(false);
return; return;
} }
const color = selectedColor;
let payloadBody: PayloadBody;
try { try {
if (file) { if (payloadBody) {
const passName = payloadBody.receipts[selectedDose].name.replace(' ', '-');
//console.log('> get payload'); const vaxName = payloadBody.receipts[selectedDose].vaccineName.replace(' ', '-');
payloadBody = await getPayloadBodyFromFile(file, color); const passDose = payloadBody.receipts[selectedDose].numDoses;
const passName = payloadBody.receipt.name.replace(' ', '-');
const vaxName = payloadBody.receipt.vaccineName.replace(' ', '-');
const passDose = payloadBody.receipt.numDoses;
const covidPassFilename = `grassroots-receipt-${passName}-${vaxName}-${passDose}.pkpass`; const covidPassFilename = `grassroots-receipt-${passName}-${vaxName}-${passDose}.pkpass`;
//console.log('> increment count'); //console.log('> increment count');
await incrementCount(); await incrementCount();
//console.log('> generatePass'); // console.log('> generatePass');
let pass = await PassData.generatePass(payloadBody); const pass = await PassData.generatePass(payloadBody, selectedDose);
//console.log('> create blob'); //console.log('> create blob');
const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"}); const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"});
//console.log(`> save blob as ${covidPassFilename}`); //console.log(`> save blob as ${covidPassFilename}`);
saveAs(passBlob, covidPassFilename); saveAs(passBlob, covidPassFilename);
setLoading(false); setSaveLoading(false);
} }
@ -231,16 +281,16 @@ function Form(): JSX.Element {
Sentry.captureException(e); Sentry.captureException(e);
if (e.message != undefined) { if (e.message != undefined) {
setErrorMessage(e.message); setAddErrorMessage(e.message);
} else { } else {
setErrorMessage("Unable to continue."); setAddErrorMessage("Unable to continue.");
} }
} else { } else {
setErrorMessage("Unexpected error. Sorry."); setAddErrorMessage("Unexpected error. Sorry.");
} }
setLoading(false); setSaveLoading(false);
} }
} }
@ -249,21 +299,17 @@ function Form(): JSX.Element {
async function saveAsPhoto() { async function saveAsPhoto() {
setLoading(true); setSaveLoading(true);
if (!file && !qrCode) { if (!file && !qrCode) {
setErrorMessage('noFileOrQrCode'); setAddErrorMessage('noFileOrQrCode');
setLoading(false); setSaveLoading(false);
return; return;
} }
let payloadBody: PayloadBody;
try { try {
payloadBody = await getPayloadBodyFromFile(file, COLORS.GREEN);
await incrementCount(); await incrementCount();
let photoBlob = await Photo.generatePass(payloadBody, selectedDose);
let photoBlob = await Photo.generatePass(payloadBody);
saveAs(photoBlob, 'pass.png'); saveAs(photoBlob, 'pass.png');
// need to clean up // need to clean up
@ -273,11 +319,11 @@ function Form(): JSX.Element {
const body = document.getElementById('pass-image'); const body = document.getElementById('pass-image');
body.hidden = true; body.hidden = true;
setLoading(false); setSaveLoading(false);
} catch (e) { } catch (e) {
Sentry.captureException(e); Sentry.captureException(e);
setErrorMessage(e.message); setAddErrorMessage(e.message);
setLoading(false); setSaveLoading(false);
} }
} }
const verifierLink = () => <li className="flex flex-row items-center"> const verifierLink = () => <li className="flex flex-row items-center">
@ -292,23 +338,27 @@ function Form(): JSX.Element {
</p> </p>
</li> </li>
const setDose = (e) => {
setSelectedDose(e.target.value);
}
function checkBrowserType() { function checkBrowserType() {
// if (isIPad13) { // if (isIPad13) {
// setErrorMessage('Sorry. Apple does not support the use of Wallet on iPad. Please use iPhone/Safari.'); // setAddErrorMessage('Sorry. Apple does not support the use of Wallet on iPad. Please use iPhone/Safari.');
// setIsDisabledAppleWallet(true); // setIsDisabledAppleWallet(true);
// } // }
// if (!isSafari && !isChrome) { // if (!isSafari && !isChrome) {
// setErrorMessage('Sorry. Apple Wallet pass can be added using Safari or Chrome only.'); // setAddErrorMessage('Sorry. Apple Wallet pass can be added using Safari or Chrome only.');
// setIsDisabledAppleWallet(true); // setIsDisabledAppleWallet(true);
// } // }
// if (isIOS && (!osVersion.includes('13') && !osVersion.includes('14') && !osVersion.includes('15'))) { // if (isIOS && (!osVersion.includes('13') && !osVersion.includes('14') && !osVersion.includes('15'))) {
// setErrorMessage('Sorry, iOS 13+ is needed for the Apple Wallet functionality to work') // setAddErrorMessage('Sorry, iOS 13+ is needed for the Apple Wallet functionality to work')
// setIsDisabledAppleWallet(true); // setIsDisabledAppleWallet(true);
// } // }
if (isIOS && !isSafari) { if (isIOS && !isSafari) {
// setErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS'); // setAddErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS');
setErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS'); setAddErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS');
setIsDisabledAppleWallet(true); setIsDisabledAppleWallet(true);
console.log('not safari') console.log('not safari')
} }
@ -346,13 +396,21 @@ function Form(): JSX.Element {
<Card step="2" heading={t('index:selectCertificate')} content={ <Card step="2" heading={t('index:selectCertificate')} content={
<div className="space-y-5"> <div className="space-y-5">
<p>{t('index:selectCertificateDescription')}</p> <p>{t('index:selectCertificateDescription')}</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5"> <div className="grid grid-cols-1 md:grid-cols-2 gap-5 items-center justify-start">
<button <button
type="button" type="button"
onClick={showFileDialog} onClick={showFileDialog}
className="focus:outline-none h-20 bg-green-600 hover:bg-gray-700 text-white font-semibold rounded-md"> className="focus:outline-none h-20 bg-green-600 hover:bg-gray-700 text-white font-semibold rounded-md">
{t('index:openFile')} {t('index:openFile')}
</button> </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"
strokeWidth="4"/>
<path className="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
</svg>
</div>
</div> </div>
<input type='file' <input type='file'
@ -378,10 +436,37 @@ function Form(): JSX.Element {
</span> </span>
</div> </div>
} }
{fileErrorMessages.map((message, i) =>
<Alert message={message} key={'error-' + i} type="error" />
)}
</div> </div>
}/> }/>
<Card step="3" heading={t('index:addToWalletHeader')} content={ {showDoseOption && <Card step="3" heading={'Choose dose number'} content={
<div className="space-y-5">
<p>
{t('index:formatChange')}
<br /><br />
{t('index:saveMultiple')}
</p>
<link href="https://cdn.jsdelivr.net/npm/@tailwindcss/custom-forms@0.2.1/dist/custom-forms.css" rel="stylesheet"/>
<div className="block">
<div className="mt-2">
{payloadBody && Object.keys(payloadBody.receipts).map(key =>
<div key={key}>
<label className="inline-flex items-center">
<input onChange={setDose} type="radio" className="form-radio" name="radio" value={key} checked={parseInt(key) == selectedDose} />
<span className="ml-2">Dose {key}</span>
</label>
</div>
)}
</div>
</div>
</div>
} />}
<Card step={showDoseOption ? '4' : '3'} heading={t('index:addToWalletHeader')} content={
<div className="space-y-5"> <div className="space-y-5">
{/* <p> {/* <p>
{t('index:dataPrivacyDescription')} {t('index:dataPrivacyDescription')}
@ -401,17 +486,17 @@ function Form(): JSX.Element {
</div> </div>
<div className="flex flex-row items-center justify-start"> <div className="flex flex-row items-center justify-start">
<button disabled={isDisabledAppleWallet || loading} id="download" type="submit" value='applewallet' name='action' <button disabled={isDisabledAppleWallet || saveLoading ||!payloadBody} id="download" type="submit" value='applewallet' name='action'
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400"> className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
{t('index:addToWallet')} {t('index:addToWallet')}
</button> </button>
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
<button id="saveAsPhoto" type="button" disabled={loading} value='photo' name='action' onClick={saveAsPhoto} <button id="saveAsPhoto" type="button" disabled={saveLoading || !payloadBody} value='photo' name='action' onClick={saveAsPhoto}
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400"> className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
{t('index:saveAsPhoto')} {t('index:saveAsPhoto')}
</button> </button>
<div id="spin" className={loading ? undefined : "hidden"}> <div id="spin" className={saveLoading ? undefined : "hidden"}>
<svg className="animate-spin h-5 w-5 ml-4" viewBox="0 0 24 24"> <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" <circle className="opacity-0" cx="12" cy="12" r="10" stroke="currentColor"
strokeWidth="4"/> strokeWidth="4"/>
@ -420,7 +505,7 @@ function Form(): JSX.Element {
</svg> </svg>
</div> </div>
</div> </div>
{errorMessages.map((message, i) => {addErrorMessages.map((message, i) =>
<Alert message={message} key={'error-' + i} type="error" /> <Alert message={message} key={'error-' + i} type="error" />
)} )}
{/* {warningMessages.map((message, i) => {/* {warningMessages.map((message, i) =>

View File

@ -15,6 +15,8 @@ ontarioHealth: Ontario Ministry of Health
gotoOntarioHealth: Go to 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. 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.
reminderNotToRepeat: If you have completed this step before, simply proceed to Step 2. reminderNotToRepeat: If you have completed this step before, simply proceed to Step 2.
formatChange: After the recent vaccination receipt formatting change, both doses are included in the same file. Please select which dose you which dose you want to save.
saveMultiple: To save multiple receipts, please select the first one you want to save and click the Wallet or Photo button below, then change which dose is selected here and push the button again to generate another Wallet or Photo for another dose.
pickColor: Pick a Color pickColor: Pick a Color
pickColorDescription: Pick a background color for your pass. pickColorDescription: Pick a background color for your pass.
colorWhite: white colorWhite: white

View File

@ -81,11 +81,11 @@ export class PassData {
return await response.arrayBuffer() return await response.arrayBuffer()
} }
static async generatePass(payloadBody: PayloadBody): Promise<Buffer> { static async generatePass(payloadBody: PayloadBody, numDose: number): Promise<Buffer> {
// Create Payload // Create Payload
try { try {
const payload: Payload = new Payload(payloadBody); const payload: Payload = new Payload(payloadBody, numDose);
payload.serialNumber = uuid4(); payload.serialNumber = uuid4();

View File

@ -4,6 +4,9 @@ import {COLORS} from "./colors";
export class Receipt { export class Receipt {
constructor(public name: string, public vaccinationDate: string, public vaccineName: string, public dateOfBirth: string, public numDoses: number, public organization: string) {}; constructor(public name: string, public vaccinationDate: string, public vaccineName: string, public dateOfBirth: string, public numDoses: number, public organization: string) {};
} }
export interface HashTable<T> {
[key: string]: T;
}
enum TextAlignment { enum TextAlignment {
right = 'PKTextAlignmentRight', right = 'PKTextAlignmentRight',
@ -28,7 +31,7 @@ export interface PassDictionary {
export interface PayloadBody { export interface PayloadBody {
// color: COLORS; // color: COLORS;
rawData: string; rawData: string;
receipt: Receipt; receipts: HashTable<Receipt>;
} }
export class Payload { export class Payload {
@ -43,12 +46,12 @@ export class Payload {
serialNumber: string; serialNumber: string;
generic: PassDictionary; generic: PassDictionary;
constructor(body: PayloadBody) { constructor(body: PayloadBody, numDose: number) {
// Get name and date of birth information // Get name and date of birth information
const name = body.receipt.name; const name = body.receipts[numDose].name;
const dateOfBirth = body.receipt.dateOfBirth; const dateOfBirth = body.receipts[numDose].dateOfBirth;
const vaccineName = body.receipt.vaccineName; const vaccineName = body.receipts[numDose].vaccineName;
let vaccineNameProper = vaccineName.charAt(0) + vaccineName.substr(1).toLowerCase(); let vaccineNameProper = vaccineName.charAt(0) + vaccineName.substr(1).toLowerCase();
if (vaccineName.includes('PFIZER')) if (vaccineName.includes('PFIZER'))
@ -61,7 +64,7 @@ export class Payload {
if (vaccineName.includes('ASTRAZENECA')) if (vaccineName.includes('ASTRAZENECA'))
vaccineNameProper = 'AstraZeneca (Vaxzevria)' vaccineNameProper = 'AstraZeneca (Vaxzevria)'
let doseVaccine = "#" + String(body.receipt.numDoses) + ": " + vaccineNameProper; let doseVaccine = "#" + String(body.receipts[numDose].numDoses) + ": " + vaccineNameProper;
if (name == undefined) { if (name == undefined) {
throw new Error('nameMissing'); throw new Error('nameMissing');
@ -85,13 +88,13 @@ export class Payload {
{ {
key: "issuer", key: "issuer",
label: "Authorized Organization", label: "Authorized Organization",
value: body.receipt.organization value: body.receipts[numDose].organization
}, },
{ {
key: "dov", key: "dov",
label: "Date", label: "Date",
value: body.receipt.vaccinationDate, value: body.receipts[numDose].vaccinationDate,
// textAlignment: TextAlignment.right // textAlignment: TextAlignment.right
} }
], ],
@ -115,10 +118,10 @@ export class Payload {
} }
// Set Values // Set Values
this.receipt = body.receipt; this.receipt = body.receipts[numDose];
this.rawData = body.rawData; this.rawData = body.rawData;
if (body.receipt.numDoses > 1 || body.receipt.vaccineName.toLowerCase().includes('janssen') || body.receipt.vaccineName.toLowerCase().includes('johnson') || body.receipt.vaccineName.toLowerCase().includes('j&j')) { if (body.receipts[numDose].numDoses > 1 || body.receipts[numDose].vaccineName.toLowerCase().includes('janssen') || body.receipts[numDose].vaccineName.toLowerCase().includes('johnson') || body.receipts[numDose].vaccineName.toLowerCase().includes('j&j')) {
this.backgroundColor = COLORS.GREEN; this.backgroundColor = COLORS.GREEN;
} else { } else {
this.backgroundColor = COLORS.YELLOW; this.backgroundColor = COLORS.YELLOW;

View File

@ -35,11 +35,11 @@ export class Photo {
static async generatePass(payloadBody: PayloadBody): Promise<Blob> { static async generatePass(payloadBody: PayloadBody, numDose: number): Promise<Blob> {
// Create Payload // Create Payload
try { try {
const payload: Payload = new Payload(payloadBody); const payload: Payload = new Payload(payloadBody, numDose);
payload.serialNumber = uuid4(); payload.serialNumber = uuid4();

View File

@ -1,4 +1,4 @@
import {PayloadBody, Receipt} from "./payload"; import {PayloadBody, Receipt, HashTable} from "./payload";
import * as PdfJS from 'pdfjs-dist' import * as PdfJS from 'pdfjs-dist'
import {COLORS} from "./colors"; import {COLORS} from "./colors";
import { getCertificatesInfoFromPDF } from "@ninja-labs/verify-pdf"; // ES6 import { getCertificatesInfoFromPDF } from "@ninja-labs/verify-pdf"; // ES6
@ -16,11 +16,11 @@ export async function getPayloadBodyFromFile(file: File, color: COLORS): Promise
// Read file // Read file
const fileBuffer = await file.arrayBuffer(); const fileBuffer = await file.arrayBuffer();
let receipt: Receipt; let receipts: HashTable<Receipt>;
switch (file.type) { switch (file.type) {
case 'application/pdf': case 'application/pdf':
receipt = await loadPDF(fileBuffer) receipts = await loadPDF(fileBuffer)
break break
default: default:
throw Error('invalidFileType') throw Error('invalidFileType')
@ -29,12 +29,12 @@ export async function getPayloadBodyFromFile(file: File, color: COLORS): Promise
const rawData = ''; // unused at the moment, the original use was to store the QR code from issuer const rawData = ''; // unused at the moment, the original use was to store the QR code from issuer
return { return {
receipt: receipt, receipts: receipts,
rawData: rawData rawData: rawData
} }
} }
async function loadPDF(signedPdfBuffer : ArrayBuffer): Promise<any> { async function loadPDF(signedPdfBuffer : ArrayBuffer): Promise<HashTable<Receipt>> {
try { try {
@ -124,44 +124,47 @@ async function loadPDF(signedPdfBuffer : ArrayBuffer): Promise<any> {
} }
async function getPdfDetails(fileBuffer: ArrayBuffer): Promise<Receipt> { async function getPdfDetails(fileBuffer: ArrayBuffer): Promise<HashTable<Receipt>> {
try { try {
const typedArray = new Uint8Array(fileBuffer); const typedArray = new Uint8Array(fileBuffer);
let loadingTask = PdfJS.getDocument(typedArray); let loadingTask = PdfJS.getDocument(typedArray);
const pdfDocument = await loadingTask.promise; const pdfDocument = await loadingTask.promise;
// Load FIRST DUE TO NEW COVAXON FORMAT // Load all dose numbers
const pageNumber = 1; const { numPages } = pdfDocument;
const receiptObj = {};
const pdfPage = await pdfDocument.getPage(pageNumber); for (let pages = 1; pages <= numPages; pages++){
const content = await pdfPage.getTextContent(); const pdfPage = await pdfDocument.getPage(pages);
const numItems = content.items.length; const content = await pdfPage.getTextContent();
let name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization; 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; for (let i = 0; i < numItems; i++) {
const value = item.str; let item = content.items[i] as TextItem;
if (value.includes('Name / Nom')) const value = item.str;
name = (content.items[i+1] as TextItem).str; if (value.includes('Name / Nom'))
if (value.includes('Date:')) { name = (content.items[i+1] as TextItem).str;
vaccinationDate = (content.items[i+1] as TextItem).str; if (value.includes('Date:')) {
vaccinationDate = vaccinationDate.split(',')[0]; 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')) { receiptObj[numDoses] = new Receipt(name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization);
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);
return Promise.resolve(receipt); return Promise.resolve(receiptObj);
} catch (e) { } catch (e) {
Sentry.captureException(e); Sentry.captureException(e);
return Promise.reject(e); return Promise.reject(e);