@hexar/biometric-identity-sdk-react-native 1.24.0 → 1.25.1
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.
|
|
3
|
+
"version": "1.25.1",
|
|
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,147 @@
|
|
|
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..c840643 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
|
+
@@ -3,6 +3,7 @@ package com.mrousavy.camera.core.utils
|
|
6
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
7
|
+
import kotlin.coroutines.cancellation.CancellationException
|
|
8
|
+
import kotlin.coroutines.resume
|
|
9
|
+
+import kotlin.coroutines.resumeWithException
|
|
10
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
11
|
+
|
|
12
|
+
suspend inline fun <T> runOnUiThreadAndWait(crossinline function: () -> T): T {
|
|
13
|
+
@@ -13,9 +14,13 @@ suspend inline fun <T> runOnUiThreadAndWait(crossinline function: () -> T): T {
|
|
14
|
+
|
|
15
|
+
return suspendCancellableCoroutine { continuation ->
|
|
16
|
+
UiThreadUtil.runOnUiThread {
|
|
17
|
+
- if (continuation.isCancelled) throw CancellationException()
|
|
18
|
+
- val result = function()
|
|
19
|
+
- continuation.resume(result)
|
|
20
|
+
+ try {
|
|
21
|
+
+ if (continuation.isCancelled) throw CancellationException()
|
|
22
|
+
+ val result = function()
|
|
23
|
+
+ continuation.resume(result)
|
|
24
|
+
+ } catch (e: Throwable) {
|
|
25
|
+
+ continuation.resumeWithException(e)
|
|
26
|
+
+ }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
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
|
|
31
|
+
index 059f677..54ead5a 100644
|
|
32
|
+
--- a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt
|
|
33
|
+
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt
|
|
34
|
+
@@ -91,7 +91,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|
35
|
+
@ReactMethod
|
|
36
|
+
fun takePhoto(viewTag: Int, options: ReadableMap, promise: Promise) {
|
|
37
|
+
backgroundCoroutineScope.launch {
|
|
38
|
+
- val view = findCameraView(viewTag)
|
|
39
|
+
+ val view = try {
|
|
40
|
+
+ findCameraView(viewTag)
|
|
41
|
+
+ } catch (e: Throwable) {
|
|
42
|
+
+ promise.reject("camera/view-not-found", "Camera view has been removed", e)
|
|
43
|
+
+ return@launch
|
|
44
|
+
+ }
|
|
45
|
+
withPromise(promise) {
|
|
46
|
+
view.takePhoto(options)
|
|
47
|
+
}
|
|
48
|
+
@@ -101,7 +106,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|
49
|
+
@ReactMethod
|
|
50
|
+
fun takeSnapshot(viewTag: Int, jsOptions: ReadableMap, promise: Promise) {
|
|
51
|
+
backgroundCoroutineScope.launch {
|
|
52
|
+
- val view = findCameraView(viewTag)
|
|
53
|
+
+ val view = try {
|
|
54
|
+
+ findCameraView(viewTag)
|
|
55
|
+
+ } catch (e: Throwable) {
|
|
56
|
+
+ promise.reject("camera/view-not-found", "Camera view has been removed", e)
|
|
57
|
+
+ return@launch
|
|
58
|
+
+ }
|
|
59
|
+
runOnUiThread {
|
|
60
|
+
try {
|
|
61
|
+
val options = TakeSnapshotOptions.fromJSValue(reactApplicationContext, jsOptions)
|
|
62
|
+
@@ -118,7 +128,13 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|
63
|
+
@ReactMethod
|
|
64
|
+
fun startRecording(viewTag: Int, jsOptions: ReadableMap, onRecordCallback: Callback) {
|
|
65
|
+
backgroundCoroutineScope.launch {
|
|
66
|
+
- val view = findCameraView(viewTag)
|
|
67
|
+
+ val view = try {
|
|
68
|
+
+ findCameraView(viewTag)
|
|
69
|
+
+ } catch (e: Throwable) {
|
|
70
|
+
+ val map = makeErrorMap("camera/view-not-found", "Camera view has been removed", e)
|
|
71
|
+
+ onRecordCallback(null, map)
|
|
72
|
+
+ return@launch
|
|
73
|
+
+ }
|
|
74
|
+
try {
|
|
75
|
+
val options = RecordVideoOptions.fromJSValue(reactApplicationContext, jsOptions)
|
|
76
|
+
view.startRecording(options, onRecordCallback)
|
|
77
|
+
@@ -136,8 +152,13 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|
78
|
+
@ReactMethod
|
|
79
|
+
fun pauseRecording(viewTag: Int, promise: Promise) {
|
|
80
|
+
backgroundCoroutineScope.launch {
|
|
81
|
+
+ val view = try {
|
|
82
|
+
+ findCameraView(viewTag)
|
|
83
|
+
+ } catch (e: Throwable) {
|
|
84
|
+
+ promise.reject("camera/view-not-found", "Camera view has been removed", e)
|
|
85
|
+
+ return@launch
|
|
86
|
+
+ }
|
|
87
|
+
withPromise(promise) {
|
|
88
|
+
- val view = findCameraView(viewTag)
|
|
89
|
+
view.pauseRecording()
|
|
90
|
+
return@withPromise null
|
|
91
|
+
}
|
|
92
|
+
@@ -147,7 +168,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|
93
|
+
@ReactMethod
|
|
94
|
+
fun resumeRecording(viewTag: Int, promise: Promise) {
|
|
95
|
+
backgroundCoroutineScope.launch {
|
|
96
|
+
- val view = findCameraView(viewTag)
|
|
97
|
+
+ val view = try {
|
|
98
|
+
+ findCameraView(viewTag)
|
|
99
|
+
+ } catch (e: Throwable) {
|
|
100
|
+
+ promise.reject("camera/view-not-found", "Camera view has been removed", e)
|
|
101
|
+
+ return@launch
|
|
102
|
+
+ }
|
|
103
|
+
withPromise(promise) {
|
|
104
|
+
view.resumeRecording()
|
|
105
|
+
return@withPromise null
|
|
106
|
+
@@ -158,7 +184,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|
107
|
+
@ReactMethod
|
|
108
|
+
fun stopRecording(viewTag: Int, promise: Promise) {
|
|
109
|
+
backgroundCoroutineScope.launch {
|
|
110
|
+
- val view = findCameraView(viewTag)
|
|
111
|
+
+ val view = try {
|
|
112
|
+
+ findCameraView(viewTag)
|
|
113
|
+
+ } catch (e: Throwable) {
|
|
114
|
+
+ promise.reject("camera/view-not-found", "Camera view has been removed", e)
|
|
115
|
+
+ return@launch
|
|
116
|
+
+ }
|
|
117
|
+
withPromise(promise) {
|
|
118
|
+
view.stopRecording()
|
|
119
|
+
return@withPromise null
|
|
120
|
+
@@ -169,7 +200,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|
121
|
+
@ReactMethod
|
|
122
|
+
fun cancelRecording(viewTag: Int, promise: Promise) {
|
|
123
|
+
backgroundCoroutineScope.launch {
|
|
124
|
+
- val view = findCameraView(viewTag)
|
|
125
|
+
+ val view = try {
|
|
126
|
+
+ findCameraView(viewTag)
|
|
127
|
+
+ } catch (e: Throwable) {
|
|
128
|
+
+ promise.reject("camera/view-not-found", "Camera view has been removed", e)
|
|
129
|
+
+ return@launch
|
|
130
|
+
+ }
|
|
131
|
+
withPromise(promise) {
|
|
132
|
+
view.cancelRecording()
|
|
133
|
+
return@withPromise null
|
|
134
|
+
@@ -180,7 +216,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|
135
|
+
@ReactMethod
|
|
136
|
+
fun focus(viewTag: Int, point: ReadableMap, promise: Promise) {
|
|
137
|
+
backgroundCoroutineScope.launch {
|
|
138
|
+
- val view = findCameraView(viewTag)
|
|
139
|
+
+ val view = try {
|
|
140
|
+
+ findCameraView(viewTag)
|
|
141
|
+
+ } catch (e: Throwable) {
|
|
142
|
+
+ promise.reject("camera/view-not-found", "Camera view has been removed", e)
|
|
143
|
+
+ return@launch
|
|
144
|
+
+ }
|
|
145
|
+
withPromise(promise) {
|
|
146
|
+
view.focus(point)
|
|
147
|
+
return@withPromise null
|
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
// Add the missing import for resumeWithException
|
|
106
|
+
let finalContent = patchedRunOnUiThread;
|
|
107
|
+
if (!finalContent.includes('import kotlin.coroutines.resumeWithException')) {
|
|
108
|
+
finalContent = finalContent.replace(
|
|
109
|
+
'import kotlin.coroutines.resume',
|
|
110
|
+
'import kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
fs.writeFileSync(runOnUiThreadFile, finalContent, 'utf8');
|
|
114
|
+
console.log('[biometric-sdk] ✓ Patched runOnUiThread.kt');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Patch 2: CameraViewModule.kt — wrap findCameraView in try/catch for all methods
|
|
118
|
+
let moduleContent = fs.readFileSync(cameraViewModuleFile, 'utf8');
|
|
119
|
+
const original = moduleContent;
|
|
120
|
+
|
|
121
|
+
// Helper: replace "val view = findCameraView(viewTag)" with try/catch version
|
|
122
|
+
// that rejects the promise. Handle both promise and callback patterns.
|
|
123
|
+
|
|
124
|
+
// Pattern for promise-based methods (takePhoto, takeSnapshot, pauseRecording,
|
|
125
|
+
// resumeRecording, stopRecording, cancelRecording, focus)
|
|
126
|
+
// Match: "val view = findCameraView(viewTag)" inside a launch block that has a promise
|
|
127
|
+
const promiseMethods = [
|
|
128
|
+
'takePhoto', 'takeSnapshot', 'resumeRecording',
|
|
129
|
+
'stopRecording', 'cancelRecording', 'focus'
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
for (const method of promiseMethods) {
|
|
133
|
+
const pattern = new RegExp(
|
|
134
|
+
`(fun ${method}\\(viewTag: Int,.*?promise: Promise\\).*?backgroundCoroutineScope\\.launch \\{\\n)(\\s*val view = findCameraView\\(viewTag\\))`,
|
|
135
|
+
's'
|
|
136
|
+
);
|
|
137
|
+
moduleContent = moduleContent.replace(pattern, (match, before, viewLine) => {
|
|
138
|
+
const indent = viewLine.match(/^(\s*)/)[1];
|
|
139
|
+
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}}`;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// pauseRecording has findCameraView inside withPromise — different pattern
|
|
144
|
+
moduleContent = moduleContent.replace(
|
|
145
|
+
/fun pauseRecording\(viewTag: Int, promise: Promise\).*?backgroundCoroutineScope\.launch \{\n(\s*)withPromise\(promise\) \{\n(\s*)val view = findCameraView\(viewTag\)/s,
|
|
146
|
+
(match, indent1, indent2) => {
|
|
147
|
+
return match.replace(
|
|
148
|
+
`${indent1}withPromise(promise) {\n${indent2}val view = findCameraView(viewTag)`,
|
|
149
|
+
`${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) {`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// startRecording uses Callback, not Promise
|
|
155
|
+
moduleContent = moduleContent.replace(
|
|
156
|
+
/(fun startRecording\(viewTag: Int,.*?onRecordCallback: Callback\).*?backgroundCoroutineScope\.launch \{\n)(\s*val view = findCameraView\(viewTag\))/s,
|
|
157
|
+
(match, before, viewLine) => {
|
|
158
|
+
const indent = viewLine.match(/^(\s*)/)[1];
|
|
159
|
+
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}}`;
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (moduleContent !== original) {
|
|
164
|
+
fs.writeFileSync(cameraViewModuleFile, moduleContent, 'utf8');
|
|
165
|
+
console.log('[biometric-sdk] ✓ Patched CameraViewModule.kt');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log('[biometric-sdk] Done.');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
applyPatch();
|
|
173
|
+
} catch (e) {
|
|
174
|
+
// Never fail the install — the patch is a safety improvement, not a requirement
|
|
175
|
+
console.warn('[biometric-sdk] Warning: could not apply vision-camera patch:', e.message);
|
|
176
|
+
}
|