@hexar/biometric-identity-sdk-react-native 1.0.34 → 1.0.36

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,19 @@
1
+ import React from 'react';
2
+ import { ThemeConfig, SupportedLanguage, BiometricError } from '@hexar/biometric-identity-sdk-core';
3
+ export interface ProfilePictureValidationResult {
4
+ isValid: boolean;
5
+ profilePicture: string;
6
+ livenessScore: number;
7
+ faceDetected: boolean;
8
+ warnings: string[];
9
+ }
10
+ export interface ProfilePictureCaptureProps {
11
+ onComplete: (result: ProfilePictureValidationResult) => void;
12
+ onError: (error: BiometricError) => void;
13
+ onCancel?: () => void;
14
+ theme?: ThemeConfig;
15
+ language?: SupportedLanguage;
16
+ }
17
+ export declare const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps>;
18
+ export default ProfilePictureCapture;
19
+ //# sourceMappingURL=ProfilePictureCapture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfilePictureCapture.d.ts","sourceRoot":"","sources":["../../src/components/ProfilePictureCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AASxE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmC,cAAc,EAAsB,MAAM,oCAAoC,CAAC;AAGzJ,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,IAAI,CAAC;IAC7D,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAuMtE,CAAC;AAuCF,eAAe,qBAAqB,CAAC"}
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ProfilePictureCapture = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const VideoRecorder_1 = require("./VideoRecorder");
40
+ const biometric_identity_sdk_core_1 = require("@hexar/biometric-identity-sdk-core");
41
+ const biometric_identity_sdk_core_2 = require("@hexar/biometric-identity-sdk-core");
42
+ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language, }) => {
43
+ const [isValidating, setIsValidating] = (0, react_1.useState)(false);
44
+ const [validationError, setValidationError] = (0, react_1.useState)(null);
45
+ (0, react_1.useEffect)(() => {
46
+ if (language) {
47
+ (0, biometric_identity_sdk_core_1.setLanguage)(language);
48
+ }
49
+ }, [language]);
50
+ const strings = (0, biometric_identity_sdk_core_1.getStrings)();
51
+ const validateWithBackend = (0, react_1.useCallback)(async (videoResult) => {
52
+ try {
53
+ const sdk = biometric_identity_sdk_core_2.BiometricIdentitySDK.getInstance();
54
+ if (!sdk.isUsingBackend()) {
55
+ biometric_identity_sdk_core_1.logger.error('Backend not available - SDK not configured with backend');
56
+ throw new Error('NETWORK_ERROR');
57
+ }
58
+ const sessionId = videoResult.sessionId;
59
+ if (!sessionId) {
60
+ biometric_identity_sdk_core_1.logger.error('Session ID not available');
61
+ throw new Error('VALIDATION_ERROR');
62
+ }
63
+ const backendClient = sdk.backendClient;
64
+ const apiEndpoint = backendClient.config.apiEndpoint;
65
+ const apiKey = backendClient.config.apiKey;
66
+ const requestBody = {
67
+ session_id: sessionId,
68
+ video_frames: videoResult.frames,
69
+ video_duration_ms: videoResult.duration,
70
+ challenges_completed: videoResult.challengesCompleted || [],
71
+ };
72
+ biometric_identity_sdk_core_1.logger.info('Validating profile picture with backend', {
73
+ sessionId,
74
+ framesCount: videoResult.frames.length,
75
+ duration: videoResult.duration,
76
+ apiEndpoint,
77
+ });
78
+ const fullUrl = `${apiEndpoint}/profile/validate-face`;
79
+ biometric_identity_sdk_core_1.logger.info('Making request to biometry backend', { url: fullUrl });
80
+ const response = await fetch(fullUrl, {
81
+ method: 'POST',
82
+ headers: {
83
+ 'Content-Type': 'application/json',
84
+ 'X-API-Key': apiKey,
85
+ },
86
+ body: JSON.stringify(requestBody),
87
+ });
88
+ biometric_identity_sdk_core_1.logger.info('Backend response received', { status: response.status, ok: response.ok });
89
+ if (!response.ok) {
90
+ const errorData = await response.json().catch(() => ({}));
91
+ biometric_identity_sdk_core_1.logger.error('Backend validation failed', { status: response.status, error: errorData });
92
+ throw new Error('VALIDATION_ERROR');
93
+ }
94
+ const data = await response.json();
95
+ biometric_identity_sdk_core_1.logger.info('Profile picture validation result', {
96
+ isValid: data.is_valid,
97
+ livenessScore: data.liveness_score,
98
+ faceDetected: data.face_detected,
99
+ });
100
+ return {
101
+ isValid: data.is_valid,
102
+ profilePicture: data.profile_picture,
103
+ livenessScore: data.liveness_score,
104
+ faceDetected: data.face_detected,
105
+ warnings: data.warnings || [],
106
+ };
107
+ }
108
+ catch (error) {
109
+ biometric_identity_sdk_core_1.logger.error('Profile picture validation error', error);
110
+ throw error;
111
+ }
112
+ }, []);
113
+ const handleVideoComplete = (0, react_1.useCallback)(async (videoResult) => {
114
+ setIsValidating(true);
115
+ setValidationError(null);
116
+ try {
117
+ const result = await validateWithBackend(videoResult);
118
+ if (!result.isValid) {
119
+ setValidationError('La validación de liveness falló. Por favor, intenta nuevamente.');
120
+ setIsValidating(false);
121
+ return;
122
+ }
123
+ setIsValidating(false);
124
+ onComplete(result);
125
+ }
126
+ catch (error) {
127
+ setIsValidating(false);
128
+ let errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.UNKNOWN_ERROR;
129
+ const getErrorMessage = (code) => {
130
+ const messages = {
131
+ NETWORK_ERROR: {
132
+ en: 'Connection error. Please check your internet connection.',
133
+ es: 'Error de conexión. Verifica tu conexión a internet.',
134
+ },
135
+ VALIDATION_TIMEOUT: {
136
+ en: 'Validation took too long. Please try again.',
137
+ es: 'La validación tardó demasiado. Intenta nuevamente.',
138
+ },
139
+ LIVENESS_CHECK_FAILED: {
140
+ en: 'Liveness verification failed. Please try again.',
141
+ es: 'La verificación de presencia vital falló. Por favor intenta de nuevo.',
142
+ },
143
+ UNKNOWN_ERROR: {
144
+ en: 'An unexpected error occurred. Please try again.',
145
+ es: 'Ocurrió un error inesperado. Por favor intenta de nuevo.',
146
+ },
147
+ };
148
+ const message = messages[code] || messages.UNKNOWN_ERROR;
149
+ return language === 'es' || language === 'es-AR' ? message.es : message.en;
150
+ };
151
+ if (error.message === 'NETWORK_ERROR' || (error.message && error.message.toLowerCase().includes('network'))) {
152
+ errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.NETWORK_ERROR;
153
+ }
154
+ else if (error.message && error.message.toLowerCase().includes('timeout')) {
155
+ errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.VALIDATION_TIMEOUT;
156
+ }
157
+ else if (error.message && error.message.toLowerCase().includes('liveness')) {
158
+ errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.LIVENESS_CHECK_FAILED;
159
+ }
160
+ const biometricError = {
161
+ name: 'BiometricError',
162
+ message: getErrorMessage(errorCode),
163
+ code: errorCode,
164
+ };
165
+ onError(biometricError);
166
+ }
167
+ }, [validateWithBackend, onComplete, onError]);
168
+ const handleVideoCancel = (0, react_1.useCallback)(() => {
169
+ if (onCancel) {
170
+ onCancel();
171
+ }
172
+ }, [onCancel]);
173
+ if (isValidating) {
174
+ return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }] },
175
+ react_1.default.createElement(react_native_1.View, { style: styles.loadingContainer },
176
+ react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: theme?.primaryColor || '#4f46e5' }),
177
+ react_1.default.createElement(react_native_1.Text, { style: [styles.loadingText, { color: theme?.textColor || '#1e1b4b' }] }, strings.liveness.processing || 'Validando foto de perfil...'),
178
+ react_1.default.createElement(react_native_1.Text, { style: [styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }] }, "Esto puede tardar unos segundos"))));
179
+ }
180
+ if (validationError) {
181
+ return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }] },
182
+ react_1.default.createElement(react_native_1.View, { style: styles.errorContainer },
183
+ react_1.default.createElement(react_native_1.Text, { style: [styles.errorTitle, { color: theme?.errorColor || '#EF4444' }] }, "Error de validaci\u00F3n"),
184
+ react_1.default.createElement(react_native_1.Text, { style: [styles.errorText, { color: theme?.textColor || '#1e1b4b' }] }, validationError))));
185
+ }
186
+ return (react_1.default.createElement(VideoRecorder_1.VideoRecorder, { theme: theme, language: language, duration: 8000, smartMode: true, onComplete: handleVideoComplete, onCancel: handleVideoCancel }));
187
+ };
188
+ exports.ProfilePictureCapture = ProfilePictureCapture;
189
+ const styles = react_native_1.StyleSheet.create({
190
+ container: {
191
+ flex: 1,
192
+ },
193
+ loadingContainer: {
194
+ flex: 1,
195
+ justifyContent: 'center',
196
+ alignItems: 'center',
197
+ padding: 24,
198
+ },
199
+ loadingText: {
200
+ fontSize: 18,
201
+ fontWeight: '600',
202
+ marginTop: 24,
203
+ },
204
+ loadingSubtext: {
205
+ fontSize: 14,
206
+ marginTop: 8,
207
+ },
208
+ errorContainer: {
209
+ flex: 1,
210
+ justifyContent: 'center',
211
+ alignItems: 'center',
212
+ padding: 24,
213
+ },
214
+ errorTitle: {
215
+ fontSize: 20,
216
+ fontWeight: '600',
217
+ marginBottom: 12,
218
+ },
219
+ errorText: {
220
+ fontSize: 16,
221
+ textAlign: 'center',
222
+ lineHeight: 24,
223
+ },
224
+ });
225
+ exports.default = exports.ProfilePictureCapture;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export { BiometricIdentityFlow } from './components/BiometricIdentityFlow';
6
6
  export { default } from './components/BiometricIdentityFlow';
7
7
  export { CameraCapture } from './components/CameraCapture';
8
8
  export { VideoRecorder } from './components/VideoRecorder';
9
+ export { ProfilePictureCapture } from './components/ProfilePictureCapture';
10
+ export type { ProfilePictureValidationResult } from './components/ProfilePictureCapture';
9
11
  export { ValidationProgress } from './components/ValidationProgress';
10
12
  export { ResultScreen } from './components/ResultScreen';
11
13
  export { ErrorScreen } from './components/ErrorScreen';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,cAAc,oCAAoC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,YAAY,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,cAAc,oCAAoC,CAAC"}
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
21
21
  return (mod && mod.__esModule) ? mod : { "default": mod };
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.useBiometricSDK = exports.InstructionsScreen = exports.ErrorScreen = exports.ResultScreen = exports.ValidationProgress = exports.VideoRecorder = exports.CameraCapture = exports.default = exports.BiometricIdentityFlow = void 0;
24
+ exports.useBiometricSDK = exports.InstructionsScreen = exports.ErrorScreen = exports.ResultScreen = exports.ValidationProgress = exports.ProfilePictureCapture = exports.VideoRecorder = exports.CameraCapture = exports.default = exports.BiometricIdentityFlow = void 0;
25
25
  // Main component
26
26
  var BiometricIdentityFlow_1 = require("./components/BiometricIdentityFlow");
27
27
  Object.defineProperty(exports, "BiometricIdentityFlow", { enumerable: true, get: function () { return BiometricIdentityFlow_1.BiometricIdentityFlow; } });
@@ -32,6 +32,8 @@ var CameraCapture_1 = require("./components/CameraCapture");
32
32
  Object.defineProperty(exports, "CameraCapture", { enumerable: true, get: function () { return CameraCapture_1.CameraCapture; } });
33
33
  var VideoRecorder_1 = require("./components/VideoRecorder");
34
34
  Object.defineProperty(exports, "VideoRecorder", { enumerable: true, get: function () { return VideoRecorder_1.VideoRecorder; } });
35
+ var ProfilePictureCapture_1 = require("./components/ProfilePictureCapture");
36
+ Object.defineProperty(exports, "ProfilePictureCapture", { enumerable: true, get: function () { return ProfilePictureCapture_1.ProfilePictureCapture; } });
35
37
  var ValidationProgress_1 = require("./components/ValidationProgress");
36
38
  Object.defineProperty(exports, "ValidationProgress", { enumerable: true, get: function () { return ValidationProgress_1.ValidationProgress; } });
37
39
  var ResultScreen_1 = require("./components/ResultScreen");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,267 @@
1
+ import React, { useState, useCallback, useRef, useEffect } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ ActivityIndicator,
7
+ SafeAreaView,
8
+ } from 'react-native';
9
+ import { VideoRecorder, VideoRecordingResult } from './VideoRecorder';
10
+ import { ThemeConfig, SupportedLanguage, getStrings, setLanguage, logger, BiometricError, BiometricErrorCode } from '@hexar/biometric-identity-sdk-core';
11
+ import { BiometricIdentitySDK } from '@hexar/biometric-identity-sdk-core';
12
+
13
+ export interface ProfilePictureValidationResult {
14
+ isValid: boolean;
15
+ profilePicture: string;
16
+ livenessScore: number;
17
+ faceDetected: boolean;
18
+ warnings: string[];
19
+ }
20
+
21
+ export interface ProfilePictureCaptureProps {
22
+ onComplete: (result: ProfilePictureValidationResult) => void;
23
+ onError: (error: BiometricError) => void;
24
+ onCancel?: () => void;
25
+ theme?: ThemeConfig;
26
+ language?: SupportedLanguage;
27
+ }
28
+
29
+ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
30
+ onComplete,
31
+ onError,
32
+ onCancel,
33
+ theme,
34
+ language,
35
+ }) => {
36
+ const [isValidating, setIsValidating] = useState(false);
37
+ const [validationError, setValidationError] = useState<string | null>(null);
38
+
39
+ useEffect(() => {
40
+ if (language) {
41
+ setLanguage(language);
42
+ }
43
+ }, [language]);
44
+
45
+ const strings = getStrings();
46
+
47
+ const validateWithBackend = useCallback(async (videoResult: VideoRecordingResult): Promise<ProfilePictureValidationResult> => {
48
+ try {
49
+ const sdk = BiometricIdentitySDK.getInstance();
50
+
51
+ if (!sdk.isUsingBackend()) {
52
+ logger.error('Backend not available - SDK not configured with backend');
53
+ throw new Error('NETWORK_ERROR');
54
+ }
55
+
56
+ const sessionId = videoResult.sessionId;
57
+ if (!sessionId) {
58
+ logger.error('Session ID not available');
59
+ throw new Error('VALIDATION_ERROR');
60
+ }
61
+
62
+ const backendClient = (sdk as any).backendClient;
63
+ const apiEndpoint = backendClient.config.apiEndpoint;
64
+ const apiKey = backendClient.config.apiKey;
65
+
66
+ const requestBody = {
67
+ session_id: sessionId,
68
+ video_frames: videoResult.frames,
69
+ video_duration_ms: videoResult.duration,
70
+ challenges_completed: videoResult.challengesCompleted || [],
71
+ };
72
+
73
+ logger.info('Validating profile picture with backend', {
74
+ sessionId,
75
+ framesCount: videoResult.frames.length,
76
+ duration: videoResult.duration,
77
+ apiEndpoint,
78
+ });
79
+
80
+ const fullUrl = `${apiEndpoint}/profile/validate-face`;
81
+ logger.info('Making request to biometry backend', { url: fullUrl });
82
+
83
+ const response = await fetch(fullUrl, {
84
+ method: 'POST',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ 'X-API-Key': apiKey,
88
+ },
89
+ body: JSON.stringify(requestBody),
90
+ });
91
+
92
+ logger.info('Backend response received', { status: response.status, ok: response.ok });
93
+
94
+ if (!response.ok) {
95
+ const errorData = await response.json().catch(() => ({}));
96
+ logger.error('Backend validation failed', { status: response.status, error: errorData });
97
+ throw new Error('VALIDATION_ERROR');
98
+ }
99
+
100
+ const data = await response.json();
101
+
102
+ logger.info('Profile picture validation result', {
103
+ isValid: data.is_valid,
104
+ livenessScore: data.liveness_score,
105
+ faceDetected: data.face_detected,
106
+ });
107
+
108
+ return {
109
+ isValid: data.is_valid,
110
+ profilePicture: data.profile_picture,
111
+ livenessScore: data.liveness_score,
112
+ faceDetected: data.face_detected,
113
+ warnings: data.warnings || [],
114
+ };
115
+ } catch (error: any) {
116
+ logger.error('Profile picture validation error', error);
117
+ throw error;
118
+ }
119
+ }, []);
120
+
121
+ const handleVideoComplete = useCallback(async (videoResult: VideoRecordingResult) => {
122
+ setIsValidating(true);
123
+ setValidationError(null);
124
+
125
+ try {
126
+ const result = await validateWithBackend(videoResult);
127
+
128
+ if (!result.isValid) {
129
+ setValidationError('La validación de liveness falló. Por favor, intenta nuevamente.');
130
+ setIsValidating(false);
131
+ return;
132
+ }
133
+
134
+ setIsValidating(false);
135
+ onComplete(result);
136
+ } catch (error: any) {
137
+ setIsValidating(false);
138
+ let errorCode = BiometricErrorCode.UNKNOWN_ERROR;
139
+
140
+ const getErrorMessage = (code: BiometricErrorCode): string => {
141
+ const messages: Record<string, { en: string; es: string }> = {
142
+ NETWORK_ERROR: {
143
+ en: 'Connection error. Please check your internet connection.',
144
+ es: 'Error de conexión. Verifica tu conexión a internet.',
145
+ },
146
+ VALIDATION_TIMEOUT: {
147
+ en: 'Validation took too long. Please try again.',
148
+ es: 'La validación tardó demasiado. Intenta nuevamente.',
149
+ },
150
+ LIVENESS_CHECK_FAILED: {
151
+ en: 'Liveness verification failed. Please try again.',
152
+ es: 'La verificación de presencia vital falló. Por favor intenta de nuevo.',
153
+ },
154
+ UNKNOWN_ERROR: {
155
+ en: 'An unexpected error occurred. Please try again.',
156
+ es: 'Ocurrió un error inesperado. Por favor intenta de nuevo.',
157
+ },
158
+ };
159
+
160
+ const message = messages[code] || messages.UNKNOWN_ERROR;
161
+ return language === 'es' || language === 'es-AR' ? message.es : message.en;
162
+ };
163
+
164
+ if (error.message === 'NETWORK_ERROR' || (error.message && error.message.toLowerCase().includes('network'))) {
165
+ errorCode = BiometricErrorCode.NETWORK_ERROR;
166
+ } else if (error.message && error.message.toLowerCase().includes('timeout')) {
167
+ errorCode = BiometricErrorCode.VALIDATION_TIMEOUT;
168
+ } else if (error.message && error.message.toLowerCase().includes('liveness')) {
169
+ errorCode = BiometricErrorCode.LIVENESS_CHECK_FAILED;
170
+ }
171
+
172
+ const biometricError: BiometricError = {
173
+ name: 'BiometricError',
174
+ message: getErrorMessage(errorCode),
175
+ code: errorCode,
176
+ } as BiometricError;
177
+ onError(biometricError);
178
+ }
179
+ }, [validateWithBackend, onComplete, onError]);
180
+
181
+ const handleVideoCancel = useCallback(() => {
182
+ if (onCancel) {
183
+ onCancel();
184
+ }
185
+ }, [onCancel]);
186
+
187
+ if (isValidating) {
188
+ return (
189
+ <SafeAreaView style={[styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }]}>
190
+ <View style={styles.loadingContainer}>
191
+ <ActivityIndicator size="large" color={theme?.primaryColor || '#4f46e5'} />
192
+ <Text style={[styles.loadingText, { color: theme?.textColor || '#1e1b4b' }]}>
193
+ {strings.liveness.processing || 'Validando foto de perfil...'}
194
+ </Text>
195
+ <Text style={[styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }]}>
196
+ Esto puede tardar unos segundos
197
+ </Text>
198
+ </View>
199
+ </SafeAreaView>
200
+ );
201
+ }
202
+
203
+ if (validationError) {
204
+ return (
205
+ <SafeAreaView style={[styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }]}>
206
+ <View style={styles.errorContainer}>
207
+ <Text style={[styles.errorTitle, { color: theme?.errorColor || '#EF4444' }]}>
208
+ Error de validación
209
+ </Text>
210
+ <Text style={[styles.errorText, { color: theme?.textColor || '#1e1b4b' }]}>
211
+ {validationError}
212
+ </Text>
213
+ </View>
214
+ </SafeAreaView>
215
+ );
216
+ }
217
+
218
+ return (
219
+ <VideoRecorder
220
+ theme={theme}
221
+ language={language}
222
+ duration={8000}
223
+ smartMode={true}
224
+ onComplete={handleVideoComplete}
225
+ onCancel={handleVideoCancel}
226
+ />
227
+ );
228
+ };
229
+
230
+ const styles = StyleSheet.create({
231
+ container: {
232
+ flex: 1,
233
+ },
234
+ loadingContainer: {
235
+ flex: 1,
236
+ justifyContent: 'center',
237
+ alignItems: 'center',
238
+ padding: 24,
239
+ },
240
+ loadingText: {
241
+ fontSize: 18,
242
+ fontWeight: '600',
243
+ marginTop: 24,
244
+ },
245
+ loadingSubtext: {
246
+ fontSize: 14,
247
+ marginTop: 8,
248
+ },
249
+ errorContainer: {
250
+ flex: 1,
251
+ justifyContent: 'center',
252
+ alignItems: 'center',
253
+ padding: 24,
254
+ },
255
+ errorTitle: {
256
+ fontSize: 20,
257
+ fontWeight: '600',
258
+ marginBottom: 12,
259
+ },
260
+ errorText: {
261
+ fontSize: 16,
262
+ textAlign: 'center',
263
+ lineHeight: 24,
264
+ },
265
+ });
266
+
267
+ export default ProfilePictureCapture;
package/src/index.ts CHANGED
@@ -10,6 +10,8 @@ export { default } from './components/BiometricIdentityFlow';
10
10
  // Individual components (for custom implementations)
11
11
  export { CameraCapture } from './components/CameraCapture';
12
12
  export { VideoRecorder } from './components/VideoRecorder';
13
+ export { ProfilePictureCapture } from './components/ProfilePictureCapture';
14
+ export type { ProfilePictureValidationResult } from './components/ProfilePictureCapture';
13
15
  export { ValidationProgress } from './components/ValidationProgress';
14
16
  export { ResultScreen } from './components/ResultScreen';
15
17
  export { ErrorScreen } from './components/ErrorScreen';