@powercred/liveness-web 0.0.0-stage → 0.1.1
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 +282 -2
- package/THIRD_PARTY_NOTICES.txt +216 -0
- package/dist/assets/liveness-face.bin +0 -0
- package/dist/assets/liveness-runtime-compat.js +20 -0
- package/dist/assets/liveness-runtime-compat.wasm +0 -0
- package/dist/assets/liveness-runtime.js +20 -0
- package/dist/assets/liveness-runtime.wasm +0 -0
- package/dist/index.d.ts +11 -0
- package/dist/liveness-device-C5ygqTPx.js +3741 -0
- package/dist/liveness.iife.js +70 -0
- package/dist/liveness.js +645 -0
- package/dist/sdk.d.ts +31 -0
- package/dist/types.d.ts +138 -0
- package/dist/ui/strings.d.ts +42 -0
- package/package.json +16 -4
package/dist/liveness.js
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
const oe = { gettingReady: "Getting ready…", center: "Center your face in the oval", moveCloser: "Move a little closer", moveBack: "Move a little back", lookStraight: "Look straight at the camera", noFace: "We can't see your face", multipleFaces: "Only one person, please", turnLeft: "Turn your head to your LEFT", turnRight: "Turn your head to your RIGHT", lookUp: "Look UP", lookDown: "Look DOWN", smile: "Smile", blink: "Blink once", openMouth: "Open your mouth", holdStill: "Hold still", great: "Great!", wrongDirection: "That was the other way — let's try again", faceLost: "We lost your face — let's try again", interrupted: "The check was interrupted — let's try again", timeUp: "Time's up — let's try again", tooSlow: "That took a while — let's try again", flashIntro: "Hold still — the screen will flash colours", checking: "Checking…", passedTitle: "Done", passedDetail: "Thank you, your liveness check is complete.", submittedTitle: "Thank you", failedTitle: "We couldn't confirm that", failedDetail: "Please try again in good light, facing the camera.", noAttemptsDetail: "Please contact the company that asked you to do this check.", tryAgain: "Try again", cameraDenied: "Camera access is needed. Allow it in your browser settings and try again.", cameraUnavailable: "We couldn't start your camera.", unsupportedBrowser: "This browser can't run the check. Please use an up-to-date Chrome, Safari, Edge or Firefox.", insecureContext: "This page must be opened over https.", network: "Connection problem. Please check your internet and try again.", sessionExpired: "This check has expired. Please start again.", serviceUnavailable: "The check is temporarily unavailable. Please try again shortly.", genericError: "Something went wrong. Please try again." }, le = `
|
|
2
|
+
:host { all: initial; display: block; }
|
|
3
|
+
.pcl { --primary: var(--pcl-primary, #4b45a1); --ok: #1f9d55; --warn: #e3a008; --bad: #d64545;
|
|
4
|
+
font-family: var(--pcl-font, system-ui, -apple-system, "Segoe UI", sans-serif); color: #fff;
|
|
5
|
+
position: relative; width: 100%; max-width: 560px; margin: 0 auto; }
|
|
6
|
+
.stage { position: relative; width: 100%; aspect-ratio: 3 / 4; background: #000; border-radius: 16px; overflow: hidden; }
|
|
7
|
+
@media (min-aspect-ratio: 4/3) and (min-width: 700px) { .stage { aspect-ratio: 4 / 3; } }
|
|
8
|
+
video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
|
|
9
|
+
.oval { position: absolute; left: 50%; top: 45%; width: 62%; aspect-ratio: 3 / 4; max-height: 76%; transform: translate(-50%, -50%);
|
|
10
|
+
border-radius: 50%; border: 3px solid rgba(255,255,255,.85); box-shadow: 0 0 0 9999px rgba(0,0,0,.5);
|
|
11
|
+
transition: border-color .2s; }
|
|
12
|
+
.oval[data-state="ok"] { border-color: var(--ok); }
|
|
13
|
+
.oval[data-state="warn"] { border-color: var(--warn); }
|
|
14
|
+
.prompt { position: absolute; left: 0; right: 0; bottom: 6%; padding: 0 16px; text-align: center;
|
|
15
|
+
font-size: clamp(18px, 5vw, 26px); font-weight: 650; line-height: 1.25; text-shadow: 0 2px 8px rgba(0,0,0,.8); }
|
|
16
|
+
.progress { position: absolute; top: 14px; left: 0; right: 0; display: flex; gap: 8px; justify-content: center; }
|
|
17
|
+
.dot { width: 10px; height: 10px; border-radius: 50%; background: rgba(255,255,255,.35); }
|
|
18
|
+
.dot[data-state="current"] { background: #fff; }
|
|
19
|
+
.dot[data-state="done"] { background: var(--ok); }
|
|
20
|
+
.countdown { position: absolute; top: 10px; right: 12px; font: 600 14px/1 inherit; background: rgba(0,0,0,.5);
|
|
21
|
+
padding: 6px 10px; border-radius: 99px; font-family: inherit; }
|
|
22
|
+
.debug { position: absolute; left: 8px; top: 34px; font: 12px/1.35 ui-monospace, monospace; background: rgba(0,0,0,.6);
|
|
23
|
+
padding: 6px 8px; border-radius: 6px; white-space: pre; }
|
|
24
|
+
.pcl.flashing .stage { position: fixed; inset: 0; z-index: 2147483647; border-radius: 0; aspect-ratio: auto;
|
|
25
|
+
background: var(--flash, #000); max-width: none; }
|
|
26
|
+
.pcl.flashing video { left: 50%; top: 50%; width: auto; height: 45vh; aspect-ratio: 3 / 4; transform: translate(-50%, -50%) scaleX(-1);
|
|
27
|
+
border-radius: 50%; inset: auto; }
|
|
28
|
+
.pcl.flashing .oval, .pcl.flashing .progress, .pcl.flashing .countdown { display: none; }
|
|
29
|
+
.pcl.flashing .prompt { bottom: 10%; }
|
|
30
|
+
.panel { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
31
|
+
gap: 12px; padding: 24px; text-align: center; background: rgba(15,17,21,.92); border-radius: 16px; }
|
|
32
|
+
.panel[hidden] { display: none; }
|
|
33
|
+
.icon { width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center; font-size: 30px; font-weight: 700; }
|
|
34
|
+
.icon.ok { background: var(--ok); } .icon.bad { background: var(--bad); }
|
|
35
|
+
.spinner { width: 44px; height: 44px; border-radius: 50%; border: 4px solid rgba(255,255,255,.25); border-top-color: #fff;
|
|
36
|
+
animation: spin 0.9s linear infinite; }
|
|
37
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
38
|
+
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 3s; } }
|
|
39
|
+
.title { font-size: 20px; font-weight: 650; margin: 0; }
|
|
40
|
+
.detail { font-size: 15px; margin: 0; color: #d0d4dc; max-width: 34ch; }
|
|
41
|
+
button { font: inherit; font-size: 16px; font-weight: 600; color: #fff; background: var(--primary); border: 0; border-radius: 10px;
|
|
42
|
+
padding: 12px 22px; cursor: pointer; }
|
|
43
|
+
button:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
|
|
44
|
+
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
|
|
45
|
+
`;
|
|
46
|
+
class ce {
|
|
47
|
+
constructor(e = {}) {
|
|
48
|
+
this.host = document.createElement("div");
|
|
49
|
+
const s = this.host.attachShadow({ mode: "open" });
|
|
50
|
+
s.innerHTML = `<style>${le}</style>
|
|
51
|
+
<div class="pcl">
|
|
52
|
+
<div class="stage">
|
|
53
|
+
<video playsinline muted autoplay></video>
|
|
54
|
+
<div class="oval" data-state="idle"></div>
|
|
55
|
+
<div class="progress"></div>
|
|
56
|
+
<div class="countdown" hidden></div>
|
|
57
|
+
<div class="debug" hidden></div>
|
|
58
|
+
<div class="prompt" role="status" aria-live="polite"></div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="panel" hidden role="dialog" aria-live="assertive"></div>
|
|
61
|
+
</div>`;
|
|
62
|
+
const i = (n) => s.querySelector(n);
|
|
63
|
+
this.root = i(".pcl"), this.video = i("video"), this.prompt = i(".prompt"), this.countdownEl = i(".countdown"), this.progressEl = i(".progress"), this.oval = i(".oval"), this.debugEl = i(".debug"), this.panel = i(".panel"), e.primaryColor && this.root.style.setProperty("--pcl-primary", e.primaryColor), e.fontFamily && this.root.style.setProperty("--pcl-font", e.fontFamily);
|
|
64
|
+
}
|
|
65
|
+
mount(e) {
|
|
66
|
+
e.appendChild(this.host);
|
|
67
|
+
}
|
|
68
|
+
unmount() {
|
|
69
|
+
this.host.remove();
|
|
70
|
+
}
|
|
71
|
+
async attach(e) {
|
|
72
|
+
this.video.srcObject = e, await this.video.play().catch(() => {
|
|
73
|
+
}), this.video.videoWidth || await new Promise((s) => this.video.addEventListener("loadedmetadata", () => s(), { once: !0 }));
|
|
74
|
+
}
|
|
75
|
+
setPrompt(e) {
|
|
76
|
+
this.prompt.textContent !== e && (this.prompt.textContent = e);
|
|
77
|
+
}
|
|
78
|
+
setCountdown(e) {
|
|
79
|
+
this.countdownEl.hidden = e === null, e !== null && (this.countdownEl.textContent = `${e}s`);
|
|
80
|
+
}
|
|
81
|
+
setProgress(e, s, i) {
|
|
82
|
+
this.progressEl.childElementCount !== e && (this.progressEl.innerHTML = Array.from({ length: e }, () => '<span class="dot"></span>').join("")), Array.from(this.progressEl.children).forEach((n, a) => {
|
|
83
|
+
n.setAttribute("data-state", a < s ? "done" : a === i ? "current" : "todo");
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
setOval(e) {
|
|
87
|
+
this.oval.setAttribute("data-state", e);
|
|
88
|
+
}
|
|
89
|
+
setFlash(e) {
|
|
90
|
+
this.root.classList.toggle("flashing", e !== null), e ? this.root.style.setProperty("--flash", e) : this.root.style.removeProperty("--flash");
|
|
91
|
+
}
|
|
92
|
+
setDebug(e) {
|
|
93
|
+
this.debugEl.hidden = e === null, e !== null && (this.debugEl.textContent = e);
|
|
94
|
+
}
|
|
95
|
+
showPanel(e, s, i = "", n) {
|
|
96
|
+
const a = e === "working" ? '<div class="spinner" aria-hidden="true"></div>' : `<div class="icon ${e}" aria-hidden="true">${e === "ok" ? "✓" : "!"}</div>`;
|
|
97
|
+
if (this.panel.innerHTML = `${a}<p class="title"></p>${i ? '<p class="detail"></p>' : ""}${n ? '<button type="button"></button>' : ""}`, this.panel.querySelector(".title").textContent = s, i && (this.panel.querySelector(".detail").textContent = i), this.panel.hidden = !1, !n) return Promise.resolve();
|
|
98
|
+
const r = this.panel.querySelector("button");
|
|
99
|
+
return r.textContent = n, r.focus(), new Promise((o) => r.addEventListener("click", () => o(), { once: !0 }));
|
|
100
|
+
}
|
|
101
|
+
hidePanel() {
|
|
102
|
+
this.panel.hidden = !0, this.panel.innerHTML = "";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const de = { runtime: "liveness-runtime", runtimeCompat: "liveness-runtime-compat", model: "liveness-face.bin" }, he = import.meta.url, ue = "assets/";
|
|
106
|
+
function pe(t) {
|
|
107
|
+
const e = new URL(t || ue, t ? document.baseURI : he).href;
|
|
108
|
+
return e.endsWith("/") ? e : e + "/";
|
|
109
|
+
}
|
|
110
|
+
class U extends Error {
|
|
111
|
+
}
|
|
112
|
+
class I {
|
|
113
|
+
constructor(e) {
|
|
114
|
+
this.engine = e, this.lastTs = 0, this.inferenceMs = [];
|
|
115
|
+
}
|
|
116
|
+
static async create(e, s = 2e4) {
|
|
117
|
+
const i = pe(e), n = import("./liveness-device-C5ygqTPx.js").then((r) => r.createEngine(i, de));
|
|
118
|
+
let a;
|
|
119
|
+
try {
|
|
120
|
+
const r = await Promise.race([n, new Promise((o, l) => {
|
|
121
|
+
a = setTimeout(() => l(new U("face tracker load timed out")), s);
|
|
122
|
+
})]);
|
|
123
|
+
return new I(r);
|
|
124
|
+
} catch (r) {
|
|
125
|
+
throw n.then((o) => o.close(), () => {
|
|
126
|
+
}), r instanceof U ? r : new U(String(r));
|
|
127
|
+
} finally {
|
|
128
|
+
clearTimeout(a);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
detect(e, s) {
|
|
132
|
+
const i = performance.now(), n = Math.max(i, this.lastTs + 1);
|
|
133
|
+
this.lastTs = n;
|
|
134
|
+
const a = this.engine.detect(e, n, s);
|
|
135
|
+
return this.inferenceMs.push(performance.now() - i), this.inferenceMs.length > 60 && this.inferenceMs.shift(), a;
|
|
136
|
+
}
|
|
137
|
+
medianInferenceMs() {
|
|
138
|
+
if (this.inferenceMs.length < 10) return null;
|
|
139
|
+
const e = [...this.inferenceMs].sort((s, i) => s - i);
|
|
140
|
+
return e[Math.floor(e.length / 2)];
|
|
141
|
+
}
|
|
142
|
+
close() {
|
|
143
|
+
this.engine.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function x(t) {
|
|
147
|
+
return t.faceCount >= 1 && t.yaw !== void 0;
|
|
148
|
+
}
|
|
149
|
+
class me {
|
|
150
|
+
constructor(e = 0.5) {
|
|
151
|
+
this.alpha = e;
|
|
152
|
+
}
|
|
153
|
+
apply(e) {
|
|
154
|
+
return x(e) ? (this.yaw = this.yaw === void 0 ? e.yaw : this.alpha * e.yaw + (1 - this.alpha) * this.yaw, this.pitch = this.pitch === void 0 ? e.pitch : this.alpha * e.pitch + (1 - this.alpha) * this.pitch, { ...e, yaw: this.yaw, pitch: this.pitch }) : e;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function fe(t) {
|
|
158
|
+
if (t.length === 0) return;
|
|
159
|
+
const e = [...t].sort((i, n) => i - n), s = Math.floor(e.length / 2);
|
|
160
|
+
return e.length % 2 ? e[s] : (e[s - 1] + e[s]) / 2;
|
|
161
|
+
}
|
|
162
|
+
const w = { turnHeadDeg: 20, nodDeg: 15, jawOpen: 0.4, smile: 0.5, blinkClosed: 0.6, blinkOpen: 0.25, blinkMaxMs: 600, poseHoldMs: 150, expressionHoldMs: 300, baselineMs: 300 };
|
|
163
|
+
class R {
|
|
164
|
+
constructor(e) {
|
|
165
|
+
this.ms = e, this.since = null, this.misses = 0;
|
|
166
|
+
}
|
|
167
|
+
update(e, s) {
|
|
168
|
+
return s ? (this.since === null && (this.since = e), this.misses = 0, e - this.since >= this.ms) : this.since !== null && this.misses === 0 ? (this.misses = 1, !1) : (this.since = null, this.misses = 0, !1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
class G {
|
|
172
|
+
constructor(e, s, i) {
|
|
173
|
+
this.axis = e, this.negative = s, this.threshold = i, this.passedAt = null, this.start = null, this.samples = [], this.extreme = 0, this.hold = new R(w.poseHoldMs), this.opposite = new R(w.poseHoldMs);
|
|
174
|
+
}
|
|
175
|
+
feed(e) {
|
|
176
|
+
if (this.passedAt !== null) return "passed";
|
|
177
|
+
if (this.start === null && (this.start = e.t), !x(e)) return this.hold.update(e.t, !1), this.opposite.update(e.t, !1), "pending";
|
|
178
|
+
const s = e[this.axis];
|
|
179
|
+
if (this.baseline === void 0) {
|
|
180
|
+
if (this.samples.push(s), e.t - this.start < w.baselineMs) return "pending";
|
|
181
|
+
this.baseline = fe(this.samples);
|
|
182
|
+
}
|
|
183
|
+
const i = s - this.baseline;
|
|
184
|
+
(this.negative ? i < this.extreme : i > this.extreme) && (this.extreme = i);
|
|
185
|
+
const n = this.negative ? i <= -this.threshold : i >= this.threshold, a = this.negative ? i >= this.threshold : i <= -this.threshold;
|
|
186
|
+
return this.opposite.update(e.t, a) ? "wrong_direction" : this.hold.update(e.t, n) ? (this.passedAt = e.t, "passed") : "pending";
|
|
187
|
+
}
|
|
188
|
+
get peak() {
|
|
189
|
+
return this.axis === "yaw" ? { yaw: D(this.extreme) } : { pitch: D(this.extreme) };
|
|
190
|
+
}
|
|
191
|
+
get confidence() {
|
|
192
|
+
return j(Math.abs(this.extreme) / (this.threshold * 1.5));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
class X {
|
|
196
|
+
constructor(e, s) {
|
|
197
|
+
this.attr = e, this.threshold = s, this.passedAt = null, this.highest = 0, this.hold = new R(w.expressionHoldMs);
|
|
198
|
+
}
|
|
199
|
+
feed(e) {
|
|
200
|
+
if (this.passedAt !== null) return "passed";
|
|
201
|
+
const s = x(e) ? e[this.attr] ?? 0 : 0;
|
|
202
|
+
return this.highest = Math.max(this.highest, s), this.hold.update(e.t, x(e) && s >= this.threshold) ? (this.passedAt = e.t, "passed") : "pending";
|
|
203
|
+
}
|
|
204
|
+
get peak() {
|
|
205
|
+
return { score: D(this.highest, 3) };
|
|
206
|
+
}
|
|
207
|
+
get confidence() {
|
|
208
|
+
return j(this.highest);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
class we {
|
|
212
|
+
constructor() {
|
|
213
|
+
this.passedAt = null, this.closedAt = null, this.highest = 0;
|
|
214
|
+
}
|
|
215
|
+
feed(e) {
|
|
216
|
+
if (this.passedAt !== null) return "passed";
|
|
217
|
+
if (!x(e)) return "pending";
|
|
218
|
+
if (this.highest = Math.max(this.highest, e.blink), e.blink >= w.blinkClosed) this.closedAt === null && (this.closedAt = e.t);
|
|
219
|
+
else if (this.closedAt !== null) {
|
|
220
|
+
if (e.t - this.closedAt > w.blinkMaxMs) this.closedAt = null;
|
|
221
|
+
else if (e.blink <= w.blinkOpen) return this.passedAt = this.closedAt, "passed";
|
|
222
|
+
}
|
|
223
|
+
return "pending";
|
|
224
|
+
}
|
|
225
|
+
get peak() {
|
|
226
|
+
return { score: D(this.highest, 3) };
|
|
227
|
+
}
|
|
228
|
+
get confidence() {
|
|
229
|
+
return j(this.highest);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function J(t, e) {
|
|
233
|
+
switch (t) {
|
|
234
|
+
case "turn_head":
|
|
235
|
+
return new G("yaw", e === "left", w.turnHeadDeg);
|
|
236
|
+
case "nod":
|
|
237
|
+
return new G("pitch", e === "up", w.nodDeg);
|
|
238
|
+
case "smile":
|
|
239
|
+
return new X("smile", w.smile);
|
|
240
|
+
case "open_mouth":
|
|
241
|
+
return new X("jawOpen", w.jawOpen);
|
|
242
|
+
case "blink":
|
|
243
|
+
return new we();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const k = { holdMs: 800, center: 0.1, minHeight: 0.35, maxHeight: 0.7, maxAngle: 12 };
|
|
247
|
+
function ge(t) {
|
|
248
|
+
return t.faceCount > 1 ? "multiple_faces" : x(t) ? t.boxHeight < k.minHeight ? "move_closer" : t.boxHeight > k.maxHeight ? "move_back" : Math.abs(t.centerX - 0.5) > k.center || Math.abs(t.centerY - 0.5) > k.center ? "center" : Math.abs(t.yaw) > k.maxAngle || Math.abs(t.pitch) > k.maxAngle ? "look_straight" : "ok" : "no_face";
|
|
249
|
+
}
|
|
250
|
+
class ve {
|
|
251
|
+
constructor() {
|
|
252
|
+
this.hold = new R(k.holdMs);
|
|
253
|
+
}
|
|
254
|
+
feed(e) {
|
|
255
|
+
const s = ge(e);
|
|
256
|
+
return { hint: s, ready: this.hold.update(e.t, s === "ok") };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function Y(t) {
|
|
260
|
+
return x(t) && t.faceCount === 1 && Math.abs(t.yaw) < 10 && Math.abs(t.pitch) < 10 && t.blink < 0.3;
|
|
261
|
+
}
|
|
262
|
+
class be {
|
|
263
|
+
constructor() {
|
|
264
|
+
this.multi = 0, this.lostSince = null;
|
|
265
|
+
}
|
|
266
|
+
feed(e) {
|
|
267
|
+
if (this.multi = e.faceCount > 1 ? this.multi + 1 : 0, this.multi >= 5) return "multiple_faces";
|
|
268
|
+
if (e.faceCount === 0) {
|
|
269
|
+
if (this.lostSince === null && (this.lostSince = e.t), e.t - this.lostSince >= 1500) return "face_lost";
|
|
270
|
+
} else this.lostSince = null;
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function j(t) {
|
|
275
|
+
return Math.max(0, Math.min(1, t));
|
|
276
|
+
}
|
|
277
|
+
function D(t, e = 1) {
|
|
278
|
+
const s = 10 ** e;
|
|
279
|
+
return Math.round(t * s) / s;
|
|
280
|
+
}
|
|
281
|
+
const ye = "liveness-web", ke = "0.1.1";
|
|
282
|
+
function _e(t) {
|
|
283
|
+
const e = navigator.userAgent, s = navigator.userAgentData;
|
|
284
|
+
return { schema_version: 1, liveness_session_id: t.session.liveness_session_id, sdk: { name: ye, version: ke }, platform: { os: (s?.platform || navigator.platform || "").slice(0, 64) || null, device: null, browser: e.slice(0, 64) }, camera: { width: t.camera.width ?? null, height: t.camera.height ?? null, fps_measured: t.camera.frameRate ?? null, facing: "user" }, video: { mime: t.video.mime, duration_ms: t.video.durationMs, started_at_epoch_ms: t.video.startedAtEpochMs }, mode: t.session.mode, challenges: t.challenges, flash: t.flash, stills: t.stills, device_result: { passed: !0, aborted_reason: null, mode: t.mode, fallback_reason: t.fallbackReason, frames_total: t.tally.framesTotal, frames_with_face: t.tally.framesWithFace, frames_with_two_faces: t.tally.framesWithTwoFaces } };
|
|
285
|
+
}
|
|
286
|
+
class p extends Error {
|
|
287
|
+
constructor(e, s) {
|
|
288
|
+
super(s || e), this.code = e;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function K() {
|
|
292
|
+
if (!window.isSecureContext) throw new p("insecure_context", "The camera needs an https page");
|
|
293
|
+
if (!navigator.mediaDevices?.getUserMedia) throw new p("unsupported_browser");
|
|
294
|
+
if (typeof MediaRecorder > "u") throw new p("unsupported_browser");
|
|
295
|
+
try {
|
|
296
|
+
return await navigator.mediaDevices.getUserMedia({ video: { facingMode: "user", width: { ideal: 640 }, height: { ideal: 480 }, frameRate: { ideal: 30 } }, audio: !1 });
|
|
297
|
+
} catch (t) {
|
|
298
|
+
const e = t?.name;
|
|
299
|
+
throw e === "NotAllowedError" || e === "SecurityError" ? new p("camera_denied") : new p("camera_unavailable", e);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function Q(t) {
|
|
303
|
+
t?.getTracks().forEach((e) => e.stop());
|
|
304
|
+
}
|
|
305
|
+
function xe(t = (e) => MediaRecorder.isTypeSupported(e)) {
|
|
306
|
+
return ["video/mp4;codecs=avc1.42E01E", "video/mp4", "video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm"].find((e) => t(e)) || "";
|
|
307
|
+
}
|
|
308
|
+
class Me {
|
|
309
|
+
constructor(e, s = 12e5) {
|
|
310
|
+
this.chunks = [], this.t0 = 0, this.epoch = 0;
|
|
311
|
+
const i = xe();
|
|
312
|
+
this.recorder = new MediaRecorder(e, i ? { mimeType: i, videoBitsPerSecond: s } : { videoBitsPerSecond: s }), this.recorder.ondataavailable = (n) => {
|
|
313
|
+
n.data && n.data.size && this.chunks.push(n.data);
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
async start() {
|
|
317
|
+
const e = new Promise((s) => this.recorder.addEventListener("start", () => s(), { once: !0 }));
|
|
318
|
+
this.stopped = new Promise((s) => this.recorder.addEventListener("stop", () => s(), { once: !0 })), this.recorder.start(1e3), await e, this.t0 = performance.now(), this.epoch = Date.now();
|
|
319
|
+
}
|
|
320
|
+
now() {
|
|
321
|
+
return Math.round(performance.now() - this.t0);
|
|
322
|
+
}
|
|
323
|
+
async stop() {
|
|
324
|
+
this.recorder.state !== "inactive" && this.recorder.stop(), await this.stopped;
|
|
325
|
+
const e = (this.recorder.mimeType || "video/webm").split(";")[0];
|
|
326
|
+
return new Blob(this.chunks, { type: e });
|
|
327
|
+
}
|
|
328
|
+
abort() {
|
|
329
|
+
try {
|
|
330
|
+
this.recorder.state !== "inactive" && this.recorder.stop();
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function Pe(t, e) {
|
|
336
|
+
const s = t.videoWidth, i = t.videoHeight, n = Math.min(1, 1280 / Math.max(s, i)), a = document.createElement("canvas");
|
|
337
|
+
a.width = Math.round(s * n), a.height = Math.round(i * n), a.getContext("2d").drawImage(t, 0, 0, a.width, a.height);
|
|
338
|
+
let r = 0.9;
|
|
339
|
+
for (; ; ) {
|
|
340
|
+
const o = await new Promise((l) => a.toBlob(l, "image/jpeg", r));
|
|
341
|
+
if (!o) throw new p("capture_failed");
|
|
342
|
+
if (o.size <= e || r <= 0.45) return o;
|
|
343
|
+
r -= 0.1;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const _ = (t) => new Promise((e) => setTimeout(e, t)), Ce = () => new Promise((t) => requestAnimationFrame(t)), Te = 120, Se = 700, Ee = 400, Ae = 1e3, Re = 5e3, De = 2200;
|
|
347
|
+
class A extends Error {
|
|
348
|
+
}
|
|
349
|
+
class Z extends Error {
|
|
350
|
+
}
|
|
351
|
+
function W(t, e) {
|
|
352
|
+
switch (t.type) {
|
|
353
|
+
case "turn_head":
|
|
354
|
+
return t.direction === "left" ? e.turnLeft : e.turnRight;
|
|
355
|
+
case "nod":
|
|
356
|
+
return t.direction === "up" ? e.lookUp : e.lookDown;
|
|
357
|
+
case "smile":
|
|
358
|
+
return e.smile;
|
|
359
|
+
case "blink":
|
|
360
|
+
return e.blink;
|
|
361
|
+
case "open_mouth":
|
|
362
|
+
return e.openMouth;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function Fe(t, e) {
|
|
366
|
+
switch (t) {
|
|
367
|
+
case "ok":
|
|
368
|
+
return e.lookStraight;
|
|
369
|
+
case "no_face":
|
|
370
|
+
return e.noFace;
|
|
371
|
+
case "multiple_faces":
|
|
372
|
+
return e.multipleFaces;
|
|
373
|
+
case "move_closer":
|
|
374
|
+
return e.moveCloser;
|
|
375
|
+
case "move_back":
|
|
376
|
+
return e.moveBack;
|
|
377
|
+
case "center":
|
|
378
|
+
return e.center;
|
|
379
|
+
case "look_straight":
|
|
380
|
+
return e.lookStraight;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function Le(t) {
|
|
384
|
+
const { session: e, view: s, strings: i } = t, n = t.mode === "device" && t.tracker !== null, a = new me(), r = { framesTotal: 0, framesWithFace: 0, framesWithTwoFaces: 0 };
|
|
385
|
+
let o = null, l = !1, m = () => {
|
|
386
|
+
};
|
|
387
|
+
const P = new Promise((d) => {
|
|
388
|
+
m = d;
|
|
389
|
+
}), M = () => {
|
|
390
|
+
document.visibilityState === "hidden" && (l = !0, m());
|
|
391
|
+
};
|
|
392
|
+
M(), document.addEventListener("visibilitychange", M);
|
|
393
|
+
const c = () => {
|
|
394
|
+
if (t.isCancelled()) throw new A();
|
|
395
|
+
if (l) throw new Z();
|
|
396
|
+
}, C = async () => {
|
|
397
|
+
const d = await Promise.race([Ce(), P.then(() => NaN)]);
|
|
398
|
+
return c(), d;
|
|
399
|
+
}, E = () => new Promise((d) => {
|
|
400
|
+
const f = s.video;
|
|
401
|
+
f.requestVideoFrameCallback ? f.requestVideoFrameCallback(() => d()) : requestAnimationFrame(() => d()), setTimeout(d, 250);
|
|
402
|
+
}), u = async (d, f) => {
|
|
403
|
+
await E(), c();
|
|
404
|
+
const b = a.apply(t.tracker.detect(s.video, d));
|
|
405
|
+
return f && (r.framesTotal++, b.faceCount >= 1 && r.framesWithFace++, b.faceCount >= 2 && r.framesWithTwoFaces++), b;
|
|
406
|
+
};
|
|
407
|
+
try {
|
|
408
|
+
let d = null;
|
|
409
|
+
if (n) {
|
|
410
|
+
const g = new ve(), v = performance.now();
|
|
411
|
+
let h = 0;
|
|
412
|
+
for (; ; ) {
|
|
413
|
+
d = await u(performance.now() - v, !1), h++;
|
|
414
|
+
const T = t.tracker.medianInferenceMs();
|
|
415
|
+
if (h >= 15 && T !== null && T > Te) return { kind: "fallback", reason: "slow_device" };
|
|
416
|
+
const { hint: y, ready: S } = g.feed(d);
|
|
417
|
+
if (s.setPrompt(Fe(y, i)), s.setOval(y === "ok" ? "ok" : "warn"), S) break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
o = new Me(t.stream), await o.start(), t.emit({ type: "recording_started" });
|
|
421
|
+
const f = e.limits.max_video_seconds * 1e3, b = [], $ = [], F = async (g) => {
|
|
422
|
+
$.push({ index: b.length, captured_at_ms: o.now(), reason: g }), b.push(await Pe(s.video, e.limits.max_still_bytes));
|
|
423
|
+
};
|
|
424
|
+
if (n) {
|
|
425
|
+
const g = performance.now() + 1e3;
|
|
426
|
+
for (; d && !Y(d) && performance.now() < g; ) d = await u(o.now(), !0);
|
|
427
|
+
await F("positioning");
|
|
428
|
+
} else s.setOval("idle"), s.setPrompt(i.lookStraight), await _(1500), c(), await F("positioning");
|
|
429
|
+
const L = [], q = [], H = e.challenges.length;
|
|
430
|
+
if (e.mode === "active") {
|
|
431
|
+
const g = new be();
|
|
432
|
+
for (const [v, h] of e.challenges.entries()) {
|
|
433
|
+
s.setProgress(H, v, v), s.setPrompt(W(h, i)), s.setOval("idle"), t.emit({ type: "challenge_started", challenge: h });
|
|
434
|
+
const T = o.now();
|
|
435
|
+
if (n) {
|
|
436
|
+
let y = J(h.type, h.direction), S = 1, z = performance.now();
|
|
437
|
+
for (; ; ) {
|
|
438
|
+
const B = await u(o.now(), !0), N = g.feed(B);
|
|
439
|
+
if (N) return { kind: "aborted", reason: N };
|
|
440
|
+
if (o.now() > f - Re) return { kind: "aborted", reason: "too_slow" };
|
|
441
|
+
const V = y.feed(B);
|
|
442
|
+
if (V === "wrong_direction") return { kind: "aborted", reason: "wrong_direction" };
|
|
443
|
+
if (V === "passed") {
|
|
444
|
+
L.push({ order: h.order, type: h.type, direction: h.direction, start_ms: T, end_ms: o.now(), passed: !0, confidence: Oe(y.confidence), timed_out: S > 1, peak: y.peak }), q.push(h.type), s.setOval("ok"), s.setPrompt(i.great), s.setProgress(H, v + 1, null), t.emit({ type: "challenge_passed", challenge: h }), await _(Ee);
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
if (performance.now() - z > h.timeout_ms) {
|
|
448
|
+
if (S > 1) return { kind: "aborted", reason: `challenge_timeout:${h.type}` };
|
|
449
|
+
S = 2, y = J(h.type, h.direction), z = performance.now(), s.setPrompt(W(h, i));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} else await ee(s, c, t.timedWindowMs, W(h, i)), L.push({ order: h.order, type: h.type, direction: h.direction, start_ms: T, end_ms: o.now(), passed: null, confidence: null, timed_out: !1, peak: null }), s.setProgress(H, v + 1, null), s.setPrompt(i.lookStraight), await _(Se), c();
|
|
453
|
+
}
|
|
454
|
+
} else await ee(s, c, De, i.holdStill);
|
|
455
|
+
const ie = await He(t, o, c, C);
|
|
456
|
+
if (c(), s.setFlash(null), s.setPrompt(i.lookStraight), n) {
|
|
457
|
+
const g = performance.now() + 2e3;
|
|
458
|
+
for (; ; ) {
|
|
459
|
+
const v = await u(o.now(), !0);
|
|
460
|
+
if (Y(v) || performance.now() > g) break;
|
|
461
|
+
}
|
|
462
|
+
} else await _(1500), c();
|
|
463
|
+
await F("return_to_centre"), c();
|
|
464
|
+
const ne = o.now(), ae = o.epoch, O = await o.stop();
|
|
465
|
+
if (o = null, O.size > e.limits.max_video_bytes) throw new p("recording_too_large");
|
|
466
|
+
const re = _e({ session: e, mode: t.mode, fallbackReason: t.fallbackReason, camera: t.stream.getVideoTracks()[0]?.getSettings() ?? {}, video: { mime: O.type, durationMs: ne, startedAtEpochMs: ae }, challenges: L, flash: ie, stills: $, tally: r });
|
|
467
|
+
return { kind: "recorded", video: O, stills: b, metadata: re, passedChallenges: q };
|
|
468
|
+
} catch (d) {
|
|
469
|
+
if (d instanceof Z) return { kind: "aborted", reason: "interrupted" };
|
|
470
|
+
throw d;
|
|
471
|
+
} finally {
|
|
472
|
+
document.removeEventListener("visibilitychange", M), o?.abort(), s.setFlash(null), s.setCountdown(null), s.setDebug(null);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async function ee(t, e, s, i) {
|
|
476
|
+
t.setPrompt(i);
|
|
477
|
+
const n = performance.now() + s;
|
|
478
|
+
for (; performance.now() < n; ) e(), t.setCountdown(Math.ceil((n - performance.now()) / 1e3)), await _(100);
|
|
479
|
+
t.setCountdown(null);
|
|
480
|
+
}
|
|
481
|
+
async function He(t, e, s, i) {
|
|
482
|
+
const n = t.session.flash, a = { enabled: !1, started_at_ms: null, segments: [], screen_brightness: null, exposure_locked: !1, skipped_reason: null };
|
|
483
|
+
if (!n.enabled) return a;
|
|
484
|
+
if (!t.allowFlash) return { ...a, skipped_reason: "disabled_by_host" };
|
|
485
|
+
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return { ...a, skipped_reason: "reduced_motion" };
|
|
486
|
+
t.emit({ type: "flash_started" }), t.view.setFlash("#000000"), t.view.setPrompt(t.strings.flashIntro), await _(Ae), s(), t.view.setPrompt(""), a.enabled = !0;
|
|
487
|
+
for (const r of n.segments) {
|
|
488
|
+
await i(), t.view.setFlash(r.color);
|
|
489
|
+
const o = await i(), l = Math.round(o - e.t0);
|
|
490
|
+
a.started_at_ms === null && (a.started_at_ms = l), await _(Math.max(0, r.duration_ms - (performance.now() - o))), a.segments.push({ color: r.color, start_ms: l, end_ms: e.now() });
|
|
491
|
+
}
|
|
492
|
+
return t.view.setFlash(null), t.emit({ type: "flash_ended" }), a;
|
|
493
|
+
}
|
|
494
|
+
function Oe(t) {
|
|
495
|
+
return Math.round(t * 100) / 100;
|
|
496
|
+
}
|
|
497
|
+
const Ue = 6e4;
|
|
498
|
+
async function We(t, e, s) {
|
|
499
|
+
const i = new FormData();
|
|
500
|
+
i.append("video", s.video, s.video.type === "video/mp4" ? "attempt.mp4" : "attempt.webm"), s.stills.forEach((a, r) => i.append("stills", a, `${r}.jpg`)), i.append("metadata", JSON.stringify(s.metadata)), i.append("token", e.token);
|
|
501
|
+
const n = t.replace(/\/+$/, "") + e.upload_url;
|
|
502
|
+
for (let a = 1; ; a++) {
|
|
503
|
+
const r = new AbortController(), o = setTimeout(() => r.abort(), Ue);
|
|
504
|
+
let l;
|
|
505
|
+
try {
|
|
506
|
+
l = await fetch(n, { method: "POST", body: i, signal: r.signal });
|
|
507
|
+
} catch (P) {
|
|
508
|
+
if (clearTimeout(o), a < 2) continue;
|
|
509
|
+
throw new p("network", String(P));
|
|
510
|
+
}
|
|
511
|
+
clearTimeout(o);
|
|
512
|
+
const m = await l.json().catch(() => ({}));
|
|
513
|
+
if (l.ok) return m;
|
|
514
|
+
throw new p(Ie(l.status), je(m) || `HTTP ${l.status}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function Ie(t) {
|
|
518
|
+
switch (t) {
|
|
519
|
+
case 401:
|
|
520
|
+
return "session_invalid";
|
|
521
|
+
case 409:
|
|
522
|
+
return "already_passed";
|
|
523
|
+
case 410:
|
|
524
|
+
return "session_expired";
|
|
525
|
+
case 413:
|
|
526
|
+
return "recording_too_large";
|
|
527
|
+
case 422:
|
|
528
|
+
return "recording_rejected";
|
|
529
|
+
case 429:
|
|
530
|
+
return "attempts_exhausted";
|
|
531
|
+
case 503:
|
|
532
|
+
return "service_unavailable";
|
|
533
|
+
default:
|
|
534
|
+
return "server_error";
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function je(t) {
|
|
538
|
+
const e = t?.detail;
|
|
539
|
+
return e && typeof e == "object" && "error" in e ? String(e.error) : typeof e == "string" ? e : null;
|
|
540
|
+
}
|
|
541
|
+
const $e = { interrupted: "interrupted", wrong_direction: "wrongDirection", face_lost: "faceLost", multiple_faces: "multipleFaces", too_slow: "tooSlow" }, qe = { camera_denied: "cameraDenied", camera_unavailable: "cameraUnavailable", unsupported_browser: "unsupportedBrowser", insecure_context: "insecureContext", network: "network", session_expired: "sessionExpired", session_invalid: "sessionExpired", service_unavailable: "serviceUnavailable", attempts_exhausted: "noAttemptsDetail" };
|
|
542
|
+
class ze {
|
|
543
|
+
constructor(e) {
|
|
544
|
+
if (this.config = e, this.view = null, this.stream = null, this.tracker = null, this.cancelled = !1, this.running = !1, this.wakeOnCancel = null, !e.baseUrl) throw new Error("PowerCredLiveness: baseUrl is required");
|
|
545
|
+
if (!e.session && !e.sessionProvider) throw new Error("PowerCredLiveness: pass session or sessionProvider");
|
|
546
|
+
this.strings = { ...oe, ...e.strings || {} };
|
|
547
|
+
}
|
|
548
|
+
async start(e) {
|
|
549
|
+
if (this.running) throw new Error("PowerCredLiveness: already running");
|
|
550
|
+
this.running = !0, this.cancelled = !1;
|
|
551
|
+
const s = this.strings, i = this.view = new ce(this.config.theme);
|
|
552
|
+
i.mount(e), i.setPrompt(s.gettingReady);
|
|
553
|
+
let n = null, a = null;
|
|
554
|
+
try {
|
|
555
|
+
if (n = this.config.session ?? await this.config.sessionProvider(), Date.parse(n.expires_at) < Date.now()) throw new p("session_expired");
|
|
556
|
+
const r = n.client_checks === "none" ? "none" : "device", o = r === "device" ? I.create(this.config.assetsUrl).catch(() => null) : Promise.resolve(null);
|
|
557
|
+
this.stream = await K(), await i.attach(this.stream), this.emit({ type: "camera_ready" }), this.tracker = await o;
|
|
558
|
+
let l = r === "device" && this.tracker ? "device" : "none", m = r === "device" && !this.tracker ? "model_unavailable" : null;
|
|
559
|
+
this.emit({ type: "mode", mode: l, fallbackReason: m });
|
|
560
|
+
const P = this.config.maxLocalRetries ?? 2;
|
|
561
|
+
let M = 0;
|
|
562
|
+
for (; ; ) {
|
|
563
|
+
const c = await Le({ session: n, view: i, strings: s, stream: this.stream, tracker: l === "device" ? this.tracker : null, mode: l, fallbackReason: m, allowFlash: this.config.flash !== !1, timedWindowMs: Math.round((this.config.timedWindowSeconds ?? 3.5) * 1e3), debug: !1, emit: (d) => this.emit(d), isCancelled: () => this.cancelled });
|
|
564
|
+
if (c.kind === "fallback") {
|
|
565
|
+
this.tracker?.close(), this.tracker = null, l = "none", m = c.reason, this.emit({ type: "mode", mode: l, fallbackReason: m });
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (a = { mode: l, fallbackReason: m, passedChallenges: c.kind === "recorded" ? c.passedChallenges : [] }, c.kind === "aborted") {
|
|
569
|
+
this.emit({ type: "attempt_aborted", reason: c.reason }), c.reason !== "interrupted" && M++;
|
|
570
|
+
const d = s[$e[c.reason] ?? "timeUp"];
|
|
571
|
+
if (M > P) return this.config.showResult !== !1 && await i.showPanel("bad", s.failedTitle, s.failedDetail), this.finish({ status: "failed", livenessSessionId: n.liveness_session_id, device: a, reason: c.reason });
|
|
572
|
+
await this.untilCancelled(i.showPanel("bad", d, "", s.tryAgain)), i.hidePanel(), await this.ensureCamera(i);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const C = c.stills[c.stills.length - 1], E = () => We(this.config.baseUrl, n, c);
|
|
576
|
+
if (this.emit({ type: "uploading", bytes: c.video.size }), this.config.waitForServer === !1) {
|
|
577
|
+
const d = n.liveness_session_id;
|
|
578
|
+
return E().then((f) => this.serverResult(f), (f) => this.emit({ type: "error", code: te(f), message: se(f) })), this.config.showResult !== !1 && await i.showPanel("ok", s.submittedTitle), this.finish({ status: "submitted", livenessSessionId: d, device: a, selfie: C });
|
|
579
|
+
}
|
|
580
|
+
await i.showPanel("working", s.checking);
|
|
581
|
+
const u = await E();
|
|
582
|
+
if (this.serverResult(u), u.status === "passed") return this.config.showResult !== !1 && await i.showPanel("ok", s.passedTitle, s.passedDetail), this.finish({ status: "passed", livenessSessionId: u.liveness_session_id, attempt: u.attempt, device: a, server: u, retryAllowed: !1, selfie: C });
|
|
583
|
+
if (u.retry_allowed) {
|
|
584
|
+
await this.untilCancelled(i.showPanel("bad", s.failedTitle, s.failedDetail, s.tryAgain)), i.hidePanel(), await this.ensureCamera(i);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
return this.config.showResult !== !1 && await i.showPanel("bad", s.failedTitle, s.noAttemptsDetail), this.finish({ status: u.status === "inconclusive" ? "inconclusive" : "failed", livenessSessionId: u.liveness_session_id, attempt: u.attempt, device: a, server: u, retryAllowed: !1, selfie: C });
|
|
588
|
+
}
|
|
589
|
+
} catch (r) {
|
|
590
|
+
if (r instanceof A) return this.finish({ status: "cancelled", livenessSessionId: n?.liveness_session_id ?? null, device: a, reason: "cancelled" });
|
|
591
|
+
const o = te(r);
|
|
592
|
+
this.emit({ type: "error", code: o, message: se(r) });
|
|
593
|
+
const l = s[qe[o] ?? "genericError"];
|
|
594
|
+
return this.config.showResult !== !1 && await i.showPanel("bad", l), this.finish({ status: "error", livenessSessionId: n?.liveness_session_id ?? null, device: a, reason: o });
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
async ensureCamera(e) {
|
|
598
|
+
this.stream?.getVideoTracks().some((s) => s.readyState === "live") || (Q(this.stream), this.stream = await K(), await e.attach(this.stream));
|
|
599
|
+
}
|
|
600
|
+
cancel() {
|
|
601
|
+
this.cancelled = !0, this.wakeOnCancel?.();
|
|
602
|
+
}
|
|
603
|
+
untilCancelled(e) {
|
|
604
|
+
return new Promise((s, i) => {
|
|
605
|
+
if (this.cancelled) return i(new A());
|
|
606
|
+
this.wakeOnCancel = () => i(new A()), e.then(() => {
|
|
607
|
+
this.wakeOnCancel = null, s();
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
destroy() {
|
|
612
|
+
this.cancel(), this.release(), this.view?.unmount(), this.view = null;
|
|
613
|
+
}
|
|
614
|
+
finish(e) {
|
|
615
|
+
return this.release(), this.running = !1, e;
|
|
616
|
+
}
|
|
617
|
+
release() {
|
|
618
|
+
Q(this.stream), this.stream = null, this.tracker?.close(), this.tracker = null;
|
|
619
|
+
}
|
|
620
|
+
serverResult(e) {
|
|
621
|
+
this.emit({ type: "server_result", result: e });
|
|
622
|
+
try {
|
|
623
|
+
this.config.onServerResult?.(e);
|
|
624
|
+
} catch {
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
emit(e) {
|
|
628
|
+
try {
|
|
629
|
+
this.config.onEvent?.(e);
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function te(t) {
|
|
635
|
+
return t instanceof p ? t.code : "unexpected";
|
|
636
|
+
}
|
|
637
|
+
function se(t) {
|
|
638
|
+
return t instanceof p ? t.message : "Unexpected error";
|
|
639
|
+
}
|
|
640
|
+
const Be = "0.1.1";
|
|
641
|
+
export {
|
|
642
|
+
ze as PowerCredLiveness,
|
|
643
|
+
Be as VERSION,
|
|
644
|
+
oe as defaultStrings
|
|
645
|
+
};
|
package/dist/sdk.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PowerCredLiveness: mounts the check into a container, runs it, uploads, and resolves with the
|
|
3
|
+
* outcome. The server's verdict is the only one that counts; the device's checks only guide the
|
|
4
|
+
* user and catch problems early.
|
|
5
|
+
*/
|
|
6
|
+
import type { LivenessConfig, LivenessResult } from './types';
|
|
7
|
+
export declare class PowerCredLiveness {
|
|
8
|
+
private readonly config;
|
|
9
|
+
private view;
|
|
10
|
+
private stream;
|
|
11
|
+
private tracker;
|
|
12
|
+
private cancelled;
|
|
13
|
+
private running;
|
|
14
|
+
private wakeOnCancel;
|
|
15
|
+
private readonly strings;
|
|
16
|
+
constructor(config: LivenessConfig);
|
|
17
|
+
/** Runs the check inside `container`. Resolves once; never rejects for user-facing problems. */
|
|
18
|
+
start(container: HTMLElement): Promise<LivenessResult>;
|
|
19
|
+
/** Phones often stop the camera while the browser is in the background: reopen it if so. */
|
|
20
|
+
private ensureCamera;
|
|
21
|
+
/** Stops a running check (also while it waits on a "Try again" button); start() then resolves "cancelled". */
|
|
22
|
+
cancel(): void;
|
|
23
|
+
/** Waits for a user action, unless the host cancels first. */
|
|
24
|
+
private untilCancelled;
|
|
25
|
+
/** Releases the camera and removes the UI. */
|
|
26
|
+
destroy(): void;
|
|
27
|
+
private finish;
|
|
28
|
+
private release;
|
|
29
|
+
private serverResult;
|
|
30
|
+
private emit;
|
|
31
|
+
}
|