@tn3w/openage 1.0.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 +151 -0
- package/dist/openage.esm.js +3383 -0
- package/dist/openage.esm.js.map +1 -0
- package/dist/openage.min.js +2 -0
- package/dist/openage.min.js.map +1 -0
- package/dist/openage.umd.js +3408 -0
- package/dist/openage.umd.js.map +1 -0
- package/package.json +56 -0
- package/src/age-estimator.d.ts +17 -0
- package/src/age-estimator.js +77 -0
- package/src/challenge.d.ts +7 -0
- package/src/challenge.js +493 -0
- package/src/constants.d.ts +24 -0
- package/src/constants.js +35 -0
- package/src/face-tracker.d.ts +39 -0
- package/src/face-tracker.js +132 -0
- package/src/index.d.ts +91 -0
- package/src/index.js +303 -0
- package/src/liveness.d.ts +62 -0
- package/src/liveness.js +220 -0
- package/src/positioning.d.ts +13 -0
- package/src/positioning.js +39 -0
- package/src/transport.d.ts +61 -0
- package/src/transport.js +305 -0
- package/src/ui.d.ts +37 -0
- package/src/ui.js +1048 -0
- package/src/vm-client.d.ts +62 -0
- package/src/vm-client.js +219 -0
- package/src/widget.d.ts +58 -0
- package/src/widget.js +617 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface VMSession {
|
|
2
|
+
wasmJs: string;
|
|
3
|
+
wasmBin: string;
|
|
4
|
+
loaderJs: string;
|
|
5
|
+
challengeVmbc: string;
|
|
6
|
+
rounds: number;
|
|
7
|
+
exports: Record<string, string>;
|
|
8
|
+
models: Record<
|
|
9
|
+
string,
|
|
10
|
+
{ url: string }
|
|
11
|
+
>;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface VMBridge {
|
|
16
|
+
trackFace(): string;
|
|
17
|
+
captureFrame(): string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export declare function initVM(
|
|
21
|
+
session: VMSession
|
|
22
|
+
): Promise<void>;
|
|
23
|
+
|
|
24
|
+
export declare function decryptModel(
|
|
25
|
+
session: VMSession,
|
|
26
|
+
modelId: string
|
|
27
|
+
): Promise<ArrayBuffer>;
|
|
28
|
+
|
|
29
|
+
export declare function setFaceData(
|
|
30
|
+
faceData: unknown
|
|
31
|
+
): void;
|
|
32
|
+
|
|
33
|
+
export declare function setChallengeParams(
|
|
34
|
+
params: unknown
|
|
35
|
+
): void;
|
|
36
|
+
|
|
37
|
+
export declare function registerBridge(
|
|
38
|
+
bridge: VMBridge
|
|
39
|
+
): void;
|
|
40
|
+
|
|
41
|
+
export declare function unregisterBridge(): void;
|
|
42
|
+
|
|
43
|
+
export declare function executeChallenge(): Uint8Array;
|
|
44
|
+
|
|
45
|
+
export declare function toBase64(
|
|
46
|
+
bytes: Uint8Array
|
|
47
|
+
): string;
|
|
48
|
+
|
|
49
|
+
export declare function destroyVM(): void;
|
|
50
|
+
|
|
51
|
+
export declare function isVMLoaded(): boolean;
|
|
52
|
+
|
|
53
|
+
export declare function ensureModels(
|
|
54
|
+
session: VMSession,
|
|
55
|
+
onProgress?: (fraction: number) => void
|
|
56
|
+
): Promise<void>;
|
|
57
|
+
|
|
58
|
+
export declare function getMediaPipeModelBuffer(
|
|
59
|
+
session: VMSession
|
|
60
|
+
): Promise<ArrayBuffer>;
|
|
61
|
+
|
|
62
|
+
export declare function clearModelCache(): void;
|
package/src/vm-client.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
let wasmModule = null;
|
|
2
|
+
let vmSession = null;
|
|
3
|
+
let challengeBundle = null;
|
|
4
|
+
let _faceData = null;
|
|
5
|
+
let _challengeParams = null;
|
|
6
|
+
let _bridge = null;
|
|
7
|
+
|
|
8
|
+
function loadScript(url) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const existing = document.querySelector(`script[src="${url}"]`);
|
|
11
|
+
if (existing) return resolve();
|
|
12
|
+
|
|
13
|
+
const script = document.createElement('script');
|
|
14
|
+
script.src = url;
|
|
15
|
+
script.onload = resolve;
|
|
16
|
+
script.onerror = () => {
|
|
17
|
+
reject(new Error(`Failed to load ${url}`));
|
|
18
|
+
};
|
|
19
|
+
document.head.appendChild(script);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function initVM(session) {
|
|
24
|
+
vmSession = session;
|
|
25
|
+
|
|
26
|
+
await loadScript(session.wasmJs);
|
|
27
|
+
|
|
28
|
+
const loaderModule = await import(session.loaderJs);
|
|
29
|
+
wasmModule = await loaderModule.initModule(session.wasmBin);
|
|
30
|
+
|
|
31
|
+
const initFn = session.exports.vm_init;
|
|
32
|
+
const result = wasmModule[`_${initFn}`]();
|
|
33
|
+
if (result !== 0) throw new Error('VM init failed');
|
|
34
|
+
|
|
35
|
+
const bundleResponse = await fetch(session.challengeVmbc);
|
|
36
|
+
challengeBundle = new Uint8Array(await bundleResponse.arrayBuffer());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function decryptModel(session, modelId) {
|
|
40
|
+
if (!wasmModule || !vmSession) {
|
|
41
|
+
throw new Error('VM not loaded');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const modelInfo = session.models[modelId];
|
|
45
|
+
if (!modelInfo) {
|
|
46
|
+
throw new Error(`Unknown model: ${modelId}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const response = await fetch(modelInfo.url);
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(`Failed to fetch model: ${modelId}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const encrypted = new Uint8Array(await response.arrayBuffer());
|
|
55
|
+
|
|
56
|
+
const decryptFn = session.exports.vm_decrypt_blob;
|
|
57
|
+
const freeFn = session.exports.vm_free;
|
|
58
|
+
const length = encrypted.length;
|
|
59
|
+
|
|
60
|
+
const inputPtr = wasmModule._malloc(length);
|
|
61
|
+
wasmModule.HEAPU8.set(encrypted, inputPtr);
|
|
62
|
+
|
|
63
|
+
const outLenPtr = wasmModule._malloc(4);
|
|
64
|
+
const outPtr = wasmModule[`_${decryptFn}`](inputPtr, length, outLenPtr);
|
|
65
|
+
wasmModule._free(inputPtr);
|
|
66
|
+
|
|
67
|
+
if (!outPtr) {
|
|
68
|
+
wasmModule._free(outLenPtr);
|
|
69
|
+
throw new Error(`Decryption failed: ${modelId}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const outLen = readU32(wasmModule, outLenPtr);
|
|
73
|
+
wasmModule._free(outLenPtr);
|
|
74
|
+
|
|
75
|
+
const result = new Uint8Array(outLen);
|
|
76
|
+
result.set(wasmModule.HEAPU8.subarray(outPtr, outPtr + outLen));
|
|
77
|
+
wasmModule[`_${freeFn}`](outPtr);
|
|
78
|
+
return result.buffer;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function readU32(mod, ptr) {
|
|
82
|
+
return (
|
|
83
|
+
mod.HEAPU8[ptr] |
|
|
84
|
+
(mod.HEAPU8[ptr + 1] << 8) |
|
|
85
|
+
(mod.HEAPU8[ptr + 2] << 16) |
|
|
86
|
+
(mod.HEAPU8[ptr + 3] << 24)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function defineVmGlobal(name, getter) {
|
|
91
|
+
try {
|
|
92
|
+
delete window[name];
|
|
93
|
+
} catch (_) {}
|
|
94
|
+
Object.defineProperty(window, name, {
|
|
95
|
+
get: getter,
|
|
96
|
+
set() {},
|
|
97
|
+
configurable: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function setFaceData(faceData) {
|
|
102
|
+
_faceData = faceData;
|
|
103
|
+
defineVmGlobal('__vmFaceData', () => _faceData);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function setChallengeParams(params) {
|
|
107
|
+
_challengeParams = params;
|
|
108
|
+
defineVmGlobal('__vmChallenge', () => _challengeParams);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function registerBridge(bridge) {
|
|
112
|
+
_bridge = Object.freeze({ ...bridge });
|
|
113
|
+
defineVmGlobal('__vmBridge', () => _bridge);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function unregisterBridge() {
|
|
117
|
+
_bridge = null;
|
|
118
|
+
try {
|
|
119
|
+
delete window.__vmBridge;
|
|
120
|
+
} catch (_) {}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function executeChallenge() {
|
|
124
|
+
if (!wasmModule || !challengeBundle) {
|
|
125
|
+
throw new Error('VM not loaded');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const execFn = vmSession.exports.vm_exec_bytecode;
|
|
129
|
+
const freeFn = vmSession.exports.vm_free;
|
|
130
|
+
const length = challengeBundle.length;
|
|
131
|
+
|
|
132
|
+
const inputPtr = wasmModule._malloc(length);
|
|
133
|
+
wasmModule.HEAPU8.set(challengeBundle, inputPtr);
|
|
134
|
+
|
|
135
|
+
const outLenPtr = wasmModule._malloc(4);
|
|
136
|
+
const outPtr = wasmModule[`_${execFn}`](inputPtr, length, outLenPtr);
|
|
137
|
+
wasmModule._free(inputPtr);
|
|
138
|
+
|
|
139
|
+
if (!outPtr) {
|
|
140
|
+
wasmModule._free(outLenPtr);
|
|
141
|
+
const errFn = vmSession.exports.vm_last_error;
|
|
142
|
+
let message = 'VM execution failed';
|
|
143
|
+
if (errFn) {
|
|
144
|
+
const errPtr = wasmModule[`_${errFn}`]();
|
|
145
|
+
if (errPtr) {
|
|
146
|
+
const detail = wasmModule.UTF8ToString(errPtr);
|
|
147
|
+
if (detail) message = detail;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
throw new Error(message);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const outLen = readU32(wasmModule, outLenPtr);
|
|
154
|
+
wasmModule._free(outLenPtr);
|
|
155
|
+
|
|
156
|
+
const result = new Uint8Array(outLen);
|
|
157
|
+
result.set(wasmModule.HEAPU8.subarray(outPtr, outPtr + outLen));
|
|
158
|
+
wasmModule[`_${freeFn}`](outPtr);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function toBase64(bytes) {
|
|
163
|
+
let binary = '';
|
|
164
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
165
|
+
binary += String.fromCharCode(bytes[i]);
|
|
166
|
+
}
|
|
167
|
+
return btoa(binary);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function destroyVM() {
|
|
171
|
+
if (!wasmModule || !vmSession) return;
|
|
172
|
+
|
|
173
|
+
const destroyFn = vmSession.exports.vm_destroy;
|
|
174
|
+
if (destroyFn) {
|
|
175
|
+
wasmModule[`_${destroyFn}`]();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
wasmModule = null;
|
|
179
|
+
vmSession = null;
|
|
180
|
+
challengeBundle = null;
|
|
181
|
+
_faceData = null;
|
|
182
|
+
_challengeParams = null;
|
|
183
|
+
_bridge = null;
|
|
184
|
+
|
|
185
|
+
for (const name of ['__vmFaceData', '__vmChallenge', '__vmBridge']) {
|
|
186
|
+
try {
|
|
187
|
+
delete window[name];
|
|
188
|
+
} catch (_) {}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function isVMLoaded() {
|
|
193
|
+
return wasmModule !== null && vmSession !== null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const decryptedCache = new Map();
|
|
197
|
+
|
|
198
|
+
async function decryptAndCache(session, modelId) {
|
|
199
|
+
if (decryptedCache.has(modelId)) {
|
|
200
|
+
return decryptedCache.get(modelId);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const buffer = await decryptModel(session, modelId);
|
|
204
|
+
decryptedCache.set(modelId, buffer);
|
|
205
|
+
return buffer;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function ensureModels(session, onProgress) {
|
|
209
|
+
await decryptAndCache(session, 'mediapipe');
|
|
210
|
+
onProgress?.(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export async function getMediaPipeModelBuffer(session) {
|
|
214
|
+
return decryptAndCache(session, 'mediapipe');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function clearModelCache() {
|
|
218
|
+
decryptedCache.clear();
|
|
219
|
+
}
|
package/src/widget.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface WidgetParams {
|
|
2
|
+
sitekey?: string;
|
|
3
|
+
server?: string;
|
|
4
|
+
mode?: "serverless" | "sitekey" | "custom";
|
|
5
|
+
theme?: "light" | "dark" | "auto";
|
|
6
|
+
size?: "compact" | "normal" | "invisible";
|
|
7
|
+
action?: string;
|
|
8
|
+
minAge?: number;
|
|
9
|
+
callback?: (token: string) => void;
|
|
10
|
+
errorCallback?: (error: string | Error) => void;
|
|
11
|
+
expiredCallback?: () => void;
|
|
12
|
+
closeCallback?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare class Widget {
|
|
16
|
+
id: string;
|
|
17
|
+
params: WidgetParams;
|
|
18
|
+
state: string;
|
|
19
|
+
token: string | null;
|
|
20
|
+
onChallenge: ((widget: Widget) => void) | null;
|
|
21
|
+
onStartClick: (() => void) | null;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
container: string | HTMLElement,
|
|
25
|
+
params: WidgetParams
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
startChallenge(): void;
|
|
29
|
+
openPopup(): HTMLVideoElement | null;
|
|
30
|
+
openModal(): HTMLVideoElement | null;
|
|
31
|
+
closePopup(): void;
|
|
32
|
+
showHero(statusText: string): void;
|
|
33
|
+
showReady(): void;
|
|
34
|
+
showCamera(): HTMLVideoElement | null;
|
|
35
|
+
showLiveness(): void;
|
|
36
|
+
setHeroStatus(text: string): void;
|
|
37
|
+
setVideoStatus(text: string): void;
|
|
38
|
+
setInstruction(text: string): void;
|
|
39
|
+
setStatus(text: string): void;
|
|
40
|
+
setProgress(fraction: number): void;
|
|
41
|
+
setTask(taskId: string | null): void;
|
|
42
|
+
showActions(label: string): void;
|
|
43
|
+
hideActions(): void;
|
|
44
|
+
showResult(
|
|
45
|
+
outcome: "pass" | "fail" | "retry",
|
|
46
|
+
message: string
|
|
47
|
+
): void;
|
|
48
|
+
showError(message: string): void;
|
|
49
|
+
clearError(): void;
|
|
50
|
+
setState(state: string): void;
|
|
51
|
+
getToken(): string | null;
|
|
52
|
+
reset(): void;
|
|
53
|
+
destroy(): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export declare function createModalWidget(
|
|
57
|
+
params: WidgetParams
|
|
58
|
+
): Widget;
|