@jupitermetalabs/face-zk-sdk 0.1.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/LICENSE +201 -0
- package/README.md +181 -0
- package/assets/README.md +22 -0
- package/assets/face-guidance/face-logic.js.txt +77 -0
- package/assets/face-guidance/index.html +173 -0
- package/assets/face-guidance/pose-guidance.js.txt +403 -0
- package/assets/liveness/antispoof.js.txt +143 -0
- package/assets/liveness/index.html +451 -0
- package/assets/liveness/liveness.js.txt +1003 -0
- package/assets/mediapipe/face_mesh.js.txt +131 -0
- package/assets/mediapipe/face_mesh_solution_packed_assets.data +0 -0
- package/assets/mediapipe/face_mesh_solution_simd_wasm_bin.wasm +0 -0
- package/assets/mediapipe/face_mesh_solution_wasm_bin.wasm +0 -0
- package/assets/onnx/ort-wasm-simd.wasm +0 -0
- package/assets/onnx/ort-wasm.wasm +0 -0
- package/assets/onnx/ort.min.js.txt +7 -0
- package/assets/wasm/zk_face_wasm_bg.wasm +0 -0
- package/assets/zk-worker.html +472 -0
- package/cli/copy-ort-assets.js +65 -0
- package/cli/setup.js +266 -0
- package/dist/FaceZkSdk.d.ts +69 -0
- package/dist/FaceZkSdk.js +132 -0
- package/dist/assets/onnx/ort-min.d.ts +1 -0
- package/dist/assets/onnx/ort-min.js +8 -0
- package/dist/config/defaults.d.ts +49 -0
- package/dist/config/defaults.js +55 -0
- package/dist/config/types.d.ts +123 -0
- package/dist/config/types.js +16 -0
- package/dist/core/enrollment-core.d.ts +68 -0
- package/dist/core/enrollment-core.js +202 -0
- package/dist/core/matching.d.ts +69 -0
- package/dist/core/matching.js +96 -0
- package/dist/core/types.d.ts +365 -0
- package/dist/core/types.js +34 -0
- package/dist/core/verification-core.d.ts +120 -0
- package/dist/core/verification-core.js +434 -0
- package/dist/core/zk-core.d.ts +69 -0
- package/dist/core/zk-core.js +240 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +39 -0
- package/dist/react-native/adapters/faceEmbeddingProvider.d.ts +38 -0
- package/dist/react-native/adapters/faceEmbeddingProvider.js +41 -0
- package/dist/react-native/adapters/imageDataProvider.d.ts +53 -0
- package/dist/react-native/adapters/imageDataProvider.js +97 -0
- package/dist/react-native/adapters/livenessProvider.d.ts +133 -0
- package/dist/react-native/adapters/livenessProvider.js +144 -0
- package/dist/react-native/adapters/zkProofEngine-webview.d.ts +73 -0
- package/dist/react-native/adapters/zkProofEngine-webview.js +129 -0
- package/dist/react-native/components/FacePoseGuidanceWebView.d.ts +30 -0
- package/dist/react-native/components/FacePoseGuidanceWebView.js +474 -0
- package/dist/react-native/components/LivenessWebView.d.ts +39 -0
- package/dist/react-native/components/LivenessWebView.js +348 -0
- package/dist/react-native/components/OnnxRuntimeWebView.d.ts +54 -0
- package/dist/react-native/components/OnnxRuntimeWebView.js +394 -0
- package/dist/react-native/components/ZkProofWebView.d.ts +59 -0
- package/dist/react-native/components/ZkProofWebView.js +259 -0
- package/dist/react-native/dependencies.d.ts +144 -0
- package/dist/react-native/dependencies.js +123 -0
- package/dist/react-native/hooks/useOnnxLoader.d.ts +38 -0
- package/dist/react-native/hooks/useOnnxLoader.js +81 -0
- package/dist/react-native/hooks/useWasmLoader.d.ts +30 -0
- package/dist/react-native/hooks/useWasmLoader.js +122 -0
- package/dist/react-native/index.d.ts +59 -0
- package/dist/react-native/index.js +96 -0
- package/dist/react-native/services/FaceRecognition.d.ts +70 -0
- package/dist/react-native/services/FaceRecognition.js +517 -0
- package/dist/react-native/ui/FaceZkVerificationFlow.d.ts +97 -0
- package/dist/react-native/ui/FaceZkVerificationFlow.js +433 -0
- package/dist/react-native/ui/ReferenceEnrollmentFlow.d.ts +72 -0
- package/dist/react-native/ui/ReferenceEnrollmentFlow.js +321 -0
- package/dist/react-native/utils/faceAlignment.d.ts +37 -0
- package/dist/react-native/utils/faceAlignment.js +182 -0
- package/dist/react-native/utils/modelInitialisationChecks.d.ts +36 -0
- package/dist/react-native/utils/modelInitialisationChecks.js +92 -0
- package/dist/react-native/utils/resolveModelUri.d.ts +55 -0
- package/dist/react-native/utils/resolveModelUri.js +172 -0
- package/dist/react-native/utils/resolveUiConfig.d.ts +41 -0
- package/dist/react-native/utils/resolveUiConfig.js +76 -0
- package/dist/storage/defaultStorageAdapter.d.ts +44 -0
- package/dist/storage/defaultStorageAdapter.js +299 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/face-zk.config.example.js +88 -0
- package/package.json +76 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 JupiterMeta Labs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { Asset } from "expo-asset";
|
|
17
|
+
import { useCameraPermissions } from "expo-camera";
|
|
18
|
+
import * as FileSystem from "expo-file-system/legacy";
|
|
19
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
20
|
+
import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
|
|
21
|
+
import { WebView } from "react-native-webview";
|
|
22
|
+
export const ZkFaceAuth = ({ onSuccess, onError, manualTargetPose, referenceImageUri, renderOverlay, headless = true, }) => {
|
|
23
|
+
const webViewRef = useRef(null);
|
|
24
|
+
const [htmlContent, setHtmlContent] = useState(null);
|
|
25
|
+
const [permission, requestPermission] = useCameraPermissions();
|
|
26
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
27
|
+
const [loadError, setLoadError] = useState(null);
|
|
28
|
+
// Realtime engine state
|
|
29
|
+
const [engineState, setEngineState] = useState(null);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
loadResources();
|
|
32
|
+
}, [headless]);
|
|
33
|
+
const loadResources = async () => {
|
|
34
|
+
try {
|
|
35
|
+
console.log("[ZkFaceAuth] Loading resources...");
|
|
36
|
+
setLoadError(null);
|
|
37
|
+
// 1. Load HTML and JS files
|
|
38
|
+
console.log("[ZkFaceAuth] Resolving assets...");
|
|
39
|
+
const htmlAsset = Asset.fromModule(require("../../assets/liveness/index.html"));
|
|
40
|
+
const antispoofJsAsset = Asset.fromModule(require("../../assets/liveness/antispoof.js.txt"));
|
|
41
|
+
const livenessJsAsset = Asset.fromModule(require("../../assets/liveness/liveness.js.txt"));
|
|
42
|
+
// Load MediaPipe Local Assets
|
|
43
|
+
const mpFaceMeshJsAsset = Asset.fromModule(require("../../assets/mediapipe/face_mesh.js.txt"));
|
|
44
|
+
console.log("[ZkFaceAuth] Assets resolved, beginning download...");
|
|
45
|
+
await Promise.all([
|
|
46
|
+
htmlAsset.downloadAsync(),
|
|
47
|
+
antispoofJsAsset.downloadAsync(),
|
|
48
|
+
livenessJsAsset.downloadAsync(),
|
|
49
|
+
mpFaceMeshJsAsset.downloadAsync(),
|
|
50
|
+
]);
|
|
51
|
+
console.log("[ZkFaceAuth] Initial assets downloaded. Reading strings...");
|
|
52
|
+
const html = await FileSystem.readAsStringAsync(htmlAsset.localUri || htmlAsset.uri);
|
|
53
|
+
const antispoofJs = await FileSystem.readAsStringAsync(antispoofJsAsset.localUri || antispoofJsAsset.uri);
|
|
54
|
+
const livenessJs = await FileSystem.readAsStringAsync(livenessJsAsset.localUri || livenessJsAsset.uri);
|
|
55
|
+
const mpFaceMeshJs = await FileSystem.readAsStringAsync(mpFaceMeshJsAsset.localUri || mpFaceMeshJsAsset.uri);
|
|
56
|
+
console.log("[ZkFaceAuth] JS read successfully. Injecting...");
|
|
57
|
+
// 2. Inject JS into HTML
|
|
58
|
+
let finalHtml = html
|
|
59
|
+
.replace('<script src="/static/js/onnx_antispoof.js"></script>', `<script>${antispoofJs}</script>`)
|
|
60
|
+
.replace('<script src="/static/js/real_liveness.js"></script>', `<script>${livenessJs}</script>`)
|
|
61
|
+
.replace("<!-- MEDIAPIPE_LOCAL_INJECT -->", `<script>${mpFaceMeshJs}</script>`);
|
|
62
|
+
// Also check for relative paths if any were changed
|
|
63
|
+
finalHtml = finalHtml
|
|
64
|
+
.replace('<script src="./antispoof.js"></script>', `<script>${antispoofJs}</script>`)
|
|
65
|
+
.replace('<script src="./liveness.js"></script>', `<script>${livenessJs}</script>`);
|
|
66
|
+
// Inject headless mode variable
|
|
67
|
+
if (headless) {
|
|
68
|
+
finalHtml = finalHtml.replace(/<\s*head\s*>/i, "<head><script>window.HEADLESS_MODE = true;</script>");
|
|
69
|
+
}
|
|
70
|
+
console.log("[ZkFaceAuth] HTML prepared");
|
|
71
|
+
setHtmlContent(finalHtml);
|
|
72
|
+
setIsLoading(false);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error("[ZkFaceAuth] Error loading resources:", error);
|
|
76
|
+
setLoadError(error.message);
|
|
77
|
+
setIsLoading(false);
|
|
78
|
+
onError("Failed to load liveness resources: " + error.message);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const injectModel = async () => {
|
|
82
|
+
try {
|
|
83
|
+
console.log("[ZkFaceAuth] Injecting model...");
|
|
84
|
+
// ... (model injection logic remains same)
|
|
85
|
+
const modelAsset = Asset.fromModule(require("../../assets/models/antispoof.onnx"));
|
|
86
|
+
await modelAsset.downloadAsync();
|
|
87
|
+
const modelBase64 = await FileSystem.readAsStringAsync(modelAsset.localUri || modelAsset.uri, {
|
|
88
|
+
encoding: FileSystem.EncodingType.Base64,
|
|
89
|
+
});
|
|
90
|
+
// Read reference image if available (Base64 on Native)
|
|
91
|
+
let referenceBase64 = "";
|
|
92
|
+
if (referenceImageUri) {
|
|
93
|
+
try {
|
|
94
|
+
referenceBase64 = await FileSystem.readAsStringAsync(referenceImageUri, { encoding: FileSystem.EncodingType.Base64 });
|
|
95
|
+
referenceBase64 = `data:image/jpeg;base64,${referenceBase64}`;
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
console.warn("Failed to read reference image for injection", e);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Read MediaPipe WASM bindings (Base64)
|
|
102
|
+
console.log("[ZkFaceAuth] Reading MediaPipe binaries...");
|
|
103
|
+
const mpWasmSimdAsset = Asset.fromModule(require("../../assets/mediapipe/face_mesh_solution_simd_wasm_bin.wasm"));
|
|
104
|
+
const mpWasmAsset = Asset.fromModule(require("../../assets/mediapipe/face_mesh_solution_wasm_bin.wasm"));
|
|
105
|
+
const mpDataAsset = Asset.fromModule(require("../../assets/mediapipe/face_mesh_solution_packed_assets.data"));
|
|
106
|
+
await Promise.all([
|
|
107
|
+
mpWasmSimdAsset.downloadAsync(),
|
|
108
|
+
mpWasmAsset.downloadAsync(),
|
|
109
|
+
mpDataAsset.downloadAsync(),
|
|
110
|
+
]);
|
|
111
|
+
const mpWasmSimdBase64 = await FileSystem.readAsStringAsync(mpWasmSimdAsset.localUri || mpWasmSimdAsset.uri, { encoding: FileSystem.EncodingType.Base64 });
|
|
112
|
+
const mpWasmBase64 = await FileSystem.readAsStringAsync(mpWasmAsset.localUri || mpWasmAsset.uri, { encoding: FileSystem.EncodingType.Base64 });
|
|
113
|
+
const mpDataBase64 = await FileSystem.readAsStringAsync(mpDataAsset.localUri || mpDataAsset.uri, { encoding: FileSystem.EncodingType.Base64 });
|
|
114
|
+
const injectScript = `
|
|
115
|
+
// Inject MediaPipe Files globally to intercept locateFile
|
|
116
|
+
window.MP_WASM_SIMD_BASE64 = ${JSON.stringify(mpWasmSimdBase64)};
|
|
117
|
+
window.MP_WASM_BASE64 = ${JSON.stringify(mpWasmBase64)};
|
|
118
|
+
window.MP_DATA_BASE64 = ${JSON.stringify(mpDataBase64)};
|
|
119
|
+
console.log("[ZkFaceAuth] MediaPipe injected");
|
|
120
|
+
|
|
121
|
+
if (window.loadAntispoofModel) {
|
|
122
|
+
window.loadAntispoofModel(${JSON.stringify(modelBase64)});
|
|
123
|
+
} else {
|
|
124
|
+
console.error('loadAntispoofModel not found');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Set target pose if available
|
|
128
|
+
if (${JSON.stringify(manualTargetPose)}) {
|
|
129
|
+
window.TARGET_POSE = ${JSON.stringify(manualTargetPose)};
|
|
130
|
+
console.log('Target Pose Injected:', window.TARGET_POSE);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Set Reference Image if available
|
|
134
|
+
if (${JSON.stringify(referenceBase64)}) {
|
|
135
|
+
window.REFERENCE_IMAGE_URI = ${JSON.stringify(referenceBase64)};
|
|
136
|
+
console.log('Reference Image Injected');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Start execution ONLY after injecting Native variables
|
|
140
|
+
if (window.initializeLiveness) {
|
|
141
|
+
window.initializeLiveness();
|
|
142
|
+
} else {
|
|
143
|
+
console.error('initializeLiveness not found');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
true;
|
|
147
|
+
`;
|
|
148
|
+
webViewRef.current?.injectJavaScript(injectScript);
|
|
149
|
+
console.log("[ZkFaceAuth] Model & Pose injection script sent");
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.error("[ZkFaceAuth] Failed to inject model:", error);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
// ... (rest of render logic remains same)
|
|
156
|
+
const [redirecting, setRedirecting] = useState(false);
|
|
157
|
+
const handleMessage = (event) => {
|
|
158
|
+
try {
|
|
159
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
160
|
+
if (data.type === "log") {
|
|
161
|
+
console.log("[ZkFaceAuth] Log:", data.message);
|
|
162
|
+
}
|
|
163
|
+
else if (data.type === "liveness_state") {
|
|
164
|
+
// High Frequency State Updates from the AI Engine
|
|
165
|
+
setEngineState(data.data);
|
|
166
|
+
}
|
|
167
|
+
else if (data.type === "success") {
|
|
168
|
+
setRedirecting(true); // Visual feedback that RN got the msg
|
|
169
|
+
onSuccess(data.image, data.metadata);
|
|
170
|
+
}
|
|
171
|
+
else if (data.type === "error") {
|
|
172
|
+
console.error("[ZkFaceAuth] WebView Error:", data.message);
|
|
173
|
+
onError(data.message);
|
|
174
|
+
}
|
|
175
|
+
else if (data.type === "modelLoaded") {
|
|
176
|
+
console.log("[ZkFaceAuth] Model loaded confirmed");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
console.error("[ZkFaceAuth] Error parsing message:", e);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
// ... (rest of code) ...
|
|
184
|
+
// Inside return (after WebView, before closing View if wrapped?)
|
|
185
|
+
// Wait, LivenessWebView returns WebView directly. I need to wrap it if I want overlay.
|
|
186
|
+
// Actually, LivenessWebView is wrapped in ScanScreen.tsx.
|
|
187
|
+
// But I can't change LivenessWebView return type structure easily without breaking styles?
|
|
188
|
+
// Let's modify the return structure.
|
|
189
|
+
if (isLoading || !htmlContent) {
|
|
190
|
+
if (loadError) {
|
|
191
|
+
return (<View style={styles.center}>
|
|
192
|
+
<Text style={[styles.text, { color: "#ef4444" }]}>
|
|
193
|
+
Failed to load resources: {loadError}
|
|
194
|
+
</Text>
|
|
195
|
+
</View>);
|
|
196
|
+
}
|
|
197
|
+
return (<View style={styles.center}>
|
|
198
|
+
<ActivityIndicator size="large" color="#10b981"/>
|
|
199
|
+
<Text style={[styles.text, { marginTop: 20 }]}>
|
|
200
|
+
Loading capabilities...
|
|
201
|
+
</Text>
|
|
202
|
+
</View>);
|
|
203
|
+
}
|
|
204
|
+
// Handle Camera Permissions Explicitly
|
|
205
|
+
if (!permission?.granted) {
|
|
206
|
+
if (permission?.canAskAgain) {
|
|
207
|
+
requestPermission();
|
|
208
|
+
return (<View style={styles.center}>
|
|
209
|
+
<ActivityIndicator size="large" color="#10b981"/>
|
|
210
|
+
<Text style={[styles.text, { marginTop: 20 }]}>
|
|
211
|
+
Requesting camera access...
|
|
212
|
+
</Text>
|
|
213
|
+
</View>);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// Temporarily render a view, but in a real flow it might just call onError
|
|
217
|
+
// Since this mount is often unmounted/remounted, it is safer to call onError here if we can't show UI
|
|
218
|
+
// But Since this is a component, let's just show a text that says "Camera permission denied"
|
|
219
|
+
// or we can call onError. Let's call onError on mount if denied.
|
|
220
|
+
setTimeout(() => onError("Camera permission denied. Please enable it in Settings."), 50);
|
|
221
|
+
return (<View style={styles.center}>
|
|
222
|
+
<Text style={[styles.text, { color: "#ef4444" }]}>
|
|
223
|
+
Camera access denied
|
|
224
|
+
</Text>
|
|
225
|
+
</View>);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (redirecting) {
|
|
229
|
+
return (<View style={styles.center}>
|
|
230
|
+
<ActivityIndicator size="large" color="#10b981"/>
|
|
231
|
+
<Text style={[styles.text, { marginTop: 20 }]}>
|
|
232
|
+
Processing Capture...
|
|
233
|
+
</Text>
|
|
234
|
+
</View>);
|
|
235
|
+
}
|
|
236
|
+
return (<View style={styles.container}>
|
|
237
|
+
<WebView ref={webViewRef}
|
|
238
|
+
// CRITICAL: baseUrl must be https://localhost/ to provide a Secure Context for WebAssembly/ONNX.
|
|
239
|
+
// It does NOT make actual network requests, but prevents the WebView from throwing security errors.
|
|
240
|
+
source={{ html: htmlContent, baseUrl: "https://localhost/" }} style={styles.webview} javaScriptEnabled={true} domStorageEnabled={true} allowsInlineMediaPlayback={true} mediaPlaybackRequiresUserAction={false} scrollEnabled={false} bounces={false} overScrollMode="never" scalesPageToFit={true} onMessage={handleMessage}
|
|
241
|
+
// @ts-expect-error — onPermissionRequest is an Android WebView prop not in react-native-webview types
|
|
242
|
+
onPermissionRequest={(event) => {
|
|
243
|
+
const { resources } = event.nativeEvent;
|
|
244
|
+
if (resources.includes("camera")) {
|
|
245
|
+
event.grant(resources);
|
|
246
|
+
}
|
|
247
|
+
}} onLoadEnd={() => {
|
|
248
|
+
console.log("[ZkFaceAuth] Load End - Injecting model");
|
|
249
|
+
injectModel();
|
|
250
|
+
}} onError={(syntheticEvent) => {
|
|
251
|
+
const { nativeEvent } = syntheticEvent;
|
|
252
|
+
console.error("[ZkFaceAuth] WebView Error:", nativeEvent);
|
|
253
|
+
}} onHttpError={(syntheticEvent) => {
|
|
254
|
+
const { nativeEvent } = syntheticEvent;
|
|
255
|
+
console.error("[ZkFaceAuth] WebView HTTP Error:", nativeEvent);
|
|
256
|
+
}} originWhitelist={["*"]}/>
|
|
257
|
+
|
|
258
|
+
{/* Render Customer Overlay or Default Fallback Overlayer */}
|
|
259
|
+
{headless && renderOverlay && engineState && (<View style={StyleSheet.absoluteFillObject} pointerEvents="none">
|
|
260
|
+
{renderOverlay(engineState)}
|
|
261
|
+
</View>)}
|
|
262
|
+
|
|
263
|
+
{/* Render built in default overlay if they want headless but provided no overlay */}
|
|
264
|
+
{headless && !renderOverlay && engineState && (<DefaultLivenessOverlay state={engineState}/>)}
|
|
265
|
+
</View>);
|
|
266
|
+
};
|
|
267
|
+
// Extremely basic default overlay for consumers who don't want to build their own UI
|
|
268
|
+
// but still use the cleanly detached SDK.
|
|
269
|
+
const DefaultLivenessOverlay = ({ state, }) => {
|
|
270
|
+
let borderColor = "#e2e8f0";
|
|
271
|
+
if (state.phase === "success")
|
|
272
|
+
borderColor = "#22c55e";
|
|
273
|
+
else if (state.phase === "fail")
|
|
274
|
+
borderColor = "#ef4444";
|
|
275
|
+
else if (state.isFaceLocked)
|
|
276
|
+
borderColor = "#6366f1";
|
|
277
|
+
return (<View style={StyleSheet.absoluteFillObject} pointerEvents="none">
|
|
278
|
+
{/* Dark semi transparent background with a cutout for the face */}
|
|
279
|
+
<View style={[
|
|
280
|
+
StyleSheet.absoluteFillObject,
|
|
281
|
+
{
|
|
282
|
+
backgroundColor: "rgba(0,0,0,0.5)",
|
|
283
|
+
justifyContent: "center",
|
|
284
|
+
alignItems: "center",
|
|
285
|
+
},
|
|
286
|
+
]}>
|
|
287
|
+
{state.promptText && (<Text style={{
|
|
288
|
+
color: "white",
|
|
289
|
+
fontSize: 24,
|
|
290
|
+
fontWeight: "bold",
|
|
291
|
+
position: "absolute",
|
|
292
|
+
top: 100,
|
|
293
|
+
}}>
|
|
294
|
+
{state.promptText}
|
|
295
|
+
</Text>)}
|
|
296
|
+
|
|
297
|
+
<View style={{
|
|
298
|
+
width: 320,
|
|
299
|
+
height: 320,
|
|
300
|
+
borderRadius: 160,
|
|
301
|
+
borderWidth: 4,
|
|
302
|
+
borderColor: borderColor,
|
|
303
|
+
backgroundColor: "transparent", // this would be a real cutout in a true complex UI
|
|
304
|
+
}}/>
|
|
305
|
+
|
|
306
|
+
{/* Progress bar mapping 0-100 */}
|
|
307
|
+
{state.progressPercent > 0 && state.progressPercent < 100 && (<View style={{
|
|
308
|
+
position: "absolute",
|
|
309
|
+
bottom: 150,
|
|
310
|
+
width: 200,
|
|
311
|
+
height: 8,
|
|
312
|
+
backgroundColor: "#333",
|
|
313
|
+
borderRadius: 4,
|
|
314
|
+
}}>
|
|
315
|
+
<View style={{
|
|
316
|
+
width: `${state.progressPercent}%`,
|
|
317
|
+
height: "100%",
|
|
318
|
+
backgroundColor: "#6366f1",
|
|
319
|
+
borderRadius: 4,
|
|
320
|
+
}}/>
|
|
321
|
+
</View>)}
|
|
322
|
+
|
|
323
|
+
{state.icon && (<Text style={{ fontSize: 48, position: "absolute", bottom: 50 }}>
|
|
324
|
+
{state.icon}
|
|
325
|
+
</Text>)}
|
|
326
|
+
</View>
|
|
327
|
+
</View>);
|
|
328
|
+
};
|
|
329
|
+
const styles = StyleSheet.create({
|
|
330
|
+
container: {
|
|
331
|
+
flex: 1,
|
|
332
|
+
backgroundColor: "#000",
|
|
333
|
+
},
|
|
334
|
+
webview: {
|
|
335
|
+
flex: 1,
|
|
336
|
+
backgroundColor: "transparent",
|
|
337
|
+
},
|
|
338
|
+
center: {
|
|
339
|
+
flex: 1,
|
|
340
|
+
justifyContent: "center",
|
|
341
|
+
alignItems: "center",
|
|
342
|
+
backgroundColor: "#fff",
|
|
343
|
+
},
|
|
344
|
+
text: {
|
|
345
|
+
fontSize: 16,
|
|
346
|
+
color: "#333",
|
|
347
|
+
},
|
|
348
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 JupiterMeta Labs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import React from 'react';
|
|
17
|
+
import { WebView } from 'react-native-webview';
|
|
18
|
+
interface OnnxRuntimeBridgeProps {
|
|
19
|
+
onReady: (bridge: OnnxRuntimeBridge) => void;
|
|
20
|
+
onError: (error: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare class OnnxRuntimeBridge {
|
|
23
|
+
private webViewRef;
|
|
24
|
+
private messageCallbacks;
|
|
25
|
+
private ready;
|
|
26
|
+
constructor(webViewRef: React.RefObject<WebView | null>);
|
|
27
|
+
loadModels(detModelData: string, recModelData: string, wasmData?: string): Promise<void>;
|
|
28
|
+
runDetection(imageData: Float32Array, width: number, height: number): Promise<{
|
|
29
|
+
outputs: Record<string, {
|
|
30
|
+
data: number[];
|
|
31
|
+
dims: number[];
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
runRecognition(imageData: Float32Array, width: number, height: number): Promise<{
|
|
35
|
+
data: number[];
|
|
36
|
+
dims: number[];
|
|
37
|
+
}>;
|
|
38
|
+
handleMessage(event: any): void;
|
|
39
|
+
/**
|
|
40
|
+
* Send a message to the WebView via postMessage.
|
|
41
|
+
* postMessage has no practical size limit (unlike injectJavaScript which fails
|
|
42
|
+
* on Android above ~4 MB). This is critical for large model and tensor payloads.
|
|
43
|
+
*/
|
|
44
|
+
sendMessage(type: string, params?: any): void;
|
|
45
|
+
/**
|
|
46
|
+
* Encodes a Float32Array as base64 binary using chunked String.fromCharCode
|
|
47
|
+
* to avoid call-stack overflow on large arrays (e.g. 640×640×3 = 4.9 MB).
|
|
48
|
+
* Result is ~4/3× the byte size, far smaller than JSON (which costs ~6–8×).
|
|
49
|
+
*/
|
|
50
|
+
private float32ToBase64;
|
|
51
|
+
isReady(): boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare const OnnxRuntimeWebView: React.FC<OnnxRuntimeBridgeProps>;
|
|
54
|
+
export {};
|