@hexar/biometric-identity-sdk-react-native 1.17.0 → 1.18.0

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":"CameraCapture.d.ts","sourceRoot":"","sources":["../../src/components/CameraCapture.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAsC,MAAM,OAAO,CAAC;AAa3D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmC,MAAM,oCAAoC,CAAC;AAIrH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqQtD,CAAC;AA0JF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"CameraCapture.d.ts","sourceRoot":"","sources":["../../src/components/CameraCapture.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmC,MAAM,oCAAoC,CAAC;AAIrH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAoStD,CAAC;AA0JF,eAAe,aAAa,CAAC"}
@@ -68,6 +68,24 @@ const CameraCapture = ({ mode, theme, language, onCapture, onCancel, }) => {
68
68
  const timeout = setTimeout(() => setDeviceReady(true), 5000);
69
69
  return () => clearTimeout(timeout);
70
70
  }, [device]);
71
+ // Trigger autofocus on the center of the viewfinder.
72
+ // Some Android devices (e.g. Ulefone Armor series) don't auto-focus
73
+ // without an explicit programmatic trigger.
74
+ const triggerFocus = (0, react_1.useCallback)(() => {
75
+ try {
76
+ cameraRef.current?.focus({ x: width / 2, y: height / 2 });
77
+ }
78
+ catch {
79
+ // focus() not supported or camera not ready — safe to ignore
80
+ }
81
+ }, []);
82
+ // Trigger focus once camera is ready
83
+ (0, react_1.useEffect)(() => {
84
+ if (device && hasPermission) {
85
+ const timer = setTimeout(triggerFocus, 500);
86
+ return () => clearTimeout(timer);
87
+ }
88
+ }, [device, hasPermission, triggerFocus]);
71
89
  const checkPermissions = async () => {
72
90
  try {
73
91
  // First check if we already have permission from the hook
@@ -99,11 +117,22 @@ const CameraCapture = ({ mode, theme, language, onCapture, onCancel, }) => {
99
117
  react_native_1.Alert.alert('Camera Permission Required', errorMsg);
100
118
  }
101
119
  };
120
+ const handleTapToFocus = (0, react_1.useCallback)((evt) => {
121
+ try {
122
+ cameraRef.current?.focus({ x: evt.nativeEvent.locationX, y: evt.nativeEvent.locationY });
123
+ }
124
+ catch {
125
+ // ignore — device may not support tap-to-focus
126
+ }
127
+ }, []);
102
128
  const handleCapture = async () => {
103
129
  if (isCapturing || !cameraRef.current || !device)
104
130
  return;
105
131
  try {
106
132
  setIsCapturing(true);
133
+ // Trigger focus before capture to ensure sharpness
134
+ triggerFocus();
135
+ await new Promise(r => setTimeout(r, 300));
107
136
  // Capture photo using react-native-vision-camera
108
137
  const photo = await cameraRef.current.takePhoto({
109
138
  flash: 'off',
@@ -195,9 +224,9 @@ const CameraCapture = ({ mode, theme, language, onCapture, onCancel, }) => {
195
224
  react_1.default.createElement(react_native_1.Text, { style: styles.buttonText }, strings.common.cancel || 'Cancel')))));
196
225
  }
197
226
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
198
- react_1.default.createElement(react_native_1.View, { style: styles.cameraContainer },
227
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.cameraContainer, activeOpacity: 1, onPress: handleTapToFocus },
199
228
  react_1.default.createElement(react_native_vision_camera_1.Camera, { ref: cameraRef, style: react_native_1.StyleSheet.absoluteFill, device: device, isActive: true, photo: true }),
200
- react_1.default.createElement(react_native_1.View, { style: styles.overlay },
229
+ react_1.default.createElement(react_native_1.View, { style: styles.overlay, pointerEvents: "none" },
201
230
  react_1.default.createElement(react_native_1.View, { style: styles.frameContainer },
202
231
  react_1.default.createElement(react_native_1.View, { style: styles.frame },
203
232
  react_1.default.createElement(react_native_1.View, { style: [styles.corner, styles.cornerTopLeft] }),
@@ -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;AAcxE,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;AA2CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAq+BtD,CAAC;AA4PF,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;AAexE,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;AA2CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAq/BtD,CAAC;AA4PF,eAAe,aAAa,CAAC"}
@@ -116,6 +116,17 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
116
116
  const videoRecordingRef = (0, react_1.useRef)(null);
117
117
  const isRecordingRef = (0, react_1.useRef)(false);
118
118
  const recordingTimeoutRef = (0, react_1.useRef)(null);
119
+ // Trigger autofocus on center — some Android devices (e.g. Ulefone Armor)
120
+ // don't auto-focus without an explicit programmatic trigger.
121
+ const triggerFocus = (0, react_1.useCallback)(() => {
122
+ try {
123
+ const { width: w, height: h } = react_native_1.Dimensions.get('window');
124
+ cameraRef.current?.focus({ x: w / 2, y: h / 2 });
125
+ }
126
+ catch {
127
+ // focus() not supported or camera not ready — safe to ignore
128
+ }
129
+ }, []);
119
130
  const minDurationMs = 8000;
120
131
  const totalDuration = duration || Math.max(minDurationMs, challenges.reduce((sum, c) => sum + c.duration_ms, 0) + 2000);
121
132
  (0, react_1.useEffect)(() => {
@@ -220,6 +231,9 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
220
231
  (0, react_1.useEffect)(() => {
221
232
  if (phase !== 'countdown')
222
233
  return;
234
+ // Trigger focus at the start of countdown so the camera has ~3s to lock focus
235
+ if (countdown === 3)
236
+ triggerFocus();
223
237
  if (countdown > 0) {
224
238
  react_native_1.Animated.sequence([
225
239
  react_native_1.Animated.timing(scaleAnim, {
@@ -639,6 +653,8 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
639
653
  setPhase('recording');
640
654
  recordingStartTime.current = Date.now();
641
655
  isRecordingRef.current = true;
656
+ // Ensure camera is focused before recording
657
+ triggerFocus();
642
658
  // Reset completed challenges ref when starting new recording
643
659
  completedChallengesRef.current = [];
644
660
  setCompletedChallenges([]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -3,7 +3,7 @@
3
3
  * Handles ID document photo capture
4
4
  */
5
5
 
6
- import React, { useState, useRef, useEffect } from 'react';
6
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
7
7
  import {
8
8
  View,
9
9
  Text,
@@ -63,6 +63,25 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
63
63
  return () => clearTimeout(timeout);
64
64
  }, [device]);
65
65
 
66
+ // Trigger autofocus on the center of the viewfinder.
67
+ // Some Android devices (e.g. Ulefone Armor series) don't auto-focus
68
+ // without an explicit programmatic trigger.
69
+ const triggerFocus = useCallback(() => {
70
+ try {
71
+ cameraRef.current?.focus({ x: width / 2, y: height / 2 });
72
+ } catch {
73
+ // focus() not supported or camera not ready — safe to ignore
74
+ }
75
+ }, []);
76
+
77
+ // Trigger focus once camera is ready
78
+ useEffect(() => {
79
+ if (device && hasPermission) {
80
+ const timer = setTimeout(triggerFocus, 500);
81
+ return () => clearTimeout(timer);
82
+ }
83
+ }, [device, hasPermission, triggerFocus]);
84
+
66
85
  const checkPermissions = async () => {
67
86
  try {
68
87
  // First check if we already have permission from the hook
@@ -98,12 +117,24 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
98
117
  }
99
118
  };
100
119
 
120
+ const handleTapToFocus = useCallback((evt: any) => {
121
+ try {
122
+ cameraRef.current?.focus({ x: evt.nativeEvent.locationX, y: evt.nativeEvent.locationY });
123
+ } catch {
124
+ // ignore — device may not support tap-to-focus
125
+ }
126
+ }, []);
127
+
101
128
  const handleCapture = async () => {
102
129
  if (isCapturing || !cameraRef.current || !device) return;
103
130
 
104
131
  try {
105
132
  setIsCapturing(true);
106
133
 
134
+ // Trigger focus before capture to ensure sharpness
135
+ triggerFocus();
136
+ await new Promise(r => setTimeout(r, 300));
137
+
107
138
  // Capture photo using react-native-vision-camera
108
139
  const photo = await cameraRef.current.takePhoto({
109
140
  flash: 'off',
@@ -231,8 +262,8 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
231
262
 
232
263
  return (
233
264
  <View style={styles.container}>
234
- {/* Camera View */}
235
- <View style={styles.cameraContainer}>
265
+ {/* Camera View — tap anywhere to trigger focus */}
266
+ <TouchableOpacity style={styles.cameraContainer} activeOpacity={1} onPress={handleTapToFocus}>
236
267
  <Camera
237
268
  ref={cameraRef}
238
269
  style={StyleSheet.absoluteFill}
@@ -242,7 +273,7 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
242
273
  />
243
274
 
244
275
  {/* Document Frame Overlay */}
245
- <View style={styles.overlay}>
276
+ <View style={styles.overlay} pointerEvents="none">
246
277
  <View style={styles.frameContainer}>
247
278
  <View style={styles.frame}>
248
279
  <View style={[styles.corner, styles.cornerTopLeft]} />
@@ -252,7 +283,7 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
252
283
  </View>
253
284
  </View>
254
285
  </View>
255
- </View>
286
+ </TouchableOpacity>
256
287
 
257
288
  {/* Instructions */}
258
289
  <View style={styles.instructionsContainer}>
@@ -14,6 +14,7 @@ import {
14
14
  Platform,
15
15
  Alert,
16
16
  ActivityIndicator,
17
+ Dimensions,
17
18
  } from 'react-native';
18
19
  import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
19
20
  import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
@@ -146,6 +147,17 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
146
147
  const videoRecordingRef = useRef<any>(null);
147
148
  const isRecordingRef = useRef<boolean>(false);
148
149
  const recordingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
150
+ // Trigger autofocus on center — some Android devices (e.g. Ulefone Armor)
151
+ // don't auto-focus without an explicit programmatic trigger.
152
+ const triggerFocus = useCallback(() => {
153
+ try {
154
+ const { width: w, height: h } = Dimensions.get('window');
155
+ cameraRef.current?.focus({ x: w / 2, y: h / 2 });
156
+ } catch {
157
+ // focus() not supported or camera not ready — safe to ignore
158
+ }
159
+ }, []);
160
+
149
161
  const minDurationMs = 8000;
150
162
  const totalDuration = duration || Math.max(
151
163
  minDurationMs,
@@ -260,6 +272,9 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
260
272
  useEffect(() => {
261
273
  if (phase !== 'countdown') return;
262
274
 
275
+ // Trigger focus at the start of countdown so the camera has ~3s to lock focus
276
+ if (countdown === 3) triggerFocus();
277
+
263
278
  if (countdown > 0) {
264
279
  Animated.sequence([
265
280
  Animated.timing(scaleAnim, {
@@ -745,6 +760,8 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
745
760
  setPhase('recording');
746
761
  recordingStartTime.current = Date.now();
747
762
  isRecordingRef.current = true;
763
+ // Ensure camera is focused before recording
764
+ triggerFocus();
748
765
  // Reset completed challenges ref when starting new recording
749
766
  completedChallengesRef.current = [];
750
767
  setCompletedChallenges([]);