vesant-sdk 1.7.1-dev.301c6df → 1.7.1-dev.328baf7
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-D23KhWeh.d.mts → client-5q4whhGu.d.mts} +1 -1
- package/dist/{client-fy3trF2K.d.ts → client-BaUekQT8.d.ts} +32 -10
- package/dist/{client-B3DCAHlg.d.ts → client-DUHC-68_.d.ts} +1 -1
- package/dist/{client-BwXMQA2a.d.mts → client-XDEhh9VN.d.mts} +32 -10
- package/dist/compliance/index.d.mts +2 -2
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +39 -1
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +39 -1
- 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/geolocation/index.js +39 -1
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +39 -1
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +66 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +66 -9
- 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 +27 -7
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +27 -8
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +150 -26
- package/dist/kyc/index.d.ts +150 -26
- package/dist/kyc/index.js +27 -7
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +27 -8
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +9 -2
- package/dist/react.d.ts +9 -2
- package/dist/react.js +166 -35
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +166 -35
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, Y as UseGeolocationOptions, Z as UseGeolocationResult, _ as UseLocationCaptureOptions, $ as UseLocationCaptureResult, a0 as UseLocationRequestsOptions, a1 as UseLocationRequestsResult } from './client-
|
|
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, Y as UseGeolocationOptions, Z as UseGeolocationResult, _ as UseLocationCaptureOptions, $ as UseLocationCaptureResult, a0 as UseLocationRequestsOptions, a1 as UseLocationRequestsResult } from './client-XDEhh9VN.mjs';
|
|
2
|
+
import { e as LoginVerificationResponse, L as LoginVerificationRequest, f as RegistrationVerificationResponse, R as RegistrationVerificationRequest, h as TransactionVerificationResponse, g as TransactionVerificationRequest, C as ComplianceClient } from './client-5q4whhGu.mjs';
|
|
3
3
|
import { RiskProfileClient } from './risk-profile/index.mjs';
|
|
4
4
|
import { C as CustomerProfile } from './types-BOFaMQxI.mjs';
|
|
5
5
|
import { KycClient, CreateEventBasedFaceVerificationSessionRequest, CreateEventBasedFaceVerificationSessionResponse, EventBasedFaceVerificationCallback, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult } from './kyc/index.mjs';
|
|
@@ -63,6 +63,12 @@ declare function createDeviceFingerprint(): DeviceFingerprintRequest;
|
|
|
63
63
|
/**
|
|
64
64
|
* React hook for generating cipherText
|
|
65
65
|
*
|
|
66
|
+
* The tenant's GPS requirements are read from the client before each generation.
|
|
67
|
+
* The client reuses a fetched copy for a short period, so this does not add a
|
|
68
|
+
* request per event, and a requirement changed in the compliance console takes
|
|
69
|
+
* effect without reloading the page. Call `refreshConfig` to apply a change
|
|
70
|
+
* immediately.
|
|
71
|
+
*
|
|
66
72
|
* @example
|
|
67
73
|
* ```tsx
|
|
68
74
|
* function LoginForm() {
|
|
@@ -79,6 +85,7 @@ declare function createDeviceFingerprint(): DeviceFingerprintRequest;
|
|
|
79
85
|
*/
|
|
80
86
|
declare function useCipherText(client?: GeolocationClient): {
|
|
81
87
|
generate: (options: CipherTextOptions) => Promise<CipherTextResult | null>;
|
|
88
|
+
refreshConfig: () => Promise<void>;
|
|
82
89
|
configLoading: boolean;
|
|
83
90
|
configError: Error | null;
|
|
84
91
|
};
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DeviceFingerprintRequest, G as GeolocationClient, i as CipherTextOptions, l as CipherTextResult, Y as UseGeolocationOptions, Z as UseGeolocationResult, _ as UseLocationCaptureOptions, $ as UseLocationCaptureResult, a0 as UseLocationRequestsOptions, a1 as UseLocationRequestsResult } from './client-
|
|
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, Y as UseGeolocationOptions, Z as UseGeolocationResult, _ as UseLocationCaptureOptions, $ as UseLocationCaptureResult, a0 as UseLocationRequestsOptions, a1 as UseLocationRequestsResult } from './client-BaUekQT8.js';
|
|
2
|
+
import { e as LoginVerificationResponse, L as LoginVerificationRequest, f as RegistrationVerificationResponse, R as RegistrationVerificationRequest, h as TransactionVerificationResponse, g as TransactionVerificationRequest, C as ComplianceClient } from './client-DUHC-68_.js';
|
|
3
3
|
import { RiskProfileClient } from './risk-profile/index.js';
|
|
4
4
|
import { C as CustomerProfile } from './types-UGyDl1fd.js';
|
|
5
5
|
import { KycClient, CreateEventBasedFaceVerificationSessionRequest, CreateEventBasedFaceVerificationSessionResponse, EventBasedFaceVerificationCallback, UseKycAlertsOptions, UseKycAlertsResult, UseKycOverviewOptions, UseKycOverviewResult, UseKycPreferencesResult, UseKycRequestsOptions, UseKycRequestsResult, UseKycSubmissionOptions, UseKycSubmissionResult } from './kyc/index.js';
|
|
@@ -63,6 +63,12 @@ declare function createDeviceFingerprint(): DeviceFingerprintRequest;
|
|
|
63
63
|
/**
|
|
64
64
|
* React hook for generating cipherText
|
|
65
65
|
*
|
|
66
|
+
* The tenant's GPS requirements are read from the client before each generation.
|
|
67
|
+
* The client reuses a fetched copy for a short period, so this does not add a
|
|
68
|
+
* request per event, and a requirement changed in the compliance console takes
|
|
69
|
+
* effect without reloading the page. Call `refreshConfig` to apply a change
|
|
70
|
+
* immediately.
|
|
71
|
+
*
|
|
66
72
|
* @example
|
|
67
73
|
* ```tsx
|
|
68
74
|
* function LoginForm() {
|
|
@@ -79,6 +85,7 @@ declare function createDeviceFingerprint(): DeviceFingerprintRequest;
|
|
|
79
85
|
*/
|
|
80
86
|
declare function useCipherText(client?: GeolocationClient): {
|
|
81
87
|
generate: (options: CipherTextOptions) => Promise<CipherTextResult | null>;
|
|
88
|
+
refreshConfig: () => Promise<void>;
|
|
82
89
|
configLoading: boolean;
|
|
83
90
|
configError: Error | null;
|
|
84
91
|
};
|
package/dist/react.js
CHANGED
|
@@ -429,14 +429,11 @@ function useCipherText(client) {
|
|
|
429
429
|
const [configLoading, setConfigLoading] = React.useState(!!client);
|
|
430
430
|
const [configError, setConfigError] = React.useState(null);
|
|
431
431
|
const configRef = React.useRef(void 0);
|
|
432
|
-
const configPromiseRef = React.useRef(null);
|
|
433
432
|
React.useEffect(() => {
|
|
434
433
|
if (!client) return;
|
|
435
434
|
let cancelled = false;
|
|
436
435
|
setConfigLoading(true);
|
|
437
|
-
|
|
438
|
-
configPromiseRef.current = promise;
|
|
439
|
-
promise.then((cfg) => {
|
|
436
|
+
client.getGPSConfig().then((cfg) => {
|
|
440
437
|
if (!cancelled) {
|
|
441
438
|
configRef.current = cfg;
|
|
442
439
|
setConfigLoading(false);
|
|
@@ -451,13 +448,21 @@ function useCipherText(client) {
|
|
|
451
448
|
cancelled = true;
|
|
452
449
|
};
|
|
453
450
|
}, [client]);
|
|
451
|
+
const refreshConfig = React.useCallback(async () => {
|
|
452
|
+
if (!client) return;
|
|
453
|
+
try {
|
|
454
|
+
configRef.current = await client.refreshGPSConfig();
|
|
455
|
+
setConfigError(null);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
setConfigError(error instanceof Error ? error : new Error("Failed to refresh GPS config"));
|
|
458
|
+
}
|
|
459
|
+
}, [client]);
|
|
454
460
|
const generate = React.useCallback(
|
|
455
461
|
async (options) => {
|
|
456
462
|
try {
|
|
457
|
-
if (
|
|
463
|
+
if (client) {
|
|
458
464
|
try {
|
|
459
|
-
|
|
460
|
-
configRef.current = cfg;
|
|
465
|
+
configRef.current = await client.getGPSConfig();
|
|
461
466
|
} catch {
|
|
462
467
|
}
|
|
463
468
|
}
|
|
@@ -467,9 +472,9 @@ function useCipherText(client) {
|
|
|
467
472
|
return null;
|
|
468
473
|
}
|
|
469
474
|
},
|
|
470
|
-
[]
|
|
475
|
+
[client]
|
|
471
476
|
);
|
|
472
|
-
return { generate, configLoading, configError };
|
|
477
|
+
return { generate, refreshConfig, configLoading, configError };
|
|
473
478
|
}
|
|
474
479
|
function useLocationRequests(client, options = {}) {
|
|
475
480
|
const [requests, setRequests] = React.useState([]);
|
|
@@ -930,19 +935,19 @@ var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id
|
|
|
930
935
|
var FACE_MESSAGES = {
|
|
931
936
|
idle: "Initializing camera...",
|
|
932
937
|
loading: "Loading face detection...",
|
|
933
|
-
scanning: "Position your face in the
|
|
938
|
+
scanning: "Position your face in the oval",
|
|
934
939
|
too_far: "Face too far. Please move closer to the camera",
|
|
935
940
|
too_close: "Too close. Please move back a little",
|
|
936
|
-
off_center: "Center your face in the
|
|
941
|
+
off_center: "Center your face in the oval",
|
|
937
942
|
ok: "Looking great! Tap the button to capture"
|
|
938
943
|
};
|
|
939
944
|
var FACE_COLORS = {
|
|
940
945
|
idle: "#9ca3af",
|
|
941
|
-
loading: "#
|
|
942
|
-
scanning: "#
|
|
946
|
+
loading: "#ef4444",
|
|
947
|
+
scanning: "#ef4444",
|
|
943
948
|
too_far: "#ef4444",
|
|
944
|
-
too_close: "#
|
|
945
|
-
off_center: "#
|
|
949
|
+
too_close: "#ef4444",
|
|
950
|
+
off_center: "#ef4444",
|
|
946
951
|
ok: "#10b981"
|
|
947
952
|
};
|
|
948
953
|
var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
|
|
@@ -1007,11 +1012,32 @@ function loadMediaPipeFaceDetection(assetBasePath) {
|
|
|
1007
1012
|
return loader;
|
|
1008
1013
|
}
|
|
1009
1014
|
|
|
1015
|
+
// src/kyc/hostedJourney.ts
|
|
1016
|
+
var REUSE_KYC_SESSION_EXPIRY_MS = 10 * 60 * 1e3;
|
|
1017
|
+
function tryStartOperation(guard) {
|
|
1018
|
+
if (guard.current) return false;
|
|
1019
|
+
guard.current = true;
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
function releaseOperation(guard) {
|
|
1023
|
+
guard.current = false;
|
|
1024
|
+
}
|
|
1025
|
+
function retryTarget(hasHostedJourney) {
|
|
1026
|
+
return hasHostedJourney ? "journey" : "capture";
|
|
1027
|
+
}
|
|
1028
|
+
function modalMaxWidth(hasHostedJourney) {
|
|
1029
|
+
return hasHostedJourney ? 680 : 448;
|
|
1030
|
+
}
|
|
1031
|
+
function resultForModalClose(stageKind, result) {
|
|
1032
|
+
const terminal = stageKind === "accepted" || stageKind === "declined" || stageKind === "max_attempts";
|
|
1033
|
+
return terminal && result !== void 0 ? result : null;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1010
1036
|
// src/kyc/FaceCaptureModal.tsx
|
|
1011
1037
|
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
1012
|
-
var
|
|
1038
|
+
var ACCEPTED_AUTO_CLOSE_MS = 1200;
|
|
1013
1039
|
var LIVENESS_MESSAGES = [
|
|
1014
|
-
"Position your face inside the
|
|
1040
|
+
"Position your face inside the oval",
|
|
1015
1041
|
"Make sure your face is well lit",
|
|
1016
1042
|
"Remove glasses or hats if possible",
|
|
1017
1043
|
"Look straight at the camera, then tap Capture"
|
|
@@ -1020,6 +1046,8 @@ function headerSubtitle(stageKind) {
|
|
|
1020
1046
|
switch (stageKind) {
|
|
1021
1047
|
case "qr":
|
|
1022
1048
|
return "Use your phone to continue";
|
|
1049
|
+
case "journey":
|
|
1050
|
+
return "Follow the steps to verify your identity";
|
|
1023
1051
|
case "accepted":
|
|
1024
1052
|
return "All set";
|
|
1025
1053
|
case "declined":
|
|
@@ -1045,7 +1073,9 @@ function FaceCaptureModal({
|
|
|
1045
1073
|
}) {
|
|
1046
1074
|
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
1047
1075
|
const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
1076
|
+
const journeyMode = Boolean(session.verification_url);
|
|
1048
1077
|
const initialStage = (() => {
|
|
1078
|
+
if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
|
|
1049
1079
|
if (initialChoice === "this") return { kind: "capture" };
|
|
1050
1080
|
if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
1051
1081
|
return { kind: "choose" };
|
|
@@ -1063,19 +1093,20 @@ function FaceCaptureModal({
|
|
|
1063
1093
|
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
1064
1094
|
const cancelledRef = React.useRef(false);
|
|
1065
1095
|
const submittingRef = React.useRef(false);
|
|
1096
|
+
const journeyRetryingRef = React.useRef(false);
|
|
1066
1097
|
React.useEffect(() => {
|
|
1067
1098
|
const timer = setTimeout(() => {
|
|
1068
1099
|
setStage(
|
|
1069
|
-
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
|
|
1100
|
+
(prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
|
|
1070
1101
|
);
|
|
1071
|
-
},
|
|
1102
|
+
}, REUSE_KYC_SESSION_EXPIRY_MS);
|
|
1072
1103
|
return () => clearTimeout(timer);
|
|
1073
1104
|
}, [session.token]);
|
|
1074
1105
|
React.useEffect(() => {
|
|
1075
1106
|
if (stage.kind !== "qr") return;
|
|
1076
1107
|
let stopped = false;
|
|
1077
1108
|
const intervalMs = 2e3;
|
|
1078
|
-
const deadline = Date.now() +
|
|
1109
|
+
const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
|
|
1079
1110
|
let mobileSeen = stage.mobileConnected;
|
|
1080
1111
|
const tick = async () => {
|
|
1081
1112
|
if (stopped || cancelledRef.current) return;
|
|
@@ -1116,6 +1147,64 @@ function FaceCaptureModal({
|
|
|
1116
1147
|
stopped = true;
|
|
1117
1148
|
};
|
|
1118
1149
|
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
1150
|
+
React.useEffect(() => {
|
|
1151
|
+
if (stage.kind !== "journey" || !stage.url) return;
|
|
1152
|
+
let stopped = false;
|
|
1153
|
+
const checkOnce = async () => {
|
|
1154
|
+
try {
|
|
1155
|
+
const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
|
|
1156
|
+
if (applyResult(result)) {
|
|
1157
|
+
stopped = true;
|
|
1158
|
+
return true;
|
|
1159
|
+
}
|
|
1160
|
+
} catch {
|
|
1161
|
+
}
|
|
1162
|
+
return false;
|
|
1163
|
+
};
|
|
1164
|
+
const tick = async () => {
|
|
1165
|
+
if (stopped || cancelledRef.current) return;
|
|
1166
|
+
await checkOnce();
|
|
1167
|
+
if (!stopped && !cancelledRef.current) {
|
|
1168
|
+
setTimeout(tick, 2500);
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
void tick();
|
|
1172
|
+
let frameOrigin = "";
|
|
1173
|
+
try {
|
|
1174
|
+
frameOrigin = new URL(stage.url).origin;
|
|
1175
|
+
} catch {
|
|
1176
|
+
frameOrigin = "";
|
|
1177
|
+
}
|
|
1178
|
+
const onMessage = (e) => {
|
|
1179
|
+
if (!frameOrigin || e.origin !== frameOrigin) return;
|
|
1180
|
+
const status = e.data?.verification_status;
|
|
1181
|
+
if (status) void checkOnce();
|
|
1182
|
+
};
|
|
1183
|
+
window.addEventListener("message", onMessage);
|
|
1184
|
+
return () => {
|
|
1185
|
+
stopped = true;
|
|
1186
|
+
window.removeEventListener("message", onMessage);
|
|
1187
|
+
};
|
|
1188
|
+
}, [stage.kind, stage.kind === "journey" ? stage.url : ""]);
|
|
1189
|
+
const handleJourneyRetry = async () => {
|
|
1190
|
+
if (!tryStartOperation(journeyRetryingRef)) return;
|
|
1191
|
+
setStage({ kind: "journey", url: "" });
|
|
1192
|
+
try {
|
|
1193
|
+
const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
|
|
1194
|
+
if (refreshed.verification_url) {
|
|
1195
|
+
setStage({ kind: "journey", url: refreshed.verification_url });
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
setStage({
|
|
1201
|
+
kind: "error",
|
|
1202
|
+
message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
|
|
1203
|
+
});
|
|
1204
|
+
} finally {
|
|
1205
|
+
releaseOperation(journeyRetryingRef);
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1119
1208
|
const applyResult = (result) => {
|
|
1120
1209
|
if (result.status === "accepted") {
|
|
1121
1210
|
setStage({ kind: "accepted", result });
|
|
@@ -1243,12 +1332,22 @@ function FaceCaptureModal({
|
|
|
1243
1332
|
}, 3e3);
|
|
1244
1333
|
return () => clearInterval(interval);
|
|
1245
1334
|
}, [captureMode]);
|
|
1246
|
-
const close = (result) => {
|
|
1335
|
+
const close = React.useCallback((result) => {
|
|
1247
1336
|
cancelledRef.current = true;
|
|
1248
1337
|
if (result === null && onCancel) {
|
|
1249
1338
|
onCancel();
|
|
1250
1339
|
}
|
|
1251
1340
|
onComplete(result);
|
|
1341
|
+
}, [onCancel, onComplete]);
|
|
1342
|
+
React.useEffect(() => {
|
|
1343
|
+
if (stage.kind !== "accepted") return;
|
|
1344
|
+
const result = stage.result;
|
|
1345
|
+
const timer = setTimeout(() => close(result), ACCEPTED_AUTO_CLOSE_MS);
|
|
1346
|
+
return () => clearTimeout(timer);
|
|
1347
|
+
}, [close, stage]);
|
|
1348
|
+
const handleHeaderClose = () => {
|
|
1349
|
+
const result = "result" in stage ? stage.result : void 0;
|
|
1350
|
+
close(resultForModalClose(stage.kind, result));
|
|
1252
1351
|
};
|
|
1253
1352
|
const renderChoose = () => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: circleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })))), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "How would you like to verify?"), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1254
1353
|
"button",
|
|
@@ -1307,12 +1406,28 @@ function FaceCaptureModal({
|
|
|
1307
1406
|
};
|
|
1308
1407
|
const renderSubmitting = () => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Verifying your photo\u2026"), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
|
|
1309
1408
|
const renderAccepted = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: successCircleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Verified"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Your identity has been confirmed. You can continue."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Continue")));
|
|
1310
|
-
const renderDeclined = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Retake")), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
|
|
1409
|
+
const renderDeclined = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, journeyMode ? /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => void handleJourneyRetry() }, /* @__PURE__ */ React__default.default.createElement("span", null, "Try Again")) : /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Retake")), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
|
|
1311
1410
|
const renderMaxAttempts = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Maximum Attempts Reached"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "For your security, your account has been temporarily restricted", result.data.freeze_duration_minutes ? ` for ${result.data.freeze_duration_minutes} minutes` : "", ". Please contact support if you need immediate help."), result.data?.enforce_logout && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), result.data?.block && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "This action has been blocked for security reasons."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
|
|
1312
1411
|
const renderError = (message) => {
|
|
1313
1412
|
const sessionExpired = /session\s+expired/i.test(message);
|
|
1314
|
-
return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something Went Wrong"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, sessionExpired ? "Your verification session is no longer valid. Please start a new verification from the beginning." : message), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, !sessionExpired && /* @__PURE__ */ React__default.default.createElement(
|
|
1413
|
+
return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something Went Wrong"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, sessionExpired ? "Your verification session is no longer valid. Please start a new verification from the beginning." : message), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, !sessionExpired && /* @__PURE__ */ React__default.default.createElement(
|
|
1414
|
+
"button",
|
|
1415
|
+
{
|
|
1416
|
+
style: primaryButtonStyle,
|
|
1417
|
+
onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
|
|
1418
|
+
},
|
|
1419
|
+
"Try Again"
|
|
1420
|
+
), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
|
|
1315
1421
|
};
|
|
1422
|
+
const renderJourney = (url) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, url ? /* @__PURE__ */ React__default.default.createElement(
|
|
1423
|
+
"iframe",
|
|
1424
|
+
{
|
|
1425
|
+
src: url,
|
|
1426
|
+
title: "Identity verification",
|
|
1427
|
+
allow: "camera; microphone",
|
|
1428
|
+
style: journeyFrameStyle
|
|
1429
|
+
}
|
|
1430
|
+
) : /* @__PURE__ */ React__default.default.createElement("div", { style: { padding: 24, textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Preparing your next attempt\u2026")), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...attemptsTextStyle, textAlign: "center" } }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
|
|
1316
1431
|
const body = (() => {
|
|
1317
1432
|
switch (stage.kind) {
|
|
1318
1433
|
case "choose":
|
|
@@ -1321,6 +1436,8 @@ function FaceCaptureModal({
|
|
|
1321
1436
|
return renderQRStage(stage.mobileConnected);
|
|
1322
1437
|
case "capture":
|
|
1323
1438
|
return renderCapture(stage.declinedReason);
|
|
1439
|
+
case "journey":
|
|
1440
|
+
return renderJourney(stage.url);
|
|
1324
1441
|
case "submitting":
|
|
1325
1442
|
return renderSubmitting();
|
|
1326
1443
|
case "accepted":
|
|
@@ -1333,10 +1450,10 @@ function FaceCaptureModal({
|
|
|
1333
1450
|
return renderError(stage.message);
|
|
1334
1451
|
}
|
|
1335
1452
|
})();
|
|
1336
|
-
return /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, /* @__PURE__ */ React__default.default.createElement("style", null, keyframes), /* @__PURE__ */ React__default.default.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: modalStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: headerStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1453
|
+
return /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, /* @__PURE__ */ React__default.default.createElement("style", null, keyframes), /* @__PURE__ */ React__default.default.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...modalStyle, maxWidth: modalMaxWidth(journeyMode) } }, /* @__PURE__ */ React__default.default.createElement("div", { style: headerStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1337
1454
|
"button",
|
|
1338
1455
|
{
|
|
1339
|
-
onClick:
|
|
1456
|
+
onClick: handleHeaderClose,
|
|
1340
1457
|
style: closeButtonStyle,
|
|
1341
1458
|
"aria-label": "Close"
|
|
1342
1459
|
},
|
|
@@ -1366,7 +1483,8 @@ var modalStyle = {
|
|
|
1366
1483
|
borderRadius: 16,
|
|
1367
1484
|
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1368
1485
|
width: "100%",
|
|
1369
|
-
|
|
1486
|
+
maxHeight: "calc(100vh - 32px)",
|
|
1487
|
+
overflowY: "auto",
|
|
1370
1488
|
animation: "zoomIn .2s ease-out"
|
|
1371
1489
|
};
|
|
1372
1490
|
var headerStyle = {
|
|
@@ -1392,6 +1510,13 @@ var contentStyle = {
|
|
|
1392
1510
|
flexDirection: "column",
|
|
1393
1511
|
gap: 16
|
|
1394
1512
|
};
|
|
1513
|
+
var journeyFrameStyle = {
|
|
1514
|
+
width: "100%",
|
|
1515
|
+
height: "min(520px, 60vh)",
|
|
1516
|
+
border: 0,
|
|
1517
|
+
borderRadius: 12,
|
|
1518
|
+
background: "#f9fafb"
|
|
1519
|
+
};
|
|
1395
1520
|
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1396
1521
|
var visualGuideStyle = { position: "relative" };
|
|
1397
1522
|
var circleStyle = {
|
|
@@ -1658,9 +1783,10 @@ function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
|
|
|
1658
1783
|
if (error) {
|
|
1659
1784
|
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")));
|
|
1660
1785
|
}
|
|
1661
|
-
const
|
|
1662
|
-
const
|
|
1663
|
-
const
|
|
1786
|
+
const detectionAvailable = !faceDetectionFailed;
|
|
1787
|
+
const detectionActive = ready && detectionAvailable;
|
|
1788
|
+
const liveMessage = detectionAvailable ? FACE_MESSAGES[faceStatus] : message;
|
|
1789
|
+
const liveFaceColor = detectionAvailable ? FACE_COLORS[faceStatus] : void 0;
|
|
1664
1790
|
const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
|
|
1665
1791
|
const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
|
|
1666
1792
|
const dynamicCircleStyle = {
|
|
@@ -1776,20 +1902,22 @@ var faceOverlayContainerStyle = {
|
|
|
1776
1902
|
};
|
|
1777
1903
|
var faceRingWrapStyle = {
|
|
1778
1904
|
position: "relative",
|
|
1779
|
-
|
|
1780
|
-
aspectRatio: "
|
|
1905
|
+
height: "64%",
|
|
1906
|
+
aspectRatio: "3 / 4"
|
|
1781
1907
|
};
|
|
1782
1908
|
var faceDashedRingStyle = {
|
|
1783
1909
|
position: "absolute",
|
|
1784
1910
|
inset: -6,
|
|
1785
|
-
borderRadius:
|
|
1786
|
-
border: "6px dashed rgba(255, 255, 255, 0.4)"
|
|
1787
|
-
animation: "spin 10s linear infinite"
|
|
1911
|
+
borderRadius: 9999,
|
|
1912
|
+
border: "6px dashed rgba(255, 255, 255, 0.4)"
|
|
1788
1913
|
};
|
|
1789
1914
|
var faceCircleStyle = {
|
|
1790
1915
|
position: "absolute",
|
|
1791
1916
|
inset: 0,
|
|
1792
|
-
|
|
1917
|
+
// Tailwind's `rounded-full` resolves to a very large fixed radius. On a
|
|
1918
|
+
// 3:4 box that creates the normal KYC capsule/head guide with straighter
|
|
1919
|
+
// sides; 50% would instead produce a mathematically perfect ellipse.
|
|
1920
|
+
borderRadius: 9999,
|
|
1793
1921
|
border: "4px solid #00bc7d",
|
|
1794
1922
|
boxShadow: "0 0 0 1000px rgba(0, 0, 0, 0.5)"
|
|
1795
1923
|
};
|
|
@@ -2132,6 +2260,7 @@ function isValidFileSize(file, maxSizeBytes = 10 * 1024 * 1024) {
|
|
|
2132
2260
|
}
|
|
2133
2261
|
function formatKycStatus(status) {
|
|
2134
2262
|
const statusMap = {
|
|
2263
|
+
awaiting_kyc: "Awaiting KYC",
|
|
2135
2264
|
pending: "Pending Review",
|
|
2136
2265
|
accepted: "Approved",
|
|
2137
2266
|
declined: "Rejected",
|
|
@@ -2142,6 +2271,8 @@ function formatKycStatus(status) {
|
|
|
2142
2271
|
}
|
|
2143
2272
|
function getStatusColor(status) {
|
|
2144
2273
|
const colorMap = {
|
|
2274
|
+
awaiting_kyc: "#94a3b8",
|
|
2275
|
+
// slate — not started
|
|
2145
2276
|
pending: "#f59e0b",
|
|
2146
2277
|
// amber
|
|
2147
2278
|
accepted: "#10b981",
|