Implement test and recovery certificates
This commit is contained in:
parent
728211a338
commit
c632d2c2e8
|
@ -1,62 +0,0 @@
|
||||||
version: "3.3"
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
traefik:
|
|
||||||
image: "traefik:v2.4"
|
|
||||||
command:
|
|
||||||
#- "--log.level=DEBUG"
|
|
||||||
- "--api.insecure=false"
|
|
||||||
- "--providers.docker=true"
|
|
||||||
- "--providers.docker.exposedbydefault=false"
|
|
||||||
- "--entrypoints.web.address=:80"
|
|
||||||
- "--entrypoints.websecure.address=:443"
|
|
||||||
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
|
|
||||||
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
|
|
||||||
#- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
|
|
||||||
- "--certificatesresolvers.myresolver.acme.email=marvin.sextro@gmail.com"
|
|
||||||
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
|
|
||||||
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
|
|
||||||
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
|
|
||||||
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- "./letsencrypt:/letsencrypt"
|
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
|
||||||
|
|
||||||
covidpass-api:
|
|
||||||
image: "marvinsxtr/covidpass-api:latest"
|
|
||||||
restart: "unless-stopped"
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
ports:
|
|
||||||
- "8000:8000"
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.covidpass-api.rule=Host(`api.covidpass.marvinsextro.de`)"
|
|
||||||
- "traefik.http.routers.covidpass-api.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.covidpass-api.tls.certresolver=myresolver"
|
|
||||||
secrets:
|
|
||||||
- env
|
|
||||||
|
|
||||||
covidpass:
|
|
||||||
image: "marvinsxtr/covidpass:latest"
|
|
||||||
restart: "unless-stopped"
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.covidpass.rule=Host(`covidpass.marvinsextro.de`)"
|
|
||||||
- "traefik.http.routers.covidpass.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.covidpass.tls.certresolver=myresolver"
|
|
||||||
depends_on:
|
|
||||||
- covidpass-api
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
env:
|
|
||||||
file: ./.env
|
|
|
@ -1,7 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
locales: ['en', 'de', 'de-DE'],
|
locales: ['en', 'de', 'de-DE', 'de-AT', 'de-LI', 'de-LU', 'de-CH'],
|
||||||
localeExtension: 'yml',
|
localeExtension: 'yml',
|
||||||
},
|
},
|
||||||
};
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
de
|
|
@ -0,0 +1 @@
|
||||||
|
de
|
|
@ -0,0 +1 @@
|
||||||
|
de
|
|
@ -0,0 +1 @@
|
||||||
|
de
|
|
@ -2,7 +2,7 @@ noFileOrQrCode: Bitte scanne einen QR-Code oder wähle eine Datei aus
|
||||||
signatureFailed: Fehler beim Signieren der Karte auf dem Server
|
signatureFailed: Fehler beim Signieren der Karte auf dem Server
|
||||||
decodingFailed: Dekodierung der QR-Code-Daten fehlgeschlagen
|
decodingFailed: Dekodierung der QR-Code-Daten fehlgeschlagen
|
||||||
invalidColor: Ungültige Farbe
|
invalidColor: Ungültige Farbe
|
||||||
vaccinationInfo: Impfinformationen konnten nicht gelesen werden
|
certificateData: Zertifikats-Daten konnten nicht gelesen werden
|
||||||
nameMissing: Name konnte nicht gelesen werden
|
nameMissing: Name konnte nicht gelesen werden
|
||||||
dobMissing: Geburtsdatum konnte nicht gelesen werden
|
dobMissing: Geburtsdatum konnte nicht gelesen werden
|
||||||
invalidMedicalProduct: Ungültiges Medizinprodukt
|
invalidMedicalProduct: Ungültiges Medizinprodukt
|
||||||
|
@ -12,3 +12,6 @@ invalidFileType: Ungültiger Dateityp
|
||||||
couldNotDecode: Dekodierung aus QR-Code fehlgeschlagen
|
couldNotDecode: Dekodierung aus QR-Code fehlgeschlagen
|
||||||
couldNotFindQrCode: QR-Code konnte in der ausgewählten Datei nicht gefunden werden
|
couldNotFindQrCode: QR-Code konnte in der ausgewählten Datei nicht gefunden werden
|
||||||
invalidQrCode: Ungültiger QR-Code
|
invalidQrCode: Ungültiger QR-Code
|
||||||
|
certificateType: Kein gültiger Zertifikatstyp gefunden
|
||||||
|
invalidTestResult: Ungültiges Testergebnis
|
||||||
|
invalidTestType: Ungültiger Testtyp
|
|
@ -2,7 +2,7 @@ noFileOrQrCode: Please scan a QR Code, or select a file
|
||||||
signatureFailed: Error while signing pass on server
|
signatureFailed: Error while signing pass on server
|
||||||
decodingFailed: Failed to decode QR code payload
|
decodingFailed: Failed to decode QR code payload
|
||||||
invalidColor: Invalid color
|
invalidColor: Invalid color
|
||||||
vaccinationInfo: Failed to read vaccination information
|
certificateData: Failed to read certificate data
|
||||||
nameMissing: Failed to read name
|
nameMissing: Failed to read name
|
||||||
dobMissing: Failed to read date of birth
|
dobMissing: Failed to read date of birth
|
||||||
invalidMedicalProduct: Invalid medical product
|
invalidMedicalProduct: Invalid medical product
|
||||||
|
@ -12,3 +12,6 @@ invalidFileType: Invalid file type
|
||||||
couldNotDecode: Could not decode QR code from file
|
couldNotDecode: Could not decode QR code from file
|
||||||
couldNotFindQrCode: Could not find QR Code in provided file
|
couldNotFindQrCode: Could not find QR Code in provided file
|
||||||
invalidQrCode: Invalid QR code
|
invalidQrCode: Invalid QR code
|
||||||
|
certificateType: No valid certificate type found
|
||||||
|
invalidTestResult: Invalid test result
|
||||||
|
invalidTestType: Invalid test type
|
89
src/pass.ts
89
src/pass.ts
|
@ -2,7 +2,7 @@ import {toBuffer as createZip} from 'do-not-zip';
|
||||||
import {v4 as uuid4} from 'uuid';
|
import {v4 as uuid4} from 'uuid';
|
||||||
|
|
||||||
import {Constants} from "./constants";
|
import {Constants} from "./constants";
|
||||||
import {Payload, PayloadBody} from "./payload";
|
import {Payload, PayloadBody, PassDictionary} from "./payload";
|
||||||
import {ValueSets} from "./value_sets";
|
import {ValueSets} from "./value_sets";
|
||||||
|
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
|
@ -21,21 +21,6 @@ interface QrCode {
|
||||||
messageEncoding: Encoding;
|
messageEncoding: Encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Field {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
textAlignment?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PassStructureDictionary {
|
|
||||||
headerFields: Array<Field>;
|
|
||||||
primaryFields: Array<Field>;
|
|
||||||
secondaryFields: Array<Field>;
|
|
||||||
auxiliaryFields: Array<Field>;
|
|
||||||
backFields: Array<Field>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SignData {
|
interface SignData {
|
||||||
PassJsonHash: string;
|
PassJsonHash: string;
|
||||||
useBlackVersion: boolean;
|
useBlackVersion: boolean;
|
||||||
|
@ -56,7 +41,7 @@ export class PassData {
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
barcodes: Array<QrCode>;
|
barcodes: Array<QrCode>;
|
||||||
barcode: QrCode;
|
barcode: QrCode;
|
||||||
generic: PassStructureDictionary;
|
generic: PassDictionary;
|
||||||
|
|
||||||
// Generates a sha1 hash from a given buffer
|
// Generates a sha1 hash from a given buffer
|
||||||
private static getBufferHash(buffer: Buffer | string): string {
|
private static getBufferHash(buffer: Buffer | string): string {
|
||||||
|
@ -157,74 +142,6 @@ export class PassData {
|
||||||
this.serialNumber = uuid4(); // Generate random UUID v4
|
this.serialNumber = uuid4(); // Generate random UUID v4
|
||||||
this.barcodes = [qrCode];
|
this.barcodes = [qrCode];
|
||||||
this.barcode = qrCode;
|
this.barcode = qrCode;
|
||||||
this.generic = {
|
this.generic = payload.generic;
|
||||||
headerFields: [
|
|
||||||
{
|
|
||||||
key: "type",
|
|
||||||
label: "Certificate Type",
|
|
||||||
value: payload.certificateType
|
|
||||||
}
|
|
||||||
],
|
|
||||||
primaryFields: [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "Name",
|
|
||||||
value: payload.name
|
|
||||||
}
|
|
||||||
],
|
|
||||||
secondaryFields: [
|
|
||||||
{
|
|
||||||
key: "dose",
|
|
||||||
label: "Dose",
|
|
||||||
value: payload.dose
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "dov",
|
|
||||||
label: "Date of Vaccination",
|
|
||||||
value: payload.dateOfVaccination,
|
|
||||||
textAlignment: "PKTextAlignmentRight"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
auxiliaryFields: [
|
|
||||||
{
|
|
||||||
key: "vaccine",
|
|
||||||
label: "Vaccine",
|
|
||||||
value: payload.vaccineName
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "dob",
|
|
||||||
label: "Date of Birth",
|
|
||||||
value: payload.dateOfBirth,
|
|
||||||
textAlignment: "PKTextAlignmentRight"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
backFields: [
|
|
||||||
{
|
|
||||||
key: "uvci",
|
|
||||||
label: "Unique Certificate Identifier (UVCI)",
|
|
||||||
value: payload.uvci
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "issuer",
|
|
||||||
label: "Certificate Issuer",
|
|
||||||
value: payload.certificateIssuer
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "country",
|
|
||||||
label: "Country of Vaccination",
|
|
||||||
value: payload.countryOfVaccination
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "manufacturer",
|
|
||||||
label: "Manufacturer",
|
|
||||||
value: payload.manufacturer
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "disclaimer",
|
|
||||||
label: "Disclaimer",
|
|
||||||
value: "This certificate is only valid in combination with the ID card of the certificate holder and expires one year + 14 days after the last dose. The validity of this certificate was not checked by CovidPass."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
297
src/payload.ts
297
src/payload.ts
|
@ -1,6 +1,31 @@
|
||||||
import {ValueSets} from "./value_sets";
|
import {ValueSets} from "./value_sets";
|
||||||
import {Constants} from "./constants";
|
import {Constants} from "./constants";
|
||||||
|
|
||||||
|
enum CertificateType {
|
||||||
|
Vaccination = 'Vaccination',
|
||||||
|
Test = 'Test',
|
||||||
|
Recovery = 'Recovery',
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TextAlignment {
|
||||||
|
right = 'PKTextAlignmentRight',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Field {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
textAlignment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PassDictionary {
|
||||||
|
headerFields: Array<Field>;
|
||||||
|
primaryFields: Array<Field>;
|
||||||
|
secondaryFields: Array<Field>;
|
||||||
|
auxiliaryFields: Array<Field>;
|
||||||
|
backFields: Array<Field>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PayloadBody {
|
export interface PayloadBody {
|
||||||
color: string;
|
color: string;
|
||||||
rawData: string;
|
rawData: string;
|
||||||
|
@ -8,7 +33,7 @@ export interface PayloadBody {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Payload {
|
export class Payload {
|
||||||
certificateType: string = 'Vaccination';
|
certificateType: CertificateType;
|
||||||
|
|
||||||
rawData: string;
|
rawData: string;
|
||||||
|
|
||||||
|
@ -19,17 +44,7 @@ export class Payload {
|
||||||
img2x: Buffer;
|
img2x: Buffer;
|
||||||
dark: boolean;
|
dark: boolean;
|
||||||
|
|
||||||
name: string;
|
generic: PassDictionary;
|
||||||
dose: string;
|
|
||||||
dateOfVaccination: string;
|
|
||||||
dateOfBirth: string;
|
|
||||||
uvci: string;
|
|
||||||
certificateIssuer: string;
|
|
||||||
medicalProductKey: string;
|
|
||||||
|
|
||||||
countryOfVaccination: string;
|
|
||||||
vaccineName: string;
|
|
||||||
manufacturer: string;
|
|
||||||
|
|
||||||
constructor(body: PayloadBody, valueSets: ValueSets) {
|
constructor(body: PayloadBody, valueSets: ValueSets) {
|
||||||
|
|
||||||
|
@ -41,39 +56,99 @@ export class Payload {
|
||||||
|
|
||||||
const dark = body.color != 'white'
|
const dark = body.color != 'white'
|
||||||
|
|
||||||
|
const healthCertificate = body.decodedData['-260'];
|
||||||
|
const covidCertificate = healthCertificate['1']; // Version number subject to change
|
||||||
|
|
||||||
// Get Vaccine, Name and Date of Birth information
|
if (covidCertificate == undefined) {
|
||||||
const vaccinationInformation = body.decodedData['-260']['1']['v'][0];
|
throw new Error('certificateData');
|
||||||
const nameInformation = body.decodedData['-260']['1']['nam'];
|
|
||||||
const dateOfBirthInformation = body.decodedData['-260']['1']['dob'];
|
|
||||||
|
|
||||||
if (vaccinationInformation == undefined) {
|
|
||||||
throw new Error('vaccinationInfo');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get name and date of birth information
|
||||||
|
const nameInformation = covidCertificate['nam'];
|
||||||
|
const dateOfBirthInformation = covidCertificate['dob'];
|
||||||
|
|
||||||
if (nameInformation == undefined) {
|
if (nameInformation == undefined) {
|
||||||
throw new Error('nameMissing');
|
throw new Error('nameMissing');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dateOfBirthInformation == undefined) {
|
if (dateOfBirthInformation == undefined) {
|
||||||
throw new Error('dobMissing');
|
throw new Error('dobMissing');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Medical, country and manufacturer information
|
const name = `${nameInformation['fn']}, ${nameInformation['gn']}`;
|
||||||
const medialProductKey = vaccinationInformation['mp'];
|
const dateOfBirth = dateOfBirthInformation;
|
||||||
const countryCode = vaccinationInformation['co'];
|
|
||||||
const manufacturerKey = vaccinationInformation['ma'];
|
|
||||||
|
|
||||||
if (!(medialProductKey in valueSets.medicalProducts)) {
|
let properties: object;
|
||||||
throw new Error('invalidMedicalProduct');
|
|
||||||
|
// Set certificate type and properties
|
||||||
|
if (covidCertificate['v'] !== undefined) {
|
||||||
|
this.certificateType = CertificateType.Vaccination;
|
||||||
|
properties = covidCertificate['v'][0];
|
||||||
}
|
}
|
||||||
|
if (covidCertificate['t'] !== undefined) {
|
||||||
|
this.certificateType = CertificateType.Test;
|
||||||
|
properties = covidCertificate['t'][0];
|
||||||
|
}
|
||||||
|
if (covidCertificate['r'] !== undefined) {
|
||||||
|
this.certificateType = CertificateType.Recovery;
|
||||||
|
properties = covidCertificate['r'][0];
|
||||||
|
}
|
||||||
|
if (this.certificateType == undefined) {
|
||||||
|
throw new Error('certificateType')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get country code, identifier and issuer
|
||||||
|
const countryCode = properties['co'];
|
||||||
|
const uvci = properties['ci'];
|
||||||
|
const certificateIssuer = properties['is'];
|
||||||
|
|
||||||
if (!(countryCode in valueSets.countryCodes)) {
|
if (!(countryCode in valueSets.countryCodes)) {
|
||||||
throw new Error('invalidCountryCode')
|
throw new Error('invalidCountryCode');
|
||||||
}
|
|
||||||
if (!(manufacturerKey in valueSets.manufacturers)) {
|
|
||||||
throw new Error('invalidManufacturer')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const country = valueSets.countryCodes[countryCode].display;
|
||||||
|
|
||||||
|
const generic: PassDictionary = {
|
||||||
|
headerFields: [
|
||||||
|
{
|
||||||
|
key: "type",
|
||||||
|
label: "Certificate Type",
|
||||||
|
value: this.certificateType
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primaryFields: [
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
label: "Name",
|
||||||
|
value: name
|
||||||
|
}
|
||||||
|
],
|
||||||
|
secondaryFields: [],
|
||||||
|
auxiliaryFields: [
|
||||||
|
{
|
||||||
|
key: "dob",
|
||||||
|
label: "Date of Birth",
|
||||||
|
value: dateOfBirth,
|
||||||
|
textAlignment: TextAlignment.right
|
||||||
|
}
|
||||||
|
],
|
||||||
|
backFields: [
|
||||||
|
{
|
||||||
|
key: "uvci",
|
||||||
|
label: "Unique Certificate Identifier (UVCI)",
|
||||||
|
value: uvci
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "issuer",
|
||||||
|
label: "Certificate Issuer",
|
||||||
|
value: certificateIssuer
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "country",
|
||||||
|
label: "Country",
|
||||||
|
value: country
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// Set Values
|
// Set Values
|
||||||
this.rawData = body.rawData;
|
this.rawData = body.rawData;
|
||||||
|
@ -85,16 +160,158 @@ export class Payload {
|
||||||
this.img2x = dark ? Constants.img2xWhite : Constants.img2xBlack
|
this.img2x = dark ? Constants.img2xWhite : Constants.img2xBlack
|
||||||
this.dark = dark;
|
this.dark = dark;
|
||||||
|
|
||||||
this.name = `${nameInformation['fn']}, ${nameInformation['gn']}`;
|
this.generic = Payload.fillPassData(this.certificateType, generic, properties, valueSets);
|
||||||
this.dose = `${vaccinationInformation['dn']}/${vaccinationInformation['sd']}`;
|
|
||||||
this.dateOfVaccination = vaccinationInformation['dt'];
|
|
||||||
this.dateOfBirth = dateOfBirthInformation;
|
|
||||||
this.uvci = vaccinationInformation['ci'];
|
|
||||||
this.certificateIssuer = vaccinationInformation['is'];
|
|
||||||
|
|
||||||
this.countryOfVaccination = valueSets.countryCodes[countryCode].display;
|
|
||||||
this.vaccineName = valueSets.medicalProducts[medialProductKey].display;
|
|
||||||
this.manufacturer = valueSets.manufacturers[manufacturerKey].display;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fillPassData(type: CertificateType, data: PassDictionary, properties: Object, valueSets: ValueSets): PassDictionary {
|
||||||
|
switch (type) {
|
||||||
|
case CertificateType.Vaccination:
|
||||||
|
const dose = `${properties['dn']}/${properties['sd']}`;
|
||||||
|
const dateOfVaccination = properties['dt'];
|
||||||
|
const medialProductKey = properties['mp'];
|
||||||
|
const manufacturerKey = properties['ma'];
|
||||||
|
|
||||||
|
if (!(medialProductKey in valueSets.medicalProducts)) {
|
||||||
|
throw new Error('invalidMedicalProduct');
|
||||||
|
}
|
||||||
|
if (!(manufacturerKey in valueSets.manufacturers)) {
|
||||||
|
throw new Error('invalidManufacturer')
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaccineName = valueSets.medicalProducts[medialProductKey].display;
|
||||||
|
const manufacturer = valueSets.manufacturers[manufacturerKey].display;
|
||||||
|
|
||||||
|
data.secondaryFields.push(...[
|
||||||
|
{
|
||||||
|
key: "dose",
|
||||||
|
label: "Dose",
|
||||||
|
value: dose
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "dov",
|
||||||
|
label: "Date of Vaccination",
|
||||||
|
value: dateOfVaccination,
|
||||||
|
textAlignment: TextAlignment.right
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
data.auxiliaryFields.splice(0, 0, {
|
||||||
|
key: "vaccine",
|
||||||
|
label: "Vaccine",
|
||||||
|
value: vaccineName
|
||||||
|
});
|
||||||
|
data.backFields.push(...[
|
||||||
|
{
|
||||||
|
key: "manufacturer",
|
||||||
|
label: "Manufacturer",
|
||||||
|
value: manufacturer
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "disclaimer",
|
||||||
|
label: "Disclaimer",
|
||||||
|
value: "This certificate is only valid in combination with the ID card of the certificate holder and expires one year + 14 days after the last dose. The validity of this certificate was not checked by CovidPass."
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case CertificateType.Test:
|
||||||
|
const testTypeKey = properties['tt'];
|
||||||
|
const testDateTimeString = properties['sc'];
|
||||||
|
const testResultKey = properties['tr'];
|
||||||
|
const testingCentre = properties['tc'];
|
||||||
|
|
||||||
|
if (!(testResultKey in valueSets.testResults)) {
|
||||||
|
throw new Error('invalidTestResult');
|
||||||
|
}
|
||||||
|
if (!(testTypeKey in valueSets.testTypes)) {
|
||||||
|
throw new Error('invalidTestType')
|
||||||
|
}
|
||||||
|
|
||||||
|
const testResult = valueSets.testResults[testResultKey].display;
|
||||||
|
const testType = valueSets.testTypes[testTypeKey].display;
|
||||||
|
|
||||||
|
const testTime = testDateTimeString.replace(/.*T/, '').replace('Z', ' ') + 'UTC';
|
||||||
|
const testDate = testDateTimeString.replace(/T.*/,'');
|
||||||
|
|
||||||
|
data.secondaryFields.push(...[
|
||||||
|
{
|
||||||
|
key: "result",
|
||||||
|
label: "Result",
|
||||||
|
value: testResult
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "dot",
|
||||||
|
label: "Date of Test",
|
||||||
|
value: testDate,
|
||||||
|
textAlignment: TextAlignment.right
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
data.auxiliaryFields.pop();
|
||||||
|
data.auxiliaryFields.push(...[
|
||||||
|
{
|
||||||
|
key: "test",
|
||||||
|
label: "Test Type",
|
||||||
|
value: testType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "time",
|
||||||
|
label: "Time of Test",
|
||||||
|
value: testTime,
|
||||||
|
textAlignment: TextAlignment.right
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
if (testingCentre !== undefined)
|
||||||
|
data.backFields.push({
|
||||||
|
key: "centre",
|
||||||
|
label: "Testing Centre",
|
||||||
|
value: testingCentre
|
||||||
|
});
|
||||||
|
data.backFields.push({
|
||||||
|
key: "disclaimer",
|
||||||
|
label: "Disclaimer",
|
||||||
|
value: "This certificate is only valid in combination with the ID card of the certificate holder and may expire 24h after the test. The validity of this certificate was not checked by CovidPass."
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case CertificateType.Recovery:
|
||||||
|
const firstPositiveTestDate = properties['fr'];
|
||||||
|
const validFrom = properties['df'];
|
||||||
|
const validUntil = properties['du'];
|
||||||
|
|
||||||
|
data.secondaryFields.push(...[
|
||||||
|
{
|
||||||
|
key: "result",
|
||||||
|
label: "Test Result",
|
||||||
|
value: "Detected"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "from",
|
||||||
|
label: "Valid From",
|
||||||
|
value: validFrom,
|
||||||
|
textAlignment: TextAlignment.right
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
data.auxiliaryFields.pop();
|
||||||
|
data.auxiliaryFields.push(...[
|
||||||
|
{
|
||||||
|
key: "testdate",
|
||||||
|
label: "Test Date",
|
||||||
|
value: firstPositiveTestDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "until",
|
||||||
|
label: "Valid Until",
|
||||||
|
value: validUntil,
|
||||||
|
textAlignment: TextAlignment.right
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
data.backFields.push({
|
||||||
|
key: "disclaimer",
|
||||||
|
label: "Disclaimer",
|
||||||
|
value: "This certificate is only valid in combination with the ID card of the certificate holder. The validity of this certificate was not checked by CovidPass."
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('certificateType');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,8 @@ interface ValueTypes {
|
||||||
medicalProducts: string;
|
medicalProducts: string;
|
||||||
countryCodes: string;
|
countryCodes: string;
|
||||||
manufacturers: string;
|
manufacturers: string;
|
||||||
|
testResults: string;
|
||||||
|
testTypes: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ValueSets {
|
export class ValueSets {
|
||||||
|
@ -10,25 +12,50 @@ export class ValueSets {
|
||||||
medicalProducts: 'vaccine-medicinal-product.json',
|
medicalProducts: 'vaccine-medicinal-product.json',
|
||||||
countryCodes: 'country-2-codes.json',
|
countryCodes: 'country-2-codes.json',
|
||||||
manufacturers: 'vaccine-mah-manf.json',
|
manufacturers: 'vaccine-mah-manf.json',
|
||||||
|
testResults: 'test-result.json',
|
||||||
|
testTypes: 'test-type.json',
|
||||||
}
|
}
|
||||||
|
|
||||||
medicalProducts: object;
|
medicalProducts: object;
|
||||||
countryCodes: object;
|
countryCodes: object;
|
||||||
manufacturers: object;
|
manufacturers: object;
|
||||||
|
testResults: object;
|
||||||
|
testTypes: object;
|
||||||
|
|
||||||
|
private constructor(
|
||||||
private constructor(medicalProducts: object, countryCodes: object, manufacturers: object) {
|
medicalProducts: object,
|
||||||
|
countryCodes: object,
|
||||||
|
manufacturers: object,
|
||||||
|
testResults: object,
|
||||||
|
testTypes: object
|
||||||
|
) {
|
||||||
this.medicalProducts = medicalProducts;
|
this.medicalProducts = medicalProducts;
|
||||||
this.countryCodes = countryCodes;
|
this.countryCodes = countryCodes;
|
||||||
this.manufacturers = manufacturers;
|
this.manufacturers = manufacturers;
|
||||||
|
this.testResults = testResults;
|
||||||
|
this.testTypes = testTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async fetchValueSet(file: string): Promise<object> {
|
||||||
|
return await (await fetch(ValueSets.VALUE_SET_BASE_URL + file)).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async loadValueSets(): Promise<ValueSets> {
|
public static async loadValueSets(): Promise<ValueSets> {
|
||||||
// Load all Value Sets from GitHub
|
// Load all Value Sets from GitHub
|
||||||
let medicalProducts = await (await fetch(ValueSets.VALUE_SET_BASE_URL + ValueSets.VALUE_TYPES.medicalProducts)).json();
|
let [medicalProducts, countryCodes, manufacturers, testResults, testTypes] = await Promise.all([
|
||||||
let countryCodes = await (await fetch(ValueSets.VALUE_SET_BASE_URL + ValueSets.VALUE_TYPES.countryCodes)).json();
|
ValueSets.fetchValueSet(ValueSets.VALUE_TYPES.medicalProducts),
|
||||||
let manufacturers = await (await fetch(ValueSets.VALUE_SET_BASE_URL + ValueSets.VALUE_TYPES.manufacturers)).json();
|
ValueSets.fetchValueSet(ValueSets.VALUE_TYPES.countryCodes),
|
||||||
|
ValueSets.fetchValueSet(ValueSets.VALUE_TYPES.manufacturers),
|
||||||
|
ValueSets.fetchValueSet(ValueSets.VALUE_TYPES.testResults),
|
||||||
|
ValueSets.fetchValueSet(ValueSets.VALUE_TYPES.testTypes)
|
||||||
|
]);
|
||||||
|
|
||||||
return new ValueSets(medicalProducts['valueSetValues'], countryCodes['valueSetValues'], manufacturers['valueSetValues']);
|
return new ValueSets(
|
||||||
|
medicalProducts['valueSetValues'],
|
||||||
|
countryCodes['valueSetValues'],
|
||||||
|
manufacturers['valueSetValues'],
|
||||||
|
testResults['valueSetValues'],
|
||||||
|
testTypes['valueSetValues']
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue