vesant-sdk 1.7.0-dev.9b89b96 → 1.7.0-dev.a85efc5
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/dist/{client-ZNdnpWe7.d.mts → client-B0qhE2kr.d.mts} +1 -1
- package/dist/{client-DoMSYMMR.d.ts → client-DF7hlMEz.d.ts} +13 -0
- package/dist/{client-DMIRx7Tu.d.mts → client-DrjgZoH_.d.mts} +13 -0
- package/dist/{client-C3DCmGe9.d.ts → client-DtH2RLuy.d.ts} +1 -1
- package/dist/compliance/index.d.mts +2 -2
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +0 -3
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +0 -3
- package/dist/compliance/index.mjs.map +1 -1
- package/dist/geolocation/index.d.mts +2 -2
- package/dist/geolocation/index.d.ts +2 -2
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -8
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.js +4 -4
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +4 -4
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +56 -5
- package/dist/kyc/index.d.ts +56 -5
- package/dist/kyc/index.js +4 -4
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +4 -4
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +4 -2
- package/dist/react.d.ts +4 -2
- package/dist/react.js +125 -77
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +125 -77
- package/dist/react.mjs.map +1 -1
- package/dist/tax/index.d.mts +18 -1
- package/dist/tax/index.d.ts +18 -1
- package/dist/tax/index.js +4 -1
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +4 -1
- package/dist/tax/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -883,8 +883,90 @@ var Camera = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i
|
|
|
883
883
|
var Done = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik04LjUgMTIuNUwxMC41IDE0LjVMMTUuNSA5LjUiIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPg0KPHBhdGggZD0iTTcgMy4zMzc4MkM4LjQ3MDg3IDIuNDg2OTcgMTAuMTc4NiAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJDMjIgMTcuNTIyOCAxNy41MjI4IDIyIDEyIDIyQzYuNDc3MTUgMjIgMiAxNy41MjI4IDIgMTJDMiAxMC4xNzg2IDIuNDg2OTcgOC40NzA4NyAzLjMzNzgyIDciIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPg0KPC9zdmc+";
|
|
884
884
|
var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNC41IDkuNTAwMDJMOS41IDE0LjVNOS40OTk5OCA5LjVMMTQuNSAxNC41IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjxwYXRoIGQ9Ik03IDMuMzM3ODJDOC40NzA4NyAyLjQ4Njk3IDEwLjE3ODYgMiAxMiAyQzE3LjUyMjggMiAyMiA2LjQ3NzE1IDIyIDEyQzIyIDE3LjUyMjggMTcuNTIyOCAyMiAxMiAyMkM2LjQ3NzE1IDIyIDIgMTcuNTIyOCAyIDEyQzIgMTAuMTc4NiAyLjQ4Njk3IDguNDcwODcgMy4zMzc4MiA3IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjwvc3ZnPg==";
|
|
885
885
|
|
|
886
|
+
// src/kyc/faceDetection.ts
|
|
887
|
+
var FACE_MESSAGES = {
|
|
888
|
+
idle: "Initializing camera...",
|
|
889
|
+
loading: "Loading face detection...",
|
|
890
|
+
scanning: "Position your face in the circle",
|
|
891
|
+
too_far: "Face too far. Please move closer to the camera",
|
|
892
|
+
too_close: "Too close. Please move back a little",
|
|
893
|
+
off_center: "Center your face in the circle",
|
|
894
|
+
ok: "Looking great! Tap the button to capture"
|
|
895
|
+
};
|
|
896
|
+
var FACE_COLORS = {
|
|
897
|
+
idle: "#9ca3af",
|
|
898
|
+
loading: "#3b82f6",
|
|
899
|
+
scanning: "#3b82f6",
|
|
900
|
+
too_far: "#ef4444",
|
|
901
|
+
too_close: "#f59e0b",
|
|
902
|
+
off_center: "#f59e0b",
|
|
903
|
+
ok: "#10b981"
|
|
904
|
+
};
|
|
905
|
+
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
906
|
+
function mapDetectionsToFaceStatus(detections) {
|
|
907
|
+
if (detections.length === 0) return "scanning";
|
|
908
|
+
const bb = detections[0].boundingBox;
|
|
909
|
+
if (bb.width < 0.18) return "too_far";
|
|
910
|
+
if (bb.width > 0.72) return "too_close";
|
|
911
|
+
if (bb.xCenter < 0.28 || bb.xCenter > 0.72 || bb.yCenter < 0.28 || bb.yCenter > 0.72) {
|
|
912
|
+
return "off_center";
|
|
913
|
+
}
|
|
914
|
+
return "ok";
|
|
915
|
+
}
|
|
916
|
+
function isCaptureDisabled(ready, detectionActive, faceStatus) {
|
|
917
|
+
return !ready || detectionActive && faceStatus !== "ok";
|
|
918
|
+
}
|
|
919
|
+
function resolveMediaPipeBase(base) {
|
|
920
|
+
if (!base) return MEDIAPIPE_BASE;
|
|
921
|
+
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
922
|
+
}
|
|
923
|
+
var LIVENESS_SAMPLE_LIMIT = 20;
|
|
924
|
+
function buildLivenessArtifact(samples) {
|
|
925
|
+
if (samples.length === 0) return void 0;
|
|
926
|
+
return JSON.stringify({
|
|
927
|
+
version: 1,
|
|
928
|
+
source: "mediapipe-face-detection",
|
|
929
|
+
samples: samples.slice(-LIVENESS_SAMPLE_LIMIT)
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
var mediapipeLoaderPromises = /* @__PURE__ */ new Map();
|
|
933
|
+
function loadMediaPipeFaceDetection(assetBasePath) {
|
|
934
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
935
|
+
return Promise.reject(new Error("MediaPipe requires a browser environment"));
|
|
936
|
+
}
|
|
937
|
+
const w = window;
|
|
938
|
+
if (w.FaceDetection) {
|
|
939
|
+
return Promise.resolve(w.FaceDetection);
|
|
940
|
+
}
|
|
941
|
+
const src = `${resolveMediaPipeBase(assetBasePath)}/face_detection.js`;
|
|
942
|
+
const existing = mediapipeLoaderPromises.get(src);
|
|
943
|
+
if (existing) return existing;
|
|
944
|
+
const loader = new Promise((resolve, reject) => {
|
|
945
|
+
const script = document.createElement("script");
|
|
946
|
+
script.src = src;
|
|
947
|
+
script.async = true;
|
|
948
|
+
script.crossOrigin = "anonymous";
|
|
949
|
+
script.onload = () => {
|
|
950
|
+
if (w.FaceDetection) {
|
|
951
|
+
resolve(w.FaceDetection);
|
|
952
|
+
} else {
|
|
953
|
+
mediapipeLoaderPromises.delete(src);
|
|
954
|
+
reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
script.onerror = () => {
|
|
958
|
+
mediapipeLoaderPromises.delete(src);
|
|
959
|
+
reject(new Error("Failed to load MediaPipe face_detection script"));
|
|
960
|
+
};
|
|
961
|
+
document.head.appendChild(script);
|
|
962
|
+
});
|
|
963
|
+
mediapipeLoaderPromises.set(src, loader);
|
|
964
|
+
return loader;
|
|
965
|
+
}
|
|
966
|
+
|
|
886
967
|
// src/kyc/FaceCaptureModal.tsx
|
|
887
968
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
969
|
+
var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
|
|
888
970
|
var LIVENESS_MESSAGES = [
|
|
889
971
|
"Position your face inside the circle",
|
|
890
972
|
"Make sure your face is well lit",
|
|
@@ -915,7 +997,8 @@ function FaceCaptureModal({
|
|
|
915
997
|
onComplete,
|
|
916
998
|
onCancel,
|
|
917
999
|
defaultDevice,
|
|
918
|
-
renderQR
|
|
1000
|
+
renderQR,
|
|
1001
|
+
mediapipeAssetBasePath
|
|
919
1002
|
}) {
|
|
920
1003
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
921
1004
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
@@ -932,15 +1015,24 @@ function FaceCaptureModal({
|
|
|
932
1015
|
const [captureMode, setCaptureMode] = useState("idle");
|
|
933
1016
|
const [capturedPreview, setCapturedPreview] = useState(null);
|
|
934
1017
|
const [capturedBase64, setCapturedBase64] = useState(null);
|
|
1018
|
+
const [capturedArtifact, setCapturedArtifact] = useState(null);
|
|
935
1019
|
const [livenessMessage, setLivenessMessage] = useState(LIVENESS_MESSAGES[0]);
|
|
936
1020
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
937
1021
|
const cancelledRef = useRef(false);
|
|
938
1022
|
const submittingRef = useRef(false);
|
|
1023
|
+
useEffect(() => {
|
|
1024
|
+
const timer = setTimeout(() => {
|
|
1025
|
+
setStage(
|
|
1026
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1027
|
+
);
|
|
1028
|
+
}, SESSION_EXPIRY_MS);
|
|
1029
|
+
return () => clearTimeout(timer);
|
|
1030
|
+
}, [session.token]);
|
|
939
1031
|
useEffect(() => {
|
|
940
1032
|
if (stage.kind !== "qr") return;
|
|
941
1033
|
let stopped = false;
|
|
942
1034
|
const intervalMs = 2e3;
|
|
943
|
-
const deadline = Date.now() +
|
|
1035
|
+
const deadline = Date.now() + SESSION_EXPIRY_MS;
|
|
944
1036
|
let mobileSeen = stage.mobileConnected;
|
|
945
1037
|
const tick = async () => {
|
|
946
1038
|
if (stopped || cancelledRef.current) return;
|
|
@@ -1021,7 +1113,10 @@ function FaceCaptureModal({
|
|
|
1021
1113
|
const data = await client.submitEventBasedFaceVerificationSession({
|
|
1022
1114
|
token: session.token,
|
|
1023
1115
|
reference: ref,
|
|
1024
|
-
proof
|
|
1116
|
+
proof,
|
|
1117
|
+
// undefined when detection wasn't running for this capture —
|
|
1118
|
+
// JSON.stringify drops it, so the field is absent on the wire.
|
|
1119
|
+
liveness_artifact: capturedArtifact ?? void 0
|
|
1025
1120
|
});
|
|
1026
1121
|
return { ok: true, data };
|
|
1027
1122
|
} catch (err2) {
|
|
@@ -1067,16 +1162,18 @@ function FaceCaptureModal({
|
|
|
1067
1162
|
submittingRef.current = false;
|
|
1068
1163
|
}
|
|
1069
1164
|
};
|
|
1070
|
-
const handleCameraCapture = (dataUrl) => {
|
|
1165
|
+
const handleCameraCapture = (dataUrl, livenessArtifact) => {
|
|
1071
1166
|
const raw = dataUrl.split(",")[1] ?? "";
|
|
1072
1167
|
setCapturedPreview(dataUrl);
|
|
1073
1168
|
setCapturedBase64(raw);
|
|
1169
|
+
setCapturedArtifact(livenessArtifact ?? null);
|
|
1074
1170
|
setCaptureMode("preview");
|
|
1075
1171
|
};
|
|
1076
1172
|
const handleCameraCancel = () => setCaptureMode("idle");
|
|
1077
1173
|
const handleRetake = () => {
|
|
1078
1174
|
setCapturedPreview(null);
|
|
1079
1175
|
setCapturedBase64(null);
|
|
1176
|
+
setCapturedArtifact(null);
|
|
1080
1177
|
setCaptureMode("live");
|
|
1081
1178
|
};
|
|
1082
1179
|
const handleConfirmSubmit = () => {
|
|
@@ -1148,14 +1245,15 @@ function FaceCaptureModal({
|
|
|
1148
1245
|
{
|
|
1149
1246
|
onCapture: handleCameraCapture,
|
|
1150
1247
|
onCancel: handleCameraCancel,
|
|
1151
|
-
message: livenessMessage
|
|
1248
|
+
message: livenessMessage,
|
|
1249
|
+
assetBasePath: mediapipeAssetBasePath
|
|
1152
1250
|
}
|
|
1153
1251
|
), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1154
1252
|
}
|
|
1155
1253
|
if (captureMode === "preview" && capturedPreview) {
|
|
1156
1254
|
return /* @__PURE__ */ React.createElement("div", { style: contentStyle }, declinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "Looks good? Submit this selfie or retake it."), /* @__PURE__ */ React.createElement("div", { style: previewBoxStyle }, /* @__PURE__ */ React.createElement("img", { src: capturedPreview, alt: "Captured selfie preview", style: previewImgStyle })), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleConfirmSubmit }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, "Submit")), /* @__PURE__ */ React.createElement("button", { style: secondaryButtonStyle, onClick: handleRetake }, "Retake"), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1157
1255
|
}
|
|
1158
|
-
return /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React.createElement("div", { style: circleStyle }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React.createElement("div", { style: badgeStyle }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, declinedReason ? "Try
|
|
1256
|
+
return /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React.createElement("div", { style: circleStyle }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React.createElement("div", { style: badgeStyle }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, declinedReason ? "Try Again" : "Open Camera")), hasMethodChoice && /* @__PURE__ */ React.createElement(
|
|
1159
1257
|
"button",
|
|
1160
1258
|
{
|
|
1161
1259
|
style: secondaryButtonStyle,
|
|
@@ -1167,10 +1265,10 @@ function FaceCaptureModal({
|
|
|
1167
1265
|
const renderSubmitting = () => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "Verifying your photo\u2026"), /* @__PURE__ */ React.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
|
|
1168
1266
|
const renderAccepted = (result) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: successCircleStyle }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Verified"), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "Your identity has been confirmed. You can continue."), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Continue")));
|
|
1169
1267
|
const renderDeclined = (result) => /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, "Retake")), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
|
|
1170
|
-
const renderMaxAttempts = (result) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Maximum Attempts Reached"), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, "For your security, your account has been temporarily restricted", result.data.freeze_duration_minutes ? ` for ${result.data.freeze_duration_minutes} minutes` : "", ". Please contact support if you need immediate help."), result.data?.enforce_logout && /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
|
|
1268
|
+
const renderMaxAttempts = (result) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Maximum Attempts Reached"), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, "For your security, your account has been temporarily restricted", result.data.freeze_duration_minutes ? ` for ${result.data.freeze_duration_minutes} minutes` : "", ". Please contact support if you need immediate help."), result.data?.enforce_logout && /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), result.data?.block && /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, "This action has been blocked for security reasons."), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
|
|
1171
1269
|
const renderError = (message) => {
|
|
1172
1270
|
const sessionExpired = /session\s+expired/i.test(message);
|
|
1173
|
-
return /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something
|
|
1271
|
+
return /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something Went Wrong"), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, sessionExpired ? "Your verification session is no longer valid. Please start a new verification from the beginning." : message), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, !sessionExpired && /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try Again"), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1174
1272
|
};
|
|
1175
1273
|
const body = (() => {
|
|
1176
1274
|
switch (stage.kind) {
|
|
@@ -1379,59 +1477,10 @@ var attemptsTextStyle = {
|
|
|
1379
1477
|
};
|
|
1380
1478
|
var footerStyle = { padding: "12px 24px 16px", borderTop: "1px solid #f3f4f6" };
|
|
1381
1479
|
var footerTextStyle = { margin: 0, fontSize: 12, color: "#9ca3af", textAlign: "center" };
|
|
1382
|
-
|
|
1383
|
-
idle: "Initializing camera...",
|
|
1384
|
-
loading: "Loading face detection...",
|
|
1385
|
-
scanning: "Position your face in the circle",
|
|
1386
|
-
too_far: "Face too far. Please move closer to the camera",
|
|
1387
|
-
too_close: "Too close. Please move back a little",
|
|
1388
|
-
off_center: "Center your face in the circle",
|
|
1389
|
-
ok: "Looking great! Tap the button to capture"
|
|
1390
|
-
};
|
|
1391
|
-
var FACE_COLORS = {
|
|
1392
|
-
idle: "#9ca3af",
|
|
1393
|
-
loading: "#3b82f6",
|
|
1394
|
-
scanning: "#3b82f6",
|
|
1395
|
-
too_far: "#ef4444",
|
|
1396
|
-
too_close: "#f59e0b",
|
|
1397
|
-
off_center: "#f59e0b",
|
|
1398
|
-
ok: "#10b981"
|
|
1399
|
-
};
|
|
1400
|
-
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
1401
|
-
var mediapipeLoaderPromise = null;
|
|
1402
|
-
function loadMediaPipeFaceDetection() {
|
|
1403
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1404
|
-
return Promise.reject(new Error("MediaPipe requires a browser environment"));
|
|
1405
|
-
}
|
|
1406
|
-
const w = window;
|
|
1407
|
-
if (w.FaceDetection) {
|
|
1408
|
-
return Promise.resolve(w.FaceDetection);
|
|
1409
|
-
}
|
|
1410
|
-
if (mediapipeLoaderPromise) return mediapipeLoaderPromise;
|
|
1411
|
-
mediapipeLoaderPromise = new Promise((resolve, reject) => {
|
|
1412
|
-
const script = document.createElement("script");
|
|
1413
|
-
script.src = `${MEDIAPIPE_BASE}/face_detection.js`;
|
|
1414
|
-
script.async = true;
|
|
1415
|
-
script.crossOrigin = "anonymous";
|
|
1416
|
-
script.onload = () => {
|
|
1417
|
-
if (w.FaceDetection) {
|
|
1418
|
-
resolve(w.FaceDetection);
|
|
1419
|
-
} else {
|
|
1420
|
-
mediapipeLoaderPromise = null;
|
|
1421
|
-
reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
|
|
1422
|
-
}
|
|
1423
|
-
};
|
|
1424
|
-
script.onerror = () => {
|
|
1425
|
-
mediapipeLoaderPromise = null;
|
|
1426
|
-
reject(new Error("Failed to load MediaPipe face_detection script"));
|
|
1427
|
-
};
|
|
1428
|
-
document.head.appendChild(script);
|
|
1429
|
-
});
|
|
1430
|
-
return mediapipeLoaderPromise;
|
|
1431
|
-
}
|
|
1432
|
-
function LiveCamera({ onCapture, onCancel, message }) {
|
|
1480
|
+
function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
|
|
1433
1481
|
const videoRef = useRef(null);
|
|
1434
1482
|
const streamRef = useRef(null);
|
|
1483
|
+
const samplesRef = useRef([]);
|
|
1435
1484
|
const [ready, setReady] = useState(false);
|
|
1436
1485
|
const [error, setError] = useState(null);
|
|
1437
1486
|
const [faceStatus, setFaceStatus] = useState("idle");
|
|
@@ -1486,31 +1535,29 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1486
1535
|
let detector = null;
|
|
1487
1536
|
setFaceStatus("loading");
|
|
1488
1537
|
setFaceDetectionFailed(false);
|
|
1538
|
+
samplesRef.current = [];
|
|
1489
1539
|
(async () => {
|
|
1490
1540
|
try {
|
|
1491
|
-
const FaceDetection = await loadMediaPipeFaceDetection();
|
|
1541
|
+
const FaceDetection = await loadMediaPipeFaceDetection(assetBasePath);
|
|
1492
1542
|
if (cancelled) return;
|
|
1543
|
+
const assetBase = resolveMediaPipeBase(assetBasePath);
|
|
1493
1544
|
const d = new FaceDetection({
|
|
1494
|
-
locateFile: (f) => `${
|
|
1545
|
+
locateFile: (f) => `${assetBase}/${f}`
|
|
1495
1546
|
});
|
|
1496
1547
|
d.setOptions({ model: "short", minDetectionConfidence: 0.5 });
|
|
1497
1548
|
d.onResults((results) => {
|
|
1498
1549
|
if (cancelled) return;
|
|
1499
1550
|
const detections = results.detections ?? [];
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
next = "off_center";
|
|
1509
|
-
} else {
|
|
1510
|
-
next = "ok";
|
|
1511
|
-
}
|
|
1551
|
+
const status = mapDetectionsToFaceStatus(detections);
|
|
1552
|
+
samplesRef.current.push({
|
|
1553
|
+
t: Date.now(),
|
|
1554
|
+
status,
|
|
1555
|
+
...detections.length > 0 ? { boundingBox: detections[0].boundingBox } : {}
|
|
1556
|
+
});
|
|
1557
|
+
if (samplesRef.current.length > LIVENESS_SAMPLE_LIMIT) {
|
|
1558
|
+
samplesRef.current.shift();
|
|
1512
1559
|
}
|
|
1513
|
-
setFaceStatus(
|
|
1560
|
+
setFaceStatus(status);
|
|
1514
1561
|
});
|
|
1515
1562
|
await d.initialize();
|
|
1516
1563
|
if (cancelled) return;
|
|
@@ -1546,7 +1593,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1546
1593
|
} catch {
|
|
1547
1594
|
}
|
|
1548
1595
|
};
|
|
1549
|
-
}, [ready]);
|
|
1596
|
+
}, [ready, assetBasePath]);
|
|
1550
1597
|
const capture = () => {
|
|
1551
1598
|
const video = videoRef.current;
|
|
1552
1599
|
if (!video || !video.videoWidth) return;
|
|
@@ -1563,7 +1610,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1563
1610
|
ctx.drawImage(video, sx, sy, size, size, 0, 0, size, size);
|
|
1564
1611
|
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
1565
1612
|
streamRef.current = null;
|
|
1566
|
-
onCapture(canvas.toDataURL("image/jpeg", 0.9));
|
|
1613
|
+
onCapture(canvas.toDataURL("image/jpeg", 0.9), buildLivenessArtifact(samplesRef.current));
|
|
1567
1614
|
};
|
|
1568
1615
|
if (error) {
|
|
1569
1616
|
return /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center", padding: 0 } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Camera unavailable"), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, error), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Back")));
|
|
@@ -1571,7 +1618,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1571
1618
|
const detectionActive = ready && !faceDetectionFailed;
|
|
1572
1619
|
const liveMessage = detectionActive ? FACE_MESSAGES[faceStatus] : message;
|
|
1573
1620
|
const liveFaceColor = detectionActive ? FACE_COLORS[faceStatus] : void 0;
|
|
1574
|
-
const captureDisabled =
|
|
1621
|
+
const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
|
|
1575
1622
|
const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
|
|
1576
1623
|
const dynamicCircleStyle = {
|
|
1577
1624
|
...faceCircleStyle,
|
|
@@ -2098,6 +2145,7 @@ function useEventBasedFaceVerificationSubmission(client) {
|
|
|
2098
2145
|
defaultDevice: opts.defaultDevice,
|
|
2099
2146
|
renderQR: opts.renderQR,
|
|
2100
2147
|
onCancel: opts.onCancel,
|
|
2148
|
+
mediapipeAssetBasePath: opts.mediapipeAssetBasePath,
|
|
2101
2149
|
onComplete: (result) => {
|
|
2102
2150
|
cleanup();
|
|
2103
2151
|
resolve(result);
|