@seontechnologies/seon-id-verification
Version:
An advanced SDK featuring web components for natural person identification through document scanning, facial recognition, hand gesture, and face turning detection, designed for secure and efficient user verification.
354 lines (353 loc) • 12.7 kB
JavaScript
import { o as B, r as ee, t as K, p as te, q as re, j as x, v as ne, a as z, l as C } from "./index-BbjzUPso.mjs";
import { C as oe, a as se } from "./CameraCard-3JuZsKFO.mjs";
import { s as Y, X as ie, E as ce, I as ae, U as le, S as ue, L as de, a as G } from "./UploadLivenessVideo-BToA_mwa.mjs";
import { useRef as v, useState as I, useEffect as F, useCallback as S, useMemo as fe } from "react";
import { S as me } from "./SelectInput-qpWhDzf1.mjs";
import { u as he } from "./useQuery-Bnd3TQtL.mjs";
const pe = async () => (await ne()).map((t) => ({
value: t.deviceId,
text: t.label
})), ge = ({ onDataLoaded: e }) => {
const r = v(null), [t, o] = I(null), [n, s] = I(null), { data: i, error: c } = he({
queryKey: ["cameraList"],
initialData: [],
queryFn: pe
}), a = v(!1);
F(() => {
if (t)
return () => {
t.getTracks().forEach((m) => {
m.stop();
}), B();
};
}, [t]), F(() => {
}, [c]);
const l = S(async (m) => {
if (!ee()) {
K({ ERROR: "Camera access denied" });
return;
}
a.current = !0;
const b = {
video: {
deviceId: {
exact: m || (await te()).deviceId
}
}
};
navigator.mediaDevices.getUserMedia(b).then(async (w) => {
if (!n)
return;
const _ = await re(w);
o(_.stream), n.srcObject = _.stream;
}).catch((w) => {
console.error(w);
});
}, [n]);
F(() => {
if (!n) {
a.current = !1;
return;
}
a.current || l();
const m = () => {
e == null || e();
};
return n.addEventListener("loadeddata", m), () => {
n.removeEventListener("loadeddata", m);
};
}, [l, e, n]);
const g = x.jsxs(x.Fragment, { children: [i.length <= 1 ? null : x.jsx("div", { className: "tw-absolute tw-z-[9999] tw-top-[-36px] hover:tw-top-1 tw-opacity-100 hover:tw-opacity-75 tw-transition-all tw-duration-300", children: x.jsx(me, { idPrefix: "liveness-camera", onChange: (m) => {
n && l(m.value);
}, options: i, placeholder: i[0], chevronPosition: "bottom" }) }), x.jsx("video", { id: "liveness-webcam", ref: s, className: "tw-bg-black tw-object-cover tw-object-center tw-max-w-none tw-w-full tw-h-full sm:tw-h-auto", autoPlay: !0, playsInline: !0 }), !1] });
return {
videoMarkupRef: r,
videoComponent: g,
videoStream: t,
videoElement: n
};
}, ye = [
"video/mp4",
"video/webm",
"video/webm;codecs=vp8",
"video/webm;codecs=daala",
"video/webm;codecs=h264"
], H = ye.find((e) => MediaRecorder.isTypeSupported(e)) || "video/webm", we = ({ stream: e, onRecordingFinished: r }) => {
const t = v(null), o = v([]), n = v(!1);
F(() => {
if (!e)
return;
const c = (g) => {
g.data.size > 0 && o.current.push(g.data);
}, a = async () => {
const g = new Blob(o.current, {
type: H
});
o.current = [], n.current && r(g);
}, l = new MediaRecorder(e, {
mimeType: H
});
return l.addEventListener("dataavailable", c), l.addEventListener("stop", a), t.current = l, () => {
l.removeEventListener("dataavailable", c), l.removeEventListener("stop", a), l.stop();
};
}, [e, r]);
const s = S(() => !t.current || t.current.state === "recording" ? !1 : (n.current = !1, o.current = [], t.current.start(), !0), []), i = S((c) => !t.current || t.current.state === "inactive" ? !1 : (n.current = c, t.current.stop(), !0), []);
return {
startRecording: s,
stopRecording: i
};
}, xe = (e) => {
const r = Math.sqrt(e.x ** 2 + e.y ** 2);
if (r === 0)
throw new Error("Direction vector cannot be zero");
return { x: e.x / r, y: e.y / r };
}, ve = (e, r) => ({
x: r.x - e.x,
y: r.y - e.y
}), _e = (e, r) => {
const t = {
x: r.x - e.x,
y: r.y - e.y
};
return {
x: -t.y,
y: t.x
};
}, ke = (e, r, t) => {
const o = t.x, n = t.y;
return (o * (e.x - r.x) + n * (e.y - r.y)) / Math.sqrt(o * o + n * n);
}, V = (e, r) => Math.sqrt(Math.pow(e.x - r.x, 2) + Math.pow(e.y - r.y, 2)), P = (e, r) => {
const t = {
x: r.x - e.x,
y: r.y - e.y
};
return Math.atan2(t.y, t.x) * 180 / Math.PI;
}, Oe = (e, r, t) => {
const o = xe(r), n = { x: t.x - e.x, y: t.y - e.y }, s = n.x * o.x + n.y * o.y;
return {
x: e.x + o.x * s,
y: e.y + o.y * s
};
}, be = ({ boundingBox: e, videoHeight: r, videoWidth: t }) => {
const o = e.width / t, n = e.height / r, s = Math.max(o, n), i = e.originX / t, c = e.originY / r, a = 1 - (i + o), l = 1 - (c + n), { faceTopEdgeMin: g, faceRightEdgeMin: m, faceBottomEdgeMin: b, faceLeftEdgeMin: w, minFaceArea: _ } = z.config.livenessCheckSettings.thresholdConfiguration || {}, y = {
top: g ?? 0.3,
right: m ?? 0.2,
bottom: b ?? 0.1,
left: w ?? 0.2,
size: _ ?? 0.4
}, h = c > y.top, D = i > y.right, d = l > y.bottom, M = a > y.left, k = s > y.size;
return {
top: c,
bottom: l,
right: i,
left: a,
width: o,
height: n,
size: s,
topOk: h,
rightOk: D,
bottomOk: d,
leftOk: M,
sizeOk: k,
overallOk: h && D && d && M && k
};
}, Me = ({ keypoints: e }) => {
const [r, t, o, n, s, i] = e, c = ve(s, i), a = Oe(s, c, o), l = V(s, i), g = V(s, a), m = g ? (g / l - 0.5) * 2 * 45 : 0, { rollAbsMaxDeg: b, pitchMin: w, pitchMax: _, yawAbsMaxDeg: y } = z.config.livenessCheckSettings.thresholdConfiguration || {}, h = {
roll: b ?? 10,
pitchMin: w ?? -0.12,
pitchMax: _ ?? 0.45,
yaw: y ?? 15
}, D = !!g && Math.abs(m) < h.yaw, d = V(s, i), M = _e(s, i), k = ke(o, s, M), u = d ? k / d : 0, O = d > 0 && u > h.pitchMin && u < h.pitchMax, R = P(r, t), A = P(o, n) - 90, U = P(s, i), L = (R + A + U) / 3, $ = Math.abs(L) < h.roll;
return {
eyeLeft: r,
eyeRight: t,
nose: o,
mouth: n,
earLeft: s,
earRight: i,
yawReference: a,
yaw: m,
yawOk: D,
pitch: u,
pitchOk: O,
roll: L,
rollOk: $,
overallOk: D && O && $
};
}, De = 10, T = ce.mediaPipe.CFaceDetectionConfig, Ee = ({ setDetectionInfo: e, videoMarkupRef: r, videoElement: t }) => {
const o = v(null), n = v(0), s = v(null), i = v(null), c = v(null), [a, l] = I(!1), [g, m] = I(!1), [b, w] = I(null), [_, y] = I(!1), h = S(async () => {
var L, $;
if (i.current && cancelAnimationFrame(i.current), c.current && clearTimeout(c.current), !(t != null && t.srcObject) || !s.current || !(s.current instanceof Y)) {
c.current = setTimeout(() => h(), 1e3);
return;
}
const d = performance.now(), M = s.current.detectForVideo(t, d).detections;
let k = !1, u = "", O = "position_face";
if (M.length === 1) {
const j = M[0], { keypoints: N, boundingBox: q } = j;
if (q) {
const p = t.videoWidth, E = t.videoHeight, f = be({
boundingBox: q,
videoWidth: p,
videoHeight: E
});
u += `Position: ${f.overallOk ? "" : "!!!"}
`, u += ` top: ${(f.top * 100).toFixed(2)}% ${f.topOk ? "" : "!!!"}
`, u += ` bottom: ${(f.bottom * 100).toFixed(2)}% ${f.bottomOk ? "" : "!!!"}
`, u += ` right: ${(f.right * 100).toFixed(2)}% ${f.rightOk ? "" : "!!!"}
`, u += ` left: ${(f.left * 100).toFixed(2)}% ${f.leftOk ? "" : "!!!"}
`, u += ` size: ${(f.size * 100).toFixed(2)}% ${f.sizeOk ? "" : "!!!"}
`, ($ = (L = r.current) == null ? void 0 : L.querySelector("#face")) == null || $.setAttribute("style", `top: ${f.top * 100}%; right: ${f.right * 100}%; width: ${f.width * 100}%; height: ${f.height * 100}%;`), f.overallOk || (O = f.sizeOk ? "center_face" : "move_closer", k = !0);
} else
u += `No bounding box detected
`;
if (N.length === 6) {
const p = Me({
keypoints: N
}), E = (f, { x: Q, y: J }, Z = "red") => {
var W, X;
(X = (W = r.current) == null ? void 0 : W.querySelector(f)) == null || X.setAttribute("style", `top: ${J * 100}%; right: ${Q * 100}%; background: ${Z};`);
};
E("#left_eye", p.eyeLeft), E("#right_eye", p.eyeRight), E("#nose", p.nose), E("#mouth", p.mouth), E("#left_ear", p.earLeft), E("#right_ear", p.earRight), E("#yaw_reference", p.yawReference, "green"), u += `
Rotation: ${p.overallOk ? "" : "!!!"}
`, u += ` yaw: ${p.yaw.toFixed(2)}° ${p.yawOk ? "" : "!!!"}
`, u += ` pitch: ${(p.pitch * 100).toFixed(2)}% ${p.pitchOk ? "" : "!!!"}
`, u += ` roll: ${p.roll.toFixed(2)}° ${p.rollOk ? "" : "!!!"}
`, p.overallOk || (O = "straighten_face", k = !0);
} else
u += `No keypoints detected
`;
} else
u = M.length ? "Multiple faces detected" : "No face detected", k = !0;
e(u), w(k ? O : "hold"), k ? (l(!1), n.current = 0, o.current = null) : (n.current = n.current + 1, n.current === De && (l(!0), o.current = d));
const R = z.config.promptLength;
if ((o.current ? d - o.current : 0) > R * 1e3) {
i.current && cancelAnimationFrame(i.current), m(!0);
return;
}
i.current = window.requestAnimationFrame(() => h());
}, [t, e, r]);
return F(() => (ie.forVisionTasks(T.forVisionTasks).then((d) => Y.createFromOptions(d, {
baseOptions: {
modelAssetPath: T.modelAssetPath,
delegate: "GPU"
},
minDetectionConfidence: T.minDetectionConfidence,
runningMode: "VIDEO"
})).then((d) => {
s.current = d, y(!0);
}), () => {
var d;
(d = s.current) == null || d.close(), s.current = null;
}), []), {
startDetector: S(() => {
h();
}, [h]),
isFaceDetected: a,
isCompleted: g,
instruction: b,
initialized: _
};
}, Ce = ({ onSucess: e, onFail: r }) => {
const [t, o] = I(!1), n = S(async (s, i = []) => {
o(!0);
try {
if (!s)
throw new Error("Video blob is null");
if (!((s == null ? void 0 : s.size) || 0))
throw new Error("Video blob size is 0");
const a = await new ae(i).getUploadUrl();
if (!(a != null && a.ok))
throw new Error("Error getting video upload url");
if ((await new le({
url: a.videoUploadUrl,
video: s,
mimeType: s.type
}).startUploadLivenessVideo()).ok === !1)
throw new Error("Error uploading video");
e();
} catch (c) {
r(c);
} finally {
o(!1);
}
}, [e, r]);
return {
isUploading: t,
uploadVideo: n
};
}, Re = (e) => {
K({
title: C.error_liveness_upload_title,
description: C.error_liveness_upload_description,
btnText: C.restart_button,
ERROR: e
});
}, Pe = ({ onNext: e, onBack: r }) => {
const t = v(null), o = v(null), n = S((R) => {
o.current && (o.current.innerText = R);
}, []), s = S(() => {
var R;
n("Camera loaded"), (R = t.current) == null || R.call(t);
}, [n]), { isUploading: i, uploadVideo: c } = Ce({
onFail: Re,
onSucess: e
}), { videoComponent: a, videoStream: l, videoElement: g, videoMarkupRef: m } = ge({
onDataLoaded: s
}), { startRecording: b, stopRecording: w } = we({
onRecordingFinished: c,
stream: l
}), { startDetector: _, isCompleted: y, isFaceDetected: h, instruction: D, initialized: d } = Ee({
videoMarkupRef: m,
setDetectionInfo: n,
videoElement: g
}), M = fe(() => {
switch (D) {
case "center_face":
return {
text: C.liveness_check_action_center_face
};
case "move_closer":
return {
text: C.liveness_check_action_move_closer
};
case "straighten_face":
return {
text: C.liveness_check_action_straighten_face
};
case "hold":
return {
text: C.liveness_check_action_hold
};
}
return {
icon: x.jsx(ue, { width: 48, hanging: 48 }),
text: C.liveness_check_action_position_face
};
}, [D]);
F(() => (t.current = _, () => {
t.current = null;
}), [_]), F(() => {
y ? w(!0) : h ? b() : w(!1);
}, [h, y, b, w]);
const O = [
{
animateTo: {
startAnimationTo: h,
strokeDasharray: "0",
strokeWidth: "4"
},
outline: "idle",
dimension: "face-only"
},
{
outline: "countdown",
dimension: "face-only",
startAnimation: h
}
][h || y ? 1 : 0];
return x.jsxs(x.Fragment, { children: [x.jsx(oe, { className: `tw-w-full tw-h-full sm:tw-h-auto ${i && "tw-opacity-0"}`, children: x.jsxs(se, { idPrefix: "selfie-liveness-camera", onBack: r, title: C.liveness_check_selfie_flow_title, instructions: M, children: [x.jsxs(de, { dimension: O.dimension, outline: O.outline, startAnimation: O.startAnimation, animateTo: O.animateTo, isSuccessful: y, children: [a, !d && x.jsx(G, { isUploading: !0 })] }), !1] }) }), x.jsx(G, { isUploading: i })] });
};
export {
Pe as default
};