@hexar/biometric-identity-sdk-core 1.0.11 → 1.0.13
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.
|
@@ -183,12 +183,25 @@ class BiometricIdentitySDK {
|
|
|
183
183
|
*/
|
|
184
184
|
async storeVideoRecording(videoResult) {
|
|
185
185
|
try {
|
|
186
|
+
logger_1.logger.info('Storing video recording', {
|
|
187
|
+
framesCount: videoResult.frames?.length || 0,
|
|
188
|
+
duration: videoResult.duration,
|
|
189
|
+
hasSessionId: !!videoResult.sessionId
|
|
190
|
+
});
|
|
191
|
+
if (!videoResult.frames || videoResult.frames.length === 0) {
|
|
192
|
+
throw new Error('Video recording has no frames');
|
|
193
|
+
}
|
|
186
194
|
this.updateState({ isLoading: true, currentStep: types_1.SDKStep.RECORD_LIVENESS });
|
|
187
195
|
this.updateState({
|
|
188
196
|
videoData: videoResult,
|
|
189
197
|
progress: 75,
|
|
190
198
|
isLoading: false,
|
|
191
199
|
});
|
|
200
|
+
const stateAfterStore = this.getState();
|
|
201
|
+
logger_1.logger.info('Video recording stored', {
|
|
202
|
+
storedFramesCount: stateAfterStore.videoData?.frames?.length || 0,
|
|
203
|
+
hasVideoData: !!stateAfterStore.videoData
|
|
204
|
+
});
|
|
192
205
|
}
|
|
193
206
|
catch (error) {
|
|
194
207
|
this.handleError(error);
|
|
@@ -258,22 +271,50 @@ class BiometricIdentitySDK {
|
|
|
258
271
|
*/
|
|
259
272
|
async validateWithBackend() {
|
|
260
273
|
if (!this.state.frontID || !this.state.videoData) {
|
|
274
|
+
logger_1.logger.error('Validation failed: missing required data', {
|
|
275
|
+
hasFrontID: !!this.state.frontID,
|
|
276
|
+
hasVideoData: !!this.state.videoData
|
|
277
|
+
});
|
|
261
278
|
throw new Error('Required data not available');
|
|
262
279
|
}
|
|
280
|
+
logger_1.logger.info('Starting backend validation', {
|
|
281
|
+
videoFramesCount: this.state.videoData.frames?.length || 0,
|
|
282
|
+
videoDuration: this.state.videoData.duration,
|
|
283
|
+
hasBackID: !!this.state.backID
|
|
284
|
+
});
|
|
263
285
|
this.updateState({ progress: 80 });
|
|
264
286
|
try {
|
|
265
|
-
// Limit and sample video frames to prevent payload size issues
|
|
266
|
-
// Take max 30 frames, sampling evenly across the video
|
|
267
287
|
let videoFrames = this.state.videoData.frames;
|
|
268
|
-
if (videoFrames.length === 0) {
|
|
288
|
+
if (!videoFrames || videoFrames.length === 0) {
|
|
289
|
+
logger_1.logger.error('No video frames in state', {
|
|
290
|
+
videoDataExists: !!this.state.videoData,
|
|
291
|
+
framesProperty: typeof this.state.videoData.frames,
|
|
292
|
+
framesLength: this.state.videoData.frames?.length
|
|
293
|
+
});
|
|
269
294
|
throw new Error('No video frames available for validation');
|
|
270
295
|
}
|
|
271
|
-
|
|
296
|
+
if (videoFrames.length < 10) {
|
|
297
|
+
logger_1.logger.warn('Insufficient frames for validation', {
|
|
298
|
+
frameCount: videoFrames.length,
|
|
299
|
+
minimumRequired: 10
|
|
300
|
+
});
|
|
301
|
+
}
|
|
272
302
|
const MAX_FRAMES = 30;
|
|
273
303
|
if (videoFrames.length > MAX_FRAMES) {
|
|
274
304
|
const step = Math.floor(videoFrames.length / MAX_FRAMES);
|
|
275
305
|
videoFrames = videoFrames.filter((_, index) => index % step === 0).slice(0, MAX_FRAMES);
|
|
306
|
+
logger_1.logger.info('Sampled frames for validation', {
|
|
307
|
+
originalCount: this.state.videoData.frames.length,
|
|
308
|
+
sampledCount: videoFrames.length
|
|
309
|
+
});
|
|
276
310
|
}
|
|
311
|
+
logger_1.logger.info('Sending validation request to backend', {
|
|
312
|
+
framesCount: videoFrames.length,
|
|
313
|
+
duration: this.state.videoData.duration,
|
|
314
|
+
challengesCount: this.state.videoData.challengesCompleted?.length || 0,
|
|
315
|
+
frontImageSize: this.state.frontID.data?.length || 0,
|
|
316
|
+
backImageSize: this.state.backID?.data?.length || 0
|
|
317
|
+
});
|
|
277
318
|
const response = await this.backendClient.fullValidation({
|
|
278
319
|
frontIdImage: this.state.frontID.data,
|
|
279
320
|
backIdImage: this.state.backID?.data,
|
|
@@ -282,11 +323,15 @@ class BiometricIdentitySDK {
|
|
|
282
323
|
challengesCompleted: this.state.videoData.challengesCompleted || [],
|
|
283
324
|
});
|
|
284
325
|
this.updateState({ progress: 95 });
|
|
285
|
-
// Convert backend response to SDK format
|
|
286
326
|
return this.backendClient.convertToValidationResult(response);
|
|
287
327
|
}
|
|
288
328
|
catch (error) {
|
|
289
|
-
|
|
329
|
+
logger_1.logger.error('Backend validation failed', {
|
|
330
|
+
errorMessage: error?.message,
|
|
331
|
+
errorName: error?.name,
|
|
332
|
+
errorStack: error?.stack
|
|
333
|
+
});
|
|
334
|
+
throw this.createError(types_1.BiometricErrorCode.NETWORK_ERROR, error?.message || 'Backend validation failed', error);
|
|
290
335
|
}
|
|
291
336
|
}
|
|
292
337
|
/**
|
|
@@ -88,10 +88,10 @@ class BackendClient {
|
|
|
88
88
|
*/
|
|
89
89
|
async fullValidation(params) {
|
|
90
90
|
if (!this.currentSessionId) {
|
|
91
|
-
|
|
91
|
+
logger_1.logger.info('No session ID, generating challenge');
|
|
92
92
|
await this.generateChallenge();
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
const requestBody = {
|
|
95
95
|
front_id_image: params.frontIdImage,
|
|
96
96
|
back_id_image: params.backIdImage,
|
|
97
97
|
video_frames: params.videoFrames,
|
|
@@ -101,7 +101,20 @@ class BackendClient {
|
|
|
101
101
|
document_type: params.documentType,
|
|
102
102
|
country_code: params.countryCode,
|
|
103
103
|
device_info: params.deviceInfo,
|
|
104
|
+
};
|
|
105
|
+
logger_1.logger.info('Full validation request', {
|
|
106
|
+
hasSessionId: !!this.currentSessionId,
|
|
107
|
+
sessionId: this.currentSessionId,
|
|
108
|
+
videoFramesCount: params.videoFrames.length,
|
|
109
|
+
hasFrontImage: !!params.frontIdImage,
|
|
110
|
+
hasBackImage: !!params.backIdImage,
|
|
111
|
+
frontImageLength: params.frontIdImage?.length || 0,
|
|
112
|
+
backImageLength: params.backIdImage?.length || 0,
|
|
113
|
+
averageFrameLength: params.videoFrames.length > 0
|
|
114
|
+
? Math.round(params.videoFrames.reduce((sum, f) => sum + f.length, 0) / params.videoFrames.length)
|
|
115
|
+
: 0
|
|
104
116
|
});
|
|
117
|
+
return this.request('/api/v1/validate', 'POST', requestBody);
|
|
105
118
|
}
|
|
106
119
|
/**
|
|
107
120
|
* Convert backend response to SDK ValidationResult format
|
|
@@ -189,23 +202,45 @@ class BackendClient {
|
|
|
189
202
|
});
|
|
190
203
|
clearTimeout(timeoutId);
|
|
191
204
|
if (!response.ok) {
|
|
192
|
-
|
|
205
|
+
let errorData = {};
|
|
206
|
+
try {
|
|
207
|
+
const text = await response.text();
|
|
208
|
+
if (text) {
|
|
209
|
+
errorData = JSON.parse(text);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
errorData = {};
|
|
214
|
+
}
|
|
215
|
+
const errorMessage = errorData?.error?.message ||
|
|
216
|
+
errorData?.detail ||
|
|
217
|
+
errorData?.message ||
|
|
218
|
+
`Request failed with status ${response.status}`;
|
|
219
|
+
logger_1.logger.error('Backend request failed', {
|
|
220
|
+
url,
|
|
221
|
+
method,
|
|
222
|
+
status: response.status,
|
|
223
|
+
statusText: response.statusText,
|
|
224
|
+
errorData
|
|
225
|
+
});
|
|
193
226
|
if (response.status === 413) {
|
|
194
227
|
throw new Error(`Payload too large (413). The request body exceeds the server's size limit. Try reducing the number of video frames or image sizes.`);
|
|
195
228
|
}
|
|
196
|
-
|
|
197
|
-
throw new Error(errorData?.error?.message ||
|
|
198
|
-
errorData?.detail ||
|
|
199
|
-
`Request failed with status ${response.status}`);
|
|
229
|
+
throw new Error(errorMessage);
|
|
200
230
|
}
|
|
201
231
|
return await response.json();
|
|
202
232
|
}
|
|
203
233
|
catch (error) {
|
|
204
234
|
clearTimeout(timeoutId);
|
|
205
235
|
if (error.name === 'AbortError') {
|
|
236
|
+
logger_1.logger.error('Request timeout', { url, method, timeout: this.config.timeout });
|
|
206
237
|
throw new Error('Request timeout');
|
|
207
238
|
}
|
|
208
|
-
|
|
239
|
+
if (error.message) {
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
logger_1.logger.error('Unexpected request error', { url, method, error });
|
|
243
|
+
throw new Error('Network request failed');
|
|
209
244
|
}
|
|
210
245
|
}
|
|
211
246
|
}
|
|
@@ -54,7 +54,7 @@ exports.esAR = {
|
|
|
54
54
|
liveness: {
|
|
55
55
|
title: 'Verificación Facial',
|
|
56
56
|
preparing: 'Preparando verificación...',
|
|
57
|
-
getReady: '¡
|
|
57
|
+
getReady: '¡Preparate!',
|
|
58
58
|
countdownMessage: 'Vas a realizar acciones.\nSeguí las instrucciones en pantalla.',
|
|
59
59
|
recordingInstructions: 'Mantené tu rostro visible y seguí las instrucciones',
|
|
60
60
|
instructions: {
|
package/package.json
CHANGED
|
@@ -236,6 +236,16 @@ export class BiometricIdentitySDK {
|
|
|
236
236
|
*/
|
|
237
237
|
async storeVideoRecording(videoResult: VideoResult): Promise<void> {
|
|
238
238
|
try {
|
|
239
|
+
logger.info('Storing video recording', {
|
|
240
|
+
framesCount: videoResult.frames?.length || 0,
|
|
241
|
+
duration: videoResult.duration,
|
|
242
|
+
hasSessionId: !!videoResult.sessionId
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!videoResult.frames || videoResult.frames.length === 0) {
|
|
246
|
+
throw new Error('Video recording has no frames');
|
|
247
|
+
}
|
|
248
|
+
|
|
239
249
|
this.updateState({ isLoading: true, currentStep: SDKStep.RECORD_LIVENESS });
|
|
240
250
|
|
|
241
251
|
this.updateState({
|
|
@@ -244,6 +254,12 @@ export class BiometricIdentitySDK {
|
|
|
244
254
|
isLoading: false,
|
|
245
255
|
});
|
|
246
256
|
|
|
257
|
+
const stateAfterStore = this.getState();
|
|
258
|
+
logger.info('Video recording stored', {
|
|
259
|
+
storedFramesCount: stateAfterStore.videoData?.frames?.length || 0,
|
|
260
|
+
hasVideoData: !!stateAfterStore.videoData
|
|
261
|
+
});
|
|
262
|
+
|
|
247
263
|
} catch (error) {
|
|
248
264
|
this.handleError(error);
|
|
249
265
|
throw error;
|
|
@@ -331,26 +347,58 @@ export class BiometricIdentitySDK {
|
|
|
331
347
|
*/
|
|
332
348
|
private async validateWithBackend(): Promise<ValidationResult> {
|
|
333
349
|
if (!this.state.frontID || !this.state.videoData) {
|
|
350
|
+
logger.error('Validation failed: missing required data', {
|
|
351
|
+
hasFrontID: !!this.state.frontID,
|
|
352
|
+
hasVideoData: !!this.state.videoData
|
|
353
|
+
});
|
|
334
354
|
throw new Error('Required data not available');
|
|
335
355
|
}
|
|
336
356
|
|
|
357
|
+
logger.info('Starting backend validation', {
|
|
358
|
+
videoFramesCount: this.state.videoData.frames?.length || 0,
|
|
359
|
+
videoDuration: this.state.videoData.duration,
|
|
360
|
+
hasBackID: !!this.state.backID
|
|
361
|
+
});
|
|
362
|
+
|
|
337
363
|
this.updateState({ progress: 80 });
|
|
338
364
|
|
|
339
365
|
try {
|
|
340
|
-
// Limit and sample video frames to prevent payload size issues
|
|
341
|
-
// Take max 30 frames, sampling evenly across the video
|
|
342
366
|
let videoFrames = this.state.videoData.frames;
|
|
343
|
-
|
|
367
|
+
|
|
368
|
+
if (!videoFrames || videoFrames.length === 0) {
|
|
369
|
+
logger.error('No video frames in state', {
|
|
370
|
+
videoDataExists: !!this.state.videoData,
|
|
371
|
+
framesProperty: typeof this.state.videoData.frames,
|
|
372
|
+
framesLength: this.state.videoData.frames?.length
|
|
373
|
+
});
|
|
344
374
|
throw new Error('No video frames available for validation');
|
|
345
375
|
}
|
|
376
|
+
|
|
377
|
+
if (videoFrames.length < 10) {
|
|
378
|
+
logger.warn('Insufficient frames for validation', {
|
|
379
|
+
frameCount: videoFrames.length,
|
|
380
|
+
minimumRequired: 10
|
|
381
|
+
});
|
|
382
|
+
}
|
|
346
383
|
|
|
347
|
-
// Sample frames if we have too many (max 30 frames to keep payload reasonable)
|
|
348
384
|
const MAX_FRAMES = 30;
|
|
349
385
|
if (videoFrames.length > MAX_FRAMES) {
|
|
350
386
|
const step = Math.floor(videoFrames.length / MAX_FRAMES);
|
|
351
387
|
videoFrames = videoFrames.filter((_, index) => index % step === 0).slice(0, MAX_FRAMES);
|
|
388
|
+
logger.info('Sampled frames for validation', {
|
|
389
|
+
originalCount: this.state.videoData.frames.length,
|
|
390
|
+
sampledCount: videoFrames.length
|
|
391
|
+
});
|
|
352
392
|
}
|
|
353
393
|
|
|
394
|
+
logger.info('Sending validation request to backend', {
|
|
395
|
+
framesCount: videoFrames.length,
|
|
396
|
+
duration: this.state.videoData.duration,
|
|
397
|
+
challengesCount: this.state.videoData.challengesCompleted?.length || 0,
|
|
398
|
+
frontImageSize: this.state.frontID.data?.length || 0,
|
|
399
|
+
backImageSize: this.state.backID?.data?.length || 0
|
|
400
|
+
});
|
|
401
|
+
|
|
354
402
|
const response = await this.backendClient.fullValidation({
|
|
355
403
|
frontIdImage: this.state.frontID.data,
|
|
356
404
|
backIdImage: this.state.backID?.data,
|
|
@@ -361,12 +409,16 @@ export class BiometricIdentitySDK {
|
|
|
361
409
|
|
|
362
410
|
this.updateState({ progress: 95 });
|
|
363
411
|
|
|
364
|
-
// Convert backend response to SDK format
|
|
365
412
|
return this.backendClient.convertToValidationResult(response);
|
|
366
|
-
} catch (error) {
|
|
413
|
+
} catch (error: any) {
|
|
414
|
+
logger.error('Backend validation failed', {
|
|
415
|
+
errorMessage: error?.message,
|
|
416
|
+
errorName: error?.name,
|
|
417
|
+
errorStack: error?.stack
|
|
418
|
+
});
|
|
367
419
|
throw this.createError(
|
|
368
420
|
BiometricErrorCode.NETWORK_ERROR,
|
|
369
|
-
'Backend validation failed',
|
|
421
|
+
error?.message || 'Backend validation failed',
|
|
370
422
|
error
|
|
371
423
|
);
|
|
372
424
|
}
|
package/src/api/BackendClient.ts
CHANGED
|
@@ -261,24 +261,39 @@ export class BackendClient {
|
|
|
261
261
|
deviceInfo?: Record<string, any>;
|
|
262
262
|
}): Promise<FullValidationResponse> {
|
|
263
263
|
if (!this.currentSessionId) {
|
|
264
|
-
|
|
264
|
+
logger.info('No session ID, generating challenge');
|
|
265
265
|
await this.generateChallenge();
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
const requestBody = {
|
|
269
|
+
front_id_image: params.frontIdImage,
|
|
270
|
+
back_id_image: params.backIdImage,
|
|
271
|
+
video_frames: params.videoFrames,
|
|
272
|
+
video_duration_ms: params.videoDurationMs,
|
|
273
|
+
session_id: this.currentSessionId,
|
|
274
|
+
challenges_completed: params.challengesCompleted || [],
|
|
275
|
+
document_type: params.documentType,
|
|
276
|
+
country_code: params.countryCode,
|
|
277
|
+
device_info: params.deviceInfo,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
logger.info('Full validation request', {
|
|
281
|
+
hasSessionId: !!this.currentSessionId,
|
|
282
|
+
sessionId: this.currentSessionId,
|
|
283
|
+
videoFramesCount: params.videoFrames.length,
|
|
284
|
+
hasFrontImage: !!params.frontIdImage,
|
|
285
|
+
hasBackImage: !!params.backIdImage,
|
|
286
|
+
frontImageLength: params.frontIdImage?.length || 0,
|
|
287
|
+
backImageLength: params.backIdImage?.length || 0,
|
|
288
|
+
averageFrameLength: params.videoFrames.length > 0
|
|
289
|
+
? Math.round(params.videoFrames.reduce((sum, f) => sum + f.length, 0) / params.videoFrames.length)
|
|
290
|
+
: 0
|
|
291
|
+
});
|
|
292
|
+
|
|
268
293
|
return this.request<FullValidationResponse>(
|
|
269
294
|
'/api/v1/validate',
|
|
270
295
|
'POST',
|
|
271
|
-
|
|
272
|
-
front_id_image: params.frontIdImage,
|
|
273
|
-
back_id_image: params.backIdImage,
|
|
274
|
-
video_frames: params.videoFrames,
|
|
275
|
-
video_duration_ms: params.videoDurationMs,
|
|
276
|
-
session_id: this.currentSessionId,
|
|
277
|
-
challenges_completed: params.challengesCompleted || [],
|
|
278
|
-
document_type: params.documentType,
|
|
279
|
-
country_code: params.countryCode,
|
|
280
|
-
device_info: params.deviceInfo,
|
|
281
|
-
}
|
|
296
|
+
requestBody
|
|
282
297
|
);
|
|
283
298
|
}
|
|
284
299
|
|
|
@@ -382,19 +397,36 @@ export class BackendClient {
|
|
|
382
397
|
clearTimeout(timeoutId);
|
|
383
398
|
|
|
384
399
|
if (!response.ok) {
|
|
385
|
-
|
|
400
|
+
let errorData: Record<string, any> = {};
|
|
401
|
+
try {
|
|
402
|
+
const text = await response.text();
|
|
403
|
+
if (text) {
|
|
404
|
+
errorData = JSON.parse(text);
|
|
405
|
+
}
|
|
406
|
+
} catch {
|
|
407
|
+
errorData = {};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const errorMessage = errorData?.error?.message ||
|
|
411
|
+
errorData?.detail ||
|
|
412
|
+
errorData?.message ||
|
|
413
|
+
`Request failed with status ${response.status}`;
|
|
414
|
+
|
|
415
|
+
logger.error('Backend request failed', {
|
|
416
|
+
url,
|
|
417
|
+
method,
|
|
418
|
+
status: response.status,
|
|
419
|
+
statusText: response.statusText,
|
|
420
|
+
errorData
|
|
421
|
+
});
|
|
422
|
+
|
|
386
423
|
if (response.status === 413) {
|
|
387
424
|
throw new Error(
|
|
388
425
|
`Payload too large (413). The request body exceeds the server's size limit. Try reducing the number of video frames or image sizes.`
|
|
389
426
|
);
|
|
390
427
|
}
|
|
391
428
|
|
|
392
|
-
|
|
393
|
-
throw new Error(
|
|
394
|
-
errorData?.error?.message ||
|
|
395
|
-
errorData?.detail ||
|
|
396
|
-
`Request failed with status ${response.status}`
|
|
397
|
-
);
|
|
429
|
+
throw new Error(errorMessage);
|
|
398
430
|
}
|
|
399
431
|
|
|
400
432
|
return await response.json() as T;
|
|
@@ -402,10 +434,16 @@ export class BackendClient {
|
|
|
402
434
|
clearTimeout(timeoutId);
|
|
403
435
|
|
|
404
436
|
if (error.name === 'AbortError') {
|
|
437
|
+
logger.error('Request timeout', { url, method, timeout: this.config.timeout });
|
|
405
438
|
throw new Error('Request timeout');
|
|
406
439
|
}
|
|
407
440
|
|
|
408
|
-
|
|
441
|
+
if (error.message) {
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
logger.error('Unexpected request error', { url, method, error });
|
|
446
|
+
throw new Error('Network request failed');
|
|
409
447
|
}
|
|
410
448
|
}
|
|
411
449
|
}
|
|
@@ -57,7 +57,7 @@ export const esAR: LanguageStrings = {
|
|
|
57
57
|
liveness: {
|
|
58
58
|
title: 'Verificación Facial',
|
|
59
59
|
preparing: 'Preparando verificación...',
|
|
60
|
-
getReady: '¡
|
|
60
|
+
getReady: '¡Preparate!',
|
|
61
61
|
countdownMessage: 'Vas a realizar acciones.\nSeguí las instrucciones en pantalla.',
|
|
62
62
|
recordingInstructions: 'Mantené tu rostro visible y seguí las instrucciones',
|
|
63
63
|
instructions: {
|