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.mjs
CHANGED
|
@@ -924,19 +924,19 @@ var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id
|
|
|
924
924
|
var FACE_MESSAGES = {
|
|
925
925
|
idle: "Initializing camera...",
|
|
926
926
|
loading: "Loading face detection...",
|
|
927
|
-
scanning: "Position your face in the
|
|
927
|
+
scanning: "Position your face in the oval",
|
|
928
928
|
too_far: "Face too far. Please move closer to the camera",
|
|
929
929
|
too_close: "Too close. Please move back a little",
|
|
930
|
-
off_center: "Center your face in the
|
|
930
|
+
off_center: "Center your face in the oval",
|
|
931
931
|
ok: "Looking great! Tap the button to capture"
|
|
932
932
|
};
|
|
933
933
|
var FACE_COLORS = {
|
|
934
934
|
idle: "#9ca3af",
|
|
935
|
-
loading: "#
|
|
936
|
-
scanning: "#
|
|
935
|
+
loading: "#ef4444",
|
|
936
|
+
scanning: "#ef4444",
|
|
937
937
|
too_far: "#ef4444",
|
|
938
|
-
too_close: "#
|
|
939
|
-
off_center: "#
|
|
938
|
+
too_close: "#ef4444",
|
|
939
|
+
off_center: "#ef4444",
|
|
940
940
|
ok: "#10b981"
|
|
941
941
|
};
|
|
942
942
|
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
@@ -1001,11 +1001,77 @@ function loadMediaPipeFaceDetection(assetBasePath) {
|
|
|
1001
1001
|
return loader;
|
|
1002
1002
|
}
|
|
1003
1003
|
|
|
1004
|
+
// src/kyc/hostedJourney.ts
|
|
1005
|
+
var REUSE_KYC_SESSION_EXPIRY_MS = 10 * 60 * 1e3;
|
|
1006
|
+
var JOURNEY_FRAME_HEIGHT = "clamp(500px, 64vh, 680px)";
|
|
1007
|
+
var JOURNEY_CONFIRM_TIMEOUT_MS = 45e3;
|
|
1008
|
+
function journeyOutcomeFromMessage(data) {
|
|
1009
|
+
const status = data?.verification_status;
|
|
1010
|
+
if (typeof status !== "string") return null;
|
|
1011
|
+
switch (status) {
|
|
1012
|
+
case "verification.accepted":
|
|
1013
|
+
return "accepted";
|
|
1014
|
+
case "verification.declined":
|
|
1015
|
+
return "declined";
|
|
1016
|
+
case "verification.cancelled":
|
|
1017
|
+
case "request.timeout":
|
|
1018
|
+
case "request.invalid":
|
|
1019
|
+
case "request.unauthorized":
|
|
1020
|
+
case "request.deleted":
|
|
1021
|
+
return "cancelled";
|
|
1022
|
+
default:
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function journeyPollIntervalMs(confirming) {
|
|
1027
|
+
return confirming ? 1e3 : 2500;
|
|
1028
|
+
}
|
|
1029
|
+
function pendingJourneyResult(reference) {
|
|
1030
|
+
return {
|
|
1031
|
+
event: "reuse_kyc",
|
|
1032
|
+
reference,
|
|
1033
|
+
resource_id: "",
|
|
1034
|
+
status: "pending",
|
|
1035
|
+
data: {
|
|
1036
|
+
retries_remaining: 0,
|
|
1037
|
+
retry_limit_exceeded: false,
|
|
1038
|
+
trigger_alert: false,
|
|
1039
|
+
enforce_logout: false,
|
|
1040
|
+
freeze_account: false,
|
|
1041
|
+
block: false,
|
|
1042
|
+
freeze_duration_minutes: 0
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function tryStartOperation(guard) {
|
|
1047
|
+
if (guard.current) return false;
|
|
1048
|
+
guard.current = true;
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
function releaseOperation(guard) {
|
|
1052
|
+
guard.current = false;
|
|
1053
|
+
}
|
|
1054
|
+
function retryTarget(hasHostedJourney) {
|
|
1055
|
+
return hasHostedJourney ? "journey" : "capture";
|
|
1056
|
+
}
|
|
1057
|
+
var CAPTURE_MODAL_MAX_WIDTH = 448;
|
|
1058
|
+
function modalMaxWidth(hasHostedJourney) {
|
|
1059
|
+
return hasHostedJourney ? CAPTURE_MODAL_MAX_WIDTH : CAPTURE_MODAL_MAX_WIDTH;
|
|
1060
|
+
}
|
|
1061
|
+
var JOURNEY_CONTENT_PADDING = 12;
|
|
1062
|
+
function resultForModalClose(stageKind, result, unconfirmed) {
|
|
1063
|
+
const terminal = stageKind === "accepted" || stageKind === "declined" || stageKind === "max_attempts";
|
|
1064
|
+
if (terminal && result !== void 0) return result;
|
|
1065
|
+
const awaitingConfirmation = stageKind === "confirming" || stageKind === "unconfirmed";
|
|
1066
|
+
if (awaitingConfirmation && unconfirmed !== void 0) return unconfirmed;
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1004
1070
|
// src/kyc/FaceCaptureModal.tsx
|
|
1005
1071
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1006
|
-
var
|
|
1072
|
+
var ACCEPTED_AUTO_CLOSE_MS = 1200;
|
|
1007
1073
|
var LIVENESS_MESSAGES = [
|
|
1008
|
-
"Position your face inside the
|
|
1074
|
+
"Position your face inside the oval",
|
|
1009
1075
|
"Make sure your face is well lit",
|
|
1010
1076
|
"Remove glasses or hats if possible",
|
|
1011
1077
|
"Look straight at the camera, then tap Capture"
|
|
@@ -1014,6 +1080,12 @@ function headerSubtitle(stageKind) {
|
|
|
1014
1080
|
switch (stageKind) {
|
|
1015
1081
|
case "qr":
|
|
1016
1082
|
return "Use your phone to continue";
|
|
1083
|
+
case "journey":
|
|
1084
|
+
return "Follow the steps to verify your identity";
|
|
1085
|
+
case "confirming":
|
|
1086
|
+
return "Confirming your result";
|
|
1087
|
+
case "unconfirmed":
|
|
1088
|
+
return "Still confirming";
|
|
1017
1089
|
case "accepted":
|
|
1018
1090
|
return "All set";
|
|
1019
1091
|
case "declined":
|
|
@@ -1039,7 +1111,9 @@ function FaceCaptureModal({
|
|
|
1039
1111
|
}) {
|
|
1040
1112
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
1041
1113
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
1114
|
+
const journeyMode = Boolean(session.verification_url);
|
|
1042
1115
|
const initialStage = (() => {
|
|
1116
|
+
if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
|
|
1043
1117
|
if (initialChoice === "this") return { kind: "capture" };
|
|
1044
1118
|
if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
1045
1119
|
return { kind: "choose" };
|
|
@@ -1057,19 +1131,20 @@ function FaceCaptureModal({
|
|
|
1057
1131
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
1058
1132
|
const cancelledRef = useRef(false);
|
|
1059
1133
|
const submittingRef = useRef(false);
|
|
1134
|
+
const journeyRetryingRef = useRef(false);
|
|
1060
1135
|
useEffect(() => {
|
|
1061
1136
|
const timer = setTimeout(() => {
|
|
1062
1137
|
setStage(
|
|
1063
|
-
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1138
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
|
|
1064
1139
|
);
|
|
1065
|
-
},
|
|
1140
|
+
}, REUSE_KYC_SESSION_EXPIRY_MS);
|
|
1066
1141
|
return () => clearTimeout(timer);
|
|
1067
1142
|
}, [session.token]);
|
|
1068
1143
|
useEffect(() => {
|
|
1069
1144
|
if (stage.kind !== "qr") return;
|
|
1070
1145
|
let stopped = false;
|
|
1071
1146
|
const intervalMs = 2e3;
|
|
1072
|
-
const deadline = Date.now() +
|
|
1147
|
+
const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
|
|
1073
1148
|
let mobileSeen = stage.mobileConnected;
|
|
1074
1149
|
const tick = async () => {
|
|
1075
1150
|
if (stopped || cancelledRef.current) return;
|
|
@@ -1110,6 +1185,82 @@ function FaceCaptureModal({
|
|
|
1110
1185
|
stopped = true;
|
|
1111
1186
|
};
|
|
1112
1187
|
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
1188
|
+
const journeyUrl = stage.kind === "journey" || stage.kind === "confirming" ? stage.url : "";
|
|
1189
|
+
useEffect(() => {
|
|
1190
|
+
if (stage.kind !== "journey" && stage.kind !== "confirming" || !journeyUrl) return;
|
|
1191
|
+
const intervalMs = journeyPollIntervalMs(stage.kind === "confirming");
|
|
1192
|
+
let stopped = false;
|
|
1193
|
+
const checkOnce = async () => {
|
|
1194
|
+
try {
|
|
1195
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1196
|
+
if (applyResult(result)) {
|
|
1197
|
+
stopped = true;
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
} catch {
|
|
1201
|
+
}
|
|
1202
|
+
return false;
|
|
1203
|
+
};
|
|
1204
|
+
const tick = async () => {
|
|
1205
|
+
if (stopped || cancelledRef.current) return;
|
|
1206
|
+
await checkOnce();
|
|
1207
|
+
if (!stopped && !cancelledRef.current) {
|
|
1208
|
+
setTimeout(tick, intervalMs);
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
void tick();
|
|
1212
|
+
let frameOrigin = "";
|
|
1213
|
+
try {
|
|
1214
|
+
frameOrigin = new URL(journeyUrl).origin;
|
|
1215
|
+
} catch {
|
|
1216
|
+
frameOrigin = "";
|
|
1217
|
+
}
|
|
1218
|
+
const onMessage = (e) => {
|
|
1219
|
+
if (!frameOrigin || e.origin !== frameOrigin) return;
|
|
1220
|
+
const outcome = journeyOutcomeFromMessage(e.data);
|
|
1221
|
+
if (!outcome) return;
|
|
1222
|
+
void checkOnce();
|
|
1223
|
+
setStage((prev) => {
|
|
1224
|
+
if (prev.kind !== "journey") return prev;
|
|
1225
|
+
if (outcome === "cancelled") {
|
|
1226
|
+
return { kind: "error", message: "The verification was closed before it finished. Please try again." };
|
|
1227
|
+
}
|
|
1228
|
+
return { kind: "confirming", outcome, url: prev.url };
|
|
1229
|
+
});
|
|
1230
|
+
};
|
|
1231
|
+
window.addEventListener("message", onMessage);
|
|
1232
|
+
return () => {
|
|
1233
|
+
stopped = true;
|
|
1234
|
+
window.removeEventListener("message", onMessage);
|
|
1235
|
+
};
|
|
1236
|
+
}, [stage.kind, journeyUrl]);
|
|
1237
|
+
useEffect(() => {
|
|
1238
|
+
if (stage.kind !== "confirming") return;
|
|
1239
|
+
const outcome = stage.outcome;
|
|
1240
|
+
const timer = setTimeout(() => {
|
|
1241
|
+
setStage((prev) => prev.kind === "confirming" ? { kind: "unconfirmed", outcome } : prev);
|
|
1242
|
+
}, JOURNEY_CONFIRM_TIMEOUT_MS);
|
|
1243
|
+
return () => clearTimeout(timer);
|
|
1244
|
+
}, [stage.kind, stage.kind === "confirming" ? stage.outcome : ""]);
|
|
1245
|
+
const handleJourneyRetry = async () => {
|
|
1246
|
+
if (!tryStartOperation(journeyRetryingRef)) return;
|
|
1247
|
+
setStage({ kind: "journey", url: "" });
|
|
1248
|
+
try {
|
|
1249
|
+
const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
|
|
1250
|
+
if (refreshed.verification_url) {
|
|
1251
|
+
setStage({ kind: "journey", url: refreshed.verification_url });
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
|
|
1255
|
+
} catch (err) {
|
|
1256
|
+
setStage({
|
|
1257
|
+
kind: "error",
|
|
1258
|
+
message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
|
|
1259
|
+
});
|
|
1260
|
+
} finally {
|
|
1261
|
+
releaseOperation(journeyRetryingRef);
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1113
1264
|
const applyResult = (result) => {
|
|
1114
1265
|
if (result.status === "accepted") {
|
|
1115
1266
|
setStage({ kind: "accepted", result });
|
|
@@ -1237,12 +1388,23 @@ function FaceCaptureModal({
|
|
|
1237
1388
|
}, 3e3);
|
|
1238
1389
|
return () => clearInterval(interval);
|
|
1239
1390
|
}, [captureMode]);
|
|
1240
|
-
const close = (result) => {
|
|
1391
|
+
const close = useCallback((result) => {
|
|
1241
1392
|
cancelledRef.current = true;
|
|
1242
1393
|
if (result === null && onCancel) {
|
|
1243
1394
|
onCancel();
|
|
1244
1395
|
}
|
|
1245
1396
|
onComplete(result);
|
|
1397
|
+
}, [onCancel, onComplete]);
|
|
1398
|
+
useEffect(() => {
|
|
1399
|
+
if (stage.kind !== "accepted") return;
|
|
1400
|
+
const result = stage.result;
|
|
1401
|
+
const timer = setTimeout(() => close(result), ACCEPTED_AUTO_CLOSE_MS);
|
|
1402
|
+
return () => clearTimeout(timer);
|
|
1403
|
+
}, [close, stage]);
|
|
1404
|
+
const handleHeaderClose = () => {
|
|
1405
|
+
const result = "result" in stage ? stage.result : void 0;
|
|
1406
|
+
const unconfirmed = stage.kind === "confirming" || stage.kind === "unconfirmed" ? pendingJourneyResult(session.reference) : void 0;
|
|
1407
|
+
close(resultForModalClose(stage.kind, result, unconfirmed));
|
|
1246
1408
|
};
|
|
1247
1409
|
const renderChoose = () => /* @__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("p", { style: subtitleStyle }, "How would you like to verify?"), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement(
|
|
1248
1410
|
"button",
|
|
@@ -1301,12 +1463,37 @@ function FaceCaptureModal({
|
|
|
1301
1463
|
};
|
|
1302
1464
|
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."));
|
|
1303
1465
|
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")));
|
|
1304
|
-
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));
|
|
1466
|
+
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 }, journeyMode ? /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => void handleJourneyRetry() }, /* @__PURE__ */ React.createElement("span", null, "Try Again")) : /* @__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));
|
|
1305
1467
|
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")));
|
|
1306
1468
|
const renderError = (message) => {
|
|
1307
1469
|
const sessionExpired = /session\s+expired/i.test(message);
|
|
1308
|
-
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(
|
|
1470
|
+
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(
|
|
1471
|
+
"button",
|
|
1472
|
+
{
|
|
1473
|
+
style: primaryButtonStyle,
|
|
1474
|
+
onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
|
|
1475
|
+
},
|
|
1476
|
+
"Try Again"
|
|
1477
|
+
), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1309
1478
|
};
|
|
1479
|
+
const renderConfirming = () => /* @__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("h3", { style: titleStyle }, "Confirming your result\u2026"), /* @__PURE__ */ React.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
|
|
1480
|
+
const renderUnconfirmed = (outcome) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Still confirming"), /* @__PURE__ */ React.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.createElement("div", { style: buttonsContainerStyle }, outcome === "declined" && /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => void handleJourneyRetry() }, "Try Again"), /* @__PURE__ */ React.createElement(
|
|
1481
|
+
"button",
|
|
1482
|
+
{
|
|
1483
|
+
style: outcome === "accepted" ? primaryButtonStyle : cancelButtonStyle,
|
|
1484
|
+
onClick: () => close(pendingJourneyResult(session.reference))
|
|
1485
|
+
},
|
|
1486
|
+
"Close"
|
|
1487
|
+
)));
|
|
1488
|
+
const renderJourney = (url) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, padding: JOURNEY_CONTENT_PADDING } }, url ? /* @__PURE__ */ React.createElement(
|
|
1489
|
+
"iframe",
|
|
1490
|
+
{
|
|
1491
|
+
src: url,
|
|
1492
|
+
title: "Identity verification",
|
|
1493
|
+
allow: "camera; microphone",
|
|
1494
|
+
style: journeyFrameStyle
|
|
1495
|
+
}
|
|
1496
|
+
) : /* @__PURE__ */ React.createElement("div", { style: { padding: 24, textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "Preparing your next attempt\u2026")), /* @__PURE__ */ React.createElement("p", { style: { ...attemptsTextStyle, textAlign: "center" } }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
|
|
1310
1497
|
const body = (() => {
|
|
1311
1498
|
switch (stage.kind) {
|
|
1312
1499
|
case "choose":
|
|
@@ -1315,6 +1502,12 @@ function FaceCaptureModal({
|
|
|
1315
1502
|
return renderQRStage(stage.mobileConnected);
|
|
1316
1503
|
case "capture":
|
|
1317
1504
|
return renderCapture(stage.declinedReason);
|
|
1505
|
+
case "journey":
|
|
1506
|
+
return renderJourney(stage.url);
|
|
1507
|
+
case "confirming":
|
|
1508
|
+
return renderConfirming();
|
|
1509
|
+
case "unconfirmed":
|
|
1510
|
+
return renderUnconfirmed(stage.outcome);
|
|
1318
1511
|
case "submitting":
|
|
1319
1512
|
return renderSubmitting();
|
|
1320
1513
|
case "accepted":
|
|
@@ -1327,10 +1520,10 @@ function FaceCaptureModal({
|
|
|
1327
1520
|
return renderError(stage.message);
|
|
1328
1521
|
}
|
|
1329
1522
|
})();
|
|
1330
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("style", null, keyframes), /* @__PURE__ */ React.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React.createElement("div", { style: modalStyle }, /* @__PURE__ */ React.createElement("div", { style: headerStyle }, /* @__PURE__ */ React.createElement(
|
|
1523
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("style", null, keyframes), /* @__PURE__ */ React.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React.createElement("div", { style: { ...modalStyle, maxWidth: modalMaxWidth(journeyMode) } }, /* @__PURE__ */ React.createElement("div", { style: headerStyle }, /* @__PURE__ */ React.createElement(
|
|
1331
1524
|
"button",
|
|
1332
1525
|
{
|
|
1333
|
-
onClick:
|
|
1526
|
+
onClick: handleHeaderClose,
|
|
1334
1527
|
style: closeButtonStyle,
|
|
1335
1528
|
"aria-label": "Close"
|
|
1336
1529
|
},
|
|
@@ -1360,7 +1553,8 @@ var modalStyle = {
|
|
|
1360
1553
|
borderRadius: 16,
|
|
1361
1554
|
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1362
1555
|
width: "100%",
|
|
1363
|
-
|
|
1556
|
+
maxHeight: "calc(100vh - 32px)",
|
|
1557
|
+
overflowY: "auto",
|
|
1364
1558
|
animation: "zoomIn .2s ease-out"
|
|
1365
1559
|
};
|
|
1366
1560
|
var headerStyle = {
|
|
@@ -1386,6 +1580,13 @@ var contentStyle = {
|
|
|
1386
1580
|
flexDirection: "column",
|
|
1387
1581
|
gap: 16
|
|
1388
1582
|
};
|
|
1583
|
+
var journeyFrameStyle = {
|
|
1584
|
+
width: "100%",
|
|
1585
|
+
height: JOURNEY_FRAME_HEIGHT,
|
|
1586
|
+
border: 0,
|
|
1587
|
+
borderRadius: 12,
|
|
1588
|
+
background: "#f9fafb"
|
|
1589
|
+
};
|
|
1389
1590
|
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1390
1591
|
var visualGuideStyle = { position: "relative" };
|
|
1391
1592
|
var circleStyle = {
|
|
@@ -1652,9 +1853,10 @@ function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
|
|
|
1652
1853
|
if (error) {
|
|
1653
1854
|
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")));
|
|
1654
1855
|
}
|
|
1655
|
-
const
|
|
1656
|
-
const
|
|
1657
|
-
const
|
|
1856
|
+
const detectionAvailable = !faceDetectionFailed;
|
|
1857
|
+
const detectionActive = ready && detectionAvailable;
|
|
1858
|
+
const liveMessage = detectionAvailable ? FACE_MESSAGES[faceStatus] : message;
|
|
1859
|
+
const liveFaceColor = detectionAvailable ? FACE_COLORS[faceStatus] : void 0;
|
|
1658
1860
|
const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
|
|
1659
1861
|
const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
|
|
1660
1862
|
const dynamicCircleStyle = {
|
|
@@ -1770,20 +1972,22 @@ var faceOverlayContainerStyle = {
|
|
|
1770
1972
|
};
|
|
1771
1973
|
var faceRingWrapStyle = {
|
|
1772
1974
|
position: "relative",
|
|
1773
|
-
|
|
1774
|
-
aspectRatio: "
|
|
1975
|
+
height: "64%",
|
|
1976
|
+
aspectRatio: "3 / 4"
|
|
1775
1977
|
};
|
|
1776
1978
|
var faceDashedRingStyle = {
|
|
1777
1979
|
position: "absolute",
|
|
1778
1980
|
inset: -6,
|
|
1779
|
-
borderRadius:
|
|
1780
|
-
border: "6px dashed rgba(255, 255, 255, 0.4)"
|
|
1781
|
-
animation: "spin 10s linear infinite"
|
|
1981
|
+
borderRadius: 9999,
|
|
1982
|
+
border: "6px dashed rgba(255, 255, 255, 0.4)"
|
|
1782
1983
|
};
|
|
1783
1984
|
var faceCircleStyle = {
|
|
1784
1985
|
position: "absolute",
|
|
1785
1986
|
inset: 0,
|
|
1786
|
-
|
|
1987
|
+
// Tailwind's `rounded-full` resolves to a very large fixed radius. On a
|
|
1988
|
+
// 3:4 box that creates the normal KYC capsule/head guide with straighter
|
|
1989
|
+
// sides; 50% would instead produce a mathematically perfect ellipse.
|
|
1990
|
+
borderRadius: 9999,
|
|
1787
1991
|
border: "4px solid #00bc7d",
|
|
1788
1992
|
boxShadow: "0 0 0 1000px rgba(0, 0, 0, 0.5)"
|
|
1789
1993
|
};
|
|
@@ -2126,6 +2330,7 @@ function isValidFileSize(file, maxSizeBytes = 10 * 1024 * 1024) {
|
|
|
2126
2330
|
}
|
|
2127
2331
|
function formatKycStatus(status) {
|
|
2128
2332
|
const statusMap = {
|
|
2333
|
+
awaiting_kyc: "Awaiting KYC",
|
|
2129
2334
|
pending: "Pending Review",
|
|
2130
2335
|
accepted: "Approved",
|
|
2131
2336
|
declined: "Rejected",
|
|
@@ -2136,6 +2341,8 @@ function formatKycStatus(status) {
|
|
|
2136
2341
|
}
|
|
2137
2342
|
function getStatusColor(status) {
|
|
2138
2343
|
const colorMap = {
|
|
2344
|
+
awaiting_kyc: "#94a3b8",
|
|
2345
|
+
// slate — not started
|
|
2139
2346
|
pending: "#f59e0b",
|
|
2140
2347
|
// amber
|
|
2141
2348
|
accepted: "#10b981",
|
|
@@ -2162,6 +2369,13 @@ function getRiskColor(risk) {
|
|
|
2162
2369
|
};
|
|
2163
2370
|
return colorMap[risk] || "#6b7280";
|
|
2164
2371
|
}
|
|
2372
|
+
function isMobileDevice(userAgent) {
|
|
2373
|
+
const ua = userAgent ?? (typeof navigator === "undefined" ? "" : navigator.userAgent);
|
|
2374
|
+
return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
|
|
2375
|
+
}
|
|
2376
|
+
function buildEventBasedFaceVerificationQrPayload(token) {
|
|
2377
|
+
return `vesant://reuse-kyc?token=${encodeURIComponent(token)}`;
|
|
2378
|
+
}
|
|
2165
2379
|
function useEventBasedFaceVerificationSubmission(client) {
|
|
2166
2380
|
const startSession = useCallback(
|
|
2167
2381
|
(request) => client.createEventBasedFaceVerificationSession(request),
|
|
@@ -2209,6 +2423,6 @@ function useEventBasedFaceVerificationSubmission(client) {
|
|
|
2209
2423
|
return { startSession, verifyFace, runFlow };
|
|
2210
2424
|
}
|
|
2211
2425
|
|
|
2212
|
-
export { createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useEventBasedFaceVerificationSubmission, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useTransactionVerification };
|
|
2426
|
+
export { FaceCaptureModal_default as FaceCaptureModal, buildEventBasedFaceVerificationQrPayload, createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isMobileDevice, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useEventBasedFaceVerificationSubmission, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useTransactionVerification };
|
|
2213
2427
|
//# sourceMappingURL=react.mjs.map
|
|
2214
2428
|
//# sourceMappingURL=react.mjs.map
|