@hexar/biometric-identity-sdk-react-native 1.0.8 → 1.0.9

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.
@@ -12,6 +12,7 @@ export interface BiometricIdentityFlowProps {
12
12
  language?: SupportedLanguage;
13
13
  customTranslations?: Record<string, string>;
14
14
  smartLivenessMode?: boolean;
15
+ routeBack?: () => void;
15
16
  styles?: {
16
17
  container?: ViewStyle;
17
18
  content?: ViewStyle;
@@ -1 +1 @@
1
- {"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA2C,MAAM,OAAO,CAAC;AAChE,OAAO,EAOL,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,cAAc,EAKd,iBAAiB,EAClB,MAAM,oCAAoC,CAAC;AAU5C,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,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,CAoTtE,CAAC;AAiOF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA2C,MAAM,OAAO,CAAC;AAChE,OAAO,EAOL,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,cAAc,EAKd,iBAAiB,EAClB,MAAM,oCAAoC,CAAC;AAU5C,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,CAiTtE,CAAC;AAiOF,eAAe,qBAAqB,CAAC"}
@@ -48,7 +48,7 @@ const ValidationProgress_1 = require("./ValidationProgress");
48
48
  const ResultScreen_1 = require("./ResultScreen");
49
49
  const ErrorScreen_1 = require("./ErrorScreen");
50
50
  const InstructionsScreen_1 = require("./InstructionsScreen");
51
- const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language, customTranslations, smartLivenessMode = true, styles: customStyles, }) => {
51
+ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language, customTranslations, smartLivenessMode = true, routeBack, styles: customStyles, }) => {
52
52
  const { sdk, state, isInitialized, isUsingBackend, challenges, uploadFrontID, uploadBackID, storeVideoRecording, fetchChallenges, validateIdentity, reset, } = (0, useBiometricSDK_1.useBiometricSDK)();
53
53
  const [showCamera, setShowCamera] = (0, react_1.useState)(false);
54
54
  const [cameraMode, setCameraMode] = (0, react_1.useState)('front');
@@ -158,11 +158,7 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
158
158
  }
159
159
  // Show instructions on first load
160
160
  if (showInstructions) {
161
- return (react_1.default.createElement(InstructionsScreen_1.InstructionsScreen, { theme: theme, language: language, onStart: () => setShowInstructions(false), onCancel: onError ? () => onError({
162
- name: 'BiometricError',
163
- message: 'User cancelled',
164
- code: biometric_identity_sdk_core_1.BiometricErrorCode.USER_CANCELLED,
165
- }) : undefined, styles: customStyles }));
161
+ return (react_1.default.createElement(InstructionsScreen_1.InstructionsScreen, { theme: theme, language: language, onStart: () => setShowInstructions(false), routeBack: routeBack, styles: customStyles }));
166
162
  }
167
163
  // Show camera/video recorder
168
164
  if (showCamera) {
@@ -9,7 +9,7 @@ export interface InstructionsScreenProps {
9
9
  theme?: ThemeConfig;
10
10
  language?: SupportedLanguage;
11
11
  onStart: () => void;
12
- onCancel?: () => void;
12
+ routeBack?: () => void;
13
13
  styles?: {
14
14
  container?: ViewStyle;
15
15
  content?: ViewStyle;
@@ -1 +1 @@
1
- {"version":3,"file":"InstructionsScreen.d.ts","sourceRoot":"","sources":["../../src/components/InstructionsScreen.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AACnD,OAAO,EAML,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAA2B,MAAM,oCAAoC,CAAC;AAE7G,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,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,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CA4GhE,CAAC;AAiMF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"InstructionsScreen.d.ts","sourceRoot":"","sources":["../../src/components/InstructionsScreen.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AACnD,OAAO,EAML,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAA2B,MAAM,oCAAoC,CAAC;AAE7G,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,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,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAyGhE,CAAC;AAuMF,eAAe,kBAAkB,CAAC"}
@@ -41,7 +41,7 @@ exports.InstructionsScreen = void 0;
41
41
  const react_1 = __importStar(require("react"));
42
42
  const react_native_1 = require("react-native");
43
43
  const biometric_identity_sdk_core_1 = require("@hexar/biometric-identity-sdk-core");
44
- const InstructionsScreen = ({ theme, language = 'en', onStart, onCancel, styles: customStyles, }) => {
44
+ const InstructionsScreen = ({ theme, language = 'en', onStart, routeBack, styles: customStyles, }) => {
45
45
  const [strings, setStrings] = (0, react_1.useState)(() => {
46
46
  // Set initial language
47
47
  if (language) {
@@ -61,6 +61,9 @@ const InstructionsScreen = ({ theme, language = 'en', onStart, onCancel, styles:
61
61
  const tipsContent = getTipsContent(language);
62
62
  const privacyContent = getPrivacyContent(language);
63
63
  return (react_1.default.createElement(react_native_1.View, { style: [styles.container, customStyles?.container] },
64
+ routeBack && (react_1.default.createElement(react_native_1.View, { style: styles.header },
65
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.backButton, onPress: routeBack },
66
+ react_1.default.createElement(react_native_1.Text, { style: [styles.backButtonText, { color: theme?.textColor || '#000000' }] }, strings.common.back || 'Volver')))),
64
67
  react_1.default.createElement(react_native_1.ScrollView, { contentContainerStyle: [styles.content, customStyles?.content] },
65
68
  react_1.default.createElement(react_native_1.Text, { style: [styles.title, { color: theme?.textColor || '#000000' }] }, strings.instructions.title),
66
69
  react_1.default.createElement(react_native_1.Text, { style: [styles.subtitle, { color: theme?.secondaryTextColor || '#6B7280' }] }, strings.instructions.subtitle),
@@ -76,16 +79,9 @@ const InstructionsScreen = ({ theme, language = 'en', onStart, onCancel, styles:
76
79
  react_1.default.createElement(react_native_1.View, { style: styles.privacyContainer },
77
80
  react_1.default.createElement(react_native_1.Text, { style: styles.privacyText }, privacyContent))),
78
81
  react_1.default.createElement(react_native_1.View, { style: styles.footer },
79
- onCancel && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
80
- styles.button,
81
- styles.cancelButton,
82
- { borderColor: theme?.errorColor || '#EF4444' },
83
- ], onPress: onCancel },
84
- react_1.default.createElement(react_native_1.Text, { style: [styles.buttonText, { color: theme?.errorColor || '#EF4444' }] }, strings.common.cancel || 'Cancel'))),
85
82
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
86
83
  styles.button,
87
84
  { backgroundColor: theme?.primaryColor || '#6366F1' },
88
- onCancel && styles.startButton,
89
85
  ], onPress: onStart },
90
86
  react_1.default.createElement(react_native_1.Text, { style: styles.buttonText }, strings.instructions.startButton)))));
91
87
  };
@@ -135,6 +131,22 @@ const styles = react_native_1.StyleSheet.create({
135
131
  flex: 1,
136
132
  backgroundColor: '#FFFFFF',
137
133
  },
134
+ header: {
135
+ paddingTop: 16,
136
+ paddingHorizontal: 16,
137
+ paddingBottom: 8,
138
+ borderBottomWidth: 1,
139
+ borderBottomColor: '#E5E7EB',
140
+ },
141
+ backButton: {
142
+ paddingVertical: 8,
143
+ paddingHorizontal: 12,
144
+ alignSelf: 'flex-start',
145
+ },
146
+ backButtonText: {
147
+ fontSize: 16,
148
+ fontWeight: '600',
149
+ },
138
150
  content: {
139
151
  padding: 24,
140
152
  },
@@ -182,22 +194,12 @@ const styles = react_native_1.StyleSheet.create({
182
194
  padding: 24,
183
195
  borderTopWidth: 1,
184
196
  borderTopColor: '#E5E7EB',
185
- flexDirection: 'row',
186
- gap: 12,
187
197
  },
188
198
  button: {
189
199
  paddingVertical: 16,
190
200
  paddingHorizontal: 32,
191
201
  borderRadius: 8,
192
202
  alignItems: 'center',
193
- flex: 1,
194
- },
195
- cancelButton: {
196
- borderWidth: 2,
197
- backgroundColor: 'transparent',
198
- },
199
- startButton: {
200
- flex: 1,
201
203
  },
202
204
  buttonText: {
203
205
  color: '#FFFFFF',
@@ -1 +1 @@
1
- {"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAA2B,MAAM,oCAAoC,CAAC;AAGlI,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAmsBtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAA2B,MAAM,oCAAoC,CAAC;AAGlI,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAiwBtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
@@ -274,13 +274,19 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
274
274
  react_native_1.Alert.alert('Recording Error', 'Failed to record video. Please try again.', [{ text: 'OK', onPress: onCancel }]);
275
275
  }, [onCancel]);
276
276
  const handleVideoComplete = (0, react_1.useCallback)(async (video) => {
277
- console.log('handleVideoComplete called with video:', video?.path);
277
+ react_native_1.Alert.alert('Video Complete', `handleVideoComplete called\nVideo path: ${video?.path || 'N/A'}`);
278
+ if (!isRecordingRef.current && phase !== 'processing') {
279
+ react_native_1.Alert.alert('Info', 'Video already processed, ignoring duplicate callback');
280
+ return;
281
+ }
278
282
  try {
279
283
  setPhase('processing');
284
+ setOverallProgress(100);
280
285
  const actualDuration = Date.now() - recordingStartTime.current;
281
- console.log('Video duration:', actualDuration, 'min required:', minDurationMs);
286
+ react_native_1.Alert.alert('Video Processing', `Duration: ${(actualDuration / 1000).toFixed(1)}s\nMin required: ${(minDurationMs / 1000).toFixed(1)}s\nFrames: ${frames.length}`);
282
287
  if (actualDuration < minDurationMs) {
283
288
  setPhase('recording');
289
+ setOverallProgress(0);
284
290
  react_native_1.Alert.alert(strings.errors.videoTooShort?.title || 'Recording Too Short', strings.errors.videoTooShort?.message || `Video must be at least ${minDurationMs / 1000} seconds. Please try again.`, [{ text: strings.common.retry || 'OK', onPress: resetAndRetry }]);
285
291
  return;
286
292
  }
@@ -289,36 +295,41 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
289
295
  try {
290
296
  const RNFS = require('react-native-fs');
291
297
  videoBase64 = await RNFS.readFile(video.path, 'base64');
292
- console.log('Video file read successfully, size:', videoBase64.length);
298
+ react_native_1.Alert.alert('Video File Read', `Successfully read video file\nSize: ${videoBase64.length} bytes`);
293
299
  }
294
300
  catch (fsError) {
295
- console.warn('Could not read video file, using captured frames:', fsError);
301
+ react_native_1.Alert.alert('Video File Warning', `Could not read video file, using captured frames\nError: ${fsError}`);
296
302
  }
297
303
  }
304
+ const finalFrames = frames.length > 0 ? frames : (videoBase64 ? [videoBase64] : []);
305
+ if (finalFrames.length === 0) {
306
+ react_native_1.Alert.alert('Error', 'No frames available, cannot complete');
307
+ setPhase('recording');
308
+ handleRecordingError(new Error('No video frames captured'));
309
+ return;
310
+ }
298
311
  const result = {
299
- frames: frames.length > 0 ? frames : (videoBase64 ? [videoBase64] : []),
312
+ frames: finalFrames,
300
313
  duration: actualDuration,
301
314
  instructionsFollowed: completedChallenges.length === challenges.length,
302
- qualityScore: frames.length > 0 ? Math.min(100, (frames.length / 30) * 100) : 85,
315
+ qualityScore: finalFrames.length > 0 ? Math.min(100, (finalFrames.length / 30) * 100) : 85,
303
316
  challengesCompleted: completedChallenges,
304
317
  sessionId,
305
318
  };
306
- console.log('Video recording completed successfully:', {
307
- duration: actualDuration,
308
- frames: result.frames.length,
309
- challengesCompleted: completedChallenges.length,
310
- instructionsFollowed: result.instructionsFollowed
311
- });
319
+ react_native_1.Alert.alert('Video Completed Successfully', `Duration: ${(actualDuration / 1000).toFixed(1)}s\nFrames: ${result.frames.length}\nChallenges: ${completedChallenges.length}/${challenges.length}\nInstructions followed: ${result.instructionsFollowed ? 'Yes' : 'No'}\nQuality: ${result.qualityScore.toFixed(0)}%`);
320
+ isRecordingRef.current = false;
312
321
  onComplete(result);
313
322
  }
314
323
  catch (error) {
315
- console.error('Error processing video:', error);
324
+ react_native_1.Alert.alert('Error Processing Video', `Error: ${error instanceof Error ? error.message : String(error)}`);
316
325
  setPhase('recording');
326
+ setOverallProgress(0);
317
327
  handleRecordingError(error);
318
328
  }
319
- }, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs]);
329
+ }, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs, phase]);
320
330
  const startFrameCapture = (0, react_1.useCallback)(() => {
321
331
  if (cameraRef.current && device) {
332
+ react_native_1.Alert.alert('Frame Capture', 'Starting frame capture mode');
322
333
  frameCaptureInterval.current = setInterval(async () => {
323
334
  try {
324
335
  const photo = await cameraRef.current?.takePhoto({
@@ -346,44 +357,82 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
346
357
  }
347
358
  }
348
359
  catch (error) {
349
- console.warn('Frame capture error:', error);
360
+ react_native_1.Alert.alert('Frame Capture Error', `Error: ${error instanceof Error ? error.message : String(error)}`);
350
361
  }
351
362
  }, 100);
352
363
  }
353
364
  }, [device]);
354
365
  const stopRecording = (0, react_1.useCallback)(async () => {
355
- console.log('Stopping recording...');
356
- isRecordingRef.current = false;
366
+ react_native_1.Alert.alert('Stop Recording', 'Stopping recording...');
367
+ if (frameCaptureInterval.current) {
368
+ clearInterval(frameCaptureInterval.current);
369
+ frameCaptureInterval.current = null;
370
+ }
357
371
  if (videoRecordingRef.current) {
358
372
  try {
359
- console.log('Stopping video recording');
360
- await videoRecordingRef.current.stop();
361
- console.log('Video recording stopped');
373
+ react_native_1.Alert.alert('Stop Recording', 'Stopping video recording...');
374
+ const recording = videoRecordingRef.current;
375
+ videoRecordingRef.current = null;
376
+ isRecordingRef.current = false;
377
+ await recording.stop();
378
+ react_native_1.Alert.alert('Recording Stopped', 'Video recording stopped\nWaiting for onRecordingFinished callback');
362
379
  }
363
380
  catch (error) {
364
- console.error('Error stopping video recording:', error);
381
+ react_native_1.Alert.alert('Error Stopping Recording', `Error: ${error instanceof Error ? error.message : String(error)}`);
382
+ isRecordingRef.current = false;
383
+ const actualDuration = Date.now() - recordingStartTime.current;
384
+ if (actualDuration >= minDurationMs && frames.length > 0) {
385
+ react_native_1.Alert.alert('Using Captured Frames', 'Video stopped with error, using captured frames');
386
+ const result = {
387
+ frames,
388
+ duration: actualDuration,
389
+ instructionsFollowed: completedChallenges.length === challenges.length,
390
+ qualityScore: Math.min(100, (frames.length / 30) * 100),
391
+ challengesCompleted: completedChallenges,
392
+ sessionId,
393
+ };
394
+ onComplete(result);
395
+ }
365
396
  }
366
- videoRecordingRef.current = null;
367
397
  }
368
- if (frameCaptureInterval.current) {
369
- clearInterval(frameCaptureInterval.current);
370
- frameCaptureInterval.current = null;
398
+ else {
399
+ isRecordingRef.current = false;
371
400
  }
372
- }, []);
401
+ }, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs]);
373
402
  const runChallenge = (0, react_1.useCallback)((index) => {
374
403
  if (index >= challenges.length) {
404
+ react_native_1.Alert.alert('Challenges Complete', 'All challenges completed, stopping recording');
405
+ setOverallProgress(100);
375
406
  if (isRecordingRef.current) {
376
407
  const elapsed = Date.now() - recordingStartTime.current;
408
+ react_native_1.Alert.alert('Checking Duration', `Elapsed: ${(elapsed / 1000).toFixed(1)}s\nMin required: ${(minDurationMs / 1000).toFixed(1)}s`);
377
409
  if (elapsed < minDurationMs) {
410
+ const remaining = minDurationMs - elapsed;
411
+ react_native_1.Alert.alert('Waiting', `Waiting additional ${(remaining / 1000).toFixed(1)}s to meet minimum duration`);
378
412
  setTimeout(() => {
379
413
  if (isRecordingRef.current) {
380
414
  stopRecording();
381
415
  }
382
- }, minDurationMs - elapsed);
416
+ }, remaining);
383
417
  return;
384
418
  }
385
419
  stopRecording();
386
420
  }
421
+ else {
422
+ const actualDuration = Date.now() - recordingStartTime.current;
423
+ if (actualDuration >= minDurationMs && frames.length > 0) {
424
+ react_native_1.Alert.alert('Completing', 'Recording already stopped, completing with frames');
425
+ const result = {
426
+ frames,
427
+ duration: actualDuration,
428
+ instructionsFollowed: completedChallenges.length === challenges.length,
429
+ qualityScore: Math.min(100, (frames.length / 30) * 100),
430
+ challengesCompleted: completedChallenges,
431
+ sessionId,
432
+ };
433
+ onComplete(result);
434
+ }
435
+ }
387
436
  return;
388
437
  }
389
438
  const challenge = challenges[index];
@@ -428,39 +477,37 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
428
477
  setPhase('recording');
429
478
  recordingStartTime.current = Date.now();
430
479
  isRecordingRef.current = true;
431
- console.log('Starting video recording, total duration:', totalDuration);
480
+ react_native_1.Alert.alert('Start Recording', `Starting video recording\nTotal duration: ${(totalDuration / 1000).toFixed(1)}s`);
432
481
  if (cameraRef.current && device) {
433
482
  try {
434
483
  videoRecordingRef.current = await cameraRef.current.startRecording({
435
484
  flash: 'off',
436
485
  onRecordingFinished: (video) => {
437
- console.log('Video recording finished callback called', video);
438
- if (isRecordingRef.current) {
439
- handleVideoComplete(video);
440
- }
486
+ react_native_1.Alert.alert('Recording Finished', `Video recording finished callback called\nPath: ${video?.path || 'N/A'}`);
487
+ handleVideoComplete(video);
441
488
  },
442
489
  onRecordingError: (error) => {
443
- console.error('Recording error:', error);
490
+ react_native_1.Alert.alert('Recording Error', `Error: ${error instanceof Error ? error.message : String(error)}`);
444
491
  handleRecordingError(error);
445
492
  },
446
493
  });
447
- console.log('Video recording started successfully');
494
+ react_native_1.Alert.alert('Recording Started', 'Video recording started successfully');
448
495
  }
449
496
  catch (error) {
450
- console.warn('Video recording not available, falling back to frame capture:', error);
497
+ react_native_1.Alert.alert('Recording Fallback', `Video recording not available, falling back to frame capture\nError: ${error}`);
451
498
  startFrameCapture();
452
499
  }
453
500
  }
454
501
  else {
455
- console.log('Camera not available, using frame capture');
502
+ react_native_1.Alert.alert('Camera Not Available', 'Camera not available, using frame capture');
456
503
  startFrameCapture();
457
504
  }
458
505
  runChallenge(0);
459
506
  const timeoutId = setTimeout(() => {
460
- console.log('Recording timeout reached, stopping recording');
507
+ react_native_1.Alert.alert('Timeout', 'Recording timeout reached, stopping recording');
461
508
  if (isRecordingRef.current) {
462
509
  stopRecording().catch(err => {
463
- console.error('Error stopping recording on timeout:', err);
510
+ react_native_1.Alert.alert('Timeout Error', `Error stopping recording on timeout: ${err}`);
464
511
  });
465
512
  }
466
513
  }, totalDuration);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -39,6 +39,7 @@ export interface BiometricIdentityFlowProps {
39
39
  language?: SupportedLanguage;
40
40
  customTranslations?: Record<string, string>;
41
41
  smartLivenessMode?: boolean;
42
+ routeBack?: () => void;
42
43
  styles?: {
43
44
  container?: ViewStyle;
44
45
  content?: ViewStyle;
@@ -52,6 +53,7 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
52
53
  language,
53
54
  customTranslations,
54
55
  smartLivenessMode = true,
56
+ routeBack,
55
57
  styles: customStyles,
56
58
  }) => {
57
59
  const {
@@ -194,11 +196,7 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
194
196
  theme={theme}
195
197
  language={language}
196
198
  onStart={() => setShowInstructions(false)}
197
- onCancel={onError ? () => onError({
198
- name: 'BiometricError',
199
- message: 'User cancelled',
200
- code: BiometricErrorCode.USER_CANCELLED,
201
- } as BiometricError) : undefined}
199
+ routeBack={routeBack}
202
200
  styles={customStyles}
203
201
  />
204
202
  );
@@ -18,7 +18,7 @@ export interface InstructionsScreenProps {
18
18
  theme?: ThemeConfig;
19
19
  language?: SupportedLanguage;
20
20
  onStart: () => void;
21
- onCancel?: () => void;
21
+ routeBack?: () => void;
22
22
  styles?: {
23
23
  container?: ViewStyle;
24
24
  content?: ViewStyle;
@@ -29,7 +29,7 @@ export const InstructionsScreen: React.FC<InstructionsScreenProps> = ({
29
29
  theme,
30
30
  language = 'en',
31
31
  onStart,
32
- onCancel,
32
+ routeBack,
33
33
  styles: customStyles,
34
34
  }) => {
35
35
  const [strings, setStrings] = useState(() => {
@@ -55,6 +55,18 @@ export const InstructionsScreen: React.FC<InstructionsScreenProps> = ({
55
55
 
56
56
  return (
57
57
  <View style={[styles.container, customStyles?.container]}>
58
+ {routeBack && (
59
+ <View style={styles.header}>
60
+ <TouchableOpacity
61
+ style={styles.backButton}
62
+ onPress={routeBack}
63
+ >
64
+ <Text style={[styles.backButtonText, { color: theme?.textColor || '#000000' }]}>
65
+ {strings.common.back || 'Volver'}
66
+ </Text>
67
+ </TouchableOpacity>
68
+ </View>
69
+ )}
58
70
  <ScrollView contentContainerStyle={[styles.content, customStyles?.content]}>
59
71
  <Text style={[styles.title, { color: theme?.textColor || '#000000' }]}>
60
72
  {strings.instructions.title}
@@ -104,27 +116,12 @@ export const InstructionsScreen: React.FC<InstructionsScreenProps> = ({
104
116
  </View>
105
117
  </ScrollView>
106
118
 
107
- {/* Footer Buttons */}
119
+ {/* Start Button */}
108
120
  <View style={styles.footer}>
109
- {onCancel && (
110
- <TouchableOpacity
111
- style={[
112
- styles.button,
113
- styles.cancelButton,
114
- { borderColor: theme?.errorColor || '#EF4444' },
115
- ]}
116
- onPress={onCancel}
117
- >
118
- <Text style={[styles.buttonText, { color: theme?.errorColor || '#EF4444' }]}>
119
- {strings.common.cancel || 'Cancel'}
120
- </Text>
121
- </TouchableOpacity>
122
- )}
123
121
  <TouchableOpacity
124
122
  style={[
125
123
  styles.button,
126
124
  { backgroundColor: theme?.primaryColor || '#6366F1' },
127
- onCancel && styles.startButton,
128
125
  ]}
129
126
  onPress={onStart}
130
127
  >
@@ -205,6 +202,22 @@ const styles = StyleSheet.create({
205
202
  flex: 1,
206
203
  backgroundColor: '#FFFFFF',
207
204
  },
205
+ header: {
206
+ paddingTop: 16,
207
+ paddingHorizontal: 16,
208
+ paddingBottom: 8,
209
+ borderBottomWidth: 1,
210
+ borderBottomColor: '#E5E7EB',
211
+ },
212
+ backButton: {
213
+ paddingVertical: 8,
214
+ paddingHorizontal: 12,
215
+ alignSelf: 'flex-start',
216
+ },
217
+ backButtonText: {
218
+ fontSize: 16,
219
+ fontWeight: '600',
220
+ },
208
221
  content: {
209
222
  padding: 24,
210
223
  },
@@ -252,22 +265,12 @@ const styles = StyleSheet.create({
252
265
  padding: 24,
253
266
  borderTopWidth: 1,
254
267
  borderTopColor: '#E5E7EB',
255
- flexDirection: 'row',
256
- gap: 12,
257
268
  },
258
269
  button: {
259
270
  paddingVertical: 16,
260
271
  paddingHorizontal: 32,
261
272
  borderRadius: 8,
262
273
  alignItems: 'center',
263
- flex: 1,
264
- },
265
- cancelButton: {
266
- borderWidth: 2,
267
- backgroundColor: 'transparent',
268
- },
269
- startButton: {
270
- flex: 1,
271
274
  },
272
275
  buttonText: {
273
276
  color: '#FFFFFF',
@@ -328,16 +328,26 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
328
328
  }, [onCancel]);
329
329
 
330
330
  const handleVideoComplete = useCallback(async (video: any) => {
331
- console.log('handleVideoComplete called with video:', video?.path);
331
+ Alert.alert('Video Complete', `handleVideoComplete called\nVideo path: ${video?.path || 'N/A'}`);
332
+
333
+ if (!isRecordingRef.current && phase !== 'processing') {
334
+ Alert.alert('Info', 'Video already processed, ignoring duplicate callback');
335
+ return;
336
+ }
332
337
 
333
338
  try {
334
339
  setPhase('processing');
335
- const actualDuration = Date.now() - recordingStartTime.current;
340
+ setOverallProgress(100);
336
341
 
337
- console.log('Video duration:', actualDuration, 'min required:', minDurationMs);
342
+ const actualDuration = Date.now() - recordingStartTime.current;
343
+ Alert.alert(
344
+ 'Video Processing',
345
+ `Duration: ${(actualDuration / 1000).toFixed(1)}s\nMin required: ${(minDurationMs / 1000).toFixed(1)}s\nFrames: ${frames.length}`
346
+ );
338
347
 
339
348
  if (actualDuration < minDurationMs) {
340
349
  setPhase('recording');
350
+ setOverallProgress(0);
341
351
  Alert.alert(
342
352
  strings.errors.videoTooShort?.title || 'Recording Too Short',
343
353
  strings.errors.videoTooShort?.message || `Video must be at least ${minDurationMs / 1000} seconds. Please try again.`,
@@ -351,38 +361,48 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
351
361
  try {
352
362
  const RNFS = require('react-native-fs');
353
363
  videoBase64 = await RNFS.readFile(video.path, 'base64');
354
- console.log('Video file read successfully, size:', videoBase64.length);
364
+ Alert.alert('Video File Read', `Successfully read video file\nSize: ${videoBase64.length} bytes`);
355
365
  } catch (fsError) {
356
- console.warn('Could not read video file, using captured frames:', fsError);
366
+ Alert.alert('Video File Warning', `Could not read video file, using captured frames\nError: ${fsError}`);
357
367
  }
358
368
  }
359
369
 
370
+ const finalFrames = frames.length > 0 ? frames : (videoBase64 ? [videoBase64] : []);
371
+
372
+ if (finalFrames.length === 0) {
373
+ Alert.alert('Error', 'No frames available, cannot complete');
374
+ setPhase('recording');
375
+ handleRecordingError(new Error('No video frames captured'));
376
+ return;
377
+ }
378
+
360
379
  const result: VideoRecordingResult = {
361
- frames: frames.length > 0 ? frames : (videoBase64 ? [videoBase64] : []),
380
+ frames: finalFrames,
362
381
  duration: actualDuration,
363
382
  instructionsFollowed: completedChallenges.length === challenges.length,
364
- qualityScore: frames.length > 0 ? Math.min(100, (frames.length / 30) * 100) : 85,
383
+ qualityScore: finalFrames.length > 0 ? Math.min(100, (finalFrames.length / 30) * 100) : 85,
365
384
  challengesCompleted: completedChallenges,
366
385
  sessionId,
367
386
  };
368
387
 
369
- console.log('Video recording completed successfully:', {
370
- duration: actualDuration,
371
- frames: result.frames.length,
372
- challengesCompleted: completedChallenges.length,
373
- instructionsFollowed: result.instructionsFollowed
374
- });
388
+ Alert.alert(
389
+ 'Video Completed Successfully',
390
+ `Duration: ${(actualDuration / 1000).toFixed(1)}s\nFrames: ${result.frames.length}\nChallenges: ${completedChallenges.length}/${challenges.length}\nInstructions followed: ${result.instructionsFollowed ? 'Yes' : 'No'}\nQuality: ${result.qualityScore.toFixed(0)}%`
391
+ );
375
392
 
393
+ isRecordingRef.current = false;
376
394
  onComplete(result);
377
395
  } catch (error) {
378
- console.error('Error processing video:', error);
396
+ Alert.alert('Error Processing Video', `Error: ${error instanceof Error ? error.message : String(error)}`);
379
397
  setPhase('recording');
398
+ setOverallProgress(0);
380
399
  handleRecordingError(error);
381
400
  }
382
- }, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs]);
401
+ }, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs, phase]);
383
402
 
384
403
  const startFrameCapture = useCallback(() => {
385
404
  if (cameraRef.current && device) {
405
+ Alert.alert('Frame Capture', 'Starting frame capture mode');
386
406
  frameCaptureInterval.current = setInterval(async () => {
387
407
  try {
388
408
  const photo = await cameraRef.current?.takePhoto({
@@ -409,46 +429,90 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
409
429
  }
410
430
  }
411
431
  } catch (error) {
412
- console.warn('Frame capture error:', error);
432
+ Alert.alert('Frame Capture Error', `Error: ${error instanceof Error ? error.message : String(error)}`);
413
433
  }
414
434
  }, 100);
415
435
  }
416
436
  }, [device]);
417
437
 
418
438
  const stopRecording = useCallback(async () => {
419
- console.log('Stopping recording...');
420
- isRecordingRef.current = false;
439
+ Alert.alert('Stop Recording', 'Stopping recording...');
440
+
441
+ if (frameCaptureInterval.current) {
442
+ clearInterval(frameCaptureInterval.current);
443
+ frameCaptureInterval.current = null;
444
+ }
421
445
 
422
446
  if (videoRecordingRef.current) {
423
447
  try {
424
- console.log('Stopping video recording');
425
- await videoRecordingRef.current.stop();
426
- console.log('Video recording stopped');
448
+ Alert.alert('Stop Recording', 'Stopping video recording...');
449
+ const recording = videoRecordingRef.current;
450
+ videoRecordingRef.current = null;
451
+ isRecordingRef.current = false;
452
+
453
+ await recording.stop();
454
+ Alert.alert('Recording Stopped', 'Video recording stopped\nWaiting for onRecordingFinished callback');
427
455
  } catch (error) {
428
- console.error('Error stopping video recording:', error);
456
+ Alert.alert('Error Stopping Recording', `Error: ${error instanceof Error ? error.message : String(error)}`);
457
+ isRecordingRef.current = false;
458
+
459
+ const actualDuration = Date.now() - recordingStartTime.current;
460
+ if (actualDuration >= minDurationMs && frames.length > 0) {
461
+ Alert.alert('Using Captured Frames', 'Video stopped with error, using captured frames');
462
+ const result: VideoRecordingResult = {
463
+ frames,
464
+ duration: actualDuration,
465
+ instructionsFollowed: completedChallenges.length === challenges.length,
466
+ qualityScore: Math.min(100, (frames.length / 30) * 100),
467
+ challengesCompleted: completedChallenges,
468
+ sessionId,
469
+ };
470
+ onComplete(result);
471
+ }
429
472
  }
430
- videoRecordingRef.current = null;
431
- }
432
-
433
- if (frameCaptureInterval.current) {
434
- clearInterval(frameCaptureInterval.current);
435
- frameCaptureInterval.current = null;
473
+ } else {
474
+ isRecordingRef.current = false;
436
475
  }
437
- }, []);
476
+ }, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs]);
438
477
 
439
478
  const runChallenge = useCallback((index: number) => {
440
479
  if (index >= challenges.length) {
480
+ Alert.alert('Challenges Complete', 'All challenges completed, stopping recording');
481
+ setOverallProgress(100);
482
+
441
483
  if (isRecordingRef.current) {
442
484
  const elapsed = Date.now() - recordingStartTime.current;
485
+ Alert.alert(
486
+ 'Checking Duration',
487
+ `Elapsed: ${(elapsed / 1000).toFixed(1)}s\nMin required: ${(minDurationMs / 1000).toFixed(1)}s`
488
+ );
489
+
443
490
  if (elapsed < minDurationMs) {
491
+ const remaining = minDurationMs - elapsed;
492
+ Alert.alert('Waiting', `Waiting additional ${(remaining / 1000).toFixed(1)}s to meet minimum duration`);
444
493
  setTimeout(() => {
445
494
  if (isRecordingRef.current) {
446
495
  stopRecording();
447
496
  }
448
- }, minDurationMs - elapsed);
497
+ }, remaining);
449
498
  return;
450
499
  }
500
+
451
501
  stopRecording();
502
+ } else {
503
+ const actualDuration = Date.now() - recordingStartTime.current;
504
+ if (actualDuration >= minDurationMs && frames.length > 0) {
505
+ Alert.alert('Completing', 'Recording already stopped, completing with frames');
506
+ const result: VideoRecordingResult = {
507
+ frames,
508
+ duration: actualDuration,
509
+ instructionsFollowed: completedChallenges.length === challenges.length,
510
+ qualityScore: Math.min(100, (frames.length / 30) * 100),
511
+ challengesCompleted: completedChallenges,
512
+ sessionId,
513
+ };
514
+ onComplete(result);
515
+ }
452
516
  }
453
517
  return;
454
518
  }
@@ -505,40 +569,38 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
505
569
  recordingStartTime.current = Date.now();
506
570
  isRecordingRef.current = true;
507
571
 
508
- console.log('Starting video recording, total duration:', totalDuration);
572
+ Alert.alert('Start Recording', `Starting video recording\nTotal duration: ${(totalDuration / 1000).toFixed(1)}s`);
509
573
 
510
574
  if (cameraRef.current && device) {
511
575
  try {
512
576
  videoRecordingRef.current = await cameraRef.current.startRecording({
513
577
  flash: 'off',
514
578
  onRecordingFinished: (video: any) => {
515
- console.log('Video recording finished callback called', video);
516
- if (isRecordingRef.current) {
517
- handleVideoComplete(video);
518
- }
579
+ Alert.alert('Recording Finished', `Video recording finished callback called\nPath: ${video?.path || 'N/A'}`);
580
+ handleVideoComplete(video);
519
581
  },
520
582
  onRecordingError: (error: any) => {
521
- console.error('Recording error:', error);
583
+ Alert.alert('Recording Error', `Error: ${error instanceof Error ? error.message : String(error)}`);
522
584
  handleRecordingError(error);
523
585
  },
524
586
  });
525
- console.log('Video recording started successfully');
587
+ Alert.alert('Recording Started', 'Video recording started successfully');
526
588
  } catch (error) {
527
- console.warn('Video recording not available, falling back to frame capture:', error);
589
+ Alert.alert('Recording Fallback', `Video recording not available, falling back to frame capture\nError: ${error}`);
528
590
  startFrameCapture();
529
591
  }
530
592
  } else {
531
- console.log('Camera not available, using frame capture');
593
+ Alert.alert('Camera Not Available', 'Camera not available, using frame capture');
532
594
  startFrameCapture();
533
595
  }
534
596
 
535
597
  runChallenge(0);
536
598
 
537
599
  const timeoutId = setTimeout(() => {
538
- console.log('Recording timeout reached, stopping recording');
600
+ Alert.alert('Timeout', 'Recording timeout reached, stopping recording');
539
601
  if (isRecordingRef.current) {
540
602
  stopRecording().catch(err => {
541
- console.error('Error stopping recording on timeout:', err);
603
+ Alert.alert('Timeout Error', `Error stopping recording on timeout: ${err}`);
542
604
  });
543
605
  }
544
606
  }, totalDuration);