@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,518 @@
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.OnnxRuntimeWebView = exports.OnnxRuntimeBridge = void 0;
52
+ const react_1 = __importStar(require("react"));
53
+ const react_native_1 = require("react-native");
54
+ const react_native_webview_1 = require("react-native-webview");
55
+ const ort_min_1 = require("../../assets/onnx/ort-min");
56
+ class OnnxRuntimeBridge {
57
+ webViewRef = null;
58
+ messageCallbacks = new Map();
59
+ ready = false;
60
+ constructor(webViewRef) {
61
+ this.webViewRef = webViewRef;
62
+ }
63
+ async loadModels(detModelData, recModelData, wasmData, ageGenderModelData) {
64
+ console.log('[OnnxRuntimeBridge] loadModels called with data lengths:', {
65
+ detModelData: detModelData.length,
66
+ recModelData: recModelData.length,
67
+ wasmData: wasmData?.length ?? 0,
68
+ ageGenderModelData: ageGenderModelData?.length ?? 0,
69
+ });
70
+ return new Promise((resolve, reject) => {
71
+ const timeout = setTimeout(() => {
72
+ this.messageCallbacks.delete('modelsLoaded');
73
+ this.messageCallbacks.delete('error');
74
+ reject(new Error('loadModels timeout after 120s'));
75
+ }, 120000);
76
+ this.messageCallbacks.set('modelsLoaded', () => {
77
+ clearTimeout(timeout);
78
+ console.log('[OnnxRuntimeBridge] ✅ Models loaded');
79
+ this.messageCallbacks.delete('modelsLoaded');
80
+ resolve();
81
+ });
82
+ this.messageCallbacks.set('error', (data) => {
83
+ clearTimeout(timeout);
84
+ this.messageCallbacks.delete('error');
85
+ reject(new Error(data.error || JSON.stringify(data)));
86
+ });
87
+ this.sendMessage('loadModels', { detModelData, recModelData, wasmData, ageGenderModelData });
88
+ });
89
+ }
90
+ async runDetection(imageData, width, height) {
91
+ return new Promise((resolve, reject) => {
92
+ const timeout = setTimeout(() => {
93
+ this.messageCallbacks.delete('detectionResult');
94
+ this.messageCallbacks.delete('error');
95
+ reject(new Error('runDetection timeout after 60s'));
96
+ }, 60000);
97
+ this.messageCallbacks.set('detectionResult', (result) => {
98
+ clearTimeout(timeout);
99
+ this.messageCallbacks.delete('detectionResult');
100
+ resolve(result);
101
+ });
102
+ this.messageCallbacks.set('error', (data) => {
103
+ clearTimeout(timeout);
104
+ this.messageCallbacks.delete('error');
105
+ reject(new Error(data.error || JSON.stringify(data)));
106
+ });
107
+ // Encode Float32Array as base64 binary — avoids JSON serialisation overhead
108
+ // (~6.5 MB base64 vs ~25 MB JSON for a 640×640 detection input).
109
+ this.sendMessage('runDetection', {
110
+ imageDataB64: this.float32ToBase64(imageData),
111
+ width,
112
+ height,
113
+ });
114
+ });
115
+ }
116
+ async runRecognition(imageData, width, height) {
117
+ return new Promise((resolve, reject) => {
118
+ const timeout = setTimeout(() => {
119
+ this.messageCallbacks.delete('recognitionResult');
120
+ this.messageCallbacks.delete('error');
121
+ reject(new Error('runRecognition timeout after 60s'));
122
+ }, 60000);
123
+ this.messageCallbacks.set('recognitionResult', (result) => {
124
+ clearTimeout(timeout);
125
+ this.messageCallbacks.delete('recognitionResult');
126
+ resolve(result);
127
+ });
128
+ this.messageCallbacks.set('error', (data) => {
129
+ clearTimeout(timeout);
130
+ this.messageCallbacks.delete('error');
131
+ reject(new Error(data.error || JSON.stringify(data)));
132
+ });
133
+ this.sendMessage('runRecognition', {
134
+ imageDataB64: this.float32ToBase64(imageData),
135
+ width,
136
+ height,
137
+ });
138
+ });
139
+ }
140
+ async runAgeGender(imageData, width, height) {
141
+ return new Promise((resolve, reject) => {
142
+ const timeout = setTimeout(() => {
143
+ this.messageCallbacks.delete('ageGenderResult');
144
+ this.messageCallbacks.delete('error');
145
+ reject(new Error('runAgeGender timeout after 60s'));
146
+ }, 60000);
147
+ this.messageCallbacks.set('ageGenderResult', (result) => {
148
+ clearTimeout(timeout);
149
+ this.messageCallbacks.delete('ageGenderResult');
150
+ resolve(result);
151
+ });
152
+ this.messageCallbacks.set('error', (data) => {
153
+ clearTimeout(timeout);
154
+ this.messageCallbacks.delete('error');
155
+ reject(new Error(data.error || JSON.stringify(data)));
156
+ });
157
+ this.sendMessage('runAgeGender', {
158
+ imageDataB64: this.float32ToBase64(imageData),
159
+ width,
160
+ height,
161
+ });
162
+ });
163
+ }
164
+ handleMessage(event) {
165
+ try {
166
+ const message = JSON.parse(event.nativeEvent.data);
167
+ const { type, ...data } = message;
168
+ if (type === '_debug') {
169
+ console.log('[OnnxRuntimeBridge] WebView debug:', data.message);
170
+ return;
171
+ }
172
+ if (type === 'ready') {
173
+ this.ready = true;
174
+ }
175
+ const callback = this.messageCallbacks.get(type);
176
+ if (callback) {
177
+ callback(data);
178
+ }
179
+ }
180
+ catch (error) {
181
+ console.error('[OnnxRuntimeBridge] Error parsing message:', error);
182
+ }
183
+ }
184
+ /**
185
+ * Send a message to the WebView via postMessage.
186
+ * postMessage has no practical size limit (unlike injectJavaScript which fails
187
+ * on Android above ~4 MB). This is critical for large model and tensor payloads.
188
+ */
189
+ sendMessage(type, params = {}) {
190
+ if (this.webViewRef?.current) {
191
+ const message = JSON.stringify({ type, ...params });
192
+ console.log('[OnnxRuntimeBridge] postMessage →', type, '|', message.length, 'bytes');
193
+ this.webViewRef.current.postMessage(message);
194
+ }
195
+ else {
196
+ console.error('[OnnxRuntimeBridge] Cannot send message — webViewRef is null');
197
+ }
198
+ }
199
+ /**
200
+ * Encodes a Float32Array as base64 binary using chunked String.fromCharCode
201
+ * to avoid call-stack overflow on large arrays (e.g. 640×640×3 = 4.9 MB).
202
+ * Result is ~4/3× the byte size, far smaller than JSON (which costs ~6–8×).
203
+ */
204
+ float32ToBase64(arr) {
205
+ const uint8 = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
206
+ const CHUNK = 8192;
207
+ let binary = '';
208
+ for (let i = 0; i < uint8.length; i += CHUNK) {
209
+ binary += String.fromCharCode.apply(null, uint8.subarray(i, i + CHUNK));
210
+ }
211
+ return btoa(binary);
212
+ }
213
+ isReady() {
214
+ return this.ready;
215
+ }
216
+ }
217
+ exports.OnnxRuntimeBridge = OnnxRuntimeBridge;
218
+ const OnnxRuntimeWebView = ({ onReady, onError }) => {
219
+ const webViewRef = (0, react_1.useRef)(null);
220
+ const bridgeRef = (0, react_1.useRef)(null);
221
+ (0, react_1.useEffect)(() => {
222
+ return () => {
223
+ bridgeRef.current = null;
224
+ };
225
+ }, []);
226
+ const handleWebViewLoad = () => {
227
+ console.log('[OnnxRuntimeWebView] WebView loaded');
228
+ if (!bridgeRef.current) {
229
+ const bridge = new OnnxRuntimeBridge(webViewRef);
230
+ bridgeRef.current = bridge;
231
+ onReady(bridge);
232
+ }
233
+ };
234
+ const handleMessage = (event) => {
235
+ try {
236
+ if (bridgeRef.current) {
237
+ bridgeRef.current.handleMessage(event);
238
+ }
239
+ const message = JSON.parse(event.nativeEvent.data);
240
+ if (message.type === 'error') {
241
+ onError(message.error);
242
+ }
243
+ }
244
+ catch (error) {
245
+ console.error('[OnnxRuntimeWebView] Message handler error:', error);
246
+ }
247
+ };
248
+ // ort.min.js is inlined from the bundled assets/onnx/ort-min.ts export.
249
+ // The WebView HTML listens for messages via the standard 'message' event —
250
+ // both window and document listeners are registered for iOS/Android compatibility.
251
+ // baseUrl 'https://localhost' provides the Secure Context required for WebAssembly.
252
+ const htmlContent = `<!DOCTYPE html>
253
+ <html>
254
+ <head>
255
+ <meta charset="UTF-8">
256
+ <title>ONNX Worker</title>
257
+ <script>${ort_min_1.ortMinJsContent}</script>
258
+ </head>
259
+ <body>
260
+ <div id="status">Initializing...</div>
261
+ <script>
262
+ let detectionSession = null;
263
+ let recognitionSession = null;
264
+ let ageGenderSession = null;
265
+
266
+ function postToRN(message) {
267
+ if (window.ReactNativeWebView) {
268
+ window.ReactNativeWebView.postMessage(JSON.stringify(message));
269
+ }
270
+ }
271
+
272
+ function b64ToArrayBuffer(b64) {
273
+ const binary = atob(b64);
274
+ const bytes = new Uint8Array(binary.length);
275
+ for (let i = 0; i < binary.length; i++) {
276
+ bytes[i] = binary.charCodeAt(i);
277
+ }
278
+ return bytes.buffer;
279
+ }
280
+
281
+ function b64ToFloat32Array(b64) {
282
+ return new Float32Array(b64ToArrayBuffer(b64));
283
+ }
284
+
285
+ async function handleRNMessage(message) {
286
+ try {
287
+ console.log('[ONNX Worker] Received message:', message.type);
288
+ if (document.getElementById('status')) {
289
+ document.getElementById('status').textContent = 'Processing: ' + message.type;
290
+ }
291
+ switch (message.type) {
292
+ case 'loadModels':
293
+ await loadModels(message.detModelData, message.recModelData, message.wasmData, message.ageGenderModelData);
294
+ break;
295
+ case 'runDetection':
296
+ await runDetection(message.imageDataB64, message.width, message.height);
297
+ break;
298
+ case 'runRecognition':
299
+ await runRecognition(message.imageDataB64, message.width, message.height);
300
+ break;
301
+ case 'runAgeGender':
302
+ await runAgeGender(message.imageDataB64, message.width, message.height);
303
+ break;
304
+ }
305
+ } catch (error) {
306
+ console.error('[ONNX Worker] Handler error:', error);
307
+ if (document.getElementById('status')) {
308
+ document.getElementById('status').textContent = 'Error: ' + error.message;
309
+ }
310
+ postToRN({ type: 'error', error: error.message });
311
+ }
312
+ }
313
+
314
+ // React Native WebView delivers postMessage via the 'message' event.
315
+ // Both window and document listeners are registered for cross-platform
316
+ // compatibility (iOS fires on window, some Android versions on document).
317
+ function onRNMessage(event) {
318
+ let message;
319
+ try {
320
+ message = JSON.parse(event.data);
321
+ } catch (e) {
322
+ console.error('[ONNX Worker] Failed to parse message:', e);
323
+ return;
324
+ }
325
+ handleRNMessage(message);
326
+ }
327
+ window.addEventListener('message', onRNMessage);
328
+ document.addEventListener('message', onRNMessage);
329
+
330
+ async function init() {
331
+ try {
332
+ console.log('[ONNX Worker] Initializing...');
333
+ document.getElementById('status').textContent = 'Ready';
334
+ postToRN({ type: 'ready' });
335
+ } catch (error) {
336
+ console.error('[ONNX Worker] Init error:', error);
337
+ document.getElementById('status').textContent = 'Error: ' + error.message;
338
+ postToRN({ type: 'error', error: error.message });
339
+ }
340
+ }
341
+
342
+ async function loadModels(detBase64, recBase64, wasmBase64, ageGenderBase64) {
343
+ try {
344
+ if (wasmBase64) {
345
+ // ORT 1.16 always attempts WebAssembly.instantiateStreaming(fetch(path)) first.
346
+ // Setting wasmBinary alone doesn't prevent the fetch from being attempted,
347
+ // which fails with "TypeError: Failed to fetch" because there is no local server.
348
+ //
349
+ // Fix: create a Blob object URL from the WASM binary. ORT can then "fetch"
350
+ // it from a blob: URL without any network access. This is compatible with
351
+ // both WebAssembly.instantiateStreaming (fast path) and instantiate (fallback).
352
+ console.log('[ONNX Worker] Creating WASM blob URL...');
353
+ const wasmBytes = b64ToArrayBuffer(wasmBase64);
354
+ const wasmBlob = new Blob([wasmBytes], { type: 'application/wasm' });
355
+ const wasmUrl = URL.createObjectURL(wasmBlob);
356
+
357
+ // Map all ORT WASM filename variants to the same local blob URL so that
358
+ // ORT can load whichever WASM backend it selects without hitting the network.
359
+ ort.env.wasm.wasmPaths = {
360
+ 'ort-wasm.wasm': wasmUrl,
361
+ 'ort-wasm-simd.wasm': wasmUrl,
362
+ 'ort-wasm-threaded.wasm': wasmUrl,
363
+ 'ort-wasm-simd-threaded.wasm': wasmUrl,
364
+ };
365
+ ort.env.wasm.numThreads = 1;
366
+ console.log('[ONNX Worker] WASM blob URL set:', wasmUrl.substring(0, 40));
367
+ }
368
+
369
+ console.log('[ONNX Worker] Loading detection model...');
370
+ document.getElementById('status').textContent = 'Loading models...';
371
+ detectionSession = await ort.InferenceSession.create(b64ToArrayBuffer(detBase64));
372
+ console.log('[ONNX Worker] Detection session created');
373
+
374
+ console.log('[ONNX Worker] Loading recognition model...');
375
+ recognitionSession = await ort.InferenceSession.create(b64ToArrayBuffer(recBase64));
376
+ console.log('[ONNX Worker] Recognition session created');
377
+
378
+ if (ageGenderBase64) {
379
+ console.log('[ONNX Worker] Loading age/gender model...');
380
+ ageGenderSession = await ort.InferenceSession.create(b64ToArrayBuffer(ageGenderBase64));
381
+ console.log('[ONNX Worker] Age/gender session created');
382
+ }
383
+
384
+ document.getElementById('status').textContent = 'Models loaded';
385
+ postToRN({ type: 'modelsLoaded' });
386
+ } catch (error) {
387
+ console.error('[ONNX Worker] Model loading error:', error);
388
+ document.getElementById('status').textContent = 'Model load error: ' + error.message;
389
+ postToRN({ type: 'error', error: error.message });
390
+ }
391
+ }
392
+
393
+ async function runDetection(imageDataB64, width, height) {
394
+ try {
395
+ if (!detectionSession) throw new Error('Detection model not loaded');
396
+
397
+ const imageData = b64ToFloat32Array(imageDataB64);
398
+ const inputTensor = new ort.Tensor('float32', imageData, [1, 3, height, width]);
399
+ const inputName = detectionSession.inputNames[0];
400
+ const results = await detectionSession.run({ [inputName]: inputTensor });
401
+
402
+ const outputs = {};
403
+ for (const key of Object.keys(results)) {
404
+ outputs[key] = {
405
+ data: Array.from(results[key].data),
406
+ dims: results[key].dims,
407
+ };
408
+ }
409
+
410
+ postToRN({ type: 'detectionResult', outputs });
411
+ } catch (error) {
412
+ console.error('[ONNX Worker] Detection error:', error);
413
+ postToRN({ type: 'error', error: error.message });
414
+ }
415
+ }
416
+
417
+ async function runRecognition(imageDataB64, width, height) {
418
+ try {
419
+ if (!recognitionSession) throw new Error('Recognition model not loaded');
420
+
421
+ const imageData = b64ToFloat32Array(imageDataB64);
422
+ const inputTensor = new ort.Tensor('float32', imageData, [1, 3, width, height]);
423
+ const inputName = recognitionSession.inputNames[0];
424
+ const results = await recognitionSession.run({ [inputName]: inputTensor });
425
+
426
+ const outputKey = Object.keys(results)[0];
427
+ const output = results[outputKey];
428
+ postToRN({
429
+ type: 'recognitionResult',
430
+ data: Array.from(output.data),
431
+ dims: output.dims,
432
+ });
433
+ } catch (error) {
434
+ console.error('[ONNX Worker] Recognition error:', error);
435
+ postToRN({ type: 'error', error: error.message });
436
+ }
437
+ }
438
+
439
+ async function runAgeGender(imageDataB64, width, height) {
440
+ try {
441
+ if (!ageGenderSession) throw new Error('Age/Gender model not loaded');
442
+
443
+ const imageData = b64ToFloat32Array(imageDataB64);
444
+ // genderage usually takes 96x96 input dims from insightface
445
+ const inputTensor = new ort.Tensor('float32', imageData, [1, 3, width, height]);
446
+ const inputName = ageGenderSession.inputNames[0];
447
+ const results = await ageGenderSession.run({ [inputName]: inputTensor });
448
+
449
+ const outputKey = Object.keys(results)[0];
450
+ const outputData = Array.from(results[outputKey].data);
451
+
452
+ // Compute input range using a loop (spread operator overflows stack on large arrays)
453
+ let minVal = Infinity, maxVal = -Infinity;
454
+ for (let i = 0; i < imageData.length; i++) {
455
+ if (imageData[i] < minVal) minVal = imageData[i];
456
+ if (imageData[i] > maxVal) maxVal = imageData[i];
457
+ }
458
+ postToRN({ type: '_debug', message: '[AgeGender] input range min=' + minVal.toFixed(2) + ' max=' + maxVal.toFixed(2) + ' | raw output: ' + JSON.stringify(outputData) });
459
+
460
+ let gender = 'Unknown';
461
+ let age = 0;
462
+
463
+ if (outputData.length >= 3) {
464
+ // Model output: [female_prob, male_prob, age/100]
465
+ // gender = np.argmax(predictions[:2]) → 0=Female, 1=Male
466
+ const femaleProb = outputData[0];
467
+ const maleProb = outputData[1];
468
+ gender = maleProb > femaleProb ? 'Male' : 'Female';
469
+ age = Math.round(outputData[2] * 100);
470
+ } else if (outputData.length === 2) {
471
+ gender = outputData[0] > 0.5 ? 'Female' : 'Male';
472
+ age = Math.round(outputData[1]);
473
+ } else if (outputData.length > 0) {
474
+ // Fallback
475
+ gender = outputData[0] > 0 ? 'Female' : 'Male';
476
+ age = 0;
477
+ }
478
+
479
+ postToRN({
480
+ type: 'ageGenderResult',
481
+ age: age,
482
+ gender: gender
483
+ });
484
+ } catch (error) {
485
+ console.error('[ONNX Worker] Age/Gender error:', error);
486
+ postToRN({ type: 'error', error: error.message });
487
+ }
488
+ }
489
+
490
+ if (typeof ort !== 'undefined') {
491
+ init();
492
+ } else {
493
+ window.addEventListener('load', init);
494
+ }
495
+ </script>
496
+ </body>
497
+ </html>`;
498
+ return (<react_native_1.View style={styles.hidden}>
499
+ <react_native_webview_1.WebView ref={webViewRef} source={{ html: htmlContent, baseUrl: 'https://localhost' }} onMessage={handleMessage} javaScriptEnabled={true} domStorageEnabled={true} originWhitelist={['*']} style={styles.webview} onLoad={handleWebViewLoad} onError={(syntheticEvent) => {
500
+ const { nativeEvent } = syntheticEvent;
501
+ console.error('[OnnxRuntimeWebView] WebView error:', nativeEvent);
502
+ onError(nativeEvent.description || 'WebView load error');
503
+ }}/>
504
+ </react_native_1.View>);
505
+ };
506
+ exports.OnnxRuntimeWebView = OnnxRuntimeWebView;
507
+ const styles = react_native_1.StyleSheet.create({
508
+ hidden: {
509
+ position: 'absolute',
510
+ width: 0,
511
+ height: 0,
512
+ overflow: 'hidden',
513
+ },
514
+ webview: {
515
+ width: 1,
516
+ height: 1,
517
+ },
518
+ });
@@ -0,0 +1,59 @@
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
+ export type WasmStatus = 'idle' | 'loading' | 'ready' | 'error';
19
+ export interface ZkProofBridgeInterface {
20
+ loadWasmModule(): Promise<void>;
21
+ generateProof(embedding1: number[], embedding2: number[], nonce: number): Promise<{
22
+ proof: string;
23
+ publicInputs: string[];
24
+ }>;
25
+ verifyProof(proof: string, publicInputs: string[], vk: string): Promise<boolean>;
26
+ getProofHash(proof: string): Promise<string>;
27
+ status: WasmStatus;
28
+ }
29
+ export declare class ZkProofBridge implements ZkProofBridgeInterface {
30
+ private webViewRef;
31
+ private messageCallbacks;
32
+ status: WasmStatus;
33
+ constructor(webViewRef: React.RefObject<WebView | null>);
34
+ handleMessage(event: any): void;
35
+ loadWasmModule(): Promise<void>;
36
+ generateProof(embedding1: number[], embedding2: number[], nonce: number): Promise<{
37
+ proof: string;
38
+ publicInputs: string[];
39
+ }>;
40
+ verifyProof(proof: string, publicInputs: string[], vk: string): Promise<boolean>;
41
+ getProofHash(proof: string): Promise<string>;
42
+ sendMessage(type: string, payload?: any): void;
43
+ }
44
+ export interface ZkProofWebViewProps {
45
+ onReady: (bridge: ZkProofBridge) => void;
46
+ onError: (error: string) => void;
47
+ wasmData: {
48
+ wasmBase64: string;
49
+ workerHtml: string;
50
+ } | null;
51
+ }
52
+ /**
53
+ * ZkProofWebView - Plonky3 WASM WebView Bridge
54
+ *
55
+ * Loads the zk-worker.html containing full WASM bindings and message handlers.
56
+ * Injects WASM binary as base64 for initialization.
57
+ */
58
+ export declare const ZkProofWebView: React.FC<ZkProofWebViewProps>;
59
+ export default ZkProofWebView;