verant_id_cloud_scan 1.4.4 → 1.4.5-beta.10

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,18 +1,112 @@
1
1
  import { compressAndCompareImages } from "./FaceComparison.js";
2
2
  import { formatDateToISO, DATE_FIELDS } from "./DateUtils.js";
3
3
  import { FIELD_MAPPING, FIELD_DEFAULTS, getFieldValue } from "./FieldMapping.js";
4
+ import { sanitizeFieldForDmv } from "./FormatUtils.js";
4
5
 
5
- //prod
6
- const licenseServerAddress = "https://lic.verantid.com/api/v1";
7
- const dmvServerAddress = "https://dmv.verantid.com/api/v1/dmv_check";
8
- // staging
9
- // const licenseServerAddress =
10
- // "https://verant-license-server-staging-52b03b060a98.herokuapp.com/api/v1";
11
- // const dmvServerAddress =
12
- // "https://dmv-check-server-staging-fcaab48bec21.herokuapp.com/api/v1/dmv_check";
6
+ /**
7
+ * Environment Detection & Management
8
+ *
9
+ * Defaults to PRODUCTION for all hosts (including localhost).
10
+ * Only verify-staging.verantid.com automatically uses staging.
11
+ * Host apps can explicitly override via setEnvironment('staging' | 'production').
12
+ */
13
+
14
+ // Current environment - starts as null to trigger detection on first use
15
+ let currentEnvironment = null;
16
+
17
+ /**
18
+ * Detect environment based on hostname
19
+ * @returns {'staging' | 'production'}
20
+ */
21
+ function detectEnvironment() {
22
+ // Default to production for all hosts
23
+ let environment = 'production';
24
+
25
+ // Only auto-switch to staging for VerantID's staging domain
26
+ if (typeof window !== 'undefined') {
27
+ const hostname = window.location.hostname;
28
+
29
+ // ONLY verify-staging.verantid.com uses staging by default
30
+ if (hostname === 'verify-staging.verantid.com') {
31
+ environment = 'staging';
32
+ }
33
+
34
+ // All other hostnames (localhost, customer sites, verify.verantid.com, etc.) use production
35
+ }
36
+
37
+ console.log('[CloudScan] Environment detected:', environment, 'on', typeof window !== 'undefined' ? window.location.hostname : 'server');
38
+ return environment;
39
+ }
40
+
41
+ /**
42
+ * Get current environment (detects if not already set)
43
+ * @returns {'staging' | 'production'}
44
+ */
45
+ function getEnvironment() {
46
+ if (currentEnvironment === null) {
47
+ currentEnvironment = detectEnvironment();
48
+ }
49
+ return currentEnvironment;
50
+ }
51
+
52
+ /**
53
+ * Explicitly set the environment
54
+ * @param {'staging' | 'production'} environment
55
+ * @throws {Error} If environment is not 'staging' or 'production'
56
+ */
57
+ export function setEnvironment(environment) {
58
+ if (environment !== 'staging' && environment !== 'production') {
59
+ throw new Error(`Invalid environment: "${environment}". Must be "staging" or "production".`);
60
+ }
61
+ currentEnvironment = environment;
62
+ }
63
+
64
+ /**
65
+ * Get license server address based on current environment
66
+ * @returns {string}
67
+ */
68
+ function getLicenseServerAddress() {
69
+ const env = getEnvironment();
70
+ return env === 'staging'
71
+ ? "https://lic-staging.verantid.com/api/v1"
72
+ : "https://lic.verantid.com/api/v1";
73
+ }
74
+
75
+ /**
76
+ * Get DMV server address based on current environment
77
+ * @returns {string}
78
+ */
79
+ function getDmvServerAddress() {
80
+ const env = getEnvironment();
81
+ return env === 'staging'
82
+ ? "https://dmv-check-server-staging-fcaab48bec21.herokuapp.com/api/v1/dmv_check"
83
+ : "https://dmv.verantid.com/api/v1/dmv_check";
84
+ }
85
+
86
+ /**
87
+ * Get Face Comparison server address based on current environment
88
+ * @returns {string}
89
+ */
90
+ export function getFaceComparisonServerAddress() {
91
+ const env = getEnvironment();
92
+ return env === 'staging'
93
+ ? "https://staging.face.verantid.bluerocket.us/compare_id_and_face"
94
+ : "https://faceid.verantid.com/compare_id_and_face";
95
+ }
96
+
97
+ /**
98
+ * Get Mobile Verification server address based on current environment
99
+ * @returns {string}
100
+ */
101
+ function getMobileVerificationServerAddress() {
102
+ const env = getEnvironment();
103
+ return env === 'staging'
104
+ ? "https://staging.mdl.verantid.bluerocket.us"
105
+ : "https://mdl.verantid.bluerocket.us";
106
+ }
13
107
 
14
108
  export const dmvCheck = async (clientId, scannerType, licenseData) => {
15
- const url = dmvServerAddress; // make sure dmvServerAddress is defined elsewhere
109
+ const url = getDmvServerAddress();
16
110
 
17
111
  // Build the driver object dynamically.
18
112
  const driver = {};
@@ -36,16 +130,8 @@ export const dmvCheck = async (clientId, scannerType, licenseData) => {
36
130
  value = formatDateToISO(value);
37
131
  }
38
132
 
39
- // Convert sex_code from M/F to 1/2 for AAMVA compliance
40
- if (fieldName === 'sex_code' && value) {
41
- const sexStr = String(value).trim().toUpperCase();
42
- if (sexStr === 'M' || sexStr === 'MALE') {
43
- value = '1';
44
- } else if (sexStr === 'F' || sexStr === 'FEMALE') {
45
- value = '2';
46
- }
47
- // If already 1 or 2, keep as is
48
- }
133
+ // Sanitize field value for DMV/AAMVA submission
134
+ value = sanitizeFieldForDmv(fieldName, value);
49
135
 
50
136
  driver[fieldName] = value;
51
137
  });
@@ -111,7 +197,7 @@ export const dmvCheck = async (clientId, scannerType, licenseData) => {
111
197
  };
112
198
 
113
199
  export const checkDmvFeatureLicense = async (clientId) => {
114
- const url = licenseServerAddress + "/dmv_verifications/check_license";
200
+ const url = getLicenseServerAddress() + "/dmv_verifications/check_license";
115
201
  const data = { client_id: clientId };
116
202
 
117
203
  try {
@@ -145,7 +231,7 @@ export const checkDmvFeatureLicense = async (clientId) => {
145
231
  };
146
232
 
147
233
  export const checkFaceComparisonFeatureLicense = async (clientId) => {
148
- const url = licenseServerAddress + "/facial_scans/check_license";
234
+ const url = getLicenseServerAddress() + "/facial_scans/check_license";
149
235
  const data = { client_id: clientId };
150
236
 
151
237
  try {
@@ -179,7 +265,7 @@ export const checkFaceComparisonFeatureLicense = async (clientId) => {
179
265
  }
180
266
 
181
267
  export const checkLicense = async (clientId, macAddress) => {
182
- const url = licenseServerAddress + "/check_license";
268
+ const url = getLicenseServerAddress() + "/check_license";
183
269
  const data = { client_id: clientId, mac_address: macAddress };
184
270
 
185
271
  try {
@@ -214,7 +300,7 @@ export const checkLicense = async (clientId, macAddress) => {
214
300
  * @returns {Promise<boolean>} True if autoDmv is enabled
215
301
  */
216
302
  export const checkAutoDmvEnabled = async (clientId) => {
217
- const url = licenseServerAddress + "/check_feature_license";
303
+ const url = getLicenseServerAddress() + "/check_feature_license";
218
304
  const data = { client_id: clientId };
219
305
 
220
306
  try {
@@ -239,7 +325,7 @@ export const checkAutoDmvEnabled = async (clientId) => {
239
325
  }
240
326
  };
241
327
  export const getBarcodeLicenseKey = async () => {
242
- const url = licenseServerAddress + "/get_barcode_license_key";
328
+ const url = getLicenseServerAddress() + "/get_barcode_license_key";
243
329
 
244
330
  try {
245
331
  const response = await fetch(url, {
@@ -272,6 +358,9 @@ const postToScannerApi = async (
272
358
  formattedAddress = `http://${scannerAddress}`;
273
359
  }
274
360
 
361
+ // Remove trailing slash to prevent double slashes in URL
362
+ formattedAddress = formattedAddress.replace(/\/+$/, '');
363
+
275
364
  let apiAddress = `${formattedAddress}/securelink`;
276
365
 
277
366
  const controller = new AbortController();
@@ -362,3 +451,70 @@ export const checkScannerPresent = async (scannerAddress) => {
362
451
  return false;
363
452
  }
364
453
  };
454
+
455
+ /**
456
+ * Create a verification session for mobile ID verification
457
+ *
458
+ * POST /sessions
459
+ * Returns: session_id, session_url (for QR code), websocket_url (for real-time updates)
460
+ *
461
+ * @param {string} userId - User ID for the verification session
462
+ * @param {string} clientId - Client ID for licensing and tracking
463
+ * @param {string} [origin] - Holder page origin; binds the server's
464
+ * SessionTranscript to the wallet's view (HPKE info). Required on non-default hosts.
465
+ * @returns {Promise<Object>} Session data with session_id, session_url, and websocket_url
466
+ */
467
+ export const createVerificationSession = async (userId, clientId, origin) => {
468
+ const url = getMobileVerificationServerAddress() + "/sessions";
469
+ const data = { user_id: userId, client_id: clientId };
470
+ if (origin) {
471
+ data.origin = origin;
472
+ }
473
+
474
+ try {
475
+ const response = await fetch(url, {
476
+ method: "POST",
477
+ headers: {
478
+ "Content-Type": "application/json",
479
+ },
480
+ body: JSON.stringify(data),
481
+ });
482
+
483
+ if (response.ok) {
484
+ const responseData = await response.json();
485
+ return responseData;
486
+ } else {
487
+ // Read the body once as text — fetch consumes the stream on first read,
488
+ // so a `response.json()` then `response.text()` fallback would throw
489
+ // `TypeError: body stream already read` and mask the original error.
490
+ const errorText = await response.text();
491
+ let errorData = null;
492
+ try {
493
+ errorData = JSON.parse(errorText);
494
+ } catch {
495
+ // Non-JSON body (e.g. plain text 502 from API Gateway).
496
+ }
497
+
498
+ if (errorData) {
499
+ console.error("Verification session creation failed:", errorData);
500
+ return {
501
+ status: 'error',
502
+ message: errorData.message || 'Failed to create verification session',
503
+ error: errorData,
504
+ };
505
+ }
506
+ console.error("Verification session creation failed:", errorText);
507
+ return {
508
+ status: 'error',
509
+ message: 'Failed to create verification session',
510
+ raw_error: errorText,
511
+ };
512
+ }
513
+ } catch (error) {
514
+ console.error("There was a problem with the verification session request:", error);
515
+ return {
516
+ status: 'error',
517
+ message: error.message || 'Unable to connect to verification service.'
518
+ };
519
+ }
520
+ };
package/CHANGELOG.md ADDED
@@ -0,0 +1,198 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.4.5-beta.10] - 2026-05-11
9
+
10
+ ### Fixed
11
+ - iOS HPKE decryption failure on non-default hosts (e.g. ngrok tunnels,
12
+ custom staging domains). `startMobileVerification()` now derives the
13
+ holder page origin from `mobileVerifyBaseUrl` (falling back to the
14
+ CSR page's `window.location.origin` for same-origin dev setups) and
15
+ forwards it to `POST /sessions`. The backend uses this origin to
16
+ build its SessionTranscript, which must match the origin the mobile
17
+ wallet binds to. Without it, the server fell back to a per-environment
18
+ default that disagreed with the wallet's view, causing
19
+ `"Decryption failed: Could not decrypt credential response"` (400).
20
+ - `WebSocketClient.connect()` no longer hangs forever when the underlying
21
+ socket emits `onerror` or `onclose` before `onopen`. The returned Promise
22
+ now rejects with a tagged error in those paths so awaiters can handle
23
+ connection failures instead of waiting indefinitely.
24
+ - `startMobileVerification()` QR code URL construction now uses `new URL()`
25
+ to safely combine `mobileVerifyBaseUrl` with the session id. Previously,
26
+ a base URL containing a query string or hash (e.g. `https://host/path?x=1`)
27
+ produced an invalid result like `https://host/path?x=1/{session_id}`.
28
+ - `createVerificationSession()` no longer throws "body stream already
29
+ read" when an error response is not valid JSON. The response body is
30
+ now read once as text, then parsed in a try/catch instead of using
31
+ `response.json()` followed by a `response.text()` fallback.
32
+ - `startMobileVerification()` no longer emits duplicate `onError`
33
+ callbacks when a WebSocket failure surfaces via both
34
+ `WebSocketClient.connect()` and the outer catch. Errors already
35
+ reported by a lower layer carry an `error.reported` flag, and the
36
+ outer catch skips re-emitting in that case.
37
+ - `WebSocketClient.connect()` now invokes `callbacks.onError` from the
38
+ close-before-open path (previously only `onerror` did). Direct
39
+ `connectToVerificationSession` callers receive the failure event
40
+ even when no preceding `onerror` fires (e.g., server closes the
41
+ socket immediately). The synthesized error carries `reported: true`
42
+ so `startMobileVerification`'s outer catch still avoids double-emit.
43
+ - `WebSocketClient._handleMessage()` no longer logs the raw WebSocket
44
+ frame to the console. Verification payloads carry PII (mDL fields,
45
+ portrait base64), so the raw `console.log` of `data` was dropped.
46
+ Per-route type logs below remain for debug visibility.
47
+ - `WebSocketClient.connect()` now releases its socket reference when
48
+ `onerror` fires before `onopen`, so callers can immediately retry
49
+ `connect()` without hitting "WebSocket connection already exists"
50
+ in implementations where `onclose` doesn't fire after `onerror`.
51
+ - `WebSocketClient` now transitions to the `ERROR` state (not
52
+ `CLOSED`) when the socket closes before opening, so
53
+ `status`/`onStatusChange` reflect a connection failure rather than
54
+ a normal disconnect. Post-open closes still transition to `CLOSED`.
55
+ Now driven by an explicit `hasOpened` flag (set in `onopen`) — the
56
+ previous version used the Promise's `settled` flag, which is also
57
+ set by pre-open `onerror` and would incorrectly downgrade the
58
+ ERROR state to CLOSED when the follow-up onclose fired.
59
+ - `WebSocketClient.connect()` synchronous-throw rejections now carry
60
+ a stable `.code` (`NO_WEBSOCKET_IMPL` when the Node fallback fails,
61
+ `CONNECTION_FAILED` when the `WebSocket` constructor throws). The
62
+ thrown/rejected error now matches the structured `onError` payload,
63
+ so awaiters of the Promise can branch on the failure reason without
64
+ relying on the `onError` callback.
65
+ - `WebSocketClient.disconnect()` called while the socket is still
66
+ CONNECTING no longer reports a spurious connection error. The
67
+ `onclose` triggered by the user-initiated close used to flip
68
+ `status` from `closed` back to `error` and emit `onError`; it now
69
+ detects an instance-level `_closeRequested` flag set by
70
+ `disconnect()` and skips both. The pending `connect()` Promise
71
+ rejects with `code: 'CLIENT_DISCONNECTED'` so awaiters resolve.
72
+ - `WebSocketClient._handleMessage()` now decodes binary WebSocket
73
+ frames (Node `Buffer` from the `ws` package, browser `ArrayBuffer`
74
+ / `Uint8Array` when `binaryType === 'arraybuffer'`) via `TextDecoder`
75
+ before `JSON.parse`. Previously, anything that wasn't a string
76
+ passed through as-is, so `routeKey` was always undefined and every
77
+ frame was silently dropped as "Unknown message route."
78
+ - Removed informational `console.log` calls from the mobile
79
+ verification code path (`WebSocketClient` + `startMobileVerification`).
80
+ Several of them embedded the `session_id` or QR target URL, which
81
+ is effectively a session access token and shouldn't be sent to
82
+ application logs / monitoring. Diagnostic `console.warn` and
83
+ `console.error` calls are preserved.
84
+
85
+ ### Changed
86
+ - `startMobileVerification()` error callbacks now emit stage-specific
87
+ codes — `SESSION_CREATION_FAILED`, `QR_GENERATION_FAILED`, or
88
+ `WEBSOCKET_CONNECTION_FAILED` — so callers can distinguish which step
89
+ failed. Upstream `error.code` values are preserved when present.
90
+
91
+ ### Internal
92
+ - The internal `createVerificationSession()` API helper gained an
93
+ optional third `origin` parameter; `startMobileVerification()` now
94
+ populates it from `mobileVerifyBaseUrl` (or `window.location.origin`
95
+ for same-origin dev). Host apps don't need to change anything — keep
96
+ calling `startMobileVerification()` as before.
97
+ - `package.json` now declares a `"browser": { "ws": false }` field so
98
+ browser bundlers (Vite / Webpack / Rollup) stub out the Node-only
99
+ `ws` optional dependency instead of trying to bundle it. Has no
100
+ effect in Node — the dynamic `import('ws')` fallback still works.
101
+
102
+ ## [1.4.5-beta.9] - 2026-04-21
103
+
104
+ ### Added
105
+ - Mobile ID verification support (VNT-1409, VNT-1419, VNT-1429, VNT-1455)
106
+ - Added `startMobileVerification()` function to initiate mobile verification sessions
107
+ - Added `createVerificationSession()` API function to create verification sessions with user ID and client ID
108
+ - Added WebSocket client (`WebSocketClient.js`) for real-time verification updates from AWS API Gateway
109
+ - Added mobile verification server address configuration with environment detection
110
+ - QR code generation for mobile verification sessions with customizable base URLs
111
+ - Real-time verification callbacks: `onVerificationStarted`, `onResult`, `onStatusChange`, `onError`
112
+ - Session management with session ID, session URL, WebSocket URL, and QR code SVG
113
+ - Connection state management (CONNECTING, OPEN, CLOSED, ERROR) for WebSocket connections
114
+
115
+ ### Changed
116
+ - Updated environment detection to support mobile verification endpoints
117
+ - Mobile verification uses `staging.mdl.verantid.bluerocket.us` for staging
118
+ - Mobile verification uses `mdl.verantid.bluerocket.us` for production
119
+ - Updated scanner type detection to use `scannerProfile` from scanner response in auto-DMV verification
120
+ - Enhanced TypeScript definitions (`index.d.ts`) to include mobile verification types and callbacks
121
+
122
+ ## [1.4.5-beta.8] - 2026-04-15
123
+
124
+ ### Changed
125
+ - Moved UV/IR image type definitions to scanner profile configuration
126
+ - Added `frontUvImageType`, `backUvImageType`, `frontIrImageType`, and `backIrImageType` to IDXpressPlus profile
127
+ - Updated image extraction logic to use profile-defined UV/IR types instead of hardcoded strings
128
+ - Updated TypeScript definitions (`index.d.ts`) to include UV/IR base64 fields in `scanId` return type
129
+ - Removed UV/IR fields from local scanner functions (`localScanId`, `localContinueScanId`) as local scanners do not support UV/IR imaging
130
+
131
+ ### Fixed
132
+ - Eliminated redundant variable declarations in `ScannerApi.js` by declaring UV/IR variables at function scope
133
+
134
+ ## [1.4.5-beta.7] - 2026-04-15
135
+
136
+ ### Changed
137
+ - Internal refactoring and code review fixes
138
+
139
+ ## [1.4.5-beta.6] - 2026-04-15
140
+
141
+ ### Added
142
+ - UV/IR image support for IDXpressPlus scanner profile
143
+ - Added `licenseFrontUvBase64`, `licenseBackUvBase64`, `licenseFrontIrBase64`, and `licenseBackIrBase64` fields to scan results
144
+ - Scanner now captures and returns ultraviolet (UV) and infrared (IR) images alongside standard color images
145
+ - Image types supported: fclr (front color), rclr (rear color), fclruv (front UV), rclruv (rear UV), fgsir (front IR), rgsir (rear IR)
146
+
147
+ ### Fixed
148
+ - Image extraction logic now captures all image types from scanner response instead of only front/back color images
149
+
150
+ ### Changed
151
+ - Updated IDXpressPlus scanner profile to include UV/IR parameters in default settings and restore configuration
152
+ - Added `clrUvIrResolution`, `fclrUvEnabled`, `rclrUvEnabled`, `fgsIrEnabled`, and `rgsIrEnabled` to `restoreParamKeys`
153
+ - Updated `ScannerApi.js` to extract UV/IR images from GetDocument response
154
+ - Updated `CloudScan.js` `scanId` function to return UV/IR image data (`licenseFrontUvBase64`, `licenseBackUvBase64`, `licenseFrontIrBase64`, `licenseBackIrBase64`)
155
+ - Local scanner functions (`localScanId`, `localContinueScanId`) do not include UV/IR fields as local scanners (VerantId6S) do not support UV/IR imaging
156
+
157
+ ## [1.4.5-beta.5] - 2026-04-14
158
+
159
+ ### Changed
160
+ - Applied dynamic environment URLs to face comparison feature
161
+
162
+ ## [1.4.5-beta.4] - 2026-04-14
163
+
164
+ ### Added
165
+ - Error validation for invalid environment type
166
+
167
+ ### Changed
168
+ - Updated default server URLs to production endpoints
169
+
170
+ ## [1.4.5-beta.3] - 2026-04-13
171
+
172
+ ### Fixed
173
+ - Various bug fixes and code quality improvements
174
+
175
+ ## [1.4.5-beta.2] - 2026-04-12
176
+
177
+ ### Added
178
+ - Dynamic environment detection for server URLs
179
+ - Support for multiple deployment environments
180
+
181
+ ### Changed
182
+ - Server address handling now supports trailing slashes
183
+ - Code improvements based on Copilot suggestions
184
+
185
+ ## [1.4.5-beta.1] - 2026-04-11
186
+
187
+ ### Added
188
+ - IDXpress scanner profiles support
189
+ - New scanner profile configuration options
190
+
191
+ ---
192
+
193
+ ## Version Number Guide
194
+
195
+ - **Major** (1.x.x): Breaking changes
196
+ - **Minor** (x.4.x): New features, backwards compatible
197
+ - **Patch** (x.x.5): Bug fixes, backwards compatible
198
+ - **Beta** (x.x.x-beta.x): Pre-release versions for testing