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.mjs
CHANGED
|
@@ -87,6 +87,61 @@ function getBrowserInfo() {
|
|
|
87
87
|
return { browser, browser_version: browserVersion, os, os_version: osVersion };
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// src/geolocation/integrity.ts
|
|
91
|
+
var isNil = (v) => v === null || v === void 0;
|
|
92
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
93
|
+
const flags = [];
|
|
94
|
+
const now = opts.now ?? (() => Date.now());
|
|
95
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
96
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
97
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
98
|
+
if (isNil(accuracy)) {
|
|
99
|
+
flags.push("accuracy_missing");
|
|
100
|
+
} else if (accuracy === 0) {
|
|
101
|
+
flags.push("accuracy_zero");
|
|
102
|
+
} else if (accuracy < minAccuracy) {
|
|
103
|
+
flags.push("accuracy_implausibly_small");
|
|
104
|
+
}
|
|
105
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
106
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
107
|
+
flags.push("motion_without_accuracy");
|
|
108
|
+
}
|
|
109
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
110
|
+
flags.push("altitude_without_accuracy");
|
|
111
|
+
}
|
|
112
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
113
|
+
flags.push("stale_timestamp");
|
|
114
|
+
}
|
|
115
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
116
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
117
|
+
}
|
|
118
|
+
return { suspicious: flags.length > 0, flags };
|
|
119
|
+
}
|
|
120
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
121
|
+
const c = position.coords;
|
|
122
|
+
return {
|
|
123
|
+
latitude: c.latitude,
|
|
124
|
+
longitude: c.longitude,
|
|
125
|
+
accuracy: c.accuracy,
|
|
126
|
+
altitude: c.altitude,
|
|
127
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
128
|
+
heading: c.heading,
|
|
129
|
+
speed: c.speed,
|
|
130
|
+
timestamp: position.timestamp
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function probeGeolocationPermission() {
|
|
134
|
+
try {
|
|
135
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
136
|
+
return "unavailable";
|
|
137
|
+
}
|
|
138
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
139
|
+
return status.state;
|
|
140
|
+
} catch {
|
|
141
|
+
return "unavailable";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
90
145
|
// src/geolocation/ciphertext.ts
|
|
91
146
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
92
147
|
async function computeHMAC(key, message) {
|
|
@@ -139,6 +194,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
139
194
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
140
195
|
return null;
|
|
141
196
|
}
|
|
197
|
+
const permissionState = await probeGeolocationPermission();
|
|
142
198
|
return new Promise((resolve) => {
|
|
143
199
|
navigator.geolocation.getCurrentPosition(
|
|
144
200
|
(position) => {
|
|
@@ -155,6 +211,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
155
211
|
resolve(null);
|
|
156
212
|
return;
|
|
157
213
|
}
|
|
214
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
158
215
|
resolve({
|
|
159
216
|
latitude,
|
|
160
217
|
longitude,
|
|
@@ -163,7 +220,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
163
220
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
164
221
|
heading: position.coords.heading ?? void 0,
|
|
165
222
|
speed: position.coords.speed ?? void 0,
|
|
166
|
-
timestamp: position.timestamp
|
|
223
|
+
timestamp: position.timestamp,
|
|
224
|
+
integrity_flags: flags.length ? flags : void 0
|
|
167
225
|
});
|
|
168
226
|
},
|
|
169
227
|
() => {
|
|
@@ -230,6 +288,9 @@ async function generateCipherText(options, config) {
|
|
|
230
288
|
);
|
|
231
289
|
if (location) {
|
|
232
290
|
locationData = location;
|
|
291
|
+
if (location.integrity_flags?.length) {
|
|
292
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
293
|
+
}
|
|
233
294
|
} else if (gpsRequiredByConfig) {
|
|
234
295
|
throw new VesantError(
|
|
235
296
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -332,8 +393,6 @@ function useGeolocation(client, options = {}) {
|
|
|
332
393
|
if (optionsRef.current.autoVerify) {
|
|
333
394
|
const detectAndVerify = async () => {
|
|
334
395
|
try {
|
|
335
|
-
const ipResponse = await fetch("https://api.ipify.org?format=json");
|
|
336
|
-
const { ip } = await ipResponse.json();
|
|
337
396
|
let deviceFingerprint;
|
|
338
397
|
if (optionsRef.current.includeDeviceFingerprint && typeof window !== "undefined") {
|
|
339
398
|
const deviceId = generateDeviceId();
|
|
@@ -347,7 +406,6 @@ function useGeolocation(client, options = {}) {
|
|
|
347
406
|
};
|
|
348
407
|
}
|
|
349
408
|
await verifyIP({
|
|
350
|
-
ip_address: ip,
|
|
351
409
|
user_id: optionsRef.current.userId || "",
|
|
352
410
|
event_type: optionsRef.current.eventType || "page_view",
|
|
353
411
|
device_fingerprint: deviceFingerprint
|
|
@@ -661,6 +719,12 @@ function useLocationCapture(client, options) {
|
|
|
661
719
|
if (accuracy !== null && accuracy !== void 0 && accuracy > 0) {
|
|
662
720
|
capture.accuracy = accuracy;
|
|
663
721
|
}
|
|
722
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), {
|
|
723
|
+
permissionState: await probeGeolocationPermission()
|
|
724
|
+
});
|
|
725
|
+
if (flags.length) {
|
|
726
|
+
capture.integrity_flags = flags;
|
|
727
|
+
}
|
|
664
728
|
}
|
|
665
729
|
}
|
|
666
730
|
if (gpsError) {
|
|
@@ -883,8 +947,90 @@ var Camera = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i
|
|
|
883
947
|
var Done = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik04LjUgMTIuNUwxMC41IDE0LjVMMTUuNSA5LjUiIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPg0KPHBhdGggZD0iTTcgMy4zMzc4MkM4LjQ3MDg3IDIuNDg2OTcgMTAuMTc4NiAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJDMjIgMTcuNTIyOCAxNy41MjI4IDIyIDEyIDIyQzYuNDc3MTUgMjIgMiAxNy41MjI4IDIgMTJDMiAxMC4xNzg2IDIuNDg2OTcgOC40NzA4NyAzLjMzNzgyIDciIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPg0KPC9zdmc+";
|
|
884
948
|
var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNC41IDkuNTAwMDJMOS41IDE0LjVNOS40OTk5OCA5LjVMMTQuNSAxNC41IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjxwYXRoIGQ9Ik03IDMuMzM3ODJDOC40NzA4NyAyLjQ4Njk3IDEwLjE3ODYgMiAxMiAyQzE3LjUyMjggMiAyMiA2LjQ3NzE1IDIyIDEyQzIyIDE3LjUyMjggMTcuNTIyOCAyMiAxMiAyMkM2LjQ3NzE1IDIyIDIgMTcuNTIyOCAyIDEyQzIgMTAuMTc4NiAyLjQ4Njk3IDguNDcwODcgMy4zMzc4MiA3IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjwvc3ZnPg==";
|
|
885
949
|
|
|
950
|
+
// src/kyc/faceDetection.ts
|
|
951
|
+
var FACE_MESSAGES = {
|
|
952
|
+
idle: "Initializing camera...",
|
|
953
|
+
loading: "Loading face detection...",
|
|
954
|
+
scanning: "Position your face in the circle",
|
|
955
|
+
too_far: "Face too far. Please move closer to the camera",
|
|
956
|
+
too_close: "Too close. Please move back a little",
|
|
957
|
+
off_center: "Center your face in the circle",
|
|
958
|
+
ok: "Looking great! Tap the button to capture"
|
|
959
|
+
};
|
|
960
|
+
var FACE_COLORS = {
|
|
961
|
+
idle: "#9ca3af",
|
|
962
|
+
loading: "#3b82f6",
|
|
963
|
+
scanning: "#3b82f6",
|
|
964
|
+
too_far: "#ef4444",
|
|
965
|
+
too_close: "#f59e0b",
|
|
966
|
+
off_center: "#f59e0b",
|
|
967
|
+
ok: "#10b981"
|
|
968
|
+
};
|
|
969
|
+
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
970
|
+
function mapDetectionsToFaceStatus(detections) {
|
|
971
|
+
if (detections.length === 0) return "scanning";
|
|
972
|
+
const bb = detections[0].boundingBox;
|
|
973
|
+
if (bb.width < 0.18) return "too_far";
|
|
974
|
+
if (bb.width > 0.72) return "too_close";
|
|
975
|
+
if (bb.xCenter < 0.28 || bb.xCenter > 0.72 || bb.yCenter < 0.28 || bb.yCenter > 0.72) {
|
|
976
|
+
return "off_center";
|
|
977
|
+
}
|
|
978
|
+
return "ok";
|
|
979
|
+
}
|
|
980
|
+
function isCaptureDisabled(ready, detectionActive, faceStatus) {
|
|
981
|
+
return !ready || detectionActive && faceStatus !== "ok";
|
|
982
|
+
}
|
|
983
|
+
function resolveMediaPipeBase(base) {
|
|
984
|
+
if (!base) return MEDIAPIPE_BASE;
|
|
985
|
+
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
986
|
+
}
|
|
987
|
+
var LIVENESS_SAMPLE_LIMIT = 20;
|
|
988
|
+
function buildLivenessArtifact(samples) {
|
|
989
|
+
if (samples.length === 0) return void 0;
|
|
990
|
+
return JSON.stringify({
|
|
991
|
+
version: 1,
|
|
992
|
+
source: "mediapipe-face-detection",
|
|
993
|
+
samples: samples.slice(-LIVENESS_SAMPLE_LIMIT)
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
var mediapipeLoaderPromises = /* @__PURE__ */ new Map();
|
|
997
|
+
function loadMediaPipeFaceDetection(assetBasePath) {
|
|
998
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
999
|
+
return Promise.reject(new Error("MediaPipe requires a browser environment"));
|
|
1000
|
+
}
|
|
1001
|
+
const w = window;
|
|
1002
|
+
if (w.FaceDetection) {
|
|
1003
|
+
return Promise.resolve(w.FaceDetection);
|
|
1004
|
+
}
|
|
1005
|
+
const src = `${resolveMediaPipeBase(assetBasePath)}/face_detection.js`;
|
|
1006
|
+
const existing = mediapipeLoaderPromises.get(src);
|
|
1007
|
+
if (existing) return existing;
|
|
1008
|
+
const loader = new Promise((resolve, reject) => {
|
|
1009
|
+
const script = document.createElement("script");
|
|
1010
|
+
script.src = src;
|
|
1011
|
+
script.async = true;
|
|
1012
|
+
script.crossOrigin = "anonymous";
|
|
1013
|
+
script.onload = () => {
|
|
1014
|
+
if (w.FaceDetection) {
|
|
1015
|
+
resolve(w.FaceDetection);
|
|
1016
|
+
} else {
|
|
1017
|
+
mediapipeLoaderPromises.delete(src);
|
|
1018
|
+
reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
script.onerror = () => {
|
|
1022
|
+
mediapipeLoaderPromises.delete(src);
|
|
1023
|
+
reject(new Error("Failed to load MediaPipe face_detection script"));
|
|
1024
|
+
};
|
|
1025
|
+
document.head.appendChild(script);
|
|
1026
|
+
});
|
|
1027
|
+
mediapipeLoaderPromises.set(src, loader);
|
|
1028
|
+
return loader;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
886
1031
|
// src/kyc/FaceCaptureModal.tsx
|
|
887
1032
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1033
|
+
var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
|
|
888
1034
|
var LIVENESS_MESSAGES = [
|
|
889
1035
|
"Position your face inside the circle",
|
|
890
1036
|
"Make sure your face is well lit",
|
|
@@ -915,7 +1061,8 @@ function FaceCaptureModal({
|
|
|
915
1061
|
onComplete,
|
|
916
1062
|
onCancel,
|
|
917
1063
|
defaultDevice,
|
|
918
|
-
renderQR
|
|
1064
|
+
renderQR,
|
|
1065
|
+
mediapipeAssetBasePath
|
|
919
1066
|
}) {
|
|
920
1067
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
921
1068
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
@@ -932,21 +1079,30 @@ function FaceCaptureModal({
|
|
|
932
1079
|
const [captureMode, setCaptureMode] = useState("idle");
|
|
933
1080
|
const [capturedPreview, setCapturedPreview] = useState(null);
|
|
934
1081
|
const [capturedBase64, setCapturedBase64] = useState(null);
|
|
1082
|
+
const [capturedArtifact, setCapturedArtifact] = useState(null);
|
|
935
1083
|
const [livenessMessage, setLivenessMessage] = useState(LIVENESS_MESSAGES[0]);
|
|
936
1084
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
937
1085
|
const cancelledRef = useRef(false);
|
|
938
1086
|
const submittingRef = useRef(false);
|
|
1087
|
+
useEffect(() => {
|
|
1088
|
+
const timer = setTimeout(() => {
|
|
1089
|
+
setStage(
|
|
1090
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1091
|
+
);
|
|
1092
|
+
}, SESSION_EXPIRY_MS);
|
|
1093
|
+
return () => clearTimeout(timer);
|
|
1094
|
+
}, [session.token]);
|
|
939
1095
|
useEffect(() => {
|
|
940
1096
|
if (stage.kind !== "qr") return;
|
|
941
1097
|
let stopped = false;
|
|
942
1098
|
const intervalMs = 2e3;
|
|
943
|
-
const deadline = Date.now() +
|
|
1099
|
+
const deadline = Date.now() + SESSION_EXPIRY_MS;
|
|
944
1100
|
let mobileSeen = stage.mobileConnected;
|
|
945
1101
|
const tick = async () => {
|
|
946
1102
|
if (stopped || cancelledRef.current) return;
|
|
947
1103
|
try {
|
|
948
1104
|
if (mobileSeen) {
|
|
949
|
-
const result = await client.
|
|
1105
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
950
1106
|
if (result.status === "accepted") {
|
|
951
1107
|
setStage({ kind: "accepted", result });
|
|
952
1108
|
stopped = true;
|
|
@@ -1018,10 +1174,13 @@ function FaceCaptureModal({
|
|
|
1018
1174
|
try {
|
|
1019
1175
|
const postPromise = (async () => {
|
|
1020
1176
|
try {
|
|
1021
|
-
const data = await client.
|
|
1177
|
+
const data = await client.submitEventBasedFaceVerificationSession({
|
|
1022
1178
|
token: session.token,
|
|
1023
1179
|
reference: ref,
|
|
1024
|
-
proof
|
|
1180
|
+
proof,
|
|
1181
|
+
// undefined when detection wasn't running for this capture —
|
|
1182
|
+
// JSON.stringify drops it, so the field is absent on the wire.
|
|
1183
|
+
liveness_artifact: capturedArtifact ?? void 0
|
|
1025
1184
|
});
|
|
1026
1185
|
return { ok: true, data };
|
|
1027
1186
|
} catch (err2) {
|
|
@@ -1034,7 +1193,7 @@ function FaceCaptureModal({
|
|
|
1034
1193
|
while (Date.now() < deadline) {
|
|
1035
1194
|
if (settled || cancelledRef.current) return null;
|
|
1036
1195
|
try {
|
|
1037
|
-
const r = await client.
|
|
1196
|
+
const r = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1038
1197
|
if (isFreshPollResult(r)) return r;
|
|
1039
1198
|
} catch {
|
|
1040
1199
|
}
|
|
@@ -1067,16 +1226,18 @@ function FaceCaptureModal({
|
|
|
1067
1226
|
submittingRef.current = false;
|
|
1068
1227
|
}
|
|
1069
1228
|
};
|
|
1070
|
-
const handleCameraCapture = (dataUrl) => {
|
|
1229
|
+
const handleCameraCapture = (dataUrl, livenessArtifact) => {
|
|
1071
1230
|
const raw = dataUrl.split(",")[1] ?? "";
|
|
1072
1231
|
setCapturedPreview(dataUrl);
|
|
1073
1232
|
setCapturedBase64(raw);
|
|
1233
|
+
setCapturedArtifact(livenessArtifact ?? null);
|
|
1074
1234
|
setCaptureMode("preview");
|
|
1075
1235
|
};
|
|
1076
1236
|
const handleCameraCancel = () => setCaptureMode("idle");
|
|
1077
1237
|
const handleRetake = () => {
|
|
1078
1238
|
setCapturedPreview(null);
|
|
1079
1239
|
setCapturedBase64(null);
|
|
1240
|
+
setCapturedArtifact(null);
|
|
1080
1241
|
setCaptureMode("live");
|
|
1081
1242
|
};
|
|
1082
1243
|
const handleConfirmSubmit = () => {
|
|
@@ -1148,14 +1309,15 @@ function FaceCaptureModal({
|
|
|
1148
1309
|
{
|
|
1149
1310
|
onCapture: handleCameraCapture,
|
|
1150
1311
|
onCancel: handleCameraCancel,
|
|
1151
|
-
message: livenessMessage
|
|
1312
|
+
message: livenessMessage,
|
|
1313
|
+
assetBasePath: mediapipeAssetBasePath
|
|
1152
1314
|
}
|
|
1153
1315
|
), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1154
1316
|
}
|
|
1155
1317
|
if (captureMode === "preview" && capturedPreview) {
|
|
1156
1318
|
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
1319
|
}
|
|
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
|
|
1320
|
+
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
1321
|
"button",
|
|
1160
1322
|
{
|
|
1161
1323
|
style: secondaryButtonStyle,
|
|
@@ -1167,10 +1329,10 @@ function FaceCaptureModal({
|
|
|
1167
1329
|
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
1330
|
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
1331
|
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")));
|
|
1332
|
+
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
1333
|
const renderError = (message) => {
|
|
1172
1334
|
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
|
|
1335
|
+
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
1336
|
};
|
|
1175
1337
|
const body = (() => {
|
|
1176
1338
|
switch (stage.kind) {
|
|
@@ -1379,59 +1541,10 @@ var attemptsTextStyle = {
|
|
|
1379
1541
|
};
|
|
1380
1542
|
var footerStyle = { padding: "12px 24px 16px", borderTop: "1px solid #f3f4f6" };
|
|
1381
1543
|
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 }) {
|
|
1544
|
+
function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
|
|
1433
1545
|
const videoRef = useRef(null);
|
|
1434
1546
|
const streamRef = useRef(null);
|
|
1547
|
+
const samplesRef = useRef([]);
|
|
1435
1548
|
const [ready, setReady] = useState(false);
|
|
1436
1549
|
const [error, setError] = useState(null);
|
|
1437
1550
|
const [faceStatus, setFaceStatus] = useState("idle");
|
|
@@ -1486,31 +1599,29 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1486
1599
|
let detector = null;
|
|
1487
1600
|
setFaceStatus("loading");
|
|
1488
1601
|
setFaceDetectionFailed(false);
|
|
1602
|
+
samplesRef.current = [];
|
|
1489
1603
|
(async () => {
|
|
1490
1604
|
try {
|
|
1491
|
-
const FaceDetection = await loadMediaPipeFaceDetection();
|
|
1605
|
+
const FaceDetection = await loadMediaPipeFaceDetection(assetBasePath);
|
|
1492
1606
|
if (cancelled) return;
|
|
1607
|
+
const assetBase = resolveMediaPipeBase(assetBasePath);
|
|
1493
1608
|
const d = new FaceDetection({
|
|
1494
|
-
locateFile: (f) => `${
|
|
1609
|
+
locateFile: (f) => `${assetBase}/${f}`
|
|
1495
1610
|
});
|
|
1496
1611
|
d.setOptions({ model: "short", minDetectionConfidence: 0.5 });
|
|
1497
1612
|
d.onResults((results) => {
|
|
1498
1613
|
if (cancelled) return;
|
|
1499
1614
|
const detections = results.detections ?? [];
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
next = "off_center";
|
|
1509
|
-
} else {
|
|
1510
|
-
next = "ok";
|
|
1511
|
-
}
|
|
1615
|
+
const status = mapDetectionsToFaceStatus(detections);
|
|
1616
|
+
samplesRef.current.push({
|
|
1617
|
+
t: Date.now(),
|
|
1618
|
+
status,
|
|
1619
|
+
...detections.length > 0 ? { boundingBox: detections[0].boundingBox } : {}
|
|
1620
|
+
});
|
|
1621
|
+
if (samplesRef.current.length > LIVENESS_SAMPLE_LIMIT) {
|
|
1622
|
+
samplesRef.current.shift();
|
|
1512
1623
|
}
|
|
1513
|
-
setFaceStatus(
|
|
1624
|
+
setFaceStatus(status);
|
|
1514
1625
|
});
|
|
1515
1626
|
await d.initialize();
|
|
1516
1627
|
if (cancelled) return;
|
|
@@ -1546,7 +1657,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1546
1657
|
} catch {
|
|
1547
1658
|
}
|
|
1548
1659
|
};
|
|
1549
|
-
}, [ready]);
|
|
1660
|
+
}, [ready, assetBasePath]);
|
|
1550
1661
|
const capture = () => {
|
|
1551
1662
|
const video = videoRef.current;
|
|
1552
1663
|
if (!video || !video.videoWidth) return;
|
|
@@ -1563,7 +1674,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1563
1674
|
ctx.drawImage(video, sx, sy, size, size, 0, 0, size, size);
|
|
1564
1675
|
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
1565
1676
|
streamRef.current = null;
|
|
1566
|
-
onCapture(canvas.toDataURL("image/jpeg", 0.9));
|
|
1677
|
+
onCapture(canvas.toDataURL("image/jpeg", 0.9), buildLivenessArtifact(samplesRef.current));
|
|
1567
1678
|
};
|
|
1568
1679
|
if (error) {
|
|
1569
1680
|
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 +1682,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
|
|
|
1571
1682
|
const detectionActive = ready && !faceDetectionFailed;
|
|
1572
1683
|
const liveMessage = detectionActive ? FACE_MESSAGES[faceStatus] : message;
|
|
1573
1684
|
const liveFaceColor = detectionActive ? FACE_COLORS[faceStatus] : void 0;
|
|
1574
|
-
const captureDisabled =
|
|
1685
|
+
const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
|
|
1575
1686
|
const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
|
|
1576
1687
|
const dynamicCircleStyle = {
|
|
1577
1688
|
...faceCircleStyle,
|
|
@@ -2078,9 +2189,9 @@ function getRiskColor(risk) {
|
|
|
2078
2189
|
};
|
|
2079
2190
|
return colorMap[risk] || "#6b7280";
|
|
2080
2191
|
}
|
|
2081
|
-
function
|
|
2192
|
+
function useEventBasedFaceVerificationSubmission(client) {
|
|
2082
2193
|
const startSession = useCallback(
|
|
2083
|
-
(request) => client.
|
|
2194
|
+
(request) => client.createEventBasedFaceVerificationSession(request),
|
|
2084
2195
|
[client]
|
|
2085
2196
|
);
|
|
2086
2197
|
const verifyFace = useCallback(
|
|
@@ -2098,6 +2209,7 @@ function useReuseKYCSubmission(client) {
|
|
|
2098
2209
|
defaultDevice: opts.defaultDevice,
|
|
2099
2210
|
renderQR: opts.renderQR,
|
|
2100
2211
|
onCancel: opts.onCancel,
|
|
2212
|
+
mediapipeAssetBasePath: opts.mediapipeAssetBasePath,
|
|
2101
2213
|
onComplete: (result) => {
|
|
2102
2214
|
cleanup();
|
|
2103
2215
|
resolve(result);
|
|
@@ -2124,6 +2236,6 @@ function useReuseKYCSubmission(client) {
|
|
|
2124
2236
|
return { startSession, verifyFace, runFlow };
|
|
2125
2237
|
}
|
|
2126
2238
|
|
|
2127
|
-
export { createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration,
|
|
2239
|
+
export { createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useEventBasedFaceVerificationSubmission, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useTransactionVerification };
|
|
2128
2240
|
//# sourceMappingURL=react.mjs.map
|
|
2129
2241
|
//# sourceMappingURL=react.mjs.map
|