vesant-sdk 1.7.0-dev.362ac6b → 1.7.0-dev.3f9ec86
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-DoMSYMMR.d.ts → client-0NOPDnT0.d.ts} +29 -26
- package/dist/{client-DMIRx7Tu.d.mts → client-CvWNRwwg.d.mts} +29 -26
- package/dist/{client-ZNdnpWe7.d.mts → client-Dps40EtF.d.mts} +2 -2
- package/dist/{client-C3DCmGe9.d.ts → client-SMxqod4j.d.ts} +2 -2
- package/dist/{client-BolQlL5e.d.mts → client-q9fN8Hhf.d.mts} +103 -1
- package/dist/{client-BolQlL5e.d.ts → client-q9fN8Hhf.d.ts} +103 -1
- package/dist/compliance/index.d.mts +3 -3
- package/dist/compliance/index.d.ts +3 -3
- package/dist/compliance/index.js +133 -22
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +133 -22
- package/dist/compliance/index.mjs.map +1 -1
- package/dist/decisions/index.d.mts +1 -1
- package/dist/decisions/index.d.ts +1 -1
- package/dist/decisions/index.js +68 -7
- package/dist/decisions/index.js.map +1 -1
- package/dist/decisions/index.mjs +68 -7
- package/dist/decisions/index.mjs.map +1 -1
- package/dist/geolocation/index.d.mts +59 -4
- package/dist/geolocation/index.d.ts +59 -4
- package/dist/geolocation/index.js +136 -19
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +134 -20
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index-C3zUKydT.d.mts +264 -0
- package/dist/index-DSDgS09k.d.ts +264 -0
- package/dist/index.d.mts +27 -10
- package/dist/index.d.ts +27 -10
- package/dist/index.js +343 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +336 -70
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +2 -2
- package/dist/kyc/core.d.ts +2 -2
- package/dist/kyc/core.js +109 -30
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +109 -30
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +164 -61
- package/dist/kyc/index.d.ts +164 -61
- package/dist/kyc/index.js +109 -30
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +109 -30
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +21 -15
- package/dist/react.d.ts +21 -15
- package/dist/react.js +199 -87
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +199 -87
- package/dist/react.mjs.map +1 -1
- package/dist/risk-profile/index.d.mts +1 -1
- package/dist/risk-profile/index.d.ts +1 -1
- package/dist/risk-profile/index.js +68 -7
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +68 -7
- package/dist/risk-profile/index.mjs.map +1 -1
- package/dist/scores/index.d.mts +1 -1
- package/dist/scores/index.d.ts +1 -1
- package/dist/scores/index.js +68 -7
- package/dist/scores/index.js.map +1 -1
- package/dist/scores/index.mjs +68 -7
- package/dist/scores/index.mjs.map +1 -1
- package/dist/tax/index.d.mts +20 -3
- package/dist/tax/index.d.ts +20 -3
- package/dist/tax/index.js +77 -10
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +77 -10
- package/dist/tax/index.mjs.map +1 -1
- package/dist/webhooks/index.d.mts +2 -189
- package/dist/webhooks/index.d.ts +2 -189
- package/dist/webhooks/index.js +85 -21
- package/dist/webhooks/index.js.map +1 -1
- package/dist/webhooks/index.mjs +85 -22
- package/dist/webhooks/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/react.js
CHANGED
|
@@ -93,6 +93,61 @@ function getBrowserInfo() {
|
|
|
93
93
|
return { browser, browser_version: browserVersion, os, os_version: osVersion };
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// src/geolocation/integrity.ts
|
|
97
|
+
var isNil = (v) => v === null || v === void 0;
|
|
98
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
99
|
+
const flags = [];
|
|
100
|
+
const now = opts.now ?? (() => Date.now());
|
|
101
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
102
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
103
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
104
|
+
if (isNil(accuracy)) {
|
|
105
|
+
flags.push("accuracy_missing");
|
|
106
|
+
} else if (accuracy === 0) {
|
|
107
|
+
flags.push("accuracy_zero");
|
|
108
|
+
} else if (accuracy < minAccuracy) {
|
|
109
|
+
flags.push("accuracy_implausibly_small");
|
|
110
|
+
}
|
|
111
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
112
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
113
|
+
flags.push("motion_without_accuracy");
|
|
114
|
+
}
|
|
115
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
116
|
+
flags.push("altitude_without_accuracy");
|
|
117
|
+
}
|
|
118
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
119
|
+
flags.push("stale_timestamp");
|
|
120
|
+
}
|
|
121
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
122
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
123
|
+
}
|
|
124
|
+
return { suspicious: flags.length > 0, flags };
|
|
125
|
+
}
|
|
126
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
127
|
+
const c = position.coords;
|
|
128
|
+
return {
|
|
129
|
+
latitude: c.latitude,
|
|
130
|
+
longitude: c.longitude,
|
|
131
|
+
accuracy: c.accuracy,
|
|
132
|
+
altitude: c.altitude,
|
|
133
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
134
|
+
heading: c.heading,
|
|
135
|
+
speed: c.speed,
|
|
136
|
+
timestamp: position.timestamp
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function probeGeolocationPermission() {
|
|
140
|
+
try {
|
|
141
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
142
|
+
return "unavailable";
|
|
143
|
+
}
|
|
144
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
145
|
+
return status.state;
|
|
146
|
+
} catch {
|
|
147
|
+
return "unavailable";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
96
151
|
// src/geolocation/ciphertext.ts
|
|
97
152
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
98
153
|
async function computeHMAC(key, message) {
|
|
@@ -145,6 +200,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
145
200
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
146
201
|
return null;
|
|
147
202
|
}
|
|
203
|
+
const permissionState = await probeGeolocationPermission();
|
|
148
204
|
return new Promise((resolve) => {
|
|
149
205
|
navigator.geolocation.getCurrentPosition(
|
|
150
206
|
(position) => {
|
|
@@ -161,6 +217,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
161
217
|
resolve(null);
|
|
162
218
|
return;
|
|
163
219
|
}
|
|
220
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
164
221
|
resolve({
|
|
165
222
|
latitude,
|
|
166
223
|
longitude,
|
|
@@ -169,7 +226,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
169
226
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
170
227
|
heading: position.coords.heading ?? void 0,
|
|
171
228
|
speed: position.coords.speed ?? void 0,
|
|
172
|
-
timestamp: position.timestamp
|
|
229
|
+
timestamp: position.timestamp,
|
|
230
|
+
integrity_flags: flags.length ? flags : void 0
|
|
173
231
|
});
|
|
174
232
|
},
|
|
175
233
|
() => {
|
|
@@ -236,6 +294,9 @@ async function generateCipherText(options, config) {
|
|
|
236
294
|
);
|
|
237
295
|
if (location) {
|
|
238
296
|
locationData = location;
|
|
297
|
+
if (location.integrity_flags?.length) {
|
|
298
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
299
|
+
}
|
|
239
300
|
} else if (gpsRequiredByConfig) {
|
|
240
301
|
throw new VesantError(
|
|
241
302
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -338,8 +399,6 @@ function useGeolocation(client, options = {}) {
|
|
|
338
399
|
if (optionsRef.current.autoVerify) {
|
|
339
400
|
const detectAndVerify = async () => {
|
|
340
401
|
try {
|
|
341
|
-
const ipResponse = await fetch("https://api.ipify.org?format=json");
|
|
342
|
-
const { ip } = await ipResponse.json();
|
|
343
402
|
let deviceFingerprint;
|
|
344
403
|
if (optionsRef.current.includeDeviceFingerprint && typeof window !== "undefined") {
|
|
345
404
|
const deviceId = generateDeviceId();
|
|
@@ -353,7 +412,6 @@ function useGeolocation(client, options = {}) {
|
|
|
353
412
|
};
|
|
354
413
|
}
|
|
355
414
|
await verifyIP({
|
|
356
|
-
ip_address: ip,
|
|
357
415
|
user_id: optionsRef.current.userId || "",
|
|
358
416
|
event_type: optionsRef.current.eventType || "page_view",
|
|
359
417
|
device_fingerprint: deviceFingerprint
|
|
@@ -667,6 +725,12 @@ function useLocationCapture(client, options) {
|
|
|
667
725
|
if (accuracy !== null && accuracy !== void 0 && accuracy > 0) {
|
|
668
726
|
capture.accuracy = accuracy;
|
|
669
727
|
}
|
|
728
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), {
|
|
729
|
+
permissionState: await probeGeolocationPermission()
|
|
730
|
+
});
|
|
731
|
+
if (flags.length) {
|
|
732
|
+
capture.integrity_flags = flags;
|
|
733
|
+
}
|
|
670
734
|
}
|
|
671
735
|
}
|
|
672
736
|
if (gpsError) {
|
|
@@ -889,8 +953,90 @@ var Camera = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i
|
|
|
889
953
|
var Done = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik04LjUgMTIuNUwxMC41IDE0LjVMMTUuNSA5LjUiIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPg0KPHBhdGggZD0iTTcgMy4zMzc4MkM4LjQ3MDg3IDIuNDg2OTcgMTAuMTc4NiAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJDMjIgMTcuNTIyOCAxNy41MjI4IDIyIDEyIDIyQzYuNDc3MTUgMjIgMiAxNy41MjI4IDIgMTJDMiAxMC4xNzg2IDIuNDg2OTcgOC40NzA4NyAzLjMzNzgyIDciIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPg0KPC9zdmc+";
|
|
890
954
|
var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNC41IDkuNTAwMDJMOS41IDE0LjVNOS40OTk5OCA5LjVMMTQuNSAxNC41IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjxwYXRoIGQ9Ik03IDMuMzM3ODJDOC40NzA4NyAyLjQ4Njk3IDEwLjE3ODYgMiAxMiAyQzE3LjUyMjggMiAyMiA2LjQ3NzE1IDIyIDEyQzIyIDE3LjUyMjggMTcuNTIyOCAyMiAxMiAyMkM2LjQ3NzE1IDIyIDIgMTcuNTIyOCAyIDEyQzIgMTAuMTc4NiAyLjQ4Njk3IDguNDcwODcgMy4zMzc4MiA3IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjwvc3ZnPg==";
|
|
891
955
|
|
|
956
|
+
// src/kyc/faceDetection.ts
|
|
957
|
+
var FACE_MESSAGES = {
|
|
958
|
+
idle: "Initializing camera...",
|
|
959
|
+
loading: "Loading face detection...",
|
|
960
|
+
scanning: "Position your face in the circle",
|
|
961
|
+
too_far: "Face too far. Please move closer to the camera",
|
|
962
|
+
too_close: "Too close. Please move back a little",
|
|
963
|
+
off_center: "Center your face in the circle",
|
|
964
|
+
ok: "Looking great! Tap the button to capture"
|
|
965
|
+
};
|
|
966
|
+
var FACE_COLORS = {
|
|
967
|
+
idle: "#9ca3af",
|
|
968
|
+
loading: "#3b82f6",
|
|
969
|
+
scanning: "#3b82f6",
|
|
970
|
+
too_far: "#ef4444",
|
|
971
|
+
too_close: "#f59e0b",
|
|
972
|
+
off_center: "#f59e0b",
|
|
973
|
+
ok: "#10b981"
|
|
974
|
+
};
|
|
975
|
+
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
976
|
+
function mapDetectionsToFaceStatus(detections) {
|
|
977
|
+
if (detections.length === 0) return "scanning";
|
|
978
|
+
const bb = detections[0].boundingBox;
|
|
979
|
+
if (bb.width < 0.18) return "too_far";
|
|
980
|
+
if (bb.width > 0.72) return "too_close";
|
|
981
|
+
if (bb.xCenter < 0.28 || bb.xCenter > 0.72 || bb.yCenter < 0.28 || bb.yCenter > 0.72) {
|
|
982
|
+
return "off_center";
|
|
983
|
+
}
|
|
984
|
+
return "ok";
|
|
985
|
+
}
|
|
986
|
+
function isCaptureDisabled(ready, detectionActive, faceStatus) {
|
|
987
|
+
return !ready || detectionActive && faceStatus !== "ok";
|
|
988
|
+
}
|
|
989
|
+
function resolveMediaPipeBase(base) {
|
|
990
|
+
if (!base) return MEDIAPIPE_BASE;
|
|
991
|
+
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
992
|
+
}
|
|
993
|
+
var LIVENESS_SAMPLE_LIMIT = 20;
|
|
994
|
+
function buildLivenessArtifact(samples) {
|
|
995
|
+
if (samples.length === 0) return void 0;
|
|
996
|
+
return JSON.stringify({
|
|
997
|
+
version: 1,
|
|
998
|
+
source: "mediapipe-face-detection",
|
|
999
|
+
samples: samples.slice(-LIVENESS_SAMPLE_LIMIT)
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
var mediapipeLoaderPromises = /* @__PURE__ */ new Map();
|
|
1003
|
+
function loadMediaPipeFaceDetection(assetBasePath) {
|
|
1004
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1005
|
+
return Promise.reject(new Error("MediaPipe requires a browser environment"));
|
|
1006
|
+
}
|
|
1007
|
+
const w = window;
|
|
1008
|
+
if (w.FaceDetection) {
|
|
1009
|
+
return Promise.resolve(w.FaceDetection);
|
|
1010
|
+
}
|
|
1011
|
+
const src = `${resolveMediaPipeBase(assetBasePath)}/face_detection.js`;
|
|
1012
|
+
const existing = mediapipeLoaderPromises.get(src);
|
|
1013
|
+
if (existing) return existing;
|
|
1014
|
+
const loader = new Promise((resolve, reject) => {
|
|
1015
|
+
const script = document.createElement("script");
|
|
1016
|
+
script.src = src;
|
|
1017
|
+
script.async = true;
|
|
1018
|
+
script.crossOrigin = "anonymous";
|
|
1019
|
+
script.onload = () => {
|
|
1020
|
+
if (w.FaceDetection) {
|
|
1021
|
+
resolve(w.FaceDetection);
|
|
1022
|
+
} else {
|
|
1023
|
+
mediapipeLoaderPromises.delete(src);
|
|
1024
|
+
reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
script.onerror = () => {
|
|
1028
|
+
mediapipeLoaderPromises.delete(src);
|
|
1029
|
+
reject(new Error("Failed to load MediaPipe face_detection script"));
|
|
1030
|
+
};
|
|
1031
|
+
document.head.appendChild(script);
|
|
1032
|
+
});
|
|
1033
|
+
mediapipeLoaderPromises.set(src, loader);
|
|
1034
|
+
return loader;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
892
1037
|
// src/kyc/FaceCaptureModal.tsx
|
|
893
1038
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1039
|
+
var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
|
|
894
1040
|
var LIVENESS_MESSAGES = [
|
|
895
1041
|
"Position your face inside the circle",
|
|
896
1042
|
"Make sure your face is well lit",
|
|
@@ -921,7 +1067,8 @@ function FaceCaptureModal({
|
|
|
921
1067
|
onComplete,
|
|
922
1068
|
onCancel,
|
|
923
1069
|
defaultDevice,
|
|
924
|
-
renderQR
|
|
1070
|
+
renderQR,
|
|
1071
|
+
mediapipeAssetBasePath
|
|
925
1072
|
}) {
|
|
926
1073
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
927
1074
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
@@ -938,21 +1085,30 @@ function FaceCaptureModal({
|
|
|
938
1085
|
const [captureMode, setCaptureMode] = React.useState("idle");
|
|
939
1086
|
const [capturedPreview, setCapturedPreview] = React.useState(null);
|
|
940
1087
|
const [capturedBase64, setCapturedBase64] = React.useState(null);
|
|
1088
|
+
const [capturedArtifact, setCapturedArtifact] = React.useState(null);
|
|
941
1089
|
const [livenessMessage, setLivenessMessage] = React.useState(LIVENESS_MESSAGES[0]);
|
|
942
1090
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
943
1091
|
const cancelledRef = React.useRef(false);
|
|
944
1092
|
const submittingRef = React.useRef(false);
|
|
1093
|
+
React.useEffect(() => {
|
|
1094
|
+
const timer = setTimeout(() => {
|
|
1095
|
+
setStage(
|
|
1096
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1097
|
+
);
|
|
1098
|
+
}, SESSION_EXPIRY_MS);
|
|
1099
|
+
return () => clearTimeout(timer);
|
|
1100
|
+
}, [session.token]);
|
|
945
1101
|
React.useEffect(() => {
|
|
946
1102
|
if (stage.kind !== "qr") return;
|
|
947
1103
|
let stopped = false;
|
|
948
1104
|
const intervalMs = 2e3;
|
|
949
|
-
const deadline = Date.now() +
|
|
1105
|
+
const deadline = Date.now() + SESSION_EXPIRY_MS;
|
|
950
1106
|
let mobileSeen = stage.mobileConnected;
|
|
951
1107
|
const tick = async () => {
|
|
952
1108
|
if (stopped || cancelledRef.current) return;
|
|
953
1109
|
try {
|
|
954
1110
|
if (mobileSeen) {
|
|
955
|
-
const result = await client.
|
|
1111
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
956
1112
|
if (result.status === "accepted") {
|
|
957
1113
|
setStage({ kind: "accepted", result });
|
|
958
1114
|
stopped = true;
|
|
@@ -1024,10 +1180,13 @@ function FaceCaptureModal({
|
|
|
1024
1180
|
try {
|
|
1025
1181
|
const postPromise = (async () => {
|
|
1026
1182
|
try {
|
|
1027
|
-
const data = await client.
|
|
1183
|
+
const data = await client.submitEventBasedFaceVerificationSession({
|
|
1028
1184
|
token: session.token,
|
|
1029
1185
|
reference: ref,
|
|
1030
|
-
proof
|
|
1186
|
+
proof,
|
|
1187
|
+
// undefined when detection wasn't running for this capture —
|
|
1188
|
+
// JSON.stringify drops it, so the field is absent on the wire.
|
|
1189
|
+
liveness_artifact: capturedArtifact ?? void 0
|
|
1031
1190
|
});
|
|
1032
1191
|
return { ok: true, data };
|
|
1033
1192
|
} catch (err2) {
|
|
@@ -1040,7 +1199,7 @@ function FaceCaptureModal({
|
|
|
1040
1199
|
while (Date.now() < deadline) {
|
|
1041
1200
|
if (settled || cancelledRef.current) return null;
|
|
1042
1201
|
try {
|
|
1043
|
-
const r = await client.
|
|
1202
|
+
const r = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1044
1203
|
if (isFreshPollResult(r)) return r;
|
|
1045
1204
|
} catch {
|
|
1046
1205
|
}
|
|
@@ -1073,16 +1232,18 @@ function FaceCaptureModal({
|
|
|
1073
1232
|
submittingRef.current = false;
|
|
1074
1233
|
}
|
|
1075
1234
|
};
|
|
1076
|
-
const handleCameraCapture = (dataUrl) => {
|
|
1235
|
+
const handleCameraCapture = (dataUrl, livenessArtifact) => {
|
|
1077
1236
|
const raw = dataUrl.split(",")[1] ?? "";
|
|
1078
1237
|
setCapturedPreview(dataUrl);
|
|
1079
1238
|
setCapturedBase64(raw);
|
|
1239
|
+
setCapturedArtifact(livenessArtifact ?? null);
|
|
1080
1240
|
setCaptureMode("preview");
|
|
1081
1241
|
};
|
|
1082
1242
|
const handleCameraCancel = () => setCaptureMode("idle");
|
|
1083
1243
|
const handleRetake = () => {
|
|
1084
1244
|
setCapturedPreview(null);
|
|
1085
1245
|
setCapturedBase64(null);
|
|
1246
|
+
setCapturedArtifact(null);
|
|
1086
1247
|
setCaptureMode("live");
|
|
1087
1248
|
};
|
|
1088
1249
|
const handleConfirmSubmit = () => {
|
|
@@ -1154,14 +1315,15 @@ function FaceCaptureModal({
|
|
|
1154
1315
|
{
|
|
1155
1316
|
onCapture: handleCameraCapture,
|
|
1156
1317
|
onCancel: handleCameraCancel,
|
|
1157
|
-
message: livenessMessage
|
|
1318
|
+
message: livenessMessage,
|
|
1319
|
+
assetBasePath: mediapipeAssetBasePath
|
|
1158
1320
|
}
|
|
1159
1321
|
), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1160
1322
|
}
|
|
1161
1323
|
if (captureMode === "preview" && capturedPreview) {
|
|
1162
1324
|
return /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Looks good? Submit this selfie or retake it."), /* @__PURE__ */ React__default.default.createElement("div", { style: previewBoxStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: capturedPreview, alt: "Captured selfie preview", style: previewImgStyle })), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleConfirmSubmit }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Submit")), /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: handleRetake }, "Retake"), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1163
1325
|
}
|
|
1164
|
-
return /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: circleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React__default.default.createElement("div", { style: badgeStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React__default.default.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React__default.default.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, declinedReason ? "Try
|
|
1326
|
+
return /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: circleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React__default.default.createElement("div", { style: badgeStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React__default.default.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React__default.default.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, declinedReason ? "Try Again" : "Open Camera")), hasMethodChoice && /* @__PURE__ */ React__default.default.createElement(
|
|
1165
1327
|
"button",
|
|
1166
1328
|
{
|
|
1167
1329
|
style: secondaryButtonStyle,
|
|
@@ -1173,10 +1335,10 @@ function FaceCaptureModal({
|
|
|
1173
1335
|
const renderSubmitting = () => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Verifying your photo\u2026"), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
|
|
1174
1336
|
const renderAccepted = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: successCircleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Verified"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Your identity has been confirmed. You can continue."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Continue")));
|
|
1175
1337
|
const renderDeclined = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Retake")), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
|
|
1176
|
-
const renderMaxAttempts = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Maximum Attempts Reached"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React__default.default.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__default.default.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
|
|
1338
|
+
const renderMaxAttempts = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Maximum Attempts Reached"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React__default.default.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__default.default.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), result.data?.block && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "This action has been blocked for security reasons."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
|
|
1177
1339
|
const renderError = (message) => {
|
|
1178
1340
|
const sessionExpired = /session\s+expired/i.test(message);
|
|
1179
|
-
return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something
|
|
1341
|
+
return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something Went Wrong"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, sessionExpired ? "Your verification session is no longer valid. Please start a new verification from the beginning." : message), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, !sessionExpired && /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try Again"), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1180
1342
|
};
|
|
1181
1343
|
const body = (() => {
|
|
1182
1344
|
switch (stage.kind) {
|
|
@@ -1385,59 +1547,10 @@ var attemptsTextStyle = {
|
|
|
1385
1547
|
};
|
|
1386
1548
|
var footerStyle = { padding: "12px 24px 16px", borderTop: "1px solid #f3f4f6" };
|
|
1387
1549
|
var footerTextStyle = { margin: 0, fontSize: 12, color: "#9ca3af", textAlign: "center" };
|
|
1388
|
-
|
|
1389
|
-
idle: "Initializing camera...",
|
|
1390
|
-
loading: "Loading face detection...",
|
|
1391
|
-
scanning: "Position your face in the circle",
|
|
1392
|
-
too_far: "Face too far. Please move closer to the camera",
|
|
1393
|
-
too_close: "Too close. Please move back a little",
|
|
1394
|
-
off_center: "Center your face in the circle",
|
|
1395
|
-
ok: "Looking great! Tap the button to capture"
|
|
1396
|
-
};
|
|
1397
|
-
var FACE_COLORS = {
|
|
1398
|
-
idle: "#9ca3af",
|
|
1399
|
-
loading: "#3b82f6",
|
|
1400
|
-
scanning: "#3b82f6",
|
|
1401
|
-
too_far: "#ef4444",
|
|
1402
|
-
too_close: "#f59e0b",
|
|
1403
|
-
off_center: "#f59e0b",
|
|
1404
|
-
ok: "#10b981"
|
|
1405
|
-
};
|
|
1406
|
-
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
1407
|
-
var mediapipeLoaderPromise = null;
|
|
1408
|
-
function loadMediaPipeFaceDetection() {
|
|
1409
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1410
|
-
return Promise.reject(new Error("MediaPipe requires a browser environment"));
|
|
1411
|
-
}
|
|
1412
|
-
const w = window;
|
|
1413
|
-
if (w.FaceDetection) {
|
|
1414
|
-
return Promise.resolve(w.FaceDetection);
|
|
1415
|
-
}
|
|
1416
|
-
if (mediapipeLoaderPromise) return mediapipeLoaderPromise;
|
|
1417
|
-
mediapipeLoaderPromise = new Promise((resolve, reject) => {
|
|
1418
|
-
const script = document.createElement("script");
|
|
1419
|
-
script.src = `${MEDIAPIPE_BASE}/face_detection.js`;
|
|
1420
|
-
script.async = true;
|
|
1421
|
-
script.crossOrigin = "anonymous";
|
|
1422
|
-
script.onload = () => {
|
|
1423
|
-
if (w.FaceDetection) {
|
|
1424
|
-
resolve(w.FaceDetection);
|
|
1425
|
-
} else {
|
|
1426
|
-
mediapipeLoaderPromise = null;
|
|
1427
|
-
reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
script.onerror = () => {
|
|
1431
|
-
mediapipeLoaderPromise = null;
|
|
1432
|
-
reject(new Error("Failed to load MediaPipe face_detection script"));
|
|
1433
|
-
};
|
|
1434
|
-
document.head.appendChild(script);
|
|
1435
|
-
});
|
|
1436
|
-
return mediapipeLoaderPromise;
|
|
1437
|
-
}
|
|
1438
|
-
function LiveCamera({ onCapture, onCancel, message }) {
|
|
1550
|
+
function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
|
|
1439
1551
|
const videoRef = React.useRef(null);
|
|
1440
1552
|
const streamRef = React.useRef(null);
|
|
1553
|
+
const samplesRef = React.useRef([]);
|
|
1441
1554
|
const [ready, setReady] = React.useState(false);
|
|
1442
1555
|
const [error, setError] = React.useState(null);
|
|
1443
1556
|
const [faceStatus, setFaceStatus] = React.useState("idle");
|
|
@@ -1492,31 +1605,29 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1492
1605
|
let detector = null;
|
|
1493
1606
|
setFaceStatus("loading");
|
|
1494
1607
|
setFaceDetectionFailed(false);
|
|
1608
|
+
samplesRef.current = [];
|
|
1495
1609
|
(async () => {
|
|
1496
1610
|
try {
|
|
1497
|
-
const FaceDetection = await loadMediaPipeFaceDetection();
|
|
1611
|
+
const FaceDetection = await loadMediaPipeFaceDetection(assetBasePath);
|
|
1498
1612
|
if (cancelled) return;
|
|
1613
|
+
const assetBase = resolveMediaPipeBase(assetBasePath);
|
|
1499
1614
|
const d = new FaceDetection({
|
|
1500
|
-
locateFile: (f) => `${
|
|
1615
|
+
locateFile: (f) => `${assetBase}/${f}`
|
|
1501
1616
|
});
|
|
1502
1617
|
d.setOptions({ model: "short", minDetectionConfidence: 0.5 });
|
|
1503
1618
|
d.onResults((results) => {
|
|
1504
1619
|
if (cancelled) return;
|
|
1505
1620
|
const detections = results.detections ?? [];
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
next = "off_center";
|
|
1515
|
-
} else {
|
|
1516
|
-
next = "ok";
|
|
1517
|
-
}
|
|
1621
|
+
const status = mapDetectionsToFaceStatus(detections);
|
|
1622
|
+
samplesRef.current.push({
|
|
1623
|
+
t: Date.now(),
|
|
1624
|
+
status,
|
|
1625
|
+
...detections.length > 0 ? { boundingBox: detections[0].boundingBox } : {}
|
|
1626
|
+
});
|
|
1627
|
+
if (samplesRef.current.length > LIVENESS_SAMPLE_LIMIT) {
|
|
1628
|
+
samplesRef.current.shift();
|
|
1518
1629
|
}
|
|
1519
|
-
setFaceStatus(
|
|
1630
|
+
setFaceStatus(status);
|
|
1520
1631
|
});
|
|
1521
1632
|
await d.initialize();
|
|
1522
1633
|
if (cancelled) return;
|
|
@@ -1552,7 +1663,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1552
1663
|
} catch {
|
|
1553
1664
|
}
|
|
1554
1665
|
};
|
|
1555
|
-
}, [ready]);
|
|
1666
|
+
}, [ready, assetBasePath]);
|
|
1556
1667
|
const capture = () => {
|
|
1557
1668
|
const video = videoRef.current;
|
|
1558
1669
|
if (!video || !video.videoWidth) return;
|
|
@@ -1569,7 +1680,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1569
1680
|
ctx.drawImage(video, sx, sy, size, size, 0, 0, size, size);
|
|
1570
1681
|
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
1571
1682
|
streamRef.current = null;
|
|
1572
|
-
onCapture(canvas.toDataURL("image/jpeg", 0.9));
|
|
1683
|
+
onCapture(canvas.toDataURL("image/jpeg", 0.9), buildLivenessArtifact(samplesRef.current));
|
|
1573
1684
|
};
|
|
1574
1685
|
if (error) {
|
|
1575
1686
|
return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center", padding: 0 } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Camera unavailable"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, error), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Back")));
|
|
@@ -1577,7 +1688,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1577
1688
|
const detectionActive = ready && !faceDetectionFailed;
|
|
1578
1689
|
const liveMessage = detectionActive ? FACE_MESSAGES[faceStatus] : message;
|
|
1579
1690
|
const liveFaceColor = detectionActive ? FACE_COLORS[faceStatus] : void 0;
|
|
1580
|
-
const captureDisabled =
|
|
1691
|
+
const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
|
|
1581
1692
|
const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
|
|
1582
1693
|
const dynamicCircleStyle = {
|
|
1583
1694
|
...faceCircleStyle,
|
|
@@ -2084,9 +2195,9 @@ function getRiskColor(risk) {
|
|
|
2084
2195
|
};
|
|
2085
2196
|
return colorMap[risk] || "#6b7280";
|
|
2086
2197
|
}
|
|
2087
|
-
function
|
|
2198
|
+
function useEventBasedFaceVerificationSubmission(client$1) {
|
|
2088
2199
|
const startSession = React.useCallback(
|
|
2089
|
-
(request) => client$1.
|
|
2200
|
+
(request) => client$1.createEventBasedFaceVerificationSession(request),
|
|
2090
2201
|
[client$1]
|
|
2091
2202
|
);
|
|
2092
2203
|
const verifyFace = React.useCallback(
|
|
@@ -2104,6 +2215,7 @@ function useReuseKYCSubmission(client$1) {
|
|
|
2104
2215
|
defaultDevice: opts.defaultDevice,
|
|
2105
2216
|
renderQR: opts.renderQR,
|
|
2106
2217
|
onCancel: opts.onCancel,
|
|
2218
|
+
mediapipeAssetBasePath: opts.mediapipeAssetBasePath,
|
|
2107
2219
|
onComplete: (result) => {
|
|
2108
2220
|
cleanup();
|
|
2109
2221
|
resolve(result);
|
|
@@ -2140,6 +2252,7 @@ exports.isValidFileSize = isValidFileSize;
|
|
|
2140
2252
|
exports.isValidFileType = isValidFileType;
|
|
2141
2253
|
exports.useCipherText = useCipherText;
|
|
2142
2254
|
exports.useCustomerProfile = useCustomerProfile;
|
|
2255
|
+
exports.useEventBasedFaceVerificationSubmission = useEventBasedFaceVerificationSubmission;
|
|
2143
2256
|
exports.useGeolocation = useGeolocation;
|
|
2144
2257
|
exports.useKycAlerts = useKycAlerts;
|
|
2145
2258
|
exports.useKycOverview = useKycOverview;
|
|
@@ -2150,7 +2263,6 @@ exports.useLocationCapture = useLocationCapture;
|
|
|
2150
2263
|
exports.useLocationRequests = useLocationRequests;
|
|
2151
2264
|
exports.useLoginVerification = useLoginVerification;
|
|
2152
2265
|
exports.useRegistration = useRegistration;
|
|
2153
|
-
exports.useReuseKYCSubmission = useReuseKYCSubmission;
|
|
2154
2266
|
exports.useTransactionVerification = useTransactionVerification;
|
|
2155
2267
|
//# sourceMappingURL=react.js.map
|
|
2156
2268
|
//# sourceMappingURL=react.js.map
|