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 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
- // Mapping of our driver field names to an array of possible keys
30
- // Optionally include a default value if no valid value is found.
31
- const fieldMapping = [
32
- {
33
- field: "jurisdiction_code",
34
- keys: [
35
- "jurisdiction_code",
36
- "jurisdictionCode",
37
- "JurisdictionCode",
38
- "MailingJurisdictionCode",
39
- ],
40
- },
41
- {
42
- field: "document_category_code",
43
- keys: [
44
- "document_category_code",
45
- "documentCategoryCode",
46
- "DocumentCategoryCode",
47
- ],
48
- default: "1", // default value if nothing is found
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
- driver[mapping.field] = value;
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
- const errorText = await response.text(); // Read once and store
157
- console.error("DMV check failed:", errorText);
158
- return errorText; // Return the stored text
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 null; // Error occurred
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
- // Ensure the scanner address has a protocol (http:// or https://)
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
- resultsArray[item.description] = fieldValue;
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
+ };
@@ -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
+ };
@@ -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,