vesant-sdk 1.7.2-dev.617dd44 → 1.7.2
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/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +74 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +74 -30
- 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 +117 -46
- package/dist/react.d.ts +117 -46
- package/dist/react.js +243 -26
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +241 -27
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -930,19 +930,19 @@ var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id
|
|
|
930
930
|
var FACE_MESSAGES = {
|
|
931
931
|
idle: "Initializing camera...",
|
|
932
932
|
loading: "Loading face detection...",
|
|
933
|
-
scanning: "Position your face in the
|
|
933
|
+
scanning: "Position your face in the oval",
|
|
934
934
|
too_far: "Face too far. Please move closer to the camera",
|
|
935
935
|
too_close: "Too close. Please move back a little",
|
|
936
|
-
off_center: "Center your face in the
|
|
936
|
+
off_center: "Center your face in the oval",
|
|
937
937
|
ok: "Looking great! Tap the button to capture"
|
|
938
938
|
};
|
|
939
939
|
var FACE_COLORS = {
|
|
940
940
|
idle: "#9ca3af",
|
|
941
|
-
loading: "#
|
|
942
|
-
scanning: "#
|
|
941
|
+
loading: "#ef4444",
|
|
942
|
+
scanning: "#ef4444",
|
|
943
943
|
too_far: "#ef4444",
|
|
944
|
-
too_close: "#
|
|
945
|
-
off_center: "#
|
|
944
|
+
too_close: "#ef4444",
|
|
945
|
+
off_center: "#ef4444",
|
|
946
946
|
ok: "#10b981"
|
|
947
947
|
};
|
|
948
948
|
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
@@ -1007,11 +1007,77 @@ function loadMediaPipeFaceDetection(assetBasePath) {
|
|
|
1007
1007
|
return loader;
|
|
1008
1008
|
}
|
|
1009
1009
|
|
|
1010
|
+
// src/kyc/hostedJourney.ts
|
|
1011
|
+
var REUSE_KYC_SESSION_EXPIRY_MS = 10 * 60 * 1e3;
|
|
1012
|
+
var JOURNEY_FRAME_HEIGHT = "clamp(500px, 64vh, 680px)";
|
|
1013
|
+
var JOURNEY_CONFIRM_TIMEOUT_MS = 45e3;
|
|
1014
|
+
function journeyOutcomeFromMessage(data) {
|
|
1015
|
+
const status = data?.verification_status;
|
|
1016
|
+
if (typeof status !== "string") return null;
|
|
1017
|
+
switch (status) {
|
|
1018
|
+
case "verification.accepted":
|
|
1019
|
+
return "accepted";
|
|
1020
|
+
case "verification.declined":
|
|
1021
|
+
return "declined";
|
|
1022
|
+
case "verification.cancelled":
|
|
1023
|
+
case "request.timeout":
|
|
1024
|
+
case "request.invalid":
|
|
1025
|
+
case "request.unauthorized":
|
|
1026
|
+
case "request.deleted":
|
|
1027
|
+
return "cancelled";
|
|
1028
|
+
default:
|
|
1029
|
+
return null;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function journeyPollIntervalMs(confirming) {
|
|
1033
|
+
return confirming ? 1e3 : 2500;
|
|
1034
|
+
}
|
|
1035
|
+
function pendingJourneyResult(reference) {
|
|
1036
|
+
return {
|
|
1037
|
+
event: "reuse_kyc",
|
|
1038
|
+
reference,
|
|
1039
|
+
resource_id: "",
|
|
1040
|
+
status: "pending",
|
|
1041
|
+
data: {
|
|
1042
|
+
retries_remaining: 0,
|
|
1043
|
+
retry_limit_exceeded: false,
|
|
1044
|
+
trigger_alert: false,
|
|
1045
|
+
enforce_logout: false,
|
|
1046
|
+
freeze_account: false,
|
|
1047
|
+
block: false,
|
|
1048
|
+
freeze_duration_minutes: 0
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function tryStartOperation(guard) {
|
|
1053
|
+
if (guard.current) return false;
|
|
1054
|
+
guard.current = true;
|
|
1055
|
+
return true;
|
|
1056
|
+
}
|
|
1057
|
+
function releaseOperation(guard) {
|
|
1058
|
+
guard.current = false;
|
|
1059
|
+
}
|
|
1060
|
+
function retryTarget(hasHostedJourney) {
|
|
1061
|
+
return hasHostedJourney ? "journey" : "capture";
|
|
1062
|
+
}
|
|
1063
|
+
var CAPTURE_MODAL_MAX_WIDTH = 448;
|
|
1064
|
+
function modalMaxWidth(hasHostedJourney) {
|
|
1065
|
+
return hasHostedJourney ? CAPTURE_MODAL_MAX_WIDTH : CAPTURE_MODAL_MAX_WIDTH;
|
|
1066
|
+
}
|
|
1067
|
+
var JOURNEY_CONTENT_PADDING = 12;
|
|
1068
|
+
function resultForModalClose(stageKind, result, unconfirmed) {
|
|
1069
|
+
const terminal = stageKind === "accepted" || stageKind === "declined" || stageKind === "max_attempts";
|
|
1070
|
+
if (terminal && result !== void 0) return result;
|
|
1071
|
+
const awaitingConfirmation = stageKind === "confirming" || stageKind === "unconfirmed";
|
|
1072
|
+
if (awaitingConfirmation && unconfirmed !== void 0) return unconfirmed;
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1010
1076
|
// src/kyc/FaceCaptureModal.tsx
|
|
1011
1077
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1012
|
-
var
|
|
1078
|
+
var ACCEPTED_AUTO_CLOSE_MS = 1200;
|
|
1013
1079
|
var LIVENESS_MESSAGES = [
|
|
1014
|
-
"Position your face inside the
|
|
1080
|
+
"Position your face inside the oval",
|
|
1015
1081
|
"Make sure your face is well lit",
|
|
1016
1082
|
"Remove glasses or hats if possible",
|
|
1017
1083
|
"Look straight at the camera, then tap Capture"
|
|
@@ -1020,6 +1086,12 @@ function headerSubtitle(stageKind) {
|
|
|
1020
1086
|
switch (stageKind) {
|
|
1021
1087
|
case "qr":
|
|
1022
1088
|
return "Use your phone to continue";
|
|
1089
|
+
case "journey":
|
|
1090
|
+
return "Follow the steps to verify your identity";
|
|
1091
|
+
case "confirming":
|
|
1092
|
+
return "Confirming your result";
|
|
1093
|
+
case "unconfirmed":
|
|
1094
|
+
return "Still confirming";
|
|
1023
1095
|
case "accepted":
|
|
1024
1096
|
return "All set";
|
|
1025
1097
|
case "declined":
|
|
@@ -1045,7 +1117,9 @@ function FaceCaptureModal({
|
|
|
1045
1117
|
}) {
|
|
1046
1118
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
1047
1119
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
1120
|
+
const journeyMode = Boolean(session.verification_url);
|
|
1048
1121
|
const initialStage = (() => {
|
|
1122
|
+
if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
|
|
1049
1123
|
if (initialChoice === "this") return { kind: "capture" };
|
|
1050
1124
|
if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
1051
1125
|
return { kind: "choose" };
|
|
@@ -1063,19 +1137,20 @@ function FaceCaptureModal({
|
|
|
1063
1137
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
1064
1138
|
const cancelledRef = React.useRef(false);
|
|
1065
1139
|
const submittingRef = React.useRef(false);
|
|
1140
|
+
const journeyRetryingRef = React.useRef(false);
|
|
1066
1141
|
React.useEffect(() => {
|
|
1067
1142
|
const timer = setTimeout(() => {
|
|
1068
1143
|
setStage(
|
|
1069
|
-
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1144
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
|
|
1070
1145
|
);
|
|
1071
|
-
},
|
|
1146
|
+
}, REUSE_KYC_SESSION_EXPIRY_MS);
|
|
1072
1147
|
return () => clearTimeout(timer);
|
|
1073
1148
|
}, [session.token]);
|
|
1074
1149
|
React.useEffect(() => {
|
|
1075
1150
|
if (stage.kind !== "qr") return;
|
|
1076
1151
|
let stopped = false;
|
|
1077
1152
|
const intervalMs = 2e3;
|
|
1078
|
-
const deadline = Date.now() +
|
|
1153
|
+
const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
|
|
1079
1154
|
let mobileSeen = stage.mobileConnected;
|
|
1080
1155
|
const tick = async () => {
|
|
1081
1156
|
if (stopped || cancelledRef.current) return;
|
|
@@ -1116,6 +1191,82 @@ function FaceCaptureModal({
|
|
|
1116
1191
|
stopped = true;
|
|
1117
1192
|
};
|
|
1118
1193
|
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
1194
|
+
const journeyUrl = stage.kind === "journey" || stage.kind === "confirming" ? stage.url : "";
|
|
1195
|
+
React.useEffect(() => {
|
|
1196
|
+
if (stage.kind !== "journey" && stage.kind !== "confirming" || !journeyUrl) return;
|
|
1197
|
+
const intervalMs = journeyPollIntervalMs(stage.kind === "confirming");
|
|
1198
|
+
let stopped = false;
|
|
1199
|
+
const checkOnce = async () => {
|
|
1200
|
+
try {
|
|
1201
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1202
|
+
if (applyResult(result)) {
|
|
1203
|
+
stopped = true;
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
} catch {
|
|
1207
|
+
}
|
|
1208
|
+
return false;
|
|
1209
|
+
};
|
|
1210
|
+
const tick = async () => {
|
|
1211
|
+
if (stopped || cancelledRef.current) return;
|
|
1212
|
+
await checkOnce();
|
|
1213
|
+
if (!stopped && !cancelledRef.current) {
|
|
1214
|
+
setTimeout(tick, intervalMs);
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
void tick();
|
|
1218
|
+
let frameOrigin = "";
|
|
1219
|
+
try {
|
|
1220
|
+
frameOrigin = new URL(journeyUrl).origin;
|
|
1221
|
+
} catch {
|
|
1222
|
+
frameOrigin = "";
|
|
1223
|
+
}
|
|
1224
|
+
const onMessage = (e) => {
|
|
1225
|
+
if (!frameOrigin || e.origin !== frameOrigin) return;
|
|
1226
|
+
const outcome = journeyOutcomeFromMessage(e.data);
|
|
1227
|
+
if (!outcome) return;
|
|
1228
|
+
void checkOnce();
|
|
1229
|
+
setStage((prev) => {
|
|
1230
|
+
if (prev.kind !== "journey") return prev;
|
|
1231
|
+
if (outcome === "cancelled") {
|
|
1232
|
+
return { kind: "error", message: "The verification was closed before it finished. Please try again." };
|
|
1233
|
+
}
|
|
1234
|
+
return { kind: "confirming", outcome, url: prev.url };
|
|
1235
|
+
});
|
|
1236
|
+
};
|
|
1237
|
+
window.addEventListener("message", onMessage);
|
|
1238
|
+
return () => {
|
|
1239
|
+
stopped = true;
|
|
1240
|
+
window.removeEventListener("message", onMessage);
|
|
1241
|
+
};
|
|
1242
|
+
}, [stage.kind, journeyUrl]);
|
|
1243
|
+
React.useEffect(() => {
|
|
1244
|
+
if (stage.kind !== "confirming") return;
|
|
1245
|
+
const outcome = stage.outcome;
|
|
1246
|
+
const timer = setTimeout(() => {
|
|
1247
|
+
setStage((prev) => prev.kind === "confirming" ? { kind: "unconfirmed", outcome } : prev);
|
|
1248
|
+
}, JOURNEY_CONFIRM_TIMEOUT_MS);
|
|
1249
|
+
return () => clearTimeout(timer);
|
|
1250
|
+
}, [stage.kind, stage.kind === "confirming" ? stage.outcome : ""]);
|
|
1251
|
+
const handleJourneyRetry = async () => {
|
|
1252
|
+
if (!tryStartOperation(journeyRetryingRef)) return;
|
|
1253
|
+
setStage({ kind: "journey", url: "" });
|
|
1254
|
+
try {
|
|
1255
|
+
const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
|
|
1256
|
+
if (refreshed.verification_url) {
|
|
1257
|
+
setStage({ kind: "journey", url: refreshed.verification_url });
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
|
|
1261
|
+
} catch (err) {
|
|
1262
|
+
setStage({
|
|
1263
|
+
kind: "error",
|
|
1264
|
+
message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
|
|
1265
|
+
});
|
|
1266
|
+
} finally {
|
|
1267
|
+
releaseOperation(journeyRetryingRef);
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1119
1270
|
const applyResult = (result) => {
|
|
1120
1271
|
if (result.status === "accepted") {
|
|
1121
1272
|
setStage({ kind: "accepted", result });
|
|
@@ -1243,12 +1394,23 @@ function FaceCaptureModal({
|
|
|
1243
1394
|
}, 3e3);
|
|
1244
1395
|
return () => clearInterval(interval);
|
|
1245
1396
|
}, [captureMode]);
|
|
1246
|
-
const close = (result) => {
|
|
1397
|
+
const close = React.useCallback((result) => {
|
|
1247
1398
|
cancelledRef.current = true;
|
|
1248
1399
|
if (result === null && onCancel) {
|
|
1249
1400
|
onCancel();
|
|
1250
1401
|
}
|
|
1251
1402
|
onComplete(result);
|
|
1403
|
+
}, [onCancel, onComplete]);
|
|
1404
|
+
React.useEffect(() => {
|
|
1405
|
+
if (stage.kind !== "accepted") return;
|
|
1406
|
+
const result = stage.result;
|
|
1407
|
+
const timer = setTimeout(() => close(result), ACCEPTED_AUTO_CLOSE_MS);
|
|
1408
|
+
return () => clearTimeout(timer);
|
|
1409
|
+
}, [close, stage]);
|
|
1410
|
+
const handleHeaderClose = () => {
|
|
1411
|
+
const result = "result" in stage ? stage.result : void 0;
|
|
1412
|
+
const unconfirmed = stage.kind === "confirming" || stage.kind === "unconfirmed" ? pendingJourneyResult(session.reference) : void 0;
|
|
1413
|
+
close(resultForModalClose(stage.kind, result, unconfirmed));
|
|
1252
1414
|
};
|
|
1253
1415
|
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
1416
|
"button",
|
|
@@ -1307,12 +1469,37 @@ function FaceCaptureModal({
|
|
|
1307
1469
|
};
|
|
1308
1470
|
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
1471
|
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));
|
|
1472
|
+
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
1473
|
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
1474
|
const renderError = (message) => {
|
|
1313
1475
|
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(
|
|
1476
|
+
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(
|
|
1477
|
+
"button",
|
|
1478
|
+
{
|
|
1479
|
+
style: primaryButtonStyle,
|
|
1480
|
+
onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
|
|
1481
|
+
},
|
|
1482
|
+
"Try Again"
|
|
1483
|
+
), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1315
1484
|
};
|
|
1485
|
+
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."));
|
|
1486
|
+
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(
|
|
1487
|
+
"button",
|
|
1488
|
+
{
|
|
1489
|
+
style: outcome === "accepted" ? primaryButtonStyle : cancelButtonStyle,
|
|
1490
|
+
onClick: () => close(pendingJourneyResult(session.reference))
|
|
1491
|
+
},
|
|
1492
|
+
"Close"
|
|
1493
|
+
)));
|
|
1494
|
+
const renderJourney = (url) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, padding: JOURNEY_CONTENT_PADDING } }, url ? /* @__PURE__ */ React__default.default.createElement(
|
|
1495
|
+
"iframe",
|
|
1496
|
+
{
|
|
1497
|
+
src: url,
|
|
1498
|
+
title: "Identity verification",
|
|
1499
|
+
allow: "camera; microphone",
|
|
1500
|
+
style: journeyFrameStyle
|
|
1501
|
+
}
|
|
1502
|
+
) : /* @__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
1503
|
const body = (() => {
|
|
1317
1504
|
switch (stage.kind) {
|
|
1318
1505
|
case "choose":
|
|
@@ -1321,6 +1508,12 @@ function FaceCaptureModal({
|
|
|
1321
1508
|
return renderQRStage(stage.mobileConnected);
|
|
1322
1509
|
case "capture":
|
|
1323
1510
|
return renderCapture(stage.declinedReason);
|
|
1511
|
+
case "journey":
|
|
1512
|
+
return renderJourney(stage.url);
|
|
1513
|
+
case "confirming":
|
|
1514
|
+
return renderConfirming();
|
|
1515
|
+
case "unconfirmed":
|
|
1516
|
+
return renderUnconfirmed(stage.outcome);
|
|
1324
1517
|
case "submitting":
|
|
1325
1518
|
return renderSubmitting();
|
|
1326
1519
|
case "accepted":
|
|
@@ -1333,10 +1526,10 @@ function FaceCaptureModal({
|
|
|
1333
1526
|
return renderError(stage.message);
|
|
1334
1527
|
}
|
|
1335
1528
|
})();
|
|
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(
|
|
1529
|
+
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
1530
|
"button",
|
|
1338
1531
|
{
|
|
1339
|
-
onClick:
|
|
1532
|
+
onClick: handleHeaderClose,
|
|
1340
1533
|
style: closeButtonStyle,
|
|
1341
1534
|
"aria-label": "Close"
|
|
1342
1535
|
},
|
|
@@ -1366,7 +1559,8 @@ var modalStyle = {
|
|
|
1366
1559
|
borderRadius: 16,
|
|
1367
1560
|
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1368
1561
|
width: "100%",
|
|
1369
|
-
|
|
1562
|
+
maxHeight: "calc(100vh - 32px)",
|
|
1563
|
+
overflowY: "auto",
|
|
1370
1564
|
animation: "zoomIn .2s ease-out"
|
|
1371
1565
|
};
|
|
1372
1566
|
var headerStyle = {
|
|
@@ -1392,6 +1586,13 @@ var contentStyle = {
|
|
|
1392
1586
|
flexDirection: "column",
|
|
1393
1587
|
gap: 16
|
|
1394
1588
|
};
|
|
1589
|
+
var journeyFrameStyle = {
|
|
1590
|
+
width: "100%",
|
|
1591
|
+
height: JOURNEY_FRAME_HEIGHT,
|
|
1592
|
+
border: 0,
|
|
1593
|
+
borderRadius: 12,
|
|
1594
|
+
background: "#f9fafb"
|
|
1595
|
+
};
|
|
1395
1596
|
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1396
1597
|
var visualGuideStyle = { position: "relative" };
|
|
1397
1598
|
var circleStyle = {
|
|
@@ -1658,9 +1859,10 @@ function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
|
|
|
1658
1859
|
if (error) {
|
|
1659
1860
|
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
1861
|
}
|
|
1661
|
-
const
|
|
1662
|
-
const
|
|
1663
|
-
const
|
|
1862
|
+
const detectionAvailable = !faceDetectionFailed;
|
|
1863
|
+
const detectionActive = ready && detectionAvailable;
|
|
1864
|
+
const liveMessage = detectionAvailable ? FACE_MESSAGES[faceStatus] : message;
|
|
1865
|
+
const liveFaceColor = detectionAvailable ? FACE_COLORS[faceStatus] : void 0;
|
|
1664
1866
|
const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
|
|
1665
1867
|
const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
|
|
1666
1868
|
const dynamicCircleStyle = {
|
|
@@ -1776,20 +1978,22 @@ var faceOverlayContainerStyle = {
|
|
|
1776
1978
|
};
|
|
1777
1979
|
var faceRingWrapStyle = {
|
|
1778
1980
|
position: "relative",
|
|
1779
|
-
|
|
1780
|
-
aspectRatio: "
|
|
1981
|
+
height: "64%",
|
|
1982
|
+
aspectRatio: "3 / 4"
|
|
1781
1983
|
};
|
|
1782
1984
|
var faceDashedRingStyle = {
|
|
1783
1985
|
position: "absolute",
|
|
1784
1986
|
inset: -6,
|
|
1785
|
-
borderRadius:
|
|
1786
|
-
border: "6px dashed rgba(255, 255, 255, 0.4)"
|
|
1787
|
-
animation: "spin 10s linear infinite"
|
|
1987
|
+
borderRadius: 9999,
|
|
1988
|
+
border: "6px dashed rgba(255, 255, 255, 0.4)"
|
|
1788
1989
|
};
|
|
1789
1990
|
var faceCircleStyle = {
|
|
1790
1991
|
position: "absolute",
|
|
1791
1992
|
inset: 0,
|
|
1792
|
-
|
|
1993
|
+
// Tailwind's `rounded-full` resolves to a very large fixed radius. On a
|
|
1994
|
+
// 3:4 box that creates the normal KYC capsule/head guide with straighter
|
|
1995
|
+
// sides; 50% would instead produce a mathematically perfect ellipse.
|
|
1996
|
+
borderRadius: 9999,
|
|
1793
1997
|
border: "4px solid #00bc7d",
|
|
1794
1998
|
boxShadow: "0 0 0 1000px rgba(0, 0, 0, 0.5)"
|
|
1795
1999
|
};
|
|
@@ -2132,6 +2336,7 @@ function isValidFileSize(file, maxSizeBytes = 10 * 1024 * 1024) {
|
|
|
2132
2336
|
}
|
|
2133
2337
|
function formatKycStatus(status) {
|
|
2134
2338
|
const statusMap = {
|
|
2339
|
+
awaiting_kyc: "Awaiting KYC",
|
|
2135
2340
|
pending: "Pending Review",
|
|
2136
2341
|
accepted: "Approved",
|
|
2137
2342
|
declined: "Rejected",
|
|
@@ -2142,6 +2347,8 @@ function formatKycStatus(status) {
|
|
|
2142
2347
|
}
|
|
2143
2348
|
function getStatusColor(status) {
|
|
2144
2349
|
const colorMap = {
|
|
2350
|
+
awaiting_kyc: "#94a3b8",
|
|
2351
|
+
// slate — not started
|
|
2145
2352
|
pending: "#f59e0b",
|
|
2146
2353
|
// amber
|
|
2147
2354
|
accepted: "#10b981",
|
|
@@ -2168,6 +2375,13 @@ function getRiskColor(risk) {
|
|
|
2168
2375
|
};
|
|
2169
2376
|
return colorMap[risk] || "#6b7280";
|
|
2170
2377
|
}
|
|
2378
|
+
function isMobileDevice(userAgent) {
|
|
2379
|
+
const ua = userAgent ?? (typeof navigator === "undefined" ? "" : navigator.userAgent);
|
|
2380
|
+
return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
|
|
2381
|
+
}
|
|
2382
|
+
function buildEventBasedFaceVerificationQrPayload(token) {
|
|
2383
|
+
return `vesant://reuse-kyc?token=${encodeURIComponent(token)}`;
|
|
2384
|
+
}
|
|
2171
2385
|
function useEventBasedFaceVerificationSubmission(client$1) {
|
|
2172
2386
|
const startSession = React.useCallback(
|
|
2173
2387
|
(request) => client$1.createEventBasedFaceVerificationSession(request),
|
|
@@ -2215,12 +2429,15 @@ function useEventBasedFaceVerificationSubmission(client$1) {
|
|
|
2215
2429
|
return { startSession, verifyFace, runFlow };
|
|
2216
2430
|
}
|
|
2217
2431
|
|
|
2432
|
+
exports.FaceCaptureModal = FaceCaptureModal_default;
|
|
2433
|
+
exports.buildEventBasedFaceVerificationQrPayload = buildEventBasedFaceVerificationQrPayload;
|
|
2218
2434
|
exports.createDeviceFingerprint = createDeviceFingerprint;
|
|
2219
2435
|
exports.fileToBase64 = fileToBase64;
|
|
2220
2436
|
exports.formatKycStatus = formatKycStatus;
|
|
2221
2437
|
exports.getBrowserInfo = getBrowserInfo;
|
|
2222
2438
|
exports.getRiskColor = getRiskColor;
|
|
2223
2439
|
exports.getStatusColor = getStatusColor;
|
|
2440
|
+
exports.isMobileDevice = isMobileDevice;
|
|
2224
2441
|
exports.isValidFileSize = isValidFileSize;
|
|
2225
2442
|
exports.isValidFileType = isValidFileType;
|
|
2226
2443
|
exports.useCipherText = useCipherText;
|