vesant-sdk 1.7.1-dev.9fc141f → 1.7.1-dev.a461ee2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +23 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -7
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.js +23 -7
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +23 -7
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +28 -10
- package/dist/kyc/index.d.ts +28 -10
- package/dist/kyc/index.js +23 -7
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +23 -7
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.js +113 -8
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +113 -8
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -1001,9 +1001,25 @@ 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
|
+
function tryStartOperation(guard) {
|
|
1007
|
+
if (guard.current) return false;
|
|
1008
|
+
guard.current = true;
|
|
1009
|
+
return true;
|
|
1010
|
+
}
|
|
1011
|
+
function releaseOperation(guard) {
|
|
1012
|
+
guard.current = false;
|
|
1013
|
+
}
|
|
1014
|
+
function retryTarget(hasHostedJourney) {
|
|
1015
|
+
return hasHostedJourney ? "journey" : "capture";
|
|
1016
|
+
}
|
|
1017
|
+
function modalMaxWidth(hasHostedJourney) {
|
|
1018
|
+
return hasHostedJourney ? 680 : 448;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1004
1021
|
// src/kyc/FaceCaptureModal.tsx
|
|
1005
1022
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1006
|
-
var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
|
|
1007
1023
|
var LIVENESS_MESSAGES = [
|
|
1008
1024
|
"Position your face inside the oval",
|
|
1009
1025
|
"Make sure your face is well lit",
|
|
@@ -1014,6 +1030,8 @@ function headerSubtitle(stageKind) {
|
|
|
1014
1030
|
switch (stageKind) {
|
|
1015
1031
|
case "qr":
|
|
1016
1032
|
return "Use your phone to continue";
|
|
1033
|
+
case "journey":
|
|
1034
|
+
return "Follow the steps to verify your identity";
|
|
1017
1035
|
case "accepted":
|
|
1018
1036
|
return "All set";
|
|
1019
1037
|
case "declined":
|
|
@@ -1039,7 +1057,9 @@ function FaceCaptureModal({
|
|
|
1039
1057
|
}) {
|
|
1040
1058
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
1041
1059
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
1060
|
+
const journeyMode = Boolean(session.verification_url);
|
|
1042
1061
|
const initialStage = (() => {
|
|
1062
|
+
if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
|
|
1043
1063
|
if (initialChoice === "this") return { kind: "capture" };
|
|
1044
1064
|
if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
1045
1065
|
return { kind: "choose" };
|
|
@@ -1057,19 +1077,20 @@ function FaceCaptureModal({
|
|
|
1057
1077
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
1058
1078
|
const cancelledRef = useRef(false);
|
|
1059
1079
|
const submittingRef = useRef(false);
|
|
1080
|
+
const journeyRetryingRef = useRef(false);
|
|
1060
1081
|
useEffect(() => {
|
|
1061
1082
|
const timer = setTimeout(() => {
|
|
1062
1083
|
setStage(
|
|
1063
|
-
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1084
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
|
|
1064
1085
|
);
|
|
1065
|
-
},
|
|
1086
|
+
}, REUSE_KYC_SESSION_EXPIRY_MS);
|
|
1066
1087
|
return () => clearTimeout(timer);
|
|
1067
1088
|
}, [session.token]);
|
|
1068
1089
|
useEffect(() => {
|
|
1069
1090
|
if (stage.kind !== "qr") return;
|
|
1070
1091
|
let stopped = false;
|
|
1071
1092
|
const intervalMs = 2e3;
|
|
1072
|
-
const deadline = Date.now() +
|
|
1093
|
+
const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
|
|
1073
1094
|
let mobileSeen = stage.mobileConnected;
|
|
1074
1095
|
const tick = async () => {
|
|
1075
1096
|
if (stopped || cancelledRef.current) return;
|
|
@@ -1110,6 +1131,64 @@ function FaceCaptureModal({
|
|
|
1110
1131
|
stopped = true;
|
|
1111
1132
|
};
|
|
1112
1133
|
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
1134
|
+
useEffect(() => {
|
|
1135
|
+
if (stage.kind !== "journey" || !stage.url) return;
|
|
1136
|
+
let stopped = false;
|
|
1137
|
+
const checkOnce = async () => {
|
|
1138
|
+
try {
|
|
1139
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1140
|
+
if (applyResult(result)) {
|
|
1141
|
+
stopped = true;
|
|
1142
|
+
return true;
|
|
1143
|
+
}
|
|
1144
|
+
} catch {
|
|
1145
|
+
}
|
|
1146
|
+
return false;
|
|
1147
|
+
};
|
|
1148
|
+
const tick = async () => {
|
|
1149
|
+
if (stopped || cancelledRef.current) return;
|
|
1150
|
+
await checkOnce();
|
|
1151
|
+
if (!stopped && !cancelledRef.current) {
|
|
1152
|
+
setTimeout(tick, 2500);
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
void tick();
|
|
1156
|
+
let frameOrigin = "";
|
|
1157
|
+
try {
|
|
1158
|
+
frameOrigin = new URL(stage.url).origin;
|
|
1159
|
+
} catch {
|
|
1160
|
+
frameOrigin = "";
|
|
1161
|
+
}
|
|
1162
|
+
const onMessage = (e) => {
|
|
1163
|
+
if (!frameOrigin || e.origin !== frameOrigin) return;
|
|
1164
|
+
const status = e.data?.verification_status;
|
|
1165
|
+
if (status) void checkOnce();
|
|
1166
|
+
};
|
|
1167
|
+
window.addEventListener("message", onMessage);
|
|
1168
|
+
return () => {
|
|
1169
|
+
stopped = true;
|
|
1170
|
+
window.removeEventListener("message", onMessage);
|
|
1171
|
+
};
|
|
1172
|
+
}, [stage.kind, stage.kind === "journey" ? stage.url : ""]);
|
|
1173
|
+
const handleJourneyRetry = async () => {
|
|
1174
|
+
if (!tryStartOperation(journeyRetryingRef)) return;
|
|
1175
|
+
setStage({ kind: "journey", url: "" });
|
|
1176
|
+
try {
|
|
1177
|
+
const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
|
|
1178
|
+
if (refreshed.verification_url) {
|
|
1179
|
+
setStage({ kind: "journey", url: refreshed.verification_url });
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
setStage({
|
|
1185
|
+
kind: "error",
|
|
1186
|
+
message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
|
|
1187
|
+
});
|
|
1188
|
+
} finally {
|
|
1189
|
+
releaseOperation(journeyRetryingRef);
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1113
1192
|
const applyResult = (result) => {
|
|
1114
1193
|
if (result.status === "accepted") {
|
|
1115
1194
|
setStage({ kind: "accepted", result });
|
|
@@ -1301,12 +1380,28 @@ function FaceCaptureModal({
|
|
|
1301
1380
|
};
|
|
1302
1381
|
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
1382
|
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));
|
|
1383
|
+
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
1384
|
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
1385
|
const renderError = (message) => {
|
|
1307
1386
|
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(
|
|
1387
|
+
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(
|
|
1388
|
+
"button",
|
|
1389
|
+
{
|
|
1390
|
+
style: primaryButtonStyle,
|
|
1391
|
+
onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
|
|
1392
|
+
},
|
|
1393
|
+
"Try Again"
|
|
1394
|
+
), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1309
1395
|
};
|
|
1396
|
+
const renderJourney = (url) => /* @__PURE__ */ React.createElement("div", { style: contentStyle }, url ? /* @__PURE__ */ React.createElement(
|
|
1397
|
+
"iframe",
|
|
1398
|
+
{
|
|
1399
|
+
src: url,
|
|
1400
|
+
title: "Identity verification",
|
|
1401
|
+
allow: "camera; microphone",
|
|
1402
|
+
style: journeyFrameStyle
|
|
1403
|
+
}
|
|
1404
|
+
) : /* @__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
1405
|
const body = (() => {
|
|
1311
1406
|
switch (stage.kind) {
|
|
1312
1407
|
case "choose":
|
|
@@ -1315,6 +1410,8 @@ function FaceCaptureModal({
|
|
|
1315
1410
|
return renderQRStage(stage.mobileConnected);
|
|
1316
1411
|
case "capture":
|
|
1317
1412
|
return renderCapture(stage.declinedReason);
|
|
1413
|
+
case "journey":
|
|
1414
|
+
return renderJourney(stage.url);
|
|
1318
1415
|
case "submitting":
|
|
1319
1416
|
return renderSubmitting();
|
|
1320
1417
|
case "accepted":
|
|
@@ -1327,7 +1424,7 @@ function FaceCaptureModal({
|
|
|
1327
1424
|
return renderError(stage.message);
|
|
1328
1425
|
}
|
|
1329
1426
|
})();
|
|
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(
|
|
1427
|
+
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
1428
|
"button",
|
|
1332
1429
|
{
|
|
1333
1430
|
onClick: () => close(null),
|
|
@@ -1360,7 +1457,8 @@ var modalStyle = {
|
|
|
1360
1457
|
borderRadius: 16,
|
|
1361
1458
|
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1362
1459
|
width: "100%",
|
|
1363
|
-
|
|
1460
|
+
maxHeight: "calc(100vh - 32px)",
|
|
1461
|
+
overflowY: "auto",
|
|
1364
1462
|
animation: "zoomIn .2s ease-out"
|
|
1365
1463
|
};
|
|
1366
1464
|
var headerStyle = {
|
|
@@ -1386,6 +1484,13 @@ var contentStyle = {
|
|
|
1386
1484
|
flexDirection: "column",
|
|
1387
1485
|
gap: 16
|
|
1388
1486
|
};
|
|
1487
|
+
var journeyFrameStyle = {
|
|
1488
|
+
width: "100%",
|
|
1489
|
+
height: "min(520px, 60vh)",
|
|
1490
|
+
border: 0,
|
|
1491
|
+
borderRadius: 12,
|
|
1492
|
+
background: "#f9fafb"
|
|
1493
|
+
};
|
|
1389
1494
|
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1390
1495
|
var visualGuideStyle = { position: "relative" };
|
|
1391
1496
|
var circleStyle = {
|