verant_id_cloud_scan 1.4.3 → 1.4.4-beta.1
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/.claude/settings.local.json +9 -0
- package/.github/workflows/publish_library.yml +27 -0
- package/Api.js +105 -123
- package/CloudScan.js +103 -7
- package/DateUtils.js +57 -0
- package/FaceComparison.js +3 -3
- package/FieldMapping.js +72 -0
- package/LocalScannerApi.js +19 -0
- package/VerificationModal.js +499 -0
- package/dmv-modal.css +512 -0
- package/index.d.ts +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# .github/workflows/publish.yml
|
|
2
|
+
name: Publish to npm
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout repo
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Use Node
|
|
18
|
+
uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: "22"
|
|
21
|
+
registry-url: "https://registry.npmjs.org"
|
|
22
|
+
|
|
23
|
+
- name: Update npm
|
|
24
|
+
run: npm install -g npm@latest
|
|
25
|
+
|
|
26
|
+
- name: Publish to npm
|
|
27
|
+
run: npm publish
|
package/Api.js
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
import { compressAndCompareImages } from "./FaceComparison.js";
|
|
2
|
+
import { formatDateToISO } from "./DateUtils.js";
|
|
3
|
+
import { FIELD_MAPPING, FIELD_DEFAULTS, getFieldValue } from "./FieldMapping.js";
|
|
2
4
|
|
|
3
5
|
//prod
|
|
4
6
|
const licenseServerAddress = "https://lic.verantid.com/api/v1";
|
|
5
7
|
const dmvServerAddress = "https://dmv.verantid.com/api/v1/dmv_check";
|
|
6
|
-
//staging
|
|
8
|
+
// staging
|
|
7
9
|
// const licenseServerAddress =
|
|
8
10
|
// "https://verant-license-server-staging-52b03b060a98.herokuapp.com/api/v1";
|
|
9
11
|
// const dmvServerAddress =
|
|
10
|
-
// "https://dmv-check-server-staging-fcaab48bec21.herokuapp.com/api/v1/dmv_check";
|
|
11
|
-
|
|
12
|
-
// Helper function that checks multiple possible keys and returns a non-empty value
|
|
13
|
-
const getLicenseFieldValue = (data, possibleKeys) => {
|
|
14
|
-
for (const key of possibleKeys) {
|
|
15
|
-
if (data.hasOwnProperty(key)) {
|
|
16
|
-
const value = data[key];
|
|
17
|
-
if (value !== undefined && value !== null && value !== "") {
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return undefined;
|
|
23
|
-
};
|
|
12
|
+
// "https://dmv-check-server-staging-fcaab48bec21.herokuapp.com/api/v1/dmv_check";
|
|
24
13
|
|
|
25
14
|
export const dmvCheck = async (clientId, scannerType, licenseData) => {
|
|
26
15
|
const url = dmvServerAddress; // make sure dmvServerAddress is defined elsewhere
|
|
@@ -28,111 +17,39 @@ export const dmvCheck = async (clientId, scannerType, licenseData) => {
|
|
|
28
17
|
// Build the driver object dynamically.
|
|
29
18
|
const driver = {};
|
|
30
19
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
field: "license_number",
|
|
54
|
-
keys: ["license_number", "licenseNumber", "LicenseNumber", "LicenseOrIDNumber"],
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
field: "expiration_date",
|
|
58
|
-
keys: [
|
|
59
|
-
"expiration_date",
|
|
60
|
-
"expirationDate",
|
|
61
|
-
"ExpirationDate",
|
|
62
|
-
"LicenseExpirationDate",
|
|
63
|
-
],
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
field: "issue_date",
|
|
67
|
-
keys: [
|
|
68
|
-
"issue_date",
|
|
69
|
-
"issueDate",
|
|
70
|
-
"IssueDate",
|
|
71
|
-
"LicenseOrIDDocumentIssueDate",
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
field: "first_name",
|
|
76
|
-
keys: ["first_name", "firstName", "FirstName"],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
field: "last_name",
|
|
80
|
-
keys: ["last_name", "lastName", "LastName", "FamilyName"],
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
field: "birth_date",
|
|
84
|
-
keys: ["birth_date", "birthDate", "BirthDate", "DateOfBirth"],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
field: "eye_color",
|
|
88
|
-
keys: ["eye_color", "eyeColor", "EyeColor"],
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
field: "sex_code",
|
|
92
|
-
keys: ["sex_code", "sexCode", "SexCode", "sex", "Sex"],
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
field: "address_line_1",
|
|
96
|
-
keys: [
|
|
97
|
-
"address_line_1",
|
|
98
|
-
"addressLine1",
|
|
99
|
-
"AddressLine1",
|
|
100
|
-
"MailingStreetAddress1",
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
field: "city",
|
|
105
|
-
keys: ["city", "City", "MailingCity"],
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
field: "state",
|
|
109
|
-
keys: ["state", "State", "MailingJurisdictionCode"],
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
field: "postal_code",
|
|
113
|
-
keys: ["postal_code", "postalCode", "PostalCode", "MailingPostalCode"],
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
field: "height",
|
|
117
|
-
keys: ["height", "Height", "HeightInFT_IN", "HeightInCM"],
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
field: "weight",
|
|
121
|
-
keys: ["weight", "Weight", "WeightInLBS", "WeightInKG"],
|
|
122
|
-
},
|
|
123
|
-
];
|
|
124
|
-
|
|
125
|
-
// Iterate over each mapping. If a non-empty value is found (or a default exists), add the field.
|
|
126
|
-
fieldMapping.forEach((mapping) => {
|
|
127
|
-
let value = getLicenseFieldValue(licenseData, mapping.keys);
|
|
128
|
-
if (value === undefined) {
|
|
129
|
-
if (mapping.hasOwnProperty("default")) {
|
|
130
|
-
value = mapping.default;
|
|
131
|
-
} else {
|
|
132
|
-
return; // Skip adding this field if no value (and no default) is found
|
|
20
|
+
// Iterate over each field in the mapping
|
|
21
|
+
Object.keys(FIELD_MAPPING).forEach((fieldName) => {
|
|
22
|
+
let value = getFieldValue(licenseData, fieldName);
|
|
23
|
+
|
|
24
|
+
// Use default value if no value found
|
|
25
|
+
if (value === undefined && FIELD_DEFAULTS.hasOwnProperty(fieldName)) {
|
|
26
|
+
value = FIELD_DEFAULTS[fieldName];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Skip if still no value
|
|
30
|
+
if (value === undefined || value === null || value === '') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Clean up the value (trim whitespace)
|
|
35
|
+
if (typeof value === 'string') {
|
|
36
|
+
value = value.trim();
|
|
37
|
+
// Skip if empty after trimming
|
|
38
|
+
if (value === '') {
|
|
39
|
+
return;
|
|
133
40
|
}
|
|
134
41
|
}
|
|
135
|
-
|
|
42
|
+
|
|
43
|
+
// Format date fields to ISO format (YYYY-MM-DD)
|
|
44
|
+
if (fieldName === 'birth_date' || fieldName === 'expiration_date' || fieldName === 'issue_date') {
|
|
45
|
+
value = formatDateToISO(value);
|
|
46
|
+
// Skip if date formatting returned empty string (invalid date)
|
|
47
|
+
if (value === '') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
driver[fieldName] = value;
|
|
136
53
|
});
|
|
137
54
|
|
|
138
55
|
// Assemble the data payload.
|
|
@@ -155,13 +72,43 @@ export const dmvCheck = async (clientId, scannerType, licenseData) => {
|
|
|
155
72
|
const responseData = await response.json();
|
|
156
73
|
return responseData; // Return the response data
|
|
157
74
|
} else {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
75
|
+
// Try to parse as JSON first (for structured error responses)
|
|
76
|
+
try {
|
|
77
|
+
const errorData = await response.json();
|
|
78
|
+
console.error("DMV check failed:", errorData);
|
|
79
|
+
|
|
80
|
+
// If there's a SOAP error message, try to extract a user-friendly message
|
|
81
|
+
let userMessage = errorData.message || 'DMV verification failed';
|
|
82
|
+
|
|
83
|
+
if (errorData.error_messages && typeof errorData.error_messages === 'string') {
|
|
84
|
+
// Try to extract text from SOAP fault
|
|
85
|
+
const reasonMatch = errorData.error_messages.match(/<s:Text[^>]*>([^<]+)<\/s:Text>/);
|
|
86
|
+
if (reasonMatch && reasonMatch[1]) {
|
|
87
|
+
userMessage = reasonMatch[1];
|
|
88
|
+
} else if (response.status === 503) {
|
|
89
|
+
userMessage = 'DMV verification service is temporarily unavailable. Please try again later.';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { ...errorData, message: userMessage };
|
|
94
|
+
} catch (parseError) {
|
|
95
|
+
// If JSON parsing fails, fall back to text
|
|
96
|
+
const errorText = await response.text();
|
|
97
|
+
console.error("DMV check failed:", errorText);
|
|
98
|
+
|
|
99
|
+
let userMessage = 'DMV verification failed';
|
|
100
|
+
if (response.status === 503) {
|
|
101
|
+
userMessage = 'DMV verification service is temporarily unavailable. Please try again later.';
|
|
102
|
+
} else if (response.status === 500) {
|
|
103
|
+
userMessage = 'DMV verification service encountered an error. Please check your data and try again.';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { status: 'error', message: userMessage, raw_error: errorText };
|
|
107
|
+
}
|
|
161
108
|
}
|
|
162
109
|
} catch (error) {
|
|
163
110
|
console.error("There was a problem with the dmv_check request:", error);
|
|
164
|
-
return
|
|
111
|
+
return { status: 'error', message: error.message || 'Unable to connect to DMV verification service.' };
|
|
165
112
|
}
|
|
166
113
|
};
|
|
167
114
|
|
|
@@ -258,6 +205,41 @@ export const checkLicense = async (clientId, macAddress) => {
|
|
|
258
205
|
}
|
|
259
206
|
};
|
|
260
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Check if client has autoDmv feature enabled
|
|
210
|
+
*
|
|
211
|
+
* TODO: This will eventually call the /check_license endpoint and return
|
|
212
|
+
* the feature_licenses.autoDmv value. For now, we're manually returning true
|
|
213
|
+
* for testing purposes.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} clientId - Client ID to check
|
|
216
|
+
* @returns {Promise<boolean>} True if autoDmv is enabled
|
|
217
|
+
*/
|
|
218
|
+
export const checkAutoDmvEnabled = async (clientId) => {
|
|
219
|
+
const url = licenseServerAddress + "/check_feature_license";
|
|
220
|
+
const data = { client_id: clientId };
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const response = await fetch(url, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: {
|
|
226
|
+
"Content-Type": "application/json",
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify(data),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const responseData = await response.json();
|
|
232
|
+
console.log("Response from checkAutoDmvEnabled:", responseData);
|
|
233
|
+
|
|
234
|
+
if (response.ok && responseData.dmv_verifications) {
|
|
235
|
+
return responseData.dmv_verifications.auto_dmv === true;
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error("Error checking autoDmv feature:", error);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
261
243
|
export const getBarcodeLicenseKey = async () => {
|
|
262
244
|
const url = licenseServerAddress + "/get_barcode_license_key";
|
|
263
245
|
|
|
@@ -337,4 +319,4 @@ export const stopScanning = (scannerAddress, commandObj) =>
|
|
|
337
319
|
postToScannerApi(scannerAddress, commandObj);
|
|
338
320
|
|
|
339
321
|
export const closeConnection = (scannerAddress, commandObj) =>
|
|
340
|
-
postToScannerApi(scannerAddress, commandObj);
|
|
322
|
+
postToScannerApi(scannerAddress, commandObj);
|
package/CloudScan.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
readBarcode,
|
|
5
5
|
} from "./BarcodeScanner.js";
|
|
6
6
|
import { driverLicenseFields } from "./DriverLicenseFields.js";
|
|
7
|
-
import { localScannerChain, scanBack } from "./LocalScannerApi.js";
|
|
7
|
+
import { localScannerChain, scanBack, scanFrontOnly, getCachedClientId } from "./LocalScannerApi.js";
|
|
8
8
|
import { compressAndCompareImages, compressImage } from "./FaceComparison.js";
|
|
9
9
|
import {
|
|
10
10
|
checkDmvFeatureLicense,
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
getBarcodeLicenseKey,
|
|
14
14
|
scannerChain,
|
|
15
15
|
} from "./ScannerApi.js";
|
|
16
|
+
import { VerificationModal } from "./VerificationModal.js";
|
|
17
|
+
import { checkAutoDmvEnabled } from "./Api.js";
|
|
16
18
|
|
|
17
19
|
let cachedLicenseFrontBase64 = "";
|
|
18
20
|
|
|
@@ -135,6 +137,33 @@ export const scanId = async (
|
|
|
135
137
|
}
|
|
136
138
|
barcodeResultsObject = extractInformation(res.barcodeResults);
|
|
137
139
|
returnObj.barcodeResultsObject = barcodeResultsObject;
|
|
140
|
+
|
|
141
|
+
// Check if autoDmv is enabled and trigger modal
|
|
142
|
+
const autoDmvEnabled = await checkAutoDmvEnabled(clientId);
|
|
143
|
+
if (autoDmvEnabled) {
|
|
144
|
+
console.log('[AUTO-DMV] Feature enabled, showing verification modal');
|
|
145
|
+
|
|
146
|
+
// Determine scanner type - you can customize this based on your needs
|
|
147
|
+
const scannerType = 'DigitalCheck'; // Default scanner type
|
|
148
|
+
|
|
149
|
+
// Create and open the modal
|
|
150
|
+
const modal = new VerificationModal(clientId, scannerType, barcodeResultsObject, {
|
|
151
|
+
primaryColor: '#0074CB' // VerantID blue
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const dmvResult = await modal.open();
|
|
156
|
+
console.log('[AUTO-DMV] Modal result:', dmvResult);
|
|
157
|
+
|
|
158
|
+
// Attach DMV result to return object
|
|
159
|
+
returnObj.dmvVerificationResult = dmvResult;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('[AUTO-DMV] Modal error:', error);
|
|
162
|
+
returnObj.dmvVerificationResult = { status: 'error', error: error.message };
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
console.log('[AUTO-DMV] Feature disabled for client:', clientId);
|
|
166
|
+
}
|
|
138
167
|
}
|
|
139
168
|
return returnObj;
|
|
140
169
|
};
|
|
@@ -156,6 +185,26 @@ function getField(keyword, foundBarcodeString, beginNewline = true) {
|
|
|
156
185
|
return subtext;
|
|
157
186
|
}
|
|
158
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Formats sex code for display
|
|
190
|
+
* Converts AAMVA numeric codes (1=Male, 2=Female) to letters (M, F)
|
|
191
|
+
* Preserves any other values unchanged
|
|
192
|
+
* @param {string} sexCode - The sex code from the barcode
|
|
193
|
+
* @returns {string} Formatted sex code
|
|
194
|
+
*/
|
|
195
|
+
function formatSexCodeForDisplay(sexCode) {
|
|
196
|
+
if (!sexCode) return '';
|
|
197
|
+
|
|
198
|
+
const code = String(sexCode).trim();
|
|
199
|
+
|
|
200
|
+
// AAMVA standard: 1 = Male, 2 = Female
|
|
201
|
+
if (code === '1') return 'M';
|
|
202
|
+
if (code === '2') return 'F';
|
|
203
|
+
|
|
204
|
+
// Return original value for anything else (9, X, M, F, etc.)
|
|
205
|
+
return sexCode;
|
|
206
|
+
}
|
|
207
|
+
|
|
159
208
|
//extracts the information from the barcode
|
|
160
209
|
function extractInformation(foundBarcodeString) {
|
|
161
210
|
var resultsArray = {};
|
|
@@ -164,7 +213,13 @@ function extractInformation(foundBarcodeString) {
|
|
|
164
213
|
var item = driverLicenseFields[i];
|
|
165
214
|
var fieldValue = getField(item.abbreviation, foundBarcodeString);
|
|
166
215
|
if (fieldValue !== false) {
|
|
167
|
-
|
|
216
|
+
// Format sex code for display while preserving original in OriginalSexCode
|
|
217
|
+
if (item.description === 'Sex') {
|
|
218
|
+
resultsArray['OriginalSexCode'] = fieldValue; // Store original for DMV verification
|
|
219
|
+
resultsArray[item.description] = formatSexCodeForDisplay(fieldValue);
|
|
220
|
+
} else {
|
|
221
|
+
resultsArray[item.description] = fieldValue;
|
|
222
|
+
}
|
|
168
223
|
}
|
|
169
224
|
if (item.abbreviation === "DAQ" && !fieldValue) {
|
|
170
225
|
fieldValue = getField("DLDAQ", foundBarcodeString, false);
|
|
@@ -193,18 +248,15 @@ export const localScanId = async (scannerAddress, clientId) => {
|
|
|
193
248
|
console.log("scanner address: " + scannerAddress);
|
|
194
249
|
let scannerResponseObj = await localScannerChain(scannerAddress, clientId);
|
|
195
250
|
|
|
196
|
-
//console.log(scannerResponseObj);
|
|
197
251
|
if (scannerResponseObj.errorMessages.length > 0) {
|
|
198
252
|
returnObj.errorMessages = scannerResponseObj.errorMessages;
|
|
199
253
|
return returnObj;
|
|
200
|
-
//return error
|
|
201
254
|
}
|
|
202
255
|
cachedLicenseFrontBase64 = scannerResponseObj.licenseFrontBase64;
|
|
203
|
-
//console.log(cachedLicenseFrontBase64);
|
|
204
256
|
return true;
|
|
205
257
|
};
|
|
206
258
|
|
|
207
|
-
export const localContinueScanId = async (scannerAddress, includeData) => {
|
|
259
|
+
export const localContinueScanId = async (scannerAddress, includeData, clientId = null, skipBackScan = false) => {
|
|
208
260
|
let barcodeResultsObject = {};
|
|
209
261
|
let returnObj = {
|
|
210
262
|
barcodeResultsObject,
|
|
@@ -217,7 +269,15 @@ export const localContinueScanId = async (scannerAddress, includeData) => {
|
|
|
217
269
|
scannerAddress = "https://localhost:53051";
|
|
218
270
|
}
|
|
219
271
|
|
|
220
|
-
let scannerResponseObj
|
|
272
|
+
let scannerResponseObj;
|
|
273
|
+
|
|
274
|
+
if (skipBackScan) {
|
|
275
|
+
// For facial verification only, skip back scan
|
|
276
|
+
scannerResponseObj = await scanFrontOnly();
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
scannerResponseObj = await scanBack(scannerAddress, includeData);
|
|
280
|
+
}
|
|
221
281
|
|
|
222
282
|
//console.log(scannerResponseObj);
|
|
223
283
|
if (scannerResponseObj.errorMessages.length > 0) {
|
|
@@ -275,6 +335,42 @@ export const localContinueScanId = async (scannerAddress, includeData) => {
|
|
|
275
335
|
}
|
|
276
336
|
barcodeResultsObject = extractInformation(barcodeResponse.barcodeResults);
|
|
277
337
|
returnObj.barcodeResultsObject = barcodeResultsObject;
|
|
338
|
+
|
|
339
|
+
// Check if autoDmv is enabled and trigger modal
|
|
340
|
+
// Use provided clientId, or fall back to cached clientId from localScanId
|
|
341
|
+
const effectiveClientId = clientId || getCachedClientId();
|
|
342
|
+
|
|
343
|
+
if (!effectiveClientId) {
|
|
344
|
+
console.warn('[AUTO-DMV] No clientId available. Auto-DMV verification will be skipped. Please ensure clientId is passed to localContinueScanId or that localScanId was called first.');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (effectiveClientId) {
|
|
348
|
+
const autoDmvEnabled = await checkAutoDmvEnabled(effectiveClientId);
|
|
349
|
+
if (autoDmvEnabled) {
|
|
350
|
+
console.log('[AUTO-DMV] Feature enabled, showing verification modal');
|
|
351
|
+
|
|
352
|
+
// Determine scanner type - you can customize this based on your needs
|
|
353
|
+
const scannerType = 'VerantId6S'; // Default scanner type for local scanner
|
|
354
|
+
|
|
355
|
+
// Create and open the modal
|
|
356
|
+
const modal = new VerificationModal(effectiveClientId, scannerType, barcodeResultsObject, {
|
|
357
|
+
primaryColor: '#0074CB' // VerantID blue
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const dmvResult = await modal.open();
|
|
362
|
+
console.log('[AUTO-DMV] Modal result:', dmvResult);
|
|
363
|
+
|
|
364
|
+
// Attach DMV result to return object
|
|
365
|
+
returnObj.dmvVerificationResult = dmvResult;
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('[AUTO-DMV] Modal error:', error);
|
|
368
|
+
returnObj.dmvVerificationResult = { status: 'error', error: error.message };
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
console.log('[AUTO-DMV] Feature disabled for client:', effectiveClientId);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
278
374
|
}
|
|
279
375
|
return returnObj;
|
|
280
376
|
};
|
package/DateUtils.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared date formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Formats a date from MMDDYYYY to YYYY-MM-DD (ISO format)
|
|
7
|
+
* Used for API submissions
|
|
8
|
+
* @param {string} mmddyyyy - Date string in MMDDYYYY format
|
|
9
|
+
* @returns {string} Date in YYYY-MM-DD format, or empty string if invalid
|
|
10
|
+
*/
|
|
11
|
+
export const formatDateToISO = (mmddyyyy) => {
|
|
12
|
+
if (!mmddyyyy || mmddyyyy.length < 8) {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
const month = mmddyyyy.slice(0, 2);
|
|
16
|
+
const day = mmddyyyy.slice(2, 4);
|
|
17
|
+
const year = mmddyyyy.slice(4, 8);
|
|
18
|
+
return `${year}-${month}-${day}`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Formats a date from MMDDYYYY to MM/DD/YYYY (display format)
|
|
23
|
+
* Used for UI display
|
|
24
|
+
* @param {string} dateStr - Date string in MMDDYYYY, YYYY-MM-DD, or MM/DD/YYYY format
|
|
25
|
+
* @returns {string} Date in MM/DD/YYYY format, or original string if already formatted
|
|
26
|
+
*/
|
|
27
|
+
export const formatDateToDisplay = (dateStr) => {
|
|
28
|
+
if (!dateStr || dateStr.length < 8) {
|
|
29
|
+
return dateStr;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Already in MM/DD/YYYY or YYYY-MM-DD format
|
|
33
|
+
if (dateStr.includes('/') || dateStr.includes('-')) {
|
|
34
|
+
return dateStr;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Convert MMDDYYYY to MM/DD/YYYY
|
|
38
|
+
const month = dateStr.slice(0, 2);
|
|
39
|
+
const day = dateStr.slice(2, 4);
|
|
40
|
+
const year = dateStr.slice(4, 8);
|
|
41
|
+
|
|
42
|
+
return `${month}/${day}/${year}`;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* List of date field names
|
|
47
|
+
*/
|
|
48
|
+
export const DATE_FIELDS = ['birth_date', 'expiration_date', 'issue_date'];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checks if a field name represents a date field
|
|
52
|
+
* @param {string} fieldName - Field name to check
|
|
53
|
+
* @returns {boolean} True if field is a date field
|
|
54
|
+
*/
|
|
55
|
+
export const isDateField = (fieldName) => {
|
|
56
|
+
return DATE_FIELDS.includes(fieldName);
|
|
57
|
+
};
|
package/FaceComparison.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
//prod
|
|
2
|
-
const faceComparisonAddress = "https://faceid.verantid.com/compare_id_and_face";
|
|
2
|
+
// const faceComparisonAddress = "https://faceid.verantid.com/compare_id_and_face";
|
|
3
3
|
//staging
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const faceComparisonAddress = "https://staging.face.verantid.bluerocket.us/compare_id_and_face";
|
|
5
|
+
|
|
6
6
|
|
|
7
7
|
export async function compressImage(source, maxSize = 2 * 1024 * 1024) {
|
|
8
8
|
async function _shrink(canvas) {
|
package/FieldMapping.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared field mapping configuration for license data
|
|
3
|
+
* Maps standardized field names to various possible keys found in license data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const FIELD_MAPPING = {
|
|
7
|
+
jurisdiction_code: ['jurisdiction_code', 'jurisdictionCode', 'JurisdictionCode', 'MailingJurisdictionCode'],
|
|
8
|
+
document_category_code: ['document_category_code', 'documentCategoryCode', 'DocumentCategoryCode'],
|
|
9
|
+
license_number: ['license_number', 'licenseNumber', 'LicenseNumber', 'LicenseOrIDNumber'],
|
|
10
|
+
expiration_date: ['expiration_date', 'expirationDate', 'ExpirationDate', 'LicenseExpirationDate'],
|
|
11
|
+
issue_date: ['issue_date', 'issueDate', 'IssueDate', 'LicenseOrIDDocumentIssueDate'],
|
|
12
|
+
first_name: ['first_name', 'firstName', 'FirstName'],
|
|
13
|
+
last_name: ['last_name', 'lastName', 'LastName', 'FamilyName'],
|
|
14
|
+
birth_date: ['birth_date', 'birthDate', 'BirthDate', 'DateOfBirth'],
|
|
15
|
+
eye_color: ['eye_color', 'eyeColor', 'EyeColor'],
|
|
16
|
+
sex_code: ['OriginalSexCode', 'sex_code', 'sexCode', 'SexCode', 'sex', 'Sex'],
|
|
17
|
+
address_line_1: ['address_line_1', 'addressLine1', 'AddressLine1', 'MailingStreetAddress1'],
|
|
18
|
+
city: ['city', 'City', 'MailingCity'],
|
|
19
|
+
state: ['state', 'State', 'MailingJurisdictionCode'],
|
|
20
|
+
postal_code: ['postal_code', 'postalCode', 'PostalCode', 'MailingPostalCode'],
|
|
21
|
+
height: ['height', 'Height', 'HeightInFT_IN', 'HeightInCM'],
|
|
22
|
+
weight: ['weight', 'Weight', 'WeightInLBS', 'WeightInKG']
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default values for certain fields
|
|
27
|
+
*/
|
|
28
|
+
export const FIELD_DEFAULTS = {
|
|
29
|
+
document_category_code: '1'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Gets the value of a field from license data by checking multiple possible keys
|
|
34
|
+
* @param {Object} data - License data object
|
|
35
|
+
* @param {string} fieldName - Standardized field name (e.g., 'first_name')
|
|
36
|
+
* @returns {string|undefined} Field value or undefined if not found
|
|
37
|
+
*/
|
|
38
|
+
export const getFieldValue = (data, fieldName) => {
|
|
39
|
+
const possibleKeys = FIELD_MAPPING[fieldName] || [fieldName];
|
|
40
|
+
|
|
41
|
+
for (const key of possibleKeys) {
|
|
42
|
+
if (data.hasOwnProperty(key)) {
|
|
43
|
+
const value = data[key];
|
|
44
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Field name labels for display
|
|
54
|
+
*/
|
|
55
|
+
export const FIELD_LABELS = {
|
|
56
|
+
first_name: 'First Name',
|
|
57
|
+
last_name: 'Last Name',
|
|
58
|
+
license_number: 'License Number',
|
|
59
|
+
birth_date: 'Date of Birth',
|
|
60
|
+
address_line_1: 'Address',
|
|
61
|
+
expiration_date: 'Expiration Date',
|
|
62
|
+
issue_date: 'Issue Date',
|
|
63
|
+
eye_color: 'Eye Color',
|
|
64
|
+
sex_code: 'Sex',
|
|
65
|
+
height: 'Height',
|
|
66
|
+
weight: 'Weight',
|
|
67
|
+
city: 'City',
|
|
68
|
+
state: 'State',
|
|
69
|
+
postal_code: 'Postal Code',
|
|
70
|
+
jurisdiction_code: 'Jurisdiction',
|
|
71
|
+
document_category_code: 'Document Category'
|
|
72
|
+
};
|
package/LocalScannerApi.js
CHANGED
|
@@ -8,6 +8,7 @@ let scanDataResults = {};
|
|
|
8
8
|
let licenseFrontBase64 = "";
|
|
9
9
|
let licenseBackBase64 = "";
|
|
10
10
|
let imageCount = 0;
|
|
11
|
+
let cachedClientId = "";
|
|
11
12
|
|
|
12
13
|
function buildError(errorText) {
|
|
13
14
|
if (errorText.includes("Invalid URL")) {
|
|
@@ -26,6 +27,7 @@ function buildError(errorText) {
|
|
|
26
27
|
export async function localScannerChain(ambirApiAddress, clientId) {
|
|
27
28
|
errorMessages = [];
|
|
28
29
|
localApiAddress = ambirApiAddress;
|
|
30
|
+
cachedClientId = clientId; // Cache clientId for use in localContinueScanId
|
|
29
31
|
|
|
30
32
|
//0 get list of local scanners
|
|
31
33
|
const localScannerResults = await getLocalScanners();
|
|
@@ -189,6 +191,18 @@ export async function localScannerChain(ambirApiAddress, clientId) {
|
|
|
189
191
|
};
|
|
190
192
|
}
|
|
191
193
|
|
|
194
|
+
export async function scanFrontOnly() {
|
|
195
|
+
// Skip the back scan entirely, close the connection
|
|
196
|
+
await closeScannerConnection();
|
|
197
|
+
return {
|
|
198
|
+
success: true,
|
|
199
|
+
scanDataResults,
|
|
200
|
+
licenseFrontBase64: licenseFrontBase64,
|
|
201
|
+
licenseBackBase64: "",
|
|
202
|
+
errorMessages,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
192
206
|
export async function scanBack() {
|
|
193
207
|
//6a set the back params for this session
|
|
194
208
|
const setParamsSuccess = await setScanParameters(false);
|
|
@@ -495,3 +509,8 @@ async function clearImageAndCloseScannerConnection() {
|
|
|
495
509
|
return false;
|
|
496
510
|
}
|
|
497
511
|
}
|
|
512
|
+
|
|
513
|
+
// Getter for cached clientId
|
|
514
|
+
export function getCachedClientId() {
|
|
515
|
+
return cachedClientId;
|
|
516
|
+
}
|