@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.
- package/README.md +46 -1
- package/assets/face-guidance/pose-guidance.js.txt +4 -2
- package/assets/liveness/liveness.js.txt +3 -0
- package/dist/FaceZkSdk.d.ts +69 -0
- package/dist/FaceZkSdk.js +136 -0
- package/dist/assets/face-guidance/pose-guidance.js.txt +4 -2
- package/dist/assets/liveness/liveness.js.txt +3 -0
- package/dist/assets/onnx/ort-min.d.ts +1 -0
- package/dist/assets/onnx/ort-min.js +11 -0
- package/dist/config/defaults.d.ts +51 -0
- package/dist/config/defaults.js +61 -0
- package/dist/config/types.d.ts +169 -0
- package/dist/config/types.js +17 -0
- package/dist/core/enrollment-core.d.ts +70 -0
- package/dist/core/enrollment-core.js +206 -0
- package/dist/core/idGenerator.d.ts +11 -0
- package/dist/core/idGenerator.js +32 -0
- package/dist/core/matching.d.ts +69 -0
- package/dist/core/matching.js +101 -0
- package/dist/core/types.d.ts +379 -0
- package/dist/core/types.js +37 -0
- package/dist/core/verification-core.d.ts +120 -0
- package/dist/core/verification-core.js +442 -0
- package/dist/core/zk-core.d.ts +69 -0
- package/dist/core/zk-core.js +244 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +51 -0
- package/dist/react-native/adapters/faceEmbeddingProvider.d.ts +38 -0
- package/dist/react-native/adapters/faceEmbeddingProvider.js +45 -0
- package/dist/react-native/adapters/imageDataProvider.d.ts +53 -0
- package/dist/react-native/adapters/imageDataProvider.js +134 -0
- package/dist/react-native/adapters/livenessProvider.d.ts +133 -0
- package/dist/react-native/adapters/livenessProvider.js +150 -0
- package/dist/react-native/adapters/zkProofEngine-webview.d.ts +73 -0
- package/dist/react-native/adapters/zkProofEngine-webview.js +135 -0
- package/dist/react-native/bundledRuntimeAssets.d.ts +39 -0
- package/dist/react-native/bundledRuntimeAssets.js +44 -0
- package/dist/react-native/components/FacePoseGuidanceWebView.d.ts +30 -0
- package/dist/react-native/components/FacePoseGuidanceWebView.js +530 -0
- package/dist/react-native/components/LivenessWebView.d.ts +39 -0
- package/dist/react-native/components/LivenessWebView.js +386 -0
- package/dist/react-native/components/OnnxRuntimeWebView.d.ts +58 -0
- package/dist/react-native/components/OnnxRuntimeWebView.js +518 -0
- package/dist/react-native/components/ZkProofWebView.d.ts +59 -0
- package/dist/react-native/components/ZkProofWebView.js +297 -0
- package/dist/react-native/dependencies.d.ts +144 -0
- package/dist/react-native/dependencies.js +130 -0
- package/dist/react-native/hooks/useOnnxLoader.d.ts +37 -0
- package/dist/react-native/hooks/useOnnxLoader.js +74 -0
- package/dist/react-native/hooks/useWasmLoader.d.ts +30 -0
- package/dist/react-native/hooks/useWasmLoader.js +158 -0
- package/dist/react-native/index.d.ts +61 -0
- package/dist/react-native/index.js +144 -0
- package/dist/react-native/services/FaceRecognition.d.ts +92 -0
- package/dist/react-native/services/FaceRecognition.js +674 -0
- package/dist/react-native/ui/FaceZkVerificationFlow.d.ts +97 -0
- package/dist/react-native/ui/FaceZkVerificationFlow.js +477 -0
- package/dist/react-native/ui/ReferenceEnrollmentFlow.d.ts +72 -0
- package/dist/react-native/ui/ReferenceEnrollmentFlow.js +369 -0
- package/dist/react-native/utils/faceAlignment.d.ts +37 -0
- package/dist/react-native/utils/faceAlignment.js +186 -0
- package/dist/react-native/utils/modelInitialisationChecks.d.ts +36 -0
- package/dist/react-native/utils/modelInitialisationChecks.js +128 -0
- package/dist/react-native/utils/resolveModelUri.d.ts +55 -0
- package/dist/react-native/utils/resolveModelUri.js +211 -0
- package/dist/react-native/utils/resolveRuntimeAsset.d.ts +25 -0
- package/dist/react-native/utils/resolveRuntimeAsset.js +94 -0
- package/dist/react-native/utils/resolveUiConfig.d.ts +41 -0
- package/dist/react-native/utils/resolveUiConfig.js +81 -0
- package/dist/storage/defaultStorageAdapter.d.ts +44 -0
- package/dist/storage/defaultStorageAdapter.js +344 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/face-zk.config.example.js +10 -3
- 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 {};
|