@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,97 @@
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
+ /**
17
+ * Face+ZK Verification Flow Component
18
+ *
19
+ * A pre-built React Native UI component for face verification with optional ZK proofs.
20
+ * This component handles:
21
+ * - Reference resolution (template, input, or ID from storage)
22
+ * - Liveness detection (via ZkFaceAuth WebView)
23
+ * - Live face capture and embedding extraction
24
+ * - Face matching against reference
25
+ * - Optional ZK proof generation and verification
26
+ */
27
+ import React from "react";
28
+ import type { ReferenceTemplate, ReferenceTemplateInput, ReferenceId, VerificationOutcome, FaceZkRuntimeConfig, VerificationOptions, UiConfig, VerificationStage } from "../../core/types";
29
+ import type { FaceEmbeddingProvider, LivenessProvider } from "../../core/verification-core";
30
+ /**
31
+ * Props for FaceZkVerificationFlow component
32
+ */
33
+ export interface FaceZkVerificationFlowProps {
34
+ /** SDK configuration */
35
+ sdkConfig: FaceZkRuntimeConfig;
36
+ /** Reference to verify against (template, input, or ID) */
37
+ reference: ReferenceTemplate | ReferenceTemplateInput | ReferenceId;
38
+ /** Verification mode */
39
+ mode: "verify-only" | "verify-with-proof";
40
+ /** Face embedding provider */
41
+ embeddingProvider: FaceEmbeddingProvider;
42
+ /** Optional liveness provider */
43
+ livenessProvider?: LivenessProvider;
44
+ /** Per-call verification options */
45
+ verificationOptions?: VerificationOptions;
46
+ /** UI customization config */
47
+ uiConfig?: UiConfig;
48
+ /**
49
+ * Optional reference pose for guided liveness.
50
+ * When provided, the liveness check will ask the user to match this pose
51
+ * (extracted from the enrolled reference template).
52
+ */
53
+ referencePose?: {
54
+ yaw: number;
55
+ pitch: number;
56
+ roll: number;
57
+ };
58
+ /** Called when verification completes (success or failure) */
59
+ onComplete: (outcome: VerificationOutcome) => void;
60
+ /** Called when user cancels */
61
+ onCancel?: () => void;
62
+ /** Called on stage changes */
63
+ onStageChange?: (stage: VerificationStage) => void;
64
+ /** Whether to show the flow as a modal */
65
+ modal?: boolean;
66
+ /** Modal visibility (if modal=true) */
67
+ visible?: boolean;
68
+ /** Custom overlay renderer for liveness */
69
+ renderOverlay?: (state: unknown) => React.ReactNode;
70
+ }
71
+ /**
72
+ * A drop-in React Native UI component orchestrating the complete Face+ZK verification lifecycle.
73
+ *
74
+ * This component handles the complex choreography between:
75
+ * 1. Loading reference templates and initializing cryptographic WASM engines.
76
+ * 2. Mounting the `ZkFaceAuth` camera view to capture liveness and extract embeddings.
77
+ * 3. Delegating the matched vectors to the background ZK engine to cryptographically prove identity.
78
+ *
79
+ * **UI Customization:** You can aggressively customize this flow using the `uiConfig` prop, appending your own brand colors, localized strings, or entirely replacing the rendering of different stages (Loading, Success, Error).
80
+ *
81
+ * @param {FaceZkVerificationFlowProps} props - Configuration for the UI and required platform adapters.
82
+ * @returns {React.FC} A safely encapsulated verification modal or inline view.
83
+ *
84
+ * @example
85
+ * <FaceZkVerificationFlow
86
+ * sdkConfig={config}
87
+ * reference={refId}
88
+ * mode="verify-with-proof"
89
+ * embeddingProvider={provider}
90
+ * onComplete={(outcome) => {
91
+ * if (outcome.success) {
92
+ * Alert.alert("Authorized", `Hash: ${outcome.zkProof.hash}`);
93
+ * }
94
+ * }}
95
+ * />
96
+ */
97
+ export declare const FaceZkVerificationFlow: React.FC<FaceZkVerificationFlowProps>;
@@ -0,0 +1,477 @@
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.FaceZkVerificationFlow = void 0;
52
+ /**
53
+ * Face+ZK Verification Flow Component
54
+ *
55
+ * A pre-built React Native UI component for face verification with optional ZK proofs.
56
+ * This component handles:
57
+ * - Reference resolution (template, input, or ID from storage)
58
+ * - Liveness detection (via ZkFaceAuth WebView)
59
+ * - Live face capture and embedding extraction
60
+ * - Face matching against reference
61
+ * - Optional ZK proof generation and verification
62
+ */
63
+ const react_1 = __importStar(require("react"));
64
+ const react_native_1 = require("react-native");
65
+ const verification_core_1 = require("../../core/verification-core");
66
+ const zkProofEngine_webview_1 = require("../adapters/zkProofEngine-webview");
67
+ const livenessProvider_1 = require("../adapters/livenessProvider");
68
+ const FileSystem = __importStar(require("expo-file-system/legacy"));
69
+ const dependencies_1 = require("../dependencies");
70
+ const resolveUiConfig_1 = require("../utils/resolveUiConfig");
71
+ const FaceZkSdk_1 = require("../../FaceZkSdk");
72
+ /**
73
+ * A drop-in React Native UI component orchestrating the complete Face+ZK verification lifecycle.
74
+ *
75
+ * This component handles the complex choreography between:
76
+ * 1. Loading reference templates and initializing cryptographic WASM engines.
77
+ * 2. Mounting the `ZkFaceAuth` camera view to capture liveness and extract embeddings.
78
+ * 3. Delegating the matched vectors to the background ZK engine to cryptographically prove identity.
79
+ *
80
+ * **UI Customization:** You can aggressively customize this flow using the `uiConfig` prop, appending your own brand colors, localized strings, or entirely replacing the rendering of different stages (Loading, Success, Error).
81
+ *
82
+ * @param {FaceZkVerificationFlowProps} props - Configuration for the UI and required platform adapters.
83
+ * @returns {React.FC} A safely encapsulated verification modal or inline view.
84
+ *
85
+ * @example
86
+ * <FaceZkVerificationFlow
87
+ * sdkConfig={config}
88
+ * reference={refId}
89
+ * mode="verify-with-proof"
90
+ * embeddingProvider={provider}
91
+ * onComplete={(outcome) => {
92
+ * if (outcome.success) {
93
+ * Alert.alert("Authorized", `Hash: ${outcome.zkProof.hash}`);
94
+ * }
95
+ * }}
96
+ * />
97
+ */
98
+ const FaceZkVerificationFlow = ({ sdkConfig, reference, mode, embeddingProvider, livenessProvider, verificationOptions = {}, uiConfig = {}, referencePose, onComplete, onCancel, onStageChange, modal = false, visible = true, renderOverlay, }) => {
99
+ const [stage, setStage] = (0, react_1.useState)("IDLE");
100
+ const [bridgeReady, setBridgeReady] = (0, react_1.useState)(false);
101
+ const [zkBridge, setZkBridge] = (0, react_1.useState)(null);
102
+ const [outcome, setOutcome] = (0, react_1.useState)(null);
103
+ // FaceMesh landmarks from the liveness WebView, stored for research/analytics.
104
+ const faceMeshLandmarksRef = (0, react_1.useRef)(null);
105
+ // Liveness result produced by ZkFaceAuth WebView (ONNX anti-spoof score + challenges).
106
+ // Stored in a ref so runVerification always reads the latest value without needing
107
+ // it in the dependency array.
108
+ const webViewLivenessProviderRef = (0, react_1.useRef)(null);
109
+ // Resolve theme + strings from uiConfig
110
+ const ui = (0, resolveUiConfig_1.resolveUiConfig)(uiConfig);
111
+ const { theme, strings } = ui;
112
+ // Get injected dependencies
113
+ const deps = (0, dependencies_1.getSdkDependencies)();
114
+ const { OnnxRuntimeWebView, ZkProofWebView, ZkFaceAuth, faceRecognitionService, useWasmLoader, } = deps;
115
+ // Load WASM for ZK proofs
116
+ const { wasmData } = useWasmLoader();
117
+ // Notify parent of stage changes
118
+ (0, react_1.useEffect)(() => {
119
+ onStageChange?.(stage);
120
+ }, [stage, onStageChange]);
121
+ // Load models when bridge is ready
122
+ (0, react_1.useEffect)(() => {
123
+ if (bridgeReady && faceRecognitionService.isBridgeSet()) {
124
+ setStage("REFERENCE_LOADING");
125
+ faceRecognitionService
126
+ .loadModels()
127
+ .then(() => {
128
+ console.log("[FaceZkVerificationFlow] Models loaded, ready for liveness");
129
+ setStage("LIVENESS");
130
+ })
131
+ .catch((err) => {
132
+ console.error("[FaceZkVerificationFlow] Model loading failed:", err);
133
+ handleError({
134
+ code: "SYSTEM_ERROR",
135
+ message: "Failed to load face recognition models",
136
+ details: { error: String(err) },
137
+ });
138
+ });
139
+ }
140
+ }, [bridgeReady, faceRecognitionService]);
141
+ // Guard: SDK must be initialized before rendering
142
+ if (!FaceZkSdk_1.FaceZkSdk.isInitialized()) {
143
+ return (<react_native_1.View style={{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }}>
144
+ <react_native_1.Text style={{ color: "#f97316", fontSize: 16, textAlign: "center" }}>
145
+ FaceZkSdk is not initialized.{"\n"}Call initializeSdk() from '@jupitermetalabs/face-zk-sdk/react-native' before rendering this component.
146
+ </react_native_1.Text>
147
+ </react_native_1.View>);
148
+ }
149
+ // Initialize ONNX bridge for face recognition
150
+ const handleBridgeReady = (bridge) => {
151
+ console.log("[FaceZkVerificationFlow] ONNX bridge ready");
152
+ faceRecognitionService.setBridge(bridge);
153
+ setBridgeReady(true);
154
+ };
155
+ // Initialize ZK Proof bridge
156
+ const handleZkBridgeReady = (bridge) => {
157
+ console.log("[FaceZkVerificationFlow] ZK bridge ready");
158
+ setZkBridge(bridge);
159
+ };
160
+ // Handle liveness success (image captured)
161
+ const handleLivenessSuccess = async (imageUri, metadata) => {
162
+ // Build a LivenessProvider from the WebView's ONNX anti-spoof score so that
163
+ // verifyOnly / verifyWithProof can record the real result in VerificationOutcome.
164
+ const spoofScore = metadata?.spoofScore ?? 0; // 0 = real, 1 = spoof
165
+ webViewLivenessProviderRef.current = (0, livenessProvider_1.createLivenessProvider)({ spoofScore });
166
+ faceMeshLandmarksRef.current = metadata?.faceMeshLandmarks ?? null;
167
+ console.log("[FaceZkVerificationFlow] Liveness passed, image captured:", imageUri.substring(0, 80) + "...");
168
+ setStage("CAPTURING");
169
+ try {
170
+ // The liveness WebView returns a data:image/jpeg;base64,... URI.
171
+ // Expo ImageManipulator on Android cannot process data URIs – it needs a file:// path.
172
+ let fileUri = imageUri;
173
+ if (imageUri.startsWith("data:")) {
174
+ const base64Data = imageUri.split(",")[1];
175
+ const tempPath = `${FileSystem.cacheDirectory}liveness_capture_${Date.now()}.jpg`;
176
+ await FileSystem.writeAsStringAsync(tempPath, base64Data, {
177
+ encoding: FileSystem.EncodingType.Base64,
178
+ });
179
+ fileUri = tempPath;
180
+ console.log("[FaceZkVerificationFlow] Saved liveness image to:", fileUri);
181
+ }
182
+ // Brief delay for UI feedback
183
+ setTimeout(() => runVerification(fileUri), 500);
184
+ }
185
+ catch (err) {
186
+ console.error("[FaceZkVerificationFlow] Error saving liveness image:", err);
187
+ handleError({
188
+ code: "SYSTEM_ERROR",
189
+ message: "Failed to process liveness image",
190
+ details: { error: String(err) },
191
+ });
192
+ }
193
+ };
194
+ // Handle liveness error
195
+ const handleLivenessError = (message) => {
196
+ console.error("[FaceZkVerificationFlow] Liveness error:", message);
197
+ // Perspective check failures mean the user's angle was wrong — recoverable.
198
+ // Silently retry rather than surfacing a hard error to the caller.
199
+ if (message.includes("Perspective Check Failed")) {
200
+ console.log("[FaceZkVerificationFlow] Perspective check failed — auto-retrying liveness");
201
+ handleRetry();
202
+ return;
203
+ }
204
+ handleError({
205
+ code: "LIVENESS_FAILED",
206
+ message,
207
+ details: { stage: "liveness" },
208
+ });
209
+ };
210
+ // Run verification
211
+ const runVerification = async (imageUri) => {
212
+ setStage("EMBEDDING");
213
+ // The WebView-derived provider carries the actual ONNX spoof score from the
214
+ // liveness session that just completed. The prop-provided livenessProvider
215
+ // is used as a fallback for callers that bypass ZkFaceAuth entirely.
216
+ const effectiveLivenessProvider = webViewLivenessProviderRef.current ?? livenessProvider;
217
+ try {
218
+ let result;
219
+ if (mode === "verify-only") {
220
+ console.log("[FaceZkVerificationFlow] Running verify-only");
221
+ setStage("MATCHING");
222
+ result = await (0, verification_core_1.verifyOnly)(reference, imageUri, sdkConfig, embeddingProvider, { livenessProvider: effectiveLivenessProvider, ...verificationOptions });
223
+ }
224
+ else {
225
+ console.log("[FaceZkVerificationFlow] Running verify-with-proof");
226
+ setStage("MATCHING");
227
+ // Dynamically build a ZK-enabled config from the bridge.
228
+ // This lets the host app omit `zk.engine` in sdkConfig — the flow
229
+ // creates the engine automatically from the mounted ZkProofWebView.
230
+ let zkFaceZkRuntimeConfig = sdkConfig;
231
+ if (zkBridge && zkBridge.status === "ready") {
232
+ const engine = (0, zkProofEngine_webview_1.createZkProofEngineWebView)(zkBridge);
233
+ zkFaceZkRuntimeConfig = {
234
+ ...sdkConfig,
235
+ zk: {
236
+ enabled: true,
237
+ requiredForSuccess: sdkConfig.zk?.requiredForSuccess ?? false,
238
+ engine,
239
+ },
240
+ };
241
+ console.log("[FaceZkVerificationFlow] ZK engine created from bridge");
242
+ }
243
+ else {
244
+ console.warn("[FaceZkVerificationFlow] ZK bridge not ready, will fall back to verify-only");
245
+ }
246
+ result = await (0, verification_core_1.verifyWithProof)(reference, imageUri, zkFaceZkRuntimeConfig, embeddingProvider, { livenessProvider: effectiveLivenessProvider, ...verificationOptions });
247
+ // Update stage for ZK proof generation
248
+ if (result.zkProof) {
249
+ setStage("ZK_PROOF");
250
+ }
251
+ }
252
+ console.log("[FaceZkVerificationFlow] Verification complete:", {
253
+ success: result.success,
254
+ score: result.score,
255
+ hasZkProof: !!result.zkProof,
256
+ });
257
+ // Merge FaceMesh landmarks into live capture result if available
258
+ if (faceMeshLandmarksRef.current && result.live) {
259
+ result = { ...result, live: { ...result.live, faceMeshLandmarks: faceMeshLandmarksRef.current } };
260
+ }
261
+ setOutcome(result);
262
+ setStage("DONE");
263
+ onComplete(result);
264
+ }
265
+ catch (err) {
266
+ console.error("[FaceZkVerificationFlow] Verification failed:", err);
267
+ handleError({
268
+ code: "SYSTEM_ERROR",
269
+ message: err instanceof Error ? err.message : "Verification failed",
270
+ details: { error: String(err) },
271
+ });
272
+ }
273
+ };
274
+ const handleError = (error) => {
275
+ const outcome = {
276
+ success: false,
277
+ score: 0,
278
+ error,
279
+ };
280
+ setOutcome(outcome);
281
+ setStage("DONE");
282
+ onComplete(outcome);
283
+ };
284
+ const handleRetry = () => {
285
+ setStage("LIVENESS");
286
+ setOutcome(null);
287
+ };
288
+ const handleCancel = () => {
289
+ onCancel?.();
290
+ };
291
+ const getStageMessage = () => {
292
+ switch (stage) {
293
+ case "IDLE": return strings.loadingInitializing;
294
+ case "REFERENCE_LOADING": return strings.loadingModels;
295
+ case "CAPTURING": return strings.loadingCapturing;
296
+ case "EMBEDDING": return strings.loadingEmbedding;
297
+ case "MATCHING": return strings.loadingMatching;
298
+ case "ZK_PROOF": return strings.loadingZkProof;
299
+ default: return "";
300
+ }
301
+ };
302
+ const isLoadingStage = stage === "IDLE" ||
303
+ stage === "REFERENCE_LOADING" ||
304
+ stage === "CAPTURING" ||
305
+ stage === "EMBEDDING" ||
306
+ stage === "MATCHING" ||
307
+ stage === "ZK_PROOF";
308
+ const stageMessage = getStageMessage();
309
+ const content = (<react_native_1.SafeAreaView style={[styles.container, { backgroundColor: theme.colors.background }]}>
310
+ <react_native_1.StatusBar barStyle="light-content"/>
311
+
312
+ {/* Hidden ONNX Runtime WebView for face recognition */}
313
+ <OnnxRuntimeWebView onReady={handleBridgeReady} onError={(err) => {
314
+ console.error("[FaceZkVerificationFlow] Bridge error:", err);
315
+ handleError({
316
+ code: "SYSTEM_ERROR",
317
+ message: "Face recognition initialization failed",
318
+ details: { error: err },
319
+ });
320
+ }}/>
321
+
322
+ {/* Hidden ZK Proof WebView (if ZK is enabled) */}
323
+ {mode === "verify-with-proof" && wasmData && (<ZkProofWebView onReady={handleZkBridgeReady} onError={(err) => {
324
+ console.error("[FaceZkVerificationFlow] ZK bridge error:", err);
325
+ }} wasmData={wasmData}/>)}
326
+
327
+ {/* Loading / Processing States */}
328
+ {isLoadingStage && (ui.renderLoading ? ui.renderLoading(stage, stageMessage) : (<react_native_1.View style={[styles.loadingContainer, { backgroundColor: theme.colors.background }]}>
329
+ <react_native_1.ActivityIndicator size="large" color={theme.colors.primary}/>
330
+ <react_native_1.Text style={[styles.loadingText, { color: theme.colors.text }]}>{stageMessage}</react_native_1.Text>
331
+ </react_native_1.View>))}
332
+
333
+ {/* Liveness State */}
334
+ {stage === "LIVENESS" && (<react_native_1.View style={styles.livenessContainer}>
335
+ <ZkFaceAuth onSuccess={handleLivenessSuccess} onError={handleLivenessError} manualTargetPose={referencePose} renderOverlay={ui.renderOverlay ?? renderOverlay} headless={false}/>
336
+ <react_native_1.TouchableOpacity style={[styles.cancelButton, {
337
+ backgroundColor: theme.colors.surface,
338
+ borderRadius: theme.borderRadius,
339
+ }]} onPress={handleCancel}>
340
+ <react_native_1.Text style={[styles.cancelButtonText, { color: theme.colors.text }]}>
341
+ {strings.cancelButton}
342
+ </react_native_1.Text>
343
+ </react_native_1.TouchableOpacity>
344
+ </react_native_1.View>)}
345
+
346
+ {/* Done – Success */}
347
+ {stage === "DONE" && outcome?.success && (ui.renderSuccess ? ui.renderSuccess(outcome) : (<react_native_1.View style={[styles.resultContainer, { backgroundColor: theme.colors.background }]}>
348
+ <react_native_1.Text style={[styles.successIcon, { color: theme.colors.primary }]}>✓</react_native_1.Text>
349
+ <react_native_1.Text style={[styles.successTitle, { color: theme.colors.text }]}>
350
+ {strings.verificationSuccessTitle}
351
+ </react_native_1.Text>
352
+ <react_native_1.Text style={[styles.successScore, { color: theme.colors.primary }]}>
353
+ {(0, resolveUiConfig_1.interpolate)(strings.verificationSuccessSubtitle, { score: outcome.score.toFixed(1) })}
354
+ </react_native_1.Text>
355
+ {outcome.zkProof && (<react_native_1.Text style={[styles.zkHash, { color: theme.colors.textMuted }]}>
356
+ ZK Hash: {outcome.zkProof.hash.substring(0, 16)}...
357
+ </react_native_1.Text>)}
358
+ </react_native_1.View>))}
359
+
360
+ {/* Done – Error */}
361
+ {stage === "DONE" && outcome && !outcome.success && (ui.renderError ? ui.renderError(outcome.error ?? { code: "SYSTEM_ERROR", message: "Unknown error" }, { onRetry: handleRetry, onCancel: handleCancel }) : (<react_native_1.View style={[styles.resultContainer, { backgroundColor: theme.colors.background }]}>
362
+ <react_native_1.Text style={[styles.errorIcon, { color: theme.colors.error }]}>✕</react_native_1.Text>
363
+ <react_native_1.Text style={[styles.errorTitle, { color: theme.colors.text }]}>
364
+ {strings.verificationErrorTitle}
365
+ </react_native_1.Text>
366
+ <react_native_1.Text style={[styles.errorText, { color: theme.colors.textMuted }]}>
367
+ {outcome.error?.message || "Unknown error"}
368
+ </react_native_1.Text>
369
+ <react_native_1.TouchableOpacity style={[styles.retryButton, {
370
+ backgroundColor: theme.colors.primary,
371
+ borderRadius: theme.borderRadius,
372
+ }]} onPress={handleRetry}>
373
+ <react_native_1.Text style={[styles.retryButtonText, { color: theme.colors.text }]}>
374
+ {strings.retryButton}
375
+ </react_native_1.Text>
376
+ </react_native_1.TouchableOpacity>
377
+ </react_native_1.View>))}
378
+ </react_native_1.SafeAreaView>);
379
+ if (modal) {
380
+ return (<react_native_1.Modal visible={visible} animationType="slide" presentationStyle="fullScreen" onRequestClose={handleCancel}>
381
+ {content}
382
+ </react_native_1.Modal>);
383
+ }
384
+ return content;
385
+ };
386
+ exports.FaceZkVerificationFlow = FaceZkVerificationFlow;
387
+ const styles = react_native_1.StyleSheet.create({
388
+ container: {
389
+ flex: 1,
390
+ backgroundColor: "#000",
391
+ },
392
+ loadingContainer: {
393
+ flex: 1,
394
+ justifyContent: "center",
395
+ alignItems: "center",
396
+ backgroundColor: "#000",
397
+ },
398
+ loadingText: {
399
+ marginTop: 16,
400
+ color: "#fff",
401
+ fontSize: 16,
402
+ },
403
+ livenessContainer: {
404
+ flex: 1,
405
+ },
406
+ resultContainer: {
407
+ flex: 1,
408
+ justifyContent: "center",
409
+ alignItems: "center",
410
+ backgroundColor: "#000",
411
+ padding: 24,
412
+ },
413
+ successIcon: {
414
+ fontSize: 72,
415
+ color: "#4CAF50",
416
+ marginBottom: 24,
417
+ },
418
+ successTitle: {
419
+ fontSize: 28,
420
+ fontWeight: "bold",
421
+ color: "#fff",
422
+ marginBottom: 12,
423
+ },
424
+ successScore: {
425
+ fontSize: 20,
426
+ color: "#4CAF50",
427
+ marginBottom: 8,
428
+ },
429
+ zkHash: {
430
+ fontSize: 14,
431
+ color: "#888",
432
+ fontFamily: "monospace",
433
+ },
434
+ errorIcon: {
435
+ fontSize: 72,
436
+ color: "#F44336",
437
+ marginBottom: 24,
438
+ },
439
+ errorTitle: {
440
+ fontSize: 24,
441
+ fontWeight: "bold",
442
+ color: "#fff",
443
+ marginBottom: 12,
444
+ },
445
+ errorText: {
446
+ fontSize: 16,
447
+ color: "#aaa",
448
+ textAlign: "center",
449
+ marginBottom: 32,
450
+ },
451
+ retryButton: {
452
+ backgroundColor: "#4CAF50",
453
+ paddingHorizontal: 32,
454
+ paddingVertical: 12,
455
+ borderRadius: 8,
456
+ },
457
+ retryButtonText: {
458
+ color: "#fff",
459
+ fontSize: 16,
460
+ fontWeight: "600",
461
+ },
462
+ cancelButton: {
463
+ position: "absolute",
464
+ bottom: 40,
465
+ left: 24,
466
+ right: 24,
467
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
468
+ paddingVertical: 16,
469
+ borderRadius: 8,
470
+ alignItems: "center",
471
+ },
472
+ cancelButtonText: {
473
+ color: "#fff",
474
+ fontSize: 16,
475
+ fontWeight: "600",
476
+ },
477
+ });
@@ -0,0 +1,72 @@
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
+ /**
17
+ * Reference Enrollment Flow Component
18
+ *
19
+ * A pre-built React Native UI component for enrolling a reference template.
20
+ * This component handles:
21
+ * - Camera capture with pose guidance
22
+ * - Face detection and embedding extraction
23
+ * - Reference template creation
24
+ * - Optional persistence via storage adapter
25
+ */
26
+ import React from "react";
27
+ import type { ReferenceTemplate, FaceZkRuntimeConfig, UiConfig, SdkError, EnrollmentOptions } from "../../core/types";
28
+ import type { FaceEmbeddingProvider } from "../../core/enrollment-core";
29
+ /**
30
+ * Props for ReferenceEnrollmentFlow component
31
+ */
32
+ export interface ReferenceEnrollmentFlowProps {
33
+ /** SDK configuration */
34
+ sdkConfig: FaceZkRuntimeConfig;
35
+ /** Face embedding provider */
36
+ embeddingProvider: FaceEmbeddingProvider;
37
+ /** Enrollment options */
38
+ enrollmentOptions?: EnrollmentOptions;
39
+ /** UI customization config */
40
+ uiConfig?: UiConfig;
41
+ /** Called when enrollment completes successfully */
42
+ onComplete: (template: ReferenceTemplate) => void;
43
+ /** Called when user cancels enrollment */
44
+ onCancel?: () => void;
45
+ /** Called on errors */
46
+ onError?: (error: SdkError) => void;
47
+ /** Whether to show the flow as a modal */
48
+ modal?: boolean;
49
+ /** Modal visibility (if modal=true) */
50
+ visible?: boolean;
51
+ }
52
+ /**
53
+ * A drop-in React Native UI component orchestrating face capture and template enrollment.
54
+ *
55
+ * This component guides a user through positioning their face correctly using the `FacePoseGuidanceWebView`, captures an optimal frame, extracts their facial embedding, and saves the resulting `ReferenceTemplate` directly to local storage (if `persist: true` is provided).
56
+ *
57
+ * **Context:** Once a reference is created via this UI, its `referenceId` is the key needed by `FaceZkVerificationFlow` to authenticate the user in the future.
58
+ *
59
+ * @param {ReferenceEnrollmentFlowProps} props - Configuration and dependencies.
60
+ * @returns {React.FC} An encapsulated camera flow for enrollment.
61
+ *
62
+ * @example
63
+ * <ReferenceEnrollmentFlow
64
+ * sdkConfig={config}
65
+ * embeddingProvider={provider}
66
+ * enrollmentOptions={{ persist: true, metadata: { role: "admin" } }}
67
+ * onComplete={(template) => {
68
+ * myBackend.saveRefId(template.referenceId);
69
+ * }}
70
+ * />
71
+ */
72
+ export declare const ReferenceEnrollmentFlow: React.FC<ReferenceEnrollmentFlowProps>;