diff --git a/package.json b/package.json index 6e3f703..a9d8bf2 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "credo-ts-media-sharing": "^0.0.1-alpha.9", "credo-ts-receipts": "^0.0.1-alpha.5", "express": "^4.18.1", + "mrz": "^4.2.0", "qrcode": "^1.5.3", "reflect-metadata": "^0.1.13", "swagger-ui-express": "^4.4.0", diff --git a/src/model/messages/mrtd/EMrtdDataSubmitMessage.ts b/src/model/messages/mrtd/EMrtdDataSubmitMessage.ts index da5dc5e..8ed092b 100644 --- a/src/model/messages/mrtd/EMrtdDataSubmitMessage.ts +++ b/src/model/messages/mrtd/EMrtdDataSubmitMessage.ts @@ -1,9 +1,37 @@ import { EMrtdData } from '@2060.io/credo-ts-didcomm-mrtd' import { Expose } from 'class-transformer' +import * as Mrz from 'mrz' import { BaseMessage, BaseMessageOptions } from '../BaseMessage' import { MessageType } from '../MessageType' +export type EMrtdRawData = { + raw: Record + processed: { + documentType: string + documentNumber?: string + issuingState?: string + dateOfBirth?: string + dateOfExpiry?: string + sex?: string + nationality?: string + lastName?: string + firstName?: string + mrzOptionalData?: string + faceImages: string[] + nameOfHolder?: string + otherNames?: string[] + personalNumber?: string + placeOfBirth?: string[] + permanentAddress?: string[] + telephone?: string + profession?: string + title?: string + personalSummary?: string + custodyInformation?: string + } +} + export interface EMrtdDataSubmitMessageOptions extends BaseMessageOptions { dataGroups: EMrtdData } @@ -17,7 +45,7 @@ export class EMrtdDataSubmitMessage extends BaseMessage { this.threadId = options.threadId this.timestamp = options.timestamp ?? new Date() this.connectionId = options.connectionId - this.dataGroups = options.dataGroups + this.dataGroups = this.parseDataGroups(options.dataGroups) } } @@ -25,5 +53,66 @@ export class EMrtdDataSubmitMessage extends BaseMessage { public static readonly type = MessageType.EMrtdDataSubmitMessage @Expose() - public dataGroups!: EMrtdData + public dataGroups!: EMrtdRawData + + public parseDataGroups({ raw, parsed }: EMrtdData): EMrtdRawData { + if (!parsed || !parsed.fields) return this.dataGroups + + const dataUrls = parsed.fields.images.map(image => { + const faceMimeType = image.imageType === 1 ? 'image/jp2' : 'image/jpeg' + return `data:${faceMimeType};base64,${image.imageData.toString('base64')}` + }) + + const mrzString = parsed.fields.mrzData + const formattedMrz = (len => { + switch (len) { + case 88: + return mrzString.slice(0, 44) + '\n' + mrzString.slice(44) + case 72: + return mrzString.slice(0, 36) + '\n' + mrzString.slice(36) + case 90: + return mrzString.slice(0, 30) + '\n' + mrzString.slice(30, 60) + '\n' + mrzString.slice(60) + case 30: + return mrzString + case 18: + return mrzString.slice(0, 9) + '\n' + mrzString.slice(9) + default: + throw new Error(`Unrecognized MRZ length: ${len}`) + } + })(mrzString.length) + const parsedMrz = Mrz.parse(formattedMrz) + + const newEmrtdData: EMrtdRawData = { + raw: raw, + processed: { + documentType: parsedMrz.format, + documentNumber: parsedMrz.documentNumber ?? undefined, + issuingState: parsedMrz.fields.issuingState ?? undefined, + dateOfExpiry: parsedMrz.fields.expirationDate ?? undefined, // TODO: Check and specify date format + sex: parsedMrz.fields.sex ?? undefined, + nationality: parsedMrz.fields.nationality ?? undefined, + lastName: parsedMrz.fields.lastName ?? undefined, + firstName: parsedMrz.fields.firstName ?? undefined, + mrzOptionalData: parsedMrz.fields.optional1 ?? undefined, + faceImages: dataUrls, + nameOfHolder: + parsed.fields.additionalPersonalData?.nameOfHolder ?? + `${parsedMrz.fields.lastName} ${parsedMrz.fields.firstName}`, + dateOfBirth: + parsed.fields.additionalPersonalData?.fullDateOfBirth.toString() ?? + parsedMrz.fields.birthDate ?? + undefined, // TODO: Check and specify date format + otherNames: parsed.fields.additionalPersonalData?.otherNames, + personalNumber: parsed.fields.additionalPersonalData?.personalNumber, + placeOfBirth: parsed.fields.additionalPersonalData?.placeOfBirth, + permanentAddress: parsed.fields.additionalPersonalData?.permanentAddress, + telephone: parsed.fields.additionalPersonalData?.telephone, + profession: parsed.fields.additionalPersonalData?.profession, + title: parsed.fields.additionalPersonalData?.title, + personalSummary: parsed.fields.additionalPersonalData?.personalSummary, + custodyInformation: parsed.fields.additionalPersonalData?.custodyInformation, + }, + } + return newEmrtdData + } }