@parselo/scanner-core
v0.2.0
Published
Platform-agnostic core: AAMVA PDF417 parsing, offline license verification, and PII-free scan analytics.
Downloads
466
Readme
@parselo/scanner-core
Platform-agnostic core for the Parselo Canadian ID scanning SDK. Parses AAMVA PDF417 barcodes, magnetic-stripe tracks, and ICAO 9303 MRZ from passports and travel documents into a unified canonical document shape. Includes offline ES256 license enforcement and PII-free usage analytics.
No Capacitor dependency — fully testable in Node.
What it parses
| Document type | Format | Notes |
|---|---|---|
| Canadian / US driver's licences & ID cards | PDF417 (AAMVA) | All provinces; dates normalised to ISO |
| BC / ON / AB licences (older stock) | Magnetic-stripe tracks encoded in PDF417 | Three-track %…? format |
| Passports, emergency travel docs, PR cards | ICAO 9303 TD3 MRZ (2 × 44 chars) | Five check digits validated; VIZ cross-reference corrects < OCR corruption |
All document types produce the same CanonicalDocument shape with identical field names and ISO YYYY-MM-DD dates.
Install
npm install @parselo/scanner-coreA native capture plugin is required for on-device use:
| Use case | Plugin |
|---|---|
| Driver's licence / barcode | @parselo/capacitor-pdf417 |
| Passport / travel document | @parselo/capacitor-mrz |
Scanning a driver's licence
import { Scanner, type BarcodeNative } from "@parselo/scanner-core";
import { Pdf417 } from "@parselo/capacitor-pdf417";
const native: BarcodeNative = {
captureAndDecodePdf417: async () => {
const { raw } = await Pdf417.decodePdf417({ image: dataUrl });
return raw; // null if no barcode found
},
};
const scanner = new Scanner({ license, analytics, native });
await scanner.init();
const result = await scanner.scan();
if (result.ok && result.document) {
const { fields, jurisdiction } = result.document;
console.log(fields.firstName?.value, fields.lastName?.value);
console.log(fields.dateOfBirth?.value); // "1985-03-12"
console.log(jurisdiction); // "CA-QC"
}Scanning a passport or travel document
import { Scanner, type MrzNative } from "@parselo/scanner-core";
import { Mrz } from "@parselo/capacitor-mrz";
const mrzNative: MrzNative = {
captureAndRecognizeMrz: async () => {
const { lines } = await Mrz.recognizeText({ image: dataUrl });
return lines; // string[] of OCR observations
},
};
const scanner = new Scanner({ license, analytics, native, mrzNative });
await scanner.init();
const result = await scanner.scanPassport();
if (result.ok && result.document) {
const { fields, jurisdiction } = result.document;
console.log(fields.firstName?.value, fields.lastName?.value);
console.log(fields.dateOfBirth?.value); // "1978-11-28"
console.log(fields.country?.value); // "MEX" (ICAO 3-char)
console.log(jurisdiction); // "CA" for Canadian-issued docs
}Handles standard passports (P<), emergency travel documents (PU), permanent
resident travel documents (PR), and foreign passports. The parser cross-references
Vision's biographical-zone OCR lines to recover names even when the OCR-B < fill
character is misread.
Canonical document shape
interface CanonicalDocument {
documentType: "drivers_license" | "id_card" | "passport" | "unknown";
jurisdiction: string; // "CA-QC", "CA-BC", "CA", "MEX", …
fields: {
// All document types
firstName?: Field;
lastName?: Field;
middleName?: Field;
dateOfBirth?: Field; // ISO YYYY-MM-DD
expiryDate?: Field; // ISO YYYY-MM-DD
documentNumber?: Field;
sex?: Field;
// Driver's licences
addressStreet?: Field;
addressCity?: Field;
addressRegion?: Field;
addressPostalCode?: Field;
vehicleClass?: Field;
// Passports / travel documents
country?: Field; // ICAO 3-char issuing country
};
raw?: Record<string, string>;
}
interface Field {
value: string;
confidence: number; // 0–1
source: "barcode" | "mrz";
}Scan result
interface ScanResult {
ok: boolean;
document?: CanonicalDocument;
error?: ScanError;
}
type ScanError =
| "no_barcode" | "not_aamva" // barcode path
| "no_mrz" // passport path
| "license_expired" | "license_bundle_mismatch"
| "license_bad_signature" | "license_unknown_key" | "license_malformed";License enforcement
Tokens are ES256 JWTs signed by AWS KMS. scanner.init() verifies the
signature offline against the embedded public key — no network call required.
Each token encodes an expiry date, an allowed bundle ID list, and a scan quota.
const { valid, reason, claims } = await scanner.init();
if (!valid) {
// reason: "expired" | "bundle_mismatch" | "bad_signature"
// | "unknown_key" | "malformed"
}Analytics
One PII-free event per scan (document type, jurisdiction, success/fail, confidence bucket, device model — never document field values). Events buffer locally and flush in batches. Force a flush with:
await scanner.flushAnalytics();License
MIT
