@jupitermetalabs/face-zk-sdk 0.3.4 → 0.3.7

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