@hexar/biometric-identity-sdk-react-native 1.2.0 → 1.3.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/assets/images/face-biometry.png +0 -0
- package/dist/components/BiometricIdentityFlow.d.ts.map +1 -1
- package/dist/components/BiometricIdentityFlow.js +18 -2
- package/dist/components/FacePositioningGuide.d.ts +22 -0
- package/dist/components/FacePositioningGuide.d.ts.map +1 -0
- package/dist/components/FacePositioningGuide.js +139 -0
- package/dist/components/ProfilePictureCapture.d.ts.map +1 -1
- package/dist/components/ProfilePictureCapture.js +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +3 -3
- package/src/assets/images/face-biometry.png +0 -0
- package/src/components/BiometricIdentityFlow.tsx +31 -4
- package/src/components/FacePositioningGuide.tsx +201 -0
- package/src/components/ProfilePictureCapture.tsx +14 -1
- package/src/index.ts +1 -0
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAOL,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,cAAc,EAKd,iBAAiB,EAElB,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAOL,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,cAAc,EAKd,iBAAiB,EAElB,MAAM,oCAAoC,CAAC;AA+B5C,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,OAAO,CAAC,EAAE,SAAS,CAAC;KACrB,CAAC;CACH;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAictE,CAAC;AA+NF,eAAe,qBAAqB,CAAC"}
|
|
@@ -67,11 +67,13 @@ const ValidationProgress_1 = require("./ValidationProgress");
|
|
|
67
67
|
const ResultScreen_1 = require("./ResultScreen");
|
|
68
68
|
const ErrorScreen_1 = require("./ErrorScreen");
|
|
69
69
|
const InstructionsScreen_1 = require("./InstructionsScreen");
|
|
70
|
+
const FacePositioningGuide_1 = require("./FacePositioningGuide");
|
|
70
71
|
const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language, customTranslations, smartLivenessMode = true, routeBack, styles: customStyles, }) => {
|
|
71
72
|
const { sdk, state, isInitialized, isUsingBackend, challenges, uploadFrontID, uploadBackID, storeVideoRecording, fetchChallenges, validateIdentity, reset, } = (0, useBiometricSDK_1.useBiometricSDK)();
|
|
72
73
|
const [showCamera, setShowCamera] = (0, react_1.useState)(false);
|
|
73
74
|
const [cameraMode, setCameraMode] = (0, react_1.useState)('front');
|
|
74
75
|
const [showInstructions, setShowInstructions] = (0, react_1.useState)(true);
|
|
76
|
+
const [showFacePositioningGuide, setShowFacePositioningGuide] = (0, react_1.useState)(false);
|
|
75
77
|
const [currentChallenges, setCurrentChallenges] = (0, react_1.useState)([]);
|
|
76
78
|
const [isLoadingChallenges, setIsLoadingChallenges] = (0, react_1.useState)(false);
|
|
77
79
|
const onValidationCompleteRef = (0, react_1.useRef)(onValidationComplete);
|
|
@@ -117,7 +119,15 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
117
119
|
*/
|
|
118
120
|
const handleCaptureStart = (0, react_1.useCallback)(async (mode) => {
|
|
119
121
|
setCameraMode(mode);
|
|
120
|
-
if (mode === 'video'
|
|
122
|
+
if (mode === 'video') {
|
|
123
|
+
setShowFacePositioningGuide(true);
|
|
124
|
+
// Challenges will be loaded when guide is dismissed and we show camera
|
|
125
|
+
}
|
|
126
|
+
if (mode !== 'video') {
|
|
127
|
+
setShowCamera(true);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (smartLivenessMode && isUsingBackend) {
|
|
121
131
|
setIsLoadingChallenges(true);
|
|
122
132
|
try {
|
|
123
133
|
const challenges = await fetchChallenges('active');
|
|
@@ -186,8 +196,11 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
186
196
|
});
|
|
187
197
|
setCurrentChallenges(translatedChallenges);
|
|
188
198
|
}
|
|
189
|
-
setShowCamera(true);
|
|
190
199
|
}, [smartLivenessMode, isUsingBackend, fetchChallenges, sdk]);
|
|
200
|
+
const handleFacePositioningContinue = (0, react_1.useCallback)(() => {
|
|
201
|
+
setShowFacePositioningGuide(false);
|
|
202
|
+
setShowCamera(true);
|
|
203
|
+
}, []);
|
|
191
204
|
/**
|
|
192
205
|
* Handle capture completion
|
|
193
206
|
*/
|
|
@@ -287,6 +300,9 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
287
300
|
if (showInstructions) {
|
|
288
301
|
return (react_1.default.createElement(InstructionsScreen_1.InstructionsScreen, { theme: theme, language: language, onStart: () => setShowInstructions(false), routeBack: routeBack, styles: customStyles }));
|
|
289
302
|
}
|
|
303
|
+
if (showFacePositioningGuide) {
|
|
304
|
+
return (react_1.default.createElement(FacePositioningGuide_1.FacePositioningGuide, { theme: theme, language: language, onContinue: handleFacePositioningContinue, onCancel: () => setShowFacePositioningGuide(false), styles: customStyles }));
|
|
305
|
+
}
|
|
290
306
|
if (showCamera) {
|
|
291
307
|
if (cameraMode === 'video') {
|
|
292
308
|
return (react_1.default.createElement(VideoRecorder_1.VideoRecorder, { theme: theme, language: language, challenges: currentChallenges, smartMode: smartLivenessMode, sessionId: sdk.getSessionId() || undefined, onComplete: handleCaptureComplete, onCancel: () => setShowCamera(false), onFetchChallenges: async () => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Face Positioning Guide Component
|
|
3
|
+
* Shared view that instructs users to keep their face inside the oval
|
|
4
|
+
* for better approval rate. Used before video capture in both
|
|
5
|
+
* BiometricIdentityFlow and ProfilePictureCapture.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { ViewStyle } from 'react-native';
|
|
9
|
+
import { ThemeConfig, SupportedLanguage } from '@hexar/biometric-identity-sdk-core';
|
|
10
|
+
export interface FacePositioningGuideProps {
|
|
11
|
+
theme?: ThemeConfig;
|
|
12
|
+
language?: SupportedLanguage;
|
|
13
|
+
onContinue: () => void;
|
|
14
|
+
onCancel?: () => void;
|
|
15
|
+
styles?: {
|
|
16
|
+
container?: ViewStyle;
|
|
17
|
+
content?: ViewStyle;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare const FacePositioningGuide: React.FC<FacePositioningGuideProps>;
|
|
21
|
+
export default FacePositioningGuide;
|
|
22
|
+
//# sourceMappingURL=FacePositioningGuide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FacePositioningGuide.d.ts","sourceRoot":"","sources":["../../src/components/FacePositioningGuide.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAOL,SAAS,EAEV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAA2B,MAAM,oCAAoC,CAAC;AAI7G,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,OAAO,CAAC,EAAE,SAAS,CAAC;KACrB,CAAC;CACH;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAwEpE,CAAC;AA+FF,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Face Positioning Guide Component
|
|
4
|
+
* Shared view that instructs users to keep their face inside the oval
|
|
5
|
+
* for better approval rate. Used before video capture in both
|
|
6
|
+
* BiometricIdentityFlow and ProfilePictureCapture.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FacePositioningGuide = void 0;
|
|
13
|
+
const react_1 = __importDefault(require("react"));
|
|
14
|
+
const react_native_1 = require("react-native");
|
|
15
|
+
const biometric_identity_sdk_core_1 = require("@hexar/biometric-identity-sdk-core");
|
|
16
|
+
const FACE_GUIDE_IMAGE = require('../assets/images/face-biometry.png');
|
|
17
|
+
const FacePositioningGuide = ({ theme, language, onContinue, onCancel, styles: customStyles, }) => {
|
|
18
|
+
if (language) {
|
|
19
|
+
(0, biometric_identity_sdk_core_1.setLanguage)(language);
|
|
20
|
+
}
|
|
21
|
+
const strings = (0, biometric_identity_sdk_core_1.getStrings)();
|
|
22
|
+
const liveness = strings.liveness;
|
|
23
|
+
const guide = liveness?.facePositioningGuide;
|
|
24
|
+
const title = guide?.title ?? 'Position your face';
|
|
25
|
+
const description = guide?.description ?? 'Keep your face inside the oval for the best approval rate. Positioning your face outside the frame may reduce your chances of approval.';
|
|
26
|
+
const tip = guide?.tip ?? 'Face inside = better approval • Face outside = lower chances';
|
|
27
|
+
const continueLabel = guide?.continue ?? strings.common?.continue ?? 'Continue';
|
|
28
|
+
const primaryColor = theme?.primaryColor ?? '#6366F1';
|
|
29
|
+
const textColor = theme?.textColor ?? '#000000';
|
|
30
|
+
const secondaryTextColor = theme?.secondaryTextColor ?? '#6B7280';
|
|
31
|
+
const backgroundColor = theme?.backgroundColor ?? '#FFFFFF';
|
|
32
|
+
const styles = createStyles({
|
|
33
|
+
primaryColor,
|
|
34
|
+
textColor,
|
|
35
|
+
secondaryTextColor,
|
|
36
|
+
backgroundColor,
|
|
37
|
+
});
|
|
38
|
+
return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, customStyles?.container] },
|
|
39
|
+
onCancel && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.cancelButton, onPress: onCancel },
|
|
40
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.cancelText, { color: textColor }] }, strings.common?.cancel ?? 'Cancel'))),
|
|
41
|
+
react_1.default.createElement(react_native_1.ScrollView, { contentContainerStyle: [styles.scrollContent, customStyles?.content], showsVerticalScrollIndicator: false },
|
|
42
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.title, { color: textColor }] }, title),
|
|
43
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.description, { color: secondaryTextColor }] }, description),
|
|
44
|
+
react_1.default.createElement(react_native_1.View, { style: styles.imageWrapper },
|
|
45
|
+
react_1.default.createElement(react_native_1.View, { style: styles.imageShadowContainer },
|
|
46
|
+
react_1.default.createElement(react_native_1.Image, { source: FACE_GUIDE_IMAGE, style: styles.guideImage, resizeMode: "contain" }))),
|
|
47
|
+
react_1.default.createElement(react_native_1.View, { style: [styles.tipBox, { borderColor: primaryColor + '30', backgroundColor: primaryColor + '08' }] },
|
|
48
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.tipText, { color: secondaryTextColor }] }, tip)),
|
|
49
|
+
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.continueButton, { backgroundColor: primaryColor }], onPress: onContinue, activeOpacity: 0.85 },
|
|
50
|
+
react_1.default.createElement(react_native_1.Text, { style: styles.continueButtonText }, continueLabel)))));
|
|
51
|
+
};
|
|
52
|
+
exports.FacePositioningGuide = FacePositioningGuide;
|
|
53
|
+
const createStyles = (vars) => react_native_1.StyleSheet.create({
|
|
54
|
+
container: {
|
|
55
|
+
flex: 1,
|
|
56
|
+
backgroundColor: vars.backgroundColor,
|
|
57
|
+
zIndex: 9999,
|
|
58
|
+
},
|
|
59
|
+
cancelButton: {
|
|
60
|
+
alignSelf: 'flex-start',
|
|
61
|
+
paddingVertical: 12,
|
|
62
|
+
paddingHorizontal: 16,
|
|
63
|
+
marginTop: 8,
|
|
64
|
+
},
|
|
65
|
+
cancelText: {
|
|
66
|
+
fontSize: 16,
|
|
67
|
+
fontWeight: '500',
|
|
68
|
+
},
|
|
69
|
+
scrollContent: {
|
|
70
|
+
paddingHorizontal: 24,
|
|
71
|
+
paddingBottom: 40,
|
|
72
|
+
alignItems: 'center',
|
|
73
|
+
},
|
|
74
|
+
title: {
|
|
75
|
+
fontSize: 24,
|
|
76
|
+
fontWeight: '700',
|
|
77
|
+
marginBottom: 12,
|
|
78
|
+
textAlign: 'center',
|
|
79
|
+
},
|
|
80
|
+
description: {
|
|
81
|
+
fontSize: 16,
|
|
82
|
+
lineHeight: 24,
|
|
83
|
+
textAlign: 'center',
|
|
84
|
+
marginBottom: 28,
|
|
85
|
+
paddingHorizontal: 8,
|
|
86
|
+
},
|
|
87
|
+
imageWrapper: {
|
|
88
|
+
marginBottom: 24,
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
},
|
|
92
|
+
imageShadowContainer: {
|
|
93
|
+
width: 260,
|
|
94
|
+
height: 320,
|
|
95
|
+
borderRadius: 130,
|
|
96
|
+
backgroundColor: 'transparent',
|
|
97
|
+
shadowColor: '#000',
|
|
98
|
+
shadowOffset: { width: 0, height: 8 },
|
|
99
|
+
shadowOpacity: 0.25,
|
|
100
|
+
shadowRadius: 12,
|
|
101
|
+
elevation: 12,
|
|
102
|
+
overflow: 'hidden',
|
|
103
|
+
},
|
|
104
|
+
guideImage: {
|
|
105
|
+
width: '100%',
|
|
106
|
+
height: '100%',
|
|
107
|
+
},
|
|
108
|
+
tipBox: {
|
|
109
|
+
paddingVertical: 14,
|
|
110
|
+
paddingHorizontal: 20,
|
|
111
|
+
borderRadius: 12,
|
|
112
|
+
borderWidth: 1,
|
|
113
|
+
marginBottom: 32,
|
|
114
|
+
alignSelf: 'stretch',
|
|
115
|
+
},
|
|
116
|
+
tipText: {
|
|
117
|
+
fontSize: 14,
|
|
118
|
+
fontWeight: '600',
|
|
119
|
+
textAlign: 'center',
|
|
120
|
+
},
|
|
121
|
+
continueButton: {
|
|
122
|
+
paddingVertical: 16,
|
|
123
|
+
paddingHorizontal: 32,
|
|
124
|
+
borderRadius: 12,
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
alignSelf: 'stretch',
|
|
127
|
+
shadowColor: '#000',
|
|
128
|
+
shadowOffset: { width: 0, height: 2 },
|
|
129
|
+
shadowOpacity: 0.1,
|
|
130
|
+
shadowRadius: 4,
|
|
131
|
+
elevation: 3,
|
|
132
|
+
},
|
|
133
|
+
continueButtonText: {
|
|
134
|
+
color: '#FFFFFF',
|
|
135
|
+
fontSize: 18,
|
|
136
|
+
fontWeight: '700',
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
exports.default = exports.FacePositioningGuide;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfilePictureCapture.d.ts","sourceRoot":"","sources":["../../src/components/ProfilePictureCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ProfilePictureCapture.d.ts","sourceRoot":"","sources":["../../src/components/ProfilePictureCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAWxE,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,CA8UtE,CAAC;AAsEF,eAAe,qBAAqB,CAAC"}
|
|
@@ -37,10 +37,12 @@ exports.ProfilePictureCapture = void 0;
|
|
|
37
37
|
const react_1 = __importStar(require("react"));
|
|
38
38
|
const react_native_1 = require("react-native");
|
|
39
39
|
const VideoRecorder_1 = require("./VideoRecorder");
|
|
40
|
+
const FacePositioningGuide_1 = require("./FacePositioningGuide");
|
|
40
41
|
const biometric_identity_sdk_core_1 = require("@hexar/biometric-identity-sdk-core");
|
|
41
42
|
const useBiometricSDK_1 = require("../hooks/useBiometricSDK");
|
|
42
43
|
const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language, }) => {
|
|
43
44
|
const { sdk, isInitialized, isUsingBackend, sessionId, fetchChallenges, } = (0, useBiometricSDK_1.useBiometricSDK)();
|
|
45
|
+
const [showFacePositioningGuide, setShowFacePositioningGuide] = (0, react_1.useState)(true);
|
|
44
46
|
const [isValidating, setIsValidating] = (0, react_1.useState)(false);
|
|
45
47
|
const [currentChallenges, setCurrentChallenges] = (0, react_1.useState)([]);
|
|
46
48
|
const [isLoadingChallenges, setIsLoadingChallenges] = (0, react_1.useState)(false);
|
|
@@ -278,13 +280,16 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
278
280
|
"%")),
|
|
279
281
|
react_1.default.createElement(react_native_1.Text, { style: [styles.progressBottomText, { color: theme?.secondaryTextColor || '#64748b' }] }, strings.validation.almostDone || strings.common.loading || 'Esto puede tardar unos segundos...'))));
|
|
280
282
|
}
|
|
281
|
-
// Wait for initialization and challenge loading before showing VideoRecorder
|
|
283
|
+
// Wait for initialization and challenge loading before showing guide or VideoRecorder
|
|
282
284
|
if (!isInitialized || (isUsingBackend && isLoadingChallenges)) {
|
|
283
285
|
return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }] },
|
|
284
286
|
react_1.default.createElement(react_native_1.View, { style: styles.loadingContainer },
|
|
285
287
|
react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: theme?.primaryColor || '#4f46e5' }),
|
|
286
288
|
react_1.default.createElement(react_native_1.Text, { style: [styles.loadingText, { color: theme?.textColor || '#1e1b4b' }] }, strings.liveness.preparing || 'Preparing...'))));
|
|
287
289
|
}
|
|
290
|
+
if (showFacePositioningGuide) {
|
|
291
|
+
return (react_1.default.createElement(FacePositioningGuide_1.FacePositioningGuide, { theme: theme, language: language, onContinue: () => setShowFacePositioningGuide(false), onCancel: onCancel }));
|
|
292
|
+
}
|
|
288
293
|
return (react_1.default.createElement(VideoRecorder_1.VideoRecorder, { theme: theme, language: language, smartMode: true, challenges: currentChallenges, sessionId: sdk.getSessionId() || undefined, onComplete: handleVideoComplete, onCancel: handleVideoCancel, onFetchChallenges: handleFetchChallenges }));
|
|
289
294
|
};
|
|
290
295
|
exports.ProfilePictureCapture = ProfilePictureCapture;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { ValidationProgress } from './components/ValidationProgress';
|
|
|
12
12
|
export { ResultScreen } from './components/ResultScreen';
|
|
13
13
|
export { ErrorScreen } from './components/ErrorScreen';
|
|
14
14
|
export { InstructionsScreen } from './components/InstructionsScreen';
|
|
15
|
+
export { FacePositioningGuide } from './components/FacePositioningGuide';
|
|
15
16
|
export { useBiometricSDK } from './hooks/useBiometricSDK';
|
|
16
17
|
export * from '@hexar/biometric-identity-sdk-core';
|
|
17
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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;
|
|
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;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAGzE,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.ProfilePictureCapture = exports.VideoRecorder = exports.CameraCapture = exports.default = exports.BiometricIdentityFlow = void 0;
|
|
24
|
+
exports.useBiometricSDK = exports.FacePositioningGuide = 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; } });
|
|
@@ -42,6 +42,8 @@ var ErrorScreen_1 = require("./components/ErrorScreen");
|
|
|
42
42
|
Object.defineProperty(exports, "ErrorScreen", { enumerable: true, get: function () { return ErrorScreen_1.ErrorScreen; } });
|
|
43
43
|
var InstructionsScreen_1 = require("./components/InstructionsScreen");
|
|
44
44
|
Object.defineProperty(exports, "InstructionsScreen", { enumerable: true, get: function () { return InstructionsScreen_1.InstructionsScreen; } });
|
|
45
|
+
var FacePositioningGuide_1 = require("./components/FacePositioningGuide");
|
|
46
|
+
Object.defineProperty(exports, "FacePositioningGuide", { enumerable: true, get: function () { return FacePositioningGuide_1.FacePositioningGuide; } });
|
|
45
47
|
// Hooks
|
|
46
48
|
var useBiometricSDK_1 = require("./hooks/useBiometricSDK");
|
|
47
49
|
Object.defineProperty(exports, "useBiometricSDK", { enumerable: true, get: function () { return useBiometricSDK_1.useBiometricSDK; } });
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hexar/biometric-identity-sdk-react-native",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "React Native wrapper for Biometric Identity SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"private": false,
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc",
|
|
9
|
+
"build": "tsc && cp -r src/assets dist/",
|
|
10
10
|
"dev": "tsc --watch",
|
|
11
11
|
"clean": "rm -rf dist"
|
|
12
12
|
},
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"@hexar/biometric-identity-sdk-core": ">=1.1
|
|
14
|
+
"@hexar/biometric-identity-sdk-core": ">=1.2.1",
|
|
15
15
|
"react": ">=18.0.0",
|
|
16
16
|
"react-native": ">=0.70.0",
|
|
17
17
|
"react-native-permissions": ">=4.0.0",
|
|
Binary file
|
|
@@ -52,6 +52,7 @@ import { ValidationProgress } from './ValidationProgress';
|
|
|
52
52
|
import { ResultScreen } from './ResultScreen';
|
|
53
53
|
import { ErrorScreen } from './ErrorScreen';
|
|
54
54
|
import { InstructionsScreen } from './InstructionsScreen';
|
|
55
|
+
import { FacePositioningGuide } from './FacePositioningGuide';
|
|
55
56
|
|
|
56
57
|
export interface BiometricIdentityFlowProps {
|
|
57
58
|
onValidationComplete: (result: ValidationResult) => void;
|
|
@@ -94,6 +95,7 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
94
95
|
const [showCamera, setShowCamera] = useState(false);
|
|
95
96
|
const [cameraMode, setCameraMode] = useState<'front' | 'back' | 'video'>('front');
|
|
96
97
|
const [showInstructions, setShowInstructions] = useState(true);
|
|
98
|
+
const [showFacePositioningGuide, setShowFacePositioningGuide] = useState(false);
|
|
97
99
|
const [currentChallenges, setCurrentChallenges] = useState<ChallengeAction[]>([]);
|
|
98
100
|
const [isLoadingChallenges, setIsLoadingChallenges] = useState(false);
|
|
99
101
|
|
|
@@ -147,8 +149,18 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
147
149
|
*/
|
|
148
150
|
const handleCaptureStart = useCallback(async (mode: 'front' | 'back' | 'video') => {
|
|
149
151
|
setCameraMode(mode);
|
|
150
|
-
|
|
151
|
-
if (mode === 'video'
|
|
152
|
+
|
|
153
|
+
if (mode === 'video') {
|
|
154
|
+
setShowFacePositioningGuide(true);
|
|
155
|
+
// Challenges will be loaded when guide is dismissed and we show camera
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (mode !== 'video') {
|
|
159
|
+
setShowCamera(true);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (smartLivenessMode && isUsingBackend) {
|
|
152
164
|
setIsLoadingChallenges(true);
|
|
153
165
|
try {
|
|
154
166
|
const challenges = await fetchChallenges('active');
|
|
@@ -215,10 +227,13 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
215
227
|
});
|
|
216
228
|
setCurrentChallenges(translatedChallenges);
|
|
217
229
|
}
|
|
218
|
-
|
|
219
|
-
setShowCamera(true);
|
|
220
230
|
}, [smartLivenessMode, isUsingBackend, fetchChallenges, sdk]);
|
|
221
231
|
|
|
232
|
+
const handleFacePositioningContinue = useCallback(() => {
|
|
233
|
+
setShowFacePositioningGuide(false);
|
|
234
|
+
setShowCamera(true);
|
|
235
|
+
}, []);
|
|
236
|
+
|
|
222
237
|
/**
|
|
223
238
|
* Handle capture completion
|
|
224
239
|
*/
|
|
@@ -341,6 +356,18 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
341
356
|
);
|
|
342
357
|
}
|
|
343
358
|
|
|
359
|
+
if (showFacePositioningGuide) {
|
|
360
|
+
return (
|
|
361
|
+
<FacePositioningGuide
|
|
362
|
+
theme={theme}
|
|
363
|
+
language={language}
|
|
364
|
+
onContinue={handleFacePositioningContinue}
|
|
365
|
+
onCancel={() => setShowFacePositioningGuide(false)}
|
|
366
|
+
styles={customStyles}
|
|
367
|
+
/>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
344
371
|
if (showCamera) {
|
|
345
372
|
if (cameraMode === 'video') {
|
|
346
373
|
return (
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Face Positioning Guide Component
|
|
3
|
+
* Shared view that instructs users to keep their face inside the oval
|
|
4
|
+
* for better approval rate. Used before video capture in both
|
|
5
|
+
* BiometricIdentityFlow and ProfilePictureCapture.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import {
|
|
10
|
+
View,
|
|
11
|
+
Text,
|
|
12
|
+
StyleSheet,
|
|
13
|
+
TouchableOpacity,
|
|
14
|
+
ScrollView,
|
|
15
|
+
SafeAreaView,
|
|
16
|
+
ViewStyle,
|
|
17
|
+
Image,
|
|
18
|
+
} from 'react-native';
|
|
19
|
+
import { ThemeConfig, SupportedLanguage, getStrings, setLanguage } from '@hexar/biometric-identity-sdk-core';
|
|
20
|
+
|
|
21
|
+
const FACE_GUIDE_IMAGE = require('../assets/images/face-biometry.png');
|
|
22
|
+
|
|
23
|
+
export interface FacePositioningGuideProps {
|
|
24
|
+
theme?: ThemeConfig;
|
|
25
|
+
language?: SupportedLanguage;
|
|
26
|
+
onContinue: () => void;
|
|
27
|
+
onCancel?: () => void;
|
|
28
|
+
styles?: {
|
|
29
|
+
container?: ViewStyle;
|
|
30
|
+
content?: ViewStyle;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const FacePositioningGuide: React.FC<FacePositioningGuideProps> = ({
|
|
35
|
+
theme,
|
|
36
|
+
language,
|
|
37
|
+
onContinue,
|
|
38
|
+
onCancel,
|
|
39
|
+
styles: customStyles,
|
|
40
|
+
}) => {
|
|
41
|
+
if (language) {
|
|
42
|
+
setLanguage(language);
|
|
43
|
+
}
|
|
44
|
+
const strings = getStrings();
|
|
45
|
+
const liveness = strings.liveness as Record<string, unknown> | undefined;
|
|
46
|
+
const guide = liveness?.facePositioningGuide as { title?: string; description?: string; tip?: string; continue?: string } | undefined;
|
|
47
|
+
const title = guide?.title ?? 'Position your face';
|
|
48
|
+
const description = guide?.description ?? 'Keep your face inside the oval for the best approval rate. Positioning your face outside the frame may reduce your chances of approval.';
|
|
49
|
+
const tip = guide?.tip ?? 'Face inside = better approval • Face outside = lower chances';
|
|
50
|
+
const continueLabel = guide?.continue ?? strings.common?.continue ?? 'Continue';
|
|
51
|
+
|
|
52
|
+
const primaryColor = theme?.primaryColor ?? '#6366F1';
|
|
53
|
+
const textColor = theme?.textColor ?? '#000000';
|
|
54
|
+
const secondaryTextColor = theme?.secondaryTextColor ?? '#6B7280';
|
|
55
|
+
const backgroundColor = theme?.backgroundColor ?? '#FFFFFF';
|
|
56
|
+
|
|
57
|
+
const styles = createStyles({
|
|
58
|
+
primaryColor,
|
|
59
|
+
textColor,
|
|
60
|
+
secondaryTextColor,
|
|
61
|
+
backgroundColor,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<SafeAreaView style={[styles.container, customStyles?.container]}>
|
|
66
|
+
{onCancel && (
|
|
67
|
+
<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
|
|
68
|
+
<Text style={[styles.cancelText, { color: textColor }]}>
|
|
69
|
+
{strings.common?.cancel ?? 'Cancel'}
|
|
70
|
+
</Text>
|
|
71
|
+
</TouchableOpacity>
|
|
72
|
+
)}
|
|
73
|
+
<ScrollView
|
|
74
|
+
contentContainerStyle={[styles.scrollContent, customStyles?.content]}
|
|
75
|
+
showsVerticalScrollIndicator={false}
|
|
76
|
+
>
|
|
77
|
+
<Text style={[styles.title, { color: textColor }]}>{title}</Text>
|
|
78
|
+
<Text style={[styles.description, { color: secondaryTextColor }]}>
|
|
79
|
+
{description}
|
|
80
|
+
</Text>
|
|
81
|
+
|
|
82
|
+
<View style={styles.imageWrapper}>
|
|
83
|
+
<View style={styles.imageShadowContainer}>
|
|
84
|
+
<Image
|
|
85
|
+
source={FACE_GUIDE_IMAGE}
|
|
86
|
+
style={styles.guideImage}
|
|
87
|
+
resizeMode="contain"
|
|
88
|
+
/>
|
|
89
|
+
</View>
|
|
90
|
+
</View>
|
|
91
|
+
|
|
92
|
+
<View style={[styles.tipBox, { borderColor: primaryColor + '30', backgroundColor: primaryColor + '08' }]}>
|
|
93
|
+
<Text style={[styles.tipText, { color: secondaryTextColor }]}>{tip}</Text>
|
|
94
|
+
</View>
|
|
95
|
+
|
|
96
|
+
<TouchableOpacity
|
|
97
|
+
style={[styles.continueButton, { backgroundColor: primaryColor }]}
|
|
98
|
+
onPress={onContinue}
|
|
99
|
+
activeOpacity={0.85}
|
|
100
|
+
>
|
|
101
|
+
<Text style={styles.continueButtonText}>{continueLabel}</Text>
|
|
102
|
+
</TouchableOpacity>
|
|
103
|
+
</ScrollView>
|
|
104
|
+
</SafeAreaView>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const createStyles = (vars: {
|
|
109
|
+
primaryColor: string;
|
|
110
|
+
textColor: string;
|
|
111
|
+
secondaryTextColor: string;
|
|
112
|
+
backgroundColor: string;
|
|
113
|
+
}) =>
|
|
114
|
+
StyleSheet.create({
|
|
115
|
+
container: {
|
|
116
|
+
flex: 1,
|
|
117
|
+
backgroundColor: vars.backgroundColor,
|
|
118
|
+
zIndex: 9999,
|
|
119
|
+
},
|
|
120
|
+
cancelButton: {
|
|
121
|
+
alignSelf: 'flex-start',
|
|
122
|
+
paddingVertical: 12,
|
|
123
|
+
paddingHorizontal: 16,
|
|
124
|
+
marginTop: 8,
|
|
125
|
+
},
|
|
126
|
+
cancelText: {
|
|
127
|
+
fontSize: 16,
|
|
128
|
+
fontWeight: '500',
|
|
129
|
+
},
|
|
130
|
+
scrollContent: {
|
|
131
|
+
paddingHorizontal: 24,
|
|
132
|
+
paddingBottom: 40,
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
},
|
|
135
|
+
title: {
|
|
136
|
+
fontSize: 24,
|
|
137
|
+
fontWeight: '700',
|
|
138
|
+
marginBottom: 12,
|
|
139
|
+
textAlign: 'center',
|
|
140
|
+
},
|
|
141
|
+
description: {
|
|
142
|
+
fontSize: 16,
|
|
143
|
+
lineHeight: 24,
|
|
144
|
+
textAlign: 'center',
|
|
145
|
+
marginBottom: 28,
|
|
146
|
+
paddingHorizontal: 8,
|
|
147
|
+
},
|
|
148
|
+
imageWrapper: {
|
|
149
|
+
marginBottom: 24,
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
justifyContent: 'center',
|
|
152
|
+
},
|
|
153
|
+
imageShadowContainer: {
|
|
154
|
+
width: 260,
|
|
155
|
+
height: 320,
|
|
156
|
+
borderRadius: 130,
|
|
157
|
+
backgroundColor: 'transparent',
|
|
158
|
+
shadowColor: '#000',
|
|
159
|
+
shadowOffset: { width: 0, height: 8 },
|
|
160
|
+
shadowOpacity: 0.25,
|
|
161
|
+
shadowRadius: 12,
|
|
162
|
+
elevation: 12,
|
|
163
|
+
overflow: 'hidden',
|
|
164
|
+
},
|
|
165
|
+
guideImage: {
|
|
166
|
+
width: '100%',
|
|
167
|
+
height: '100%',
|
|
168
|
+
},
|
|
169
|
+
tipBox: {
|
|
170
|
+
paddingVertical: 14,
|
|
171
|
+
paddingHorizontal: 20,
|
|
172
|
+
borderRadius: 12,
|
|
173
|
+
borderWidth: 1,
|
|
174
|
+
marginBottom: 32,
|
|
175
|
+
alignSelf: 'stretch',
|
|
176
|
+
},
|
|
177
|
+
tipText: {
|
|
178
|
+
fontSize: 14,
|
|
179
|
+
fontWeight: '600',
|
|
180
|
+
textAlign: 'center',
|
|
181
|
+
},
|
|
182
|
+
continueButton: {
|
|
183
|
+
paddingVertical: 16,
|
|
184
|
+
paddingHorizontal: 32,
|
|
185
|
+
borderRadius: 12,
|
|
186
|
+
alignItems: 'center',
|
|
187
|
+
alignSelf: 'stretch',
|
|
188
|
+
shadowColor: '#000',
|
|
189
|
+
shadowOffset: { width: 0, height: 2 },
|
|
190
|
+
shadowOpacity: 0.1,
|
|
191
|
+
shadowRadius: 4,
|
|
192
|
+
elevation: 3,
|
|
193
|
+
},
|
|
194
|
+
continueButtonText: {
|
|
195
|
+
color: '#FFFFFF',
|
|
196
|
+
fontSize: 18,
|
|
197
|
+
fontWeight: '700',
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
export default FacePositioningGuide;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Animated,
|
|
9
9
|
} from 'react-native';
|
|
10
10
|
import { VideoRecorder, VideoRecordingResult } from './VideoRecorder';
|
|
11
|
+
import { FacePositioningGuide } from './FacePositioningGuide';
|
|
11
12
|
import { ThemeConfig, SupportedLanguage, getStrings, setLanguage, logger, BiometricError, BiometricErrorCode } from '@hexar/biometric-identity-sdk-core';
|
|
12
13
|
import { useBiometricSDK } from '../hooks/useBiometricSDK';
|
|
13
14
|
|
|
@@ -42,6 +43,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
42
43
|
fetchChallenges,
|
|
43
44
|
} = useBiometricSDK();
|
|
44
45
|
|
|
46
|
+
const [showFacePositioningGuide, setShowFacePositioningGuide] = useState(true);
|
|
45
47
|
const [isValidating, setIsValidating] = useState(false);
|
|
46
48
|
const [currentChallenges, setCurrentChallenges] = useState<any[]>([]);
|
|
47
49
|
const [isLoadingChallenges, setIsLoadingChallenges] = useState(false);
|
|
@@ -323,7 +325,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
323
325
|
);
|
|
324
326
|
}
|
|
325
327
|
|
|
326
|
-
// Wait for initialization and challenge loading before showing VideoRecorder
|
|
328
|
+
// Wait for initialization and challenge loading before showing guide or VideoRecorder
|
|
327
329
|
if (!isInitialized || (isUsingBackend && isLoadingChallenges)) {
|
|
328
330
|
return (
|
|
329
331
|
<SafeAreaView style={[styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }]}>
|
|
@@ -337,6 +339,17 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
337
339
|
);
|
|
338
340
|
}
|
|
339
341
|
|
|
342
|
+
if (showFacePositioningGuide) {
|
|
343
|
+
return (
|
|
344
|
+
<FacePositioningGuide
|
|
345
|
+
theme={theme}
|
|
346
|
+
language={language}
|
|
347
|
+
onContinue={() => setShowFacePositioningGuide(false)}
|
|
348
|
+
onCancel={onCancel}
|
|
349
|
+
/>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
340
353
|
return (
|
|
341
354
|
<VideoRecorder
|
|
342
355
|
theme={theme}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { ValidationProgress } from './components/ValidationProgress';
|
|
|
16
16
|
export { ResultScreen } from './components/ResultScreen';
|
|
17
17
|
export { ErrorScreen } from './components/ErrorScreen';
|
|
18
18
|
export { InstructionsScreen } from './components/InstructionsScreen';
|
|
19
|
+
export { FacePositioningGuide } from './components/FacePositioningGuide';
|
|
19
20
|
|
|
20
21
|
// Hooks
|
|
21
22
|
export { useBiometricSDK } from './hooks/useBiometricSDK';
|