@hexar/biometric-identity-sdk-react-native 1.0.24 → 1.0.26

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.
@@ -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;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,CA2WtE,CAAC;AA8NF,eAAe,qBAAqB,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;AAuB5C,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,CAyXtE,CAAC;AA8NF,eAAe,qBAAqB,CAAC"}
@@ -44,6 +44,18 @@ const biometric_identity_sdk_core_1 = require("@hexar/biometric-identity-sdk-cor
44
44
  const useBiometricSDK_1 = require("../hooks/useBiometricSDK");
45
45
  const CameraCapture_1 = require("./CameraCapture");
46
46
  const VideoRecorder_1 = require("./VideoRecorder");
47
+ const getInstructionMap = (strings) => ({
48
+ look_left: { text: strings.liveness.instructions.lookLeft || 'Slowly turn your head LEFT' },
49
+ look_right: { text: strings.liveness.instructions.lookRight || 'Slowly turn your head RIGHT' },
50
+ look_up: { text: strings.liveness.instructions.lookUp || 'Look UP' },
51
+ look_down: { text: strings.liveness.instructions.lookDown || 'Look DOWN' },
52
+ turn_head_left: { text: strings.liveness.instructions.turnHeadLeft || 'Turn your head LEFT' },
53
+ turn_head_right: { text: strings.liveness.instructions.turnHeadRight || 'Turn your head RIGHT' },
54
+ smile: { text: strings.liveness.instructions.smile || 'Smile' },
55
+ blink: { text: strings.liveness.instructions.blink || 'Blink your eyes naturally' },
56
+ open_mouth: { text: strings.liveness.instructions.openMouth || 'Open your mouth slightly' },
57
+ stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and follow the instructions' },
58
+ });
47
59
  const ValidationProgress_1 = require("./ValidationProgress");
48
60
  const ResultScreen_1 = require("./ResultScreen");
49
61
  const ErrorScreen_1 = require("./ErrorScreen");
@@ -100,12 +112,26 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
100
112
  }
101
113
  catch (error) {
102
114
  biometric_identity_sdk_core_1.logger.warn('Failed to fetch challenges, using defaults');
103
- setCurrentChallenges(sdk.getDefaultChallenges());
115
+ const defaultChallenges = sdk.getDefaultChallenges();
116
+ const instructionMap = getInstructionMap(strings);
117
+ const translatedChallenges = defaultChallenges.map(challenge => ({
118
+ ...challenge,
119
+ instruction: instructionMap[challenge.action]?.text || challenge.instruction,
120
+ icon: instructionMap[challenge.action]?.icon
121
+ }));
122
+ setCurrentChallenges(translatedChallenges);
104
123
  }
105
124
  setIsLoadingChallenges(false);
106
125
  }
107
126
  else if (mode === 'video' && smartLivenessMode) {
108
- setCurrentChallenges(sdk.getDefaultChallenges());
127
+ const defaultChallenges = sdk.getDefaultChallenges();
128
+ const instructionMap = getInstructionMap(strings);
129
+ const translatedChallenges = defaultChallenges.map(challenge => ({
130
+ ...challenge,
131
+ instruction: instructionMap[challenge.action]?.text || challenge.instruction,
132
+ icon: instructionMap[challenge.action]?.icon
133
+ }));
134
+ setCurrentChallenges(translatedChallenges);
109
135
  }
110
136
  setShowCamera(true);
111
137
  }, [smartLivenessMode, isUsingBackend, fetchChallenges, sdk]);
@@ -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,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,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;AA8CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAo0BtD,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,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,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;AA0CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAs1BtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
@@ -49,41 +49,37 @@ const getDefaultChallenges = (strings) => [
49
49
  instruction: strings.liveness.instructions.lookLeft || 'Slowly turn your head to the LEFT',
50
50
  duration_ms: 2500,
51
51
  order: 1,
52
- icon: '←',
53
52
  },
54
53
  {
55
54
  action: 'look_right',
56
55
  instruction: strings.liveness.instructions.lookRight || 'Slowly turn your head to the RIGHT',
57
56
  duration_ms: 2500,
58
57
  order: 2,
59
- icon: '→',
60
58
  },
61
59
  {
62
60
  action: 'blink',
63
61
  instruction: strings.liveness.instructions.blink || 'Blink your eyes naturally',
64
62
  duration_ms: 2000,
65
63
  order: 3,
66
- icon: '👁',
67
64
  },
68
65
  {
69
66
  action: 'smile',
70
- instruction: strings.liveness.instructions.smile || 'Smile 😊',
67
+ instruction: strings.liveness.instructions.smile || 'Smile',
71
68
  duration_ms: 2000,
72
69
  order: 4,
73
- icon: '😊',
74
70
  },
75
71
  ];
76
72
  const getInstructionMap = (strings) => ({
77
- look_left: { text: strings.liveness.instructions.lookLeft || 'Slowly turn your head LEFT', icon: '←' },
78
- look_right: { text: strings.liveness.instructions.lookRight || 'Slowly turn your head RIGHT', icon: '→' },
79
- look_up: { text: strings.liveness.instructions.lookUp || 'Look UP', icon: '↑' },
80
- look_down: { text: strings.liveness.instructions.lookDown || 'Look DOWN', icon: '↓' },
81
- turn_head_left: { text: strings.liveness.instructions.turnHeadLeft || 'Turn your head LEFT', icon: '←' },
82
- turn_head_right: { text: strings.liveness.instructions.turnHeadRight || 'Turn your head RIGHT', icon: '→' },
83
- smile: { text: strings.liveness.instructions.smile || 'Smile 😊', icon: '😊' },
84
- blink: { text: strings.liveness.instructions.blink || 'Blink your eyes naturally', icon: '👁' },
85
- open_mouth: { text: strings.liveness.instructions.openMouth || 'Open your mouth slightly', icon: '😮' },
86
- stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and follow the instructions', icon: '📷' },
73
+ look_left: { text: strings.liveness.instructions.lookLeft || 'Slowly turn your head LEFT' },
74
+ look_right: { text: strings.liveness.instructions.lookRight || 'Slowly turn your head RIGHT' },
75
+ look_up: { text: strings.liveness.instructions.lookUp || 'Look UP' },
76
+ look_down: { text: strings.liveness.instructions.lookDown || 'Look DOWN' },
77
+ turn_head_left: { text: strings.liveness.instructions.turnHeadLeft || 'Turn your head LEFT' },
78
+ turn_head_right: { text: strings.liveness.instructions.turnHeadRight || 'Turn your head RIGHT' },
79
+ smile: { text: strings.liveness.instructions.smile || 'Smile' },
80
+ blink: { text: strings.liveness.instructions.blink || 'Blink your eyes naturally' },
81
+ open_mouth: { text: strings.liveness.instructions.openMouth || 'Open your mouth slightly' },
82
+ stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and follow the instructions' },
87
83
  });
88
84
  const VideoRecorder = ({ theme, language, duration, instructions, challenges: propChallenges, sessionId, smartMode = true, onComplete, onCancel, onFetchChallenges, }) => {
89
85
  (0, react_1.useEffect)(() => {
@@ -183,7 +179,6 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
183
179
  instruction: currentStrings.liveness.instructions.stayStill || 'Look at the camera and stay still',
184
180
  duration_ms: duration || 5000,
185
181
  order: 1,
186
- icon: '📷',
187
182
  },
188
183
  ];
189
184
  }
@@ -346,11 +341,35 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
346
341
  });
347
342
  if (photo) {
348
343
  consecutiveErrors = 0;
344
+ let base64Data = null;
349
345
  try {
350
346
  const RNFS = require('react-native-fs');
351
- const base64 = await RNFS.readFile(photo.path, 'base64');
347
+ base64Data = await RNFS.readFile(photo.path, 'base64');
348
+ }
349
+ catch (fsError) {
350
+ try {
351
+ const fileUri = react_native_1.Platform.OS === 'android' ? `file://${photo.path}` : photo.path;
352
+ const response = await fetch(fileUri);
353
+ const blob = await response.blob();
354
+ base64Data = await new Promise((resolve, reject) => {
355
+ const reader = new FileReader();
356
+ reader.onloadend = () => {
357
+ const result = reader.result;
358
+ const base64 = result.includes(',') ? result.split(',')[1] : result;
359
+ resolve(base64);
360
+ };
361
+ reader.onerror = reject;
362
+ reader.readAsDataURL(blob);
363
+ });
364
+ }
365
+ catch (fetchError) {
366
+ biometric_identity_sdk_core_1.logger.error('Failed to read photo file as base64:', fetchError);
367
+ base64Data = null;
368
+ }
369
+ }
370
+ if (base64Data) {
352
371
  setFrames(prev => {
353
- const newFrames = prev.length < 100 ? [...prev, base64] : prev;
372
+ const newFrames = prev.length < 100 ? [...prev, base64Data] : prev;
354
373
  framesRef.current = newFrames;
355
374
  if (newFrames.length % 10 === 0) {
356
375
  biometric_identity_sdk_core_1.logger.info('Captured frames:', newFrames.length);
@@ -358,13 +377,8 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
358
377
  return newFrames;
359
378
  });
360
379
  }
361
- catch (fsError) {
362
- biometric_identity_sdk_core_1.logger.warn('Could not read photo file, using path');
363
- setFrames(prev => {
364
- const newFrames = prev.length < 100 ? [...prev, photo.path] : prev;
365
- framesRef.current = newFrames;
366
- return newFrames;
367
- });
380
+ else {
381
+ biometric_identity_sdk_core_1.logger.warn('Could not convert photo to base64, skipping frame');
368
382
  }
369
383
  }
370
384
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "clean": "rm -rf dist"
12
12
  },
13
13
  "peerDependencies": {
14
- "@hexar/biometric-identity-sdk-core": ">=1.0.14",
14
+ "@hexar/biometric-identity-sdk-core": ">=1.0.16",
15
15
  "react": ">=18.0.0",
16
16
  "react-native": ">=0.70.0",
17
17
  "react-native-permissions": ">=4.0.0",
@@ -28,6 +28,19 @@ import type { ChallengeAction } from './VideoRecorder';
28
28
  import { useBiometricSDK } from '../hooks/useBiometricSDK';
29
29
  import { CameraCapture } from './CameraCapture';
30
30
  import { VideoRecorder, VideoRecordingResult } from './VideoRecorder';
31
+
32
+ const getInstructionMap = (strings: any): Record<string, { text: string; icon?: string }> => ({
33
+ look_left: { text: strings.liveness.instructions.lookLeft || 'Slowly turn your head LEFT' },
34
+ look_right: { text: strings.liveness.instructions.lookRight || 'Slowly turn your head RIGHT' },
35
+ look_up: { text: strings.liveness.instructions.lookUp || 'Look UP' },
36
+ look_down: { text: strings.liveness.instructions.lookDown || 'Look DOWN' },
37
+ turn_head_left: { text: strings.liveness.instructions.turnHeadLeft || 'Turn your head LEFT' },
38
+ turn_head_right: { text: strings.liveness.instructions.turnHeadRight || 'Turn your head RIGHT' },
39
+ smile: { text: strings.liveness.instructions.smile || 'Smile' },
40
+ blink: { text: strings.liveness.instructions.blink || 'Blink your eyes naturally' },
41
+ open_mouth: { text: strings.liveness.instructions.openMouth || 'Open your mouth slightly' },
42
+ stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and follow the instructions' },
43
+ });
31
44
  import { ValidationProgress } from './ValidationProgress';
32
45
  import { ResultScreen } from './ResultScreen';
33
46
  import { ErrorScreen } from './ErrorScreen';
@@ -129,11 +142,25 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
129
142
  setCurrentChallenges(challenges);
130
143
  } catch (error) {
131
144
  logger.warn('Failed to fetch challenges, using defaults');
132
- setCurrentChallenges(sdk.getDefaultChallenges());
145
+ const defaultChallenges = sdk.getDefaultChallenges();
146
+ const instructionMap = getInstructionMap(strings);
147
+ const translatedChallenges = defaultChallenges.map(challenge => ({
148
+ ...challenge,
149
+ instruction: instructionMap[challenge.action]?.text || challenge.instruction,
150
+ icon: instructionMap[challenge.action]?.icon
151
+ }));
152
+ setCurrentChallenges(translatedChallenges);
133
153
  }
134
154
  setIsLoadingChallenges(false);
135
155
  } else if (mode === 'video' && smartLivenessMode) {
136
- setCurrentChallenges(sdk.getDefaultChallenges());
156
+ const defaultChallenges = sdk.getDefaultChallenges();
157
+ const instructionMap = getInstructionMap(strings);
158
+ const translatedChallenges = defaultChallenges.map(challenge => ({
159
+ ...challenge,
160
+ instruction: instructionMap[challenge.action]?.text || challenge.instruction,
161
+ icon: instructionMap[challenge.action]?.icon
162
+ }));
163
+ setCurrentChallenges(translatedChallenges);
137
164
  }
138
165
 
139
166
  setShowCamera(true);
@@ -62,42 +62,38 @@ const getDefaultChallenges = (strings: any): ChallengeAction[] => [
62
62
  instruction: strings.liveness.instructions.lookLeft || 'Slowly turn your head to the LEFT',
63
63
  duration_ms: 2500,
64
64
  order: 1,
65
- icon: '←',
66
65
  },
67
66
  {
68
67
  action: 'look_right',
69
68
  instruction: strings.liveness.instructions.lookRight || 'Slowly turn your head to the RIGHT',
70
69
  duration_ms: 2500,
71
70
  order: 2,
72
- icon: '→',
73
71
  },
74
72
  {
75
73
  action: 'blink',
76
74
  instruction: strings.liveness.instructions.blink || 'Blink your eyes naturally',
77
75
  duration_ms: 2000,
78
76
  order: 3,
79
- icon: '👁',
80
77
  },
81
78
  {
82
79
  action: 'smile',
83
- instruction: strings.liveness.instructions.smile || 'Smile 😊',
80
+ instruction: strings.liveness.instructions.smile || 'Smile',
84
81
  duration_ms: 2000,
85
82
  order: 4,
86
- icon: '😊',
87
83
  },
88
84
  ];
89
85
 
90
- const getInstructionMap = (strings: any): Record<string, { text: string; icon: string }> => ({
91
- look_left: { text: strings.liveness.instructions.lookLeft || 'Slowly turn your head LEFT', icon: '←' },
92
- look_right: { text: strings.liveness.instructions.lookRight || 'Slowly turn your head RIGHT', icon: '→' },
93
- look_up: { text: strings.liveness.instructions.lookUp || 'Look UP', icon: '↑' },
94
- look_down: { text: strings.liveness.instructions.lookDown || 'Look DOWN', icon: '↓' },
95
- turn_head_left: { text: strings.liveness.instructions.turnHeadLeft || 'Turn your head LEFT', icon: '←' },
96
- turn_head_right: { text: strings.liveness.instructions.turnHeadRight || 'Turn your head RIGHT', icon: '→' },
97
- smile: { text: strings.liveness.instructions.smile || 'Smile 😊', icon: '😊' },
98
- blink: { text: strings.liveness.instructions.blink || 'Blink your eyes naturally', icon: '👁' },
99
- open_mouth: { text: strings.liveness.instructions.openMouth || 'Open your mouth slightly', icon: '😮' },
100
- stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and follow the instructions', icon: '📷' },
86
+ const getInstructionMap = (strings: any): Record<string, { text: string; icon?: string }> => ({
87
+ look_left: { text: strings.liveness.instructions.lookLeft || 'Slowly turn your head LEFT' },
88
+ look_right: { text: strings.liveness.instructions.lookRight || 'Slowly turn your head RIGHT' },
89
+ look_up: { text: strings.liveness.instructions.lookUp || 'Look UP' },
90
+ look_down: { text: strings.liveness.instructions.lookDown || 'Look DOWN' },
91
+ turn_head_left: { text: strings.liveness.instructions.turnHeadLeft || 'Turn your head LEFT' },
92
+ turn_head_right: { text: strings.liveness.instructions.turnHeadRight || 'Turn your head RIGHT' },
93
+ smile: { text: strings.liveness.instructions.smile || 'Smile' },
94
+ blink: { text: strings.liveness.instructions.blink || 'Blink your eyes naturally' },
95
+ open_mouth: { text: strings.liveness.instructions.openMouth || 'Open your mouth slightly' },
96
+ stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and follow the instructions' },
101
97
  });
102
98
 
103
99
  export const VideoRecorder: React.FC<VideoRecorderProps> = ({
@@ -213,17 +209,16 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
213
209
  order: idx + 1,
214
210
  icon: instructionMap[inst]?.icon,
215
211
  }));
216
- } else {
217
- challengeList = smartMode ? getDefaultChallenges(currentStrings) : [
218
- {
219
- action: 'stay_still',
220
- instruction: currentStrings.liveness.instructions.stayStill || 'Look at the camera and stay still',
221
- duration_ms: duration || 5000,
222
- order: 1,
223
- icon: '📷',
224
- },
225
- ];
226
- }
212
+ } else {
213
+ challengeList = smartMode ? getDefaultChallenges(currentStrings) : [
214
+ {
215
+ action: 'stay_still',
216
+ instruction: currentStrings.liveness.instructions.stayStill || 'Look at the camera and stay still',
217
+ duration_ms: duration || 5000,
218
+ order: 1,
219
+ },
220
+ ];
221
+ }
227
222
 
228
223
  setChallenges(challengeList);
229
224
  setPhase('countdown');
@@ -416,24 +411,43 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
416
411
 
417
412
  if (photo) {
418
413
  consecutiveErrors = 0;
414
+ let base64Data: string | null = null;
415
+
419
416
  try {
420
417
  const RNFS = require('react-native-fs');
421
- const base64 = await RNFS.readFile(photo.path, 'base64');
418
+ base64Data = await RNFS.readFile(photo.path, 'base64');
419
+ } catch (fsError) {
420
+ try {
421
+ const fileUri = Platform.OS === 'android' ? `file://${photo.path}` : photo.path;
422
+ const response = await fetch(fileUri);
423
+ const blob = await response.blob();
424
+ base64Data = await new Promise<string>((resolve, reject) => {
425
+ const reader = new FileReader();
426
+ reader.onloadend = () => {
427
+ const result = reader.result as string;
428
+ const base64 = result.includes(',') ? result.split(',')[1] : result;
429
+ resolve(base64);
430
+ };
431
+ reader.onerror = reject;
432
+ reader.readAsDataURL(blob);
433
+ });
434
+ } catch (fetchError) {
435
+ logger.error('Failed to read photo file as base64:', fetchError);
436
+ base64Data = null;
437
+ }
438
+ }
439
+
440
+ if (base64Data) {
422
441
  setFrames(prev => {
423
- const newFrames = prev.length < 100 ? [...prev, base64] : prev;
442
+ const newFrames = prev.length < 100 ? [...prev, base64Data!] : prev;
424
443
  framesRef.current = newFrames;
425
444
  if (newFrames.length % 10 === 0) {
426
445
  logger.info('Captured frames:', newFrames.length);
427
446
  }
428
447
  return newFrames;
429
448
  });
430
- } catch (fsError) {
431
- logger.warn('Could not read photo file, using path');
432
- setFrames(prev => {
433
- const newFrames = prev.length < 100 ? [...prev, photo.path] : prev;
434
- framesRef.current = newFrames;
435
- return newFrames;
436
- });
449
+ } else {
450
+ logger.warn('Could not convert photo to base64, skipping frame');
437
451
  }
438
452
  }
439
453
  } catch (error: any) {