verant_id_cloud_scan 1.4.5-beta.11 → 1.4.5-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish_library.yml +7 -1
- package/Api.js +39 -26
- package/Api.test.js +65 -0
- package/CHANGELOG.md +24 -0
- package/CloudScan.js +3 -1
- package/{FaceComparison.js → FaceApi.js} +36 -1
- package/FieldMapping.js +4 -2
- package/VerificationModal.js +4 -0
- package/index.d.ts +16 -8
- package/package.json +1 -1
package/Api.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { compressAndCompareImages } from "./FaceComparison.js";
|
|
2
1
|
import { formatDateToISO, DATE_FIELDS } from "./DateUtils.js";
|
|
3
2
|
import { FIELD_MAPPING, FIELD_DEFAULTS, getFieldValue } from "./FieldMapping.js";
|
|
4
3
|
import { sanitizeFieldForDmv } from "./FormatUtils.js";
|
|
@@ -62,47 +61,61 @@ export function setEnvironment(environment) {
|
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
/**
|
|
65
|
-
*
|
|
64
|
+
* Per-service host by environment.
|
|
65
|
+
*/
|
|
66
|
+
const HOSTS = {
|
|
67
|
+
staging: {
|
|
68
|
+
license: "https://lic-staging.verantid.com/api/v1",
|
|
69
|
+
dmv: "https://dmv-check-server-staging-fcaab48bec21.herokuapp.com",
|
|
70
|
+
face: "https://staging.face.verantid.bluerocket.us",
|
|
71
|
+
mobile: "https://staging.mdl.verantid.bluerocket.us",
|
|
72
|
+
},
|
|
73
|
+
production: {
|
|
74
|
+
license: "https://lic.verantid.com/api/v1",
|
|
75
|
+
dmv: "https://dmv.verantid.com",
|
|
76
|
+
face: "https://faceid.verantid.com",
|
|
77
|
+
mobile: "https://mdl.verantid.bluerocket.us",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a service's host for the current environment.
|
|
83
|
+
* @param {'license' | 'dmv' | 'face' | 'mobile'} service
|
|
66
84
|
* @returns {string}
|
|
67
85
|
*/
|
|
86
|
+
function host(service) {
|
|
87
|
+
return HOSTS[getEnvironment()][service];
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
function getLicenseServerAddress() {
|
|
69
|
-
|
|
70
|
-
return env === 'staging'
|
|
71
|
-
? "https://lic-staging.verantid.com/api/v1"
|
|
72
|
-
: "https://lic.verantid.com/api/v1";
|
|
91
|
+
return host("license");
|
|
73
92
|
}
|
|
74
93
|
|
|
75
94
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
95
|
+
* Still on /api/v1 (SOAP transport), which does NOT carry
|
|
96
|
+
* the personDigitalImage match. The field already forwards in the driver object,
|
|
97
|
+
* but the "License Portrait" match indicator will only round-trip once this path
|
|
98
|
+
* is switched to /api/v2/dmv_check (REST 3.0). That endpoint is request/response
|
|
99
|
+
* compatible — change the path below — but it must NOT be flipped until VNT-1463
|
|
100
|
+
* is deployed to these hosts, or all DMV checks will 404.
|
|
78
101
|
*/
|
|
79
102
|
function getDmvServerAddress() {
|
|
80
|
-
|
|
81
|
-
return env === 'staging'
|
|
82
|
-
? "https://dmv-check-server-staging-fcaab48bec21.herokuapp.com/api/v1/dmv_check"
|
|
83
|
-
: "https://dmv.verantid.com/api/v1/dmv_check";
|
|
103
|
+
return `${host("dmv")}/api/v1/dmv_check`;
|
|
84
104
|
}
|
|
85
105
|
|
|
86
|
-
/**
|
|
87
|
-
* Get Face Comparison server address based on current environment
|
|
88
|
-
* @returns {string}
|
|
89
|
-
*/
|
|
90
106
|
export function getFaceComparisonServerAddress() {
|
|
91
|
-
|
|
92
|
-
return env === 'staging'
|
|
93
|
-
? "https://staging.face.verantid.bluerocket.us/compare_id_and_face"
|
|
94
|
-
: "https://faceid.verantid.com/compare_id_and_face";
|
|
107
|
+
return `${host("face")}/compare_id_and_face`;
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
/**
|
|
98
|
-
*
|
|
99
|
-
* @returns {string}
|
|
111
|
+
* License Portrait extraction
|
|
100
112
|
*/
|
|
113
|
+
export function getExtractPortraitServerAddress() {
|
|
114
|
+
return `${host("face")}/extract_portrait`;
|
|
115
|
+
}
|
|
116
|
+
|
|
101
117
|
function getMobileVerificationServerAddress() {
|
|
102
|
-
|
|
103
|
-
return env === 'staging'
|
|
104
|
-
? "https://staging.mdl.verantid.bluerocket.us"
|
|
105
|
-
: "https://mdl.verantid.bluerocket.us";
|
|
118
|
+
return host("mobile");
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
export const dmvCheck = async (clientId, scannerType, licenseData) => {
|
package/Api.test.js
CHANGED
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
checkScannerPresentDetailed,
|
|
15
15
|
buildSecurelinkUrl,
|
|
16
16
|
getNetworkInfo,
|
|
17
|
+
dmvCheck,
|
|
17
18
|
} from "./Api.js";
|
|
19
|
+
import { FIELD_MAPPING, FIELD_LABELS, getFieldValue } from "./FieldMapping.js";
|
|
18
20
|
|
|
19
21
|
let originalFetch;
|
|
20
22
|
|
|
@@ -336,3 +338,66 @@ describe("postToScannerApi (throw-on-error wrapper) — JSON contract (VNT-1522)
|
|
|
336
338
|
);
|
|
337
339
|
});
|
|
338
340
|
});
|
|
341
|
+
|
|
342
|
+
describe("dmvCheck — personDigitalImage passthrough (VNT-1464)", () => {
|
|
343
|
+
// A base64 data URI that includes '+', '/', and '=' padding, to prove the
|
|
344
|
+
// value is forwarded byte-for-byte (no sanitization / no re-encoding).
|
|
345
|
+
const PORTRAIT =
|
|
346
|
+
"data:image/jpeg;base64,/9j/4AAQSkZJRg+ABAQEAYABgAAD/2wBDAA==";
|
|
347
|
+
|
|
348
|
+
it("forwards personDigitalImage into the driver object unchanged", async () => {
|
|
349
|
+
let sentBody = null;
|
|
350
|
+
globalThis.fetch = async (_url, init) => {
|
|
351
|
+
sentBody = JSON.parse(init.body);
|
|
352
|
+
return jsonResponse({ status: "success", match_status: true });
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
await dmvCheck("client-1", "Upload", {
|
|
356
|
+
jurisdiction_code: "P6",
|
|
357
|
+
license_number: "D123",
|
|
358
|
+
personDigitalImage: PORTRAIT,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
assert.equal(
|
|
362
|
+
sentBody.driver.person_digital_image,
|
|
363
|
+
PORTRAIT,
|
|
364
|
+
"base64 portrait should pass through byte-for-byte",
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("does not send person_digital_image when it is absent", async () => {
|
|
369
|
+
let sentBody = null;
|
|
370
|
+
globalThis.fetch = async (_url, init) => {
|
|
371
|
+
sentBody = JSON.parse(init.body);
|
|
372
|
+
return jsonResponse({ status: "success" });
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
await dmvCheck("client-1", "Upload", {
|
|
376
|
+
jurisdiction_code: "P6",
|
|
377
|
+
license_number: "D123",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
assert.equal(
|
|
381
|
+
Object.prototype.hasOwnProperty.call(sentBody.driver, "person_digital_image"),
|
|
382
|
+
false,
|
|
383
|
+
"absent field should not be added to the driver",
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe("FieldMapping — person_digital_image (VNT-1464)", () => {
|
|
389
|
+
it("resolves the value from every supported key variant", () => {
|
|
390
|
+
for (const key of [
|
|
391
|
+
"person_digital_image",
|
|
392
|
+
"personDigitalImage",
|
|
393
|
+
"PersonDigitalImage",
|
|
394
|
+
]) {
|
|
395
|
+
assert.equal(getFieldValue({ [key]: "IMG" }, "person_digital_image"), "IMG");
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("is registered in FIELD_MAPPING and labeled 'License Portrait'", () => {
|
|
400
|
+
assert.ok(FIELD_MAPPING.person_digital_image, "should exist in FIELD_MAPPING");
|
|
401
|
+
assert.equal(FIELD_LABELS.person_digital_image, "License Portrait");
|
|
402
|
+
});
|
|
403
|
+
});
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.5-beta.14] - 2026-06-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`extractPortraitFromLicense(clientId, licenseImage)`** (VNT-1464 / VNT-1470).
|
|
12
|
+
Extracts the cropped portrait from a license image via the face lambda's
|
|
13
|
+
dedicated `/extract_portrait` endpoint — sends only the license image, no
|
|
14
|
+
comparison. Resolves to `{ extractedPortrait, error? }` where
|
|
15
|
+
`extractedPortrait` is a base64 JPEG data URI or `null` when no face is
|
|
16
|
+
detected. Exported from `CloudScan.js` and typed in `index.d.ts`.
|
|
17
|
+
- **`person_digital_image` field support** (VNT-1464). Added to `FIELD_MAPPING`
|
|
18
|
+
(key variants `person_digital_image` / `personDigitalImage` /
|
|
19
|
+
`PersonDigitalImage`) and `FIELD_LABELS` (`"License Portrait"`). `dmvCheck`
|
|
20
|
+
forwards it in the driver payload when present; `VerificationModal` renders
|
|
21
|
+
`"Photo"` for the field on the confirmation step instead of the base64 string.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Renamed `FaceComparison.js` → `FaceApi.js`; it now houses both the face
|
|
25
|
+
comparison and portrait-extraction clients. No public API change — the
|
|
26
|
+
library entry point still exports `startFaceComparison` and
|
|
27
|
+
`extractPortraitFromLicense`.
|
|
28
|
+
- Centralized per-environment host resolution in `Api.js` behind a single
|
|
29
|
+
`HOSTS` map and a `host(service)` helper. The address getters now append
|
|
30
|
+
their path to a per-service host instead of each branching on environment.
|
|
31
|
+
|
|
8
32
|
## [1.4.5-beta.11] - 2026-05-21
|
|
9
33
|
|
|
10
34
|
### Added
|
package/CloudScan.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./BarcodeScanner.js";
|
|
6
6
|
import { driverLicenseFields } from "./DriverLicenseFields.js";
|
|
7
7
|
import { localScannerChain, scanBack, scanFrontOnly, getCachedClientId } from "./LocalScannerApi.js";
|
|
8
|
-
import { compressAndCompareImages, compressImage } from "./
|
|
8
|
+
import { compressAndCompareImages, compressImage } from "./FaceApi.js";
|
|
9
9
|
import {
|
|
10
10
|
checkDmvFeatureLicense,
|
|
11
11
|
checkFaceComparisonFeatureLicense,
|
|
@@ -39,6 +39,8 @@ export const startFaceComparison = async (clientId, licenseSrc, faceSrc) => {
|
|
|
39
39
|
return await compressAndCompareImages(clientId, licenseSrc, faceSrc);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
export { extractPortraitFromLicense } from "./FaceApi.js";
|
|
43
|
+
|
|
42
44
|
export const checkFaceCompareFeature = async (clientId) => {
|
|
43
45
|
return await checkFaceComparisonFeatureLicense(clientId);
|
|
44
46
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getFaceComparisonServerAddress } from "./Api.js";
|
|
1
|
+
import { getFaceComparisonServerAddress, getExtractPortraitServerAddress } from "./Api.js";
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
export async function compressImage(source, maxSize = 2 * 1024 * 1024) {
|
|
@@ -98,6 +98,41 @@ export async function compressAndCompareImages(
|
|
|
98
98
|
return result;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Extracts a portrait from a license image
|
|
103
|
+
*
|
|
104
|
+
* @param {string} clientId
|
|
105
|
+
* @param {string|File|HTMLCanvasElement} licenseInput
|
|
106
|
+
* @param {number} [maxSize] max compressed size in bytes (defaults to 2 MB)
|
|
107
|
+
* @returns {Promise<{ extractedPortrait: string|null, error?: { error: any, details: any } }>}
|
|
108
|
+
*/
|
|
109
|
+
export async function extractPortraitFromLicense(clientId, licenseInput) {
|
|
110
|
+
let licSource = licenseInput;
|
|
111
|
+
if (typeof licSource === 'string') {
|
|
112
|
+
licSource = await base64ToCanvas(licSource);
|
|
113
|
+
}
|
|
114
|
+
const licenseBase64 = await compressImage(licSource);
|
|
115
|
+
|
|
116
|
+
const res = await fetch(getExtractPortraitServerAddress(), {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: { "Content-Type": "application/json" },
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
client_id: clientId,
|
|
121
|
+
license_front: `data:image/jpeg;base64,${licenseBase64}`,
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const data = await res.json();
|
|
126
|
+
|
|
127
|
+
const result = { extractedPortrait: data?.extractedPortrait ?? null };
|
|
128
|
+
|
|
129
|
+
if (data?.error || data?.details) {
|
|
130
|
+
result.error = { error: data?.error, details: data?.details };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
101
136
|
async function base64ToCanvas(src) {
|
|
102
137
|
// if it’s missing the data: prefix, add it
|
|
103
138
|
const dataUrl = src.startsWith('data:') ? src : `data:image/jpeg;base64,${src}`;
|
package/FieldMapping.js
CHANGED
|
@@ -19,7 +19,8 @@ export const FIELD_MAPPING = {
|
|
|
19
19
|
state: ['state', 'State', 'MailingJurisdictionCode'],
|
|
20
20
|
postal_code: ['postal_code', 'postalCode', 'PostalCode', 'MailingPostalCode'],
|
|
21
21
|
height: ['OriginalHeight', 'height', 'Height', 'HeightInFT_IN', 'HeightInCM'],
|
|
22
|
-
weight: ['weight', 'Weight', 'WeightInLBS', 'WeightInKG']
|
|
22
|
+
weight: ['weight', 'Weight', 'WeightInLBS', 'WeightInKG'],
|
|
23
|
+
person_digital_image: ['person_digital_image', 'personDigitalImage', 'PersonDigitalImage']
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -68,5 +69,6 @@ export const FIELD_LABELS = {
|
|
|
68
69
|
state: 'State',
|
|
69
70
|
postal_code: 'Postal Code',
|
|
70
71
|
jurisdiction_code: 'Jurisdiction',
|
|
71
|
-
document_category_code: 'Document Category'
|
|
72
|
+
document_category_code: 'Document Category',
|
|
73
|
+
person_digital_image: 'License Portrait'
|
|
72
74
|
};
|
package/VerificationModal.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -19,6 +19,14 @@ declare module "verant_id_cloud_scan" {
|
|
|
19
19
|
range: string;
|
|
20
20
|
}>;
|
|
21
21
|
|
|
22
|
+
export function extractPortraitFromLicense(
|
|
23
|
+
clientId: string,
|
|
24
|
+
licenseImage: string | File | HTMLCanvasElement,
|
|
25
|
+
): Promise<{
|
|
26
|
+
extractedPortrait: string | null;
|
|
27
|
+
error?: { error: any; details: any };
|
|
28
|
+
}>;
|
|
29
|
+
|
|
22
30
|
export function readImageBarcode(
|
|
23
31
|
imageFile: File,
|
|
24
32
|
clientId?: string | null,
|
|
@@ -80,13 +88,13 @@ declare module "verant_id_cloud_scan" {
|
|
|
80
88
|
}>;
|
|
81
89
|
|
|
82
90
|
// Mobile Verification Types
|
|
83
|
-
export type ConnectionStateValue =
|
|
91
|
+
export type ConnectionStateValue = "connecting" | "open" | "closed" | "error";
|
|
84
92
|
|
|
85
93
|
export const ConnectionState: {
|
|
86
|
-
readonly CONNECTING:
|
|
87
|
-
readonly OPEN:
|
|
88
|
-
readonly CLOSED:
|
|
89
|
-
readonly ERROR:
|
|
94
|
+
readonly CONNECTING: "connecting";
|
|
95
|
+
readonly OPEN: "open";
|
|
96
|
+
readonly CLOSED: "closed";
|
|
97
|
+
readonly ERROR: "error";
|
|
90
98
|
};
|
|
91
99
|
|
|
92
100
|
export interface VerificationCallbacks {
|
|
@@ -127,14 +135,14 @@ declare module "verant_id_cloud_scan" {
|
|
|
127
135
|
* use `startMobileVerification`.
|
|
128
136
|
*/
|
|
129
137
|
export function connectToVerificationSession(
|
|
130
|
-
options: ConnectToVerificationSessionOptions
|
|
138
|
+
options: ConnectToVerificationSessionOptions,
|
|
131
139
|
): Promise<ConnectedSession>;
|
|
132
140
|
|
|
133
141
|
export function startMobileVerification(
|
|
134
142
|
userId: string,
|
|
135
143
|
clientId: string,
|
|
136
144
|
callbacks?: VerificationCallbacks,
|
|
137
|
-
mobileVerifyBaseUrl?: string
|
|
145
|
+
mobileVerifyBaseUrl?: string,
|
|
138
146
|
): Promise<VerificationSession>;
|
|
139
147
|
|
|
140
148
|
export class WebSocketClient {
|
|
@@ -234,4 +242,4 @@ export function scannerPresentDetailed(
|
|
|
234
242
|
* Only verify-staging.verantid.com automatically uses staging.
|
|
235
243
|
* @param environment - 'staging' or 'production'
|
|
236
244
|
*/
|
|
237
|
-
export function setEnvironment(environment:
|
|
245
|
+
export function setEnvironment(environment: "staging" | "production"): void;
|