@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,132 @@
|
|
|
1
|
+
import { MEDIAPIPE_CDN, MEDIAPIPE_WASM, MEDIAPIPE_VISION, MEDIAPIPE_MODEL } from './constants.js';
|
|
2
|
+
|
|
3
|
+
let FaceLandmarker = null;
|
|
4
|
+
let landmarker = null;
|
|
5
|
+
let lastTimestampMs = -1;
|
|
6
|
+
let visionModule = null;
|
|
7
|
+
|
|
8
|
+
export async function loadVision() {
|
|
9
|
+
if (visionModule) return visionModule;
|
|
10
|
+
visionModule = await import(MEDIAPIPE_VISION);
|
|
11
|
+
FaceLandmarker = visionModule.FaceLandmarker;
|
|
12
|
+
return visionModule;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function loadModel() {
|
|
16
|
+
const response = await fetch(MEDIAPIPE_MODEL);
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error('Failed to load face landmarker model');
|
|
19
|
+
}
|
|
20
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function initTracker(modelBuffer) {
|
|
24
|
+
const vision = await loadVision();
|
|
25
|
+
const resolver = await vision.FilesetResolver.forVisionTasks(MEDIAPIPE_WASM);
|
|
26
|
+
|
|
27
|
+
if (landmarker) {
|
|
28
|
+
landmarker.close();
|
|
29
|
+
}
|
|
30
|
+
lastTimestampMs = -1;
|
|
31
|
+
|
|
32
|
+
landmarker = await FaceLandmarker.createFromOptions(resolver, {
|
|
33
|
+
baseOptions: {
|
|
34
|
+
modelAssetBuffer: new Uint8Array(modelBuffer),
|
|
35
|
+
delegate: 'GPU',
|
|
36
|
+
},
|
|
37
|
+
runningMode: 'VIDEO',
|
|
38
|
+
numFaces: 2,
|
|
39
|
+
outputFaceBlendshapes: true,
|
|
40
|
+
outputFacialTransformationMatrixes: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function track(video, timestampMs) {
|
|
45
|
+
if (!landmarker) return null;
|
|
46
|
+
|
|
47
|
+
const normalized = normalizeTimestamp(timestampMs);
|
|
48
|
+
const result = landmarker.detectForVideo(video, normalized);
|
|
49
|
+
const faceCount = result.faceLandmarks?.length ?? 0;
|
|
50
|
+
|
|
51
|
+
if (faceCount === 0) {
|
|
52
|
+
return { faceCount: 0, timestampMs: normalized };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const landmarks = result.faceLandmarks[0];
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
faceCount,
|
|
59
|
+
timestampMs: normalized,
|
|
60
|
+
landmarks,
|
|
61
|
+
blendshapes: parseBlendshapes(result.faceBlendshapes?.[0]),
|
|
62
|
+
headPose: extractHeadPose(result.facialTransformationMatrixes?.[0]),
|
|
63
|
+
boundingBox: computeBoundingBox(landmarks),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function destroyTracker() {
|
|
68
|
+
if (landmarker) {
|
|
69
|
+
landmarker.close();
|
|
70
|
+
landmarker = null;
|
|
71
|
+
}
|
|
72
|
+
lastTimestampMs = -1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isTrackerReady() {
|
|
76
|
+
return landmarker !== null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeTimestamp(timestampMs) {
|
|
80
|
+
const safe = Number.isFinite(timestampMs) ? timestampMs : performance.now();
|
|
81
|
+
const whole = Math.floor(safe);
|
|
82
|
+
const normalized = Math.max(whole, lastTimestampMs + 1);
|
|
83
|
+
lastTimestampMs = normalized;
|
|
84
|
+
return normalized;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseBlendshapes(blendshapeResult) {
|
|
88
|
+
if (!blendshapeResult?.categories) return {};
|
|
89
|
+
const map = {};
|
|
90
|
+
for (const category of blendshapeResult.categories) {
|
|
91
|
+
map[category.categoryName] = category.score;
|
|
92
|
+
}
|
|
93
|
+
return map;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function extractHeadPose(matrix) {
|
|
97
|
+
if (!matrix?.data || matrix.data.length < 16) {
|
|
98
|
+
return { yaw: 0, pitch: 0, roll: 0 };
|
|
99
|
+
}
|
|
100
|
+
const m = matrix.data;
|
|
101
|
+
const deg = 180 / Math.PI;
|
|
102
|
+
return {
|
|
103
|
+
yaw: Math.atan2(m[8], m[10]) * deg,
|
|
104
|
+
pitch: Math.asin(-Math.max(-1, Math.min(1, m[9]))) * deg,
|
|
105
|
+
roll: Math.atan2(m[1], m[5]) * deg,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function computeBoundingBox(landmarks) {
|
|
110
|
+
let minX = 1,
|
|
111
|
+
minY = 1,
|
|
112
|
+
maxX = 0,
|
|
113
|
+
maxY = 0;
|
|
114
|
+
|
|
115
|
+
for (const point of landmarks) {
|
|
116
|
+
if (point.x < minX) minX = point.x;
|
|
117
|
+
if (point.y < minY) minY = point.y;
|
|
118
|
+
if (point.x > maxX) maxX = point.x;
|
|
119
|
+
if (point.y > maxY) maxY = point.y;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const width = maxX - minX;
|
|
123
|
+
const height = maxY - minY;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
x: minX,
|
|
127
|
+
y: minY,
|
|
128
|
+
width,
|
|
129
|
+
height,
|
|
130
|
+
area: width * height,
|
|
131
|
+
};
|
|
132
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { WidgetParams } from "./widget";
|
|
2
|
+
import { TokenPayload } from "./transport";
|
|
3
|
+
|
|
4
|
+
export type OpenAgeEvent =
|
|
5
|
+
| "verified"
|
|
6
|
+
| "expired"
|
|
7
|
+
| "error"
|
|
8
|
+
| "opened"
|
|
9
|
+
| "closed";
|
|
10
|
+
|
|
11
|
+
export interface OpenAge {
|
|
12
|
+
render(
|
|
13
|
+
container: string | HTMLElement,
|
|
14
|
+
params?: WidgetParams
|
|
15
|
+
): string;
|
|
16
|
+
|
|
17
|
+
open(params?: WidgetParams): string;
|
|
18
|
+
|
|
19
|
+
bind(
|
|
20
|
+
element: string | HTMLElement,
|
|
21
|
+
params?: WidgetParams
|
|
22
|
+
): () => void;
|
|
23
|
+
|
|
24
|
+
reset(widgetId: string): void;
|
|
25
|
+
remove(widgetId: string): void;
|
|
26
|
+
getToken(widgetId: string): string | null;
|
|
27
|
+
execute(widgetId: string): void;
|
|
28
|
+
|
|
29
|
+
challenge(params?: WidgetParams): Promise<string>;
|
|
30
|
+
|
|
31
|
+
on(
|
|
32
|
+
event: OpenAgeEvent,
|
|
33
|
+
handler: (...args: any[]) => void
|
|
34
|
+
): void;
|
|
35
|
+
|
|
36
|
+
off(
|
|
37
|
+
event: OpenAgeEvent,
|
|
38
|
+
handler: (...args: any[]) => void
|
|
39
|
+
): void;
|
|
40
|
+
|
|
41
|
+
once(
|
|
42
|
+
event: OpenAgeEvent,
|
|
43
|
+
handler: (...args: any[]) => void
|
|
44
|
+
): void;
|
|
45
|
+
|
|
46
|
+
verify(token: string): Promise<TokenPayload | null>;
|
|
47
|
+
decode(token: string): TokenPayload | null;
|
|
48
|
+
|
|
49
|
+
version: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare const OpenAge: OpenAge;
|
|
53
|
+
export default OpenAge;
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
render,
|
|
57
|
+
open,
|
|
58
|
+
bind,
|
|
59
|
+
reset,
|
|
60
|
+
remove,
|
|
61
|
+
getToken,
|
|
62
|
+
execute,
|
|
63
|
+
challenge,
|
|
64
|
+
on,
|
|
65
|
+
off,
|
|
66
|
+
once,
|
|
67
|
+
} from "./index";
|
|
68
|
+
|
|
69
|
+
export { verify, decode } from "./transport";
|
|
70
|
+
export { version } from "./constants";
|
|
71
|
+
|
|
72
|
+
export type { WidgetParams } from "./widget";
|
|
73
|
+
export type { TokenPayload } from "./transport";
|
|
74
|
+
export type { TrackingResult, HeadPose, BoundingBox } from "./face-tracker";
|
|
75
|
+
export type { LivenessSession, LivenessTask } from "./liveness";
|
|
76
|
+
export type { AgeResult } from "./age-estimator";
|
|
77
|
+
export type { Transport, TransportResult } from "./transport";
|
|
78
|
+
|
|
79
|
+
declare global {
|
|
80
|
+
interface Window {
|
|
81
|
+
openage?: {
|
|
82
|
+
sitekey?: string;
|
|
83
|
+
server?: string;
|
|
84
|
+
mode?: "serverless" | "sitekey" | "custom";
|
|
85
|
+
locale?: string;
|
|
86
|
+
theme?: "light" | "dark" | "auto";
|
|
87
|
+
render?: "explicit";
|
|
88
|
+
};
|
|
89
|
+
OpenAge: OpenAge;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { Widget, createModalWidget } from './widget.js';
|
|
2
|
+
import { runChallenge } from './challenge.js';
|
|
3
|
+
import { verifyToken, decodeToken } from './transport.js';
|
|
4
|
+
import { VERSION } from './constants.js';
|
|
5
|
+
|
|
6
|
+
export class EventEmitter {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.listeners = new Map();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
on(event, handler) {
|
|
12
|
+
if (!this.listeners.has(event)) {
|
|
13
|
+
this.listeners.set(event, new Set());
|
|
14
|
+
}
|
|
15
|
+
this.listeners.get(event).add(handler);
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
off(event, handler) {
|
|
20
|
+
const handlers = this.listeners.get(event);
|
|
21
|
+
if (handlers) handlers.delete(handler);
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
once(event, handler) {
|
|
26
|
+
const wrapper = (...args) => {
|
|
27
|
+
this.off(event, wrapper);
|
|
28
|
+
handler(...args);
|
|
29
|
+
};
|
|
30
|
+
wrapper._original = handler;
|
|
31
|
+
return this.on(event, wrapper);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
emit(event, ...args) {
|
|
35
|
+
const handlers = this.listeners.get(event);
|
|
36
|
+
if (!handlers) return;
|
|
37
|
+
for (const handler of [...handlers]) {
|
|
38
|
+
handler(...args);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
removeAllListeners(event) {
|
|
43
|
+
if (event) {
|
|
44
|
+
this.listeners.delete(event);
|
|
45
|
+
} else {
|
|
46
|
+
this.listeners.clear();
|
|
47
|
+
}
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const emitter = new EventEmitter();
|
|
53
|
+
const widgets = new Map();
|
|
54
|
+
|
|
55
|
+
function normalizeParams(params) {
|
|
56
|
+
const globalConfig = typeof window !== 'undefined' ? window.openage || {} : {};
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
mode: 'serverless',
|
|
60
|
+
theme: 'auto',
|
|
61
|
+
size: 'normal',
|
|
62
|
+
minAge: 18,
|
|
63
|
+
...globalConfig,
|
|
64
|
+
...params,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function startWidget(widget) {
|
|
69
|
+
widget.onChallenge = () => {
|
|
70
|
+
emitter.emit('opened', widget.id);
|
|
71
|
+
runChallenge(widget, emitter);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function render(container, params = {}) {
|
|
76
|
+
const normalized = normalizeParams(params);
|
|
77
|
+
const widget = new Widget(container, normalized);
|
|
78
|
+
widgets.set(widget.id, widget);
|
|
79
|
+
startWidget(widget);
|
|
80
|
+
return widget.id;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function open(params = {}) {
|
|
84
|
+
const normalized = normalizeParams(params);
|
|
85
|
+
const widget = createModalWidget(normalized);
|
|
86
|
+
widget.anchorElement = normalized.anchorElement || null;
|
|
87
|
+
widgets.set(widget.id, widget);
|
|
88
|
+
|
|
89
|
+
widget.onChallenge = () => {
|
|
90
|
+
emitter.emit('opened', widget.id);
|
|
91
|
+
runChallenge(widget, emitter);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
widget.startChallenge();
|
|
95
|
+
return widget.id;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function bind(element, params = {}) {
|
|
99
|
+
const normalized = normalizeParams(params);
|
|
100
|
+
const target = typeof element === 'string' ? document.querySelector(element) : element;
|
|
101
|
+
|
|
102
|
+
if (!target) {
|
|
103
|
+
throw new Error('OpenAge: element not found');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let isReplayingClick = false;
|
|
107
|
+
let activeWidgetId = null;
|
|
108
|
+
|
|
109
|
+
const replayTargetClick = () => {
|
|
110
|
+
isReplayingClick = true;
|
|
111
|
+
try {
|
|
112
|
+
target.click();
|
|
113
|
+
} finally {
|
|
114
|
+
isReplayingClick = false;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const clearActiveWidget = () => {
|
|
119
|
+
activeWidgetId = null;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handler = (event) => {
|
|
123
|
+
if (isReplayingClick || activeWidgetId) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
event.stopPropagation();
|
|
129
|
+
event.stopImmediatePropagation?.();
|
|
130
|
+
|
|
131
|
+
const widgetId = open({
|
|
132
|
+
...normalized,
|
|
133
|
+
anchorElement: target,
|
|
134
|
+
callback: (token) => {
|
|
135
|
+
normalized.callback?.(token);
|
|
136
|
+
clearActiveWidget();
|
|
137
|
+
replayTargetClick();
|
|
138
|
+
},
|
|
139
|
+
errorCallback: (error) => {
|
|
140
|
+
clearActiveWidget();
|
|
141
|
+
normalized.errorCallback?.(error);
|
|
142
|
+
},
|
|
143
|
+
closeCallback: () => {
|
|
144
|
+
clearActiveWidget();
|
|
145
|
+
normalized.closeCallback?.();
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
activeWidgetId = widgetId;
|
|
150
|
+
|
|
151
|
+
return widgetId;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
target.addEventListener('click', handler, true);
|
|
155
|
+
|
|
156
|
+
return () => {
|
|
157
|
+
target.removeEventListener('click', handler, true);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function reset(widgetId) {
|
|
162
|
+
widgets.get(widgetId)?.reset();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function remove(widgetId) {
|
|
166
|
+
const widget = widgets.get(widgetId);
|
|
167
|
+
if (!widget) return;
|
|
168
|
+
widget.destroy();
|
|
169
|
+
widgets.delete(widgetId);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getToken(widgetId) {
|
|
173
|
+
return widgets.get(widgetId)?.getToken() || null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function execute(widgetId) {
|
|
177
|
+
const widget = widgets.get(widgetId);
|
|
178
|
+
if (!widget) return;
|
|
179
|
+
widget.startChallenge();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function challenge(params = {}) {
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
open({
|
|
185
|
+
...params,
|
|
186
|
+
callback: (token) => resolve(token),
|
|
187
|
+
errorCallback: (error) => reject(typeof error === 'string' ? new Error(error) : error),
|
|
188
|
+
closeCallback: () => reject(new Error('User dismissed')),
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function on(event, handler) {
|
|
194
|
+
emitter.on(event, handler);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function off(event, handler) {
|
|
198
|
+
emitter.off(event, handler);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function once(event, handler) {
|
|
202
|
+
emitter.once(event, handler);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function autoRender() {
|
|
206
|
+
if (typeof document === 'undefined') return;
|
|
207
|
+
|
|
208
|
+
const globalConfig = typeof window !== 'undefined' ? window.openage || {} : {};
|
|
209
|
+
|
|
210
|
+
if (globalConfig.render === 'explicit') return;
|
|
211
|
+
|
|
212
|
+
const elements = document.querySelectorAll('.openage');
|
|
213
|
+
|
|
214
|
+
for (const element of elements) {
|
|
215
|
+
const params = {
|
|
216
|
+
sitekey: element.dataset.sitekey,
|
|
217
|
+
theme: element.dataset.theme,
|
|
218
|
+
size: element.dataset.size,
|
|
219
|
+
action: element.dataset.action,
|
|
220
|
+
mode: element.dataset.mode,
|
|
221
|
+
server: element.dataset.server,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (element.dataset.callback) {
|
|
225
|
+
params.callback = (token) => {
|
|
226
|
+
const fn = window[element.dataset.callback];
|
|
227
|
+
if (typeof fn === 'function') fn(token);
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (element.dataset.errorCallback) {
|
|
232
|
+
params.errorCallback = (error) => {
|
|
233
|
+
const fn = window[element.dataset.errorCallback];
|
|
234
|
+
if (typeof fn === 'function') fn(error);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (element.dataset.expiredCallback) {
|
|
239
|
+
params.expiredCallback = () => {
|
|
240
|
+
const fn = window[element.dataset.expiredCallback];
|
|
241
|
+
if (typeof fn === 'function') fn();
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (element.dataset.bind) {
|
|
246
|
+
const target = document.getElementById(element.dataset.bind);
|
|
247
|
+
if (target) {
|
|
248
|
+
bind(target, params);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
render(element, params);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const onloadName = document.currentScript?.dataset?.onload;
|
|
257
|
+
if (onloadName && typeof window[onloadName] === 'function') {
|
|
258
|
+
window[onloadName]();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (typeof document !== 'undefined') {
|
|
263
|
+
if (document.readyState === 'loading') {
|
|
264
|
+
document.addEventListener('DOMContentLoaded', autoRender);
|
|
265
|
+
} else {
|
|
266
|
+
autoRender();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const OpenAge = {
|
|
271
|
+
render,
|
|
272
|
+
open,
|
|
273
|
+
bind,
|
|
274
|
+
reset,
|
|
275
|
+
remove,
|
|
276
|
+
getToken,
|
|
277
|
+
execute,
|
|
278
|
+
challenge,
|
|
279
|
+
on,
|
|
280
|
+
off,
|
|
281
|
+
once,
|
|
282
|
+
verify: verifyToken,
|
|
283
|
+
decode: decodeToken,
|
|
284
|
+
version: VERSION,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export default OpenAge;
|
|
288
|
+
export {
|
|
289
|
+
render,
|
|
290
|
+
open,
|
|
291
|
+
bind,
|
|
292
|
+
reset,
|
|
293
|
+
remove,
|
|
294
|
+
getToken,
|
|
295
|
+
execute,
|
|
296
|
+
challenge,
|
|
297
|
+
on,
|
|
298
|
+
off,
|
|
299
|
+
once,
|
|
300
|
+
verifyToken as verify,
|
|
301
|
+
decodeToken as decode,
|
|
302
|
+
VERSION as version,
|
|
303
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { TrackingResult } from "./face-tracker";
|
|
2
|
+
|
|
3
|
+
export interface LivenessTask {
|
|
4
|
+
id: string;
|
|
5
|
+
instruction: string;
|
|
6
|
+
check: (history: MotionEntry[]) => boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MotionEntry {
|
|
10
|
+
timestamp: number;
|
|
11
|
+
headPose: { yaw: number; pitch: number; roll: number };
|
|
12
|
+
blendshapes: Record<string, number>;
|
|
13
|
+
boundingBox: { area: number; [key: string]: number };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LivenessSession {
|
|
17
|
+
tasks: LivenessTask[];
|
|
18
|
+
currentIndex: number;
|
|
19
|
+
history: MotionEntry[];
|
|
20
|
+
taskStartTime: number;
|
|
21
|
+
completedTasks: number;
|
|
22
|
+
requiredPasses: number;
|
|
23
|
+
failed: boolean;
|
|
24
|
+
failReason: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export declare function pickTasks(
|
|
28
|
+
count?: number
|
|
29
|
+
): LivenessTask[];
|
|
30
|
+
|
|
31
|
+
export declare function createSession(
|
|
32
|
+
tasks?: LivenessTask[]
|
|
33
|
+
): LivenessSession;
|
|
34
|
+
|
|
35
|
+
export declare function processFrame(
|
|
36
|
+
session: LivenessSession,
|
|
37
|
+
trackingResult: TrackingResult
|
|
38
|
+
): void;
|
|
39
|
+
|
|
40
|
+
export declare function isComplete(
|
|
41
|
+
session: LivenessSession
|
|
42
|
+
): boolean;
|
|
43
|
+
|
|
44
|
+
export declare function isPassed(
|
|
45
|
+
session: LivenessSession
|
|
46
|
+
): boolean;
|
|
47
|
+
|
|
48
|
+
export declare function currentInstruction(
|
|
49
|
+
session: LivenessSession
|
|
50
|
+
): string | null;
|
|
51
|
+
|
|
52
|
+
export declare function currentTaskId(
|
|
53
|
+
session: LivenessSession
|
|
54
|
+
): string | null;
|
|
55
|
+
|
|
56
|
+
export declare function progress(
|
|
57
|
+
session: LivenessSession
|
|
58
|
+
): number;
|
|
59
|
+
|
|
60
|
+
export declare function isSuspicious(
|
|
61
|
+
history: MotionEntry[]
|
|
62
|
+
): boolean;
|