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.js
CHANGED
|
@@ -1007,9 +1007,25 @@ 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
|
+
function tryStartOperation(guard) {
|
|
1013
|
+
if (guard.current) return false;
|
|
1014
|
+
guard.current = true;
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
function releaseOperation(guard) {
|
|
1018
|
+
guard.current = false;
|
|
1019
|
+
}
|
|
1020
|
+
function retryTarget(hasHostedJourney) {
|
|
1021
|
+
return hasHostedJourney ? "journey" : "capture";
|
|
1022
|
+
}
|
|
1023
|
+
function modalMaxWidth(hasHostedJourney) {
|
|
1024
|
+
return hasHostedJourney ? 680 : 448;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1010
1027
|
// src/kyc/FaceCaptureModal.tsx
|
|
1011
1028
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1012
|
-
var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
|
|
1013
1029
|
var LIVENESS_MESSAGES = [
|
|
1014
1030
|
"Position your face inside the oval",
|
|
1015
1031
|
"Make sure your face is well lit",
|
|
@@ -1020,6 +1036,8 @@ function headerSubtitle(stageKind) {
|
|
|
1020
1036
|
switch (stageKind) {
|
|
1021
1037
|
case "qr":
|
|
1022
1038
|
return "Use your phone to continue";
|
|
1039
|
+
case "journey":
|
|
1040
|
+
return "Follow the steps to verify your identity";
|
|
1023
1041
|
case "accepted":
|
|
1024
1042
|
return "All set";
|
|
1025
1043
|
case "declined":
|
|
@@ -1045,7 +1063,9 @@ function FaceCaptureModal({
|
|
|
1045
1063
|
}) {
|
|
1046
1064
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
1047
1065
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
1066
|
+
const journeyMode = Boolean(session.verification_url);
|
|
1048
1067
|
const initialStage = (() => {
|
|
1068
|
+
if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
|
|
1049
1069
|
if (initialChoice === "this") return { kind: "capture" };
|
|
1050
1070
|
if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
1051
1071
|
return { kind: "choose" };
|
|
@@ -1063,19 +1083,20 @@ function FaceCaptureModal({
|
|
|
1063
1083
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
1064
1084
|
const cancelledRef = React.useRef(false);
|
|
1065
1085
|
const submittingRef = React.useRef(false);
|
|
1086
|
+
const journeyRetryingRef = React.useRef(false);
|
|
1066
1087
|
React.useEffect(() => {
|
|
1067
1088
|
const timer = setTimeout(() => {
|
|
1068
1089
|
setStage(
|
|
1069
|
-
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1090
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
|
|
1070
1091
|
);
|
|
1071
|
-
},
|
|
1092
|
+
}, REUSE_KYC_SESSION_EXPIRY_MS);
|
|
1072
1093
|
return () => clearTimeout(timer);
|
|
1073
1094
|
}, [session.token]);
|
|
1074
1095
|
React.useEffect(() => {
|
|
1075
1096
|
if (stage.kind !== "qr") return;
|
|
1076
1097
|
let stopped = false;
|
|
1077
1098
|
const intervalMs = 2e3;
|
|
1078
|
-
const deadline = Date.now() +
|
|
1099
|
+
const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
|
|
1079
1100
|
let mobileSeen = stage.mobileConnected;
|
|
1080
1101
|
const tick = async () => {
|
|
1081
1102
|
if (stopped || cancelledRef.current) return;
|
|
@@ -1116,6 +1137,64 @@ function FaceCaptureModal({
|
|
|
1116
1137
|
stopped = true;
|
|
1117
1138
|
};
|
|
1118
1139
|
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
1140
|
+
React.useEffect(() => {
|
|
1141
|
+
if (stage.kind !== "journey" || !stage.url) return;
|
|
1142
|
+
let stopped = false;
|
|
1143
|
+
const checkOnce = async () => {
|
|
1144
|
+
try {
|
|
1145
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1146
|
+
if (applyResult(result)) {
|
|
1147
|
+
stopped = true;
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
} catch {
|
|
1151
|
+
}
|
|
1152
|
+
return false;
|
|
1153
|
+
};
|
|
1154
|
+
const tick = async () => {
|
|
1155
|
+
if (stopped || cancelledRef.current) return;
|
|
1156
|
+
await checkOnce();
|
|
1157
|
+
if (!stopped && !cancelledRef.current) {
|
|
1158
|
+
setTimeout(tick, 2500);
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
void tick();
|
|
1162
|
+
let frameOrigin = "";
|
|
1163
|
+
try {
|
|
1164
|
+
frameOrigin = new URL(stage.url).origin;
|
|
1165
|
+
} catch {
|
|
1166
|
+
frameOrigin = "";
|
|
1167
|
+
}
|
|
1168
|
+
const onMessage = (e) => {
|
|
1169
|
+
if (!frameOrigin || e.origin !== frameOrigin) return;
|
|
1170
|
+
const status = e.data?.verification_status;
|
|
1171
|
+
if (status) void checkOnce();
|
|
1172
|
+
};
|
|
1173
|
+
window.addEventListener("message", onMessage);
|
|
1174
|
+
return () => {
|
|
1175
|
+
stopped = true;
|
|
1176
|
+
window.removeEventListener("message", onMessage);
|
|
1177
|
+
};
|
|
1178
|
+
}, [stage.kind, stage.kind === "journey" ? stage.url : ""]);
|
|
1179
|
+
const handleJourneyRetry = async () => {
|
|
1180
|
+
if (!tryStartOperation(journeyRetryingRef)) return;
|
|
1181
|
+
setStage({ kind: "journey", url: "" });
|
|
1182
|
+
try {
|
|
1183
|
+
const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
|
|
1184
|
+
if (refreshed.verification_url) {
|
|
1185
|
+
setStage({ kind: "journey", url: refreshed.verification_url });
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
|
|
1189
|
+
} catch (err) {
|
|
1190
|
+
setStage({
|
|
1191
|
+
kind: "error",
|
|
1192
|
+
message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
|
|
1193
|
+
});
|
|
1194
|
+
} finally {
|
|
1195
|
+
releaseOperation(journeyRetryingRef);
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1119
1198
|
const applyResult = (result) => {
|
|
1120
1199
|
if (result.status === "accepted") {
|
|
1121
1200
|
setStage({ kind: "accepted", result });
|
|
@@ -1307,12 +1386,28 @@ function FaceCaptureModal({
|
|
|
1307
1386
|
};
|
|
1308
1387
|
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
1388
|
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));
|
|
1389
|
+
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
1390
|
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
1391
|
const renderError = (message) => {
|
|
1313
1392
|
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(
|
|
1393
|
+
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(
|
|
1394
|
+
"button",
|
|
1395
|
+
{
|
|
1396
|
+
style: primaryButtonStyle,
|
|
1397
|
+
onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
|
|
1398
|
+
},
|
|
1399
|
+
"Try Again"
|
|
1400
|
+
), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1315
1401
|
};
|
|
1402
|
+
const renderJourney = (url) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, url ? /* @__PURE__ */ React__default.default.createElement(
|
|
1403
|
+
"iframe",
|
|
1404
|
+
{
|
|
1405
|
+
src: url,
|
|
1406
|
+
title: "Identity verification",
|
|
1407
|
+
allow: "camera; microphone",
|
|
1408
|
+
style: journeyFrameStyle
|
|
1409
|
+
}
|
|
1410
|
+
) : /* @__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
1411
|
const body = (() => {
|
|
1317
1412
|
switch (stage.kind) {
|
|
1318
1413
|
case "choose":
|
|
@@ -1321,6 +1416,8 @@ function FaceCaptureModal({
|
|
|
1321
1416
|
return renderQRStage(stage.mobileConnected);
|
|
1322
1417
|
case "capture":
|
|
1323
1418
|
return renderCapture(stage.declinedReason);
|
|
1419
|
+
case "journey":
|
|
1420
|
+
return renderJourney(stage.url);
|
|
1324
1421
|
case "submitting":
|
|
1325
1422
|
return renderSubmitting();
|
|
1326
1423
|
case "accepted":
|
|
@@ -1333,7 +1430,7 @@ function FaceCaptureModal({
|
|
|
1333
1430
|
return renderError(stage.message);
|
|
1334
1431
|
}
|
|
1335
1432
|
})();
|
|
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(
|
|
1433
|
+
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
1434
|
"button",
|
|
1338
1435
|
{
|
|
1339
1436
|
onClick: () => close(null),
|
|
@@ -1366,7 +1463,8 @@ var modalStyle = {
|
|
|
1366
1463
|
borderRadius: 16,
|
|
1367
1464
|
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1368
1465
|
width: "100%",
|
|
1369
|
-
|
|
1466
|
+
maxHeight: "calc(100vh - 32px)",
|
|
1467
|
+
overflowY: "auto",
|
|
1370
1468
|
animation: "zoomIn .2s ease-out"
|
|
1371
1469
|
};
|
|
1372
1470
|
var headerStyle = {
|
|
@@ -1392,6 +1490,13 @@ var contentStyle = {
|
|
|
1392
1490
|
flexDirection: "column",
|
|
1393
1491
|
gap: 16
|
|
1394
1492
|
};
|
|
1493
|
+
var journeyFrameStyle = {
|
|
1494
|
+
width: "100%",
|
|
1495
|
+
height: "min(520px, 60vh)",
|
|
1496
|
+
border: 0,
|
|
1497
|
+
borderRadius: 12,
|
|
1498
|
+
background: "#f9fafb"
|
|
1499
|
+
};
|
|
1395
1500
|
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1396
1501
|
var visualGuideStyle = { position: "relative" };
|
|
1397
1502
|
var circleStyle = {
|