vesant-sdk 1.7.1-dev.14ba380 → 1.7.1-dev.1c1dca2

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.
Files changed (39) hide show
  1. package/dist/{client-D23KhWeh.d.mts → client-5q4whhGu.d.mts} +1 -1
  2. package/dist/{client-fy3trF2K.d.ts → client-BaUekQT8.d.ts} +32 -10
  3. package/dist/{client-B3DCAHlg.d.ts → client-DUHC-68_.d.ts} +1 -1
  4. package/dist/{client-BwXMQA2a.d.mts → client-XDEhh9VN.d.mts} +32 -10
  5. package/dist/compliance/index.d.mts +2 -2
  6. package/dist/compliance/index.d.ts +2 -2
  7. package/dist/compliance/index.js +39 -1
  8. package/dist/compliance/index.js.map +1 -1
  9. package/dist/compliance/index.mjs +39 -1
  10. package/dist/compliance/index.mjs.map +1 -1
  11. package/dist/geolocation/index.d.mts +2 -2
  12. package/dist/geolocation/index.d.ts +2 -2
  13. package/dist/geolocation/index.js +39 -1
  14. package/dist/geolocation/index.js.map +1 -1
  15. package/dist/geolocation/index.mjs +39 -1
  16. package/dist/geolocation/index.mjs.map +1 -1
  17. package/dist/index.d.mts +2 -2
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +62 -8
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +62 -8
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/kyc/core.js +23 -7
  24. package/dist/kyc/core.js.map +1 -1
  25. package/dist/kyc/core.mjs +23 -7
  26. package/dist/kyc/core.mjs.map +1 -1
  27. package/dist/kyc/index.d.mts +28 -10
  28. package/dist/kyc/index.d.ts +28 -10
  29. package/dist/kyc/index.js +23 -7
  30. package/dist/kyc/index.js.map +1 -1
  31. package/dist/kyc/index.mjs +23 -7
  32. package/dist/kyc/index.mjs.map +1 -1
  33. package/dist/react.d.mts +9 -2
  34. package/dist/react.d.ts +9 -2
  35. package/dist/react.js +225 -19
  36. package/dist/react.js.map +1 -1
  37. package/dist/react.mjs +225 -19
  38. package/dist/react.mjs.map +1 -1
  39. package/package.json +1 -1
package/dist/react.mjs CHANGED
@@ -423,14 +423,11 @@ function useCipherText(client) {
423
423
  const [configLoading, setConfigLoading] = useState(!!client);
424
424
  const [configError, setConfigError] = useState(null);
425
425
  const configRef = useRef(void 0);
426
- const configPromiseRef = useRef(null);
427
426
  useEffect(() => {
428
427
  if (!client) return;
429
428
  let cancelled = false;
430
429
  setConfigLoading(true);
431
- const promise = client.getGPSConfig();
432
- configPromiseRef.current = promise;
433
- promise.then((cfg) => {
430
+ client.getGPSConfig().then((cfg) => {
434
431
  if (!cancelled) {
435
432
  configRef.current = cfg;
436
433
  setConfigLoading(false);
@@ -445,13 +442,21 @@ function useCipherText(client) {
445
442
  cancelled = true;
446
443
  };
447
444
  }, [client]);
445
+ const refreshConfig = useCallback(async () => {
446
+ if (!client) return;
447
+ try {
448
+ configRef.current = await client.refreshGPSConfig();
449
+ setConfigError(null);
450
+ } catch (error) {
451
+ setConfigError(error instanceof Error ? error : new Error("Failed to refresh GPS config"));
452
+ }
453
+ }, [client]);
448
454
  const generate = useCallback(
449
455
  async (options) => {
450
456
  try {
451
- if (configPromiseRef.current) {
457
+ if (client) {
452
458
  try {
453
- const cfg = await configPromiseRef.current;
454
- configRef.current = cfg;
459
+ configRef.current = await client.getGPSConfig();
455
460
  } catch {
456
461
  }
457
462
  }
@@ -461,9 +466,9 @@ function useCipherText(client) {
461
466
  return null;
462
467
  }
463
468
  },
464
- []
469
+ [client]
465
470
  );
466
- return { generate, configLoading, configError };
471
+ return { generate, refreshConfig, configLoading, configError };
467
472
  }
468
473
  function useLocationRequests(client, options = {}) {
469
474
  const [requests, setRequests] = useState([]);
@@ -1001,9 +1006,75 @@ function loadMediaPipeFaceDetection(assetBasePath) {
1001
1006
  return loader;
1002
1007
  }
1003
1008
 
1009
+ // src/kyc/hostedJourney.ts
1010
+ var REUSE_KYC_SESSION_EXPIRY_MS = 10 * 60 * 1e3;
1011
+ var JOURNEY_FRAME_HEIGHT = "clamp(500px, 64vh, 680px)";
1012
+ var JOURNEY_CONFIRM_TIMEOUT_MS = 45e3;
1013
+ function journeyOutcomeFromMessage(data) {
1014
+ const status = data?.verification_status;
1015
+ if (typeof status !== "string") return null;
1016
+ switch (status) {
1017
+ case "verification.accepted":
1018
+ return "accepted";
1019
+ case "verification.declined":
1020
+ return "declined";
1021
+ case "verification.cancelled":
1022
+ case "request.timeout":
1023
+ case "request.invalid":
1024
+ case "request.unauthorized":
1025
+ case "request.deleted":
1026
+ return "cancelled";
1027
+ default:
1028
+ return null;
1029
+ }
1030
+ }
1031
+ function journeyPollIntervalMs(confirming) {
1032
+ return confirming ? 1e3 : 2500;
1033
+ }
1034
+ function pendingJourneyResult(reference) {
1035
+ return {
1036
+ event: "reuse_kyc",
1037
+ reference,
1038
+ resource_id: "",
1039
+ status: "pending",
1040
+ data: {
1041
+ retries_remaining: 0,
1042
+ retry_limit_exceeded: false,
1043
+ trigger_alert: false,
1044
+ enforce_logout: false,
1045
+ freeze_account: false,
1046
+ block: false,
1047
+ freeze_duration_minutes: 0
1048
+ }
1049
+ };
1050
+ }
1051
+ function tryStartOperation(guard) {
1052
+ if (guard.current) return false;
1053
+ guard.current = true;
1054
+ return true;
1055
+ }
1056
+ function releaseOperation(guard) {
1057
+ guard.current = false;
1058
+ }
1059
+ function retryTarget(hasHostedJourney) {
1060
+ return hasHostedJourney ? "journey" : "capture";
1061
+ }
1062
+ var CAPTURE_MODAL_MAX_WIDTH = 448;
1063
+ function modalMaxWidth(hasHostedJourney) {
1064
+ return hasHostedJourney ? CAPTURE_MODAL_MAX_WIDTH : CAPTURE_MODAL_MAX_WIDTH;
1065
+ }
1066
+ var JOURNEY_CONTENT_PADDING = 12;
1067
+ function resultForModalClose(stageKind, result, unconfirmed) {
1068
+ const terminal = stageKind === "accepted" || stageKind === "declined" || stageKind === "max_attempts";
1069
+ if (terminal && result !== void 0) return result;
1070
+ const awaitingConfirmation = stageKind === "confirming" || stageKind === "unconfirmed";
1071
+ if (awaitingConfirmation && unconfirmed !== void 0) return unconfirmed;
1072
+ return null;
1073
+ }
1074
+
1004
1075
  // src/kyc/FaceCaptureModal.tsx
1005
1076
  var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
1006
- var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
1077
+ var ACCEPTED_AUTO_CLOSE_MS = 1200;
1007
1078
  var LIVENESS_MESSAGES = [
1008
1079
  "Position your face inside the oval",
1009
1080
  "Make sure your face is well lit",
@@ -1014,6 +1085,12 @@ function headerSubtitle(stageKind) {
1014
1085
  switch (stageKind) {
1015
1086
  case "qr":
1016
1087
  return "Use your phone to continue";
1088
+ case "journey":
1089
+ return "Follow the steps to verify your identity";
1090
+ case "confirming":
1091
+ return "Confirming your result";
1092
+ case "unconfirmed":
1093
+ return "Still confirming";
1017
1094
  case "accepted":
1018
1095
  return "All set";
1019
1096
  case "declined":
@@ -1039,7 +1116,9 @@ function FaceCaptureModal({
1039
1116
  }) {
1040
1117
  const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
1041
1118
  const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
1119
+ const journeyMode = Boolean(session.verification_url);
1042
1120
  const initialStage = (() => {
1121
+ if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
1043
1122
  if (initialChoice === "this") return { kind: "capture" };
1044
1123
  if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
1045
1124
  return { kind: "choose" };
@@ -1057,19 +1136,20 @@ function FaceCaptureModal({
1057
1136
  const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
1058
1137
  const cancelledRef = useRef(false);
1059
1138
  const submittingRef = useRef(false);
1139
+ const journeyRetryingRef = useRef(false);
1060
1140
  useEffect(() => {
1061
1141
  const timer = setTimeout(() => {
1062
1142
  setStage(
1063
- (prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
1143
+ (prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
1064
1144
  );
1065
- }, SESSION_EXPIRY_MS);
1145
+ }, REUSE_KYC_SESSION_EXPIRY_MS);
1066
1146
  return () => clearTimeout(timer);
1067
1147
  }, [session.token]);
1068
1148
  useEffect(() => {
1069
1149
  if (stage.kind !== "qr") return;
1070
1150
  let stopped = false;
1071
1151
  const intervalMs = 2e3;
1072
- const deadline = Date.now() + SESSION_EXPIRY_MS;
1152
+ const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
1073
1153
  let mobileSeen = stage.mobileConnected;
1074
1154
  const tick = async () => {
1075
1155
  if (stopped || cancelledRef.current) return;
@@ -1110,6 +1190,82 @@ function FaceCaptureModal({
1110
1190
  stopped = true;
1111
1191
  };
1112
1192
  }, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
1193
+ const journeyUrl = stage.kind === "journey" || stage.kind === "confirming" ? stage.url : "";
1194
+ useEffect(() => {
1195
+ if (stage.kind !== "journey" && stage.kind !== "confirming" || !journeyUrl) return;
1196
+ const intervalMs = journeyPollIntervalMs(stage.kind === "confirming");
1197
+ let stopped = false;
1198
+ const checkOnce = async () => {
1199
+ try {
1200
+ const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
1201
+ if (applyResult(result)) {
1202
+ stopped = true;
1203
+ return true;
1204
+ }
1205
+ } catch {
1206
+ }
1207
+ return false;
1208
+ };
1209
+ const tick = async () => {
1210
+ if (stopped || cancelledRef.current) return;
1211
+ await checkOnce();
1212
+ if (!stopped && !cancelledRef.current) {
1213
+ setTimeout(tick, intervalMs);
1214
+ }
1215
+ };
1216
+ void tick();
1217
+ let frameOrigin = "";
1218
+ try {
1219
+ frameOrigin = new URL(journeyUrl).origin;
1220
+ } catch {
1221
+ frameOrigin = "";
1222
+ }
1223
+ const onMessage = (e) => {
1224
+ if (!frameOrigin || e.origin !== frameOrigin) return;
1225
+ const outcome = journeyOutcomeFromMessage(e.data);
1226
+ if (!outcome) return;
1227
+ void checkOnce();
1228
+ setStage((prev) => {
1229
+ if (prev.kind !== "journey") return prev;
1230
+ if (outcome === "cancelled") {
1231
+ return { kind: "error", message: "The verification was closed before it finished. Please try again." };
1232
+ }
1233
+ return { kind: "confirming", outcome, url: prev.url };
1234
+ });
1235
+ };
1236
+ window.addEventListener("message", onMessage);
1237
+ return () => {
1238
+ stopped = true;
1239
+ window.removeEventListener("message", onMessage);
1240
+ };
1241
+ }, [stage.kind, journeyUrl]);
1242
+ useEffect(() => {
1243
+ if (stage.kind !== "confirming") return;
1244
+ const outcome = stage.outcome;
1245
+ const timer = setTimeout(() => {
1246
+ setStage((prev) => prev.kind === "confirming" ? { kind: "unconfirmed", outcome } : prev);
1247
+ }, JOURNEY_CONFIRM_TIMEOUT_MS);
1248
+ return () => clearTimeout(timer);
1249
+ }, [stage.kind, stage.kind === "confirming" ? stage.outcome : ""]);
1250
+ const handleJourneyRetry = async () => {
1251
+ if (!tryStartOperation(journeyRetryingRef)) return;
1252
+ setStage({ kind: "journey", url: "" });
1253
+ try {
1254
+ const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
1255
+ if (refreshed.verification_url) {
1256
+ setStage({ kind: "journey", url: refreshed.verification_url });
1257
+ return;
1258
+ }
1259
+ setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
1260
+ } catch (err) {
1261
+ setStage({
1262
+ kind: "error",
1263
+ message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
1264
+ });
1265
+ } finally {
1266
+ releaseOperation(journeyRetryingRef);
1267
+ }
1268
+ };
1113
1269
  const applyResult = (result) => {
1114
1270
  if (result.status === "accepted") {
1115
1271
  setStage({ kind: "accepted", result });
@@ -1237,12 +1393,23 @@ function FaceCaptureModal({
1237
1393
  }, 3e3);
1238
1394
  return () => clearInterval(interval);
1239
1395
  }, [captureMode]);
1240
- const close = (result) => {
1396
+ const close = useCallback((result) => {
1241
1397
  cancelledRef.current = true;
1242
1398
  if (result === null && onCancel) {
1243
1399
  onCancel();
1244
1400
  }
1245
1401
  onComplete(result);
1402
+ }, [onCancel, onComplete]);
1403
+ useEffect(() => {
1404
+ if (stage.kind !== "accepted") return;
1405
+ const result = stage.result;
1406
+ const timer = setTimeout(() => close(result), ACCEPTED_AUTO_CLOSE_MS);
1407
+ return () => clearTimeout(timer);
1408
+ }, [close, stage]);
1409
+ const handleHeaderClose = () => {
1410
+ const result = "result" in stage ? stage.result : void 0;
1411
+ const unconfirmed = stage.kind === "confirming" || stage.kind === "unconfirmed" ? pendingJourneyResult(session.reference) : void 0;
1412
+ close(resultForModalClose(stage.kind, result, unconfirmed));
1246
1413
  };
1247
1414
  const renderChoose = () => /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React.createElement("div", { style: circleStyle }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })))), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "How would you like to verify?"), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement(
1248
1415
  "button",
@@ -1301,12 +1468,37 @@ function FaceCaptureModal({
1301
1468
  };
1302
1469
  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
1470
  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));
1471
+ 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
1472
  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
1473
  const renderError = (message) => {
1307
1474
  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("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try Again"), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
1475
+ 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(
1476
+ "button",
1477
+ {
1478
+ style: primaryButtonStyle,
1479
+ onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
1480
+ },
1481
+ "Try Again"
1482
+ ), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
1309
1483
  };
1484
+ const renderConfirming = () => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Confirming your result\u2026"), /* @__PURE__ */ React.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
1485
+ const renderUnconfirmed = (outcome) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Still confirming"), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, outcome === "accepted" ? "Your verification was submitted, but we haven't received the final result yet. You can close this window and we'll keep checking in the background." : "Your last attempt wasn't accepted, but we haven't received the details yet. You can try again or close this window."), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, outcome === "declined" && /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => void handleJourneyRetry() }, "Try Again"), /* @__PURE__ */ React.createElement(
1486
+ "button",
1487
+ {
1488
+ style: outcome === "accepted" ? primaryButtonStyle : cancelButtonStyle,
1489
+ onClick: () => close(pendingJourneyResult(session.reference))
1490
+ },
1491
+ "Close"
1492
+ )));
1493
+ const renderJourney = (url) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, padding: JOURNEY_CONTENT_PADDING } }, url ? /* @__PURE__ */ React.createElement(
1494
+ "iframe",
1495
+ {
1496
+ src: url,
1497
+ title: "Identity verification",
1498
+ allow: "camera; microphone",
1499
+ style: journeyFrameStyle
1500
+ }
1501
+ ) : /* @__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
1502
  const body = (() => {
1311
1503
  switch (stage.kind) {
1312
1504
  case "choose":
@@ -1315,6 +1507,12 @@ function FaceCaptureModal({
1315
1507
  return renderQRStage(stage.mobileConnected);
1316
1508
  case "capture":
1317
1509
  return renderCapture(stage.declinedReason);
1510
+ case "journey":
1511
+ return renderJourney(stage.url);
1512
+ case "confirming":
1513
+ return renderConfirming();
1514
+ case "unconfirmed":
1515
+ return renderUnconfirmed(stage.outcome);
1318
1516
  case "submitting":
1319
1517
  return renderSubmitting();
1320
1518
  case "accepted":
@@ -1327,10 +1525,10 @@ function FaceCaptureModal({
1327
1525
  return renderError(stage.message);
1328
1526
  }
1329
1527
  })();
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(
1528
+ 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
1529
  "button",
1332
1530
  {
1333
- onClick: () => close(null),
1531
+ onClick: handleHeaderClose,
1334
1532
  style: closeButtonStyle,
1335
1533
  "aria-label": "Close"
1336
1534
  },
@@ -1360,7 +1558,8 @@ var modalStyle = {
1360
1558
  borderRadius: 16,
1361
1559
  boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
1362
1560
  width: "100%",
1363
- maxWidth: 448,
1561
+ maxHeight: "calc(100vh - 32px)",
1562
+ overflowY: "auto",
1364
1563
  animation: "zoomIn .2s ease-out"
1365
1564
  };
1366
1565
  var headerStyle = {
@@ -1386,6 +1585,13 @@ var contentStyle = {
1386
1585
  flexDirection: "column",
1387
1586
  gap: 16
1388
1587
  };
1588
+ var journeyFrameStyle = {
1589
+ width: "100%",
1590
+ height: JOURNEY_FRAME_HEIGHT,
1591
+ border: 0,
1592
+ borderRadius: 12,
1593
+ background: "#f9fafb"
1594
+ };
1389
1595
  var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
1390
1596
  var visualGuideStyle = { position: "relative" };
1391
1597
  var circleStyle = {