@jupitermetalabs/face-zk-sdk 0.1.0
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/LICENSE +201 -0
- package/README.md +181 -0
- package/assets/README.md +22 -0
- package/assets/face-guidance/face-logic.js.txt +77 -0
- package/assets/face-guidance/index.html +173 -0
- package/assets/face-guidance/pose-guidance.js.txt +403 -0
- package/assets/liveness/antispoof.js.txt +143 -0
- package/assets/liveness/index.html +451 -0
- package/assets/liveness/liveness.js.txt +1003 -0
- package/assets/mediapipe/face_mesh.js.txt +131 -0
- package/assets/mediapipe/face_mesh_solution_packed_assets.data +0 -0
- package/assets/mediapipe/face_mesh_solution_simd_wasm_bin.wasm +0 -0
- package/assets/mediapipe/face_mesh_solution_wasm_bin.wasm +0 -0
- package/assets/onnx/ort-wasm-simd.wasm +0 -0
- package/assets/onnx/ort-wasm.wasm +0 -0
- package/assets/onnx/ort.min.js.txt +7 -0
- package/assets/wasm/zk_face_wasm_bg.wasm +0 -0
- package/assets/zk-worker.html +472 -0
- package/cli/copy-ort-assets.js +65 -0
- package/cli/setup.js +266 -0
- package/dist/FaceZkSdk.d.ts +69 -0
- package/dist/FaceZkSdk.js +132 -0
- package/dist/assets/onnx/ort-min.d.ts +1 -0
- package/dist/assets/onnx/ort-min.js +8 -0
- package/dist/config/defaults.d.ts +49 -0
- package/dist/config/defaults.js +55 -0
- package/dist/config/types.d.ts +123 -0
- package/dist/config/types.js +16 -0
- package/dist/core/enrollment-core.d.ts +68 -0
- package/dist/core/enrollment-core.js +202 -0
- package/dist/core/matching.d.ts +69 -0
- package/dist/core/matching.js +96 -0
- package/dist/core/types.d.ts +365 -0
- package/dist/core/types.js +34 -0
- package/dist/core/verification-core.d.ts +120 -0
- package/dist/core/verification-core.js +434 -0
- package/dist/core/zk-core.d.ts +69 -0
- package/dist/core/zk-core.js +240 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +39 -0
- package/dist/react-native/adapters/faceEmbeddingProvider.d.ts +38 -0
- package/dist/react-native/adapters/faceEmbeddingProvider.js +41 -0
- package/dist/react-native/adapters/imageDataProvider.d.ts +53 -0
- package/dist/react-native/adapters/imageDataProvider.js +97 -0
- package/dist/react-native/adapters/livenessProvider.d.ts +133 -0
- package/dist/react-native/adapters/livenessProvider.js +144 -0
- package/dist/react-native/adapters/zkProofEngine-webview.d.ts +73 -0
- package/dist/react-native/adapters/zkProofEngine-webview.js +129 -0
- package/dist/react-native/components/FacePoseGuidanceWebView.d.ts +30 -0
- package/dist/react-native/components/FacePoseGuidanceWebView.js +474 -0
- package/dist/react-native/components/LivenessWebView.d.ts +39 -0
- package/dist/react-native/components/LivenessWebView.js +348 -0
- package/dist/react-native/components/OnnxRuntimeWebView.d.ts +54 -0
- package/dist/react-native/components/OnnxRuntimeWebView.js +394 -0
- package/dist/react-native/components/ZkProofWebView.d.ts +59 -0
- package/dist/react-native/components/ZkProofWebView.js +259 -0
- package/dist/react-native/dependencies.d.ts +144 -0
- package/dist/react-native/dependencies.js +123 -0
- package/dist/react-native/hooks/useOnnxLoader.d.ts +38 -0
- package/dist/react-native/hooks/useOnnxLoader.js +81 -0
- package/dist/react-native/hooks/useWasmLoader.d.ts +30 -0
- package/dist/react-native/hooks/useWasmLoader.js +122 -0
- package/dist/react-native/index.d.ts +59 -0
- package/dist/react-native/index.js +96 -0
- package/dist/react-native/services/FaceRecognition.d.ts +70 -0
- package/dist/react-native/services/FaceRecognition.js +517 -0
- package/dist/react-native/ui/FaceZkVerificationFlow.d.ts +97 -0
- package/dist/react-native/ui/FaceZkVerificationFlow.js +433 -0
- package/dist/react-native/ui/ReferenceEnrollmentFlow.d.ts +72 -0
- package/dist/react-native/ui/ReferenceEnrollmentFlow.js +321 -0
- package/dist/react-native/utils/faceAlignment.d.ts +37 -0
- package/dist/react-native/utils/faceAlignment.js +182 -0
- package/dist/react-native/utils/modelInitialisationChecks.d.ts +36 -0
- package/dist/react-native/utils/modelInitialisationChecks.js +92 -0
- package/dist/react-native/utils/resolveModelUri.d.ts +55 -0
- package/dist/react-native/utils/resolveModelUri.js +172 -0
- package/dist/react-native/utils/resolveUiConfig.d.ts +41 -0
- package/dist/react-native/utils/resolveUiConfig.js +76 -0
- package/dist/storage/defaultStorageAdapter.d.ts +44 -0
- package/dist/storage/defaultStorageAdapter.js +299 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/face-zk.config.example.js +88 -0
- package/package.json +76 -0
|
@@ -0,0 +1,55 @@
|
|
|
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 type { ModelSource } from "../../config/types";
|
|
17
|
+
/** Directory where URL-downloaded models are stored persistently. */
|
|
18
|
+
export declare const MODEL_STORE_DIR: string;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a ModelSource to a local file URI.
|
|
21
|
+
*
|
|
22
|
+
* @param source The model source to resolve.
|
|
23
|
+
* @param onProgress Optional callback receiving download fraction (0–1).
|
|
24
|
+
* Only fires for `url` sources during active download;
|
|
25
|
+
* silent on cache hits.
|
|
26
|
+
* @param allowedDomains Optional hostname allowlist. When provided and non-empty,
|
|
27
|
+
* any `url` source whose hostname is not in this list is
|
|
28
|
+
* rejected before any network request is made.
|
|
29
|
+
* Pass `FaceZkConfig.allowedDomains` here in production.
|
|
30
|
+
* @throws if the source has none of: module, url, localUri
|
|
31
|
+
* @throws if the download fails (url path)
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveModelUri(source: ModelSource, onProgress?: (fraction: number) => void, allowedDomains?: string[]): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Derive the local store path for a given URL.
|
|
36
|
+
*
|
|
37
|
+
* Guards against path traversal in two passes:
|
|
38
|
+
* 1. URL-decode the last path segment so encoded separators like `%2F` or
|
|
39
|
+
* `%2E%2E` are normalised before any checks run.
|
|
40
|
+
* 2. Re-extract the basename from the decoded string (a decoded `%2F`
|
|
41
|
+
* becomes `/`, so a second `.split("/")` is needed).
|
|
42
|
+
* 3. Reject the reserved names `.` and `..` outright.
|
|
43
|
+
* 4. Allowlist `[A-Za-z0-9._-]` — the only characters that can appear in a
|
|
44
|
+
* legitimate model filename. Anything outside this set throws so the
|
|
45
|
+
* caller can surface the problem rather than silently storing a bad file.
|
|
46
|
+
*
|
|
47
|
+
* @throws if the derived filename is empty, a traversal component, or contains
|
|
48
|
+
* characters outside the safe allowlist.
|
|
49
|
+
*/
|
|
50
|
+
export declare function deriveStorePath(url: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Clear all URL-downloaded model files from the persistent store.
|
|
53
|
+
* Useful for forcing a re-download after a model version update.
|
|
54
|
+
*/
|
|
55
|
+
export declare function clearModelCache(): Promise<void>;
|
|
@@ -0,0 +1,172 @@
|
|
|
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 SDK – Model URI Resolution
|
|
18
|
+
*
|
|
19
|
+
* Resolves a `ModelSource` to a local file URI that can be passed to
|
|
20
|
+
* FileSystem.readAsStringAsync() or similar APIs.
|
|
21
|
+
*
|
|
22
|
+
* Resolution order:
|
|
23
|
+
* 1. localUri – returned as-is (already resolved)
|
|
24
|
+
* 2. module – resolved via expo-asset (Metro-bundled)
|
|
25
|
+
* 3. url – downloaded to documentDirectory on first use, path returned
|
|
26
|
+
*
|
|
27
|
+
* Models are stored in documentDirectory (not cacheDirectory) so they
|
|
28
|
+
* survive cache clears and never need re-downloading.
|
|
29
|
+
*/
|
|
30
|
+
import { Asset } from "expo-asset";
|
|
31
|
+
import * as FileSystem from "expo-file-system/legacy";
|
|
32
|
+
/** Directory where URL-downloaded models are stored persistently. */
|
|
33
|
+
export const MODEL_STORE_DIR = `${FileSystem.documentDirectory}face-zk-models/`;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a ModelSource to a local file URI.
|
|
36
|
+
*
|
|
37
|
+
* @param source The model source to resolve.
|
|
38
|
+
* @param onProgress Optional callback receiving download fraction (0–1).
|
|
39
|
+
* Only fires for `url` sources during active download;
|
|
40
|
+
* silent on cache hits.
|
|
41
|
+
* @param allowedDomains Optional hostname allowlist. When provided and non-empty,
|
|
42
|
+
* any `url` source whose hostname is not in this list is
|
|
43
|
+
* rejected before any network request is made.
|
|
44
|
+
* Pass `FaceZkConfig.allowedDomains` here in production.
|
|
45
|
+
* @throws if the source has none of: module, url, localUri
|
|
46
|
+
* @throws if the download fails (url path)
|
|
47
|
+
*/
|
|
48
|
+
export async function resolveModelUri(source, onProgress, allowedDomains) {
|
|
49
|
+
// ── 1. Already a local URI ──────────────────────────────────────────────
|
|
50
|
+
if (source.localUri) {
|
|
51
|
+
return source.localUri;
|
|
52
|
+
}
|
|
53
|
+
// ── 2. Metro-bundled module ─────────────────────────────────────────────
|
|
54
|
+
if (source.module != null) {
|
|
55
|
+
const asset = Asset.fromModule(source.module);
|
|
56
|
+
await asset.downloadAsync();
|
|
57
|
+
const uri = asset.localUri ?? asset.uri;
|
|
58
|
+
if (!uri) {
|
|
59
|
+
throw new Error("[FaceZkSdk] Failed to resolve bundled asset URI.");
|
|
60
|
+
}
|
|
61
|
+
return uri;
|
|
62
|
+
}
|
|
63
|
+
// ── 3. Remote URL – download and store persistently ─────────────────────
|
|
64
|
+
if (source.url) {
|
|
65
|
+
return downloadAndStore(source.url, onProgress, allowedDomains);
|
|
66
|
+
}
|
|
67
|
+
throw new Error("[FaceZkSdk] ModelSource must have at least one of: module, url, localUri.");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Derive the local store path for a given URL.
|
|
71
|
+
*
|
|
72
|
+
* Guards against path traversal in two passes:
|
|
73
|
+
* 1. URL-decode the last path segment so encoded separators like `%2F` or
|
|
74
|
+
* `%2E%2E` are normalised before any checks run.
|
|
75
|
+
* 2. Re-extract the basename from the decoded string (a decoded `%2F`
|
|
76
|
+
* becomes `/`, so a second `.split("/")` is needed).
|
|
77
|
+
* 3. Reject the reserved names `.` and `..` outright.
|
|
78
|
+
* 4. Allowlist `[A-Za-z0-9._-]` — the only characters that can appear in a
|
|
79
|
+
* legitimate model filename. Anything outside this set throws so the
|
|
80
|
+
* caller can surface the problem rather than silently storing a bad file.
|
|
81
|
+
*
|
|
82
|
+
* @throws if the derived filename is empty, a traversal component, or contains
|
|
83
|
+
* characters outside the safe allowlist.
|
|
84
|
+
*/
|
|
85
|
+
export function deriveStorePath(url) {
|
|
86
|
+
// Step 1 – take the last slash-delimited segment and drop any query string
|
|
87
|
+
const raw = url.split("/").pop()?.split("?")[0] ?? "";
|
|
88
|
+
// Step 2 – URL-decode so encoded traversal sequences are normalised
|
|
89
|
+
let decoded;
|
|
90
|
+
try {
|
|
91
|
+
decoded = decodeURIComponent(raw);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
decoded = raw;
|
|
95
|
+
}
|
|
96
|
+
// Step 3 – re-extract basename in case decoding introduced new separators
|
|
97
|
+
const basename = decoded.replace(/\\/g, "/").split("/").pop() ?? "";
|
|
98
|
+
// Step 4 – reject empty names and dot-traversal components
|
|
99
|
+
if (!basename || basename === "." || basename === "..") {
|
|
100
|
+
throw new Error(`[FaceZkSdk] Unsafe or empty model filename derived from URL: "${url}"`);
|
|
101
|
+
}
|
|
102
|
+
// Step 5 – allowlist: only alphanumeric, dot, hyphen, underscore
|
|
103
|
+
if (!/^[A-Za-z0-9._-]+$/.test(basename)) {
|
|
104
|
+
throw new Error(`[FaceZkSdk] Model filename contains unsafe characters: "${basename}"`);
|
|
105
|
+
}
|
|
106
|
+
return `${MODEL_STORE_DIR}${basename}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Download a remote model URL to documentDirectory.
|
|
110
|
+
* Skips download if the file already exists (store hit).
|
|
111
|
+
*
|
|
112
|
+
* Security layers (in order):
|
|
113
|
+
* 1. HTTPS-only — rejects http://, file://, data: etc.
|
|
114
|
+
* 2. Domain allowlist — rejects hostnames not in allowedDomains (if provided).
|
|
115
|
+
* 3. Path containment — asserts the derived local path stays inside MODEL_STORE_DIR.
|
|
116
|
+
*/
|
|
117
|
+
async function downloadAndStore(url, onProgress, allowedDomains) {
|
|
118
|
+
// ── Layer 1: HTTPS-only ────────────────────────────────────────────────
|
|
119
|
+
if (!url.startsWith("https://")) {
|
|
120
|
+
throw new Error(`[FaceZkSdk] Only HTTPS model URLs are permitted. Received: "${url.split("?")[0]}"`);
|
|
121
|
+
}
|
|
122
|
+
// ── Layer 2: Domain allowlist ──────────────────────────────────────────
|
|
123
|
+
if (allowedDomains && allowedDomains.length > 0) {
|
|
124
|
+
const hostnameMatch = url.match(/^https:\/\/([^/?#]+)/);
|
|
125
|
+
const hostname = hostnameMatch ? hostnameMatch[1] : null;
|
|
126
|
+
if (!hostname || !allowedDomains.includes(hostname)) {
|
|
127
|
+
throw new Error(`[FaceZkSdk] Model download blocked: hostname "${hostname ?? "unknown"}" is not in allowedDomains. ` +
|
|
128
|
+
`Allowed: [${allowedDomains.join(", ")}]`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const localPath = deriveStorePath(url);
|
|
132
|
+
// ── Layer 3: Path containment ──────────────────────────────────────────
|
|
133
|
+
if (!localPath.startsWith(MODEL_STORE_DIR)) {
|
|
134
|
+
throw new Error(`[FaceZkSdk] Security: derived path escapes model store. This is a bug — please report it.`);
|
|
135
|
+
}
|
|
136
|
+
// Ensure store directory exists
|
|
137
|
+
const dirInfo = await FileSystem.getInfoAsync(MODEL_STORE_DIR);
|
|
138
|
+
if (!dirInfo.exists) {
|
|
139
|
+
await FileSystem.makeDirectoryAsync(MODEL_STORE_DIR, {
|
|
140
|
+
intermediates: true,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Return stored file if already present
|
|
144
|
+
const fileInfo = await FileSystem.getInfoAsync(localPath);
|
|
145
|
+
if (fileInfo.exists) {
|
|
146
|
+
return localPath;
|
|
147
|
+
}
|
|
148
|
+
// Download with optional progress reporting
|
|
149
|
+
const downloadResumable = FileSystem.createDownloadResumable(url, localPath, {}, onProgress
|
|
150
|
+
? (progress) => {
|
|
151
|
+
if (progress.totalBytesExpectedToWrite > 0) {
|
|
152
|
+
onProgress(progress.totalBytesWritten / progress.totalBytesExpectedToWrite);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
: undefined);
|
|
156
|
+
const result = await downloadResumable.downloadAsync();
|
|
157
|
+
if (!result || result.status < 200 || result.status >= 300) {
|
|
158
|
+
await FileSystem.deleteAsync(localPath, { idempotent: true });
|
|
159
|
+
throw new Error(`[FaceZkSdk] Failed to download model from ${url} (HTTP ${result?.status ?? "unknown"}).`);
|
|
160
|
+
}
|
|
161
|
+
return localPath;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Clear all URL-downloaded model files from the persistent store.
|
|
165
|
+
* Useful for forcing a re-download after a model version update.
|
|
166
|
+
*/
|
|
167
|
+
export async function clearModelCache() {
|
|
168
|
+
const info = await FileSystem.getInfoAsync(MODEL_STORE_DIR);
|
|
169
|
+
if (info.exists) {
|
|
170
|
+
await FileSystem.deleteAsync(MODEL_STORE_DIR, { idempotent: true });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 SDK – UI Config Resolver
|
|
18
|
+
*
|
|
19
|
+
* Merges user-supplied UiConfig with SDK defaults so flow components
|
|
20
|
+
* always have a complete, resolved theme and string set.
|
|
21
|
+
*/
|
|
22
|
+
import type { UiConfig, FaceZkTheme, FaceZkStrings } from "../../core/types";
|
|
23
|
+
export declare const DEFAULT_THEME: FaceZkTheme;
|
|
24
|
+
export declare const DEFAULT_STRINGS: Required<FaceZkStrings>;
|
|
25
|
+
export interface ResolvedTheme extends FaceZkTheme {
|
|
26
|
+
colors: Required<FaceZkTheme["colors"]>;
|
|
27
|
+
borderRadius: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ResolvedUiConfig {
|
|
30
|
+
theme: ResolvedTheme;
|
|
31
|
+
strings: Required<FaceZkStrings>;
|
|
32
|
+
renderLoading?: UiConfig["renderLoading"];
|
|
33
|
+
renderSuccess?: UiConfig["renderSuccess"];
|
|
34
|
+
renderError?: UiConfig["renderError"];
|
|
35
|
+
renderOverlay?: UiConfig["renderOverlay"];
|
|
36
|
+
}
|
|
37
|
+
export declare function resolveUiConfig(uiConfig?: UiConfig): ResolvedUiConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Replace `{score}` placeholder in a string template.
|
|
40
|
+
*/
|
|
41
|
+
export declare function interpolate(template: string, vars: Record<string, string | number>): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
// Defaults
|
|
18
|
+
// ============================================================================
|
|
19
|
+
export const DEFAULT_THEME = {
|
|
20
|
+
colors: {
|
|
21
|
+
primary: "#4CAF50",
|
|
22
|
+
background: "#000000",
|
|
23
|
+
surface: "rgba(255,255,255,0.1)",
|
|
24
|
+
text: "#ffffff",
|
|
25
|
+
textMuted: "#aaaaaa",
|
|
26
|
+
error: "#F44336",
|
|
27
|
+
},
|
|
28
|
+
borderRadius: 8,
|
|
29
|
+
};
|
|
30
|
+
export const DEFAULT_STRINGS = {
|
|
31
|
+
loadingInitializing: "Initializing...",
|
|
32
|
+
loadingModels: "Loading face recognition models...",
|
|
33
|
+
loadingCapturing: "Capturing image...",
|
|
34
|
+
loadingProcessing: "Processing reference image...",
|
|
35
|
+
loadingEmbedding: "Processing face...",
|
|
36
|
+
loadingMatching: "Matching face...",
|
|
37
|
+
loadingZkProof: "Generating cryptographic proof...",
|
|
38
|
+
verificationSuccessTitle: "Verified!",
|
|
39
|
+
verificationSuccessSubtitle: "Match: {score}%",
|
|
40
|
+
enrollmentSuccessTitle: "Reference Enrolled",
|
|
41
|
+
enrollmentSuccessSubtitle: "Your reference has been successfully enrolled.",
|
|
42
|
+
verificationErrorTitle: "Verification Failed",
|
|
43
|
+
enrollmentErrorTitle: "Enrollment Failed",
|
|
44
|
+
cancelButton: "Cancel",
|
|
45
|
+
retryButton: "Try Again",
|
|
46
|
+
};
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Resolver
|
|
49
|
+
// ============================================================================
|
|
50
|
+
export function resolveUiConfig(uiConfig = {}) {
|
|
51
|
+
const userColors = uiConfig.theme?.colors ?? {};
|
|
52
|
+
const userTheme = uiConfig.theme ?? {};
|
|
53
|
+
return {
|
|
54
|
+
theme: {
|
|
55
|
+
colors: {
|
|
56
|
+
...DEFAULT_THEME.colors,
|
|
57
|
+
...userColors,
|
|
58
|
+
},
|
|
59
|
+
borderRadius: userTheme.borderRadius ?? DEFAULT_THEME.borderRadius,
|
|
60
|
+
},
|
|
61
|
+
strings: {
|
|
62
|
+
...DEFAULT_STRINGS,
|
|
63
|
+
...(uiConfig.strings ?? {}),
|
|
64
|
+
},
|
|
65
|
+
renderLoading: uiConfig.renderLoading,
|
|
66
|
+
renderSuccess: uiConfig.renderSuccess,
|
|
67
|
+
renderError: uiConfig.renderError,
|
|
68
|
+
renderOverlay: uiConfig.renderOverlay,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Replace `{score}` placeholder in a string template.
|
|
73
|
+
*/
|
|
74
|
+
export function interpolate(template, vars) {
|
|
75
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => key in vars ? String(vars[key]) : `{${key}}`);
|
|
76
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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 type { StorageAdapter, ReferenceId } from "../core/types";
|
|
17
|
+
/**
|
|
18
|
+
* Create a default storage adapter using AsyncStorage and FileSystem.
|
|
19
|
+
*
|
|
20
|
+
* This implementation:
|
|
21
|
+
* - Stores reference templates in AsyncStorage (small, fast access)
|
|
22
|
+
* - Stores ZK proofs in FileSystem (large, persistent)
|
|
23
|
+
* - Maintains indices for listing all stored items
|
|
24
|
+
*
|
|
25
|
+
* @returns StorageAdapter implementation
|
|
26
|
+
*/
|
|
27
|
+
export declare function createDefaultStorageAdapter(): StorageAdapter;
|
|
28
|
+
/**
|
|
29
|
+
* Get all reference IDs from the index
|
|
30
|
+
*/
|
|
31
|
+
export declare function getAllReferenceIds(): Promise<ReferenceId[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Get all proof IDs from the index
|
|
34
|
+
*/
|
|
35
|
+
export declare function getAllProofIds(): Promise<string[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Clear all stored references and proofs (for testing/debugging)
|
|
38
|
+
*/
|
|
39
|
+
export declare function clearAllStorage(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Singleton instance for convenience.
|
|
42
|
+
* Use this if you don't need custom configuration.
|
|
43
|
+
*/
|
|
44
|
+
export declare const defaultStorageAdapter: StorageAdapter;
|
|
@@ -0,0 +1,299 @@
|
|
|
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
|
+
* Default Storage Adapter for React Native
|
|
18
|
+
*
|
|
19
|
+
* Implements the StorageAdapter interface using AsyncStorage for metadata
|
|
20
|
+
* and FileSystem for larger data (embeddings, proofs).
|
|
21
|
+
*
|
|
22
|
+
* Storage strategy:
|
|
23
|
+
* - Reference templates: AsyncStorage (embeddings are ~2KB, well under 2MB limit)
|
|
24
|
+
* - ZK proofs: FileSystem (proofs can be large)
|
|
25
|
+
*/
|
|
26
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
27
|
+
import * as FileSystem from "expo-file-system/legacy";
|
|
28
|
+
/**
|
|
29
|
+
* Storage keys
|
|
30
|
+
*/
|
|
31
|
+
const STORAGE_KEYS = {
|
|
32
|
+
REFERENCE_PREFIX: "@face-zk-sdk/reference/",
|
|
33
|
+
PROOF_PREFIX: "@face-zk-sdk/proof/",
|
|
34
|
+
REFERENCE_INDEX: "@face-zk-sdk/reference-index",
|
|
35
|
+
PROOF_INDEX: "@face-zk-sdk/proof-index",
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* File paths for FileSystem storage
|
|
39
|
+
*/
|
|
40
|
+
const getFilePaths = () => {
|
|
41
|
+
const baseDir = `${FileSystem.documentDirectory}face-zk-sdk/`;
|
|
42
|
+
return {
|
|
43
|
+
baseDir,
|
|
44
|
+
proofsDir: `${baseDir}proofs/`,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Ensure storage directories exist
|
|
49
|
+
*/
|
|
50
|
+
async function ensureDirectoriesExist() {
|
|
51
|
+
const { baseDir, proofsDir } = getFilePaths();
|
|
52
|
+
try {
|
|
53
|
+
// Check if base directory exists
|
|
54
|
+
const baseDirInfo = await FileSystem.getInfoAsync(baseDir);
|
|
55
|
+
if (!baseDirInfo.exists) {
|
|
56
|
+
await FileSystem.makeDirectoryAsync(baseDir, { intermediates: true });
|
|
57
|
+
}
|
|
58
|
+
// Check if proofs directory exists
|
|
59
|
+
const proofsDirInfo = await FileSystem.getInfoAsync(proofsDir);
|
|
60
|
+
if (!proofsDirInfo.exists) {
|
|
61
|
+
await FileSystem.makeDirectoryAsync(proofsDir, { intermediates: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error("[DefaultStorageAdapter] Error creating directories:", error);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a default storage adapter using AsyncStorage and FileSystem.
|
|
71
|
+
*
|
|
72
|
+
* This implementation:
|
|
73
|
+
* - Stores reference templates in AsyncStorage (small, fast access)
|
|
74
|
+
* - Stores ZK proofs in FileSystem (large, persistent)
|
|
75
|
+
* - Maintains indices for listing all stored items
|
|
76
|
+
*
|
|
77
|
+
* @returns StorageAdapter implementation
|
|
78
|
+
*/
|
|
79
|
+
export function createDefaultStorageAdapter() {
|
|
80
|
+
return {
|
|
81
|
+
// ========================================================================
|
|
82
|
+
// Reference Template Storage
|
|
83
|
+
// ========================================================================
|
|
84
|
+
async saveReference(template) {
|
|
85
|
+
const key = `${STORAGE_KEYS.REFERENCE_PREFIX}${template.referenceId}`;
|
|
86
|
+
try {
|
|
87
|
+
// Serialize template to JSON
|
|
88
|
+
const json = JSON.stringify(template);
|
|
89
|
+
// Store in AsyncStorage
|
|
90
|
+
await AsyncStorage.setItem(key, json);
|
|
91
|
+
// Update index
|
|
92
|
+
await updateReferenceIndex(template.referenceId, "add");
|
|
93
|
+
console.log(`[DefaultStorageAdapter] Saved reference: ${template.referenceId}`);
|
|
94
|
+
return template.referenceId;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error("[DefaultStorageAdapter] Error saving reference:", error);
|
|
98
|
+
throw new Error(`Failed to save reference: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
async loadReference(referenceId) {
|
|
102
|
+
const key = `${STORAGE_KEYS.REFERENCE_PREFIX}${referenceId}`;
|
|
103
|
+
try {
|
|
104
|
+
const json = await AsyncStorage.getItem(key);
|
|
105
|
+
if (!json) {
|
|
106
|
+
console.log(`[DefaultStorageAdapter] Reference not found: ${referenceId}`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const template = JSON.parse(json);
|
|
110
|
+
console.log(`[DefaultStorageAdapter] Loaded reference: ${referenceId}`);
|
|
111
|
+
return template;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error("[DefaultStorageAdapter] Error loading reference:", error);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async deleteReference(referenceId) {
|
|
119
|
+
const key = `${STORAGE_KEYS.REFERENCE_PREFIX}${referenceId}`;
|
|
120
|
+
try {
|
|
121
|
+
await AsyncStorage.removeItem(key);
|
|
122
|
+
// Update index
|
|
123
|
+
await updateReferenceIndex(referenceId, "remove");
|
|
124
|
+
console.log(`[DefaultStorageAdapter] Deleted reference: ${referenceId}`);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.error("[DefaultStorageAdapter] Error deleting reference:", error);
|
|
128
|
+
throw new Error(`Failed to delete reference: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
// ========================================================================
|
|
132
|
+
// ZK Proof Storage (FileSystem)
|
|
133
|
+
// ========================================================================
|
|
134
|
+
async saveProof(record) {
|
|
135
|
+
const proofId = `proof_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
136
|
+
const { proofsDir } = getFilePaths();
|
|
137
|
+
const filePath = `${proofsDir}${proofId}.json`;
|
|
138
|
+
try {
|
|
139
|
+
// Ensure directories exist
|
|
140
|
+
await ensureDirectoriesExist();
|
|
141
|
+
// Serialize proof record to JSON
|
|
142
|
+
const json = JSON.stringify(record);
|
|
143
|
+
// Write to file
|
|
144
|
+
await FileSystem.writeAsStringAsync(filePath, json, {
|
|
145
|
+
encoding: FileSystem.EncodingType.UTF8,
|
|
146
|
+
});
|
|
147
|
+
// Update index
|
|
148
|
+
await updateProofIndex(proofId, "add");
|
|
149
|
+
console.log(`[DefaultStorageAdapter] Saved proof: ${proofId}`);
|
|
150
|
+
return proofId;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error("[DefaultStorageAdapter] Error saving proof:", error);
|
|
154
|
+
throw new Error(`Failed to save proof: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
async loadProof(proofId) {
|
|
158
|
+
const { proofsDir } = getFilePaths();
|
|
159
|
+
const filePath = `${proofsDir}${proofId}.json`;
|
|
160
|
+
try {
|
|
161
|
+
// Check if file exists
|
|
162
|
+
const fileInfo = await FileSystem.getInfoAsync(filePath);
|
|
163
|
+
if (!fileInfo.exists) {
|
|
164
|
+
console.log(`[DefaultStorageAdapter] Proof not found: ${proofId}`);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
// Read file
|
|
168
|
+
const json = await FileSystem.readAsStringAsync(filePath, {
|
|
169
|
+
encoding: FileSystem.EncodingType.UTF8,
|
|
170
|
+
});
|
|
171
|
+
const record = JSON.parse(json);
|
|
172
|
+
console.log(`[DefaultStorageAdapter] Loaded proof: ${proofId}`);
|
|
173
|
+
return record;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error("[DefaultStorageAdapter] Error loading proof:", error);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// Helper Functions for Index Management
|
|
184
|
+
// ============================================================================
|
|
185
|
+
/**
|
|
186
|
+
* Update the reference index (list of all reference IDs)
|
|
187
|
+
*/
|
|
188
|
+
async function updateReferenceIndex(referenceId, action) {
|
|
189
|
+
try {
|
|
190
|
+
const indexJson = await AsyncStorage.getItem(STORAGE_KEYS.REFERENCE_INDEX);
|
|
191
|
+
const index = indexJson ? JSON.parse(indexJson) : [];
|
|
192
|
+
if (action === "add") {
|
|
193
|
+
if (!index.includes(referenceId)) {
|
|
194
|
+
index.push(referenceId);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const idx = index.indexOf(referenceId);
|
|
199
|
+
if (idx !== -1) {
|
|
200
|
+
index.splice(idx, 1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
await AsyncStorage.setItem(STORAGE_KEYS.REFERENCE_INDEX, JSON.stringify(index));
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
console.error("[DefaultStorageAdapter] Error updating reference index:", error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Update the proof index (list of all proof IDs)
|
|
211
|
+
*/
|
|
212
|
+
async function updateProofIndex(proofId, action) {
|
|
213
|
+
try {
|
|
214
|
+
const indexJson = await AsyncStorage.getItem(STORAGE_KEYS.PROOF_INDEX);
|
|
215
|
+
const index = indexJson ? JSON.parse(indexJson) : [];
|
|
216
|
+
if (action === "add") {
|
|
217
|
+
if (!index.includes(proofId)) {
|
|
218
|
+
index.push(proofId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const idx = index.indexOf(proofId);
|
|
223
|
+
if (idx !== -1) {
|
|
224
|
+
index.splice(idx, 1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
await AsyncStorage.setItem(STORAGE_KEYS.PROOF_INDEX, JSON.stringify(index));
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error("[DefaultStorageAdapter] Error updating proof index:", error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get all reference IDs from the index
|
|
235
|
+
*/
|
|
236
|
+
export async function getAllReferenceIds() {
|
|
237
|
+
try {
|
|
238
|
+
const indexJson = await AsyncStorage.getItem(STORAGE_KEYS.REFERENCE_INDEX);
|
|
239
|
+
return indexJson ? JSON.parse(indexJson) : [];
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.error("[DefaultStorageAdapter] Error getting reference index:", error);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get all proof IDs from the index
|
|
248
|
+
*/
|
|
249
|
+
export async function getAllProofIds() {
|
|
250
|
+
try {
|
|
251
|
+
const indexJson = await AsyncStorage.getItem(STORAGE_KEYS.PROOF_INDEX);
|
|
252
|
+
return indexJson ? JSON.parse(indexJson) : [];
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error("[DefaultStorageAdapter] Error getting proof index:", error);
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Clear all stored references and proofs (for testing/debugging)
|
|
261
|
+
*/
|
|
262
|
+
export async function clearAllStorage() {
|
|
263
|
+
try {
|
|
264
|
+
// Get all reference IDs
|
|
265
|
+
const referenceIds = await getAllReferenceIds();
|
|
266
|
+
// Delete all references
|
|
267
|
+
for (const refId of referenceIds) {
|
|
268
|
+
const key = `${STORAGE_KEYS.REFERENCE_PREFIX}${refId}`;
|
|
269
|
+
await AsyncStorage.removeItem(key);
|
|
270
|
+
}
|
|
271
|
+
// Clear reference index
|
|
272
|
+
await AsyncStorage.removeItem(STORAGE_KEYS.REFERENCE_INDEX);
|
|
273
|
+
// Get all proof IDs
|
|
274
|
+
const proofIds = await getAllProofIds();
|
|
275
|
+
// Delete all proof files
|
|
276
|
+
const { proofsDir } = getFilePaths();
|
|
277
|
+
for (const proofId of proofIds) {
|
|
278
|
+
const filePath = `${proofsDir}${proofId}.json`;
|
|
279
|
+
try {
|
|
280
|
+
await FileSystem.deleteAsync(filePath, { idempotent: true });
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.warn(`[DefaultStorageAdapter] Error deleting proof file: ${proofId}`, error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Clear proof index
|
|
287
|
+
await AsyncStorage.removeItem(STORAGE_KEYS.PROOF_INDEX);
|
|
288
|
+
console.log("[DefaultStorageAdapter] All storage cleared");
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
console.error("[DefaultStorageAdapter] Error clearing storage:", error);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Singleton instance for convenience.
|
|
297
|
+
* Use this if you don't need custom configuration.
|
|
298
|
+
*/
|
|
299
|
+
export const defaultStorageAdapter = createDefaultStorageAdapter();
|