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.
- package/dist/{client-ZNdnpWe7.d.mts → client-B0qhE2kr.d.mts} +1 -1
- package/dist/{client-DoMSYMMR.d.ts → client-DF7hlMEz.d.ts} +13 -0
- package/dist/{client-DMIRx7Tu.d.mts → client-DrjgZoH_.d.mts} +13 -0
- package/dist/{client-C3DCmGe9.d.ts → client-DtH2RLuy.d.ts} +1 -1
- package/dist/compliance/index.d.mts +2 -2
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +0 -3
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +0 -3
- package/dist/compliance/index.mjs.map +1 -1
- package/dist/geolocation/index.d.mts +2 -2
- package/dist/geolocation/index.d.ts +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +24 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -20
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +1 -1
- package/dist/kyc/core.d.ts +1 -1
- package/dist/kyc/core.js +20 -16
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +20 -16
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +110 -55
- package/dist/kyc/index.d.ts +110 -55
- package/dist/kyc/index.js +20 -16
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +20 -16
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +16 -14
- package/dist/react.d.ts +16 -14
- package/dist/react.js +131 -83
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +131 -83
- package/dist/react.mjs.map +1 -1
- package/dist/tax/index.d.mts +18 -1
- package/dist/tax/index.d.ts +18 -1
- package/dist/tax/index.js +4 -1
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +4 -1
- package/dist/tax/index.mjs.map +1 -1
- 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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
528
|
-
startSession: (request:
|
|
529
|
-
verifyFace: (session:
|
|
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
|
-
|
|
534
|
-
|
|
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:
|
|
542
|
+
session: CreateEventBasedFaceVerificationSessionResponse;
|
|
541
543
|
} | {
|
|
542
544
|
kind: "cancelled";
|
|
543
|
-
session:
|
|
545
|
+
session: CreateEventBasedFaceVerificationSessionResponse;
|
|
544
546
|
} | {
|
|
545
547
|
kind: "completed";
|
|
546
|
-
session:
|
|
547
|
-
result:
|
|
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,
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
528
|
-
startSession: (request:
|
|
529
|
-
verifyFace: (session:
|
|
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
|
-
|
|
534
|
-
|
|
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:
|
|
542
|
+
session: CreateEventBasedFaceVerificationSessionResponse;
|
|
541
543
|
} | {
|
|
542
544
|
kind: "cancelled";
|
|
543
|
-
session:
|
|
545
|
+
session: CreateEventBasedFaceVerificationSessionResponse;
|
|
544
546
|
} | {
|
|
545
547
|
kind: "completed";
|
|
546
|
-
session:
|
|
547
|
-
result:
|
|
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,
|
|
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() +
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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) => `${
|
|
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
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
2134
|
+
function useEventBasedFaceVerificationSubmission(client$1) {
|
|
2088
2135
|
const startSession = React.useCallback(
|
|
2089
|
-
(request) => client$1.
|
|
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
|