@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,530 @@
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.FacePoseGuidanceWebView = 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 bundledRuntimeAssets_1 = require("../bundledRuntimeAssets");
59
+ const resolveModelUri_1 = require("../utils/resolveModelUri");
60
+ const resolveRuntimeAsset_1 = require("../utils/resolveRuntimeAsset");
61
+ const FacePoseGuidanceWebView = ({ referenceImageUri, onSuccess, onError, onCancel, manualTargetPose }) => {
62
+ const webViewRef = (0, react_1.useRef)(null);
63
+ const iframeRef = (0, react_1.useRef)(null);
64
+ const modelBase64Ref = (0, react_1.useRef)(null);
65
+ const [htmlContent, setHtmlContent] = (0, react_1.useState)(null);
66
+ const [permission, requestPermission] = (0, expo_camera_1.useCameraPermissions)();
67
+ const [isLoading, setIsLoading] = (0, react_1.useState)(true);
68
+ const [step, setStep] = (0, react_1.useState)("LOADING");
69
+ const [targetPose, setTargetPose] = (0, react_1.useState)(null);
70
+ (0, react_1.useEffect)(() => {
71
+ loadResources();
72
+ // Web Message Listener
73
+ if (react_native_1.Platform.OS === "web") {
74
+ const messageHandler = (event) => {
75
+ try {
76
+ // Ensure message comes from our iframe/logic
77
+ if (typeof event.data === "string") {
78
+ // We need to parse checking if it's our format
79
+ // Our format: JSON string with { type: ... }
80
+ // But postMessage might send object if not stringified.
81
+ // The polyfill sends: window.parent.postMessage(msg, '*') where msg is the JSON string?
82
+ // Let's check polyfill below.
83
+ }
84
+ // If the data is already an object (common in window.postMessage), use it.
85
+ // If it's a string, parse it.
86
+ let data = event.data;
87
+ if (typeof data === "string") {
88
+ try {
89
+ data = JSON.parse(data);
90
+ }
91
+ catch (e) {
92
+ // Not our JSON message, ignore
93
+ return;
94
+ }
95
+ }
96
+ if (data && data.type) {
97
+ handleWebMessage(data);
98
+ }
99
+ }
100
+ catch (e) {
101
+ console.error("Web Message Error:", e);
102
+ }
103
+ };
104
+ window.addEventListener("message", messageHandler);
105
+ return () => window.removeEventListener("message", messageHandler);
106
+ }
107
+ }, []);
108
+ const handleWebMessage = (data) => {
109
+ console.log("[FacePoseGuidance Web] Message:", data.type);
110
+ if (data.type === "analysis_complete") {
111
+ setTargetPose(data.pose);
112
+ setStep("INSTRUCTION");
113
+ }
114
+ else if (data.type === "analysis_failed") {
115
+ onError("Reference analysis failed: " + data.message);
116
+ }
117
+ else if (data.type === "success") {
118
+ onSuccess(data.image, { targetPose, capturedPose: data.pose, faceMeshLandmarks: data.faceMeshLandmarks });
119
+ }
120
+ else if (data.type === "error") {
121
+ onError(data.message);
122
+ }
123
+ else if (data.type === "modelLoaded") {
124
+ console.log("Anti-spoof model loaded in WebView");
125
+ }
126
+ else if (data.type === "log") {
127
+ console.log("[WebView Log]", data.message);
128
+ }
129
+ };
130
+ const loadResources = async () => {
131
+ try {
132
+ console.log("[FacePoseGuidance] Loading resources...");
133
+ // antispoof model must be provided via initializeSdk({ models: { antispoof } })
134
+ if (!FaceZkSdk_1.FaceZkSdk.isInitialized()) {
135
+ throw new Error("[FaceZkSdk] SDK not initialized. Call initializeSdk() before using face guidance.\n" +
136
+ "Required: initializeSdk({ models: { detection, recognition, antispoof: { url: '...' } } })");
137
+ }
138
+ const sdkConfig = FaceZkSdk_1.FaceZkSdk.getConfig();
139
+ if (!sdkConfig.models.antispoof) {
140
+ throw new Error("[FaceZkSdk] models.antispoof is required for face pose guidance but was not provided.\n" +
141
+ "Add it to initializeSdk(): { models: { ..., antispoof: { url: 'https://...' } } }");
142
+ }
143
+ const allowedDomains = sdkConfig.allowedDomains;
144
+ // Pick runtime asset source: config override if provided, else bundled fallback.
145
+ // Used in the web branch where we need the URI before fetching text content.
146
+ const getRuntimeSource = (key) => sdkConfig.runtimeAssets?.[key] ?? bundledRuntimeAssets_1.BUNDLED_RUNTIME_ASSETS[key];
147
+ let html, jsContent, logicContent, antispoofContent, modelBase64;
148
+ if (react_native_1.Platform.OS === "web") {
149
+ // On Web: resolve each asset to a URI then fetch its text content.
150
+ // Uses getRuntimeSource so runtimeAssets config overrides apply on web too.
151
+ const [htmlUri, jsUri, logicUri, antispoofJsUri, modelUri] = await Promise.all([
152
+ (0, resolveModelUri_1.resolveModelUri)(getRuntimeSource('faceGuidanceHtml'), undefined, allowedDomains),
153
+ (0, resolveModelUri_1.resolveModelUri)(getRuntimeSource('faceGuidancePoseJs'), undefined, allowedDomains),
154
+ (0, resolveModelUri_1.resolveModelUri)(getRuntimeSource('faceGuidanceLogicJs'), undefined, allowedDomains),
155
+ (0, resolveModelUri_1.resolveModelUri)(getRuntimeSource('antispoofJs'), undefined, allowedDomains),
156
+ (0, resolveModelUri_1.resolveModelUri)(sdkConfig.models.antispoof, undefined, allowedDomains),
157
+ ]);
158
+ const [htmlRes, jsRes, logicRes, antispoofRes] = await Promise.all([
159
+ fetch(htmlUri),
160
+ fetch(jsUri),
161
+ fetch(logicUri),
162
+ fetch(antispoofJsUri),
163
+ ]);
164
+ html = await htmlRes.text();
165
+ jsContent = await jsRes.text();
166
+ logicContent = await logicRes.text();
167
+ antispoofContent = await antispoofRes.text();
168
+ // On Web, pass model URI directly (avoid large base64 payload)
169
+ modelBase64 = modelUri;
170
+ // Inject Polyfill for ReactNativeWebView on Web
171
+ jsContent = `
172
+ ${jsContent}
173
+ window.ReactNativeWebView = {
174
+ postMessage: function(data) {
175
+ window.parent.postMessage(data, '*');
176
+ }
177
+ };
178
+ `;
179
+ }
180
+ else {
181
+ // On Native: resolveRuntimeAsset handles URI resolution + FileSystem read in one call
182
+ [html, jsContent, logicContent, antispoofContent] = await Promise.all([
183
+ (0, resolveRuntimeAsset_1.resolveRuntimeAsset)('faceGuidanceHtml', 'utf8', allowedDomains),
184
+ (0, resolveRuntimeAsset_1.resolveRuntimeAsset)('faceGuidancePoseJs', 'utf8', allowedDomains),
185
+ (0, resolveRuntimeAsset_1.resolveRuntimeAsset)('faceGuidanceLogicJs', 'utf8', allowedDomains),
186
+ (0, resolveRuntimeAsset_1.resolveRuntimeAsset)('antispoofJs', 'utf8', allowedDomains),
187
+ ]);
188
+ const antispoofUri = await (0, resolveModelUri_1.resolveModelUri)(sdkConfig.models.antispoof, undefined, allowedDomains);
189
+ modelBase64 = await FileSystem.readAsStringAsync(antispoofUri, {
190
+ encoding: FileSystem.EncodingType.Base64,
191
+ });
192
+ }
193
+ // Inject JS into HTML
194
+ // Combine scripts and inject
195
+ const combinedScript = `
196
+ ${logicContent}
197
+ ${antispoofContent}
198
+ ${jsContent}
199
+ `;
200
+ const finalHtml = html.replace('<script src="pose-guidance.js"></script>', `<script>${combinedScript}</script>`);
201
+ console.log("[FacePoseGuidance] HTML prepared");
202
+ setHtmlContent(finalHtml);
203
+ setStep("ANALYSIS");
204
+ modelBase64Ref.current = modelBase64;
205
+ }
206
+ catch (error) {
207
+ console.error("[FacePoseGuidance] Error loading resources:", error);
208
+ onError("Failed to load resources: " + error.message);
209
+ }
210
+ };
211
+ const handleMessage = (event) => {
212
+ if (!event)
213
+ return;
214
+ const data = event.nativeEvent ? event.nativeEvent.data : event.data;
215
+ try {
216
+ const parsed = typeof data === "string" ? JSON.parse(data) : data;
217
+ if (parsed.type === "analysis_complete") {
218
+ console.log("Analysis Complete", parsed.pose);
219
+ setTargetPose(parsed.pose);
220
+ setStep("INSTRUCTION");
221
+ }
222
+ else if (parsed.type === "success") {
223
+ onSuccess(parsed.image, { targetPose, capturedPose: parsed.pose, faceMeshLandmarks: parsed.faceMeshLandmarks });
224
+ }
225
+ else if (parsed.type === "error") {
226
+ console.error("WebView Error:", parsed.message);
227
+ onError(parsed.message);
228
+ }
229
+ else if (parsed.type === "modelLoaded") {
230
+ console.log("Anti-spoof model loaded in WebView");
231
+ }
232
+ else if (parsed.type === "log") {
233
+ console.log("[WebView Log]", parsed.message);
234
+ }
235
+ }
236
+ catch (e) {
237
+ console.log("Message parse error", e);
238
+ }
239
+ };
240
+ const injectScript = (script) => {
241
+ if (react_native_1.Platform.OS === "web") {
242
+ // @ts-expect-error — contentWindow.eval is present at runtime but not in the iframe element types
243
+ iframeRef.current?.contentWindow?.eval(script);
244
+ }
245
+ else {
246
+ webViewRef.current?.injectJavaScript(script);
247
+ }
248
+ };
249
+ const startAnalysis = async () => {
250
+ if (!referenceImageUri && !manualTargetPose)
251
+ return;
252
+ try {
253
+ let base64 = "";
254
+ if (referenceImageUri) {
255
+ if (react_native_1.Platform.OS === "web") {
256
+ base64 = referenceImageUri;
257
+ }
258
+ else {
259
+ base64 = await FileSystem.readAsStringAsync(referenceImageUri, {
260
+ encoding: FileSystem.EncodingType.Base64,
261
+ });
262
+ }
263
+ }
264
+ const script = `
265
+ window.startAnalysis("${base64}");
266
+ true;
267
+ `;
268
+ injectScript(script);
269
+ }
270
+ catch (e) {
271
+ console.warn("Analysis start failed", e);
272
+ onError("Failed to read reference image");
273
+ }
274
+ };
275
+ const startGuidance = async () => {
276
+ let currentPerm = permission;
277
+ if (!currentPerm?.granted) {
278
+ currentPerm = await requestPermission();
279
+ }
280
+ if (!currentPerm?.granted) {
281
+ onError("Camera permission is required to proceed.");
282
+ return;
283
+ }
284
+ setStep("GUIDANCE");
285
+ const script = `
286
+ window.startCamera();
287
+ true;
288
+ `;
289
+ injectScript(script);
290
+ };
291
+ const handleWebViewLoad = () => {
292
+ console.log("[FacePoseGuidance] WebView Loaded");
293
+ // Inject model data safely
294
+ if (modelBase64Ref.current) {
295
+ let injectModelScript;
296
+ if (react_native_1.Platform.OS === "web") {
297
+ // For Web, modelBase64Ref contains the URI
298
+ injectModelScript = `
299
+ if(window.loadAntispoofModelFromUrl) {
300
+ window.loadAntispoofModelFromUrl("${modelBase64Ref.current}");
301
+ }
302
+ `;
303
+ }
304
+ else {
305
+ // For Native, contains Base64
306
+ injectModelScript = `
307
+ if(window.loadAntispoofModel) {
308
+ window.loadAntispoofModel("${modelBase64Ref.current}");
309
+ }
310
+ `;
311
+ }
312
+ injectScript(injectModelScript);
313
+ }
314
+ if (step === "ANALYSIS") {
315
+ if (manualTargetPose) {
316
+ const script = `
317
+ (function() {
318
+ let attempts = 0;
319
+ const interval = setInterval(() => {
320
+ if (window.setTargetPose) {
321
+ clearInterval(interval);
322
+ window.setTargetPose(${manualTargetPose.yaw}, ${manualTargetPose.pitch}, ${manualTargetPose.roll});
323
+ } else {
324
+ attempts++;
325
+ if (attempts > 10) {
326
+ clearInterval(interval);
327
+ window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'error', message: 'setTargetPose missing after timeout' }));
328
+ }
329
+ }
330
+ }, 500);
331
+ })();
332
+ true;
333
+ `;
334
+ injectScript(script);
335
+ }
336
+ else if (referenceImageUri) {
337
+ // Have an image, let's call our internal helper to base64 it and send it
338
+ startAnalysis();
339
+ }
340
+ else {
341
+ // No reference and no manual target, skip analysis and go straight to instruction for straight-face
342
+ setTargetPose({ yaw: 0, pitch: 0, roll: 0 });
343
+ setStep("INSTRUCTION");
344
+ const script = `
345
+ (function() {
346
+ let attempts = 0;
347
+ const interval = setInterval(() => {
348
+ if (window.setTargetPose) {
349
+ clearInterval(interval);
350
+ window.setTargetPose(0, 0, 0);
351
+ } else {
352
+ attempts++;
353
+ if (attempts > 10) {
354
+ clearInterval(interval);
355
+ }
356
+ }
357
+ }, 500);
358
+ })();
359
+ true;
360
+ `;
361
+ injectScript(script);
362
+ }
363
+ }
364
+ };
365
+ if (!htmlContent) {
366
+ return (<react_native_1.View style={styles.center}>
367
+ <react_native_1.ActivityIndicator size="large" color="#0000ff"/>
368
+ <react_native_1.Text style={styles.textLoading}>Loading resources...</react_native_1.Text>
369
+ </react_native_1.View>);
370
+ }
371
+ return (<react_native_1.View style={styles.container}>
372
+ {react_native_1.Platform.OS === "web" ? (<iframe ref={iframeRef} srcDoc={htmlContent} style={{ width: "100%", height: "100%", border: "none" }} onLoad={handleWebViewLoad}/>) : (<react_native_webview_1.WebView ref={webViewRef} originWhitelist={["*"]}
373
+ // CRITICAL: baseUrl must be https://localhost/ to provide a Secure Context for WebAssembly/ONNX.
374
+ // It does NOT make actual network requests, but prevents the WebView from throwing security errors.
375
+ source={{ html: htmlContent, baseUrl: "https://localhost/" }} style={styles.webview} javaScriptEnabled={true} domStorageEnabled={true} allowsInlineMediaPlayback={true} mediaPlaybackRequiresUserAction={false} onMessage={handleMessage} onLoadEnd={handleWebViewLoad}
376
+ // @ts-expect-error — onPermissionRequest is an Android WebView prop not in react-native-webview types
377
+ onPermissionRequest={(event) => {
378
+ const { resources } = event.nativeEvent;
379
+ if (resources.includes("camera")) {
380
+ event.grant(resources);
381
+ }
382
+ }}/>)}
383
+
384
+ {/* Instruction Overlay */}
385
+ {step === "INSTRUCTION" && (<react_native_1.View style={styles.instructionOverlay}>
386
+ <react_native_1.View style={styles.instructionCard}>
387
+ <react_native_1.Text style={styles.instructionTitle}>
388
+ {referenceImageUri ? "Pose Matching" : "Face Enrollment"}
389
+ </react_native_1.Text>
390
+ <react_native_1.Text style={styles.instructionBody}>
391
+ {referenceImageUri
392
+ ? "We need to match the pose in your reference image."
393
+ : "We will now capture your face. Please look straight into the camera."}
394
+ </react_native_1.Text>
395
+
396
+ <react_native_1.View style={styles.poseContainer}>
397
+ {referenceImageUri ? (<react_native_1.Image source={{ uri: referenceImageUri }} style={styles.referenceImage}/>) : null}
398
+ <react_native_1.Text style={styles.poseText}>
399
+ {targetPose?.yaw && Math.abs(targetPose.yaw) > 10
400
+ ? `Look slightly ${targetPose.yaw > 0 ? "Left" : "Right"}`
401
+ : "Look Straight"}
402
+ </react_native_1.Text>
403
+ </react_native_1.View>
404
+
405
+ <react_native_1.Text style={styles.instructionSub}>
406
+ • Remove glasses • Ensure good lighting • Follow the arrows
407
+ </react_native_1.Text>
408
+
409
+ <react_native_1.TouchableOpacity style={styles.button} onPress={startGuidance}>
410
+ <react_native_1.Text style={styles.buttonText}>I'm Ready</react_native_1.Text>
411
+ </react_native_1.TouchableOpacity>
412
+
413
+ {onCancel && (<react_native_1.TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
414
+ <react_native_1.Text style={styles.cancelText}>Go Back</react_native_1.Text>
415
+ </react_native_1.TouchableOpacity>)}
416
+ </react_native_1.View>
417
+ </react_native_1.View>)}
418
+
419
+ {/* Loading Overlay for Analysis phase */}
420
+ {step === "ANALYSIS" && (<react_native_1.View style={[
421
+ styles.instructionOverlay,
422
+ { backgroundColor: "rgba(0,0,0,0.8)" },
423
+ ]}>
424
+ <react_native_1.ActivityIndicator size="large" color="#6366f1"/>
425
+ <react_native_1.Text style={styles.textLoading}>
426
+ {referenceImageUri ? "Analyzing Reference Pose..." : "Initializing Camera Session..."}
427
+ </react_native_1.Text>
428
+ </react_native_1.View>)}
429
+ </react_native_1.View>);
430
+ };
431
+ exports.FacePoseGuidanceWebView = FacePoseGuidanceWebView;
432
+ const styles = react_native_1.StyleSheet.create({
433
+ container: {
434
+ flex: 1,
435
+ backgroundColor: "#000",
436
+ },
437
+ webview: {
438
+ flex: 1,
439
+ backgroundColor: "transparent",
440
+ },
441
+ center: {
442
+ flex: 1,
443
+ justifyContent: "center",
444
+ alignItems: "center",
445
+ backgroundColor: "#111",
446
+ },
447
+ textLoading: {
448
+ marginTop: 16,
449
+ color: "#ccc",
450
+ fontSize: 14,
451
+ },
452
+ instructionOverlay: {
453
+ ...react_native_1.StyleSheet.absoluteFillObject,
454
+ backgroundColor: "rgba(0,0,0,0.85)",
455
+ justifyContent: "center",
456
+ alignItems: "center",
457
+ padding: 24,
458
+ },
459
+ instructionCard: {
460
+ width: "100%",
461
+ backgroundColor: "#1e1e1e",
462
+ borderRadius: 20,
463
+ padding: 24,
464
+ alignItems: "center",
465
+ borderWidth: 1,
466
+ borderColor: "#333",
467
+ },
468
+ instructionTitle: {
469
+ fontSize: 22,
470
+ fontWeight: "bold",
471
+ color: "#fff",
472
+ marginBottom: 12,
473
+ },
474
+ instructionBody: {
475
+ fontSize: 16,
476
+ color: "#aaa",
477
+ textAlign: "center",
478
+ marginBottom: 20,
479
+ },
480
+ poseContainer: {
481
+ alignItems: "center",
482
+ marginBottom: 24,
483
+ backgroundColor: "#000",
484
+ padding: 12,
485
+ borderRadius: 12,
486
+ },
487
+ referenceImage: {
488
+ width: 100,
489
+ height: 100,
490
+ borderRadius: 50,
491
+ marginBottom: 8,
492
+ borderWidth: 2,
493
+ borderColor: "#6366f1",
494
+ },
495
+ poseText: {
496
+ color: "#6366f1",
497
+ fontWeight: "bold",
498
+ fontSize: 16,
499
+ },
500
+ instructionSub: {
501
+ fontSize: 14,
502
+ color: "#777",
503
+ textAlign: "left",
504
+ width: "100%",
505
+ marginBottom: 24,
506
+ lineHeight: 22,
507
+ },
508
+ button: {
509
+ backgroundColor: "#6366f1",
510
+ paddingVertical: 14,
511
+ paddingHorizontal: 32,
512
+ borderRadius: 12,
513
+ width: "100%",
514
+ alignItems: "center",
515
+ },
516
+ buttonText: {
517
+ color: "#fff",
518
+ fontSize: 16,
519
+ fontWeight: "bold",
520
+ },
521
+ cancelButton: {
522
+ marginTop: 12,
523
+ padding: 10,
524
+ },
525
+ cancelText: {
526
+ color: "#aaa",
527
+ fontSize: 14,
528
+ textDecorationLine: "underline",
529
+ },
530
+ });
@@ -0,0 +1,39 @@
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
+ export type LivenessPhase = "init" | "searching_far" | "recenter" | "challenge" | "move_closer" | "verifying_near" | "success" | "fail";
18
+ export type InstructionCode = "MOVE_BACK" | "CENTER_FACE" | "LOOK_STRAIGHT" | "BLINK" | "TURN_LEFT" | "TURN_RIGHT" | "MOVE_CLOSER" | "HOLD_PHONE_HIGHER" | "HOLD_PHONE_LOWER" | "HEAD_STRAIGHT" | "HOLD_STILL" | "VERIFYING" | "VERIFICATION_FAILED";
19
+ export interface LivenessState {
20
+ phase: LivenessPhase;
21
+ instructionCode: InstructionCode;
22
+ promptText: string;
23
+ progressPercent: number;
24
+ isFaceLocked: boolean;
25
+ icon: string;
26
+ }
27
+ export interface ZkFaceAuthProps {
28
+ onSuccess: (imageUri: string, metadata?: any) => void;
29
+ onError: (message: string) => void;
30
+ manualTargetPose?: {
31
+ yaw: number;
32
+ pitch: number;
33
+ roll: number;
34
+ };
35
+ referenceImageUri?: string;
36
+ renderOverlay?: (state: LivenessState) => React.ReactNode;
37
+ headless?: boolean;
38
+ }
39
+ export declare const ZkFaceAuth: React.FC<ZkFaceAuthProps>;