vesant-sdk 1.7.2-dev.617dd44 → 1.7.2-dev.9ef17db
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/README.md +4 -0
- package/dist/{client-D23KhWeh.d.mts → client-5q4whhGu.d.mts} +1 -1
- package/dist/{client-fy3trF2K.d.ts → client-BaUekQT8.d.ts} +32 -10
- package/dist/{client-B3DCAHlg.d.ts → client-DUHC-68_.d.ts} +1 -1
- package/dist/{client-BwXMQA2a.d.mts → client-XDEhh9VN.d.mts} +32 -10
- package/dist/compliance/index.d.mts +2 -2
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +39 -1
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +39 -1
- 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/geolocation/index.js +39 -1
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +39 -1
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +113 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +113 -31
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +1 -1
- package/dist/kyc/core.d.ts +1 -1
- package/dist/kyc/core.js +74 -29
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +74 -30
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +334 -65
- package/dist/kyc/index.d.ts +334 -65
- package/dist/kyc/index.js +74 -29
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +74 -30
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +126 -48
- package/dist/react.d.ts +126 -48
- package/dist/react.js +257 -35
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +255 -36
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -429,14 +429,11 @@ function useCipherText(client) {
|
|
|
429
429
|
const [configLoading, setConfigLoading] = React.useState(!!client);
|
|
430
430
|
const [configError, setConfigError] = React.useState(null);
|
|
431
431
|
const configRef = React.useRef(void 0);
|
|
432
|
-
const configPromiseRef = React.useRef(null);
|
|
433
432
|
React.useEffect(() => {
|
|
434
433
|
if (!client) return;
|
|
435
434
|
let cancelled = false;
|
|
436
435
|
setConfigLoading(true);
|
|
437
|
-
|
|
438
|
-
configPromiseRef.current = promise;
|
|
439
|
-
promise.then((cfg) => {
|
|
436
|
+
client.getGPSConfig().then((cfg) => {
|
|
440
437
|
if (!cancelled) {
|
|
441
438
|
configRef.current = cfg;
|
|
442
439
|
setConfigLoading(false);
|
|
@@ -451,13 +448,21 @@ function useCipherText(client) {
|
|
|
451
448
|
cancelled = true;
|
|
452
449
|
};
|
|
453
450
|
}, [client]);
|
|
451
|
+
const refreshConfig = React.useCallback(async () => {
|
|
452
|
+
if (!client) return;
|
|
453
|
+
try {
|
|
454
|
+
configRef.current = await client.refreshGPSConfig();
|
|
455
|
+
setConfigError(null);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
setConfigError(error instanceof Error ? error : new Error("Failed to refresh GPS config"));
|
|
458
|
+
}
|
|
459
|
+
}, [client]);
|
|
454
460
|
const generate = React.useCallback(
|
|
455
461
|
async (options) => {
|
|
456
462
|
try {
|
|
457
|
-
if (
|
|
463
|
+
if (client) {
|
|
458
464
|
try {
|
|
459
|
-
|
|
460
|
-
configRef.current = cfg;
|
|
465
|
+
configRef.current = await client.getGPSConfig();
|
|
461
466
|
} catch {
|
|
462
467
|
}
|
|
463
468
|
}
|
|
@@ -467,9 +472,9 @@ function useCipherText(client) {
|
|
|
467
472
|
return null;
|
|
468
473
|
}
|
|
469
474
|
},
|
|
470
|
-
[]
|
|
475
|
+
[client]
|
|
471
476
|
);
|
|
472
|
-
return { generate, configLoading, configError };
|
|
477
|
+
return { generate, refreshConfig, configLoading, configError };
|
|
473
478
|
}
|
|
474
479
|
function useLocationRequests(client, options = {}) {
|
|
475
480
|
const [requests, setRequests] = React.useState([]);
|
|
@@ -930,19 +935,19 @@ var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id
|
|
|
930
935
|
var FACE_MESSAGES = {
|
|
931
936
|
idle: "Initializing camera...",
|
|
932
937
|
loading: "Loading face detection...",
|
|
933
|
-
scanning: "Position your face in the
|
|
938
|
+
scanning: "Position your face in the oval",
|
|
934
939
|
too_far: "Face too far. Please move closer to the camera",
|
|
935
940
|
too_close: "Too close. Please move back a little",
|
|
936
|
-
off_center: "Center your face in the
|
|
941
|
+
off_center: "Center your face in the oval",
|
|
937
942
|
ok: "Looking great! Tap the button to capture"
|
|
938
943
|
};
|
|
939
944
|
var FACE_COLORS = {
|
|
940
945
|
idle: "#9ca3af",
|
|
941
|
-
loading: "#
|
|
942
|
-
scanning: "#
|
|
946
|
+
loading: "#ef4444",
|
|
947
|
+
scanning: "#ef4444",
|
|
943
948
|
too_far: "#ef4444",
|
|
944
|
-
too_close: "#
|
|
945
|
-
off_center: "#
|
|
949
|
+
too_close: "#ef4444",
|
|
950
|
+
off_center: "#ef4444",
|
|
946
951
|
ok: "#10b981"
|
|
947
952
|
};
|
|
948
953
|
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
@@ -1007,11 +1012,77 @@ function loadMediaPipeFaceDetection(assetBasePath) {
|
|
|
1007
1012
|
return loader;
|
|
1008
1013
|
}
|
|
1009
1014
|
|
|
1015
|
+
// src/kyc/hostedJourney.ts
|
|
1016
|
+
var REUSE_KYC_SESSION_EXPIRY_MS = 10 * 60 * 1e3;
|
|
1017
|
+
var JOURNEY_FRAME_HEIGHT = "clamp(500px, 64vh, 680px)";
|
|
1018
|
+
var JOURNEY_CONFIRM_TIMEOUT_MS = 45e3;
|
|
1019
|
+
function journeyOutcomeFromMessage(data) {
|
|
1020
|
+
const status = data?.verification_status;
|
|
1021
|
+
if (typeof status !== "string") return null;
|
|
1022
|
+
switch (status) {
|
|
1023
|
+
case "verification.accepted":
|
|
1024
|
+
return "accepted";
|
|
1025
|
+
case "verification.declined":
|
|
1026
|
+
return "declined";
|
|
1027
|
+
case "verification.cancelled":
|
|
1028
|
+
case "request.timeout":
|
|
1029
|
+
case "request.invalid":
|
|
1030
|
+
case "request.unauthorized":
|
|
1031
|
+
case "request.deleted":
|
|
1032
|
+
return "cancelled";
|
|
1033
|
+
default:
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function journeyPollIntervalMs(confirming) {
|
|
1038
|
+
return confirming ? 1e3 : 2500;
|
|
1039
|
+
}
|
|
1040
|
+
function pendingJourneyResult(reference) {
|
|
1041
|
+
return {
|
|
1042
|
+
event: "reuse_kyc",
|
|
1043
|
+
reference,
|
|
1044
|
+
resource_id: "",
|
|
1045
|
+
status: "pending",
|
|
1046
|
+
data: {
|
|
1047
|
+
retries_remaining: 0,
|
|
1048
|
+
retry_limit_exceeded: false,
|
|
1049
|
+
trigger_alert: false,
|
|
1050
|
+
enforce_logout: false,
|
|
1051
|
+
freeze_account: false,
|
|
1052
|
+
block: false,
|
|
1053
|
+
freeze_duration_minutes: 0
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function tryStartOperation(guard) {
|
|
1058
|
+
if (guard.current) return false;
|
|
1059
|
+
guard.current = true;
|
|
1060
|
+
return true;
|
|
1061
|
+
}
|
|
1062
|
+
function releaseOperation(guard) {
|
|
1063
|
+
guard.current = false;
|
|
1064
|
+
}
|
|
1065
|
+
function retryTarget(hasHostedJourney) {
|
|
1066
|
+
return hasHostedJourney ? "journey" : "capture";
|
|
1067
|
+
}
|
|
1068
|
+
var CAPTURE_MODAL_MAX_WIDTH = 448;
|
|
1069
|
+
function modalMaxWidth(hasHostedJourney) {
|
|
1070
|
+
return hasHostedJourney ? CAPTURE_MODAL_MAX_WIDTH : CAPTURE_MODAL_MAX_WIDTH;
|
|
1071
|
+
}
|
|
1072
|
+
var JOURNEY_CONTENT_PADDING = 12;
|
|
1073
|
+
function resultForModalClose(stageKind, result, unconfirmed) {
|
|
1074
|
+
const terminal = stageKind === "accepted" || stageKind === "declined" || stageKind === "max_attempts";
|
|
1075
|
+
if (terminal && result !== void 0) return result;
|
|
1076
|
+
const awaitingConfirmation = stageKind === "confirming" || stageKind === "unconfirmed";
|
|
1077
|
+
if (awaitingConfirmation && unconfirmed !== void 0) return unconfirmed;
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1010
1081
|
// src/kyc/FaceCaptureModal.tsx
|
|
1011
1082
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1012
|
-
var
|
|
1083
|
+
var ACCEPTED_AUTO_CLOSE_MS = 1200;
|
|
1013
1084
|
var LIVENESS_MESSAGES = [
|
|
1014
|
-
"Position your face inside the
|
|
1085
|
+
"Position your face inside the oval",
|
|
1015
1086
|
"Make sure your face is well lit",
|
|
1016
1087
|
"Remove glasses or hats if possible",
|
|
1017
1088
|
"Look straight at the camera, then tap Capture"
|
|
@@ -1020,6 +1091,12 @@ function headerSubtitle(stageKind) {
|
|
|
1020
1091
|
switch (stageKind) {
|
|
1021
1092
|
case "qr":
|
|
1022
1093
|
return "Use your phone to continue";
|
|
1094
|
+
case "journey":
|
|
1095
|
+
return "Follow the steps to verify your identity";
|
|
1096
|
+
case "confirming":
|
|
1097
|
+
return "Confirming your result";
|
|
1098
|
+
case "unconfirmed":
|
|
1099
|
+
return "Still confirming";
|
|
1023
1100
|
case "accepted":
|
|
1024
1101
|
return "All set";
|
|
1025
1102
|
case "declined":
|
|
@@ -1045,7 +1122,9 @@ function FaceCaptureModal({
|
|
|
1045
1122
|
}) {
|
|
1046
1123
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
1047
1124
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
1125
|
+
const journeyMode = Boolean(session.verification_url);
|
|
1048
1126
|
const initialStage = (() => {
|
|
1127
|
+
if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
|
|
1049
1128
|
if (initialChoice === "this") return { kind: "capture" };
|
|
1050
1129
|
if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
1051
1130
|
return { kind: "choose" };
|
|
@@ -1063,19 +1142,20 @@ function FaceCaptureModal({
|
|
|
1063
1142
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
1064
1143
|
const cancelledRef = React.useRef(false);
|
|
1065
1144
|
const submittingRef = React.useRef(false);
|
|
1145
|
+
const journeyRetryingRef = React.useRef(false);
|
|
1066
1146
|
React.useEffect(() => {
|
|
1067
1147
|
const timer = setTimeout(() => {
|
|
1068
1148
|
setStage(
|
|
1069
|
-
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1149
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
|
|
1070
1150
|
);
|
|
1071
|
-
},
|
|
1151
|
+
}, REUSE_KYC_SESSION_EXPIRY_MS);
|
|
1072
1152
|
return () => clearTimeout(timer);
|
|
1073
1153
|
}, [session.token]);
|
|
1074
1154
|
React.useEffect(() => {
|
|
1075
1155
|
if (stage.kind !== "qr") return;
|
|
1076
1156
|
let stopped = false;
|
|
1077
1157
|
const intervalMs = 2e3;
|
|
1078
|
-
const deadline = Date.now() +
|
|
1158
|
+
const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
|
|
1079
1159
|
let mobileSeen = stage.mobileConnected;
|
|
1080
1160
|
const tick = async () => {
|
|
1081
1161
|
if (stopped || cancelledRef.current) return;
|
|
@@ -1116,6 +1196,82 @@ function FaceCaptureModal({
|
|
|
1116
1196
|
stopped = true;
|
|
1117
1197
|
};
|
|
1118
1198
|
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
1199
|
+
const journeyUrl = stage.kind === "journey" || stage.kind === "confirming" ? stage.url : "";
|
|
1200
|
+
React.useEffect(() => {
|
|
1201
|
+
if (stage.kind !== "journey" && stage.kind !== "confirming" || !journeyUrl) return;
|
|
1202
|
+
const intervalMs = journeyPollIntervalMs(stage.kind === "confirming");
|
|
1203
|
+
let stopped = false;
|
|
1204
|
+
const checkOnce = async () => {
|
|
1205
|
+
try {
|
|
1206
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1207
|
+
if (applyResult(result)) {
|
|
1208
|
+
stopped = true;
|
|
1209
|
+
return true;
|
|
1210
|
+
}
|
|
1211
|
+
} catch {
|
|
1212
|
+
}
|
|
1213
|
+
return false;
|
|
1214
|
+
};
|
|
1215
|
+
const tick = async () => {
|
|
1216
|
+
if (stopped || cancelledRef.current) return;
|
|
1217
|
+
await checkOnce();
|
|
1218
|
+
if (!stopped && !cancelledRef.current) {
|
|
1219
|
+
setTimeout(tick, intervalMs);
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
void tick();
|
|
1223
|
+
let frameOrigin = "";
|
|
1224
|
+
try {
|
|
1225
|
+
frameOrigin = new URL(journeyUrl).origin;
|
|
1226
|
+
} catch {
|
|
1227
|
+
frameOrigin = "";
|
|
1228
|
+
}
|
|
1229
|
+
const onMessage = (e) => {
|
|
1230
|
+
if (!frameOrigin || e.origin !== frameOrigin) return;
|
|
1231
|
+
const outcome = journeyOutcomeFromMessage(e.data);
|
|
1232
|
+
if (!outcome) return;
|
|
1233
|
+
void checkOnce();
|
|
1234
|
+
setStage((prev) => {
|
|
1235
|
+
if (prev.kind !== "journey") return prev;
|
|
1236
|
+
if (outcome === "cancelled") {
|
|
1237
|
+
return { kind: "error", message: "The verification was closed before it finished. Please try again." };
|
|
1238
|
+
}
|
|
1239
|
+
return { kind: "confirming", outcome, url: prev.url };
|
|
1240
|
+
});
|
|
1241
|
+
};
|
|
1242
|
+
window.addEventListener("message", onMessage);
|
|
1243
|
+
return () => {
|
|
1244
|
+
stopped = true;
|
|
1245
|
+
window.removeEventListener("message", onMessage);
|
|
1246
|
+
};
|
|
1247
|
+
}, [stage.kind, journeyUrl]);
|
|
1248
|
+
React.useEffect(() => {
|
|
1249
|
+
if (stage.kind !== "confirming") return;
|
|
1250
|
+
const outcome = stage.outcome;
|
|
1251
|
+
const timer = setTimeout(() => {
|
|
1252
|
+
setStage((prev) => prev.kind === "confirming" ? { kind: "unconfirmed", outcome } : prev);
|
|
1253
|
+
}, JOURNEY_CONFIRM_TIMEOUT_MS);
|
|
1254
|
+
return () => clearTimeout(timer);
|
|
1255
|
+
}, [stage.kind, stage.kind === "confirming" ? stage.outcome : ""]);
|
|
1256
|
+
const handleJourneyRetry = async () => {
|
|
1257
|
+
if (!tryStartOperation(journeyRetryingRef)) return;
|
|
1258
|
+
setStage({ kind: "journey", url: "" });
|
|
1259
|
+
try {
|
|
1260
|
+
const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
|
|
1261
|
+
if (refreshed.verification_url) {
|
|
1262
|
+
setStage({ kind: "journey", url: refreshed.verification_url });
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
|
|
1266
|
+
} catch (err) {
|
|
1267
|
+
setStage({
|
|
1268
|
+
kind: "error",
|
|
1269
|
+
message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
|
|
1270
|
+
});
|
|
1271
|
+
} finally {
|
|
1272
|
+
releaseOperation(journeyRetryingRef);
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1119
1275
|
const applyResult = (result) => {
|
|
1120
1276
|
if (result.status === "accepted") {
|
|
1121
1277
|
setStage({ kind: "accepted", result });
|
|
@@ -1243,12 +1399,23 @@ function FaceCaptureModal({
|
|
|
1243
1399
|
}, 3e3);
|
|
1244
1400
|
return () => clearInterval(interval);
|
|
1245
1401
|
}, [captureMode]);
|
|
1246
|
-
const close = (result) => {
|
|
1402
|
+
const close = React.useCallback((result) => {
|
|
1247
1403
|
cancelledRef.current = true;
|
|
1248
1404
|
if (result === null && onCancel) {
|
|
1249
1405
|
onCancel();
|
|
1250
1406
|
}
|
|
1251
1407
|
onComplete(result);
|
|
1408
|
+
}, [onCancel, onComplete]);
|
|
1409
|
+
React.useEffect(() => {
|
|
1410
|
+
if (stage.kind !== "accepted") return;
|
|
1411
|
+
const result = stage.result;
|
|
1412
|
+
const timer = setTimeout(() => close(result), ACCEPTED_AUTO_CLOSE_MS);
|
|
1413
|
+
return () => clearTimeout(timer);
|
|
1414
|
+
}, [close, stage]);
|
|
1415
|
+
const handleHeaderClose = () => {
|
|
1416
|
+
const result = "result" in stage ? stage.result : void 0;
|
|
1417
|
+
const unconfirmed = stage.kind === "confirming" || stage.kind === "unconfirmed" ? pendingJourneyResult(session.reference) : void 0;
|
|
1418
|
+
close(resultForModalClose(stage.kind, result, unconfirmed));
|
|
1252
1419
|
};
|
|
1253
1420
|
const renderChoose = () => /* @__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("p", { style: subtitleStyle }, "How would you like to verify?"), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1254
1421
|
"button",
|
|
@@ -1307,12 +1474,37 @@ function FaceCaptureModal({
|
|
|
1307
1474
|
};
|
|
1308
1475
|
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."));
|
|
1309
1476
|
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")));
|
|
1310
|
-
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));
|
|
1477
|
+
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 }, journeyMode ? /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => void handleJourneyRetry() }, /* @__PURE__ */ React__default.default.createElement("span", null, "Try Again")) : /* @__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));
|
|
1311
1478
|
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")));
|
|
1312
1479
|
const renderError = (message) => {
|
|
1313
1480
|
const sessionExpired = /session\s+expired/i.test(message);
|
|
1314
|
-
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(
|
|
1481
|
+
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(
|
|
1482
|
+
"button",
|
|
1483
|
+
{
|
|
1484
|
+
style: primaryButtonStyle,
|
|
1485
|
+
onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
|
|
1486
|
+
},
|
|
1487
|
+
"Try Again"
|
|
1488
|
+
), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1315
1489
|
};
|
|
1490
|
+
const renderConfirming = () => /* @__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("h3", { style: titleStyle }, "Confirming your result\u2026"), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
|
|
1491
|
+
const renderUnconfirmed = (outcome) => /* @__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 }, "Still confirming"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, outcome === "accepted" ? "Your verification was submitted, but we haven't received the final result yet. You can close this window and we'll keep checking in the background." : "Your last attempt wasn't accepted, but we haven't received the details yet. You can try again or close this window."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, outcome === "declined" && /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => void handleJourneyRetry() }, "Try Again"), /* @__PURE__ */ React__default.default.createElement(
|
|
1492
|
+
"button",
|
|
1493
|
+
{
|
|
1494
|
+
style: outcome === "accepted" ? primaryButtonStyle : cancelButtonStyle,
|
|
1495
|
+
onClick: () => close(pendingJourneyResult(session.reference))
|
|
1496
|
+
},
|
|
1497
|
+
"Close"
|
|
1498
|
+
)));
|
|
1499
|
+
const renderJourney = (url) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, padding: JOURNEY_CONTENT_PADDING } }, url ? /* @__PURE__ */ React__default.default.createElement(
|
|
1500
|
+
"iframe",
|
|
1501
|
+
{
|
|
1502
|
+
src: url,
|
|
1503
|
+
title: "Identity verification",
|
|
1504
|
+
allow: "camera; microphone",
|
|
1505
|
+
style: journeyFrameStyle
|
|
1506
|
+
}
|
|
1507
|
+
) : /* @__PURE__ */ React__default.default.createElement("div", { style: { padding: 24, 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 }, "Preparing your next attempt\u2026")), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...attemptsTextStyle, textAlign: "center" } }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
|
|
1316
1508
|
const body = (() => {
|
|
1317
1509
|
switch (stage.kind) {
|
|
1318
1510
|
case "choose":
|
|
@@ -1321,6 +1513,12 @@ function FaceCaptureModal({
|
|
|
1321
1513
|
return renderQRStage(stage.mobileConnected);
|
|
1322
1514
|
case "capture":
|
|
1323
1515
|
return renderCapture(stage.declinedReason);
|
|
1516
|
+
case "journey":
|
|
1517
|
+
return renderJourney(stage.url);
|
|
1518
|
+
case "confirming":
|
|
1519
|
+
return renderConfirming();
|
|
1520
|
+
case "unconfirmed":
|
|
1521
|
+
return renderUnconfirmed(stage.outcome);
|
|
1324
1522
|
case "submitting":
|
|
1325
1523
|
return renderSubmitting();
|
|
1326
1524
|
case "accepted":
|
|
@@ -1333,10 +1531,10 @@ function FaceCaptureModal({
|
|
|
1333
1531
|
return renderError(stage.message);
|
|
1334
1532
|
}
|
|
1335
1533
|
})();
|
|
1336
|
-
return /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, /* @__PURE__ */ React__default.default.createElement("style", null, keyframes), /* @__PURE__ */ React__default.default.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: modalStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: headerStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1534
|
+
return /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, /* @__PURE__ */ React__default.default.createElement("style", null, keyframes), /* @__PURE__ */ React__default.default.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...modalStyle, maxWidth: modalMaxWidth(journeyMode) } }, /* @__PURE__ */ React__default.default.createElement("div", { style: headerStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1337
1535
|
"button",
|
|
1338
1536
|
{
|
|
1339
|
-
onClick:
|
|
1537
|
+
onClick: handleHeaderClose,
|
|
1340
1538
|
style: closeButtonStyle,
|
|
1341
1539
|
"aria-label": "Close"
|
|
1342
1540
|
},
|
|
@@ -1366,7 +1564,8 @@ var modalStyle = {
|
|
|
1366
1564
|
borderRadius: 16,
|
|
1367
1565
|
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1368
1566
|
width: "100%",
|
|
1369
|
-
|
|
1567
|
+
maxHeight: "calc(100vh - 32px)",
|
|
1568
|
+
overflowY: "auto",
|
|
1370
1569
|
animation: "zoomIn .2s ease-out"
|
|
1371
1570
|
};
|
|
1372
1571
|
var headerStyle = {
|
|
@@ -1392,6 +1591,13 @@ var contentStyle = {
|
|
|
1392
1591
|
flexDirection: "column",
|
|
1393
1592
|
gap: 16
|
|
1394
1593
|
};
|
|
1594
|
+
var journeyFrameStyle = {
|
|
1595
|
+
width: "100%",
|
|
1596
|
+
height: JOURNEY_FRAME_HEIGHT,
|
|
1597
|
+
border: 0,
|
|
1598
|
+
borderRadius: 12,
|
|
1599
|
+
background: "#f9fafb"
|
|
1600
|
+
};
|
|
1395
1601
|
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1396
1602
|
var visualGuideStyle = { position: "relative" };
|
|
1397
1603
|
var circleStyle = {
|
|
@@ -1658,9 +1864,10 @@ function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
|
|
|
1658
1864
|
if (error) {
|
|
1659
1865
|
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")));
|
|
1660
1866
|
}
|
|
1661
|
-
const
|
|
1662
|
-
const
|
|
1663
|
-
const
|
|
1867
|
+
const detectionAvailable = !faceDetectionFailed;
|
|
1868
|
+
const detectionActive = ready && detectionAvailable;
|
|
1869
|
+
const liveMessage = detectionAvailable ? FACE_MESSAGES[faceStatus] : message;
|
|
1870
|
+
const liveFaceColor = detectionAvailable ? FACE_COLORS[faceStatus] : void 0;
|
|
1664
1871
|
const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
|
|
1665
1872
|
const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
|
|
1666
1873
|
const dynamicCircleStyle = {
|
|
@@ -1776,20 +1983,22 @@ var faceOverlayContainerStyle = {
|
|
|
1776
1983
|
};
|
|
1777
1984
|
var faceRingWrapStyle = {
|
|
1778
1985
|
position: "relative",
|
|
1779
|
-
|
|
1780
|
-
aspectRatio: "
|
|
1986
|
+
height: "64%",
|
|
1987
|
+
aspectRatio: "3 / 4"
|
|
1781
1988
|
};
|
|
1782
1989
|
var faceDashedRingStyle = {
|
|
1783
1990
|
position: "absolute",
|
|
1784
1991
|
inset: -6,
|
|
1785
|
-
borderRadius:
|
|
1786
|
-
border: "6px dashed rgba(255, 255, 255, 0.4)"
|
|
1787
|
-
animation: "spin 10s linear infinite"
|
|
1992
|
+
borderRadius: 9999,
|
|
1993
|
+
border: "6px dashed rgba(255, 255, 255, 0.4)"
|
|
1788
1994
|
};
|
|
1789
1995
|
var faceCircleStyle = {
|
|
1790
1996
|
position: "absolute",
|
|
1791
1997
|
inset: 0,
|
|
1792
|
-
|
|
1998
|
+
// Tailwind's `rounded-full` resolves to a very large fixed radius. On a
|
|
1999
|
+
// 3:4 box that creates the normal KYC capsule/head guide with straighter
|
|
2000
|
+
// sides; 50% would instead produce a mathematically perfect ellipse.
|
|
2001
|
+
borderRadius: 9999,
|
|
1793
2002
|
border: "4px solid #00bc7d",
|
|
1794
2003
|
boxShadow: "0 0 0 1000px rgba(0, 0, 0, 0.5)"
|
|
1795
2004
|
};
|
|
@@ -2132,6 +2341,7 @@ function isValidFileSize(file, maxSizeBytes = 10 * 1024 * 1024) {
|
|
|
2132
2341
|
}
|
|
2133
2342
|
function formatKycStatus(status) {
|
|
2134
2343
|
const statusMap = {
|
|
2344
|
+
awaiting_kyc: "Awaiting KYC",
|
|
2135
2345
|
pending: "Pending Review",
|
|
2136
2346
|
accepted: "Approved",
|
|
2137
2347
|
declined: "Rejected",
|
|
@@ -2142,6 +2352,8 @@ function formatKycStatus(status) {
|
|
|
2142
2352
|
}
|
|
2143
2353
|
function getStatusColor(status) {
|
|
2144
2354
|
const colorMap = {
|
|
2355
|
+
awaiting_kyc: "#94a3b8",
|
|
2356
|
+
// slate — not started
|
|
2145
2357
|
pending: "#f59e0b",
|
|
2146
2358
|
// amber
|
|
2147
2359
|
accepted: "#10b981",
|
|
@@ -2168,6 +2380,13 @@ function getRiskColor(risk) {
|
|
|
2168
2380
|
};
|
|
2169
2381
|
return colorMap[risk] || "#6b7280";
|
|
2170
2382
|
}
|
|
2383
|
+
function isMobileDevice(userAgent) {
|
|
2384
|
+
const ua = userAgent ?? (typeof navigator === "undefined" ? "" : navigator.userAgent);
|
|
2385
|
+
return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
|
|
2386
|
+
}
|
|
2387
|
+
function buildEventBasedFaceVerificationQrPayload(token) {
|
|
2388
|
+
return `vesant://reuse-kyc?token=${encodeURIComponent(token)}`;
|
|
2389
|
+
}
|
|
2171
2390
|
function useEventBasedFaceVerificationSubmission(client$1) {
|
|
2172
2391
|
const startSession = React.useCallback(
|
|
2173
2392
|
(request) => client$1.createEventBasedFaceVerificationSession(request),
|
|
@@ -2215,12 +2434,15 @@ function useEventBasedFaceVerificationSubmission(client$1) {
|
|
|
2215
2434
|
return { startSession, verifyFace, runFlow };
|
|
2216
2435
|
}
|
|
2217
2436
|
|
|
2437
|
+
exports.FaceCaptureModal = FaceCaptureModal_default;
|
|
2438
|
+
exports.buildEventBasedFaceVerificationQrPayload = buildEventBasedFaceVerificationQrPayload;
|
|
2218
2439
|
exports.createDeviceFingerprint = createDeviceFingerprint;
|
|
2219
2440
|
exports.fileToBase64 = fileToBase64;
|
|
2220
2441
|
exports.formatKycStatus = formatKycStatus;
|
|
2221
2442
|
exports.getBrowserInfo = getBrowserInfo;
|
|
2222
2443
|
exports.getRiskColor = getRiskColor;
|
|
2223
2444
|
exports.getStatusColor = getStatusColor;
|
|
2445
|
+
exports.isMobileDevice = isMobileDevice;
|
|
2224
2446
|
exports.isValidFileSize = isValidFileSize;
|
|
2225
2447
|
exports.isValidFileType = isValidFileType;
|
|
2226
2448
|
exports.useCipherText = useCipherText;
|