verant_id_cloud_scan 1.4.4-beta.0 → 1.4.4-beta.2
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 +106 -122
- package/CloudScan.js +96 -7
- package/DateUtils.js +57 -0
- package/FieldMapping.js +72 -0
- package/LocalScannerApi.js +7 -0
- package/VerificationModal.js +499 -0
- package/dmv-modal.css +512 -0
- package/index.d.ts +1 -0
- package/package.json +1 -1
package/Api.js
CHANGED
|
@@ -1,136 +1,55 @@
|
|
|
1
|
+
import { compressAndCompareImages } from "./FaceComparison.js";
|
|
2
|
+
import { formatDateToISO } from "./DateUtils.js";
|
|
3
|
+
import { FIELD_MAPPING, FIELD_DEFAULTS, getFieldValue } from "./FieldMapping.js";
|
|
4
|
+
|
|
1
5
|
//prod
|
|
2
6
|
const licenseServerAddress = "https://lic.verantid.com/api/v1";
|
|
3
7
|
const dmvServerAddress = "https://dmv.verantid.com/api/v1/dmv_check";
|
|
4
|
-
//staging
|
|
8
|
+
// staging
|
|
5
9
|
// const licenseServerAddress =
|
|
6
10
|
// "https://verant-license-server-staging-52b03b060a98.herokuapp.com/api/v1";
|
|
7
11
|
// const dmvServerAddress =
|
|
8
12
|
// "https://dmv-check-server-staging-fcaab48bec21.herokuapp.com/api/v1/dmv_check";
|
|
9
13
|
|
|
10
|
-
// Helper function that checks multiple possible keys and returns a non-empty value
|
|
11
|
-
const getLicenseFieldValue = (data, possibleKeys) => {
|
|
12
|
-
for (const key of possibleKeys) {
|
|
13
|
-
if (data.hasOwnProperty(key)) {
|
|
14
|
-
const value = data[key];
|
|
15
|
-
if (value !== undefined && value !== null && value !== "") {
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return undefined;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
14
|
export const dmvCheck = async (clientId, scannerType, licenseData) => {
|
|
24
15
|
const url = dmvServerAddress; // make sure dmvServerAddress is defined elsewhere
|
|
25
16
|
|
|
26
17
|
// Build the driver object dynamically.
|
|
27
18
|
const driver = {};
|
|
28
19
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
field: "license_number",
|
|
52
|
-
keys: ["license_number", "licenseNumber", "LicenseNumber", "LicenseOrIDNumber"],
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
field: "expiration_date",
|
|
56
|
-
keys: [
|
|
57
|
-
"expiration_date",
|
|
58
|
-
"expirationDate",
|
|
59
|
-
"ExpirationDate",
|
|
60
|
-
"LicenseExpirationDate",
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
field: "issue_date",
|
|
65
|
-
keys: [
|
|
66
|
-
"issue_date",
|
|
67
|
-
"issueDate",
|
|
68
|
-
"IssueDate",
|
|
69
|
-
"LicenseOrIDDocumentIssueDate",
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
field: "first_name",
|
|
74
|
-
keys: ["first_name", "firstName", "FirstName"],
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
field: "last_name",
|
|
78
|
-
keys: ["last_name", "lastName", "LastName", "FamilyName"],
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
field: "birth_date",
|
|
82
|
-
keys: ["birth_date", "birthDate", "BirthDate", "DateOfBirth"],
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
field: "eye_color",
|
|
86
|
-
keys: ["eye_color", "eyeColor", "EyeColor"],
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
field: "sex_code",
|
|
90
|
-
keys: ["sex_code", "sexCode", "SexCode", "sex", "Sex"],
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
field: "address_line_1",
|
|
94
|
-
keys: [
|
|
95
|
-
"address_line_1",
|
|
96
|
-
"addressLine1",
|
|
97
|
-
"AddressLine1",
|
|
98
|
-
"MailingStreetAddress1",
|
|
99
|
-
],
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
field: "city",
|
|
103
|
-
keys: ["city", "City", "MailingCity"],
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
field: "state",
|
|
107
|
-
keys: ["state", "State", "MailingJurisdictionCode"],
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
field: "postal_code",
|
|
111
|
-
keys: ["postal_code", "postalCode", "PostalCode", "MailingPostalCode"],
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
field: "height",
|
|
115
|
-
keys: ["height", "Height", "HeightInFT_IN", "HeightInCM"],
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
field: "weight",
|
|
119
|
-
keys: ["weight", "Weight", "WeightInLBS", "WeightInKG"],
|
|
120
|
-
},
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
// Iterate over each mapping. If a non-empty value is found (or a default exists), add the field.
|
|
124
|
-
fieldMapping.forEach((mapping) => {
|
|
125
|
-
let value = getLicenseFieldValue(licenseData, mapping.keys);
|
|
126
|
-
if (value === undefined) {
|
|
127
|
-
if (mapping.hasOwnProperty("default")) {
|
|
128
|
-
value = mapping.default;
|
|
129
|
-
} else {
|
|
130
|
-
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;
|
|
131
40
|
}
|
|
132
41
|
}
|
|
133
|
-
|
|
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;
|
|
134
53
|
});
|
|
135
54
|
|
|
136
55
|
// Assemble the data payload.
|
|
@@ -153,13 +72,43 @@ export const dmvCheck = async (clientId, scannerType, licenseData) => {
|
|
|
153
72
|
const responseData = await response.json();
|
|
154
73
|
return responseData; // Return the response data
|
|
155
74
|
} else {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
+
}
|
|
159
108
|
}
|
|
160
109
|
} catch (error) {
|
|
161
110
|
console.error("There was a problem with the dmv_check request:", error);
|
|
162
|
-
return
|
|
111
|
+
return { status: 'error', message: error.message || 'Unable to connect to DMV verification service.' };
|
|
163
112
|
}
|
|
164
113
|
};
|
|
165
114
|
|
|
@@ -256,6 +205,41 @@ export const checkLicense = async (clientId, macAddress) => {
|
|
|
256
205
|
}
|
|
257
206
|
};
|
|
258
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
|
+
};
|
|
259
243
|
export const getBarcodeLicenseKey = async () => {
|
|
260
244
|
const url = licenseServerAddress + "/get_barcode_license_key";
|
|
261
245
|
|
|
@@ -379,4 +363,4 @@ export const checkScannerPresent = async (scannerAddress) => {
|
|
|
379
363
|
// Any error means scanner is not reachable
|
|
380
364
|
return false;
|
|
381
365
|
}
|
|
382
|
-
};
|
|
366
|
+
};
|
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, scanFrontOnly } 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,
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
scannerChain,
|
|
15
15
|
scannerPresent,
|
|
16
16
|
} from "./ScannerApi.js";
|
|
17
|
+
import { VerificationModal } from "./VerificationModal.js";
|
|
18
|
+
import { checkAutoDmvEnabled } from "./Api.js";
|
|
17
19
|
|
|
18
20
|
let cachedLicenseFrontBase64 = "";
|
|
19
21
|
|
|
@@ -138,6 +140,33 @@ export const scanId = async (
|
|
|
138
140
|
}
|
|
139
141
|
barcodeResultsObject = extractInformation(res.barcodeResults);
|
|
140
142
|
returnObj.barcodeResultsObject = barcodeResultsObject;
|
|
143
|
+
|
|
144
|
+
// Check if autoDmv is enabled and trigger modal
|
|
145
|
+
const autoDmvEnabled = await checkAutoDmvEnabled(clientId);
|
|
146
|
+
if (autoDmvEnabled) {
|
|
147
|
+
console.log('[AUTO-DMV] Feature enabled, showing verification modal');
|
|
148
|
+
|
|
149
|
+
// Determine scanner type - you can customize this based on your needs
|
|
150
|
+
const scannerType = 'DigitalCheck'; // Default scanner type
|
|
151
|
+
|
|
152
|
+
// Create and open the modal
|
|
153
|
+
const modal = new VerificationModal(clientId, scannerType, barcodeResultsObject, {
|
|
154
|
+
primaryColor: '#0074CB' // VerantID blue
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const dmvResult = await modal.open();
|
|
159
|
+
console.log('[AUTO-DMV] Modal result:', dmvResult);
|
|
160
|
+
|
|
161
|
+
// Attach DMV result to return object
|
|
162
|
+
returnObj.dmvVerificationResult = dmvResult;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('[AUTO-DMV] Modal error:', error);
|
|
165
|
+
returnObj.dmvVerificationResult = { status: 'error', error: error.message };
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
console.log('[AUTO-DMV] Feature disabled for client:', clientId);
|
|
169
|
+
}
|
|
141
170
|
}
|
|
142
171
|
return returnObj;
|
|
143
172
|
};
|
|
@@ -159,6 +188,26 @@ function getField(keyword, foundBarcodeString, beginNewline = true) {
|
|
|
159
188
|
return subtext;
|
|
160
189
|
}
|
|
161
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Formats sex code for display
|
|
193
|
+
* Converts AAMVA numeric codes (1=Male, 2=Female) to letters (M, F)
|
|
194
|
+
* Preserves any other values unchanged
|
|
195
|
+
* @param {string} sexCode - The sex code from the barcode
|
|
196
|
+
* @returns {string} Formatted sex code
|
|
197
|
+
*/
|
|
198
|
+
function formatSexCodeForDisplay(sexCode) {
|
|
199
|
+
if (!sexCode) return '';
|
|
200
|
+
|
|
201
|
+
const code = String(sexCode).trim();
|
|
202
|
+
|
|
203
|
+
// AAMVA standard: 1 = Male, 2 = Female
|
|
204
|
+
if (code === '1') return 'M';
|
|
205
|
+
if (code === '2') return 'F';
|
|
206
|
+
|
|
207
|
+
// Return original value for anything else (9, X, M, F, etc.)
|
|
208
|
+
return sexCode;
|
|
209
|
+
}
|
|
210
|
+
|
|
162
211
|
//extracts the information from the barcode
|
|
163
212
|
function extractInformation(foundBarcodeString) {
|
|
164
213
|
var resultsArray = {};
|
|
@@ -167,7 +216,13 @@ function extractInformation(foundBarcodeString) {
|
|
|
167
216
|
var item = driverLicenseFields[i];
|
|
168
217
|
var fieldValue = getField(item.abbreviation, foundBarcodeString);
|
|
169
218
|
if (fieldValue !== false) {
|
|
170
|
-
|
|
219
|
+
// Format sex code for display while preserving original in OriginalSexCode
|
|
220
|
+
if (item.description === 'Sex') {
|
|
221
|
+
resultsArray['OriginalSexCode'] = fieldValue; // Store original for DMV verification
|
|
222
|
+
resultsArray[item.description] = formatSexCodeForDisplay(fieldValue);
|
|
223
|
+
} else {
|
|
224
|
+
resultsArray[item.description] = fieldValue;
|
|
225
|
+
}
|
|
171
226
|
}
|
|
172
227
|
if (item.abbreviation === "DAQ" && !fieldValue) {
|
|
173
228
|
fieldValue = getField("DLDAQ", foundBarcodeString, false);
|
|
@@ -196,18 +251,15 @@ export const localScanId = async (scannerAddress, clientId) => {
|
|
|
196
251
|
console.log("scanner address: " + scannerAddress);
|
|
197
252
|
let scannerResponseObj = await localScannerChain(scannerAddress, clientId);
|
|
198
253
|
|
|
199
|
-
//console.log(scannerResponseObj);
|
|
200
254
|
if (scannerResponseObj.errorMessages.length > 0) {
|
|
201
255
|
returnObj.errorMessages = scannerResponseObj.errorMessages;
|
|
202
256
|
return returnObj;
|
|
203
|
-
//return error
|
|
204
257
|
}
|
|
205
258
|
cachedLicenseFrontBase64 = scannerResponseObj.licenseFrontBase64;
|
|
206
|
-
//console.log(cachedLicenseFrontBase64);
|
|
207
259
|
return true;
|
|
208
260
|
};
|
|
209
261
|
|
|
210
|
-
export const localContinueScanId = async (scannerAddress, includeData, skipBackScan = false) => {
|
|
262
|
+
export const localContinueScanId = async (scannerAddress, includeData, clientId = null, skipBackScan = false) => {
|
|
211
263
|
let barcodeResultsObject = {};
|
|
212
264
|
let returnObj = {
|
|
213
265
|
barcodeResultsObject,
|
|
@@ -229,7 +281,8 @@ export const localContinueScanId = async (scannerAddress, includeData, skipBackS
|
|
|
229
281
|
else {
|
|
230
282
|
scannerResponseObj = await scanBack(scannerAddress, includeData);
|
|
231
283
|
}
|
|
232
|
-
|
|
284
|
+
|
|
285
|
+
//console.log(scannerResponseObj);
|
|
233
286
|
if (scannerResponseObj.errorMessages.length > 0) {
|
|
234
287
|
returnObj.errorMessages = scannerResponseObj.errorMessages;
|
|
235
288
|
return returnObj;
|
|
@@ -285,6 +338,42 @@ export const localContinueScanId = async (scannerAddress, includeData, skipBackS
|
|
|
285
338
|
}
|
|
286
339
|
barcodeResultsObject = extractInformation(barcodeResponse.barcodeResults);
|
|
287
340
|
returnObj.barcodeResultsObject = barcodeResultsObject;
|
|
341
|
+
|
|
342
|
+
// Check if autoDmv is enabled and trigger modal
|
|
343
|
+
// Use provided clientId, or fall back to cached clientId from localScanId
|
|
344
|
+
const effectiveClientId = clientId || getCachedClientId();
|
|
345
|
+
|
|
346
|
+
if (!effectiveClientId) {
|
|
347
|
+
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.');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (effectiveClientId) {
|
|
351
|
+
const autoDmvEnabled = await checkAutoDmvEnabled(effectiveClientId);
|
|
352
|
+
if (autoDmvEnabled) {
|
|
353
|
+
console.log('[AUTO-DMV] Feature enabled, showing verification modal');
|
|
354
|
+
|
|
355
|
+
// Determine scanner type - you can customize this based on your needs
|
|
356
|
+
const scannerType = 'VerantId6S'; // Default scanner type for local scanner
|
|
357
|
+
|
|
358
|
+
// Create and open the modal
|
|
359
|
+
const modal = new VerificationModal(effectiveClientId, scannerType, barcodeResultsObject, {
|
|
360
|
+
primaryColor: '#0074CB' // VerantID blue
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const dmvResult = await modal.open();
|
|
365
|
+
console.log('[AUTO-DMV] Modal result:', dmvResult);
|
|
366
|
+
|
|
367
|
+
// Attach DMV result to return object
|
|
368
|
+
returnObj.dmvVerificationResult = dmvResult;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error('[AUTO-DMV] Modal error:', error);
|
|
371
|
+
returnObj.dmvVerificationResult = { status: 'error', error: error.message };
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
console.log('[AUTO-DMV] Feature disabled for client:', effectiveClientId);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
288
377
|
}
|
|
289
378
|
return returnObj;
|
|
290
379
|
};
|
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/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();
|
|
@@ -507,3 +509,8 @@ async function clearImageAndCloseScannerConnection() {
|
|
|
507
509
|
return false;
|
|
508
510
|
}
|
|
509
511
|
}
|
|
512
|
+
|
|
513
|
+
// Getter for cached clientId
|
|
514
|
+
export function getCachedClientId() {
|
|
515
|
+
return cachedClientId;
|
|
516
|
+
}
|