vesant-sdk 1.7.0-dev.9f7da53 → 1.7.0-dev.a85efc5

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 (43) hide show
  1. package/dist/{client-ZNdnpWe7.d.mts → client-B0qhE2kr.d.mts} +1 -1
  2. package/dist/{client-DoMSYMMR.d.ts → client-DF7hlMEz.d.ts} +13 -0
  3. package/dist/{client-DMIRx7Tu.d.mts → client-DrjgZoH_.d.mts} +13 -0
  4. package/dist/{client-C3DCmGe9.d.ts → client-DtH2RLuy.d.ts} +1 -1
  5. package/dist/compliance/index.d.mts +2 -2
  6. package/dist/compliance/index.d.ts +2 -2
  7. package/dist/compliance/index.js +0 -3
  8. package/dist/compliance/index.js.map +1 -1
  9. package/dist/compliance/index.mjs +0 -3
  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/index.d.mts +4 -4
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +24 -20
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +24 -20
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/kyc/core.d.mts +1 -1
  20. package/dist/kyc/core.d.ts +1 -1
  21. package/dist/kyc/core.js +20 -16
  22. package/dist/kyc/core.js.map +1 -1
  23. package/dist/kyc/core.mjs +20 -16
  24. package/dist/kyc/core.mjs.map +1 -1
  25. package/dist/kyc/index.d.mts +110 -55
  26. package/dist/kyc/index.d.ts +110 -55
  27. package/dist/kyc/index.js +20 -16
  28. package/dist/kyc/index.js.map +1 -1
  29. package/dist/kyc/index.mjs +20 -16
  30. package/dist/kyc/index.mjs.map +1 -1
  31. package/dist/react.d.mts +16 -14
  32. package/dist/react.d.ts +16 -14
  33. package/dist/react.js +131 -83
  34. package/dist/react.js.map +1 -1
  35. package/dist/react.mjs +131 -83
  36. package/dist/react.mjs.map +1 -1
  37. package/dist/tax/index.d.mts +18 -1
  38. package/dist/tax/index.d.ts +18 -1
  39. package/dist/tax/index.js +4 -1
  40. package/dist/tax/index.js.map +1 -1
  41. package/dist/tax/index.mjs +4 -1
  42. package/dist/tax/index.mjs.map +1 -1
  43. package/package.json +1 -1
package/dist/react.d.mts CHANGED
@@ -1,8 +1,8 @@
1
- import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, W as UseGeolocationOptions, X as UseGeolocationResult, Y as UseLocationCaptureOptions, Z as UseLocationCaptureResult, _ as UseLocationRequestsOptions, $ as UseLocationRequestsResult } from './client-DMIRx7Tu.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-ZNdnpWe7.mjs';
1
+ import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, W as UseGeolocationOptions, X as UseGeolocationResult, Y as UseLocationCaptureOptions, Z as UseLocationCaptureResult, _ as UseLocationRequestsOptions, $ as UseLocationRequestsResult } from './client-DrjgZoH_.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-B0qhE2kr.mjs';
3
3
  import { RiskProfileClient } from './risk-profile/index.mjs';
4
4
  import { C as CustomerProfile } from './types-BOFaMQxI.mjs';
5
- import { KycClient, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult, CreateReuseKycSessionRequest, CreateReuseKycSessionResponse, ReuseKycCallback } from './kyc/index.mjs';
5
+ import { KycClient, CreateEventBasedFaceVerificationSessionRequest, CreateEventBasedFaceVerificationSessionResponse, EventBasedFaceVerificationCallback, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult } from './kyc/index.mjs';
6
6
  import './client-BolQlL5e.mjs';
7
7
  import './types-CBQRNL-l.mjs';
8
8
 
@@ -508,7 +508,7 @@ declare function getStatusColor(status: string): string;
508
508
  */
509
509
  declare function getRiskColor(risk: string): string;
510
510
  /**
511
- * Hook for the Re-Use KYC face-capture flow.
511
+ * Hook for the Event-Based Face Verification face-capture flow.
512
512
  *
513
513
  * Returns helpers that cover both mobile (direct capture) and desktop
514
514
  * (QR + polling) modes. Device detection happens here on the client —
@@ -524,28 +524,30 @@ declare function getRiskColor(risk: string): string;
524
524
  * - `runFlow(req, opts?)` — convenience wrapper: `startSession` then
525
525
  * `verifyFace`, with a short-circuit when `is_required` is false.
526
526
  */
527
- declare function useReuseKYCSubmission(client: KycClient): {
528
- startSession: (request: CreateReuseKycSessionRequest) => Promise<CreateReuseKycSessionResponse>;
529
- verifyFace: (session: CreateReuseKycSessionResponse, opts?: {
527
+ declare function useEventBasedFaceVerificationSubmission(client: KycClient): {
528
+ startSession: (request: CreateEventBasedFaceVerificationSessionRequest) => Promise<CreateEventBasedFaceVerificationSessionResponse>;
529
+ verifyFace: (session: CreateEventBasedFaceVerificationSessionResponse, opts?: {
530
530
  defaultDevice?: "ask" | "this" | "mobile";
531
531
  renderQR?: (payload: string) => React.ReactNode;
532
532
  onCancel?: () => void;
533
- }) => Promise<ReuseKycCallback | null>;
534
- runFlow: (request: CreateReuseKycSessionRequest, opts?: {
533
+ mediapipeAssetBasePath?: string;
534
+ }) => Promise<EventBasedFaceVerificationCallback | null>;
535
+ runFlow: (request: CreateEventBasedFaceVerificationSessionRequest, opts?: {
535
536
  defaultDevice?: "ask" | "this" | "mobile";
536
537
  renderQR?: (payload: string) => React.ReactNode;
537
538
  onCancel?: () => void;
539
+ mediapipeAssetBasePath?: string;
538
540
  }) => Promise<{
539
541
  kind: "not_required";
540
- session: CreateReuseKycSessionResponse;
542
+ session: CreateEventBasedFaceVerificationSessionResponse;
541
543
  } | {
542
544
  kind: "cancelled";
543
- session: CreateReuseKycSessionResponse;
545
+ session: CreateEventBasedFaceVerificationSessionResponse;
544
546
  } | {
545
547
  kind: "completed";
546
- session: CreateReuseKycSessionResponse;
547
- result: ReuseKycCallback;
548
+ session: CreateEventBasedFaceVerificationSessionResponse;
549
+ result: EventBasedFaceVerificationCallback;
548
550
  }>;
549
551
  };
550
552
 
551
- export { type UseCustomerProfileOptions, type UseCustomerProfileResult, UseGeolocationOptions, UseGeolocationResult, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult, UseLocationCaptureOptions, UseLocationCaptureResult, UseLocationRequestsOptions, UseLocationRequestsResult, type UseLoginVerificationOptions, type UseLoginVerificationResult, type UseRegistrationOptions, type UseRegistrationResult, type UseTransactionVerificationOptions, type UseTransactionVerificationResult, createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useReuseKYCSubmission, useTransactionVerification };
553
+ export { type UseCustomerProfileOptions, type UseCustomerProfileResult, UseGeolocationOptions, UseGeolocationResult, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult, UseLocationCaptureOptions, UseLocationCaptureResult, UseLocationRequestsOptions, UseLocationRequestsResult, type UseLoginVerificationOptions, type UseLoginVerificationResult, type UseRegistrationOptions, type UseRegistrationResult, type UseTransactionVerificationOptions, type UseTransactionVerificationResult, createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useEventBasedFaceVerificationSubmission, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useTransactionVerification };
package/dist/react.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, W as UseGeolocationOptions, X as UseGeolocationResult, Y as UseLocationCaptureOptions, Z as UseLocationCaptureResult, _ as UseLocationRequestsOptions, $ as UseLocationRequestsResult } from './client-DoMSYMMR.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-C3DCmGe9.js';
1
+ import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, W as UseGeolocationOptions, X as UseGeolocationResult, Y as UseLocationCaptureOptions, Z as UseLocationCaptureResult, _ as UseLocationRequestsOptions, $ as UseLocationRequestsResult } from './client-DF7hlMEz.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-DtH2RLuy.js';
3
3
  import { RiskProfileClient } from './risk-profile/index.js';
4
4
  import { C as CustomerProfile } from './types-UGyDl1fd.js';
5
- import { KycClient, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult, CreateReuseKycSessionRequest, CreateReuseKycSessionResponse, ReuseKycCallback } from './kyc/index.js';
5
+ import { KycClient, CreateEventBasedFaceVerificationSessionRequest, CreateEventBasedFaceVerificationSessionResponse, EventBasedFaceVerificationCallback, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult } from './kyc/index.js';
6
6
  import './client-BolQlL5e.js';
7
7
  import './types-CBQRNL-l.js';
8
8
 
@@ -508,7 +508,7 @@ declare function getStatusColor(status: string): string;
508
508
  */
509
509
  declare function getRiskColor(risk: string): string;
510
510
  /**
511
- * Hook for the Re-Use KYC face-capture flow.
511
+ * Hook for the Event-Based Face Verification face-capture flow.
512
512
  *
513
513
  * Returns helpers that cover both mobile (direct capture) and desktop
514
514
  * (QR + polling) modes. Device detection happens here on the client —
@@ -524,28 +524,30 @@ declare function getRiskColor(risk: string): string;
524
524
  * - `runFlow(req, opts?)` — convenience wrapper: `startSession` then
525
525
  * `verifyFace`, with a short-circuit when `is_required` is false.
526
526
  */
527
- declare function useReuseKYCSubmission(client: KycClient): {
528
- startSession: (request: CreateReuseKycSessionRequest) => Promise<CreateReuseKycSessionResponse>;
529
- verifyFace: (session: CreateReuseKycSessionResponse, opts?: {
527
+ declare function useEventBasedFaceVerificationSubmission(client: KycClient): {
528
+ startSession: (request: CreateEventBasedFaceVerificationSessionRequest) => Promise<CreateEventBasedFaceVerificationSessionResponse>;
529
+ verifyFace: (session: CreateEventBasedFaceVerificationSessionResponse, opts?: {
530
530
  defaultDevice?: "ask" | "this" | "mobile";
531
531
  renderQR?: (payload: string) => React.ReactNode;
532
532
  onCancel?: () => void;
533
- }) => Promise<ReuseKycCallback | null>;
534
- runFlow: (request: CreateReuseKycSessionRequest, opts?: {
533
+ mediapipeAssetBasePath?: string;
534
+ }) => Promise<EventBasedFaceVerificationCallback | null>;
535
+ runFlow: (request: CreateEventBasedFaceVerificationSessionRequest, opts?: {
535
536
  defaultDevice?: "ask" | "this" | "mobile";
536
537
  renderQR?: (payload: string) => React.ReactNode;
537
538
  onCancel?: () => void;
539
+ mediapipeAssetBasePath?: string;
538
540
  }) => Promise<{
539
541
  kind: "not_required";
540
- session: CreateReuseKycSessionResponse;
542
+ session: CreateEventBasedFaceVerificationSessionResponse;
541
543
  } | {
542
544
  kind: "cancelled";
543
- session: CreateReuseKycSessionResponse;
545
+ session: CreateEventBasedFaceVerificationSessionResponse;
544
546
  } | {
545
547
  kind: "completed";
546
- session: CreateReuseKycSessionResponse;
547
- result: ReuseKycCallback;
548
+ session: CreateEventBasedFaceVerificationSessionResponse;
549
+ result: EventBasedFaceVerificationCallback;
548
550
  }>;
549
551
  };
550
552
 
551
- export { type UseCustomerProfileOptions, type UseCustomerProfileResult, UseGeolocationOptions, UseGeolocationResult, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult, UseLocationCaptureOptions, UseLocationCaptureResult, UseLocationRequestsOptions, UseLocationRequestsResult, type UseLoginVerificationOptions, type UseLoginVerificationResult, type UseRegistrationOptions, type UseRegistrationResult, type UseTransactionVerificationOptions, type UseTransactionVerificationResult, createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useReuseKYCSubmission, useTransactionVerification };
553
+ export { type UseCustomerProfileOptions, type UseCustomerProfileResult, UseGeolocationOptions, UseGeolocationResult, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult, UseLocationCaptureOptions, UseLocationCaptureResult, UseLocationRequestsOptions, UseLocationRequestsResult, type UseLoginVerificationOptions, type UseLoginVerificationResult, type UseRegistrationOptions, type UseRegistrationResult, type UseTransactionVerificationOptions, type UseTransactionVerificationResult, createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useEventBasedFaceVerificationSubmission, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useTransactionVerification };
package/dist/react.js CHANGED
@@ -889,8 +889,90 @@ var Camera = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i
889
889
  var Done = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik04LjUgMTIuNUwxMC41IDE0LjVMMTUuNSA5LjUiIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPg0KPHBhdGggZD0iTTcgMy4zMzc4MkM4LjQ3MDg3IDIuNDg2OTcgMTAuMTc4NiAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJDMjIgMTcuNTIyOCAxNy41MjI4IDIyIDEyIDIyQzYuNDc3MTUgMjIgMiAxNy41MjI4IDIgMTJDMiAxMC4xNzg2IDIuNDg2OTcgOC40NzA4NyAzLjMzNzgyIDciIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPg0KPC9zdmc+";
890
890
  var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNC41IDkuNTAwMDJMOS41IDE0LjVNOS40OTk5OCA5LjVMMTQuNSAxNC41IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjxwYXRoIGQ9Ik03IDMuMzM3ODJDOC40NzA4NyAyLjQ4Njk3IDEwLjE3ODYgMiAxMiAyQzE3LjUyMjggMiAyMiA2LjQ3NzE1IDIyIDEyQzIyIDE3LjUyMjggMTcuNTIyOCAyMiAxMiAyMkM2LjQ3NzE1IDIyIDIgMTcuNTIyOCAyIDEyQzIgMTAuMTc4NiAyLjQ4Njk3IDguNDcwODcgMy4zMzc4MiA3IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjwvc3ZnPg==";
891
891
 
892
+ // src/kyc/faceDetection.ts
893
+ var FACE_MESSAGES = {
894
+ idle: "Initializing camera...",
895
+ loading: "Loading face detection...",
896
+ scanning: "Position your face in the circle",
897
+ too_far: "Face too far. Please move closer to the camera",
898
+ too_close: "Too close. Please move back a little",
899
+ off_center: "Center your face in the circle",
900
+ ok: "Looking great! Tap the button to capture"
901
+ };
902
+ var FACE_COLORS = {
903
+ idle: "#9ca3af",
904
+ loading: "#3b82f6",
905
+ scanning: "#3b82f6",
906
+ too_far: "#ef4444",
907
+ too_close: "#f59e0b",
908
+ off_center: "#f59e0b",
909
+ ok: "#10b981"
910
+ };
911
+ var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
912
+ function mapDetectionsToFaceStatus(detections) {
913
+ if (detections.length === 0) return "scanning";
914
+ const bb = detections[0].boundingBox;
915
+ if (bb.width < 0.18) return "too_far";
916
+ if (bb.width > 0.72) return "too_close";
917
+ if (bb.xCenter < 0.28 || bb.xCenter > 0.72 || bb.yCenter < 0.28 || bb.yCenter > 0.72) {
918
+ return "off_center";
919
+ }
920
+ return "ok";
921
+ }
922
+ function isCaptureDisabled(ready, detectionActive, faceStatus) {
923
+ return !ready || detectionActive && faceStatus !== "ok";
924
+ }
925
+ function resolveMediaPipeBase(base) {
926
+ if (!base) return MEDIAPIPE_BASE;
927
+ return base.endsWith("/") ? base.slice(0, -1) : base;
928
+ }
929
+ var LIVENESS_SAMPLE_LIMIT = 20;
930
+ function buildLivenessArtifact(samples) {
931
+ if (samples.length === 0) return void 0;
932
+ return JSON.stringify({
933
+ version: 1,
934
+ source: "mediapipe-face-detection",
935
+ samples: samples.slice(-LIVENESS_SAMPLE_LIMIT)
936
+ });
937
+ }
938
+ var mediapipeLoaderPromises = /* @__PURE__ */ new Map();
939
+ function loadMediaPipeFaceDetection(assetBasePath) {
940
+ if (typeof window === "undefined" || typeof document === "undefined") {
941
+ return Promise.reject(new Error("MediaPipe requires a browser environment"));
942
+ }
943
+ const w = window;
944
+ if (w.FaceDetection) {
945
+ return Promise.resolve(w.FaceDetection);
946
+ }
947
+ const src = `${resolveMediaPipeBase(assetBasePath)}/face_detection.js`;
948
+ const existing = mediapipeLoaderPromises.get(src);
949
+ if (existing) return existing;
950
+ const loader = new Promise((resolve, reject) => {
951
+ const script = document.createElement("script");
952
+ script.src = src;
953
+ script.async = true;
954
+ script.crossOrigin = "anonymous";
955
+ script.onload = () => {
956
+ if (w.FaceDetection) {
957
+ resolve(w.FaceDetection);
958
+ } else {
959
+ mediapipeLoaderPromises.delete(src);
960
+ reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
961
+ }
962
+ };
963
+ script.onerror = () => {
964
+ mediapipeLoaderPromises.delete(src);
965
+ reject(new Error("Failed to load MediaPipe face_detection script"));
966
+ };
967
+ document.head.appendChild(script);
968
+ });
969
+ mediapipeLoaderPromises.set(src, loader);
970
+ return loader;
971
+ }
972
+
892
973
  // src/kyc/FaceCaptureModal.tsx
893
974
  var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
975
+ var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
894
976
  var LIVENESS_MESSAGES = [
895
977
  "Position your face inside the circle",
896
978
  "Make sure your face is well lit",
@@ -921,7 +1003,8 @@ function FaceCaptureModal({
921
1003
  onComplete,
922
1004
  onCancel,
923
1005
  defaultDevice,
924
- renderQR
1006
+ renderQR,
1007
+ mediapipeAssetBasePath
925
1008
  }) {
926
1009
  const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
927
1010
  const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
@@ -938,21 +1021,30 @@ function FaceCaptureModal({
938
1021
  const [captureMode, setCaptureMode] = React.useState("idle");
939
1022
  const [capturedPreview, setCapturedPreview] = React.useState(null);
940
1023
  const [capturedBase64, setCapturedBase64] = React.useState(null);
1024
+ const [capturedArtifact, setCapturedArtifact] = React.useState(null);
941
1025
  const [livenessMessage, setLivenessMessage] = React.useState(LIVENESS_MESSAGES[0]);
942
1026
  const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
943
1027
  const cancelledRef = React.useRef(false);
944
1028
  const submittingRef = React.useRef(false);
1029
+ React.useEffect(() => {
1030
+ const timer = setTimeout(() => {
1031
+ setStage(
1032
+ (prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
1033
+ );
1034
+ }, SESSION_EXPIRY_MS);
1035
+ return () => clearTimeout(timer);
1036
+ }, [session.token]);
945
1037
  React.useEffect(() => {
946
1038
  if (stage.kind !== "qr") return;
947
1039
  let stopped = false;
948
1040
  const intervalMs = 2e3;
949
- const deadline = Date.now() + 15 * 60 * 1e3;
1041
+ const deadline = Date.now() + SESSION_EXPIRY_MS;
950
1042
  let mobileSeen = stage.mobileConnected;
951
1043
  const tick = async () => {
952
1044
  if (stopped || cancelledRef.current) return;
953
1045
  try {
954
1046
  if (mobileSeen) {
955
- const result = await client.getReuseKycSessionStatus(session.reference);
1047
+ const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
956
1048
  if (result.status === "accepted") {
957
1049
  setStage({ kind: "accepted", result });
958
1050
  stopped = true;
@@ -1024,10 +1116,13 @@ function FaceCaptureModal({
1024
1116
  try {
1025
1117
  const postPromise = (async () => {
1026
1118
  try {
1027
- const data = await client.submitReuseKycSession({
1119
+ const data = await client.submitEventBasedFaceVerificationSession({
1028
1120
  token: session.token,
1029
1121
  reference: ref,
1030
- proof
1122
+ proof,
1123
+ // undefined when detection wasn't running for this capture —
1124
+ // JSON.stringify drops it, so the field is absent on the wire.
1125
+ liveness_artifact: capturedArtifact ?? void 0
1031
1126
  });
1032
1127
  return { ok: true, data };
1033
1128
  } catch (err2) {
@@ -1040,7 +1135,7 @@ function FaceCaptureModal({
1040
1135
  while (Date.now() < deadline) {
1041
1136
  if (settled || cancelledRef.current) return null;
1042
1137
  try {
1043
- const r = await client.getReuseKycSessionStatus(ref);
1138
+ const r = await client.getEventBasedFaceVerificationSessionStatus(session.token);
1044
1139
  if (isFreshPollResult(r)) return r;
1045
1140
  } catch {
1046
1141
  }
@@ -1073,16 +1168,18 @@ function FaceCaptureModal({
1073
1168
  submittingRef.current = false;
1074
1169
  }
1075
1170
  };
1076
- const handleCameraCapture = (dataUrl) => {
1171
+ const handleCameraCapture = (dataUrl, livenessArtifact) => {
1077
1172
  const raw = dataUrl.split(",")[1] ?? "";
1078
1173
  setCapturedPreview(dataUrl);
1079
1174
  setCapturedBase64(raw);
1175
+ setCapturedArtifact(livenessArtifact ?? null);
1080
1176
  setCaptureMode("preview");
1081
1177
  };
1082
1178
  const handleCameraCancel = () => setCaptureMode("idle");
1083
1179
  const handleRetake = () => {
1084
1180
  setCapturedPreview(null);
1085
1181
  setCapturedBase64(null);
1182
+ setCapturedArtifact(null);
1086
1183
  setCaptureMode("live");
1087
1184
  };
1088
1185
  const handleConfirmSubmit = () => {
@@ -1154,14 +1251,15 @@ function FaceCaptureModal({
1154
1251
  {
1155
1252
  onCapture: handleCameraCapture,
1156
1253
  onCancel: handleCameraCancel,
1157
- message: livenessMessage
1254
+ message: livenessMessage,
1255
+ assetBasePath: mediapipeAssetBasePath
1158
1256
  }
1159
1257
  ), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
1160
1258
  }
1161
1259
  if (captureMode === "preview" && capturedPreview) {
1162
1260
  return /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Looks good? Submit this selfie or retake it."), /* @__PURE__ */ React__default.default.createElement("div", { style: previewBoxStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: capturedPreview, alt: "Captured selfie preview", style: previewImgStyle })), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleConfirmSubmit }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Submit")), /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: handleRetake }, "Retake"), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
1163
1261
  }
1164
- return /* @__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("div", { style: badgeStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React__default.default.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React__default.default.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, declinedReason ? "Try again" : "Open Camera")), hasMethodChoice && /* @__PURE__ */ React__default.default.createElement(
1262
+ return /* @__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("div", { style: badgeStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React__default.default.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React__default.default.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, declinedReason ? "Try Again" : "Open Camera")), hasMethodChoice && /* @__PURE__ */ React__default.default.createElement(
1165
1263
  "button",
1166
1264
  {
1167
1265
  style: secondaryButtonStyle,
@@ -1173,10 +1271,10 @@ function FaceCaptureModal({
1173
1271
  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."));
1174
1272
  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")));
1175
1273
  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));
1176
- 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."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
1274
+ 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")));
1177
1275
  const renderError = (message) => {
1178
1276
  const sessionExpired = /session\s+expired/i.test(message);
1179
- 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")));
1277
+ 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")));
1180
1278
  };
1181
1279
  const body = (() => {
1182
1280
  switch (stage.kind) {
@@ -1385,59 +1483,10 @@ var attemptsTextStyle = {
1385
1483
  };
1386
1484
  var footerStyle = { padding: "12px 24px 16px", borderTop: "1px solid #f3f4f6" };
1387
1485
  var footerTextStyle = { margin: 0, fontSize: 12, color: "#9ca3af", textAlign: "center" };
1388
- var FACE_MESSAGES = {
1389
- idle: "Initializing camera...",
1390
- loading: "Loading face detection...",
1391
- scanning: "Position your face in the circle",
1392
- too_far: "Face too far. Please move closer to the camera",
1393
- too_close: "Too close. Please move back a little",
1394
- off_center: "Center your face in the circle",
1395
- ok: "Looking great! Tap the button to capture"
1396
- };
1397
- var FACE_COLORS = {
1398
- idle: "#9ca3af",
1399
- loading: "#3b82f6",
1400
- scanning: "#3b82f6",
1401
- too_far: "#ef4444",
1402
- too_close: "#f59e0b",
1403
- off_center: "#f59e0b",
1404
- ok: "#10b981"
1405
- };
1406
- var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
1407
- var mediapipeLoaderPromise = null;
1408
- function loadMediaPipeFaceDetection() {
1409
- if (typeof window === "undefined" || typeof document === "undefined") {
1410
- return Promise.reject(new Error("MediaPipe requires a browser environment"));
1411
- }
1412
- const w = window;
1413
- if (w.FaceDetection) {
1414
- return Promise.resolve(w.FaceDetection);
1415
- }
1416
- if (mediapipeLoaderPromise) return mediapipeLoaderPromise;
1417
- mediapipeLoaderPromise = new Promise((resolve, reject) => {
1418
- const script = document.createElement("script");
1419
- script.src = `${MEDIAPIPE_BASE}/face_detection.js`;
1420
- script.async = true;
1421
- script.crossOrigin = "anonymous";
1422
- script.onload = () => {
1423
- if (w.FaceDetection) {
1424
- resolve(w.FaceDetection);
1425
- } else {
1426
- mediapipeLoaderPromise = null;
1427
- reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
1428
- }
1429
- };
1430
- script.onerror = () => {
1431
- mediapipeLoaderPromise = null;
1432
- reject(new Error("Failed to load MediaPipe face_detection script"));
1433
- };
1434
- document.head.appendChild(script);
1435
- });
1436
- return mediapipeLoaderPromise;
1437
- }
1438
- function LiveCamera({ onCapture, onCancel, message }) {
1486
+ function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
1439
1487
  const videoRef = React.useRef(null);
1440
1488
  const streamRef = React.useRef(null);
1489
+ const samplesRef = React.useRef([]);
1441
1490
  const [ready, setReady] = React.useState(false);
1442
1491
  const [error, setError] = React.useState(null);
1443
1492
  const [faceStatus, setFaceStatus] = React.useState("idle");
@@ -1492,31 +1541,29 @@ function LiveCamera({ onCapture, onCancel, message }) {
1492
1541
  let detector = null;
1493
1542
  setFaceStatus("loading");
1494
1543
  setFaceDetectionFailed(false);
1544
+ samplesRef.current = [];
1495
1545
  (async () => {
1496
1546
  try {
1497
- const FaceDetection = await loadMediaPipeFaceDetection();
1547
+ const FaceDetection = await loadMediaPipeFaceDetection(assetBasePath);
1498
1548
  if (cancelled) return;
1549
+ const assetBase = resolveMediaPipeBase(assetBasePath);
1499
1550
  const d = new FaceDetection({
1500
- locateFile: (f) => `${MEDIAPIPE_BASE}/${f}`
1551
+ locateFile: (f) => `${assetBase}/${f}`
1501
1552
  });
1502
1553
  d.setOptions({ model: "short", minDetectionConfidence: 0.5 });
1503
1554
  d.onResults((results) => {
1504
1555
  if (cancelled) return;
1505
1556
  const detections = results.detections ?? [];
1506
- let next;
1507
- if (detections.length === 0) {
1508
- next = "scanning";
1509
- } else {
1510
- const bb = detections[0].boundingBox;
1511
- if (bb.width < 0.18) next = "too_far";
1512
- else if (bb.width > 0.72) next = "too_close";
1513
- else if (bb.xCenter < 0.28 || bb.xCenter > 0.72 || bb.yCenter < 0.28 || bb.yCenter > 0.72) {
1514
- next = "off_center";
1515
- } else {
1516
- next = "ok";
1517
- }
1557
+ const status = mapDetectionsToFaceStatus(detections);
1558
+ samplesRef.current.push({
1559
+ t: Date.now(),
1560
+ status,
1561
+ ...detections.length > 0 ? { boundingBox: detections[0].boundingBox } : {}
1562
+ });
1563
+ if (samplesRef.current.length > LIVENESS_SAMPLE_LIMIT) {
1564
+ samplesRef.current.shift();
1518
1565
  }
1519
- setFaceStatus(next);
1566
+ setFaceStatus(status);
1520
1567
  });
1521
1568
  await d.initialize();
1522
1569
  if (cancelled) return;
@@ -1552,7 +1599,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
1552
1599
  } catch {
1553
1600
  }
1554
1601
  };
1555
- }, [ready]);
1602
+ }, [ready, assetBasePath]);
1556
1603
  const capture = () => {
1557
1604
  const video = videoRef.current;
1558
1605
  if (!video || !video.videoWidth) return;
@@ -1569,7 +1616,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
1569
1616
  ctx.drawImage(video, sx, sy, size, size, 0, 0, size, size);
1570
1617
  streamRef.current?.getTracks().forEach((t) => t.stop());
1571
1618
  streamRef.current = null;
1572
- onCapture(canvas.toDataURL("image/jpeg", 0.9));
1619
+ onCapture(canvas.toDataURL("image/jpeg", 0.9), buildLivenessArtifact(samplesRef.current));
1573
1620
  };
1574
1621
  if (error) {
1575
1622
  return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center", padding: 0 } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Camera unavailable"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, error), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Back")));
@@ -1577,7 +1624,7 @@ function LiveCamera({ onCapture, onCancel, message }) {
1577
1624
  const detectionActive = ready && !faceDetectionFailed;
1578
1625
  const liveMessage = detectionActive ? FACE_MESSAGES[faceStatus] : message;
1579
1626
  const liveFaceColor = detectionActive ? FACE_COLORS[faceStatus] : void 0;
1580
- const captureDisabled = !ready || detectionActive && faceStatus !== "ok";
1627
+ const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
1581
1628
  const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
1582
1629
  const dynamicCircleStyle = {
1583
1630
  ...faceCircleStyle,
@@ -2084,9 +2131,9 @@ function getRiskColor(risk) {
2084
2131
  };
2085
2132
  return colorMap[risk] || "#6b7280";
2086
2133
  }
2087
- function useReuseKYCSubmission(client$1) {
2134
+ function useEventBasedFaceVerificationSubmission(client$1) {
2088
2135
  const startSession = React.useCallback(
2089
- (request) => client$1.createReuseKycSession(request),
2136
+ (request) => client$1.createEventBasedFaceVerificationSession(request),
2090
2137
  [client$1]
2091
2138
  );
2092
2139
  const verifyFace = React.useCallback(
@@ -2104,6 +2151,7 @@ function useReuseKYCSubmission(client$1) {
2104
2151
  defaultDevice: opts.defaultDevice,
2105
2152
  renderQR: opts.renderQR,
2106
2153
  onCancel: opts.onCancel,
2154
+ mediapipeAssetBasePath: opts.mediapipeAssetBasePath,
2107
2155
  onComplete: (result) => {
2108
2156
  cleanup();
2109
2157
  resolve(result);
@@ -2140,6 +2188,7 @@ exports.isValidFileSize = isValidFileSize;
2140
2188
  exports.isValidFileType = isValidFileType;
2141
2189
  exports.useCipherText = useCipherText;
2142
2190
  exports.useCustomerProfile = useCustomerProfile;
2191
+ exports.useEventBasedFaceVerificationSubmission = useEventBasedFaceVerificationSubmission;
2143
2192
  exports.useGeolocation = useGeolocation;
2144
2193
  exports.useKycAlerts = useKycAlerts;
2145
2194
  exports.useKycOverview = useKycOverview;
@@ -2150,7 +2199,6 @@ exports.useLocationCapture = useLocationCapture;
2150
2199
  exports.useLocationRequests = useLocationRequests;
2151
2200
  exports.useLoginVerification = useLoginVerification;
2152
2201
  exports.useRegistration = useRegistration;
2153
- exports.useReuseKYCSubmission = useReuseKYCSubmission;
2154
2202
  exports.useTransactionVerification = useTransactionVerification;
2155
2203
  //# sourceMappingURL=react.js.map
2156
2204
  //# sourceMappingURL=react.js.map