vesant-sdk 1.7.1-dev.9fc141f → 1.7.1-dev.a6a3bb0

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 +144 -19
  36. package/dist/react.js.map +1 -1
  37. package/dist/react.mjs +144 -19
  38. package/dist/react.mjs.map +1 -1
  39. package/package.json +1 -1
package/dist/react.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, Y as UseGeolocationOptions, Z as UseGeolocationResult, _ as UseLocationCaptureOptions, $ as UseLocationCaptureResult, a0 as UseLocationRequestsOptions, a1 as UseLocationRequestsResult } from './client-BwXMQA2a.mjs';
2
- import { e as LoginVerificationResponse, L as LoginVerificationRequest, f as RegistrationVerificationResponse, R as RegistrationVerificationRequest, h as TransactionVerificationResponse, g as TransactionVerificationRequest, C as ComplianceClient } from './client-D23KhWeh.mjs';
1
+ import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, Y as UseGeolocationOptions, Z as UseGeolocationResult, _ as UseLocationCaptureOptions, $ as UseLocationCaptureResult, a0 as UseLocationRequestsOptions, a1 as UseLocationRequestsResult } from './client-XDEhh9VN.mjs';
2
+ import { e as LoginVerificationResponse, L as LoginVerificationRequest, f as RegistrationVerificationResponse, R as RegistrationVerificationRequest, h as TransactionVerificationResponse, g as TransactionVerificationRequest, C as ComplianceClient } from './client-5q4whhGu.mjs';
3
3
  import { RiskProfileClient } from './risk-profile/index.mjs';
4
4
  import { C as CustomerProfile } from './types-BOFaMQxI.mjs';
5
5
  import { KycClient, CreateEventBasedFaceVerificationSessionRequest, CreateEventBasedFaceVerificationSessionResponse, EventBasedFaceVerificationCallback, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult } from './kyc/index.mjs';
@@ -63,6 +63,12 @@ declare function createDeviceFingerprint(): DeviceFingerprintRequest;
63
63
  /**
64
64
  * React hook for generating cipherText
65
65
  *
66
+ * The tenant's GPS requirements are read from the client before each generation.
67
+ * The client reuses a fetched copy for a short period, so this does not add a
68
+ * request per event, and a requirement changed in the compliance console takes
69
+ * effect without reloading the page. Call `refreshConfig` to apply a change
70
+ * immediately.
71
+ *
66
72
  * @example
67
73
  * ```tsx
68
74
  * function LoginForm() {
@@ -79,6 +85,7 @@ declare function createDeviceFingerprint(): DeviceFingerprintRequest;
79
85
  */
80
86
  declare function useCipherText(client?: GeolocationClient): {
81
87
  generate: (options: CipherTextOptions) => Promise<CipherTextResult | null>;
88
+ refreshConfig: () => Promise<void>;
82
89
  configLoading: boolean;
83
90
  configError: Error | null;
84
91
  };
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, Y as UseGeolocationOptions, Z as UseGeolocationResult, _ as UseLocationCaptureOptions, $ as UseLocationCaptureResult, a0 as UseLocationRequestsOptions, a1 as UseLocationRequestsResult } from './client-fy3trF2K.js';
2
- import { e as LoginVerificationResponse, L as LoginVerificationRequest, f as RegistrationVerificationResponse, R as RegistrationVerificationRequest, h as TransactionVerificationResponse, g as TransactionVerificationRequest, C as ComplianceClient } from './client-B3DCAHlg.js';
1
+ import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, Y as UseGeolocationOptions, Z as UseGeolocationResult, _ as UseLocationCaptureOptions, $ as UseLocationCaptureResult, a0 as UseLocationRequestsOptions, a1 as UseLocationRequestsResult } from './client-BaUekQT8.js';
2
+ import { e as LoginVerificationResponse, L as LoginVerificationRequest, f as RegistrationVerificationResponse, R as RegistrationVerificationRequest, h as TransactionVerificationResponse, g as TransactionVerificationRequest, C as ComplianceClient } from './client-DUHC-68_.js';
3
3
  import { RiskProfileClient } from './risk-profile/index.js';
4
4
  import { C as CustomerProfile } from './types-UGyDl1fd.js';
5
5
  import { KycClient, CreateEventBasedFaceVerificationSessionRequest, CreateEventBasedFaceVerificationSessionResponse, EventBasedFaceVerificationCallback, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult } from './kyc/index.js';
@@ -63,6 +63,12 @@ declare function createDeviceFingerprint(): DeviceFingerprintRequest;
63
63
  /**
64
64
  * React hook for generating cipherText
65
65
  *
66
+ * The tenant's GPS requirements are read from the client before each generation.
67
+ * The client reuses a fetched copy for a short period, so this does not add a
68
+ * request per event, and a requirement changed in the compliance console takes
69
+ * effect without reloading the page. Call `refreshConfig` to apply a change
70
+ * immediately.
71
+ *
66
72
  * @example
67
73
  * ```tsx
68
74
  * function LoginForm() {
@@ -79,6 +85,7 @@ declare function createDeviceFingerprint(): DeviceFingerprintRequest;
79
85
  */
80
86
  declare function useCipherText(client?: GeolocationClient): {
81
87
  generate: (options: CipherTextOptions) => Promise<CipherTextResult | null>;
88
+ refreshConfig: () => Promise<void>;
82
89
  configLoading: boolean;
83
90
  configError: Error | null;
84
91
  };
package/dist/react.js CHANGED
@@ -429,14 +429,11 @@ function useCipherText(client) {
429
429
  const [configLoading, setConfigLoading] = React.useState(!!client);
430
430
  const [configError, setConfigError] = React.useState(null);
431
431
  const configRef = React.useRef(void 0);
432
- const configPromiseRef = React.useRef(null);
433
432
  React.useEffect(() => {
434
433
  if (!client) return;
435
434
  let cancelled = false;
436
435
  setConfigLoading(true);
437
- const promise = client.getGPSConfig();
438
- configPromiseRef.current = promise;
439
- promise.then((cfg) => {
436
+ client.getGPSConfig().then((cfg) => {
440
437
  if (!cancelled) {
441
438
  configRef.current = cfg;
442
439
  setConfigLoading(false);
@@ -451,13 +448,21 @@ function useCipherText(client) {
451
448
  cancelled = true;
452
449
  };
453
450
  }, [client]);
451
+ const refreshConfig = React.useCallback(async () => {
452
+ if (!client) return;
453
+ try {
454
+ configRef.current = await client.refreshGPSConfig();
455
+ setConfigError(null);
456
+ } catch (error) {
457
+ setConfigError(error instanceof Error ? error : new Error("Failed to refresh GPS config"));
458
+ }
459
+ }, [client]);
454
460
  const generate = React.useCallback(
455
461
  async (options) => {
456
462
  try {
457
- if (configPromiseRef.current) {
463
+ if (client) {
458
464
  try {
459
- const cfg = await configPromiseRef.current;
460
- configRef.current = cfg;
465
+ configRef.current = await client.getGPSConfig();
461
466
  } catch {
462
467
  }
463
468
  }
@@ -467,9 +472,9 @@ function useCipherText(client) {
467
472
  return null;
468
473
  }
469
474
  },
470
- []
475
+ [client]
471
476
  );
472
- return { generate, configLoading, configError };
477
+ return { generate, refreshConfig, configLoading, configError };
473
478
  }
474
479
  function useLocationRequests(client, options = {}) {
475
480
  const [requests, setRequests] = React.useState([]);
@@ -1007,9 +1012,30 @@ function loadMediaPipeFaceDetection(assetBasePath) {
1007
1012
  return loader;
1008
1013
  }
1009
1014
 
1015
+ // src/kyc/hostedJourney.ts
1016
+ var REUSE_KYC_SESSION_EXPIRY_MS = 10 * 60 * 1e3;
1017
+ function tryStartOperation(guard) {
1018
+ if (guard.current) return false;
1019
+ guard.current = true;
1020
+ return true;
1021
+ }
1022
+ function releaseOperation(guard) {
1023
+ guard.current = false;
1024
+ }
1025
+ function retryTarget(hasHostedJourney) {
1026
+ return hasHostedJourney ? "journey" : "capture";
1027
+ }
1028
+ function modalMaxWidth(hasHostedJourney) {
1029
+ return hasHostedJourney ? 680 : 448;
1030
+ }
1031
+ function resultForModalClose(stageKind, result) {
1032
+ const terminal = stageKind === "accepted" || stageKind === "declined" || stageKind === "max_attempts";
1033
+ return terminal && result !== void 0 ? result : null;
1034
+ }
1035
+
1010
1036
  // src/kyc/FaceCaptureModal.tsx
1011
1037
  var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
1012
- var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
1038
+ var ACCEPTED_AUTO_CLOSE_MS = 1200;
1013
1039
  var LIVENESS_MESSAGES = [
1014
1040
  "Position your face inside the oval",
1015
1041
  "Make sure your face is well lit",
@@ -1020,6 +1046,8 @@ function headerSubtitle(stageKind) {
1020
1046
  switch (stageKind) {
1021
1047
  case "qr":
1022
1048
  return "Use your phone to continue";
1049
+ case "journey":
1050
+ return "Follow the steps to verify your identity";
1023
1051
  case "accepted":
1024
1052
  return "All set";
1025
1053
  case "declined":
@@ -1045,7 +1073,9 @@ function FaceCaptureModal({
1045
1073
  }) {
1046
1074
  const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
1047
1075
  const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
1076
+ const journeyMode = Boolean(session.verification_url);
1048
1077
  const initialStage = (() => {
1078
+ if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
1049
1079
  if (initialChoice === "this") return { kind: "capture" };
1050
1080
  if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
1051
1081
  return { kind: "choose" };
@@ -1063,19 +1093,20 @@ function FaceCaptureModal({
1063
1093
  const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
1064
1094
  const cancelledRef = React.useRef(false);
1065
1095
  const submittingRef = React.useRef(false);
1096
+ const journeyRetryingRef = React.useRef(false);
1066
1097
  React.useEffect(() => {
1067
1098
  const timer = setTimeout(() => {
1068
1099
  setStage(
1069
- (prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
1100
+ (prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
1070
1101
  );
1071
- }, SESSION_EXPIRY_MS);
1102
+ }, REUSE_KYC_SESSION_EXPIRY_MS);
1072
1103
  return () => clearTimeout(timer);
1073
1104
  }, [session.token]);
1074
1105
  React.useEffect(() => {
1075
1106
  if (stage.kind !== "qr") return;
1076
1107
  let stopped = false;
1077
1108
  const intervalMs = 2e3;
1078
- const deadline = Date.now() + SESSION_EXPIRY_MS;
1109
+ const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
1079
1110
  let mobileSeen = stage.mobileConnected;
1080
1111
  const tick = async () => {
1081
1112
  if (stopped || cancelledRef.current) return;
@@ -1116,6 +1147,64 @@ function FaceCaptureModal({
1116
1147
  stopped = true;
1117
1148
  };
1118
1149
  }, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
1150
+ React.useEffect(() => {
1151
+ if (stage.kind !== "journey" || !stage.url) return;
1152
+ let stopped = false;
1153
+ const checkOnce = async () => {
1154
+ try {
1155
+ const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
1156
+ if (applyResult(result)) {
1157
+ stopped = true;
1158
+ return true;
1159
+ }
1160
+ } catch {
1161
+ }
1162
+ return false;
1163
+ };
1164
+ const tick = async () => {
1165
+ if (stopped || cancelledRef.current) return;
1166
+ await checkOnce();
1167
+ if (!stopped && !cancelledRef.current) {
1168
+ setTimeout(tick, 2500);
1169
+ }
1170
+ };
1171
+ void tick();
1172
+ let frameOrigin = "";
1173
+ try {
1174
+ frameOrigin = new URL(stage.url).origin;
1175
+ } catch {
1176
+ frameOrigin = "";
1177
+ }
1178
+ const onMessage = (e) => {
1179
+ if (!frameOrigin || e.origin !== frameOrigin) return;
1180
+ const status = e.data?.verification_status;
1181
+ if (status) void checkOnce();
1182
+ };
1183
+ window.addEventListener("message", onMessage);
1184
+ return () => {
1185
+ stopped = true;
1186
+ window.removeEventListener("message", onMessage);
1187
+ };
1188
+ }, [stage.kind, stage.kind === "journey" ? stage.url : ""]);
1189
+ const handleJourneyRetry = async () => {
1190
+ if (!tryStartOperation(journeyRetryingRef)) return;
1191
+ setStage({ kind: "journey", url: "" });
1192
+ try {
1193
+ const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
1194
+ if (refreshed.verification_url) {
1195
+ setStage({ kind: "journey", url: refreshed.verification_url });
1196
+ return;
1197
+ }
1198
+ setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
1199
+ } catch (err) {
1200
+ setStage({
1201
+ kind: "error",
1202
+ message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
1203
+ });
1204
+ } finally {
1205
+ releaseOperation(journeyRetryingRef);
1206
+ }
1207
+ };
1119
1208
  const applyResult = (result) => {
1120
1209
  if (result.status === "accepted") {
1121
1210
  setStage({ kind: "accepted", result });
@@ -1243,12 +1332,22 @@ function FaceCaptureModal({
1243
1332
  }, 3e3);
1244
1333
  return () => clearInterval(interval);
1245
1334
  }, [captureMode]);
1246
- const close = (result) => {
1335
+ const close = React.useCallback((result) => {
1247
1336
  cancelledRef.current = true;
1248
1337
  if (result === null && onCancel) {
1249
1338
  onCancel();
1250
1339
  }
1251
1340
  onComplete(result);
1341
+ }, [onCancel, onComplete]);
1342
+ React.useEffect(() => {
1343
+ if (stage.kind !== "accepted") return;
1344
+ const result = stage.result;
1345
+ const timer = setTimeout(() => close(result), ACCEPTED_AUTO_CLOSE_MS);
1346
+ return () => clearTimeout(timer);
1347
+ }, [close, stage]);
1348
+ const handleHeaderClose = () => {
1349
+ const result = "result" in stage ? stage.result : void 0;
1350
+ close(resultForModalClose(stage.kind, result));
1252
1351
  };
1253
1352
  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
1353
  "button",
@@ -1307,12 +1406,28 @@ function FaceCaptureModal({
1307
1406
  };
1308
1407
  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
1408
  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));
1409
+ 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
1410
  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
1411
  const renderError = (message) => {
1313
1412
  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("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try Again"), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
1413
+ 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(
1414
+ "button",
1415
+ {
1416
+ style: primaryButtonStyle,
1417
+ onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
1418
+ },
1419
+ "Try Again"
1420
+ ), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
1315
1421
  };
1422
+ const renderJourney = (url) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, url ? /* @__PURE__ */ React__default.default.createElement(
1423
+ "iframe",
1424
+ {
1425
+ src: url,
1426
+ title: "Identity verification",
1427
+ allow: "camera; microphone",
1428
+ style: journeyFrameStyle
1429
+ }
1430
+ ) : /* @__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
1431
  const body = (() => {
1317
1432
  switch (stage.kind) {
1318
1433
  case "choose":
@@ -1321,6 +1436,8 @@ function FaceCaptureModal({
1321
1436
  return renderQRStage(stage.mobileConnected);
1322
1437
  case "capture":
1323
1438
  return renderCapture(stage.declinedReason);
1439
+ case "journey":
1440
+ return renderJourney(stage.url);
1324
1441
  case "submitting":
1325
1442
  return renderSubmitting();
1326
1443
  case "accepted":
@@ -1333,10 +1450,10 @@ function FaceCaptureModal({
1333
1450
  return renderError(stage.message);
1334
1451
  }
1335
1452
  })();
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(
1453
+ 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
1454
  "button",
1338
1455
  {
1339
- onClick: () => close(null),
1456
+ onClick: handleHeaderClose,
1340
1457
  style: closeButtonStyle,
1341
1458
  "aria-label": "Close"
1342
1459
  },
@@ -1366,7 +1483,8 @@ var modalStyle = {
1366
1483
  borderRadius: 16,
1367
1484
  boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
1368
1485
  width: "100%",
1369
- maxWidth: 448,
1486
+ maxHeight: "calc(100vh - 32px)",
1487
+ overflowY: "auto",
1370
1488
  animation: "zoomIn .2s ease-out"
1371
1489
  };
1372
1490
  var headerStyle = {
@@ -1392,6 +1510,13 @@ var contentStyle = {
1392
1510
  flexDirection: "column",
1393
1511
  gap: 16
1394
1512
  };
1513
+ var journeyFrameStyle = {
1514
+ width: "100%",
1515
+ height: "min(520px, 60vh)",
1516
+ border: 0,
1517
+ borderRadius: 12,
1518
+ background: "#f9fafb"
1519
+ };
1395
1520
  var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
1396
1521
  var visualGuideStyle = { position: "relative" };
1397
1522
  var circleStyle = {