verant_id_cloud_scan 1.4.3 → 1.4.4-beta.1

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