vesant-sdk 1.7.1-dev.284f68d → 1.7.1-dev.2b64301
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 +130 -10
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +130 -10
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -1007,9 +1007,30 @@ 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
|
+
function resultForModalClose(stageKind, result) {
|
|
1027
|
+
const terminal = stageKind === "accepted" || stageKind === "declined" || stageKind === "max_attempts";
|
|
1028
|
+
return terminal && result !== void 0 ? result : null;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1010
1031
|
// src/kyc/FaceCaptureModal.tsx
|
|
1011
1032
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1012
|
-
var
|
|
1033
|
+
var ACCEPTED_AUTO_CLOSE_MS = 1200;
|
|
1013
1034
|
var LIVENESS_MESSAGES = [
|
|
1014
1035
|
"Position your face inside the oval",
|
|
1015
1036
|
"Make sure your face is well lit",
|
|
@@ -1020,6 +1041,8 @@ function headerSubtitle(stageKind) {
|
|
|
1020
1041
|
switch (stageKind) {
|
|
1021
1042
|
case "qr":
|
|
1022
1043
|
return "Use your phone to continue";
|
|
1044
|
+
case "journey":
|
|
1045
|
+
return "Follow the steps to verify your identity";
|
|
1023
1046
|
case "accepted":
|
|
1024
1047
|
return "All set";
|
|
1025
1048
|
case "declined":
|
|
@@ -1045,7 +1068,9 @@ function FaceCaptureModal({
|
|
|
1045
1068
|
}) {
|
|
1046
1069
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
1047
1070
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
1071
|
+
const journeyMode = Boolean(session.verification_url);
|
|
1048
1072
|
const initialStage = (() => {
|
|
1073
|
+
if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
|
|
1049
1074
|
if (initialChoice === "this") return { kind: "capture" };
|
|
1050
1075
|
if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
1051
1076
|
return { kind: "choose" };
|
|
@@ -1063,19 +1088,20 @@ function FaceCaptureModal({
|
|
|
1063
1088
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
1064
1089
|
const cancelledRef = React.useRef(false);
|
|
1065
1090
|
const submittingRef = React.useRef(false);
|
|
1091
|
+
const journeyRetryingRef = React.useRef(false);
|
|
1066
1092
|
React.useEffect(() => {
|
|
1067
1093
|
const timer = setTimeout(() => {
|
|
1068
1094
|
setStage(
|
|
1069
|
-
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1095
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
|
|
1070
1096
|
);
|
|
1071
|
-
},
|
|
1097
|
+
}, REUSE_KYC_SESSION_EXPIRY_MS);
|
|
1072
1098
|
return () => clearTimeout(timer);
|
|
1073
1099
|
}, [session.token]);
|
|
1074
1100
|
React.useEffect(() => {
|
|
1075
1101
|
if (stage.kind !== "qr") return;
|
|
1076
1102
|
let stopped = false;
|
|
1077
1103
|
const intervalMs = 2e3;
|
|
1078
|
-
const deadline = Date.now() +
|
|
1104
|
+
const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
|
|
1079
1105
|
let mobileSeen = stage.mobileConnected;
|
|
1080
1106
|
const tick = async () => {
|
|
1081
1107
|
if (stopped || cancelledRef.current) return;
|
|
@@ -1116,6 +1142,64 @@ function FaceCaptureModal({
|
|
|
1116
1142
|
stopped = true;
|
|
1117
1143
|
};
|
|
1118
1144
|
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
1145
|
+
React.useEffect(() => {
|
|
1146
|
+
if (stage.kind !== "journey" || !stage.url) return;
|
|
1147
|
+
let stopped = false;
|
|
1148
|
+
const checkOnce = async () => {
|
|
1149
|
+
try {
|
|
1150
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1151
|
+
if (applyResult(result)) {
|
|
1152
|
+
stopped = true;
|
|
1153
|
+
return true;
|
|
1154
|
+
}
|
|
1155
|
+
} catch {
|
|
1156
|
+
}
|
|
1157
|
+
return false;
|
|
1158
|
+
};
|
|
1159
|
+
const tick = async () => {
|
|
1160
|
+
if (stopped || cancelledRef.current) return;
|
|
1161
|
+
await checkOnce();
|
|
1162
|
+
if (!stopped && !cancelledRef.current) {
|
|
1163
|
+
setTimeout(tick, 2500);
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
void tick();
|
|
1167
|
+
let frameOrigin = "";
|
|
1168
|
+
try {
|
|
1169
|
+
frameOrigin = new URL(stage.url).origin;
|
|
1170
|
+
} catch {
|
|
1171
|
+
frameOrigin = "";
|
|
1172
|
+
}
|
|
1173
|
+
const onMessage = (e) => {
|
|
1174
|
+
if (!frameOrigin || e.origin !== frameOrigin) return;
|
|
1175
|
+
const status = e.data?.verification_status;
|
|
1176
|
+
if (status) void checkOnce();
|
|
1177
|
+
};
|
|
1178
|
+
window.addEventListener("message", onMessage);
|
|
1179
|
+
return () => {
|
|
1180
|
+
stopped = true;
|
|
1181
|
+
window.removeEventListener("message", onMessage);
|
|
1182
|
+
};
|
|
1183
|
+
}, [stage.kind, stage.kind === "journey" ? stage.url : ""]);
|
|
1184
|
+
const handleJourneyRetry = async () => {
|
|
1185
|
+
if (!tryStartOperation(journeyRetryingRef)) return;
|
|
1186
|
+
setStage({ kind: "journey", url: "" });
|
|
1187
|
+
try {
|
|
1188
|
+
const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
|
|
1189
|
+
if (refreshed.verification_url) {
|
|
1190
|
+
setStage({ kind: "journey", url: refreshed.verification_url });
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
setStage({
|
|
1196
|
+
kind: "error",
|
|
1197
|
+
message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
|
|
1198
|
+
});
|
|
1199
|
+
} finally {
|
|
1200
|
+
releaseOperation(journeyRetryingRef);
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1119
1203
|
const applyResult = (result) => {
|
|
1120
1204
|
if (result.status === "accepted") {
|
|
1121
1205
|
setStage({ kind: "accepted", result });
|
|
@@ -1243,12 +1327,22 @@ function FaceCaptureModal({
|
|
|
1243
1327
|
}, 3e3);
|
|
1244
1328
|
return () => clearInterval(interval);
|
|
1245
1329
|
}, [captureMode]);
|
|
1246
|
-
const close = (result) => {
|
|
1330
|
+
const close = React.useCallback((result) => {
|
|
1247
1331
|
cancelledRef.current = true;
|
|
1248
1332
|
if (result === null && onCancel) {
|
|
1249
1333
|
onCancel();
|
|
1250
1334
|
}
|
|
1251
1335
|
onComplete(result);
|
|
1336
|
+
}, [onCancel, onComplete]);
|
|
1337
|
+
React.useEffect(() => {
|
|
1338
|
+
if (stage.kind !== "accepted") return;
|
|
1339
|
+
const result = stage.result;
|
|
1340
|
+
const timer = setTimeout(() => close(result), ACCEPTED_AUTO_CLOSE_MS);
|
|
1341
|
+
return () => clearTimeout(timer);
|
|
1342
|
+
}, [close, stage]);
|
|
1343
|
+
const handleHeaderClose = () => {
|
|
1344
|
+
const result = "result" in stage ? stage.result : void 0;
|
|
1345
|
+
close(resultForModalClose(stage.kind, result));
|
|
1252
1346
|
};
|
|
1253
1347
|
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
1348
|
"button",
|
|
@@ -1307,12 +1401,28 @@ function FaceCaptureModal({
|
|
|
1307
1401
|
};
|
|
1308
1402
|
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
1403
|
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));
|
|
1404
|
+
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
1405
|
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
1406
|
const renderError = (message) => {
|
|
1313
1407
|
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(
|
|
1408
|
+
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(
|
|
1409
|
+
"button",
|
|
1410
|
+
{
|
|
1411
|
+
style: primaryButtonStyle,
|
|
1412
|
+
onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
|
|
1413
|
+
},
|
|
1414
|
+
"Try Again"
|
|
1415
|
+
), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1315
1416
|
};
|
|
1417
|
+
const renderJourney = (url) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, url ? /* @__PURE__ */ React__default.default.createElement(
|
|
1418
|
+
"iframe",
|
|
1419
|
+
{
|
|
1420
|
+
src: url,
|
|
1421
|
+
title: "Identity verification",
|
|
1422
|
+
allow: "camera; microphone",
|
|
1423
|
+
style: journeyFrameStyle
|
|
1424
|
+
}
|
|
1425
|
+
) : /* @__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
1426
|
const body = (() => {
|
|
1317
1427
|
switch (stage.kind) {
|
|
1318
1428
|
case "choose":
|
|
@@ -1321,6 +1431,8 @@ function FaceCaptureModal({
|
|
|
1321
1431
|
return renderQRStage(stage.mobileConnected);
|
|
1322
1432
|
case "capture":
|
|
1323
1433
|
return renderCapture(stage.declinedReason);
|
|
1434
|
+
case "journey":
|
|
1435
|
+
return renderJourney(stage.url);
|
|
1324
1436
|
case "submitting":
|
|
1325
1437
|
return renderSubmitting();
|
|
1326
1438
|
case "accepted":
|
|
@@ -1333,10 +1445,10 @@ function FaceCaptureModal({
|
|
|
1333
1445
|
return renderError(stage.message);
|
|
1334
1446
|
}
|
|
1335
1447
|
})();
|
|
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(
|
|
1448
|
+
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
1449
|
"button",
|
|
1338
1450
|
{
|
|
1339
|
-
onClick:
|
|
1451
|
+
onClick: handleHeaderClose,
|
|
1340
1452
|
style: closeButtonStyle,
|
|
1341
1453
|
"aria-label": "Close"
|
|
1342
1454
|
},
|
|
@@ -1366,7 +1478,8 @@ var modalStyle = {
|
|
|
1366
1478
|
borderRadius: 16,
|
|
1367
1479
|
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1368
1480
|
width: "100%",
|
|
1369
|
-
|
|
1481
|
+
maxHeight: "calc(100vh - 32px)",
|
|
1482
|
+
overflowY: "auto",
|
|
1370
1483
|
animation: "zoomIn .2s ease-out"
|
|
1371
1484
|
};
|
|
1372
1485
|
var headerStyle = {
|
|
@@ -1392,6 +1505,13 @@ var contentStyle = {
|
|
|
1392
1505
|
flexDirection: "column",
|
|
1393
1506
|
gap: 16
|
|
1394
1507
|
};
|
|
1508
|
+
var journeyFrameStyle = {
|
|
1509
|
+
width: "100%",
|
|
1510
|
+
height: "min(520px, 60vh)",
|
|
1511
|
+
border: 0,
|
|
1512
|
+
borderRadius: 12,
|
|
1513
|
+
background: "#f9fafb"
|
|
1514
|
+
};
|
|
1395
1515
|
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1396
1516
|
var visualGuideStyle = { position: "relative" };
|
|
1397
1517
|
var circleStyle = {
|