verant_id_cloud_scan 1.4.4-beta.0 → 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/Api.js +106 -166
- package/CloudScan.js +96 -10
- package/DateUtils.js +57 -0
- package/FieldMapping.js +72 -0
- package/LocalScannerApi.js +7 -0
- package/ScannerApi.js +0 -4
- package/VerificationModal.js +499 -0
- package/dmv-modal.css +512 -0
- package/index.d.ts +1 -2
- 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
|
|
|
@@ -284,13 +268,7 @@ const postToScannerApi = async (
|
|
|
284
268
|
commandObj,
|
|
285
269
|
timeout = 10000
|
|
286
270
|
) => {
|
|
287
|
-
|
|
288
|
-
let formattedAddress = scannerAddress;
|
|
289
|
-
if (!scannerAddress.startsWith('http://') && !scannerAddress.startsWith('https://')) {
|
|
290
|
-
formattedAddress = `http://${scannerAddress}`;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
let apiAddress = `${formattedAddress}/securelink`;
|
|
271
|
+
let apiAddress =`${scannerAddress}/securelink`;
|
|
294
272
|
|
|
295
273
|
const controller = new AbortController();
|
|
296
274
|
const signal = controller.signal;
|
|
@@ -342,41 +320,3 @@ export const stopScanning = (scannerAddress, commandObj) =>
|
|
|
342
320
|
|
|
343
321
|
export const closeConnection = (scannerAddress, commandObj) =>
|
|
344
322
|
postToScannerApi(scannerAddress, commandObj);
|
|
345
|
-
|
|
346
|
-
// Check if scanner is present by attempting to connect and disconnect
|
|
347
|
-
export const checkScannerPresent = async (scannerAddress) => {
|
|
348
|
-
try {
|
|
349
|
-
// Generate a unique message ID for this check
|
|
350
|
-
const messageId = new Date().toISOString();
|
|
351
|
-
|
|
352
|
-
// Attempt to establish connection
|
|
353
|
-
const connectCommand = {
|
|
354
|
-
Command: "Connect",
|
|
355
|
-
MessageId: messageId,
|
|
356
|
-
Exclusive: "false", // Use non-exclusive mode for just checking
|
|
357
|
-
IdleTimeout: "5",
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
const connectResponse = await postToScannerApi(scannerAddress, connectCommand, 5000);
|
|
361
|
-
|
|
362
|
-
// If connection failed or no session ID, scanner not present
|
|
363
|
-
if (!connectResponse || !connectResponse.SessionId) {
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Got a session ID, now disconnect
|
|
368
|
-
const disconnectCommand = {
|
|
369
|
-
Command: "Disconnect",
|
|
370
|
-
MessageId: new Date().toISOString(),
|
|
371
|
-
SessionId: connectResponse.SessionId,
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
await postToScannerApi(scannerAddress, disconnectCommand, 5000);
|
|
375
|
-
|
|
376
|
-
// Successfully connected and disconnected
|
|
377
|
-
return true;
|
|
378
|
-
} catch (error) {
|
|
379
|
-
// Any error means scanner is not reachable
|
|
380
|
-
return false;
|
|
381
|
-
}
|
|
382
|
-
};
|
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,
|
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
dmvCheck,
|
|
13
13
|
getBarcodeLicenseKey,
|
|
14
14
|
scannerChain,
|
|
15
|
-
scannerPresent,
|
|
16
15
|
} from "./ScannerApi.js";
|
|
16
|
+
import { VerificationModal } from "./VerificationModal.js";
|
|
17
|
+
import { checkAutoDmvEnabled } from "./Api.js";
|
|
17
18
|
|
|
18
19
|
let cachedLicenseFrontBase64 = "";
|
|
19
20
|
|
|
@@ -33,8 +34,6 @@ export const checkFaceCompareFeature = async (clientId) => {
|
|
|
33
34
|
return await checkFaceComparisonFeatureLicense(clientId);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
export { scannerPresent };
|
|
37
|
-
|
|
38
37
|
export async function readImageBarcode(imageFile) {
|
|
39
38
|
let returnObj = {
|
|
40
39
|
barcodeResultsObject: {},
|
|
@@ -138,6 +137,33 @@ export const scanId = async (
|
|
|
138
137
|
}
|
|
139
138
|
barcodeResultsObject = extractInformation(res.barcodeResults);
|
|
140
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
|
+
}
|
|
141
167
|
}
|
|
142
168
|
return returnObj;
|
|
143
169
|
};
|
|
@@ -159,6 +185,26 @@ function getField(keyword, foundBarcodeString, beginNewline = true) {
|
|
|
159
185
|
return subtext;
|
|
160
186
|
}
|
|
161
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
|
+
|
|
162
208
|
//extracts the information from the barcode
|
|
163
209
|
function extractInformation(foundBarcodeString) {
|
|
164
210
|
var resultsArray = {};
|
|
@@ -167,7 +213,13 @@ function extractInformation(foundBarcodeString) {
|
|
|
167
213
|
var item = driverLicenseFields[i];
|
|
168
214
|
var fieldValue = getField(item.abbreviation, foundBarcodeString);
|
|
169
215
|
if (fieldValue !== false) {
|
|
170
|
-
|
|
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
|
+
}
|
|
171
223
|
}
|
|
172
224
|
if (item.abbreviation === "DAQ" && !fieldValue) {
|
|
173
225
|
fieldValue = getField("DLDAQ", foundBarcodeString, false);
|
|
@@ -196,18 +248,15 @@ export const localScanId = async (scannerAddress, clientId) => {
|
|
|
196
248
|
console.log("scanner address: " + scannerAddress);
|
|
197
249
|
let scannerResponseObj = await localScannerChain(scannerAddress, clientId);
|
|
198
250
|
|
|
199
|
-
//console.log(scannerResponseObj);
|
|
200
251
|
if (scannerResponseObj.errorMessages.length > 0) {
|
|
201
252
|
returnObj.errorMessages = scannerResponseObj.errorMessages;
|
|
202
253
|
return returnObj;
|
|
203
|
-
//return error
|
|
204
254
|
}
|
|
205
255
|
cachedLicenseFrontBase64 = scannerResponseObj.licenseFrontBase64;
|
|
206
|
-
//console.log(cachedLicenseFrontBase64);
|
|
207
256
|
return true;
|
|
208
257
|
};
|
|
209
258
|
|
|
210
|
-
export const localContinueScanId = async (scannerAddress, includeData, skipBackScan = false) => {
|
|
259
|
+
export const localContinueScanId = async (scannerAddress, includeData, clientId = null, skipBackScan = false) => {
|
|
211
260
|
let barcodeResultsObject = {};
|
|
212
261
|
let returnObj = {
|
|
213
262
|
barcodeResultsObject,
|
|
@@ -229,7 +278,8 @@ export const localContinueScanId = async (scannerAddress, includeData, skipBackS
|
|
|
229
278
|
else {
|
|
230
279
|
scannerResponseObj = await scanBack(scannerAddress, includeData);
|
|
231
280
|
}
|
|
232
|
-
|
|
281
|
+
|
|
282
|
+
//console.log(scannerResponseObj);
|
|
233
283
|
if (scannerResponseObj.errorMessages.length > 0) {
|
|
234
284
|
returnObj.errorMessages = scannerResponseObj.errorMessages;
|
|
235
285
|
return returnObj;
|
|
@@ -285,6 +335,42 @@ export const localContinueScanId = async (scannerAddress, includeData, skipBackS
|
|
|
285
335
|
}
|
|
286
336
|
barcodeResultsObject = extractInformation(barcodeResponse.barcodeResults);
|
|
287
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
|
+
}
|
|
288
374
|
}
|
|
289
375
|
return returnObj;
|
|
290
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/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
|
+
}
|
package/ScannerApi.js
CHANGED
|
@@ -41,10 +41,6 @@ export async function dmvCheck(clientId, scannerType, licenseData) {
|
|
|
41
41
|
return await API.dmvCheck(clientId, scannerType, licenseData);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export async function scannerPresent(scannerAddress) {
|
|
45
|
-
return await API.checkScannerPresent(scannerAddress);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
44
|
//This is the top level function that calls the other functions in order
|
|
49
45
|
export async function scannerChain(
|
|
50
46
|
scannerAddress,
|