verant_id_cloud_scan 1.4.4 → 1.4.5
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/Api.js +4 -11
- package/CloudScan.js +81 -24
- package/FormatUtils.js +145 -1
- package/package.json +1 -1
package/Api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compressAndCompareImages } from "./FaceComparison.js";
|
|
2
2
|
import { formatDateToISO, DATE_FIELDS } from "./DateUtils.js";
|
|
3
3
|
import { FIELD_MAPPING, FIELD_DEFAULTS, getFieldValue } from "./FieldMapping.js";
|
|
4
|
+
import { sanitizeFieldForDmv } from "./FormatUtils.js";
|
|
4
5
|
|
|
5
6
|
//prod
|
|
6
7
|
const licenseServerAddress = "https://lic.verantid.com/api/v1";
|
|
@@ -12,7 +13,7 @@ const dmvServerAddress = "https://dmv.verantid.com/api/v1/dmv_check";
|
|
|
12
13
|
// "https://dmv-check-server-staging-fcaab48bec21.herokuapp.com/api/v1/dmv_check";
|
|
13
14
|
|
|
14
15
|
export const dmvCheck = async (clientId, scannerType, licenseData) => {
|
|
15
|
-
const url = dmvServerAddress;
|
|
16
|
+
const url = dmvServerAddress;
|
|
16
17
|
|
|
17
18
|
// Build the driver object dynamically.
|
|
18
19
|
const driver = {};
|
|
@@ -36,16 +37,8 @@ export const dmvCheck = async (clientId, scannerType, licenseData) => {
|
|
|
36
37
|
value = formatDateToISO(value);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const sexStr = String(value).trim().toUpperCase();
|
|
42
|
-
if (sexStr === 'M' || sexStr === 'MALE') {
|
|
43
|
-
value = '1';
|
|
44
|
-
} else if (sexStr === 'F' || sexStr === 'FEMALE') {
|
|
45
|
-
value = '2';
|
|
46
|
-
}
|
|
47
|
-
// If already 1 or 2, keep as is
|
|
48
|
-
}
|
|
40
|
+
// Sanitize field value for DMV/AAMVA submission
|
|
41
|
+
value = sanitizeFieldForDmv(fieldName, value);
|
|
49
42
|
|
|
50
43
|
driver[fieldName] = value;
|
|
51
44
|
});
|
package/CloudScan.js
CHANGED
|
@@ -210,20 +210,33 @@ function getField(keyword, foundBarcodeString, beginNewline = true) {
|
|
|
210
210
|
keywordOffset = keyword.length + 1; // +1 for \n
|
|
211
211
|
|
|
212
212
|
if (foundIndex === -1) {
|
|
213
|
-
// Try 2: After DL subfile marker
|
|
214
|
-
|
|
215
|
-
if (
|
|
213
|
+
// Try 2: After DL subfile marker (common for first field on line)
|
|
214
|
+
const dlIndex = foundBarcodeString.indexOf("DL" + keyword);
|
|
215
|
+
if (dlIndex !== -1) {
|
|
216
|
+
// Make sure it's actually after a DL marker, not in the middle of data
|
|
217
|
+
// Check if "DL" appears right before our keyword
|
|
218
|
+
foundIndex = dlIndex;
|
|
216
219
|
keywordOffset = keyword.length + 2; // +2 for "DL"
|
|
217
220
|
}
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
if (foundIndex === -1) {
|
|
221
224
|
// Try 3: After ZO subfile marker
|
|
222
|
-
|
|
223
|
-
if (
|
|
225
|
+
const zoIndex = foundBarcodeString.indexOf("ZO" + keyword);
|
|
226
|
+
if (zoIndex !== -1) {
|
|
227
|
+
foundIndex = zoIndex;
|
|
224
228
|
keywordOffset = keyword.length + 2; // +2 for "ZO"
|
|
225
229
|
}
|
|
226
230
|
}
|
|
231
|
+
|
|
232
|
+
if (foundIndex === -1) {
|
|
233
|
+
// Try 4: After ZW subfile marker (Washington enhanced licenses)
|
|
234
|
+
const zwIndex = foundBarcodeString.indexOf("ZW" + keyword);
|
|
235
|
+
if (zwIndex !== -1) {
|
|
236
|
+
foundIndex = zwIndex;
|
|
237
|
+
keywordOffset = keyword.length + 2; // +2 for "ZW"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
227
240
|
} else {
|
|
228
241
|
// When beginNewline=false (used for DAQ workaround)
|
|
229
242
|
foundIndex = foundBarcodeString.indexOf(keyword);
|
|
@@ -232,21 +245,32 @@ function getField(keyword, foundBarcodeString, beginNewline = true) {
|
|
|
232
245
|
|
|
233
246
|
if (foundIndex === -1) return false;
|
|
234
247
|
|
|
235
|
-
// Find the
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
248
|
+
// Find the end of this field's value
|
|
249
|
+
// Strategy: Look for the next field code (3 uppercase letters starting with D or Z)
|
|
250
|
+
// Field codes can appear after: newline, carriage return, or space (for jurisdiction-specific Z* fields only)
|
|
251
|
+
|
|
252
|
+
const startPos = foundIndex + keywordOffset;
|
|
253
|
+
const remainingString = foundBarcodeString.substring(startPos);
|
|
254
|
+
|
|
255
|
+
// Look for the next field code pattern
|
|
256
|
+
// Standard field codes: DA*, DB*, DC*, DD*, DE*, DF*, DG*, DH*, DI*, DJ*, DK*, DL*, DM*, DN*, DO*, DP*
|
|
257
|
+
// Jurisdiction-specific: ZA*, ZB*, ZC*, ..., ZW*, ZX*, ZY*, ZZ*
|
|
258
|
+
// Pattern matches:
|
|
259
|
+
// - \nDAA, \nZNB (newline + field code starting with D or Z)
|
|
260
|
+
// - \rDAA (carriage return + field code)
|
|
261
|
+
// - ZWZ (space + field code starting with Z - for jurisdiction codes that may appear mid-line)
|
|
262
|
+
const nextFieldPattern = /[\n\r]([D][A-Z]{2}|[Z][A-Z]{2})|\s([Z][A-Z]{2})/;
|
|
263
|
+
const match = remainingString.match(nextFieldPattern);
|
|
264
|
+
|
|
265
|
+
let endPos;
|
|
266
|
+
if (match) {
|
|
267
|
+
endPos = startPos + match.index;
|
|
245
268
|
} else {
|
|
246
|
-
|
|
269
|
+
// No next field found, use end of string
|
|
270
|
+
endPos = foundBarcodeString.length;
|
|
247
271
|
}
|
|
248
272
|
|
|
249
|
-
var subtext = foundBarcodeString.substring(
|
|
273
|
+
var subtext = foundBarcodeString.substring(startPos, endPos).trim();
|
|
250
274
|
return subtext;
|
|
251
275
|
}
|
|
252
276
|
|
|
@@ -269,13 +293,25 @@ export function extractInformation(foundBarcodeString) {
|
|
|
269
293
|
resultsArray[item.description] = formatHeightForDisplay(fieldValue);
|
|
270
294
|
}
|
|
271
295
|
// Parse FullName (DAA) into separate FirstName/LastName/MiddleName fields
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
296
|
+
else if (item.description === 'FullName') {
|
|
297
|
+
// AAMVA standard format: LastName,FirstName,MiddleName
|
|
298
|
+
if (fieldValue.includes(',')) {
|
|
299
|
+
const parts = fieldValue.split(',').map(p => p.trim());
|
|
300
|
+
// Only set these if they don't already exist (some states use separate fields)
|
|
301
|
+
if (!resultsArray['LastName'] && parts[0]) resultsArray['LastName'] = parts[0];
|
|
302
|
+
if (!resultsArray['FirstName'] && parts[1]) resultsArray['FirstName'] = parts[1];
|
|
303
|
+
if (!resultsArray['MiddleName'] && parts[2]) resultsArray['MiddleName'] = parts[2];
|
|
304
|
+
} else {
|
|
305
|
+
// Some states use space-separated format: "FIRST MIDDLE LAST"
|
|
306
|
+
const parts = fieldValue.split(/\s+/);
|
|
307
|
+
if (parts.length >= 2) {
|
|
308
|
+
if (!resultsArray['FirstName']) resultsArray['FirstName'] = parts[0];
|
|
309
|
+
if (!resultsArray['LastName']) resultsArray['LastName'] = parts[parts.length - 1];
|
|
310
|
+
if (parts.length >= 3 && !resultsArray['MiddleName']) {
|
|
311
|
+
resultsArray['MiddleName'] = parts.slice(1, -1).join(' ');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
279
315
|
// Also store the full name
|
|
280
316
|
resultsArray[item.description] = fieldValue;
|
|
281
317
|
}
|
|
@@ -291,6 +327,27 @@ export function extractInformation(foundBarcodeString) {
|
|
|
291
327
|
}
|
|
292
328
|
}
|
|
293
329
|
|
|
330
|
+
// Extract jurisdiction code from ANSI header if not found in fields
|
|
331
|
+
if (!resultsArray['MailingJurisdictionCode'] && !resultsArray['JurisdictionCode']) {
|
|
332
|
+
const ansiMatch = foundBarcodeString.match(/^ANSI\s+(\d{6})/);
|
|
333
|
+
if (ansiMatch) {
|
|
334
|
+
const issuerCode = ansiMatch[1];
|
|
335
|
+
// Map AAMVA IIN (Issuer Identification Number) to state codes
|
|
336
|
+
const iinToState = {
|
|
337
|
+
'636038': 'OH', // Ohio
|
|
338
|
+
'636026': 'CT', // Connecticut
|
|
339
|
+
'636045': 'WA', // Washington
|
|
340
|
+
'636036': 'NJ', // New Jersey
|
|
341
|
+
'636005': 'SC', // South Carolina
|
|
342
|
+
// Add more as needed
|
|
343
|
+
};
|
|
344
|
+
const state = iinToState[issuerCode];
|
|
345
|
+
if (state) {
|
|
346
|
+
resultsArray['MailingJurisdictionCode'] = state;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
294
351
|
return resultsArray;
|
|
295
352
|
}
|
|
296
353
|
|
package/FormatUtils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FormatUtils.js
|
|
3
|
-
* Utility functions for formatting barcode field data for display
|
|
3
|
+
* Utility functions for formatting barcode field data for display and AAMVA submission
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -52,3 +52,147 @@ export function formatHeightForDisplay(height) {
|
|
|
52
52
|
// Return original value for any other format
|
|
53
53
|
return height;
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sanitizes field values for DMV/AAMVA submission
|
|
58
|
+
* Cleans up common issues with barcode data to ensure AAMVA acceptance
|
|
59
|
+
* @param {string} fieldName - The standardized field name (e.g., 'height', 'sex_code')
|
|
60
|
+
* @param {string} value - The field value to sanitize
|
|
61
|
+
* @returns {string} Sanitized value ready for AAMVA submission
|
|
62
|
+
*/
|
|
63
|
+
export function sanitizeFieldForDmv(fieldName, value) {
|
|
64
|
+
if (!value) return value;
|
|
65
|
+
|
|
66
|
+
switch (fieldName) {
|
|
67
|
+
case 'height':
|
|
68
|
+
return sanitizeHeight(value);
|
|
69
|
+
|
|
70
|
+
case 'sex_code':
|
|
71
|
+
return sanitizeSexCode(value);
|
|
72
|
+
|
|
73
|
+
case 'eye_color':
|
|
74
|
+
return sanitizeEyeColor(value);
|
|
75
|
+
|
|
76
|
+
case 'license_number':
|
|
77
|
+
return sanitizeLicenseNumber(value);
|
|
78
|
+
|
|
79
|
+
case 'postal_code':
|
|
80
|
+
return sanitizePostalCode(value);
|
|
81
|
+
|
|
82
|
+
case 'last_name':
|
|
83
|
+
return sanitizeLastName(value);
|
|
84
|
+
|
|
85
|
+
default:
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Converts height to total inches for AAMVA
|
|
92
|
+
* Handles formats: "509" (5'9"), "066 in" (66 inches), "067" (67 inches)
|
|
93
|
+
* @param {string} height - Height value from barcode
|
|
94
|
+
* @returns {string} Total inches as numeric string
|
|
95
|
+
*/
|
|
96
|
+
function sanitizeHeight(height) {
|
|
97
|
+
let heightStr = String(height).trim();
|
|
98
|
+
|
|
99
|
+
// Remove " in", " cm", or any other suffix
|
|
100
|
+
heightStr = heightStr.replace(/\s*(in|cm|IN|CM)\s*$/i, '').trim();
|
|
101
|
+
|
|
102
|
+
// Check if it's in AAMVA 3-digit feet/inches format (e.g., "509" = 5'09")
|
|
103
|
+
if (/^\d{3}$/.test(heightStr)) {
|
|
104
|
+
const feet = parseInt(heightStr.charAt(0), 10);
|
|
105
|
+
const inches = parseInt(heightStr.substring(1), 10);
|
|
106
|
+
return String(feet * 12 + inches); // Convert to total inches
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// If it's already a numeric value (with possible leading zeros), parse it
|
|
110
|
+
if (/^\d+$/.test(heightStr)) {
|
|
111
|
+
return String(parseInt(heightStr, 10)); // Remove leading zeros
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Otherwise keep as-is
|
|
115
|
+
return height;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Converts sex code from M/F to 1/2 for AAMVA
|
|
120
|
+
* @param {string} sexCode - Sex code from barcode
|
|
121
|
+
* @returns {string} AAMVA numeric code (1=Male, 2=Female)
|
|
122
|
+
*/
|
|
123
|
+
function sanitizeSexCode(sexCode) {
|
|
124
|
+
const sexStr = String(sexCode).trim().toUpperCase();
|
|
125
|
+
|
|
126
|
+
if (sexStr === 'M' || sexStr === 'MALE') {
|
|
127
|
+
return '1';
|
|
128
|
+
} else if (sexStr === 'F' || sexStr === 'FEMALE') {
|
|
129
|
+
return '2';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If already 1 or 2, keep as is
|
|
133
|
+
return sexCode;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Validates eye color against AAMVA standard codes and logs an error if invalid
|
|
138
|
+
* Only transforms BRN → BRO (known alternative spelling for Brown)
|
|
139
|
+
* Example: "BRN" → "BRO"
|
|
140
|
+
* @param {string} eyeColor - Eye color from barcode
|
|
141
|
+
* @returns {string} Eye color value (unchanged unless BRN)
|
|
142
|
+
*/
|
|
143
|
+
function sanitizeEyeColor(eyeColor) {
|
|
144
|
+
// AAMVA-accepted eye color codes (official list)
|
|
145
|
+
const VALID_EYE_COLORS = ['BLK', 'BLU', 'BRO', 'DIC', 'GRY', 'GRN', 'HAZ', 'MAR', 'PNK', 'UNK'];
|
|
146
|
+
|
|
147
|
+
let value = String(eyeColor).trim();
|
|
148
|
+
|
|
149
|
+
// Map BRN → BRO (known alternative spelling for Brown)
|
|
150
|
+
if (value === 'BRN') {
|
|
151
|
+
value = 'BRO';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Validate against AAMVA standard list and log error if invalid
|
|
155
|
+
if (!VALID_EYE_COLORS.includes(value)) {
|
|
156
|
+
console.error(
|
|
157
|
+
`[AAMVA Validation Error] Eye color "${value}" is not a valid AAMVA code. ` +
|
|
158
|
+
`Accepted values: BLK (Black), BLU (Blue), BRO (Brown), DIC (Dichromatic), ` +
|
|
159
|
+
`GRY (Gray), GRN (Green), HAZ (Hazel), MAR (Maroon), PNK (Pink), UNK (Unknown). ` +
|
|
160
|
+
`DMV verification will likely fail.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Trims leading and trailing spaces from license number
|
|
169
|
+
* Preserves internal spaces as some jurisdictions may require them
|
|
170
|
+
* Example: " QWE 234 KHL " → "QWE 234 KHL"
|
|
171
|
+
* @param {string} licenseNumber - License number from barcode
|
|
172
|
+
* @returns {string} License number with leading/trailing spaces removed
|
|
173
|
+
*/
|
|
174
|
+
function sanitizeLicenseNumber(licenseNumber) {
|
|
175
|
+
return String(licenseNumber).trim();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Sanitizes postal code by removing dashes and trimming spaces
|
|
180
|
+
* AAMVA may not accept dashes in zip codes
|
|
181
|
+
* Example: "08223-1518" → "082231518", " 12345 " → "12345"
|
|
182
|
+
* @param {string} postalCode - Postal code from barcode
|
|
183
|
+
* @returns {string} Sanitized postal code without dashes or extra spaces
|
|
184
|
+
*/
|
|
185
|
+
function sanitizePostalCode(postalCode) {
|
|
186
|
+
return String(postalCode).replace(/-/g, '').trim();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Sanitizes last name by removing spaces (combines suffix with last name)
|
|
191
|
+
* When suffix is included in last name field, remove spaces for AAMVA
|
|
192
|
+
* Example: "MYLAST JR" → "MYLASTJR", "O'BRIEN III" → "O'BRIENIII"
|
|
193
|
+
* @param {string} lastName - Last name from barcode
|
|
194
|
+
* @returns {string} Last name with spaces removed
|
|
195
|
+
*/
|
|
196
|
+
function sanitizeLastName(lastName) {
|
|
197
|
+
return String(lastName).replace(/\s+/g, '');
|
|
198
|
+
}
|