diff --git a/README.md b/README.md index c64da9a..a06310e 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,8 @@ impl and extended by Consensas: /~https://github.com/Consensas/information-passpor A ruby impl of health cards (DVCI) /~https://github.com/dvci/health_cards +Helpers for FHIR / JSON-LD schematics +/~https://github.com/fhircat ### Vaccination credential initiatives @@ -333,7 +335,7 @@ opinion: https://www.yogitatrainingcenter.com/w3c-verifiable-credentials-the-fai collection / survey of all initiatives: https://docs.google.com/document/d/1MQfZzlkYkXCXvnUXd7Cd6Y5g0RRXrKxGGqVcbBnSk1k/edit - +https://techcrunch.com/2021/01/19/europe-is-working-on-a-common-framework-for-vaccine-passports/ ### OT: Linting TS diff --git a/packages/immu-core/package.json b/packages/immu-core/package.json index 5ad59d5..0d0cdc8 100644 --- a/packages/immu-core/package.json +++ b/packages/immu-core/package.json @@ -20,6 +20,7 @@ "@babel/preset-typescript": "^7.12.7", "@truffle/contract": "^4.3.5", "@types/dotenv-flow": "^3.1.0", + "@types/handlebars": "^4.1.0", "@types/jest": "^26.0.20", "@types/jsonld": "^1.5.2", "@types/node": "^12.0.0", @@ -58,6 +59,7 @@ "ethereumjs-util": "^7.0.7", "ethr-did-registry": "^0.0.3", "ethr-did-resolver": "git@github.com:cod1ng-earth/ethr-did-resolver.git", + "handlebars": "^4.7.7", "jsonld": "^4.0.1", "key-did-resolver": "^0.2.4", "secp256k1": "^4.0.2", diff --git a/packages/immu-core/src/@types/Fhir.ts b/packages/immu-core/src/@types/Fhir.ts index a296b6c..99dbc23 100644 --- a/packages/immu-core/src/@types/Fhir.ts +++ b/packages/immu-core/src/@types/Fhir.ts @@ -5,11 +5,3 @@ export interface FHIRBundle { export interface FHIRResource { resource: Record; } - -export interface ImmunizationInputParams { - lotNumber: string; - vaccineCode: string; - occurrenceDateTime: Date; - doseNumber: number; - doseQuantity: number; -} diff --git a/packages/immu-core/src/@types/index.d.ts b/packages/immu-core/src/@types/index.d.ts index 19d8205..1ad8974 100644 --- a/packages/immu-core/src/@types/index.d.ts +++ b/packages/immu-core/src/@types/index.d.ts @@ -1,2 +1,7 @@ declare module 'did-method-key'; declare module '@relocke/base58'; + +declare module '*.mustache' { + const content: string; + export default content; +} diff --git a/packages/immu-core/src/EthRegistry.ts b/packages/immu-core/src/EthRegistry.ts index 48609e4..3dde862 100644 --- a/packages/immu-core/src/EthRegistry.ts +++ b/packages/immu-core/src/EthRegistry.ts @@ -94,7 +94,7 @@ export class EthRegistry { } //todo: check if the endpoint responds so we can't add bullshit ;) - const { contract } = this.didRegistries[network]; + const { contract } = this.getDidRegistry(network); const duration = 60 * 60 * 24 * 365 * 2; const endpointBuffer = Buffer.from(endpoint, 'utf-8'); diff --git a/packages/immu-core/src/__tests__/VerifyImmunizationPresentations.test.ts b/packages/immu-core/src/__tests__/FhirHL7Credentials.test.ts similarity index 72% rename from packages/immu-core/src/__tests__/VerifyImmunizationPresentations.test.ts rename to packages/immu-core/src/__tests__/FhirHL7Credentials.test.ts index e4087c9..6fe76bf 100644 --- a/packages/immu-core/src/__tests__/VerifyImmunizationPresentations.test.ts +++ b/packages/immu-core/src/__tests__/FhirHL7Credentials.test.ts @@ -1,17 +1,23 @@ -import { Issuer, Resolver, VaccinationCredentialVerifier, Verifier } from '..'; +import { Account } from 'web3-core'; +import { + Issuer, + Resolver, + VaccinationCredentialVerifier, + VerifiableCredential, + CreateFhirHL7Immunization, + SMARTHEALTH_CARD_CRED_TYPE +} from '..'; import { DID } from '../@types'; -import { Create as CreateClaim, TYPE as SMARTHEALTH_CARD_CRED_TYPE } from '../semantic/FhirHL7VaccinationCredential'; import newRegistry from './common/newRegistry'; import web3 from './common/web3Provider'; -describe('Vaccination Credentials', () => { +describe('Fhir HL7 / M$ Smart Health Card Vaccination Credentials', () => { let resolver: Resolver; - let issuerAccount, subjectAccount, verifierAccount; + let issuerAccount: Account; + let subjectAccount: Account; let didIssuer: DID; let didSubject: DID; - let didVerifier: DID; let issuer: Issuer; - let verifier: Verifier; beforeAll(async () => { const registry = await newRegistry(web3); @@ -25,32 +31,29 @@ describe('Vaccination Credentials', () => { issuerAccount = web3.eth.accounts.create(); subjectAccount = web3.eth.accounts.create(); - verifierAccount = web3.eth.accounts.create(); didIssuer = `did:ethr:development:${issuerAccount.address}`; didSubject = `did:ethr:development:${subjectAccount.address}`; - didVerifier = `did:ethr:development:${verifierAccount.address}`; issuer = new Issuer(resolver, didIssuer); - verifier = new Verifier(resolver); }); - let credentials = []; + let credentials: VerifiableCredential[] = []; it('can verify valid vaccination credentials (happy path)', async () => { - const immunization1 = CreateClaim({ - doseNumber: 1, + const immunization1 = CreateFhirHL7Immunization({ + doseSequence: 1, doseQuantity: 50, lotNumber: 'ABCDE', occurrenceDateTime: new Date('2021-01-01T11:45:33+11:00'), - vaccineCode: '208' + cvxCode: '208' }); - const immunization2 = CreateClaim({ - doseNumber: 2, + const immunization2 = CreateFhirHL7Immunization({ + doseSequence: 2, doseQuantity: 80, lotNumber: 'EDCBA', occurrenceDateTime: new Date('2021-01-30T12:45:33+11:00'), - vaccineCode: '208' + cvxCode: '208' }); const credential1 = await issuer.issueCredential(didSubject, immunization1, [SMARTHEALTH_CARD_CRED_TYPE]); diff --git a/packages/immu-core/src/__tests__/SchemaOrgCredentials.test.ts b/packages/immu-core/src/__tests__/SchemaOrgCredentials.test.ts index 3bd5df5..71bc5e7 100644 --- a/packages/immu-core/src/__tests__/SchemaOrgCredentials.test.ts +++ b/packages/immu-core/src/__tests__/SchemaOrgCredentials.test.ts @@ -1,13 +1,22 @@ -import { Create, TYPE as SCHEMAORG_CRED_TYPE } from '../semantic/SchemaOrgCredential'; -import { Issuer, Resolver, VaccinationCredentialVerifier, Verifier } from '..'; +import { + Issuer, + Resolver, + VaccinationCredentialVerifier, + Verifier, + VerifiableCredential, + CreateSchemaOrgImmunization, + SCHEMAORG_CRED_TYPE +} from '..'; import { DID } from '../@types'; import newRegistry from './common/newRegistry'; import web3 from './common/web3Provider'; +import { Account } from 'web3-core'; -describe('Vaccination Credentials', () => { +describe('Schema.org Vaccination Credentials', () => { let resolver: Resolver; - let issuerAccount, subjectAccount; + let issuerAccount: Account; + let subjectAccount: Account; let didIssuer: DID; let didSubject: DID; let issuer: Issuer; @@ -33,27 +42,30 @@ describe('Vaccination Credentials', () => { verifier = new Verifier(resolver); }); - let credentials = []; + let credentials: VerifiableCredential[] = []; + let immunization1: any; - it('can create a schema.org credential', async () => { - const vaccination1 = Create({ - doseNumber: 1, + it('can create a schema.org immunization claim', async () => { + immunization1 = CreateSchemaOrgImmunization({ + doseSequence: 1, doseQuantity: 50, lotNumber: 'ABCDE', occurrenceDateTime: new Date('2021-01-01T11:45:33+11:00'), - vaccineCode: '210' + cvxCode: '208' }); + }); - const vaccination2 = Create({ - doseNumber: 2, + it('can create a verifiable set of schema.org credential', async () => { + const immunization2 = CreateSchemaOrgImmunization({ + doseSequence: 2, doseQuantity: 50, lotNumber: 'EDCBA', occurrenceDateTime: new Date('2021-01-30T11:45:33+11:00'), - vaccineCode: '210' + cvxCode: '208' }); - const credentialPayload1 = await issuer.issueCredential(didSubject, vaccination1, [SCHEMAORG_CRED_TYPE]); - const credentialPayload2 = await issuer.issueCredential(didSubject, vaccination2, [SCHEMAORG_CRED_TYPE]); + const credentialPayload1 = await issuer.issueCredential(didSubject, immunization1, [SCHEMAORG_CRED_TYPE]); + const credentialPayload2 = await issuer.issueCredential(didSubject, immunization2, [SCHEMAORG_CRED_TYPE]); const signingKey = await resolver.resolve(didIssuer); diff --git a/packages/immu-core/src/index.ts b/packages/immu-core/src/index.ts index 30e9de2..5fba98a 100644 --- a/packages/immu-core/src/index.ts +++ b/packages/immu-core/src/index.ts @@ -9,20 +9,7 @@ export * from './displayCredential'; export { VerifiedCredential, VerifiableCredential, Verifiable, W3CCredential } from 'did-jwt-vc'; export { JWTVerified } from 'did-jwt'; -export { default as VaccinationCredentialVerifier } from './semantic/VaccinationCredentialVerifier'; -export { - Create as CreateFhirHL7VaccinationCredential, - TYPE as SMARTHEALTH_CARD_CRED_TYPE -} from './semantic/FhirHL7VaccinationCredential'; - -export { - Create as CreateSchemaOrgVaccinationCredential, - TYPE as SCHEMAORG_CARD_CRED_TYPE -} from './semantic/SchemaOrgCredential'; - export { PublicKey, Authentication, DIDDocument } from 'did-resolver'; -export { ImmunizationInputParams } from './@types/Fhir'; - export { CredentialPayload, JwtCredentialSubject, @@ -31,3 +18,17 @@ export { export { DID, BufferLike, JSONProof } from './@types'; export * from './@types/Jolocom'; + +export { FHIRResource, FHIRBundle } from './@types/Fhir'; +export { default as VaccinationCredentialVerifier } from './semantic/VaccinationCredentialVerifier'; +export { + Create as CreateFhirHL7Immunization, + TYPE as SMARTHEALTH_CARD_CRED_TYPE +} from './semantic/FhirHL7VaccinationCredential'; + +export { + Create as CreateSchemaOrgImmunization, + TYPE as SCHEMAORG_CRED_TYPE +} from './semantic/SchemaOrgVaccinationCredential'; + +export * as Covid19 from './semantic/Covid19'; diff --git a/packages/immu-core/src/semantic/Covid19.ts b/packages/immu-core/src/semantic/Covid19.ts new file mode 100644 index 0000000..4791611 --- /dev/null +++ b/packages/immu-core/src/semantic/Covid19.ts @@ -0,0 +1,174 @@ +//https://www2a.cdc.gov/vaccines/IIS/IISStandards/vaccines.asp?rpt=cvx + +type Status = 'Active' | 'Inactive'; + +export interface ManufacturerCode { + cdcProductName: string; + shortDescription: string; + manufacturer: string; + mvxCode: string; + status: Status; +} + +/** + * represents a normalized vaccination type ("drug") + */ +export interface CovidVaccination { + shortDescription: string; + fullVaccineName: string; + cvxCode: string; + vaccineStatus: Status; + notes: string; + cpt?: { + cptCode: string; + vaccineName: string; + description: string; + }; + mvx?: ManufacturerCode[]; +} + +/** + * represents a vaccination "event" ("immunization") + */ +export interface CovidImmunization { + lotNumber: string; + occurrenceDateTime: Date; + doseSequence: number; + doseQuantity?: number; + description?: string; + cvxCode: string; + cvx?: CovidVaccination; +} + +/** + * generic interface to render an immunization template + */ +export interface ImmunizationTemplateParams extends CovidImmunization { + drug: { + name?: string; + code: { + codingSystem: string; + codeValue: string; + description?: string; + }; + manufacturer?: { + identifier: string; + name: string; + }; + }; +} + +export function decodeDrugCode(codingSystem: string, encodedValue: string): CovidVaccination | undefined { + let cvxCode: string; + switch (codingSystem) { + case 'http://hl7.org/fhir/sid/cvx': + cvxCode = encodedValue; + break; + case 'CDC-MVX.CVX': + cvxCode = encodedValue.split('.')[1].replace('CVX-', ''); + break; + default: + throw Error(`don't recognize coding system ${codingSystem}`); + } + return Covid19Vaccinations.find((c) => c.cvxCode === cvxCode); +} + +export const Covid19Vaccinations: CovidVaccination[] = [ + { + shortDescription: 'COVID-19 vaccine, vector-nr, rS-Ad26, PF, 0.5 mL', + fullVaccineName: + 'SARS-COV-2 (COVID-19) vaccine, vector non-replicating, recombinant spike protein-Ad26, preservative free, 0.5 mL', + cvxCode: '212', + vaccineStatus: 'Active', + notes: 'Potential EUA, 1-dose vaccine', + cpt: { + cptCode: '91303', + vaccineName: 'COVID-19 vaccine, vector-nr, rS-Ad26, PF, 0.5 mL', + description: + 'Severe acute respiratory syndrome coronavirus 2 (SARS-CoV-2) (coronavirus disease [COVID-19]) vaccine, DNA, spike protein, adenovirus type 26 (Ad26) vector, preservative free, 5x1010 viral particles/0.5mL dosage, for intramuscular use' + }, + mvx: [ + { + cdcProductName: 'Janssen (J&J) COVID-19 Vaccine', + shortDescription: 'COVID-19 vaccine, vector-nr, rS-Ad26, PF, 0.5 mL', + manufacturer: 'Janssen', + mvxCode: 'JSN', + status: 'Active' + } + ] + }, + { + shortDescription: 'COVID-19 vaccine, vector-nr, rS-ChAdOx1, PF, 0.5 mL ', + fullVaccineName: + 'SARS-COV-2 (COVID-19) vaccine, vector non-replicating, recombinant spike protein-ChAdOx1, preservative free, 0.5 mL ', + cvxCode: '210', + vaccineStatus: 'Active', + notes: 'Potential EUA, 2-dose vaccine', + cpt: { + cptCode: '91302', + vaccineName: 'COVID-19 vaccine, vector-nr, rS-ChAdOx1, PF, 0.5 mL', + description: + 'Severe acute respiratory syndrome coronavirus 2 (SARS-CoV-2) (coronavirus disease [COVID-19]) vaccine, DNA, spike protein, chimpanzee adenovirus Oxford 1 (ChAdOx1) vector, preservative free, 5x1010 viral particles/0.5mL dosage, for intramuscular use' + }, + mvx: [ + { + cdcProductName: 'AstraZeneca COVID-19 Vaccine', + shortDescription: 'COVID-19 vaccine, vector-nr, rS-ChAdOx1, PF, 0.5 mL ', + manufacturer: 'AstraZeneca', + mvxCode: 'ASZ', + status: 'Active' + } + ] + }, + { + shortDescription: 'COVID-19, mRNA, LNP-S, PF, 100 mcg/0.5 mL dose', + fullVaccineName: 'SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 100 mcg/0.5mL dose', + cvxCode: '207', + vaccineStatus: 'Active', + notes: 'EUA 12/18/2020, 2-dose vaccine', + cpt: { + cptCode: '91301', + vaccineName: 'COVID-19, mRNA, LNP-S, PF, 100 mcg/0.5 mL dose', + description: + 'Severe acute respiratory syndrome coronavirus 2 (SARS-CoV-2) (Coronavirus disease [COVID-19]) vaccine, mRNA-LNP, spike protein, preservative free, 100 mcg/0.5mL dosage, for intramuscular use' + }, + mvx: [ + { + cdcProductName: 'Moderna COVID-19 Vaccine', + shortDescription: 'COVID-19, mRNA, LNP-S, PF, 100 mcg/0.5 mL dose', + manufacturer: 'Moderna US, Inc.', + mvxCode: 'MOD', + status: 'Active' + } + ] + }, + { + shortDescription: 'COVID-19, mRNA, LNP-S, PF, 30 mcg/0.3 mL dose', + fullVaccineName: 'SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 30 mcg/0.3mL dose', + cvxCode: '208', + vaccineStatus: 'Active', + notes: 'EUA 12/11/2020, 2-dose vaccine', + cpt: { + cptCode: '91300', + vaccineName: 'COVID-19, mRNA, LNP-S, PF, 30 mcg/0.3 mL dose', + description: + 'Severe acute respiratory syndrome coronavirus 2 (SARS-CoV-2) (Coronavirus disease [COVID-19]) vaccine, mRNA-LNP, spike protein, preservative free, 30 mcg/0.3mL dosage, diluent reconstituted, for intramuscular use' + }, + mvx: [ + { + cdcProductName: 'Pfizer-BioNTech COVID-19 Vaccine', + shortDescription: 'COVID-19, mRNA, LNP-S, PF, 30 mcg/0.3 mL dose', + manufacturer: 'Pfizer, Inc', + mvxCode: 'PFR', + status: 'Active' + } + ] + }, + { + shortDescription: 'SARS-COV-2 (COVID-19) vaccine, UNSPECIFIED', + fullVaccineName: 'SARS-COV-2 (COVID-19) vaccine, UNSPECIFIED', + cvxCode: '213', + vaccineStatus: 'Inactive', + notes: 'Unspecified code for COVID-19 not to be used to record patient administration' + } +]; diff --git a/packages/immu-core/src/semantic/FhirHL7VaccinationCredential.ts b/packages/immu-core/src/semantic/FhirHL7VaccinationCredential.ts index 69cd187..e1c2776 100644 --- a/packages/immu-core/src/semantic/FhirHL7VaccinationCredential.ts +++ b/packages/immu-core/src/semantic/FhirHL7VaccinationCredential.ts @@ -1,79 +1,62 @@ +import { FHIRBundle } from '../@types/Fhir'; +import { CovidImmunization, ImmunizationTemplateParams, decodeDrugCode, Covid19Vaccinations } from './Covid19'; import ICheckCredentials from './ICheckCredentials'; -import { FHIRBundle, ImmunizationInputParams, FHIRResource } from '../@types/Fhir'; -import fhirTemplate from './templates/hl7_immunization.json'; +import Template from './templates/hl7_immunization'; export const TYPE = 'https://smarthealth.cards#covid19'; -//https://www2a.cdc.gov/vaccines/IIS/IISStandards/vaccines.asp?rpt=cvx -const knownCovid19CvxCodes = ['207', '208', '210', '212']; - export class FhirHL7VaccinationCredential extends ICheckCredentials { - protected checkForSchematicCorrectness(claim: Record): void { - if (!claim.fhirResource) { + protected normalize(claim: Record): CovidImmunization | undefined { + const { fhirResource } = claim; + if (!fhirResource) { throw Error("credential doesn't contain a FHIR resource"); } - } - - protected checkForContentCorrectness(claim: Record): void { - const { fhirResource } = claim; const { resource } = fhirResource; - - //todo: check the resource content | FHIR related if (resource.resourceType !== 'Immunization') return; //skip this one. const { coding } = resource.vaccineCode; - const sidCvxCode = coding.filter((coding: any) => coding.system === 'http://hl7.org/fhir/sid/cvx'); - if (!sidCvxCode) { - throw Error('we cannot recognize the immunization coding system'); - } + const vaccination = decodeDrugCode(coding[0].system, coding[0].code); - const vaccCode = sidCvxCode[0].code; - if (!knownCovid19CvxCodes.includes(vaccCode)) { - throw Error(`we don't recognize the vaccination code you received (${vaccCode})`); + if (!vaccination) { + throw new Error('couldnt decode the provided immunization code'); } - } - public checkClaimCombination(claims: Record[]): void { - if (claims.length !== 2) { - throw Error('you must present exactly 2 resources'); - } + const immunization: CovidImmunization = { + doseSequence: resource.protocolApplied?.doseNumberPositiveInt || 0, + lotNumber: resource.lotNumber || '', + occurrenceDateTime: new Date(resource.occurrenceDateTime), + cvxCode: vaccination.cvxCode, + cvx: vaccination + }; - const fhirResources = claims.map((claim) => claim.fhirResource); - const occurrenceTimes = fhirResources.map((fh) => new Date(fh.resource.occurrenceDateTime).getTime()); - const msDiff = Math.abs(occurrenceTimes[0] - occurrenceTimes[1]); - const dayDiff = msDiff / 1000 / 60 / 60 / 24; - if (dayDiff < 21) { - console.error(`the immunization dates are too close (${dayDiff})`); - //throw Error(`the immunization dates are too close (${dayDiff})`); - } + return immunization; } } -export const Create = (params: ImmunizationInputParams): FHIRBundle => { - //poor man's structured cloning - //https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript/10916838#10916838 - const fhir: FHIRResource = JSON.parse(JSON.stringify(fhirTemplate)); - - const qty = params.doseQuantity; - //todo: this must be corrected and depends on the vaccine code ;) - const doseText = `COVID-19, mRNA, LNP-S, PF, ${qty} mcg/${(qty / 100).toFixed(1)} mL dose`; - - fhir.resource.vaccineCode.coding = [ - { - code: params.vaccineCode, - display: doseText, - system: 'http://hl7.org/fhir/sid/cvx' +export const Create = (params: CovidImmunization): FHIRBundle => { + if (!params.cvx) { + const cvx = Covid19Vaccinations.find((code) => code.cvxCode == params.cvxCode); + if (!cvx) { + throw Error(`cant resolve cvx ${params.cvxCode}`); } - ]; + params.cvx = cvx; + } - fhir.resource.occurrenceDateTime = params.occurrenceDateTime.toISOString(); - fhir.resource.lotNumber = params.lotNumber; - fhir.resource.protocolApplied[0].doseNumberPositiveInt = params.doseNumber; - fhir.resource.doseQuantity.value = params.doseQuantity; + const templateParams: ImmunizationTemplateParams = { + ...params, + drug: { + code: { + description: params.cvx.shortDescription, + codeValue: params.cvxCode, + codingSystem: 'http://hl7.org/fhir/sid/cvx' + } + } + }; + const docString = Template(templateParams); return { fhirVersion: '4.0.1', - fhirResource: fhir + fhirResource: JSON.parse(docString) }; }; diff --git a/packages/immu-core/src/semantic/ICheckCredentials.ts b/packages/immu-core/src/semantic/ICheckCredentials.ts index 477b137..dcf97ff 100644 --- a/packages/immu-core/src/semantic/ICheckCredentials.ts +++ b/packages/immu-core/src/semantic/ICheckCredentials.ts @@ -1,6 +1,11 @@ import { Verifiable, W3CCredential } from 'did-jwt-vc'; import { Resolver } from '../Resolver'; import { Verifier } from '../Verifier'; +import { CovidImmunization } from './Covid19'; + +export interface VerifierFlags { + skipIssuerCheck?: boolean; +} export default abstract class ICheckCredentials { protected resolver: Resolver; @@ -11,25 +16,46 @@ export default abstract class ICheckCredentials { this.verifier = new Verifier(resolver); } - protected abstract checkForSchematicCorrectness(claim: Record): void; - protected abstract checkForContentCorrectness(claim: Record): void; - public abstract checkClaimCombination(claims: Record[]): void; + protected abstract normalize(claim: Record): CovidImmunization | undefined; + protected checkForContentCorrectness(immunization: CovidImmunization): void { + return; + } public async checkCredential( credential: Verifiable, flags?: VerifierFlags - ): Promise> { + ): Promise { if (!flags?.skipIssuerCheck) { await this.verifyIssuer(credential); + //todo: check if the credential has been revoked } - const { credentialSubject } = credential; - //todo: check if the credential has been revoked + const normalized = this.normalize(credential.credentialSubject); - this.checkForSchematicCorrectness(credentialSubject); - this.checkForContentCorrectness(credentialSubject); + if (normalized) { + this.checkForContentCorrectness(normalized); + } + return normalized; + } + + public static checkVaccinationCombination(immunizations: CovidImmunization[]): void { + if (immunizations.length !== 2) { + throw Error('you must present exactly 2 resources'); + } - return credentialSubject; + const [code1, code2] = [immunizations[0].cvxCode, immunizations[1].cvxCode]; + if (code1 != code2) { + throw Error(`[${code1}, ${code2}] are two different vaccinations. `); + } + + const treatmentDates = immunizations.map((vacc) => vacc.occurrenceDateTime.getTime()); + const msDiff = Math.abs(treatmentDates[0] - treatmentDates[1]); + const dayDiff = msDiff / 1000 / 60 / 60 / 24; + + if (dayDiff < 21) { + console.error(`the immunization dates are too close (${dayDiff})`); + //throw Error(`the immunization dates are too close (${dayDiff})`); + } } private async lookupPractitionerCredential( @@ -70,7 +96,3 @@ export default abstract class ICheckCredentials { } } } - -export interface VerifierFlags { - skipIssuerCheck?: boolean; -} diff --git a/packages/immu-core/src/semantic/SchemaOrgCredential.ts b/packages/immu-core/src/semantic/SchemaOrgCredential.ts deleted file mode 100644 index b453930..0000000 --- a/packages/immu-core/src/semantic/SchemaOrgCredential.ts +++ /dev/null @@ -1,69 +0,0 @@ -import ICheckCredentials from './ICheckCredentials'; -import { ImmunizationInputParams } from '../@types/Fhir'; -import template from './templates/schemaorg_immunization.json'; - -export const TYPE = 'https://schema.org#MedicalRecord-Vaccination'; -const knownCovid19CvxCodes = ['207', '208', '210', '212']; - -export class SchemaOrgVaccinationCredential extends ICheckCredentials { - protected checkForSchematicCorrectness(claim: Record): void { - if (!claim['schema:primaryPrevention']) throw Error("credential doesn't contain a prevention element"); - } - - protected checkForContentCorrectness(claim: Record): void { - const prevention = claim['schema:primaryPrevention']; - if (prevention['@type'] !== 'schema:MedicalTherapy-Vaccination') { - return; - } - - const drug = prevention['schema:drug']; - - const cvxId = drug['schema:identifier-cvx'].substr(4); - if (!knownCovid19CvxCodes.includes(cvxId)) { - throw Error(`we don't recognize the vaccination code you received (${drug['schema:identifier-cvx']})`); - } - } - - public checkClaimCombination(claims: Record[]): void { - if (claims.length !== 2) { - throw Error('you must present exactly 2 resources'); - } - - const treatmentDates = claims.map((claim) => new Date(claim['schema:treatmentDate']).getTime()); - const msDiff = Math.abs(treatmentDates[0] - treatmentDates[1]); - const dayDiff = msDiff / 1000 / 60 / 60 / 24; - - if (dayDiff < 21) { - console.error(`the immunization dates are too close (${dayDiff})`); - //throw Error(`the immunization dates are too close (${dayDiff})`); - } - } -} - -export const Create = (params: ImmunizationInputParams): any => { - //poor man's structured cloning - //https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript/10916838#10916838 - const doc: any = JSON.parse(JSON.stringify(template)); - - const qty = params.doseQuantity; - const doseText = `COVID-19, mRNA, LNP-S, PF, ${qty} mcg/${(qty / 100).toFixed(1)} mL dose`; - - const mvxCode = `MVX-XXX`; - const manufacturerName = 'XXX Industries Unknown Inc.'; - const cvxCode = `CVX-${params.vaccineCode}`; - const schemaIdentifier = `${mvxCode}.${cvxCode}`; - - doc['schema:primaryPrevention']['schema:identifier'] = schemaIdentifier; - doc['schema:primaryPrevention']['schema:drug']['schema:identifier'] = schemaIdentifier; - doc['schema:primaryPrevention']['schema:drug']['schema:code']['schema:codeValue'] = schemaIdentifier; - doc['schema:primaryPrevention']['schema:drug']['schema:description'] = doseText; - doc['schema:primaryPrevention']['schema:drug']['schema:identifier-cvx'] = cvxCode; - doc['schema:primaryPrevention']['schema:drug']['schema:manufacturer']['schema:name'] = manufacturerName; - doc['schema:primaryPrevention']['schema:drug']['schema:manufacturer']['schema:identifier'] = mvxCode; - doc['schema:primaryPrevention']['schema:drug']['schema:manufacturer']['schema:identifier-mvx'] = mvxCode; - doc['schema:primaryPrevention']['schema:identifier-cvx'] = cvxCode; - doc['schema:primaryPrevention']['schema:identifier-mvx'] = mvxCode; - doc['schema:treatmentDate'] = params.occurrenceDateTime.toISOString(); - - return doc; -}; diff --git a/packages/immu-core/src/semantic/SchemaOrgVaccinationCredential.ts b/packages/immu-core/src/semantic/SchemaOrgVaccinationCredential.ts new file mode 100644 index 0000000..ec67a18 --- /dev/null +++ b/packages/immu-core/src/semantic/SchemaOrgVaccinationCredential.ts @@ -0,0 +1,68 @@ +import { Covid19Vaccinations, CovidImmunization, decodeDrugCode, ImmunizationTemplateParams } from './Covid19'; +import ICheckCredentials from './ICheckCredentials'; +import Template from './templates/schemaorg_immunization'; + +export const TYPE = 'https://schema.org#ImmunizationRecord'; + +export class SchemaOrgVaccinationCredential extends ICheckCredentials { + protected normalize(claim: Record): CovidImmunization | undefined { + const { primaryPrevention } = claim; + if (!primaryPrevention) throw Error("credential doesn't contain a prevention element"); + + if (primaryPrevention['@type'] !== 'ImmunizationRecommendation') { + return; + } + + const vaccination = decodeDrugCode(primaryPrevention.drug.code.codingSystem, primaryPrevention.drug.code.codeValue); + //todo (very low prio): check whether a contained manufacturer information matches the decoded cvx + if (!vaccination) { + throw new Error('couldnt decode the provided immunization code'); + } + + const immunization: CovidImmunization = { + description: claim.name, + doseSequence: claim.doseSequence || 0, + lotNumber: claim.lotNumber || '', + occurrenceDateTime: new Date(claim.immunizationDate), + cvxCode: vaccination.cvxCode, + cvx: vaccination + }; + + return immunization; + } +} + +/** + * based on //https://docs.google.com/document/d/1pCyS_lhbMGhOkq1jFEkI_od-9QunURKzGWA7ty5DCII/edit + */ +export const Create = (params: CovidImmunization): any => { + if (!params.cvx) { + const cvx = Covid19Vaccinations.find((code) => code.cvxCode == params.cvxCode); + if (!cvx) { + throw Error(`cant resolve cvx ${params.cvxCode}`); + } + params.cvx = cvx; + } + + if (!params.cvx.mvx) { + //todo: use a shorter schema then ;) + throw Error('no MVX code available for that vaccine.'); + } + + const templateParams: ImmunizationTemplateParams = { + ...params, + drug: { + code: { + codingSystem: 'CDC-MVX.CVX', + codeValue: `MVX-${params.cvx.mvx[0].mvxCode}.CVX-${params.cvxCode}` + }, + manufacturer: { + identifier: `MVX-${params.cvx.mvx[0].mvxCode}`, + name: params.cvx.mvx[0].manufacturer + } + } + }; + + const docString = Template(templateParams); + return JSON.parse(docString); +}; diff --git a/packages/immu-core/src/semantic/VaccinationCredentialVerifier.ts b/packages/immu-core/src/semantic/VaccinationCredentialVerifier.ts index 3e12df5..0ea27de 100644 --- a/packages/immu-core/src/semantic/VaccinationCredentialVerifier.ts +++ b/packages/immu-core/src/semantic/VaccinationCredentialVerifier.ts @@ -1,9 +1,11 @@ import { VerifiableCredential } from 'did-jwt-vc'; + import { Resolver } from '../Resolver'; import { Verifier } from '../Verifier'; import ICheckCredentials, { VerifierFlags } from './ICheckCredentials'; import { FhirHL7VaccinationCredential, TYPE as SMARTHEALTH_CARD_CRED_TYPE } from './FhirHL7VaccinationCredential'; -import { SchemaOrgVaccinationCredential, TYPE as SCHEMAORG_CRED_TYPE } from './SchemaOrgCredential'; +import { SchemaOrgVaccinationCredential, TYPE as SCHEMAORG_CRED_TYPE } from './SchemaOrgVaccinationCredential'; +import { CovidImmunization } from './Covid19'; /** * verifies credentials using pluggable validation strategies @@ -18,7 +20,7 @@ export default class VaccinationCredentialVerifier { this.resolver = resolver; this.verifier = new Verifier(resolver); } - //isKnownIssuer = (did: string) => {}; + initialize() { this.strategies = { [SMARTHEALTH_CARD_CRED_TYPE]: new FhirHL7VaccinationCredential(this.resolver), @@ -30,31 +32,32 @@ export default class VaccinationCredentialVerifier { return Object.keys(this.strategies); } + public getStrategy(types: string[]) { + const strategyType = this.supportedStrategies.find((t) => types.includes(t)); + if (!strategyType) { + throw Error('dont have a verification strategy for this credential type'); + } + return this.strategies[strategyType]; + } + async verify(presentedCredentials: VerifiableCredential[], flags?: VerifierFlags) { - const appliedStrategies: Set = new Set(); - const verifiedClaims: Record[] = []; + const immunizations: CovidImmunization[] = []; for await (const credential of presentedCredentials) { const verifiedCredential = await this.verifier.verifyCredential(credential); try { - const strategyType = this.supportedStrategies.find((t) => verifiedCredential.type.includes(t)); - if (!strategyType) { - throw Error('dont have a verification strategy for this credential type'); - } - - const iCheckCredentials = this.strategies[strategyType]; - appliedStrategies.add(iCheckCredentials); + const iCheckCredentials = this.getStrategy(verifiedCredential.type); - const verifiedClaim = await iCheckCredentials.checkCredential(verifiedCredential, flags); - verifiedClaims.push(verifiedClaim); + const immunization = await iCheckCredentials.checkCredential(verifiedCredential, flags); + if (immunization) { + immunizations.push(immunization); + } } catch (e) { console.error(e); } } - for (const strategy of appliedStrategies) { - strategy.checkClaimCombination(verifiedClaims); - } + ICheckCredentials.checkVaccinationCombination(immunizations); return true; } diff --git a/packages/immu-core/src/semantic/templates/hl7_immunization.json b/packages/immu-core/src/semantic/templates/hl7_immunization.ts similarity index 61% rename from packages/immu-core/src/semantic/templates/hl7_immunization.json rename to packages/immu-core/src/semantic/templates/hl7_immunization.ts index 560910f..a824ef0 100644 --- a/packages/immu-core/src/semantic/templates/hl7_immunization.json +++ b/packages/immu-core/src/semantic/templates/hl7_immunization.ts @@ -1,3 +1,8 @@ +import HBS from 'handlebars'; + +HBS.registerHelper('date', (date: Date) => date.toISOString()); + +const Template = HBS.compile(` { "resource": { "resourceType": "Immunization", @@ -8,11 +13,17 @@ ] }, "vaccineCode": { - "coding": [] + "coding": [ + { + "system": "{{ drug.code.codingSystem }}", + "code": "{{ drug.code.codeValue }}", + "display": "{{ drug.code.description }}" + } + ] }, - "occurrenceDateTime": "", + "occurrenceDateTime": "{{date occurrenceDateTime}}", "primarySource": true, - "lotNumber": "", + "lotNumber": "{{ lotNumber }}", "protocolApplied": [ { "targetDisease": [ @@ -26,14 +37,16 @@ ] } ], - "doseNumberPositiveInt": 0, + "doseNumberPositiveInt": {{ doseSequence }}, "seriesDosesPositiveInt": 2 } ], "doseQuantity": { "system": "http://unitsofmeasure.org", - "value": 0, + "value": {{ doseQuantity }}, "code": "ml" } } -} \ No newline at end of file +}`); + +export default Template; diff --git a/packages/immu-core/src/semantic/templates/schemaorg_immunization.json b/packages/immu-core/src/semantic/templates/schemaorg_immunization.json deleted file mode 100644 index b33232b..0000000 --- a/packages/immu-core/src/semantic/templates/schemaorg_immunization.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@context": { - "schema:": "https://schema.org", - "security": "https://w3id.org/security#" - }, - "@type": "schema:MedicalRecord-Vaccination", - "schema:name": "COVID-19 Immunization", - "schema:primaryPrevention": { - "@context": { - "schema:": "https://schema.org" - }, - "@type": "schema:MedicalTherapy-Vaccination", - "schema:name": "COVID-19 Vaccine", - "schema:identifier": "MVX-MOD.CVX-207", - "schema:drug": { - "@type": "schema:Drug", - "schema:name": "COVID-19 Vaccine", - "schema:identifier": "MVX-MOD.CVX-207", - "schema:code": { - "@type": "schema:MedicalCode", - "schema:codeValue": "MVX-MOD.CVX-207", - "schema:codingSystem": "CDC-MVX.CVX" - }, - "schema:description": "COVID-19, mRNA, LNP-S, PF, 100 mcg/0.5 mL dose", - "schema:identifier-cvx": "CVX-207", - "schema:manufacturer": { - "@type": "schema:Organization-CDC-MVX", - "schema:name": "Moderna US, Inc.", - "schema:identifier": "MVX-MOD", - "schema:identifier-mvx": "MVX-MOD" - } - }, - "schema:identifier-cvx": "CVX-207", - "schema:identifier-mvx": "MVX-MOD" - }, - "schema:treatmentDate": "2021-01-06" -} \ No newline at end of file diff --git a/packages/immu-core/src/semantic/templates/schemaorg_immunization.ts b/packages/immu-core/src/semantic/templates/schemaorg_immunization.ts new file mode 100644 index 0000000..a55a204 --- /dev/null +++ b/packages/immu-core/src/semantic/templates/schemaorg_immunization.ts @@ -0,0 +1,47 @@ +import HBS from 'handlebars'; + +HBS.registerHelper('date', (date: Date) => date.toISOString()); + +const Template = HBS.compile(` +{ + "@context": { + "schema:": "https://schema.org", + "security": "https://w3id.org/security#" + }, + "@type": "ImmunizationRecord", + "name": "COVID-19 Immunization", + "patient": {}, + "location": {}, + "primaryPrevention": { + "@type": "ImmunizationRecommendation", + "drug": { + "@type": "Drug", + "name": "{{ drug.name }}", + "code": { + "@type": "MedicalCode", + "codingSystem": "{{ drug.code.codingSystem }}", + "codeValue": "{{ drug.code.codeValue }}" + }, + {{#drug.manufacturer}} + "manufacturer": { + "@type": "Organization-CDC-MVX", + "identifier": "{{ identifier }}", + "name": "{{ name }}" + } + {{/drug.manufacturer}} + }, + "healthCondition": { + "@type": "MedicalCondition", + "code": { + "@type": "MedicalCode", + "codeValue": "U07", + "codingSystem": "ICD-10" + } + } + }, + "doseSequence": {{ doseSequence }}, + "lotNumber": "{{ lotNumber }}", + "immunizationDate": "{{date occurrenceDateTime}}" +}`); + +export default Template; diff --git a/packages/immu-frontend/src/context/IdentityContext.tsx b/packages/immu-frontend/src/context/IdentityContext.tsx index c06504c..3aaaf4a 100644 --- a/packages/immu-frontend/src/context/IdentityContext.tsx +++ b/packages/immu-frontend/src/context/IdentityContext.tsx @@ -7,8 +7,7 @@ import { DID, EthRegistry, Issuer, Resolver, Verifier } from '@immu/core'; const PRIVATE_KEY = 'private-key'; const ETH_NETWORKS: { [network: string]: string } = { - 'main':'1', - 'ropsten':'3', + 'mainnet':'1', 'rinkeby': '4', 'goerli': '42', 'development': '1337' @@ -49,9 +48,9 @@ const createAccount = (web3: Web3): Account => { const didByChainId = (account: Account, chainId: string): DID => { const address = account.address.toLowerCase(); - return (Object.keys(ETH_NETWORKS).includes(chainId)) - ? `did:ethr:${chainId}:${address}` - : `did:ethr:${address}`; + return ((chainId === 'mainnet') || (!Object.keys(ETH_NETWORKS).includes(chainId))) + ? `did:ethr:${address}` + : `did:ethr:${chainId}:${address}`; } const makeContext = (web3: Web3, chainId: string): IAccountContext => { @@ -85,6 +84,9 @@ const useIdentity = () => useContext(IdentityContext); const IdentityProvider = ({ children, chainId = 'development', web3 }: { children: React.ReactNode, chainId?: string, web3?: Web3 }) => { + if (!chainId) { + chainId = 'development'; + } //const { activate } = useWeb3React(); //const registry = new EthRegistry(resolverConfig, web3); diff --git a/packages/immu-frontend/src/molecules/CredentialCard.tsx b/packages/immu-frontend/src/molecules/CredentialCard.tsx index 14e5a1a..5d2443d 100644 --- a/packages/immu-frontend/src/molecules/CredentialCard.tsx +++ b/packages/immu-frontend/src/molecules/CredentialCard.tsx @@ -1,7 +1,7 @@ import { Box, Code, Flex, Heading, Text } from '@chakra-ui/react'; -import { VerifiableCredential } from '@immu/core'; -import React from 'react'; -import { SCHEMAORG_CARD_CRED_TYPE, SMARTHEALTH_CARD_CRED_TYPE } from '@immu/core'; +import { Covid19, VerifiableCredential } from '@immu/core'; +import React, {useEffect, useState} from 'react'; +import { useCredentialVerifier } from '..'; const CredentialCard = ({ credential, @@ -13,9 +13,22 @@ const CredentialCard = ({ onSelect?: (credential: VerifiableCredential) => unknown; }) => { const bg = 'teal.300'; - if (typeof credential === 'string') throw Error('noooo'); + const {credentialVerifier} = useCredentialVerifier(); + const [immunization, setImmunization] = useState(); + + useEffect(() => { + (async () => { + try { + const iCheckCredentials = credentialVerifier.getStrategy(credential.type); + setImmunization(await iCheckCredentials.checkCredential(credential)); + } catch(e) { + console.debug(e.message) + } + })() + }, []) + const vm: Record = { types: typeof credential.type === 'string' @@ -23,19 +36,7 @@ const CredentialCard = ({ : credential.type.filter((t) => t !== 'VerifiableCredential'), issued: new Date(credential.issuanceDate).toLocaleDateString(), issuer: credential.issuer.id - }; - if (credential.type.includes(SMARTHEALTH_CARD_CRED_TYPE)) { - const { fhirResource } = credential.credentialSubject; - vm.resourceType = fhirResource.resource.resourceType; - if (fhirResource.resource.occurrenceDateTime) { - vm.occurred = new Date(fhirResource.resource.occurrenceDateTime).toISOString() - } - } else if (credential.type.includes(SCHEMAORG_CARD_CRED_TYPE)) { - const doc = credential.credentialSubject; - vm.resourceType = doc['schema:name']; - vm.occurred = new Date(doc['schema:treatmentDate']).toISOString() - } - + }; return ( onSelect(credential)} > {vm.types.map((type: string) => ( - + {type} ))} {vm.resourceType} - {vm.occurred && - occurred on {vm.occurred} - } + {immunization && + <> + + vaccine: {immunization.cvx?.shortDescription} + + + sequence: {immunization.doseSequence} + + + occurred on {immunization.occurrenceDateTime.toLocaleDateString()} + + + } issued on: {vm.issued}{' '} diff --git a/packages/immu-patient/src/pages/Credentials.tsx b/packages/immu-patient/src/pages/Credentials.tsx index d0b5080..93b82ff 100644 --- a/packages/immu-patient/src/pages/Credentials.tsx +++ b/packages/immu-patient/src/pages/Credentials.tsx @@ -1,11 +1,11 @@ -import { Box, Button, Divider, Flex, Heading, Text, VStack } from '@chakra-ui/react'; +import { Box, Divider, Flex, Heading, Text, VStack } from '@chakra-ui/react'; import { VerifiableCredential } from '@immu/core'; -import { useCredentials } from 'hooks/CredentialStorage'; import { CredentialCard } from '@immu/frontend'; +import { SwipeableListItem } from '@sandstreamdev/react-swipeable-list'; +import '@sandstreamdev/react-swipeable-list/dist/styles.css'; +import { useCredentials } from 'hooks/CredentialStorage'; import AcceptCredentialOffer from 'organisms/AcceptCredentialOffer'; import React from 'react'; -import { SwipeableList, SwipeableListItem } from '@sandstreamdev/react-swipeable-list'; -import '@sandstreamdev/react-swipeable-list/dist/styles.css'; const CredentialsPage = () => { const { credentials, removeCredential } = useCredentials(); @@ -23,8 +23,9 @@ const CredentialsPage = () => { return ( <> {k} - {credentials[k].map((credential: VerifiableCredential) => ( + {credentials[k].map((credential: VerifiableCredential, i) => ( diff --git a/packages/immu-provider/src/molecules/NavBar.tsx b/packages/immu-provider/src/molecules/NavBar.tsx index b8a894d..bbc920f 100644 --- a/packages/immu-provider/src/molecules/NavBar.tsx +++ b/packages/immu-provider/src/molecules/NavBar.tsx @@ -4,7 +4,7 @@ import React from 'react'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore import Identicon from 'react-identicons'; -import { Link as RLink, NavLink } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; const Navbar = () => { const { did } = useIdentity(); diff --git a/packages/immu-provider/src/organisms/ImmunizationForm.tsx b/packages/immu-provider/src/organisms/ImmunizationForm.tsx index 17bde8b..4ef968d 100644 --- a/packages/immu-provider/src/organisms/ImmunizationForm.tsx +++ b/packages/immu-provider/src/organisms/ImmunizationForm.tsx @@ -1,30 +1,48 @@ -import { Button, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input, Select } from '@chakra-ui/react'; -import { ImmunizationInputParams, SCHEMAORG_CARD_CRED_TYPE, SMARTHEALTH_CARD_CRED_TYPE } from '@immu/core'; +import { + Button, + FormControl, + FormErrorMessage, + FormHelperText, + FormLabel, + Input, + Link, + Select, + Text +} from '@chakra-ui/react'; +import { Covid19, SCHEMAORG_CRED_TYPE, SMARTHEALTH_CARD_CRED_TYPE } from '@immu/core'; + import { useForm } from 'react-hook-form'; const ImmunizationForm = ({ onImmunizationCreated }: { - onImmunizationCreated: (params: ImmunizationInputParams, type: string) => void; + onImmunizationCreated: (params: Covid19.CovidImmunization, type: string) => void; }) => { const { register, handleSubmit, watch, errors } = useForm(); - - const qty = watch('doseQuantity', 0); - const doseText = `COVID-19, mRNA, LNP-S, PF, ${qty} mcg/${(qty / 100).toFixed(1)} mL dose`; + const cvxCode = watch('cvxCode', 0); + const selectedVaccine = Covid19.Covid19Vaccinations.find((vacc) => vacc.cvxCode === cvxCode); const onSubmit = (data: any) => { - const credentialParams: ImmunizationInputParams = { - doseNumber: parseInt(data.doseNumber), + const immunization: Covid19.CovidImmunization = { + doseSequence: parseInt(data.doseSequence), doseQuantity: parseInt(data.doseQuantity), lotNumber: data.lotNumber, occurrenceDateTime: new Date(), - vaccineCode: data.vaccineCode + cvxCode: data.cvxCode }; - onImmunizationCreated(credentialParams, data.credentialType); + onImmunizationCreated(immunization, data.credentialType); }; return (
+ + Credential type + + + Lot Number We need a lot number - + vaccineCode - + {Covid19.Covid19Vaccinations.map((vacc) => ( + + ))} + {selectedVaccine && {selectedVaccine.shortDescription}} See{' '} - cdc.gov's vaccine codes - + - - Dose Number - @@ -73,16 +88,13 @@ const ImmunizationForm = ({ Dose Quantity (mcg) - - {doseText} - - - - Credential type - + ) : ( diff --git a/packages/immu-provider/src/pages/IndexPage.tsx b/packages/immu-provider/src/pages/IndexPage.tsx index 853c12e..7261c04 100644 --- a/packages/immu-provider/src/pages/IndexPage.tsx +++ b/packages/immu-provider/src/pages/IndexPage.tsx @@ -1,10 +1,10 @@ import { Issuer, - CreateFhirHL7VaccinationCredential, - CreateSchemaOrgVaccinationCredential, + CreateFhirHL7Immunization, + CreateSchemaOrgImmunization, SMARTHEALTH_CARD_CRED_TYPE, - SCHEMAORG_CARD_CRED_TYPE, - ImmunizationInputParams + SCHEMAORG_CRED_TYPE, + Covid19 } from '@immu/core'; import { useIdentity } from '@immu/frontend'; import ImmunizationForm from 'organisms/ImmunizationForm'; @@ -65,15 +65,14 @@ const IndexPage: React.FC = () => { }); }; - const Creators: Record any> = { - [SMARTHEALTH_CARD_CRED_TYPE]: CreateFhirHL7VaccinationCredential, - [SCHEMAORG_CARD_CRED_TYPE]: CreateSchemaOrgVaccinationCredential + const Creators: Record any> = { + [SMARTHEALTH_CARD_CRED_TYPE]: CreateFhirHL7Immunization, + [SCHEMAORG_CRED_TYPE]: CreateSchemaOrgImmunization }; - const onImmunizationCreated = async (credentialParams: ImmunizationInputParams, credentialType: string) => { + const onImmunizationCreated = async (credentialParams: Covid19.CovidImmunization, credentialType: string) => { const credentialSubject = Creators[credentialType](credentialParams); const interactionToken = bs58.encode(crypto.randomBytes(32)); - const offerRequest: CredentialOfferRequestAttrs = { callbackURL: `${process.env.REACT_APP_COMM_SERVER}/comm/${interactionToken}?flow=credentialOfferResponse`, offeredCredentials: [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd912f6..fafce3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,6 +158,7 @@ importers: ethereumjs-util: 7.0.7 ethr-did-registry: 0.0.3 ethr-did-resolver: github.com/cod1ng-earth/ethr-did-resolver/1857c9761f2a3a750165055133b43910322e6cef + handlebars: 4.7.7 jsonld: 4.0.1 key-did-resolver: 0.2.4 secp256k1: 4.0.2 @@ -173,6 +174,7 @@ importers: '@babel/preset-typescript': 7.12.7_@babel+core@7.12.10 '@truffle/contract': 4.3.5 '@types/dotenv-flow': 3.1.0 + '@types/handlebars': 4.1.0 '@types/jest': 26.0.20 '@types/jsonld': 1.5.2 '@types/node': 12.19.12 @@ -204,6 +206,7 @@ importers: '@transmute/lds-ecdsa-secp256k1-2019': ^0.1.3 '@truffle/contract': ^4.3.5 '@types/dotenv-flow': ^3.1.0 + '@types/handlebars': ^4.1.0 '@types/jest': ^26.0.20 '@types/jsonld': ^1.5.2 '@types/node': ^12.0.0 @@ -225,6 +228,7 @@ importers: ethr-did-registry: ^0.0.3 ethr-did-resolver: git@github.com:cod1ng-earth/ethr-did-resolver.git ganache-cli: ^6.12.2 + handlebars: ^4.7.7 jest: ^26.6.3 jsonld: ^4.0.1 key-did-resolver: ^0.2.4 @@ -5365,6 +5369,13 @@ packages: '@types/node': 14.14.25 resolution: integrity: sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg== + /@types/handlebars/4.1.0: + dependencies: + handlebars: 4.7.7 + deprecated: This is a stub types definition. handlebars provides its own type definitions, so you do not need this installed. + dev: true + resolution: + integrity: sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA== /@types/history/4.7.8: dev: true resolution: @@ -9183,7 +9194,7 @@ packages: bn.js: 2.0.4 brorand: 1.1.0 hash.js: 1.1.7 - inherits: 2.0.4 + inherits: 2.0.1 dev: false resolution: integrity: sha1-hlybQgv75VAGuflp+XoNLESWZZU= @@ -11433,6 +11444,19 @@ packages: dev: false resolution: integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + /handlebars/4.7.7: + dependencies: + minimist: 1.2.5 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + engines: + node: '>=0.4.7' + hasBin: true + optionalDependencies: + uglify-js: 3.12.8 + resolution: + integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== /har-schema/2.0.0: engines: node: '>=4' @@ -14389,7 +14413,6 @@ packages: resolution: integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== /neo-async/2.6.2: - dev: false resolution: integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== /next-tick/1.0.0: @@ -18920,6 +18943,13 @@ packages: hasBin: true resolution: integrity: sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== + /uglify-js/3.12.8: + engines: + node: '>=0.8.0' + hasBin: true + optional: true + resolution: + integrity: sha512-fvBeuXOsvqjecUtF/l1dwsrrf5y2BCUk9AOJGzGcm6tE7vegku5u/YvqjyDaAGr422PLoLnrxg3EnRvTqsdC1w== /uint8arrays/1.1.0: dependencies: multibase: 3.0.1 @@ -20181,6 +20211,9 @@ packages: node: '>=0.10.0' resolution: integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + /wordwrap/1.0.0: + resolution: + integrity: sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= /workbox-background-sync/5.1.4: dependencies: workbox-core: 5.1.4