@hexar/biometric-identity-sdk-react-native 1.1.0 → 1.1.2
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/dist/components/ErrorScreen.d.ts.map +1 -1
- package/dist/components/ErrorScreen.js +65 -38
- package/dist/components/ProfilePictureCapture.d.ts.map +1 -1
- package/dist/components/ProfilePictureCapture.js +39 -66
- package/package.json +1 -1
- package/src/components/ErrorScreen.tsx +35 -36
- package/src/components/ProfilePictureCapture.tsx +47 -74
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorScreen.d.ts","sourceRoot":"","sources":["../../src/components/ErrorScreen.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"ErrorScreen.d.ts","sourceRoot":"","sources":["../../src/components/ErrorScreen.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAoB,MAAM,OAAO,CAAC;AAOzC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAA2B,MAAM,oCAAoC,CAAC;AAE7H,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAoGlD,CAAC;AA4EF,eAAe,WAAW,CAAC"}
|
|
@@ -2,46 +2,73 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Error Screen Component
|
|
4
4
|
*/
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
8
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
39
|
exports.ErrorScreen = void 0;
|
|
10
|
-
const react_1 =
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
11
41
|
const react_native_1 = require("react-native");
|
|
12
|
-
const
|
|
42
|
+
const biometric_identity_sdk_core_1 = require("@hexar/biometric-identity-sdk-core");
|
|
43
|
+
const ErrorScreen = ({ error, theme, language, onRetry, onClose, }) => {
|
|
44
|
+
(0, react_1.useEffect)(() => {
|
|
45
|
+
if (language) {
|
|
46
|
+
(0, biometric_identity_sdk_core_1.setLanguage)(language);
|
|
47
|
+
}
|
|
48
|
+
}, [language]);
|
|
49
|
+
const strings = (0, biometric_identity_sdk_core_1.getStrings)();
|
|
13
50
|
const getErrorMessage = () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
POOR_IMAGE_QUALITY: {
|
|
24
|
-
en: 'Image quality is insufficient. Please avoid glare, blur, and ensure good lighting.',
|
|
25
|
-
es: 'La calidad de la imagen es insuficiente. Evita brillos, desenfoque y asegura buena iluminación.',
|
|
26
|
-
},
|
|
27
|
-
LIVENESS_CHECK_FAILED: {
|
|
28
|
-
en: 'Liveness verification failed. Please record a new video following the instructions.',
|
|
29
|
-
es: 'La verificación de presencia vital falló. Por favor graba un nuevo video siguiendo las instrucciones.',
|
|
30
|
-
},
|
|
31
|
-
FACE_MATCH_FAILED: {
|
|
32
|
-
en: 'The face in the video does not match the document photo.',
|
|
33
|
-
es: 'El rostro en el video no coincide con la foto del documento.',
|
|
34
|
-
},
|
|
35
|
-
DOCUMENT_TAMPERED: {
|
|
36
|
-
en: 'Document appears to be tampered or altered.',
|
|
37
|
-
es: 'El documento parece estar adulterado o alterado.',
|
|
38
|
-
},
|
|
51
|
+
if (error.message) {
|
|
52
|
+
return error.message;
|
|
53
|
+
}
|
|
54
|
+
const getCameraPermissionMessage = () => {
|
|
55
|
+
const permissionError = strings.errors.cameraPermissionDenied;
|
|
56
|
+
if (typeof permissionError === 'object' && permissionError?.message) {
|
|
57
|
+
return permissionError.message;
|
|
58
|
+
}
|
|
59
|
+
return typeof permissionError === 'string' ? permissionError : 'Camera permission is required.';
|
|
39
60
|
};
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
const errorMessageMap = {
|
|
62
|
+
CAMERA_PERMISSION_DENIED: getCameraPermissionMessage(),
|
|
63
|
+
FACE_NOT_DETECTED: strings.errors.faceNotDetected || 'No face detected.',
|
|
64
|
+
POOR_IMAGE_QUALITY: strings.errors.poorImageQuality || 'Image quality is insufficient.',
|
|
65
|
+
LIVENESS_CHECK_FAILED: strings.errors.livenessCheckFailed || 'Liveness verification failed.',
|
|
66
|
+
FACE_MATCH_FAILED: strings.errors.faceMatchFailed || 'Face match failed.',
|
|
67
|
+
DOCUMENT_TAMPERED: strings.errors.documentTampered || 'Document appears to be tampered.',
|
|
68
|
+
NETWORK_ERROR: strings.errors.networkError || 'Network error.',
|
|
69
|
+
VALIDATION_TIMEOUT: strings.errors.timeout || 'Validation timeout.',
|
|
43
70
|
};
|
|
44
|
-
return
|
|
71
|
+
return errorMessageMap[error.code] || strings.errors.unknownError || 'An unexpected error occurred.';
|
|
45
72
|
};
|
|
46
73
|
return (react_1.default.createElement(react_native_1.View, { style: styles.container },
|
|
47
74
|
react_1.default.createElement(react_native_1.View, { style: styles.content },
|
|
@@ -50,10 +77,10 @@ const ErrorScreen = ({ error, theme, language = 'en', onRetry, onClose, }) => {
|
|
|
50
77
|
{ backgroundColor: theme?.errorColor || '#EF4444' },
|
|
51
78
|
] },
|
|
52
79
|
react_1.default.createElement(react_native_1.Text, { style: styles.icon }, "\u2717")),
|
|
53
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.title, { color: theme?.textColor || '#000000' }] },
|
|
80
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.title, { color: theme?.textColor || '#000000' }] }, strings.result.failure.title || strings.errors.unknownError || 'Verification Error'),
|
|
54
81
|
react_1.default.createElement(react_native_1.Text, { style: [styles.message, { color: theme?.secondaryTextColor || '#6B7280' }] }, getErrorMessage()),
|
|
55
82
|
react_1.default.createElement(react_native_1.View, { style: styles.codeContainer },
|
|
56
|
-
react_1.default.createElement(react_native_1.Text, { style: styles.codeLabel },
|
|
83
|
+
react_1.default.createElement(react_native_1.Text, { style: styles.codeLabel }, strings.common.error || 'Error Code:'),
|
|
57
84
|
react_1.default.createElement(react_native_1.Text, { style: styles.codeText }, error.code)),
|
|
58
85
|
react_1.default.createElement(react_native_1.View, { style: styles.buttonsContainer },
|
|
59
86
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
|
|
@@ -61,9 +88,9 @@ const ErrorScreen = ({ error, theme, language = 'en', onRetry, onClose, }) => {
|
|
|
61
88
|
styles.retryButton,
|
|
62
89
|
{ backgroundColor: theme?.primaryColor || '#6366F1' },
|
|
63
90
|
], onPress: onRetry },
|
|
64
|
-
react_1.default.createElement(react_native_1.Text, { style: styles.buttonText },
|
|
91
|
+
react_1.default.createElement(react_native_1.Text, { style: styles.buttonText }, strings.common.retry || 'Try Again')),
|
|
65
92
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.button, styles.closeButton], onPress: onClose },
|
|
66
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.buttonText, { color: theme?.textColor || '#000000' }] },
|
|
93
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.buttonText, { color: theme?.textColor || '#000000' }] }, strings.common.close || 'Close'))))));
|
|
67
94
|
};
|
|
68
95
|
exports.ErrorScreen = ErrorScreen;
|
|
69
96
|
const styles = react_native_1.StyleSheet.create({
|
|
@@ -1 +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,
|
|
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,CAwKtE,CAAC;AAuCF,eAAe,qBAAqB,CAAC"}
|
|
@@ -38,8 +38,9 @@ const react_1 = __importStar(require("react"));
|
|
|
38
38
|
const react_native_1 = require("react-native");
|
|
39
39
|
const VideoRecorder_1 = require("./VideoRecorder");
|
|
40
40
|
const biometric_identity_sdk_core_1 = require("@hexar/biometric-identity-sdk-core");
|
|
41
|
-
const
|
|
41
|
+
const useBiometricSDK_1 = require("../hooks/useBiometricSDK");
|
|
42
42
|
const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language, }) => {
|
|
43
|
+
const { sdk, isInitialized, isUsingBackend, } = (0, useBiometricSDK_1.useBiometricSDK)();
|
|
43
44
|
const [isValidating, setIsValidating] = (0, react_1.useState)(false);
|
|
44
45
|
const [validationError, setValidationError] = (0, react_1.useState)(null);
|
|
45
46
|
(0, react_1.useEffect)(() => {
|
|
@@ -50,8 +51,11 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
50
51
|
const strings = (0, biometric_identity_sdk_core_1.getStrings)();
|
|
51
52
|
const validateWithBackend = (0, react_1.useCallback)(async (videoResult) => {
|
|
52
53
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
if (!isInitialized) {
|
|
55
|
+
biometric_identity_sdk_core_1.logger.error('SDK not initialized');
|
|
56
|
+
throw new Error('NETWORK_ERROR');
|
|
57
|
+
}
|
|
58
|
+
if (!isUsingBackend) {
|
|
55
59
|
biometric_identity_sdk_core_1.logger.error('Backend not available - SDK not configured with backend');
|
|
56
60
|
throw new Error('NETWORK_ERROR');
|
|
57
61
|
}
|
|
@@ -60,60 +64,43 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
60
64
|
biometric_identity_sdk_core_1.logger.error('Session ID not available');
|
|
61
65
|
throw new Error('VALIDATION_ERROR');
|
|
62
66
|
}
|
|
63
|
-
const backendClient = sdk.backendClient;
|
|
64
|
-
const config = backendClient.config;
|
|
65
|
-
const requestBody = {
|
|
66
|
-
session_id: sessionId,
|
|
67
|
-
video_frames: videoResult.frames,
|
|
68
|
-
video_duration_ms: videoResult.duration,
|
|
69
|
-
challenges_completed: videoResult.challengesCompleted || [],
|
|
70
|
-
};
|
|
71
67
|
biometric_identity_sdk_core_1.logger.info('Validating profile picture with backend', {
|
|
72
68
|
sessionId,
|
|
73
69
|
framesCount: videoResult.frames.length,
|
|
74
70
|
duration: videoResult.duration,
|
|
75
|
-
|
|
71
|
+
isUsingBackend,
|
|
76
72
|
});
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
'X-API-Key': config.apiKey,
|
|
83
|
-
},
|
|
84
|
-
body: JSON.stringify(requestBody),
|
|
73
|
+
const result = await sdk.validateProfilePicture({
|
|
74
|
+
sessionId,
|
|
75
|
+
videoFrames: videoResult.frames,
|
|
76
|
+
videoDurationMs: videoResult.duration,
|
|
77
|
+
challengesCompleted: videoResult.challengesCompleted || [],
|
|
85
78
|
});
|
|
86
|
-
if (!response.ok) {
|
|
87
|
-
const errorData = await response.json().catch(() => ({}));
|
|
88
|
-
biometric_identity_sdk_core_1.logger.error('Backend validation failed', { status: response.status, error: errorData });
|
|
89
|
-
throw new Error('VALIDATION_ERROR');
|
|
90
|
-
}
|
|
91
|
-
const data = await response.json();
|
|
92
79
|
biometric_identity_sdk_core_1.logger.info('Profile picture validation result', {
|
|
93
|
-
isValid:
|
|
94
|
-
livenessScore:
|
|
95
|
-
faceDetected:
|
|
80
|
+
isValid: result.isValid,
|
|
81
|
+
livenessScore: result.livenessScore,
|
|
82
|
+
faceDetected: result.faceDetected,
|
|
96
83
|
});
|
|
97
84
|
return {
|
|
98
|
-
isValid:
|
|
99
|
-
profilePicture:
|
|
100
|
-
livenessScore:
|
|
101
|
-
faceDetected:
|
|
102
|
-
warnings:
|
|
85
|
+
isValid: result.isValid,
|
|
86
|
+
profilePicture: result.profilePicture,
|
|
87
|
+
livenessScore: result.livenessScore,
|
|
88
|
+
faceDetected: result.faceDetected,
|
|
89
|
+
warnings: result.warnings || [],
|
|
103
90
|
};
|
|
104
91
|
}
|
|
105
92
|
catch (error) {
|
|
106
93
|
biometric_identity_sdk_core_1.logger.error('Profile picture validation error', error);
|
|
107
94
|
throw error;
|
|
108
95
|
}
|
|
109
|
-
}, []);
|
|
96
|
+
}, [isInitialized, isUsingBackend, sdk]);
|
|
110
97
|
const handleVideoComplete = (0, react_1.useCallback)(async (videoResult) => {
|
|
111
98
|
setIsValidating(true);
|
|
112
99
|
setValidationError(null);
|
|
113
100
|
try {
|
|
114
101
|
const result = await validateWithBackend(videoResult);
|
|
115
102
|
if (!result.isValid) {
|
|
116
|
-
setValidationError(
|
|
103
|
+
setValidationError(strings.errors.livenessCheckFailed || 'Liveness check failed');
|
|
117
104
|
setIsValidating(false);
|
|
118
105
|
return;
|
|
119
106
|
}
|
|
@@ -122,46 +109,32 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
122
109
|
}
|
|
123
110
|
catch (error) {
|
|
124
111
|
setIsValidating(false);
|
|
112
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
113
|
+
onError(error);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
125
116
|
let errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.UNKNOWN_ERROR;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
NETWORK_ERROR: {
|
|
129
|
-
en: 'Connection error. Please check your internet connection.',
|
|
130
|
-
es: 'Error de conexión. Verifica tu conexión a internet.',
|
|
131
|
-
},
|
|
132
|
-
VALIDATION_TIMEOUT: {
|
|
133
|
-
en: 'Validation took too long. Please try again.',
|
|
134
|
-
es: 'La validación tardó demasiado. Intenta nuevamente.',
|
|
135
|
-
},
|
|
136
|
-
LIVENESS_CHECK_FAILED: {
|
|
137
|
-
en: 'Liveness verification failed. Please try again.',
|
|
138
|
-
es: 'La verificación de presencia vital falló. Por favor intenta de nuevo.',
|
|
139
|
-
},
|
|
140
|
-
UNKNOWN_ERROR: {
|
|
141
|
-
en: 'An unexpected error occurred. Please try again.',
|
|
142
|
-
es: 'Ocurrió un error inesperado. Por favor intenta de nuevo.',
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
const message = messages[code] || messages.UNKNOWN_ERROR;
|
|
146
|
-
return language === 'es' || language === 'es-AR' ? message.es : message.en;
|
|
147
|
-
};
|
|
148
|
-
if (error.message === 'NETWORK_ERROR' || (error.message && error.message.toLowerCase().includes('network'))) {
|
|
117
|
+
let errorMessage = strings.errors.unknownError || 'An unexpected error occurred.';
|
|
118
|
+
if (error?.message === 'NETWORK_ERROR' || (error?.message && error.message.toLowerCase().includes('network'))) {
|
|
149
119
|
errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.NETWORK_ERROR;
|
|
120
|
+
errorMessage = strings.errors.networkError || 'Network error. Please check your connection.';
|
|
150
121
|
}
|
|
151
|
-
else if (error
|
|
122
|
+
else if (error?.message && error.message.toLowerCase().includes('timeout')) {
|
|
152
123
|
errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.VALIDATION_TIMEOUT;
|
|
124
|
+
errorMessage = strings.errors.timeout || 'Timeout. Please try again.';
|
|
153
125
|
}
|
|
154
|
-
else if (error
|
|
126
|
+
else if (error?.message && error.message.toLowerCase().includes('liveness')) {
|
|
155
127
|
errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.LIVENESS_CHECK_FAILED;
|
|
128
|
+
errorMessage = strings.errors.livenessCheckFailed || 'Liveness check failed. Please try again.';
|
|
156
129
|
}
|
|
157
130
|
const biometricError = {
|
|
158
131
|
name: 'BiometricError',
|
|
159
|
-
message:
|
|
132
|
+
message: errorMessage,
|
|
160
133
|
code: errorCode,
|
|
161
134
|
};
|
|
162
135
|
onError(biometricError);
|
|
163
136
|
}
|
|
164
|
-
}, [validateWithBackend, onComplete, onError]);
|
|
137
|
+
}, [validateWithBackend, onComplete, onError, strings, language]);
|
|
165
138
|
const handleVideoCancel = (0, react_1.useCallback)(() => {
|
|
166
139
|
if (onCancel) {
|
|
167
140
|
onCancel();
|
|
@@ -171,13 +144,13 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
171
144
|
return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }] },
|
|
172
145
|
react_1.default.createElement(react_native_1.View, { style: styles.loadingContainer },
|
|
173
146
|
react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: theme?.primaryColor || '#4f46e5' }),
|
|
174
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.loadingText, { color: theme?.textColor || '#1e1b4b' }] }, strings.liveness.processing ||
|
|
175
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }] },
|
|
147
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.loadingText, { color: theme?.textColor || '#1e1b4b' }] }, strings.liveness.processing || strings.validation.checkingLiveness || 'Processing...'),
|
|
148
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }] }, strings.validation.almostDone || strings.common.loading || 'This may take a few seconds'))));
|
|
176
149
|
}
|
|
177
150
|
if (validationError) {
|
|
178
151
|
return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }] },
|
|
179
152
|
react_1.default.createElement(react_native_1.View, { style: styles.errorContainer },
|
|
180
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.errorTitle, { color: theme?.errorColor || '#EF4444' }] },
|
|
153
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.errorTitle, { color: theme?.errorColor || '#EF4444' }] }, strings.errors.unknownError || strings.validation.title || 'Error'),
|
|
181
154
|
react_1.default.createElement(react_native_1.Text, { style: [styles.errorText, { color: theme?.textColor || '#1e1b4b' }] }, validationError))));
|
|
182
155
|
}
|
|
183
156
|
return (react_1.default.createElement(VideoRecorder_1.VideoRecorder, { theme: theme, language: language, duration: 8000, smartMode: true, onComplete: handleVideoComplete, onCancel: handleVideoCancel }));
|
package/package.json
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* Error Screen Component
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import React from 'react';
|
|
5
|
+
import React, { useEffect } from 'react';
|
|
6
6
|
import {
|
|
7
7
|
View,
|
|
8
8
|
Text,
|
|
9
9
|
StyleSheet,
|
|
10
10
|
TouchableOpacity,
|
|
11
11
|
} from 'react-native';
|
|
12
|
-
import { BiometricError, ThemeConfig, SupportedLanguage } from '@hexar/biometric-identity-sdk-core';
|
|
12
|
+
import { BiometricError, ThemeConfig, SupportedLanguage, getStrings, setLanguage } from '@hexar/biometric-identity-sdk-core';
|
|
13
13
|
|
|
14
14
|
export interface ErrorScreenProps {
|
|
15
15
|
error: BiometricError;
|
|
@@ -22,44 +22,43 @@ export interface ErrorScreenProps {
|
|
|
22
22
|
export const ErrorScreen: React.FC<ErrorScreenProps> = ({
|
|
23
23
|
error,
|
|
24
24
|
theme,
|
|
25
|
-
language
|
|
25
|
+
language,
|
|
26
26
|
onRetry,
|
|
27
27
|
onClose,
|
|
28
28
|
}) => {
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (language) {
|
|
31
|
+
setLanguage(language);
|
|
32
|
+
}
|
|
33
|
+
}, [language]);
|
|
34
|
+
|
|
35
|
+
const strings = getStrings();
|
|
36
|
+
|
|
29
37
|
const getErrorMessage = () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
en: 'Image quality is insufficient. Please avoid glare, blur, and ensure good lighting.',
|
|
41
|
-
es: 'La calidad de la imagen es insuficiente. Evita brillos, desenfoque y asegura buena iluminación.',
|
|
42
|
-
},
|
|
43
|
-
LIVENESS_CHECK_FAILED: {
|
|
44
|
-
en: 'Liveness verification failed. Please record a new video following the instructions.',
|
|
45
|
-
es: 'La verificación de presencia vital falló. Por favor graba un nuevo video siguiendo las instrucciones.',
|
|
46
|
-
},
|
|
47
|
-
FACE_MATCH_FAILED: {
|
|
48
|
-
en: 'The face in the video does not match the document photo.',
|
|
49
|
-
es: 'El rostro en el video no coincide con la foto del documento.',
|
|
50
|
-
},
|
|
51
|
-
DOCUMENT_TAMPERED: {
|
|
52
|
-
en: 'Document appears to be tampered or altered.',
|
|
53
|
-
es: 'El documento parece estar adulterado o alterado.',
|
|
54
|
-
},
|
|
38
|
+
if (error.message) {
|
|
39
|
+
return error.message;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const getCameraPermissionMessage = () => {
|
|
43
|
+
const permissionError = strings.errors.cameraPermissionDenied;
|
|
44
|
+
if (typeof permissionError === 'object' && permissionError?.message) {
|
|
45
|
+
return permissionError.message;
|
|
46
|
+
}
|
|
47
|
+
return typeof permissionError === 'string' ? permissionError : 'Camera permission is required.';
|
|
55
48
|
};
|
|
56
49
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
const errorMessageMap: Record<string, string> = {
|
|
51
|
+
CAMERA_PERMISSION_DENIED: getCameraPermissionMessage(),
|
|
52
|
+
FACE_NOT_DETECTED: strings.errors.faceNotDetected || 'No face detected.',
|
|
53
|
+
POOR_IMAGE_QUALITY: strings.errors.poorImageQuality || 'Image quality is insufficient.',
|
|
54
|
+
LIVENESS_CHECK_FAILED: strings.errors.livenessCheckFailed || 'Liveness verification failed.',
|
|
55
|
+
FACE_MATCH_FAILED: strings.errors.faceMatchFailed || 'Face match failed.',
|
|
56
|
+
DOCUMENT_TAMPERED: strings.errors.documentTampered || 'Document appears to be tampered.',
|
|
57
|
+
NETWORK_ERROR: strings.errors.networkError || 'Network error.',
|
|
58
|
+
VALIDATION_TIMEOUT: strings.errors.timeout || 'Validation timeout.',
|
|
60
59
|
};
|
|
61
60
|
|
|
62
|
-
return
|
|
61
|
+
return errorMessageMap[error.code] || strings.errors.unknownError || 'An unexpected error occurred.';
|
|
63
62
|
};
|
|
64
63
|
|
|
65
64
|
return (
|
|
@@ -77,7 +76,7 @@ export const ErrorScreen: React.FC<ErrorScreenProps> = ({
|
|
|
77
76
|
|
|
78
77
|
{/* Error Title */}
|
|
79
78
|
<Text style={[styles.title, { color: theme?.textColor || '#000000' }]}>
|
|
80
|
-
{
|
|
79
|
+
{strings.result.failure.title || strings.errors.unknownError || 'Verification Error'}
|
|
81
80
|
</Text>
|
|
82
81
|
|
|
83
82
|
{/* Error Message */}
|
|
@@ -88,7 +87,7 @@ export const ErrorScreen: React.FC<ErrorScreenProps> = ({
|
|
|
88
87
|
{/* Error Code */}
|
|
89
88
|
<View style={styles.codeContainer}>
|
|
90
89
|
<Text style={styles.codeLabel}>
|
|
91
|
-
{
|
|
90
|
+
{strings.common.error || 'Error Code:'}
|
|
92
91
|
</Text>
|
|
93
92
|
<Text style={styles.codeText}>{error.code}</Text>
|
|
94
93
|
</View>
|
|
@@ -104,7 +103,7 @@ export const ErrorScreen: React.FC<ErrorScreenProps> = ({
|
|
|
104
103
|
onPress={onRetry}
|
|
105
104
|
>
|
|
106
105
|
<Text style={styles.buttonText}>
|
|
107
|
-
{
|
|
106
|
+
{strings.common.retry || 'Try Again'}
|
|
108
107
|
</Text>
|
|
109
108
|
</TouchableOpacity>
|
|
110
109
|
|
|
@@ -113,7 +112,7 @@ export const ErrorScreen: React.FC<ErrorScreenProps> = ({
|
|
|
113
112
|
onPress={onClose}
|
|
114
113
|
>
|
|
115
114
|
<Text style={[styles.buttonText, { color: theme?.textColor || '#000000' }]}>
|
|
116
|
-
{
|
|
115
|
+
{strings.common.close || 'Close'}
|
|
117
116
|
</Text>
|
|
118
117
|
</TouchableOpacity>
|
|
119
118
|
</View>
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from 'react-native';
|
|
9
9
|
import { VideoRecorder, VideoRecordingResult } from './VideoRecorder';
|
|
10
10
|
import { ThemeConfig, SupportedLanguage, getStrings, setLanguage, logger, BiometricError, BiometricErrorCode } from '@hexar/biometric-identity-sdk-core';
|
|
11
|
-
import {
|
|
11
|
+
import { useBiometricSDK } from '../hooks/useBiometricSDK';
|
|
12
12
|
|
|
13
13
|
export interface ProfilePictureValidationResult {
|
|
14
14
|
isValid: boolean;
|
|
@@ -33,6 +33,12 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
33
33
|
theme,
|
|
34
34
|
language,
|
|
35
35
|
}) => {
|
|
36
|
+
const {
|
|
37
|
+
sdk,
|
|
38
|
+
isInitialized,
|
|
39
|
+
isUsingBackend,
|
|
40
|
+
} = useBiometricSDK();
|
|
41
|
+
|
|
36
42
|
const [isValidating, setIsValidating] = useState(false);
|
|
37
43
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
38
44
|
|
|
@@ -46,9 +52,12 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
46
52
|
|
|
47
53
|
const validateWithBackend = useCallback(async (videoResult: VideoRecordingResult): Promise<ProfilePictureValidationResult> => {
|
|
48
54
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
if (!isInitialized) {
|
|
56
|
+
logger.error('SDK not initialized');
|
|
57
|
+
throw new Error('NETWORK_ERROR');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!isUsingBackend) {
|
|
52
61
|
logger.error('Backend not available - SDK not configured with backend');
|
|
53
62
|
throw new Error('NETWORK_ERROR');
|
|
54
63
|
}
|
|
@@ -59,60 +68,38 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
59
68
|
throw new Error('VALIDATION_ERROR');
|
|
60
69
|
}
|
|
61
70
|
|
|
62
|
-
const backendClient = (sdk as any).backendClient;
|
|
63
|
-
const config = backendClient.config;
|
|
64
|
-
|
|
65
|
-
const requestBody = {
|
|
66
|
-
session_id: sessionId,
|
|
67
|
-
video_frames: videoResult.frames,
|
|
68
|
-
video_duration_ms: videoResult.duration,
|
|
69
|
-
challenges_completed: videoResult.challengesCompleted || [],
|
|
70
|
-
};
|
|
71
|
-
|
|
72
71
|
logger.info('Validating profile picture with backend', {
|
|
73
72
|
sessionId,
|
|
74
73
|
framesCount: videoResult.frames.length,
|
|
75
74
|
duration: videoResult.duration,
|
|
76
|
-
|
|
75
|
+
isUsingBackend,
|
|
77
76
|
});
|
|
78
77
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'Content-Type': 'application/json',
|
|
85
|
-
'X-API-Key': config.apiKey,
|
|
86
|
-
},
|
|
87
|
-
body: JSON.stringify(requestBody),
|
|
78
|
+
const result = await (sdk as any).validateProfilePicture({
|
|
79
|
+
sessionId,
|
|
80
|
+
videoFrames: videoResult.frames,
|
|
81
|
+
videoDurationMs: videoResult.duration,
|
|
82
|
+
challengesCompleted: videoResult.challengesCompleted || [],
|
|
88
83
|
});
|
|
89
84
|
|
|
90
|
-
if (!response.ok) {
|
|
91
|
-
const errorData = await response.json().catch(() => ({}));
|
|
92
|
-
logger.error('Backend validation failed', { status: response.status, error: errorData });
|
|
93
|
-
throw new Error('VALIDATION_ERROR');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const data = await response.json();
|
|
97
|
-
|
|
98
85
|
logger.info('Profile picture validation result', {
|
|
99
|
-
isValid:
|
|
100
|
-
livenessScore:
|
|
101
|
-
faceDetected:
|
|
86
|
+
isValid: result.isValid,
|
|
87
|
+
livenessScore: result.livenessScore,
|
|
88
|
+
faceDetected: result.faceDetected,
|
|
102
89
|
});
|
|
103
90
|
|
|
104
91
|
return {
|
|
105
|
-
isValid:
|
|
106
|
-
profilePicture:
|
|
107
|
-
livenessScore:
|
|
108
|
-
faceDetected:
|
|
109
|
-
warnings:
|
|
92
|
+
isValid: result.isValid,
|
|
93
|
+
profilePicture: result.profilePicture,
|
|
94
|
+
livenessScore: result.livenessScore,
|
|
95
|
+
faceDetected: result.faceDetected,
|
|
96
|
+
warnings: result.warnings || [],
|
|
110
97
|
};
|
|
111
98
|
} catch (error: any) {
|
|
112
99
|
logger.error('Profile picture validation error', error);
|
|
113
100
|
throw error;
|
|
114
101
|
}
|
|
115
|
-
}, []);
|
|
102
|
+
}, [isInitialized, isUsingBackend, sdk]);
|
|
116
103
|
|
|
117
104
|
const handleVideoComplete = useCallback(async (videoResult: VideoRecordingResult) => {
|
|
118
105
|
setIsValidating(true);
|
|
@@ -122,7 +109,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
122
109
|
const result = await validateWithBackend(videoResult);
|
|
123
110
|
|
|
124
111
|
if (!result.isValid) {
|
|
125
|
-
setValidationError(
|
|
112
|
+
setValidationError(strings.errors.livenessCheckFailed || 'Liveness check failed');
|
|
126
113
|
setIsValidating(false);
|
|
127
114
|
return;
|
|
128
115
|
}
|
|
@@ -131,48 +118,34 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
131
118
|
onComplete(result);
|
|
132
119
|
} catch (error: any) {
|
|
133
120
|
setIsValidating(false);
|
|
134
|
-
let errorCode = BiometricErrorCode.UNKNOWN_ERROR;
|
|
135
121
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
es: 'Error de conexión. Verifica tu conexión a internet.',
|
|
141
|
-
},
|
|
142
|
-
VALIDATION_TIMEOUT: {
|
|
143
|
-
en: 'Validation took too long. Please try again.',
|
|
144
|
-
es: 'La validación tardó demasiado. Intenta nuevamente.',
|
|
145
|
-
},
|
|
146
|
-
LIVENESS_CHECK_FAILED: {
|
|
147
|
-
en: 'Liveness verification failed. Please try again.',
|
|
148
|
-
es: 'La verificación de presencia vital falló. Por favor intenta de nuevo.',
|
|
149
|
-
},
|
|
150
|
-
UNKNOWN_ERROR: {
|
|
151
|
-
en: 'An unexpected error occurred. Please try again.',
|
|
152
|
-
es: 'Ocurrió un error inesperado. Por favor intenta de nuevo.',
|
|
153
|
-
},
|
|
154
|
-
};
|
|
122
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
123
|
+
onError(error as BiometricError);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
155
126
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (error.message === 'NETWORK_ERROR' || (error.message && error.message.toLowerCase().includes('network'))) {
|
|
127
|
+
let errorCode = BiometricErrorCode.UNKNOWN_ERROR;
|
|
128
|
+
let errorMessage = strings.errors.unknownError || 'An unexpected error occurred.';
|
|
129
|
+
|
|
130
|
+
if (error?.message === 'NETWORK_ERROR' || (error?.message && error.message.toLowerCase().includes('network'))) {
|
|
161
131
|
errorCode = BiometricErrorCode.NETWORK_ERROR;
|
|
162
|
-
|
|
132
|
+
errorMessage = strings.errors.networkError || 'Network error. Please check your connection.';
|
|
133
|
+
} else if (error?.message && error.message.toLowerCase().includes('timeout')) {
|
|
163
134
|
errorCode = BiometricErrorCode.VALIDATION_TIMEOUT;
|
|
164
|
-
|
|
135
|
+
errorMessage = strings.errors.timeout || 'Timeout. Please try again.';
|
|
136
|
+
} else if (error?.message && error.message.toLowerCase().includes('liveness')) {
|
|
165
137
|
errorCode = BiometricErrorCode.LIVENESS_CHECK_FAILED;
|
|
138
|
+
errorMessage = strings.errors.livenessCheckFailed || 'Liveness check failed. Please try again.';
|
|
166
139
|
}
|
|
167
140
|
|
|
168
141
|
const biometricError: BiometricError = {
|
|
169
142
|
name: 'BiometricError',
|
|
170
|
-
message:
|
|
143
|
+
message: errorMessage,
|
|
171
144
|
code: errorCode,
|
|
172
145
|
} as BiometricError;
|
|
173
146
|
onError(biometricError);
|
|
174
147
|
}
|
|
175
|
-
}, [validateWithBackend, onComplete, onError]);
|
|
148
|
+
}, [validateWithBackend, onComplete, onError, strings, language]);
|
|
176
149
|
|
|
177
150
|
const handleVideoCancel = useCallback(() => {
|
|
178
151
|
if (onCancel) {
|
|
@@ -186,10 +159,10 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
186
159
|
<View style={styles.loadingContainer}>
|
|
187
160
|
<ActivityIndicator size="large" color={theme?.primaryColor || '#4f46e5'} />
|
|
188
161
|
<Text style={[styles.loadingText, { color: theme?.textColor || '#1e1b4b' }]}>
|
|
189
|
-
{strings.liveness.processing ||
|
|
162
|
+
{strings.liveness.processing || strings.validation.checkingLiveness || 'Processing...'}
|
|
190
163
|
</Text>
|
|
191
164
|
<Text style={[styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }]}>
|
|
192
|
-
|
|
165
|
+
{strings.validation.almostDone || strings.common.loading || 'This may take a few seconds'}
|
|
193
166
|
</Text>
|
|
194
167
|
</View>
|
|
195
168
|
</SafeAreaView>
|
|
@@ -201,7 +174,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
201
174
|
<SafeAreaView style={[styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }]}>
|
|
202
175
|
<View style={styles.errorContainer}>
|
|
203
176
|
<Text style={[styles.errorTitle, { color: theme?.errorColor || '#EF4444' }]}>
|
|
204
|
-
|
|
177
|
+
{strings.errors.unknownError || strings.validation.title || 'Error'}
|
|
205
178
|
</Text>
|
|
206
179
|
<Text style={[styles.errorText, { color: theme?.textColor || '#1e1b4b' }]}>
|
|
207
180
|
{validationError}
|