@hexar/biometric-identity-sdk-react-native 1.24.0 → 1.25.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,7 +8,8 @@
8
8
  "scripts": {
9
9
  "build": "tsc && cp -r src/assets dist/",
10
10
  "dev": "tsc --watch",
11
- "clean": "rm -rf dist"
11
+ "clean": "rm -rf dist",
12
+ "postinstall": "node scripts/postinstall.js"
12
13
  },
13
14
  "peerDependencies": {
14
15
  "@hexar/biometric-identity-sdk-core": ">=1.8.0",
@@ -0,0 +1,139 @@
1
+ diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/core/utils/runOnUiThread.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/core/utils/runOnUiThread.kt
2
+ index e96e78e..9b9ddfc 100644
3
+ --- a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/core/utils/runOnUiThread.kt
4
+ +++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/core/utils/runOnUiThread.kt
5
+ @@ -13,9 +13,13 @@ suspend inline fun <T> runOnUiThreadAndWait(crossinline function: () -> T): T {
6
+
7
+ return suspendCancellableCoroutine { continuation ->
8
+ UiThreadUtil.runOnUiThread {
9
+ - if (continuation.isCancelled) throw CancellationException()
10
+ - val result = function()
11
+ - continuation.resume(result)
12
+ + try {
13
+ + if (continuation.isCancelled) throw CancellationException()
14
+ + val result = function()
15
+ + continuation.resume(result)
16
+ + } catch (e: Throwable) {
17
+ + continuation.resumeWithException(e)
18
+ + }
19
+ }
20
+ }
21
+ }
22
+ diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt
23
+ index 059f677..54ead5a 100644
24
+ --- a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt
25
+ +++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt
26
+ @@ -91,7 +91,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
27
+ @ReactMethod
28
+ fun takePhoto(viewTag: Int, options: ReadableMap, promise: Promise) {
29
+ backgroundCoroutineScope.launch {
30
+ - val view = findCameraView(viewTag)
31
+ + val view = try {
32
+ + findCameraView(viewTag)
33
+ + } catch (e: Throwable) {
34
+ + promise.reject("camera/view-not-found", "Camera view has been removed", e)
35
+ + return@launch
36
+ + }
37
+ withPromise(promise) {
38
+ view.takePhoto(options)
39
+ }
40
+ @@ -101,7 +106,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
41
+ @ReactMethod
42
+ fun takeSnapshot(viewTag: Int, jsOptions: ReadableMap, promise: Promise) {
43
+ backgroundCoroutineScope.launch {
44
+ - val view = findCameraView(viewTag)
45
+ + val view = try {
46
+ + findCameraView(viewTag)
47
+ + } catch (e: Throwable) {
48
+ + promise.reject("camera/view-not-found", "Camera view has been removed", e)
49
+ + return@launch
50
+ + }
51
+ runOnUiThread {
52
+ try {
53
+ val options = TakeSnapshotOptions.fromJSValue(reactApplicationContext, jsOptions)
54
+ @@ -118,7 +128,13 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
55
+ @ReactMethod
56
+ fun startRecording(viewTag: Int, jsOptions: ReadableMap, onRecordCallback: Callback) {
57
+ backgroundCoroutineScope.launch {
58
+ - val view = findCameraView(viewTag)
59
+ + val view = try {
60
+ + findCameraView(viewTag)
61
+ + } catch (e: Throwable) {
62
+ + val map = makeErrorMap("camera/view-not-found", "Camera view has been removed", e)
63
+ + onRecordCallback(null, map)
64
+ + return@launch
65
+ + }
66
+ try {
67
+ val options = RecordVideoOptions.fromJSValue(reactApplicationContext, jsOptions)
68
+ view.startRecording(options, onRecordCallback)
69
+ @@ -136,8 +152,13 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
70
+ @ReactMethod
71
+ fun pauseRecording(viewTag: Int, promise: Promise) {
72
+ backgroundCoroutineScope.launch {
73
+ + val view = try {
74
+ + findCameraView(viewTag)
75
+ + } catch (e: Throwable) {
76
+ + promise.reject("camera/view-not-found", "Camera view has been removed", e)
77
+ + return@launch
78
+ + }
79
+ withPromise(promise) {
80
+ - val view = findCameraView(viewTag)
81
+ view.pauseRecording()
82
+ return@withPromise null
83
+ }
84
+ @@ -147,7 +168,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
85
+ @ReactMethod
86
+ fun resumeRecording(viewTag: Int, promise: Promise) {
87
+ backgroundCoroutineScope.launch {
88
+ - val view = findCameraView(viewTag)
89
+ + val view = try {
90
+ + findCameraView(viewTag)
91
+ + } catch (e: Throwable) {
92
+ + promise.reject("camera/view-not-found", "Camera view has been removed", e)
93
+ + return@launch
94
+ + }
95
+ withPromise(promise) {
96
+ view.resumeRecording()
97
+ return@withPromise null
98
+ @@ -158,7 +184,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
99
+ @ReactMethod
100
+ fun stopRecording(viewTag: Int, promise: Promise) {
101
+ backgroundCoroutineScope.launch {
102
+ - val view = findCameraView(viewTag)
103
+ + val view = try {
104
+ + findCameraView(viewTag)
105
+ + } catch (e: Throwable) {
106
+ + promise.reject("camera/view-not-found", "Camera view has been removed", e)
107
+ + return@launch
108
+ + }
109
+ withPromise(promise) {
110
+ view.stopRecording()
111
+ return@withPromise null
112
+ @@ -169,7 +200,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
113
+ @ReactMethod
114
+ fun cancelRecording(viewTag: Int, promise: Promise) {
115
+ backgroundCoroutineScope.launch {
116
+ - val view = findCameraView(viewTag)
117
+ + val view = try {
118
+ + findCameraView(viewTag)
119
+ + } catch (e: Throwable) {
120
+ + promise.reject("camera/view-not-found", "Camera view has been removed", e)
121
+ + return@launch
122
+ + }
123
+ withPromise(promise) {
124
+ view.cancelRecording()
125
+ return@withPromise null
126
+ @@ -180,7 +216,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
127
+ @ReactMethod
128
+ fun focus(viewTag: Int, point: ReadableMap, promise: Promise) {
129
+ backgroundCoroutineScope.launch {
130
+ - val view = findCameraView(viewTag)
131
+ + val view = try {
132
+ + findCameraView(viewTag)
133
+ + } catch (e: Throwable) {
134
+ + promise.reject("camera/view-not-found", "Camera view has been removed", e)
135
+ + return@launch
136
+ + }
137
+ withPromise(promise) {
138
+ view.focus(point)
139
+ return@withPromise null
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall script for @hexar/biometric-identity-sdk-react-native
5
+ *
6
+ * Patches react-native-vision-camera to prevent IllegalViewOperationException
7
+ * crashes on Android when CameraViewModule.findCameraView is called after
8
+ * the React Native view has been unmounted.
9
+ *
10
+ * The patch makes two changes:
11
+ * 1. runOnUiThreadAndWait: wraps the UI-thread lambda in try/catch so
12
+ * exceptions route back to the coroutine via resumeWithException instead
13
+ * of crashing the main thread Looper.
14
+ * 2. CameraViewModule: wraps every findCameraView call in try/catch so
15
+ * the exception becomes a Promise rejection (catchable from JS) instead
16
+ * of an uncaught native crash.
17
+ */
18
+
19
+ const { execSync } = require('child_process');
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ // Walk up from this package to find the consumer's node_modules root.
24
+ // This script runs from:
25
+ // <consumer>/node_modules/@hexar/biometric-identity-sdk-react-native/scripts/
26
+ // We need:
27
+ // <consumer>/node_modules/react-native-vision-camera/
28
+ function findConsumerRoot() {
29
+ let dir = __dirname;
30
+ // Walk up until we find a node_modules parent that contains react-native-vision-camera
31
+ for (let i = 0; i < 10; i++) {
32
+ dir = path.dirname(dir);
33
+ const candidate = path.join(dir, 'react-native-vision-camera');
34
+ if (fs.existsSync(candidate)) {
35
+ return path.dirname(dir); // consumer project root
36
+ }
37
+ // Also check if we're at a node_modules boundary
38
+ if (path.basename(dir) === 'node_modules') {
39
+ return path.dirname(dir); // consumer project root
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+
45
+ function applyPatch() {
46
+ const consumerRoot = findConsumerRoot();
47
+ if (!consumerRoot) {
48
+ // Not installed in a consumer project (e.g. local dev) — skip silently
49
+ return;
50
+ }
51
+
52
+ const visionCameraDir = path.join(consumerRoot, 'node_modules', 'react-native-vision-camera');
53
+ if (!fs.existsSync(visionCameraDir)) {
54
+ // vision-camera not installed — nothing to patch
55
+ return;
56
+ }
57
+
58
+ // Target files to patch
59
+ const runOnUiThreadFile = path.join(
60
+ visionCameraDir,
61
+ 'android/src/main/java/com/mrousavy/camera/core/utils/runOnUiThread.kt'
62
+ );
63
+ const cameraViewModuleFile = path.join(
64
+ visionCameraDir,
65
+ 'android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt'
66
+ );
67
+
68
+ if (!fs.existsSync(runOnUiThreadFile)) {
69
+ // Different version layout — skip
70
+ return;
71
+ }
72
+
73
+ // Check if already patched (idempotent)
74
+ const runOnUiThreadContent = fs.readFileSync(runOnUiThreadFile, 'utf8');
75
+ if (runOnUiThreadContent.includes('resumeWithException')) {
76
+ // Already patched
77
+ return;
78
+ }
79
+
80
+ console.log('[biometric-sdk] Patching react-native-vision-camera to fix Android IllegalViewOperationException crash...');
81
+
82
+ // Patch 1: runOnUiThread.kt — wrap UI thread lambda in try/catch
83
+ const patchedRunOnUiThread = runOnUiThreadContent.replace(
84
+ ` return suspendCancellableCoroutine { continuation ->
85
+ UiThreadUtil.runOnUiThread {
86
+ if (continuation.isCancelled) throw CancellationException()
87
+ val result = function()
88
+ continuation.resume(result)
89
+ }
90
+ }`,
91
+ ` return suspendCancellableCoroutine { continuation ->
92
+ UiThreadUtil.runOnUiThread {
93
+ try {
94
+ if (continuation.isCancelled) throw CancellationException()
95
+ val result = function()
96
+ continuation.resume(result)
97
+ } catch (e: Throwable) {
98
+ continuation.resumeWithException(e)
99
+ }
100
+ }
101
+ }`
102
+ );
103
+
104
+ if (patchedRunOnUiThread !== runOnUiThreadContent) {
105
+ fs.writeFileSync(runOnUiThreadFile, patchedRunOnUiThread, 'utf8');
106
+ console.log('[biometric-sdk] ✓ Patched runOnUiThread.kt');
107
+ }
108
+
109
+ // Patch 2: CameraViewModule.kt — wrap findCameraView in try/catch for all methods
110
+ let moduleContent = fs.readFileSync(cameraViewModuleFile, 'utf8');
111
+ const original = moduleContent;
112
+
113
+ // Helper: replace "val view = findCameraView(viewTag)" with try/catch version
114
+ // that rejects the promise. Handle both promise and callback patterns.
115
+
116
+ // Pattern for promise-based methods (takePhoto, takeSnapshot, pauseRecording,
117
+ // resumeRecording, stopRecording, cancelRecording, focus)
118
+ // Match: "val view = findCameraView(viewTag)" inside a launch block that has a promise
119
+ const promiseMethods = [
120
+ 'takePhoto', 'takeSnapshot', 'resumeRecording',
121
+ 'stopRecording', 'cancelRecording', 'focus'
122
+ ];
123
+
124
+ for (const method of promiseMethods) {
125
+ const pattern = new RegExp(
126
+ `(fun ${method}\\(viewTag: Int,.*?promise: Promise\\).*?backgroundCoroutineScope\\.launch \\{\\n)(\\s*val view = findCameraView\\(viewTag\\))`,
127
+ 's'
128
+ );
129
+ moduleContent = moduleContent.replace(pattern, (match, before, viewLine) => {
130
+ const indent = viewLine.match(/^(\s*)/)[1];
131
+ return `${before}${indent}val view = try {\n${indent} findCameraView(viewTag)\n${indent}} catch (e: Throwable) {\n${indent} promise.reject("camera/view-not-found", "Camera view has been removed", e)\n${indent} return@launch\n${indent}}`;
132
+ });
133
+ }
134
+
135
+ // pauseRecording has findCameraView inside withPromise — different pattern
136
+ moduleContent = moduleContent.replace(
137
+ /fun pauseRecording\(viewTag: Int, promise: Promise\).*?backgroundCoroutineScope\.launch \{\n(\s*)withPromise\(promise\) \{\n(\s*)val view = findCameraView\(viewTag\)/s,
138
+ (match, indent1, indent2) => {
139
+ return match.replace(
140
+ `${indent1}withPromise(promise) {\n${indent2}val view = findCameraView(viewTag)`,
141
+ `${indent1}val view = try {\n${indent1} findCameraView(viewTag)\n${indent1}} catch (e: Throwable) {\n${indent1} promise.reject("camera/view-not-found", "Camera view has been removed", e)\n${indent1} return@launch\n${indent1}}\n${indent1}withPromise(promise) {`
142
+ );
143
+ }
144
+ );
145
+
146
+ // startRecording uses Callback, not Promise
147
+ moduleContent = moduleContent.replace(
148
+ /(fun startRecording\(viewTag: Int,.*?onRecordCallback: Callback\).*?backgroundCoroutineScope\.launch \{\n)(\s*val view = findCameraView\(viewTag\))/s,
149
+ (match, before, viewLine) => {
150
+ const indent = viewLine.match(/^(\s*)/)[1];
151
+ return `${before}${indent}val view = try {\n${indent} findCameraView(viewTag)\n${indent}} catch (e: Throwable) {\n${indent} val map = makeErrorMap("camera/view-not-found", "Camera view has been removed", e)\n${indent} onRecordCallback(null, map)\n${indent} return@launch\n${indent}}`;
152
+ }
153
+ );
154
+
155
+ if (moduleContent !== original) {
156
+ fs.writeFileSync(cameraViewModuleFile, moduleContent, 'utf8');
157
+ console.log('[biometric-sdk] ✓ Patched CameraViewModule.kt');
158
+ }
159
+
160
+ console.log('[biometric-sdk] Done.');
161
+ }
162
+
163
+ try {
164
+ applyPatch();
165
+ } catch (e) {
166
+ // Never fail the install — the patch is a safety improvement, not a requirement
167
+ console.warn('[biometric-sdk] Warning: could not apply vision-camera patch:', e.message);
168
+ }