UNPKG

@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
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 };