@hexar/biometric-identity-sdk-react-native 1.1.1 → 1.1.3
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 +41 -39
- package/package.json +1 -1
- package/src/components/ErrorScreen.tsx +35 -36
- package/src/components/ProfilePictureCapture.tsx +52 -42
|
@@ -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,CAsLtE,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, sessionId, } = (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,23 +51,38 @@ 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
|
}
|
|
58
|
-
|
|
62
|
+
let sessionId = videoResult.sessionId || sdk.getSessionId();
|
|
63
|
+
if (!sessionId) {
|
|
64
|
+
biometric_identity_sdk_core_1.logger.info('No session ID available, generating challenge to create one');
|
|
65
|
+
try {
|
|
66
|
+
const challengeResponse = await sdk.generateLivenessChallenge('active');
|
|
67
|
+
sessionId = challengeResponse.session_id;
|
|
68
|
+
biometric_identity_sdk_core_1.logger.info('Session ID generated', { sessionId });
|
|
69
|
+
}
|
|
70
|
+
catch (challengeError) {
|
|
71
|
+
biometric_identity_sdk_core_1.logger.error('Failed to generate challenge for session ID', challengeError);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
59
74
|
if (!sessionId) {
|
|
60
|
-
biometric_identity_sdk_core_1.logger.error('Session ID not available');
|
|
75
|
+
biometric_identity_sdk_core_1.logger.error('Session ID not available after attempting to generate');
|
|
61
76
|
throw new Error('VALIDATION_ERROR');
|
|
62
77
|
}
|
|
63
78
|
biometric_identity_sdk_core_1.logger.info('Validating profile picture with backend', {
|
|
64
79
|
sessionId,
|
|
65
80
|
framesCount: videoResult.frames.length,
|
|
66
81
|
duration: videoResult.duration,
|
|
82
|
+
isUsingBackend,
|
|
67
83
|
});
|
|
68
84
|
const result = await sdk.validateProfilePicture({
|
|
69
|
-
sessionId,
|
|
85
|
+
sessionId: sessionId,
|
|
70
86
|
videoFrames: videoResult.frames,
|
|
71
87
|
videoDurationMs: videoResult.duration,
|
|
72
88
|
challengesCompleted: videoResult.challengesCompleted || [],
|
|
@@ -88,14 +104,14 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
88
104
|
biometric_identity_sdk_core_1.logger.error('Profile picture validation error', error);
|
|
89
105
|
throw error;
|
|
90
106
|
}
|
|
91
|
-
}, []);
|
|
107
|
+
}, [isInitialized, isUsingBackend, sdk]);
|
|
92
108
|
const handleVideoComplete = (0, react_1.useCallback)(async (videoResult) => {
|
|
93
109
|
setIsValidating(true);
|
|
94
110
|
setValidationError(null);
|
|
95
111
|
try {
|
|
96
112
|
const result = await validateWithBackend(videoResult);
|
|
97
113
|
if (!result.isValid) {
|
|
98
|
-
setValidationError(
|
|
114
|
+
setValidationError(strings.errors.livenessCheckFailed || 'Liveness check failed');
|
|
99
115
|
setIsValidating(false);
|
|
100
116
|
return;
|
|
101
117
|
}
|
|
@@ -104,46 +120,32 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
104
120
|
}
|
|
105
121
|
catch (error) {
|
|
106
122
|
setIsValidating(false);
|
|
123
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
124
|
+
onError(error);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
107
127
|
let errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.UNKNOWN_ERROR;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
NETWORK_ERROR: {
|
|
111
|
-
en: 'Connection error. Please check your internet connection.',
|
|
112
|
-
es: 'Error de conexión. Verifica tu conexión a internet.',
|
|
113
|
-
},
|
|
114
|
-
VALIDATION_TIMEOUT: {
|
|
115
|
-
en: 'Validation took too long. Please try again.',
|
|
116
|
-
es: 'La validación tardó demasiado. Intenta nuevamente.',
|
|
117
|
-
},
|
|
118
|
-
LIVENESS_CHECK_FAILED: {
|
|
119
|
-
en: 'Liveness verification failed. Please try again.',
|
|
120
|
-
es: 'La verificación de presencia vital falló. Por favor intenta de nuevo.',
|
|
121
|
-
},
|
|
122
|
-
UNKNOWN_ERROR: {
|
|
123
|
-
en: 'An unexpected error occurred. Please try again.',
|
|
124
|
-
es: 'Ocurrió un error inesperado. Por favor intenta de nuevo.',
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
const message = messages[code] || messages.UNKNOWN_ERROR;
|
|
128
|
-
return language === 'es' || language === 'es-AR' ? message.es : message.en;
|
|
129
|
-
};
|
|
130
|
-
if (error.message === 'NETWORK_ERROR' || (error.message && error.message.toLowerCase().includes('network'))) {
|
|
128
|
+
let errorMessage = strings.errors.unknownError || 'An unexpected error occurred.';
|
|
129
|
+
if (error?.message === 'NETWORK_ERROR' || (error?.message && error.message.toLowerCase().includes('network'))) {
|
|
131
130
|
errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.NETWORK_ERROR;
|
|
131
|
+
errorMessage = strings.errors.networkError || 'Network error. Please check your connection.';
|
|
132
132
|
}
|
|
133
|
-
else if (error
|
|
133
|
+
else if (error?.message && error.message.toLowerCase().includes('timeout')) {
|
|
134
134
|
errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.VALIDATION_TIMEOUT;
|
|
135
|
+
errorMessage = strings.errors.timeout || 'Timeout. Please try again.';
|
|
135
136
|
}
|
|
136
|
-
else if (error
|
|
137
|
+
else if (error?.message && error.message.toLowerCase().includes('liveness')) {
|
|
137
138
|
errorCode = biometric_identity_sdk_core_1.BiometricErrorCode.LIVENESS_CHECK_FAILED;
|
|
139
|
+
errorMessage = strings.errors.livenessCheckFailed || 'Liveness check failed. Please try again.';
|
|
138
140
|
}
|
|
139
141
|
const biometricError = {
|
|
140
142
|
name: 'BiometricError',
|
|
141
|
-
message:
|
|
143
|
+
message: errorMessage,
|
|
142
144
|
code: errorCode,
|
|
143
145
|
};
|
|
144
146
|
onError(biometricError);
|
|
145
147
|
}
|
|
146
|
-
}, [validateWithBackend, onComplete, onError]);
|
|
148
|
+
}, [validateWithBackend, onComplete, onError, strings, language]);
|
|
147
149
|
const handleVideoCancel = (0, react_1.useCallback)(() => {
|
|
148
150
|
if (onCancel) {
|
|
149
151
|
onCancel();
|
|
@@ -153,16 +155,16 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
153
155
|
return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }] },
|
|
154
156
|
react_1.default.createElement(react_native_1.View, { style: styles.loadingContainer },
|
|
155
157
|
react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: theme?.primaryColor || '#4f46e5' }),
|
|
156
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.loadingText, { color: theme?.textColor || '#1e1b4b' }] }, strings.liveness.processing ||
|
|
157
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }] },
|
|
158
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.loadingText, { color: theme?.textColor || '#1e1b4b' }] }, strings.liveness.processing || strings.validation.checkingLiveness || 'Processing...'),
|
|
159
|
+
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'))));
|
|
158
160
|
}
|
|
159
161
|
if (validationError) {
|
|
160
162
|
return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }] },
|
|
161
163
|
react_1.default.createElement(react_native_1.View, { style: styles.errorContainer },
|
|
162
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.errorTitle, { color: theme?.errorColor || '#EF4444' }] },
|
|
164
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.errorTitle, { color: theme?.errorColor || '#EF4444' }] }, strings.errors.unknownError || strings.validation.title || 'Error'),
|
|
163
165
|
react_1.default.createElement(react_native_1.Text, { style: [styles.errorText, { color: theme?.textColor || '#1e1b4b' }] }, validationError))));
|
|
164
166
|
}
|
|
165
|
-
return (react_1.default.createElement(VideoRecorder_1.VideoRecorder, { theme: theme, language: language, duration: 8000, smartMode: true, onComplete: handleVideoComplete, onCancel: handleVideoCancel }));
|
|
167
|
+
return (react_1.default.createElement(VideoRecorder_1.VideoRecorder, { theme: theme, language: language, duration: 8000, smartMode: true, sessionId: sdk.getSessionId() || undefined, onComplete: handleVideoComplete, onCancel: handleVideoCancel }));
|
|
166
168
|
};
|
|
167
169
|
exports.ProfilePictureCapture = ProfilePictureCapture;
|
|
168
170
|
const styles = react_native_1.StyleSheet.create({
|
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,13 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
33
33
|
theme,
|
|
34
34
|
language,
|
|
35
35
|
}) => {
|
|
36
|
+
const {
|
|
37
|
+
sdk,
|
|
38
|
+
isInitialized,
|
|
39
|
+
isUsingBackend,
|
|
40
|
+
sessionId,
|
|
41
|
+
} = useBiometricSDK();
|
|
42
|
+
|
|
36
43
|
const [isValidating, setIsValidating] = useState(false);
|
|
37
44
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
38
45
|
|
|
@@ -46,16 +53,31 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
46
53
|
|
|
47
54
|
const validateWithBackend = useCallback(async (videoResult: VideoRecordingResult): Promise<ProfilePictureValidationResult> => {
|
|
48
55
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
if (!isInitialized) {
|
|
57
|
+
logger.error('SDK not initialized');
|
|
58
|
+
throw new Error('NETWORK_ERROR');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!isUsingBackend) {
|
|
52
62
|
logger.error('Backend not available - SDK not configured with backend');
|
|
53
63
|
throw new Error('NETWORK_ERROR');
|
|
54
64
|
}
|
|
55
65
|
|
|
56
|
-
|
|
66
|
+
let sessionId = videoResult.sessionId || sdk.getSessionId();
|
|
67
|
+
|
|
68
|
+
if (!sessionId) {
|
|
69
|
+
logger.info('No session ID available, generating challenge to create one');
|
|
70
|
+
try {
|
|
71
|
+
const challengeResponse = await sdk.generateLivenessChallenge('active');
|
|
72
|
+
sessionId = challengeResponse.session_id;
|
|
73
|
+
logger.info('Session ID generated', { sessionId });
|
|
74
|
+
} catch (challengeError) {
|
|
75
|
+
logger.error('Failed to generate challenge for session ID', challengeError);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
57
79
|
if (!sessionId) {
|
|
58
|
-
logger.error('Session ID not available');
|
|
80
|
+
logger.error('Session ID not available after attempting to generate');
|
|
59
81
|
throw new Error('VALIDATION_ERROR');
|
|
60
82
|
}
|
|
61
83
|
|
|
@@ -63,10 +85,11 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
63
85
|
sessionId,
|
|
64
86
|
framesCount: videoResult.frames.length,
|
|
65
87
|
duration: videoResult.duration,
|
|
88
|
+
isUsingBackend,
|
|
66
89
|
});
|
|
67
90
|
|
|
68
91
|
const result = await (sdk as any).validateProfilePicture({
|
|
69
|
-
sessionId
|
|
92
|
+
sessionId: sessionId!,
|
|
70
93
|
videoFrames: videoResult.frames,
|
|
71
94
|
videoDurationMs: videoResult.duration,
|
|
72
95
|
challengesCompleted: videoResult.challengesCompleted || [],
|
|
@@ -89,7 +112,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
89
112
|
logger.error('Profile picture validation error', error);
|
|
90
113
|
throw error;
|
|
91
114
|
}
|
|
92
|
-
}, []);
|
|
115
|
+
}, [isInitialized, isUsingBackend, sdk]);
|
|
93
116
|
|
|
94
117
|
const handleVideoComplete = useCallback(async (videoResult: VideoRecordingResult) => {
|
|
95
118
|
setIsValidating(true);
|
|
@@ -99,7 +122,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
99
122
|
const result = await validateWithBackend(videoResult);
|
|
100
123
|
|
|
101
124
|
if (!result.isValid) {
|
|
102
|
-
setValidationError(
|
|
125
|
+
setValidationError(strings.errors.livenessCheckFailed || 'Liveness check failed');
|
|
103
126
|
setIsValidating(false);
|
|
104
127
|
return;
|
|
105
128
|
}
|
|
@@ -108,48 +131,34 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
108
131
|
onComplete(result);
|
|
109
132
|
} catch (error: any) {
|
|
110
133
|
setIsValidating(false);
|
|
111
|
-
let errorCode = BiometricErrorCode.UNKNOWN_ERROR;
|
|
112
134
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
LIVENESS_CHECK_FAILED: {
|
|
124
|
-
en: 'Liveness verification failed. Please try again.',
|
|
125
|
-
es: 'La verificación de presencia vital falló. Por favor intenta de nuevo.',
|
|
126
|
-
},
|
|
127
|
-
UNKNOWN_ERROR: {
|
|
128
|
-
en: 'An unexpected error occurred. Please try again.',
|
|
129
|
-
es: 'Ocurrió un error inesperado. Por favor intenta de nuevo.',
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const message = messages[code] || messages.UNKNOWN_ERROR;
|
|
134
|
-
return language === 'es' || language === 'es-AR' ? message.es : message.en;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
if (error.message === 'NETWORK_ERROR' || (error.message && error.message.toLowerCase().includes('network'))) {
|
|
135
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
136
|
+
onError(error as BiometricError);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let errorCode = BiometricErrorCode.UNKNOWN_ERROR;
|
|
141
|
+
let errorMessage = strings.errors.unknownError || 'An unexpected error occurred.';
|
|
142
|
+
|
|
143
|
+
if (error?.message === 'NETWORK_ERROR' || (error?.message && error.message.toLowerCase().includes('network'))) {
|
|
138
144
|
errorCode = BiometricErrorCode.NETWORK_ERROR;
|
|
139
|
-
|
|
145
|
+
errorMessage = strings.errors.networkError || 'Network error. Please check your connection.';
|
|
146
|
+
} else if (error?.message && error.message.toLowerCase().includes('timeout')) {
|
|
140
147
|
errorCode = BiometricErrorCode.VALIDATION_TIMEOUT;
|
|
141
|
-
|
|
148
|
+
errorMessage = strings.errors.timeout || 'Timeout. Please try again.';
|
|
149
|
+
} else if (error?.message && error.message.toLowerCase().includes('liveness')) {
|
|
142
150
|
errorCode = BiometricErrorCode.LIVENESS_CHECK_FAILED;
|
|
151
|
+
errorMessage = strings.errors.livenessCheckFailed || 'Liveness check failed. Please try again.';
|
|
143
152
|
}
|
|
144
153
|
|
|
145
154
|
const biometricError: BiometricError = {
|
|
146
155
|
name: 'BiometricError',
|
|
147
|
-
message:
|
|
156
|
+
message: errorMessage,
|
|
148
157
|
code: errorCode,
|
|
149
158
|
} as BiometricError;
|
|
150
159
|
onError(biometricError);
|
|
151
160
|
}
|
|
152
|
-
}, [validateWithBackend, onComplete, onError]);
|
|
161
|
+
}, [validateWithBackend, onComplete, onError, strings, language]);
|
|
153
162
|
|
|
154
163
|
const handleVideoCancel = useCallback(() => {
|
|
155
164
|
if (onCancel) {
|
|
@@ -163,10 +172,10 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
163
172
|
<View style={styles.loadingContainer}>
|
|
164
173
|
<ActivityIndicator size="large" color={theme?.primaryColor || '#4f46e5'} />
|
|
165
174
|
<Text style={[styles.loadingText, { color: theme?.textColor || '#1e1b4b' }]}>
|
|
166
|
-
{strings.liveness.processing ||
|
|
175
|
+
{strings.liveness.processing || strings.validation.checkingLiveness || 'Processing...'}
|
|
167
176
|
</Text>
|
|
168
177
|
<Text style={[styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }]}>
|
|
169
|
-
|
|
178
|
+
{strings.validation.almostDone || strings.common.loading || 'This may take a few seconds'}
|
|
170
179
|
</Text>
|
|
171
180
|
</View>
|
|
172
181
|
</SafeAreaView>
|
|
@@ -178,7 +187,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
178
187
|
<SafeAreaView style={[styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }]}>
|
|
179
188
|
<View style={styles.errorContainer}>
|
|
180
189
|
<Text style={[styles.errorTitle, { color: theme?.errorColor || '#EF4444' }]}>
|
|
181
|
-
|
|
190
|
+
{strings.errors.unknownError || strings.validation.title || 'Error'}
|
|
182
191
|
</Text>
|
|
183
192
|
<Text style={[styles.errorText, { color: theme?.textColor || '#1e1b4b' }]}>
|
|
184
193
|
{validationError}
|
|
@@ -194,6 +203,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
194
203
|
language={language}
|
|
195
204
|
duration={8000}
|
|
196
205
|
smartMode={true}
|
|
206
|
+
sessionId={sdk.getSessionId() || undefined}
|
|
197
207
|
onComplete={handleVideoComplete}
|
|
198
208
|
onCancel={handleVideoCancel}
|
|
199
209
|
/>
|