vesant-sdk 1.7.0-dev.e0ee6d5 → 1.7.0-dev.f9faca4

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/react.mjs CHANGED
@@ -882,10 +882,10 @@ function useCustomerProfile(client, customerId, options = {}) {
882
882
  var Camera = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjAvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvMjAwMS9SRUMtU1ZHLTIwMDEwOTA0L0RURC9zdmcxMC5kdGQiPg0KPCEtLSBVcGxvYWRlZCB0bzogU1ZHIFJlcG8sIHd3dy5zdmdyZXBvLmNvbSwgR2VuZXJhdG9yOiBTVkcgUmVwbyBNaXhlciBUb29scyAtLT4NCjxzdmcgdmVyc2lvbj0iMS4wIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgDQoJIHdpZHRoPSI4MDBweCIgaGVpZ2h0PSI4MDBweCIgdmlld0JveD0iMCAwIDY0IDY0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA2NCA2NCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBmaWxsPSIjMjMxRjIwIiBkPSJNNjAsMTBINDkuNjU2bC02LjgyOC02LjgyOEM0Mi4wNzgsMi40MjIsNDEuMDYyLDIsNDAsMkgyNGMtMS4wNjIsMC0yLjA3OCwwLjQyMi0yLjgyOCwxLjE3MkwxNC4zNDQsMTBINA0KCQljLTIuMjExLDAtNCwxLjc4OS00LDR2NDRjMCwyLjIxMSwxLjc4OSw0LDQsNGg1NmMyLjIxMSwwLDQtMS43ODksNC00VjE0QzY0LDExLjc4OSw2Mi4yMTEsMTAsNjAsMTB6IE0zMiw1MA0KCQljLTguODM2LDAtMTYtNy4xNjQtMTYtMTZzNy4xNjQtMTYsMTYtMTZzMTYsNy4xNjQsMTYsMTZTNDAuODM2LDUwLDMyLDUweiIvPg0KCTxjaXJjbGUgZmlsbD0iIzIzMUYyMCIgY3g9IjMyIiBjeT0iMzQiIHI9IjgiLz4NCjwvZz4NCjwvc3ZnPg==";
883
883
  var Done = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik04LjUgMTIuNUwxMC41IDE0LjVMMTUuNSA5LjUiIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPg0KPHBhdGggZD0iTTcgMy4zMzc4MkM4LjQ3MDg3IDIuNDg2OTcgMTAuMTc4NiAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJDMjIgMTcuNTIyOCAxNy41MjI4IDIyIDEyIDIyQzYuNDc3MTUgMjIgMiAxNy41MjI4IDIgMTJDMiAxMC4xNzg2IDIuNDg2OTcgOC40NzA4NyAzLjMzNzgyIDciIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPg0KPC9zdmc+";
884
884
  var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNC41IDkuNTAwMDJMOS41IDE0LjVNOS40OTk5OCA5LjVMMTQuNSAxNC41IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjxwYXRoIGQ9Ik03IDMuMzM3ODJDOC40NzA4NyAyLjQ4Njk3IDEwLjE3ODYgMiAxMiAyQzE3LjUyMjggMiAyMiA2LjQ3NzE1IDIyIDEyQzIyIDE3LjUyMjggMTcuNTIyOCAyMiAxMiAyMkM2LjQ3NzE1IDIyIDIgMTcuNTIyOCAyIDEyQzIgMTAuMTc4NiAyLjQ4Njk3IDguNDcwODcgMy4zMzc4MiA3IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjwvc3ZnPg==";
885
- var Upload = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNyAxN0gxNy4wMU0xNS42IDE0SDE4QzE4LjkzMTkgMTQgMTkuMzk3OCAxNCAxOS43NjU0IDE0LjE1MjJDMjAuMjU1NCAxNC4zNTUyIDIwLjY0NDggMTQuNzQ0NiAyMC44NDc4IDE1LjIzNDZDMjEgMTUuNjAyMiAyMSAxNi4wNjgxIDIxIDE3QzIxIDE3LjkzMTkgMjEgMTguMzk3OCAyMC44NDc4IDE4Ljc2NTRDMjAuNjQ0OCAxOS4yNTU0IDIwLjI1NTQgMTkuNjQ0OCAxOS43NjU0IDE5Ljg0NzhDMTkuMzk3OCAyMCAxOC45MzE5IDIwIDE4IDIwSDZDNS4wNjgxMiAyMCA0LjYwMjE4IDIwIDQuMjM0NjMgMTkuODQ3OEMzLjc0NDU4IDE5LjY0NDggMy4zNTUyMyAxOS4yNTU0IDMuMTUyMjQgMTguNzY1NEMzIDE4LjM5NzggMyAxNy45MzE5IDMgMTdDMyAxNi4wNjgxIDMgMTUuNjAyMiAzLjE1MjI0IDE1LjIzNDZDMy4zNTUyMyAxNC43NDQ2IDMuNzQ0NTggMTQuMzU1MiA0LjIzNDYzIDE0LjE1MjJDNC42MDIxOCAxNCA1LjA2ODEyIDE0IDYgMTRIOC40TTEyIDE1VjRNMTIgNEwxNSA3TTEyIDRMOSA3IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+DQo8L3N2Zz4=";
886
885
 
887
886
  // src/kyc/FaceCaptureModal.tsx
888
887
  var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
888
+ var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
889
889
  var LIVENESS_MESSAGES = [
890
890
  "Position your face inside the circle",
891
891
  "Make sure your face is well lit",
@@ -899,9 +899,9 @@ function headerSubtitle(stageKind) {
899
899
  case "accepted":
900
900
  return "All set";
901
901
  case "declined":
902
- return "Verification didn't match, try once more";
902
+ return "Verification Declined";
903
903
  case "max_attempts":
904
- return "We couldn't verify you";
904
+ return "We couldn't verify your identity";
905
905
  default:
906
906
  return "Please capture a clear photo of your face";
907
907
  }
@@ -914,16 +914,18 @@ function FaceCaptureModal({
914
914
  client,
915
915
  session,
916
916
  onComplete,
917
+ onCancel,
917
918
  defaultDevice,
918
919
  renderQR
919
920
  }) {
920
921
  const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
922
+ const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
921
923
  const initialStage = (() => {
922
- const choice = defaultDevice ?? (isMobile ? "this" : "ask");
923
- if (choice === "this") return { kind: "capture" };
924
- if (choice === "mobile") return { kind: "qr", mobileConnected: false };
924
+ if (initialChoice === "this") return { kind: "capture" };
925
+ if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
925
926
  return { kind: "choose" };
926
927
  })();
928
+ const hasMethodChoice = initialChoice === "ask";
927
929
  const [stage, setStage] = useState(initialStage);
928
930
  const [attempts, setAttempts] = useState(session.attempts);
929
931
  const maxAttempts = session.max_attempts || 1;
@@ -935,17 +937,25 @@ function FaceCaptureModal({
935
937
  const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
936
938
  const cancelledRef = useRef(false);
937
939
  const submittingRef = useRef(false);
940
+ useEffect(() => {
941
+ const timer = setTimeout(() => {
942
+ setStage(
943
+ (prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
944
+ );
945
+ }, SESSION_EXPIRY_MS);
946
+ return () => clearTimeout(timer);
947
+ }, [session.token]);
938
948
  useEffect(() => {
939
949
  if (stage.kind !== "qr") return;
940
950
  let stopped = false;
941
951
  const intervalMs = 2e3;
942
- const deadline = Date.now() + 15 * 60 * 1e3;
952
+ const deadline = Date.now() + SESSION_EXPIRY_MS;
943
953
  let mobileSeen = stage.mobileConnected;
944
954
  const tick = async () => {
945
955
  if (stopped || cancelledRef.current) return;
946
956
  try {
947
957
  if (mobileSeen) {
948
- const result = await client.getReuseKycSessionStatus(session.reference);
958
+ const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
949
959
  if (result.status === "accepted") {
950
960
  setStage({ kind: "accepted", result });
951
961
  stopped = true;
@@ -1017,7 +1027,7 @@ function FaceCaptureModal({
1017
1027
  try {
1018
1028
  const postPromise = (async () => {
1019
1029
  try {
1020
- const data = await client.submitReuseKycSession({
1030
+ const data = await client.submitEventBasedFaceVerificationSession({
1021
1031
  token: session.token,
1022
1032
  reference: ref,
1023
1033
  proof
@@ -1033,7 +1043,7 @@ function FaceCaptureModal({
1033
1043
  while (Date.now() < deadline) {
1034
1044
  if (settled || cancelledRef.current) return null;
1035
1045
  try {
1036
- const r = await client.getReuseKycSessionStatus(ref);
1046
+ const r = await client.getEventBasedFaceVerificationSessionStatus(session.token);
1037
1047
  if (isFreshPollResult(r)) return r;
1038
1048
  } catch {
1039
1049
  }
@@ -1104,6 +1114,9 @@ function FaceCaptureModal({
1104
1114
  }, [captureMode]);
1105
1115
  const close = (result) => {
1106
1116
  cancelledRef.current = true;
1117
+ if (result === null && onCancel) {
1118
+ onCancel();
1119
+ }
1107
1120
  onComplete(result);
1108
1121
  };
1109
1122
  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(
@@ -1112,7 +1125,7 @@ function FaceCaptureModal({
1112
1125
  style: primaryButtonStyle,
1113
1126
  onClick: () => setStage({ kind: "capture" })
1114
1127
  },
1115
- /* @__PURE__ */ React.createElement("img", { src: isMobile ? Camera : Upload, alt: "", width: 20, height: 20 }),
1128
+ /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }),
1116
1129
  /* @__PURE__ */ React.createElement("span", null, "Continue on this device")
1117
1130
  ), /* @__PURE__ */ React.createElement(
1118
1131
  "button",
@@ -1129,6 +1142,13 @@ function FaceCaptureModal({
1129
1142
  onClick: () => setStage({ kind: "capture" })
1130
1143
  },
1131
1144
  "Use this device instead"
1145
+ ), hasMethodChoice && /* @__PURE__ */ React.createElement(
1146
+ "button",
1147
+ {
1148
+ style: secondaryButtonStyle,
1149
+ onClick: () => setStage({ kind: "choose" })
1150
+ },
1151
+ "Change verification method"
1132
1152
  ), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
1133
1153
  const renderCapture = (declinedReason) => {
1134
1154
  if (captureMode === "live") {
@@ -1144,13 +1164,23 @@ function FaceCaptureModal({
1144
1164
  if (captureMode === "preview" && capturedPreview) {
1145
1165
  return /* @__PURE__ */ React.createElement("div", { style: contentStyle }, declinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "Looks good? Submit this selfie or retake it."), /* @__PURE__ */ React.createElement("div", { style: previewBoxStyle }, /* @__PURE__ */ React.createElement("img", { src: capturedPreview, alt: "Captured selfie preview", style: previewImgStyle })), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleConfirmSubmit }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, "Submit")), /* @__PURE__ */ React.createElement("button", { style: secondaryButtonStyle, onClick: handleRetake }, "Retake"), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
1146
1166
  }
1147
- return /* @__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("div", { style: badgeStyle }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, declinedReason ? "Try again" : "Open Camera")), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
1167
+ return /* @__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("div", { style: badgeStyle }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, declinedReason ? "Try Again" : "Open Camera")), hasMethodChoice && /* @__PURE__ */ React.createElement(
1168
+ "button",
1169
+ {
1170
+ style: secondaryButtonStyle,
1171
+ onClick: () => setStage({ kind: "choose" })
1172
+ },
1173
+ "Change verification method"
1174
+ ), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
1148
1175
  };
1149
1176
  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."));
1150
1177
  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")));
1151
- const renderDeclined = (result) => /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__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));
1152
- 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."), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
1153
- const renderError = (message) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Something went wrong"), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, message), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try again"), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
1178
+ 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));
1179
+ 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")));
1180
+ const renderError = (message) => {
1181
+ const sessionExpired = /session\s+expired/i.test(message);
1182
+ 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("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try Again"), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
1183
+ };
1154
1184
  const body = (() => {
1155
1185
  switch (stage.kind) {
1156
1186
  case "choose":
@@ -1358,11 +1388,63 @@ var attemptsTextStyle = {
1358
1388
  };
1359
1389
  var footerStyle = { padding: "12px 24px 16px", borderTop: "1px solid #f3f4f6" };
1360
1390
  var footerTextStyle = { margin: 0, fontSize: 12, color: "#9ca3af", textAlign: "center" };
1391
+ var FACE_MESSAGES = {
1392
+ idle: "Initializing camera...",
1393
+ loading: "Loading face detection...",
1394
+ scanning: "Position your face in the circle",
1395
+ too_far: "Face too far. Please move closer to the camera",
1396
+ too_close: "Too close. Please move back a little",
1397
+ off_center: "Center your face in the circle",
1398
+ ok: "Looking great! Tap the button to capture"
1399
+ };
1400
+ var FACE_COLORS = {
1401
+ idle: "#9ca3af",
1402
+ loading: "#3b82f6",
1403
+ scanning: "#3b82f6",
1404
+ too_far: "#ef4444",
1405
+ too_close: "#f59e0b",
1406
+ off_center: "#f59e0b",
1407
+ ok: "#10b981"
1408
+ };
1409
+ var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
1410
+ var mediapipeLoaderPromise = null;
1411
+ function loadMediaPipeFaceDetection() {
1412
+ if (typeof window === "undefined" || typeof document === "undefined") {
1413
+ return Promise.reject(new Error("MediaPipe requires a browser environment"));
1414
+ }
1415
+ const w = window;
1416
+ if (w.FaceDetection) {
1417
+ return Promise.resolve(w.FaceDetection);
1418
+ }
1419
+ if (mediapipeLoaderPromise) return mediapipeLoaderPromise;
1420
+ mediapipeLoaderPromise = new Promise((resolve, reject) => {
1421
+ const script = document.createElement("script");
1422
+ script.src = `${MEDIAPIPE_BASE}/face_detection.js`;
1423
+ script.async = true;
1424
+ script.crossOrigin = "anonymous";
1425
+ script.onload = () => {
1426
+ if (w.FaceDetection) {
1427
+ resolve(w.FaceDetection);
1428
+ } else {
1429
+ mediapipeLoaderPromise = null;
1430
+ reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
1431
+ }
1432
+ };
1433
+ script.onerror = () => {
1434
+ mediapipeLoaderPromise = null;
1435
+ reject(new Error("Failed to load MediaPipe face_detection script"));
1436
+ };
1437
+ document.head.appendChild(script);
1438
+ });
1439
+ return mediapipeLoaderPromise;
1440
+ }
1361
1441
  function LiveCamera({ onCapture, onCancel, message }) {
1362
1442
  const videoRef = useRef(null);
1363
1443
  const streamRef = useRef(null);
1364
1444
  const [ready, setReady] = useState(false);
1365
1445
  const [error, setError] = useState(null);
1446
+ const [faceStatus, setFaceStatus] = useState("idle");
1447
+ const [faceDetectionFailed, setFaceDetectionFailed] = useState(false);
1366
1448
  useEffect(() => {
1367
1449
  let cancelled = false;
1368
1450
  async function init() {
@@ -1403,6 +1485,77 @@ function LiveCamera({ onCapture, onCancel, message }) {
1403
1485
  streamRef.current = null;
1404
1486
  };
1405
1487
  }, []);
1488
+ useEffect(() => {
1489
+ if (!ready) {
1490
+ setFaceStatus("idle");
1491
+ return;
1492
+ }
1493
+ let cancelled = false;
1494
+ let rafId = null;
1495
+ let detector = null;
1496
+ setFaceStatus("loading");
1497
+ setFaceDetectionFailed(false);
1498
+ (async () => {
1499
+ try {
1500
+ const FaceDetection = await loadMediaPipeFaceDetection();
1501
+ if (cancelled) return;
1502
+ const d = new FaceDetection({
1503
+ locateFile: (f) => `${MEDIAPIPE_BASE}/${f}`
1504
+ });
1505
+ d.setOptions({ model: "short", minDetectionConfidence: 0.5 });
1506
+ d.onResults((results) => {
1507
+ if (cancelled) return;
1508
+ const detections = results.detections ?? [];
1509
+ let next;
1510
+ if (detections.length === 0) {
1511
+ next = "scanning";
1512
+ } else {
1513
+ const bb = detections[0].boundingBox;
1514
+ if (bb.width < 0.18) next = "too_far";
1515
+ else if (bb.width > 0.72) next = "too_close";
1516
+ else if (bb.xCenter < 0.28 || bb.xCenter > 0.72 || bb.yCenter < 0.28 || bb.yCenter > 0.72) {
1517
+ next = "off_center";
1518
+ } else {
1519
+ next = "ok";
1520
+ }
1521
+ }
1522
+ setFaceStatus(next);
1523
+ });
1524
+ await d.initialize();
1525
+ if (cancelled) return;
1526
+ detector = d;
1527
+ setFaceStatus("scanning");
1528
+ const tick = async () => {
1529
+ if (cancelled) return;
1530
+ const v = videoRef.current;
1531
+ if (v?.readyState === 4 && detector) {
1532
+ try {
1533
+ await detector.send({ image: v });
1534
+ } catch {
1535
+ }
1536
+ }
1537
+ if (!cancelled) {
1538
+ rafId = requestAnimationFrame(tick);
1539
+ }
1540
+ };
1541
+ tick();
1542
+ } catch (err) {
1543
+ console.error("Face detection failed to initialize:", err);
1544
+ if (!cancelled) {
1545
+ setFaceDetectionFailed(true);
1546
+ setFaceStatus("scanning");
1547
+ }
1548
+ }
1549
+ })();
1550
+ return () => {
1551
+ cancelled = true;
1552
+ if (rafId !== null) cancelAnimationFrame(rafId);
1553
+ try {
1554
+ detector?.close?.();
1555
+ } catch {
1556
+ }
1557
+ };
1558
+ }, [ready]);
1406
1559
  const capture = () => {
1407
1560
  const video = videoRef.current;
1408
1561
  if (!video || !video.videoWidth) return;
@@ -1424,7 +1577,17 @@ function LiveCamera({ onCapture, onCancel, message }) {
1424
1577
  if (error) {
1425
1578
  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")));
1426
1579
  }
1427
- return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement("div", { style: videoFrameStyle }, /* @__PURE__ */ React.createElement(
1580
+ const detectionActive = ready && !faceDetectionFailed;
1581
+ const liveMessage = detectionActive ? FACE_MESSAGES[faceStatus] : message;
1582
+ const liveFaceColor = detectionActive ? FACE_COLORS[faceStatus] : void 0;
1583
+ const captureDisabled = !ready || detectionActive && faceStatus !== "ok";
1584
+ const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
1585
+ const dynamicCircleStyle = {
1586
+ ...faceCircleStyle,
1587
+ ...liveFaceColor ? { borderColor: liveFaceColor } : null,
1588
+ ...ringPulse ? { animation: ringPulse } : null
1589
+ };
1590
+ return /* @__PURE__ */ React.createElement("div", { style: videoFrameStyle }, /* @__PURE__ */ React.createElement(
1428
1591
  "video",
1429
1592
  {
1430
1593
  ref: videoRef,
@@ -1433,24 +1596,87 @@ function LiveCamera({ onCapture, onCancel, message }) {
1433
1596
  autoPlay: true,
1434
1597
  style: videoStyle
1435
1598
  }
1436
- ), /* @__PURE__ */ React.createElement("div", { style: faceOverlayContainerStyle, "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("div", { style: faceRingWrapStyle }, /* @__PURE__ */ React.createElement("div", { style: faceDashedRingStyle }), /* @__PURE__ */ React.createElement("div", { style: faceCircleStyle }))), message && /* @__PURE__ */ React.createElement("div", { style: liveMessagePillContainerStyle }, /* @__PURE__ */ React.createElement("div", { style: liveMessagePillStyle }, /* @__PURE__ */ React.createElement("span", { style: liveMessagePillTextStyle }, message))), !ready && /* @__PURE__ */ React.createElement("div", { style: videoLoadingStyle }, "Starting camera\u2026")), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement(
1599
+ ), /* @__PURE__ */ React.createElement("div", { style: faceOverlayContainerStyle, "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("div", { style: faceRingWrapStyle }, /* @__PURE__ */ React.createElement("div", { style: faceDashedRingStyle }), /* @__PURE__ */ React.createElement("div", { style: dynamicCircleStyle }))), liveMessage && /* @__PURE__ */ React.createElement("div", { style: liveMessagePillContainerStyle }, /* @__PURE__ */ React.createElement("div", { style: liveMessagePillStyle }, /* @__PURE__ */ React.createElement("span", { style: liveMessagePillTextStyle }, liveMessage))), /* @__PURE__ */ React.createElement(
1600
+ "button",
1601
+ {
1602
+ type: "button",
1603
+ onClick: onCancel,
1604
+ "aria-label": "Close camera",
1605
+ style: overlayCloseButtonStyle
1606
+ },
1607
+ /* @__PURE__ */ React.createElement("img", { src: Close, alt: "", width: 16, height: 16 })
1608
+ ), !ready && /* @__PURE__ */ React.createElement("div", { style: videoLoadingStyle }, "Starting camera\u2026"), /* @__PURE__ */ React.createElement("div", { style: bottomGradientStyle }, /* @__PURE__ */ React.createElement(
1437
1609
  "button",
1438
1610
  {
1439
- style: ready ? primaryButtonStyle : { ...primaryButtonStyle, opacity: 0.5, cursor: "not-allowed" },
1611
+ type: "button",
1440
1612
  onClick: capture,
1441
- disabled: !ready
1613
+ disabled: captureDisabled,
1614
+ "aria-label": "Capture",
1615
+ style: captureDisabled ? { ...shutterButtonStyle, opacity: 0.5, cursor: "not-allowed" } : shutterButtonStyle
1442
1616
  },
1443
- /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }),
1444
- /* @__PURE__ */ React.createElement("span", null, "Capture")
1445
- ), /* @__PURE__ */ React.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Cancel")));
1617
+ /* @__PURE__ */ React.createElement("span", { style: shutterInnerStyle })
1618
+ )));
1446
1619
  }
1447
1620
  var videoFrameStyle = {
1448
1621
  position: "relative",
1449
1622
  width: "100%",
1450
- aspectRatio: "1 / 1",
1451
- borderRadius: 12,
1623
+ aspectRatio: "3 / 4",
1624
+ borderRadius: 24,
1452
1625
  overflow: "hidden",
1453
- background: "#0f172a"
1626
+ background: "#000000",
1627
+ border: "4px solid rgba(255, 255, 255, 0.08)",
1628
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)"
1629
+ };
1630
+ var overlayCloseButtonStyle = {
1631
+ position: "absolute",
1632
+ top: 12,
1633
+ right: 12,
1634
+ width: 32,
1635
+ height: 32,
1636
+ display: "inline-flex",
1637
+ alignItems: "center",
1638
+ justifyContent: "center",
1639
+ padding: 0,
1640
+ border: "none",
1641
+ borderRadius: "50%",
1642
+ background: "rgba(0, 0, 0, 0.4)",
1643
+ backdropFilter: "blur(6px)",
1644
+ color: "#ffffff",
1645
+ cursor: "pointer",
1646
+ zIndex: 10
1647
+ };
1648
+ var bottomGradientStyle = {
1649
+ position: "absolute",
1650
+ bottom: 0,
1651
+ left: 0,
1652
+ right: 0,
1653
+ padding: "16px",
1654
+ background: "linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%)",
1655
+ display: "flex",
1656
+ alignItems: "center",
1657
+ justifyContent: "center",
1658
+ zIndex: 10
1659
+ };
1660
+ var shutterButtonStyle = {
1661
+ width: 64,
1662
+ height: 64,
1663
+ borderRadius: "50%",
1664
+ background: "rgba(255, 255, 255, 0.15)",
1665
+ backdropFilter: "blur(6px)",
1666
+ border: "2px solid rgba(255, 255, 255, 0.7)",
1667
+ display: "inline-flex",
1668
+ alignItems: "center",
1669
+ justifyContent: "center",
1670
+ cursor: "pointer",
1671
+ padding: 0,
1672
+ transition: "transform 120ms ease-out, background 120ms"
1673
+ };
1674
+ var shutterInnerStyle = {
1675
+ display: "block",
1676
+ width: 44,
1677
+ height: 44,
1678
+ borderRadius: "50%",
1679
+ background: "rgba(255, 255, 255, 0.85)"
1454
1680
  };
1455
1681
  var videoStyle = {
1456
1682
  width: "100%",
@@ -1494,7 +1720,9 @@ var videoLoadingStyle = {
1494
1720
  justifyContent: "center",
1495
1721
  color: "#e5e7eb",
1496
1722
  fontSize: 14,
1497
- fontWeight: 500
1723
+ fontWeight: 500,
1724
+ background: "rgba(0, 0, 0, 0.4)",
1725
+ zIndex: 5
1498
1726
  };
1499
1727
  var liveMessagePillContainerStyle = {
1500
1728
  position: "absolute",
@@ -1859,9 +2087,9 @@ function getRiskColor(risk) {
1859
2087
  };
1860
2088
  return colorMap[risk] || "#6b7280";
1861
2089
  }
1862
- function useReuseKYCSubmission(client) {
2090
+ function useEventBasedFaceVerificationSubmission(client) {
1863
2091
  const startSession = useCallback(
1864
- (request) => client.createReuseKycSession(request),
2092
+ (request) => client.createEventBasedFaceVerificationSession(request),
1865
2093
  [client]
1866
2094
  );
1867
2095
  const verifyFace = useCallback(
@@ -1878,6 +2106,7 @@ function useReuseKYCSubmission(client) {
1878
2106
  session,
1879
2107
  defaultDevice: opts.defaultDevice,
1880
2108
  renderQR: opts.renderQR,
2109
+ onCancel: opts.onCancel,
1881
2110
  onComplete: (result) => {
1882
2111
  cleanup();
1883
2112
  resolve(result);
@@ -1904,6 +2133,6 @@ function useReuseKYCSubmission(client) {
1904
2133
  return { startSession, verifyFace, runFlow };
1905
2134
  }
1906
2135
 
1907
- export { createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useReuseKYCSubmission, useTransactionVerification };
2136
+ export { createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useEventBasedFaceVerificationSubmission, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useTransactionVerification };
1908
2137
  //# sourceMappingURL=react.mjs.map
1909
2138
  //# sourceMappingURL=react.mjs.map