verant_id_cloud_scan 1.4.5-beta.8 → 1.4.5-beta.9

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
@@ -94,6 +94,17 @@ export function getFaceComparisonServerAddress() {
94
94
  : "https://faceid.verantid.com/compare_id_and_face";
95
95
  }
96
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
+ }
107
+
97
108
  export const dmvCheck = async (clientId, scannerType, licenseData) => {
98
109
  const url = getDmvServerAddress();
99
110
 
@@ -440,3 +451,58 @@ export const checkScannerPresent = async (scannerAddress) => {
440
451
  return false;
441
452
  }
442
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
+ * @returns {Promise<Object>} Session data with session_id, session_url, and websocket_url
464
+ */
465
+ export const createVerificationSession = async (userId, clientId) => {
466
+ const url = getMobileVerificationServerAddress() + "/sessions";
467
+ const data = { user_id: userId, client_id: clientId };
468
+
469
+ try {
470
+ const response = await fetch(url, {
471
+ method: "POST",
472
+ headers: {
473
+ "Content-Type": "application/json",
474
+ },
475
+ body: JSON.stringify(data),
476
+ });
477
+
478
+ if (response.ok) {
479
+ const responseData = await response.json();
480
+ return responseData;
481
+ } else {
482
+ // Try to parse error response
483
+ try {
484
+ const errorData = await response.json();
485
+ console.error("Verification session creation failed:", errorData);
486
+ return {
487
+ status: 'error',
488
+ message: errorData.message || 'Failed to create verification session',
489
+ error: errorData
490
+ };
491
+ } catch (parseError) {
492
+ const errorText = await response.text();
493
+ console.error("Verification session creation failed:", errorText);
494
+ return {
495
+ status: 'error',
496
+ message: 'Failed to create verification session',
497
+ raw_error: errorText
498
+ };
499
+ }
500
+ }
501
+ } catch (error) {
502
+ console.error("There was a problem with the verification session request:", error);
503
+ return {
504
+ status: 'error',
505
+ message: error.message || 'Unable to connect to verification service.'
506
+ };
507
+ }
508
+ };
package/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.5-beta.9] - 2026-04-21
9
+
10
+ ### Added
11
+ - Mobile ID verification support (VNT-1409, VNT-1419, VNT-1429, VNT-1455)
12
+ - Added `startMobileVerification()` function to initiate mobile verification sessions
13
+ - Added `createVerificationSession()` API function to create verification sessions with user ID and client ID
14
+ - Added WebSocket client (`WebSocketClient.js`) for real-time verification updates from AWS API Gateway
15
+ - Added mobile verification server address configuration with environment detection
16
+ - QR code generation for mobile verification sessions with customizable base URLs
17
+ - Real-time verification callbacks: `onVerificationStarted`, `onVerificationResult`, `onStatusChange`, `onError`
18
+ - Session management with session ID, session URL, WebSocket URL, and QR code SVG
19
+ - Connection state management (CONNECTING, OPEN, CLOSED, ERROR) for WebSocket connections
20
+
21
+ ### Changed
22
+ - Updated environment detection to support mobile verification endpoints
23
+ - Mobile verification uses `staging.mdl.verantid.bluerocket.us` for staging
24
+ - Mobile verification uses `mdl.verantid.bluerocket.us` for production
25
+ - Updated scanner type detection to use `scannerProfile` from scanner response in auto-DMV verification
26
+ - Enhanced TypeScript definitions (`index.d.ts`) to include mobile verification types and callbacks
27
+
8
28
  ## [1.4.5-beta.8] - 2026-04-15
9
29
 
10
30
  ### Changed
package/CloudScan.js CHANGED
@@ -15,8 +15,14 @@ import {
15
15
  scannerPresent,
16
16
  } from "./ScannerApi.js";
17
17
  import { VerificationModal } from "./VerificationModal.js";
18
- import { checkAutoDmvEnabled, setEnvironment } from "./Api.js";
18
+ import { checkAutoDmvEnabled, setEnvironment, createVerificationSession } from "./Api.js";
19
19
  import { formatSexCodeForDisplay, formatHeightForDisplay } from "./FormatUtils.js";
20
+ import { WebSocketClient, ConnectionState } from "./WebSocketClient.js";
21
+
22
+ // Re-export WebSocket primitives so host apps can import them from the
23
+ // library entry point (e.g. for typing connection state in their own UI).
24
+ export { WebSocketClient, ConnectionState };
25
+ import QRCode from "qrcode";
20
26
 
21
27
  let cachedLicenseFrontBase64 = "";
22
28
 
@@ -496,3 +502,148 @@ export const localContinueScanId = async (scannerAddress, includeData, clientId
496
502
  }
497
503
  return returnObj;
498
504
  };
505
+
506
+ /**
507
+ * Connect to a verification session's WebSocket
508
+ *
509
+ * Low-level primitive: opens a WebSocket connection to the given API Gateway
510
+ * URL and wires up the event callbacks. Use this when the caller already has
511
+ * a session (URL obtained out-of-band). For the full flow that also creates
512
+ * the session and generates a QR code, use `startMobileVerification`.
513
+ *
514
+ * AWS message frames are parsed and dispatched as high-level events:
515
+ * - verification_started → callbacks.onVerificationStarted(payload)
516
+ * - verification_result → callbacks.onResult(payload)
517
+ * Connection lifecycle is surfaced via callbacks.onStatusChange and
518
+ * callbacks.onError with clean `{ message, code, error }` shapes.
519
+ *
520
+ * @param {Object} options
521
+ * @param {string} options.websocketUrl - API Gateway WebSocket URL (must include session_id)
522
+ * @param {Object} [options.callbacks] - Event callbacks
523
+ * @param {Function} [options.callbacks.onVerificationStarted]
524
+ * @param {Function} [options.callbacks.onResult]
525
+ * @param {Function} [options.callbacks.onStatusChange]
526
+ * @param {Function} [options.callbacks.onError]
527
+ * @returns {Promise<Object>} Handle with live `status` getter and `close()` method
528
+ */
529
+ export const connectToVerificationSession = async ({ websocketUrl, callbacks = {} } = {}) => {
530
+ if (!websocketUrl) {
531
+ throw new Error('connectToVerificationSession: websocketUrl is required');
532
+ }
533
+
534
+ const wsClient = new WebSocketClient(callbacks);
535
+
536
+ // WebSocketClient.connect() already invokes callbacks.onError on failure;
537
+ // we just let the error propagate so the caller can react too.
538
+ await wsClient.connect(websocketUrl);
539
+
540
+ return {
541
+ get status() { return wsClient.getState(); },
542
+ close: () => wsClient.disconnect()
543
+ };
544
+ };
545
+
546
+ /**
547
+ * Start mobile verification session
548
+ *
549
+ * Creates a verification session, generates a QR code, and establishes WebSocket connection
550
+ * for real-time verification updates.
551
+ *
552
+ * @param {string} userId - User ID for the verification session
553
+ * @param {string} clientId - Client ID for licensing and tracking
554
+ * @param {Object} callbacks - Event callbacks for verification lifecycle
555
+ * @param {Function} callbacks.onVerificationStarted - Called when customer starts verification
556
+ * @param {Function} callbacks.onResult - Called when verification completes with verified fields
557
+ * @param {Function} callbacks.onStatusChange - Called when WebSocket connection state changes
558
+ * @param {Function} callbacks.onError - Called on errors
559
+ * @returns {Promise<Object>} Session handle with QR code and control methods
560
+ */
561
+ export const startMobileVerification = async (userId, clientId, callbacks = {}, mobileVerifyBaseUrl) => {
562
+ try {
563
+ const sessionData = await createVerificationSession(userId, clientId);
564
+
565
+ if (sessionData.status === 'error') {
566
+ throw new Error(sessionData.message || 'Failed to create verification session');
567
+ }
568
+
569
+ const { session_id, session_url, websocket_url } = sessionData;
570
+
571
+ if (!session_id || !session_url || !websocket_url) {
572
+ throw new Error('Invalid session response: missing required fields');
573
+ }
574
+
575
+ // Generate QR code SVG
576
+ // If mobileVerifyBaseUrl is provided, construct the mobile verify URL by
577
+ // appending the session_id as a path segment (matches host-app routes like
578
+ // route("mobile-verify/:sessionId", ...)
579
+ // in VerantID-Web). Falls back to the backend session_url if no base is
580
+ // provided, but note that the backend URL typically requires auth headers
581
+ // that a phone browser can't send from a QR-code open, so host apps should
582
+ // always pass mobileVerifyBaseUrl in production.
583
+ let qrCodeUrl = session_url;
584
+ if (mobileVerifyBaseUrl) {
585
+ // Remove trailing slash if present so we don't end up with "//{session_id}"
586
+ const baseUrl = mobileVerifyBaseUrl.endsWith('/') ? mobileVerifyBaseUrl.slice(0, -1) : mobileVerifyBaseUrl;
587
+ qrCodeUrl = `${baseUrl}/${session_id}`;
588
+ console.log('[startMobileVerification] Using custom mobile verify URL:', qrCodeUrl);
589
+ } else {
590
+ console.log('[startMobileVerification] Using backend session URL for QR code:', qrCodeUrl);
591
+ }
592
+
593
+ let qrCodeSvg = '';
594
+ try {
595
+ qrCodeSvg = await QRCode.toString(qrCodeUrl, {
596
+ type: 'svg',
597
+ width: 256,
598
+ margin: 2,
599
+ errorCorrectionLevel: 'M'
600
+ });
601
+ console.log('[startMobileVerification] QR code generated');
602
+ } catch (qrError) {
603
+ console.error('[startMobileVerification] QR code generation failed:', qrError);
604
+ throw new Error('Failed to generate QR code: ' + qrError.message);
605
+ }
606
+
607
+ // Step 3: Connect to WebSocket via the shared primitive
608
+ let wsSession;
609
+ try {
610
+ wsSession = await connectToVerificationSession({
611
+ websocketUrl: websocket_url,
612
+ callbacks
613
+ });
614
+ console.log('[startMobileVerification] WebSocket connected');
615
+ } catch (wsError) {
616
+ console.error('[startMobileVerification] WebSocket connection failed:', wsError);
617
+ throw new Error('Failed to establish WebSocket connection: ' + wsError.message);
618
+ }
619
+
620
+ // Step 4: Return session handle — `status` is a live getter that
621
+ // delegates to the underlying WebSocket client, so host apps always
622
+ // observe the current connection state (not a snapshot).
623
+ return {
624
+ sessionId: session_id,
625
+ sessionUrl: session_url, // Backend API URL for session
626
+ qrCodeUrl: qrCodeUrl, // The URL that was encoded in the QR code (mobile verify page or session URL)
627
+ qrCodeSvg: qrCodeSvg,
628
+ get status() { return wsSession.status; },
629
+ close: () => {
630
+ console.log('[startMobileVerification] Closing session:', session_id);
631
+ wsSession.close();
632
+ }
633
+ };
634
+
635
+ } catch (error) {
636
+ console.error('[startMobileVerification] Error:', error);
637
+
638
+ // Notify error callback if provided
639
+ if (typeof callbacks.onError === 'function') {
640
+ callbacks.onError({
641
+ message: error.message || 'Failed to start mobile verification',
642
+ code: 'SESSION_CREATION_FAILED',
643
+ error
644
+ });
645
+ }
646
+
647
+ throw error;
648
+ }
649
+ };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * WebSocketClient.js
3
+ *
4
+ * Manages WebSocket connections to AWS API Gateway for real-time verification updates.
5
+ * Handles connection lifecycle, message parsing, and event callbacks.
6
+ */
7
+
8
+ /**
9
+ * Connection states
10
+ */
11
+ export const ConnectionState = {
12
+ CONNECTING: 'connecting',
13
+ OPEN: 'open',
14
+ CLOSED: 'closed',
15
+ ERROR: 'error'
16
+ };
17
+
18
+ /**
19
+ * WebSocket message route keys from AWS API Gateway
20
+ */
21
+ const RouteKeys = {
22
+ VERIFICATION_STARTED: 'verification_started',
23
+ VERIFICATION_RESULT: 'verification_result'
24
+ };
25
+
26
+ /**
27
+ * WebSocketClient
28
+ *
29
+ * Encapsulates WebSocket connection management and event handling for verification sessions.
30
+ */
31
+ export class WebSocketClient {
32
+ constructor(callbacks = {}) {
33
+ this.ws = null;
34
+ this.state = ConnectionState.CLOSED;
35
+ this.callbacks = {
36
+ onVerificationStarted: callbacks.onVerificationStarted || null,
37
+ onResult: callbacks.onResult || null,
38
+ onStatusChange: callbacks.onStatusChange || null,
39
+ onError: callbacks.onError || null
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Connect to WebSocket API Gateway
45
+ * @param {string} wsUrl - The WebSocket URL (already includes session_id query param)
46
+ * @returns {Promise<void>}
47
+ */
48
+ async connect(wsUrl) {
49
+ if (this.ws) {
50
+ throw new Error('WebSocket connection already exists');
51
+ }
52
+
53
+ // Resolve WebSocket implementation: native global (browser or modern Node)
54
+ // or the 'ws' package (older Node runtimes). Dynamic import is used because
55
+ // this file is an ES module — top-level `require` is not available.
56
+ let WebSocketImpl;
57
+ if (typeof WebSocket !== 'undefined') {
58
+ WebSocketImpl = WebSocket;
59
+ } else {
60
+ try {
61
+ const wsModule = await import('ws');
62
+ WebSocketImpl = wsModule.default || wsModule.WebSocket;
63
+ } catch (error) {
64
+ this._updateState(ConnectionState.ERROR);
65
+ this._invokeCallback('onError', {
66
+ message: 'No WebSocket implementation available (install "ws" for Node.js)',
67
+ code: 'NO_WEBSOCKET_IMPL',
68
+ error: error.message
69
+ });
70
+ throw error;
71
+ }
72
+ }
73
+
74
+ return new Promise((resolve, reject) => {
75
+ try {
76
+ this._updateState(ConnectionState.CONNECTING);
77
+ this.ws = new WebSocketImpl(wsUrl);
78
+
79
+ this.ws.onopen = () => {
80
+ console.log('[WebSocketClient] Connection established');
81
+ this._updateState(ConnectionState.OPEN);
82
+ resolve();
83
+ };
84
+
85
+ this.ws.onmessage = (event) => {
86
+ this._handleMessage(event.data);
87
+ };
88
+
89
+ this.ws.onerror = (error) => {
90
+ console.error('[WebSocketClient] Connection error:', error);
91
+ this._updateState(ConnectionState.ERROR);
92
+ this._invokeCallback('onError', {
93
+ message: 'WebSocket connection error',
94
+ code: 'CONNECTION_ERROR',
95
+ error
96
+ });
97
+ };
98
+
99
+ this.ws.onclose = (event) => {
100
+ console.log('[WebSocketClient] Connection closed', event.code, event.reason);
101
+ this._updateState(ConnectionState.CLOSED);
102
+ this.ws = null;
103
+ };
104
+
105
+ } catch (error) {
106
+ this._updateState(ConnectionState.ERROR);
107
+ this._invokeCallback('onError', {
108
+ message: 'Failed to create WebSocket connection',
109
+ code: 'CONNECTION_FAILED',
110
+ error: error.message
111
+ });
112
+ reject(error);
113
+ }
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Disconnect and cleanup WebSocket connection
119
+ */
120
+ disconnect() {
121
+ if (this.ws) {
122
+ console.log('[WebSocketClient] Disconnecting...');
123
+ this.ws.close();
124
+ this.ws = null;
125
+ this._updateState(ConnectionState.CLOSED);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Get current connection state
131
+ * @returns {string}
132
+ */
133
+ getState() {
134
+ return this.state;
135
+ }
136
+
137
+ /**
138
+ * Handle incoming WebSocket messages
139
+ * @private
140
+ * @param {string} data - Raw message data
141
+ */
142
+ _handleMessage(data) {
143
+ try {
144
+ console.log('[WebSocketClient] Received message:', data);
145
+
146
+ // Parse the message (AWS API Gateway may send JSON-wrapped messages)
147
+ const message = typeof data === 'string' ? JSON.parse(data) : data;
148
+
149
+ // Extract route key and payload
150
+ // AWS API Gateway format may vary, handle both direct and wrapped formats
151
+ const routeKey = message.routeKey || message.action || message.type;
152
+ const payload = message.data || message.payload || message;
153
+
154
+ // Route to appropriate handler
155
+ switch (routeKey) {
156
+ case RouteKeys.VERIFICATION_STARTED:
157
+ console.log('[WebSocketClient] Verification started');
158
+ this._invokeCallback('onVerificationStarted', payload);
159
+ break;
160
+
161
+ case RouteKeys.VERIFICATION_RESULT:
162
+ console.log('[WebSocketClient] Verification result received');
163
+ this._invokeCallback('onResult', payload);
164
+ break;
165
+
166
+ default:
167
+ console.warn('[WebSocketClient] Unknown message route:', routeKey);
168
+ break;
169
+ }
170
+
171
+ } catch (error) {
172
+ console.error('[WebSocketClient] Error parsing message:', error);
173
+ this._invokeCallback('onError', {
174
+ message: 'Failed to parse WebSocket message',
175
+ code: 'PARSE_ERROR',
176
+ error: error.message
177
+ });
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Update connection state and notify listeners
183
+ * @private
184
+ * @param {string} newState
185
+ */
186
+ _updateState(newState) {
187
+ if (this.state !== newState) {
188
+ this.state = newState;
189
+ console.log('[WebSocketClient] State changed:', newState);
190
+ this._invokeCallback('onStatusChange', newState);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Safely invoke a callback if it exists
196
+ * @private
197
+ * @param {string} callbackName
198
+ * @param {*} data
199
+ */
200
+ _invokeCallback(callbackName, data) {
201
+ if (typeof this.callbacks[callbackName] === 'function') {
202
+ try {
203
+ this.callbacks[callbackName](data);
204
+ } catch (error) {
205
+ console.error(`[WebSocketClient] Error in ${callbackName} callback:`, error);
206
+ }
207
+ }
208
+ }
209
+ }
package/index.d.ts CHANGED
@@ -78,6 +78,71 @@ declare module "verant_id_cloud_scan" {
78
78
  errorMessages: string;
79
79
  dmvVerificationResult?: any;
80
80
  }>;
81
+
82
+ // Mobile Verification Types
83
+ export type ConnectionStateValue = 'connecting' | 'open' | 'closed' | 'error';
84
+
85
+ export const ConnectionState: {
86
+ readonly CONNECTING: 'connecting';
87
+ readonly OPEN: 'open';
88
+ readonly CLOSED: 'closed';
89
+ readonly ERROR: 'error';
90
+ };
91
+
92
+ export interface VerificationCallbacks {
93
+ onVerificationStarted?: (data: any) => void;
94
+ onResult?: (verifiedFields: any) => void;
95
+ onStatusChange?: (status: ConnectionStateValue) => void;
96
+ onError?: (error: { message: string; code?: string; error?: any }) => void;
97
+ }
98
+
99
+ /**
100
+ * Handle returned by `connectToVerificationSession`. `status` is a live
101
+ * getter that reflects the current WebSocket state, not a snapshot.
102
+ */
103
+ export interface ConnectedSession {
104
+ readonly status: ConnectionStateValue;
105
+ close(): void;
106
+ }
107
+
108
+ export interface VerificationSession {
109
+ sessionId: string;
110
+ sessionUrl: string;
111
+ qrCodeUrl: string;
112
+ qrCodeSvg: string;
113
+ readonly status: ConnectionStateValue;
114
+ close: () => void;
115
+ }
116
+
117
+ export interface ConnectToVerificationSessionOptions {
118
+ websocketUrl: string;
119
+ callbacks?: VerificationCallbacks;
120
+ }
121
+
122
+ /**
123
+ * Low-level primitive: open a WebSocket connection to an existing
124
+ * verification session's API Gateway URL.
125
+ *
126
+ * For the full orchestrated flow (create session + QR + WS connect),
127
+ * use `startMobileVerification`.
128
+ */
129
+ export function connectToVerificationSession(
130
+ options: ConnectToVerificationSessionOptions
131
+ ): Promise<ConnectedSession>;
132
+
133
+ export function startMobileVerification(
134
+ userId: string,
135
+ clientId: string,
136
+ callbacks?: VerificationCallbacks,
137
+ mobileVerifyBaseUrl?: string
138
+ ): Promise<VerificationSession>;
139
+
140
+ export class WebSocketClient {
141
+ constructor(callbacks?: VerificationCallbacks);
142
+ connect(wsUrl: string): Promise<void>;
143
+ disconnect(): void;
144
+ getState(): ConnectionStateValue;
145
+ }
81
146
  }
82
147
 
83
148
  export function scannerPresent(scannerAddress: string): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verant_id_cloud_scan",
3
- "version": "1.4.5-beta.8",
3
+ "version": "1.4.5-beta.9",
4
4
  "description": "Verant ID Cloud Scan NPM Library",
5
5
  "main": "CloudScan.js",
6
6
  "types": "index.d.ts",
@@ -9,5 +9,11 @@
9
9
  },
10
10
  "author": "Verant ID Inc",
11
11
  "license": "ISC",
12
- "type": "module"
12
+ "type": "module",
13
+ "dependencies": {
14
+ "qrcode": "^1.5.3"
15
+ },
16
+ "optionalDependencies": {
17
+ "ws": "^8.16.0"
18
+ }
13
19
  }