@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,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>;
|