@hexar/biometric-identity-sdk-core 1.0.15 → 1.0.17
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.
|
@@ -82,13 +82,21 @@ export declare class BiometricIdentitySDK {
|
|
|
82
82
|
/**
|
|
83
83
|
* Compress base64 image - simplified version that works in all environments
|
|
84
84
|
* For React Native, images should be compressed at capture time
|
|
85
|
-
* This
|
|
85
|
+
* This reduces large images by skipping compression if already small enough
|
|
86
86
|
*/
|
|
87
87
|
private compressImage;
|
|
88
88
|
/**
|
|
89
89
|
* Compress image in browser environment using Canvas API
|
|
90
90
|
*/
|
|
91
91
|
private compressImageBrowser;
|
|
92
|
+
/**
|
|
93
|
+
* Fix base64 padding issues
|
|
94
|
+
*/
|
|
95
|
+
private fixBase64Padding;
|
|
96
|
+
/**
|
|
97
|
+
* Validate if string is valid base64
|
|
98
|
+
*/
|
|
99
|
+
private isValidBase64;
|
|
92
100
|
/**
|
|
93
101
|
* Get current SDK state
|
|
94
102
|
*/
|
|
@@ -299,7 +299,7 @@ class BiometricIdentitySDK {
|
|
|
299
299
|
minimumRequired: 10
|
|
300
300
|
});
|
|
301
301
|
}
|
|
302
|
-
const MAX_FRAMES =
|
|
302
|
+
const MAX_FRAMES = 15;
|
|
303
303
|
if (videoFrames.length > MAX_FRAMES) {
|
|
304
304
|
const step = Math.floor(videoFrames.length / MAX_FRAMES);
|
|
305
305
|
videoFrames = videoFrames.filter((_, index) => index % step === 0).slice(0, MAX_FRAMES);
|
|
@@ -308,13 +308,34 @@ class BiometricIdentitySDK {
|
|
|
308
308
|
sampledCount: videoFrames.length
|
|
309
309
|
});
|
|
310
310
|
}
|
|
311
|
-
const compressedFrontImage = await this.compressImage(this.state.frontID.data);
|
|
311
|
+
const compressedFrontImage = this.fixBase64Padding(await this.compressImage(this.state.frontID.data));
|
|
312
312
|
const compressedBackImage = this.state.backID?.data
|
|
313
|
-
? await this.compressImage(this.state.backID.data)
|
|
313
|
+
? this.fixBase64Padding(await this.compressImage(this.state.backID.data))
|
|
314
314
|
: undefined;
|
|
315
|
-
const
|
|
315
|
+
const validFrames = [];
|
|
316
|
+
for (let i = 0; i < videoFrames.length; i++) {
|
|
317
|
+
const frame = videoFrames[i];
|
|
318
|
+
if (!frame || typeof frame !== 'string') {
|
|
319
|
+
logger_1.logger.warn(`Skipping invalid frame ${i}: not a string`);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (frame.length < 100) {
|
|
323
|
+
logger_1.logger.warn(`Skipping invalid frame ${i}: too short (${frame.length} chars), likely a file path`);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (!this.isValidBase64(frame)) {
|
|
327
|
+
logger_1.logger.warn(`Skipping invalid frame ${i}: not valid base64`);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const compressedFrame = this.fixBase64Padding(await this.compressImage(frame));
|
|
331
|
+
validFrames.push(compressedFrame);
|
|
332
|
+
}
|
|
333
|
+
if (validFrames.length < 10) {
|
|
334
|
+
logger_1.logger.error(`Insufficient valid frames: ${validFrames.length} < 10`);
|
|
335
|
+
throw new Error(`Insufficient valid video frames: ${validFrames.length} frames available, minimum 10 required`);
|
|
336
|
+
}
|
|
316
337
|
logger_1.logger.info('Sending validation request to backend', {
|
|
317
|
-
framesCount:
|
|
338
|
+
framesCount: validFrames.length,
|
|
318
339
|
duration: this.state.videoData.duration,
|
|
319
340
|
challengesCount: this.state.videoData.challengesCompleted?.length || 0,
|
|
320
341
|
frontImageSize: compressedFrontImage.length,
|
|
@@ -326,7 +347,7 @@ class BiometricIdentitySDK {
|
|
|
326
347
|
const response = await this.backendClient.fullValidation({
|
|
327
348
|
frontIdImage: compressedFrontImage,
|
|
328
349
|
backIdImage: compressedBackImage,
|
|
329
|
-
videoFrames:
|
|
350
|
+
videoFrames: validFrames,
|
|
330
351
|
videoDurationMs: this.state.videoData.duration,
|
|
331
352
|
challengesCompleted: this.state.videoData.challengesCompleted || [],
|
|
332
353
|
});
|
|
@@ -398,13 +419,13 @@ class BiometricIdentitySDK {
|
|
|
398
419
|
/**
|
|
399
420
|
* Compress base64 image - simplified version that works in all environments
|
|
400
421
|
* For React Native, images should be compressed at capture time
|
|
401
|
-
* This
|
|
422
|
+
* This reduces large images by skipping compression if already small enough
|
|
402
423
|
*/
|
|
403
424
|
async compressImage(base64Image) {
|
|
404
425
|
if (!base64Image || base64Image.length === 0) {
|
|
405
426
|
return base64Image;
|
|
406
427
|
}
|
|
407
|
-
const MAX_SIZE =
|
|
428
|
+
const MAX_SIZE = 500 * 1024;
|
|
408
429
|
if (base64Image.length < MAX_SIZE) {
|
|
409
430
|
return base64Image;
|
|
410
431
|
}
|
|
@@ -417,6 +438,7 @@ class BiometricIdentitySDK {
|
|
|
417
438
|
catch (error) {
|
|
418
439
|
logger_1.logger.warn('Image compression not available, using original', error);
|
|
419
440
|
}
|
|
441
|
+
logger_1.logger.warn(`Image too large (${Math.round(base64Image.length / 1024)}KB) but compression not available`);
|
|
420
442
|
return base64Image;
|
|
421
443
|
}
|
|
422
444
|
/**
|
|
@@ -452,6 +474,35 @@ class BiometricIdentitySDK {
|
|
|
452
474
|
img.src = `data:image/jpeg;base64,${base64Image}`;
|
|
453
475
|
});
|
|
454
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Fix base64 padding issues
|
|
479
|
+
*/
|
|
480
|
+
fixBase64Padding(base64) {
|
|
481
|
+
if (!base64)
|
|
482
|
+
return base64;
|
|
483
|
+
let cleaned = base64.replace(/\s/g, '');
|
|
484
|
+
if (cleaned.includes(',')) {
|
|
485
|
+
cleaned = cleaned.split(',')[1] || cleaned;
|
|
486
|
+
}
|
|
487
|
+
const padding = cleaned.length % 4;
|
|
488
|
+
if (padding > 0) {
|
|
489
|
+
cleaned += '='.repeat(4 - padding);
|
|
490
|
+
}
|
|
491
|
+
return cleaned;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Validate if string is valid base64
|
|
495
|
+
*/
|
|
496
|
+
isValidBase64(str) {
|
|
497
|
+
if (!str || typeof str !== 'string')
|
|
498
|
+
return false;
|
|
499
|
+
const cleaned = str.replace(/\s/g, '');
|
|
500
|
+
if (cleaned.includes(',')) {
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
504
|
+
return base64Regex.test(cleaned) && cleaned.length > 100;
|
|
505
|
+
}
|
|
455
506
|
/**
|
|
456
507
|
* Get current SDK state
|
|
457
508
|
*/
|
package/package.json
CHANGED
|
@@ -381,7 +381,7 @@ export class BiometricIdentitySDK {
|
|
|
381
381
|
});
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
const MAX_FRAMES =
|
|
384
|
+
const MAX_FRAMES = 15;
|
|
385
385
|
if (videoFrames.length > MAX_FRAMES) {
|
|
386
386
|
const step = Math.floor(videoFrames.length / MAX_FRAMES);
|
|
387
387
|
videoFrames = videoFrames.filter((_, index) => index % step === 0).slice(0, MAX_FRAMES);
|
|
@@ -391,16 +391,40 @@ export class BiometricIdentitySDK {
|
|
|
391
391
|
});
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
const compressedFrontImage = await this.compressImage(this.state.frontID.data);
|
|
394
|
+
const compressedFrontImage = this.fixBase64Padding(await this.compressImage(this.state.frontID.data));
|
|
395
395
|
const compressedBackImage = this.state.backID?.data
|
|
396
|
-
? await this.compressImage(this.state.backID.data)
|
|
396
|
+
? this.fixBase64Padding(await this.compressImage(this.state.backID.data))
|
|
397
397
|
: undefined;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
)
|
|
398
|
+
|
|
399
|
+
const validFrames: string[] = [];
|
|
400
|
+
for (let i = 0; i < videoFrames.length; i++) {
|
|
401
|
+
const frame = videoFrames[i];
|
|
402
|
+
if (!frame || typeof frame !== 'string') {
|
|
403
|
+
logger.warn(`Skipping invalid frame ${i}: not a string`);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (frame.length < 100) {
|
|
408
|
+
logger.warn(`Skipping invalid frame ${i}: too short (${frame.length} chars), likely a file path`);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!this.isValidBase64(frame)) {
|
|
413
|
+
logger.warn(`Skipping invalid frame ${i}: not valid base64`);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const compressedFrame = this.fixBase64Padding(await this.compressImage(frame));
|
|
418
|
+
validFrames.push(compressedFrame);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (validFrames.length < 10) {
|
|
422
|
+
logger.error(`Insufficient valid frames: ${validFrames.length} < 10`);
|
|
423
|
+
throw new Error(`Insufficient valid video frames: ${validFrames.length} frames available, minimum 10 required`);
|
|
424
|
+
}
|
|
401
425
|
|
|
402
426
|
logger.info('Sending validation request to backend', {
|
|
403
|
-
framesCount:
|
|
427
|
+
framesCount: validFrames.length,
|
|
404
428
|
duration: this.state.videoData.duration,
|
|
405
429
|
challengesCount: this.state.videoData.challengesCompleted?.length || 0,
|
|
406
430
|
frontImageSize: compressedFrontImage.length,
|
|
@@ -413,7 +437,7 @@ export class BiometricIdentitySDK {
|
|
|
413
437
|
const response = await this.backendClient.fullValidation({
|
|
414
438
|
frontIdImage: compressedFrontImage,
|
|
415
439
|
backIdImage: compressedBackImage,
|
|
416
|
-
videoFrames:
|
|
440
|
+
videoFrames: validFrames,
|
|
417
441
|
videoDurationMs: this.state.videoData.duration,
|
|
418
442
|
challengesCompleted: this.state.videoData.challengesCompleted || [],
|
|
419
443
|
});
|
|
@@ -500,14 +524,14 @@ export class BiometricIdentitySDK {
|
|
|
500
524
|
/**
|
|
501
525
|
* Compress base64 image - simplified version that works in all environments
|
|
502
526
|
* For React Native, images should be compressed at capture time
|
|
503
|
-
* This
|
|
527
|
+
* This reduces large images by skipping compression if already small enough
|
|
504
528
|
*/
|
|
505
529
|
private async compressImage(base64Image: string): Promise<string> {
|
|
506
530
|
if (!base64Image || base64Image.length === 0) {
|
|
507
531
|
return base64Image;
|
|
508
532
|
}
|
|
509
533
|
|
|
510
|
-
const MAX_SIZE =
|
|
534
|
+
const MAX_SIZE = 500 * 1024;
|
|
511
535
|
if (base64Image.length < MAX_SIZE) {
|
|
512
536
|
return base64Image;
|
|
513
537
|
}
|
|
@@ -521,6 +545,7 @@ export class BiometricIdentitySDK {
|
|
|
521
545
|
logger.warn('Image compression not available, using original', error);
|
|
522
546
|
}
|
|
523
547
|
|
|
548
|
+
logger.warn(`Image too large (${Math.round(base64Image.length / 1024)}KB) but compression not available`);
|
|
524
549
|
return base64Image;
|
|
525
550
|
}
|
|
526
551
|
|
|
@@ -563,6 +588,41 @@ export class BiometricIdentitySDK {
|
|
|
563
588
|
});
|
|
564
589
|
}
|
|
565
590
|
|
|
591
|
+
/**
|
|
592
|
+
* Fix base64 padding issues
|
|
593
|
+
*/
|
|
594
|
+
private fixBase64Padding(base64: string): string {
|
|
595
|
+
if (!base64) return base64;
|
|
596
|
+
|
|
597
|
+
let cleaned = base64.replace(/\s/g, '');
|
|
598
|
+
|
|
599
|
+
if (cleaned.includes(',')) {
|
|
600
|
+
cleaned = cleaned.split(',')[1] || cleaned;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const padding = cleaned.length % 4;
|
|
604
|
+
if (padding > 0) {
|
|
605
|
+
cleaned += '='.repeat(4 - padding);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return cleaned;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Validate if string is valid base64
|
|
613
|
+
*/
|
|
614
|
+
private isValidBase64(str: string): boolean {
|
|
615
|
+
if (!str || typeof str !== 'string') return false;
|
|
616
|
+
|
|
617
|
+
const cleaned = str.replace(/\s/g, '');
|
|
618
|
+
if (cleaned.includes(',')) {
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
623
|
+
return base64Regex.test(cleaned) && cleaned.length > 100;
|
|
624
|
+
}
|
|
625
|
+
|
|
566
626
|
/**
|
|
567
627
|
* Get current SDK state
|
|
568
628
|
*/
|
package/src/api/BackendClient.ts
CHANGED
|
@@ -141,7 +141,7 @@ export class BackendClient {
|
|
|
141
141
|
this.config = {
|
|
142
142
|
apiEndpoint: config.apiEndpoint.replace(/\/$/, ''),
|
|
143
143
|
apiKey: config.apiKey,
|
|
144
|
-
timeout: config.timeout ||
|
|
144
|
+
timeout: config.timeout || 120000,
|
|
145
145
|
};
|
|
146
146
|
this.sdkSessionId = this.generateSDKSessionId();
|
|
147
147
|
}
|