vesant-sdk 1.6.6 → 1.7.0-dev.e0ee6d5
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/README.md +14 -4
- package/dist/{client-ePzhQKp9.d.mts → client-BolQlL5e.d.mts} +1 -1
- package/dist/{client-ePzhQKp9.d.ts → client-BolQlL5e.d.ts} +1 -1
- package/dist/client-C3DCmGe9.d.ts +436 -0
- package/dist/{client-C_A7QLcB.d.ts → client-DMIRx7Tu.d.mts} +5 -3
- package/dist/{client-BlCxjbY2.d.mts → client-DoMSYMMR.d.ts} +5 -3
- package/dist/client-ZNdnpWe7.d.mts +436 -0
- package/dist/compliance/index.d.mts +25 -429
- package/dist/compliance/index.d.ts +25 -429
- package/dist/compliance/index.js +187 -103
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +187 -104
- package/dist/compliance/index.mjs.map +1 -1
- package/dist/decisions/index.d.mts +2 -2
- package/dist/decisions/index.d.ts +2 -2
- package/dist/decisions/index.js +1 -1
- package/dist/decisions/index.js.map +1 -1
- package/dist/decisions/index.mjs +1 -1
- package/dist/decisions/index.mjs.map +1 -1
- package/dist/geolocation/index.d.mts +4 -4
- package/dist/geolocation/index.d.ts +4 -4
- package/dist/geolocation/index.js +7 -24
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +7 -24
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index.d.mts +12 -70
- package/dist/index.d.ts +12 -70
- package/dist/index.js +294 -292
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +293 -291
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +4 -4
- package/dist/kyc/core.d.ts +4 -4
- package/dist/kyc/core.js +78 -23
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +78 -24
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +269 -45
- package/dist/kyc/index.d.ts +269 -45
- package/dist/kyc/index.js +78 -23
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +78 -24
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +42 -7
- package/dist/react.d.ts +42 -7
- package/dist/react.js +663 -277
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +663 -277
- package/dist/react.mjs.map +1 -1
- package/dist/risk-profile/index.d.mts +4 -4
- package/dist/risk-profile/index.d.ts +4 -4
- package/dist/risk-profile/index.js +1 -1
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +1 -1
- package/dist/risk-profile/index.mjs.map +1 -1
- package/dist/scores/index.d.mts +2 -2
- package/dist/scores/index.d.ts +2 -2
- package/dist/scores/index.js +1 -1
- package/dist/scores/index.js.map +1 -1
- package/dist/scores/index.mjs +1 -1
- package/dist/scores/index.mjs.map +1 -1
- package/dist/tax/index.d.mts +6 -41
- package/dist/tax/index.d.ts +6 -41
- package/dist/tax/index.js +1 -36
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +1 -36
- package/dist/tax/index.mjs.map +1 -1
- package/dist/{types-1RzYeSal.d.mts → types-BOFaMQxI.d.mts} +2 -2
- package/dist/{types-B4Ezqo7V.d.mts → types-CBQRNL-l.d.mts} +14 -1
- package/dist/{types-B4Ezqo7V.d.ts → types-CBQRNL-l.d.ts} +14 -1
- package/dist/{types-X5Md_dD_.d.ts → types-UGyDl1fd.d.ts} +2 -2
- package/dist/webhooks/index.d.mts +189 -2
- package/dist/webhooks/index.d.ts +189 -2
- package/dist/webhooks/index.js +49 -7
- package/dist/webhooks/index.js.map +1 -1
- package/dist/webhooks/index.mjs +49 -7
- package/dist/webhooks/index.mjs.map +1 -1
- package/package.json +16 -13
- package/dist/fraud/index.d.mts +0 -80
- package/dist/fraud/index.d.ts +0 -80
- package/dist/fraud/index.js +0 -606
- package/dist/fraud/index.js.map +0 -1
- package/dist/fraud/index.mjs +0 -604
- package/dist/fraud/index.mjs.map +0 -1
- package/dist/index-B04H4xfJ.d.mts +0 -320
- package/dist/index-CItMPmLL.d.ts +0 -320
package/dist/react.js
CHANGED
|
@@ -10,7 +10,19 @@ var React__default = /*#__PURE__*/_interopDefault(React);
|
|
|
10
10
|
// src/geolocation/hooks.ts
|
|
11
11
|
|
|
12
12
|
// src/core/version.ts
|
|
13
|
-
var SDK_VERSION = "1.
|
|
13
|
+
var SDK_VERSION = "1.7.0";
|
|
14
|
+
|
|
15
|
+
// src/core/errors.ts
|
|
16
|
+
var VesantError = class _VesantError extends Error {
|
|
17
|
+
constructor(message, code, statusCode, details) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.statusCode = statusCode;
|
|
21
|
+
this.details = details;
|
|
22
|
+
this.name = "VesantError";
|
|
23
|
+
Object.setPrototypeOf(this, _VesantError.prototype);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
14
26
|
|
|
15
27
|
// src/shared/browser-utils.ts
|
|
16
28
|
function generateUUID() {
|
|
@@ -200,7 +212,7 @@ function encodePayload(payload) {
|
|
|
200
212
|
} else if (typeof Buffer !== "undefined") {
|
|
201
213
|
return Buffer.from(json, "utf-8").toString("base64");
|
|
202
214
|
}
|
|
203
|
-
throw new
|
|
215
|
+
throw new VesantError("No base64 encoding method available", "BASE64_UNAVAILABLE");
|
|
204
216
|
}
|
|
205
217
|
async function generateCipherText(options, config) {
|
|
206
218
|
const warnings = [];
|
|
@@ -225,8 +237,9 @@ async function generateCipherText(options, config) {
|
|
|
225
237
|
if (location) {
|
|
226
238
|
locationData = location;
|
|
227
239
|
} else if (gpsRequiredByConfig) {
|
|
228
|
-
throw new
|
|
229
|
-
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied
|
|
240
|
+
throw new VesantError(
|
|
241
|
+
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
242
|
+
"GPS_REQUIRED"
|
|
230
243
|
);
|
|
231
244
|
} else {
|
|
232
245
|
warnings.push("GPS location not available or permission denied");
|
|
@@ -839,7 +852,7 @@ function useCustomerProfile(client, customerId, options = {}) {
|
|
|
839
852
|
profileRef.current = profile;
|
|
840
853
|
const updateProfile = React.useCallback(
|
|
841
854
|
async (updates) => {
|
|
842
|
-
if (!profileRef.current) throw new
|
|
855
|
+
if (!profileRef.current) throw new VesantError("Profile not loaded", "PROFILE_NOT_LOADED");
|
|
843
856
|
setLoading(true);
|
|
844
857
|
setError(null);
|
|
845
858
|
try {
|
|
@@ -878,277 +891,641 @@ var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id
|
|
|
878
891
|
var Upload = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNyAxN0gxNy4wMU0xNS42IDE0SDE4QzE4LjkzMTkgMTQgMTkuMzk3OCAxNCAxOS43NjU0IDE0LjE1MjJDMjAuMjU1NCAxNC4zNTUyIDIwLjY0NDggMTQuNzQ0NiAyMC44NDc4IDE1LjIzNDZDMjEgMTUuNjAyMiAyMSAxNi4wNjgxIDIxIDE3QzIxIDE3LjkzMTkgMjEgMTguMzk3OCAyMC44NDc4IDE4Ljc2NTRDMjAuNjQ0OCAxOS4yNTU0IDIwLjI1NTQgMTkuNjQ0OCAxOS43NjU0IDE5Ljg0NzhDMTkuMzk3OCAyMCAxOC45MzE5IDIwIDE4IDIwSDZDNS4wNjgxMiAyMCA0LjYwMjE4IDIwIDQuMjM0NjMgMTkuODQ3OEMzLjc0NDU4IDE5LjY0NDggMy4zNTUyMyAxOS4yNTU0IDMuMTUyMjQgMTguNzY1NEMzIDE4LjM5NzggMyAxNy45MzE5IDMgMTdDMyAxNi4wNjgxIDMgMTUuNjAyMiAzLjE1MjI0IDE1LjIzNDZDMy4zNTUyMyAxNC43NDQ2IDMuNzQ0NTggMTQuMzU1MiA0LjIzNDYzIDE0LjE1MjJDNC42MDIxOCAxNCA1LjA2ODEyIDE0IDYgMTRIOC40TTEyIDE1VjRNMTIgNEwxNSA3TTEyIDRMOSA3IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+DQo8L3N2Zz4=";
|
|
879
892
|
|
|
880
893
|
// src/kyc/FaceCaptureModal.tsx
|
|
881
|
-
var
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
894
|
+
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
895
|
+
var LIVENESS_MESSAGES = [
|
|
896
|
+
"Position your face inside the circle",
|
|
897
|
+
"Make sure your face is well lit",
|
|
898
|
+
"Remove glasses or hats if possible",
|
|
899
|
+
"Look straight at the camera, then tap Capture"
|
|
900
|
+
];
|
|
901
|
+
function headerSubtitle(stageKind) {
|
|
902
|
+
switch (stageKind) {
|
|
903
|
+
case "qr":
|
|
904
|
+
return "Use your phone to continue";
|
|
905
|
+
case "accepted":
|
|
906
|
+
return "All set";
|
|
907
|
+
case "declined":
|
|
908
|
+
return "Verification didn't match, try once more";
|
|
909
|
+
case "max_attempts":
|
|
910
|
+
return "We couldn't verify you";
|
|
911
|
+
default:
|
|
912
|
+
return "Please capture a clear photo of your face";
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function defaultRenderQR(payload) {
|
|
916
|
+
const url = `https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=${encodeURIComponent(payload)}`;
|
|
917
|
+
return /* @__PURE__ */ React__default.default.createElement("img", { src: url, alt: "QR code", width: 220, height: 220 });
|
|
918
|
+
}
|
|
889
919
|
function FaceCaptureModal({
|
|
890
|
-
|
|
891
|
-
|
|
920
|
+
client,
|
|
921
|
+
session,
|
|
922
|
+
onComplete,
|
|
923
|
+
defaultDevice,
|
|
924
|
+
renderQR
|
|
892
925
|
}) {
|
|
893
|
-
const
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
926
|
+
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
927
|
+
const initialStage = (() => {
|
|
928
|
+
const choice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
929
|
+
if (choice === "this") return { kind: "capture" };
|
|
930
|
+
if (choice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
931
|
+
return { kind: "choose" };
|
|
932
|
+
})();
|
|
933
|
+
const [stage, setStage] = React.useState(initialStage);
|
|
934
|
+
const [attempts, setAttempts] = React.useState(session.attempts);
|
|
935
|
+
const maxAttempts = session.max_attempts || 1;
|
|
936
|
+
const [qrDeclinedReason, setQrDeclinedReason] = React.useState(null);
|
|
937
|
+
const [captureMode, setCaptureMode] = React.useState("idle");
|
|
938
|
+
const [capturedPreview, setCapturedPreview] = React.useState(null);
|
|
939
|
+
const [capturedBase64, setCapturedBase64] = React.useState(null);
|
|
940
|
+
const [livenessMessage, setLivenessMessage] = React.useState(LIVENESS_MESSAGES[0]);
|
|
941
|
+
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
942
|
+
const cancelledRef = React.useRef(false);
|
|
943
|
+
const submittingRef = React.useRef(false);
|
|
944
|
+
React.useEffect(() => {
|
|
945
|
+
if (stage.kind !== "qr") return;
|
|
946
|
+
let stopped = false;
|
|
947
|
+
const intervalMs = 2e3;
|
|
948
|
+
const deadline = Date.now() + 15 * 60 * 1e3;
|
|
949
|
+
let mobileSeen = stage.mobileConnected;
|
|
950
|
+
const tick = async () => {
|
|
951
|
+
if (stopped || cancelledRef.current) return;
|
|
952
|
+
try {
|
|
953
|
+
if (mobileSeen) {
|
|
954
|
+
const result = await client.getReuseKycSessionStatus(session.reference);
|
|
955
|
+
if (result.status === "accepted") {
|
|
956
|
+
setStage({ kind: "accepted", result });
|
|
957
|
+
stopped = true;
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (result.status === "declined") {
|
|
961
|
+
setAttempts(maxAttempts - (result.data?.retries_remaining ?? 0));
|
|
962
|
+
if (result.data?.retry_limit_exceeded) {
|
|
963
|
+
setStage({ kind: "max_attempts", result });
|
|
964
|
+
stopped = true;
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (result.declined_reason) {
|
|
968
|
+
setQrDeclinedReason(result.declined_reason);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
} else {
|
|
972
|
+
const handoff = await client.getHandoffSession(session.token);
|
|
973
|
+
if (handoff.mobile_connected) {
|
|
974
|
+
mobileSeen = true;
|
|
975
|
+
setStage({ kind: "qr", mobileConnected: true });
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
} catch {
|
|
979
|
+
}
|
|
980
|
+
if (Date.now() < deadline) {
|
|
981
|
+
setTimeout(tick, intervalMs);
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
tick();
|
|
985
|
+
return () => {
|
|
986
|
+
stopped = true;
|
|
987
|
+
};
|
|
988
|
+
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
989
|
+
const applyResult = (result) => {
|
|
990
|
+
if (result.status === "accepted") {
|
|
991
|
+
setStage({ kind: "accepted", result });
|
|
992
|
+
return true;
|
|
911
993
|
}
|
|
994
|
+
if (result.status === "declined") {
|
|
995
|
+
const nextAttempts = maxAttempts - (result.data?.retries_remaining ?? 0);
|
|
996
|
+
setAttempts(nextAttempts);
|
|
997
|
+
setCapturedPreview(null);
|
|
998
|
+
setCapturedBase64(null);
|
|
999
|
+
if (result.data?.retry_limit_exceeded) {
|
|
1000
|
+
setStage({ kind: "max_attempts", result });
|
|
1001
|
+
} else {
|
|
1002
|
+
setStage({ kind: "declined", result });
|
|
1003
|
+
}
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1006
|
+
return false;
|
|
912
1007
|
};
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
display: "flex",
|
|
980
|
-
alignItems: "center",
|
|
981
|
-
justifyContent: "center"
|
|
982
|
-
};
|
|
983
|
-
const badgeStyle = {
|
|
984
|
-
position: "absolute",
|
|
985
|
-
top: "-4px",
|
|
986
|
-
right: "-4px",
|
|
987
|
-
width: "32px",
|
|
988
|
-
height: "32px",
|
|
989
|
-
background: "#ffffff",
|
|
990
|
-
borderRadius: "50%",
|
|
991
|
-
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
|
992
|
-
display: "flex",
|
|
993
|
-
alignItems: "center",
|
|
994
|
-
justifyContent: "center"
|
|
995
|
-
};
|
|
996
|
-
const instructionsBoxStyle = {
|
|
997
|
-
background: "linear-gradient(135deg, #f9fafb 0%, rgba(243, 244, 246, 0.5) 100%)",
|
|
998
|
-
borderRadius: "12px",
|
|
999
|
-
padding: "16px",
|
|
1000
|
-
marginBottom: "24px"
|
|
1001
|
-
};
|
|
1002
|
-
const instructionsTitleStyle = {
|
|
1003
|
-
fontSize: "14px",
|
|
1004
|
-
fontWeight: 500,
|
|
1005
|
-
color: "#111827",
|
|
1006
|
-
marginBottom: "8px"
|
|
1007
|
-
};
|
|
1008
|
-
const instructionsListStyle = {
|
|
1009
|
-
fontSize: "12px",
|
|
1010
|
-
color: "#6b7280",
|
|
1011
|
-
listStyle: "none",
|
|
1012
|
-
padding: 0,
|
|
1013
|
-
margin: 0
|
|
1014
|
-
};
|
|
1015
|
-
const instructionItemStyle = {
|
|
1016
|
-
display: "flex",
|
|
1017
|
-
alignItems: "flex-start",
|
|
1018
|
-
marginBottom: "6px"
|
|
1019
|
-
};
|
|
1020
|
-
const bulletStyle = {
|
|
1021
|
-
color: "#00bc7d",
|
|
1022
|
-
marginRight: "8px",
|
|
1023
|
-
flexShrink: 0
|
|
1024
|
-
};
|
|
1025
|
-
const buttonsContainerStyle = {
|
|
1026
|
-
display: "flex",
|
|
1027
|
-
flexDirection: "column",
|
|
1028
|
-
gap: "12px"
|
|
1008
|
+
const runSubmit = async (proof) => {
|
|
1009
|
+
if (submittingRef.current) return;
|
|
1010
|
+
submittingRef.current = true;
|
|
1011
|
+
setStage({ kind: "submitting" });
|
|
1012
|
+
let settled = false;
|
|
1013
|
+
const ref = session.reference;
|
|
1014
|
+
const baselineAttempts = attempts;
|
|
1015
|
+
const isFreshPollResult = (r) => {
|
|
1016
|
+
if (r.status === "accepted") return true;
|
|
1017
|
+
if (r.status === "declined") {
|
|
1018
|
+
const derivedAttempts = maxAttempts - (r.data?.retries_remaining ?? 0);
|
|
1019
|
+
return derivedAttempts > baselineAttempts;
|
|
1020
|
+
}
|
|
1021
|
+
return false;
|
|
1022
|
+
};
|
|
1023
|
+
try {
|
|
1024
|
+
const postPromise = (async () => {
|
|
1025
|
+
try {
|
|
1026
|
+
const data = await client.submitReuseKycSession({
|
|
1027
|
+
token: session.token,
|
|
1028
|
+
reference: ref,
|
|
1029
|
+
proof
|
|
1030
|
+
});
|
|
1031
|
+
return { ok: true, data };
|
|
1032
|
+
} catch (err2) {
|
|
1033
|
+
return { ok: false, error: err2 };
|
|
1034
|
+
}
|
|
1035
|
+
})();
|
|
1036
|
+
const pollPromise = (async () => {
|
|
1037
|
+
await new Promise((r) => setTimeout(r, 12e3));
|
|
1038
|
+
const deadline = Date.now() + 12e4;
|
|
1039
|
+
while (Date.now() < deadline) {
|
|
1040
|
+
if (settled || cancelledRef.current) return null;
|
|
1041
|
+
try {
|
|
1042
|
+
const r = await client.getReuseKycSessionStatus(ref);
|
|
1043
|
+
if (isFreshPollResult(r)) return r;
|
|
1044
|
+
} catch {
|
|
1045
|
+
}
|
|
1046
|
+
if (settled || cancelledRef.current) return null;
|
|
1047
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
1048
|
+
}
|
|
1049
|
+
return null;
|
|
1050
|
+
})();
|
|
1051
|
+
const winner = await Promise.race([
|
|
1052
|
+
postPromise.then(
|
|
1053
|
+
(r) => r.ok ? { from: "post", data: r.data } : { from: "post_err" }
|
|
1054
|
+
),
|
|
1055
|
+
pollPromise.then(
|
|
1056
|
+
(data) => data ? { from: "poll", data } : { from: "poll_done" }
|
|
1057
|
+
)
|
|
1058
|
+
]);
|
|
1059
|
+
settled = true;
|
|
1060
|
+
if (winner.from === "post" && applyResult(winner.data)) return;
|
|
1061
|
+
if (winner.from === "poll" && applyResult(winner.data)) return;
|
|
1062
|
+
const [postResult, pollResult] = await Promise.all([postPromise, pollPromise]);
|
|
1063
|
+
if (postResult.ok && applyResult(postResult.data)) return;
|
|
1064
|
+
if (pollResult && applyResult(pollResult)) return;
|
|
1065
|
+
const err = postResult.ok ? null : postResult.error;
|
|
1066
|
+
setStage({
|
|
1067
|
+
kind: "error",
|
|
1068
|
+
message: err instanceof Error ? err.message : "Submission failed. Please try again."
|
|
1069
|
+
});
|
|
1070
|
+
} finally {
|
|
1071
|
+
settled = true;
|
|
1072
|
+
submittingRef.current = false;
|
|
1073
|
+
}
|
|
1029
1074
|
};
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
padding: "14px 24px",
|
|
1036
|
-
borderRadius: "12px",
|
|
1037
|
-
border: "none",
|
|
1038
|
-
cursor: isProcessing ? "not-allowed" : "pointer",
|
|
1039
|
-
transition: "all 0.2s",
|
|
1040
|
-
display: "flex",
|
|
1041
|
-
alignItems: "center",
|
|
1042
|
-
justifyContent: "center",
|
|
1043
|
-
gap: "12px",
|
|
1044
|
-
boxShadow: isProcessing ? "none" : isHoveringPrimary ? "0 20px 25px -5px rgba(0, 188, 125, 0.3)" : "0 10px 15px -3px rgba(0, 188, 125, 0.2)",
|
|
1045
|
-
fontSize: "16px"
|
|
1075
|
+
const handleCameraCapture = (dataUrl) => {
|
|
1076
|
+
const raw = dataUrl.split(",")[1] ?? "";
|
|
1077
|
+
setCapturedPreview(dataUrl);
|
|
1078
|
+
setCapturedBase64(raw);
|
|
1079
|
+
setCaptureMode("preview");
|
|
1046
1080
|
};
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
padding: "14px 24px",
|
|
1053
|
-
borderRadius: "12px",
|
|
1054
|
-
border: "2px solid #e5e7eb",
|
|
1055
|
-
cursor: isProcessing ? "not-allowed" : "pointer",
|
|
1056
|
-
transition: "all 0.2s",
|
|
1057
|
-
fontSize: "16px"
|
|
1081
|
+
const handleCameraCancel = () => setCaptureMode("idle");
|
|
1082
|
+
const handleRetake = () => {
|
|
1083
|
+
setCapturedPreview(null);
|
|
1084
|
+
setCapturedBase64(null);
|
|
1085
|
+
setCaptureMode("live");
|
|
1058
1086
|
};
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1087
|
+
const handleConfirmSubmit = () => {
|
|
1088
|
+
if (!capturedBase64) return;
|
|
1089
|
+
void runSubmit(capturedBase64);
|
|
1061
1090
|
};
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
color: "#9ca3af",
|
|
1066
|
-
margin: 0
|
|
1091
|
+
const handleOpenCamera = () => {
|
|
1092
|
+
if (submittingRef.current) return;
|
|
1093
|
+
setCaptureMode("live");
|
|
1067
1094
|
};
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
borderRadius: "50%",
|
|
1074
|
-
animation: "spin 0.6s linear infinite"
|
|
1095
|
+
const handleRetakeAfterDecline = () => {
|
|
1096
|
+
setCapturedPreview(null);
|
|
1097
|
+
setCapturedBase64(null);
|
|
1098
|
+
setCaptureMode("live");
|
|
1099
|
+
setStage({ kind: "capture" });
|
|
1075
1100
|
};
|
|
1076
|
-
|
|
1077
|
-
|
|
1101
|
+
React.useEffect(() => {
|
|
1102
|
+
if (captureMode !== "live") return;
|
|
1103
|
+
setLivenessMessage(LIVENESS_MESSAGES[0]);
|
|
1104
|
+
let i = 0;
|
|
1105
|
+
const interval = setInterval(() => {
|
|
1106
|
+
i = (i + 1) % LIVENESS_MESSAGES.length;
|
|
1107
|
+
setLivenessMessage(LIVENESS_MESSAGES[i]);
|
|
1108
|
+
}, 3e3);
|
|
1109
|
+
return () => clearInterval(interval);
|
|
1110
|
+
}, [captureMode]);
|
|
1111
|
+
const close = (result) => {
|
|
1112
|
+
cancelledRef.current = true;
|
|
1113
|
+
onComplete(result);
|
|
1078
1114
|
};
|
|
1079
|
-
const
|
|
1080
|
-
@keyframes fadeIn {
|
|
1081
|
-
from {
|
|
1082
|
-
opacity: 0;
|
|
1083
|
-
}
|
|
1084
|
-
to {
|
|
1085
|
-
opacity: 1;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
@keyframes zoomIn {
|
|
1090
|
-
from {
|
|
1091
|
-
opacity: 0;
|
|
1092
|
-
transform: scale(0.95);
|
|
1093
|
-
}
|
|
1094
|
-
to {
|
|
1095
|
-
opacity: 1;
|
|
1096
|
-
transform: scale(1);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
@keyframes spin {
|
|
1101
|
-
from {
|
|
1102
|
-
transform: rotate(0deg);
|
|
1103
|
-
}
|
|
1104
|
-
to {
|
|
1105
|
-
transform: rotate(360deg);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
`);
|
|
1109
|
-
return /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, styleTag, /* @__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(
|
|
1115
|
+
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(
|
|
1110
1116
|
"button",
|
|
1111
1117
|
{
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
onMouseLeave: () => setIsHoveringClose(false),
|
|
1115
|
-
style: closeButtonStyle,
|
|
1116
|
-
"aria-label": "Close"
|
|
1118
|
+
style: primaryButtonStyle,
|
|
1119
|
+
onClick: () => setStage({ kind: "capture" })
|
|
1117
1120
|
},
|
|
1118
|
-
/* @__PURE__ */ React__default.default.createElement("img", { src:
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
+
/* @__PURE__ */ React__default.default.createElement("img", { src: isMobile ? Camera : Upload, alt: "", width: 20, height: 20 }),
|
|
1122
|
+
/* @__PURE__ */ React__default.default.createElement("span", null, "Continue on this device")
|
|
1123
|
+
), /* @__PURE__ */ React__default.default.createElement(
|
|
1124
|
+
"button",
|
|
1125
|
+
{
|
|
1126
|
+
style: secondaryButtonStyle,
|
|
1127
|
+
onClick: () => setStage({ kind: "qr", mobileConnected: false })
|
|
1128
|
+
},
|
|
1129
|
+
"Continue on mobile (scan QR)"
|
|
1130
|
+
), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
|
|
1131
|
+
const renderQRStage = (mobileConnected) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: qrBoxStyle }, (renderQR ?? defaultRenderQR)(qrPayload)), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, mobileConnected ? "Phone connected. Complete the capture on your mobile device\u2026" : "Scan this QR with your phone camera, then complete the face capture there."), mobileConnected && /* @__PURE__ */ React__default.default.createElement("div", { style: { ...spinnerStyle, margin: "12px auto" } }), qrDeclinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Last attempt was declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, qrDeclinedReason), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...alertTextStyle, marginTop: 4 } }, "Try again on your phone. Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts, ".")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1132
|
+
"button",
|
|
1121
1133
|
{
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1134
|
+
style: secondaryButtonStyle,
|
|
1135
|
+
onClick: () => setStage({ kind: "capture" })
|
|
1136
|
+
},
|
|
1137
|
+
"Use this device instead"
|
|
1138
|
+
), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
|
|
1139
|
+
const renderCapture = (declinedReason) => {
|
|
1140
|
+
if (captureMode === "live") {
|
|
1141
|
+
return /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1142
|
+
LiveCamera,
|
|
1143
|
+
{
|
|
1144
|
+
onCapture: handleCameraCapture,
|
|
1145
|
+
onCancel: handleCameraCancel,
|
|
1146
|
+
message: livenessMessage
|
|
1147
|
+
}
|
|
1148
|
+
), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1149
|
+
}
|
|
1150
|
+
if (captureMode === "preview" && capturedPreview) {
|
|
1151
|
+
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));
|
|
1152
|
+
}
|
|
1153
|
+
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")), /* @__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));
|
|
1154
|
+
};
|
|
1155
|
+
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."));
|
|
1156
|
+
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")));
|
|
1157
|
+
const renderDeclined = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__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 }, 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));
|
|
1158
|
+
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")));
|
|
1159
|
+
const renderError = (message) => /* @__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 }, "Something went wrong"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, message), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__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) }, "Cancel")));
|
|
1160
|
+
const body = (() => {
|
|
1161
|
+
switch (stage.kind) {
|
|
1162
|
+
case "choose":
|
|
1163
|
+
return renderChoose();
|
|
1164
|
+
case "qr":
|
|
1165
|
+
return renderQRStage(stage.mobileConnected);
|
|
1166
|
+
case "capture":
|
|
1167
|
+
return renderCapture(stage.declinedReason);
|
|
1168
|
+
case "submitting":
|
|
1169
|
+
return renderSubmitting();
|
|
1170
|
+
case "accepted":
|
|
1171
|
+
return renderAccepted(stage.result);
|
|
1172
|
+
case "declined":
|
|
1173
|
+
return renderDeclined(stage.result);
|
|
1174
|
+
case "max_attempts":
|
|
1175
|
+
return renderMaxAttempts(stage.result);
|
|
1176
|
+
case "error":
|
|
1177
|
+
return renderError(stage.message);
|
|
1129
1178
|
}
|
|
1130
|
-
)
|
|
1179
|
+
})();
|
|
1180
|
+
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(
|
|
1131
1181
|
"button",
|
|
1132
1182
|
{
|
|
1133
|
-
onClick: () =>
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
disabled: isProcessing,
|
|
1137
|
-
style: primaryButtonStyle
|
|
1183
|
+
onClick: () => close(null),
|
|
1184
|
+
style: closeButtonStyle,
|
|
1185
|
+
"aria-label": "Close"
|
|
1138
1186
|
},
|
|
1139
|
-
|
|
1140
|
-
), /* @__PURE__ */ React__default.default.createElement(
|
|
1187
|
+
/* @__PURE__ */ React__default.default.createElement("img", { src: Close, alt: "", width: 16, height: 16 })
|
|
1188
|
+
), /* @__PURE__ */ React__default.default.createElement("h2", { style: titleStyle }, "Face Verification"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, headerSubtitle(stage.kind))), body, /* @__PURE__ */ React__default.default.createElement("div", { style: footerStyle }, /* @__PURE__ */ React__default.default.createElement("p", { style: footerTextStyle }, "Your photo is securely processed and used only for verification.")))));
|
|
1189
|
+
}
|
|
1190
|
+
var keyframes = `
|
|
1191
|
+
@keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
|
|
1192
|
+
@keyframes zoomIn { from { transform: scale(.96); opacity: 0 } to { transform: scale(1); opacity: 1 } }
|
|
1193
|
+
@keyframes spin { to { transform: rotate(360deg) } }
|
|
1194
|
+
@keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }
|
|
1195
|
+
`;
|
|
1196
|
+
var overlayStyle = {
|
|
1197
|
+
position: "fixed",
|
|
1198
|
+
inset: 0,
|
|
1199
|
+
background: "rgba(0,0,0,.6)",
|
|
1200
|
+
backdropFilter: "blur(4px)",
|
|
1201
|
+
display: "flex",
|
|
1202
|
+
alignItems: "center",
|
|
1203
|
+
justifyContent: "center",
|
|
1204
|
+
zIndex: 999999,
|
|
1205
|
+
padding: 16,
|
|
1206
|
+
animation: "fadeIn .2s ease-out"
|
|
1207
|
+
};
|
|
1208
|
+
var modalStyle = {
|
|
1209
|
+
background: "#fff",
|
|
1210
|
+
borderRadius: 16,
|
|
1211
|
+
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1212
|
+
width: "100%",
|
|
1213
|
+
maxWidth: 448,
|
|
1214
|
+
animation: "zoomIn .2s ease-out"
|
|
1215
|
+
};
|
|
1216
|
+
var headerStyle = {
|
|
1217
|
+
position: "relative",
|
|
1218
|
+
padding: "24px 24px 16px",
|
|
1219
|
+
borderBottom: "1px solid #f3f4f6"
|
|
1220
|
+
};
|
|
1221
|
+
var closeButtonStyle = {
|
|
1222
|
+
position: "absolute",
|
|
1223
|
+
top: 16,
|
|
1224
|
+
right: 16,
|
|
1225
|
+
padding: 8,
|
|
1226
|
+
background: "transparent",
|
|
1227
|
+
border: "none",
|
|
1228
|
+
borderRadius: 8,
|
|
1229
|
+
cursor: "pointer"
|
|
1230
|
+
};
|
|
1231
|
+
var titleStyle = { margin: "0 0 4px", fontSize: 18, fontWeight: 600, color: "#111827" };
|
|
1232
|
+
var subtitleStyle = { margin: 0, fontSize: 14, color: "#6b7280" };
|
|
1233
|
+
var contentStyle = {
|
|
1234
|
+
padding: 24,
|
|
1235
|
+
display: "flex",
|
|
1236
|
+
flexDirection: "column",
|
|
1237
|
+
gap: 16
|
|
1238
|
+
};
|
|
1239
|
+
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1240
|
+
var visualGuideStyle = { position: "relative" };
|
|
1241
|
+
var circleStyle = {
|
|
1242
|
+
width: 96,
|
|
1243
|
+
height: 96,
|
|
1244
|
+
borderRadius: "50%",
|
|
1245
|
+
background: "linear-gradient(135deg, rgba(0, 188, 125, 0.2) 0%, rgba(0, 188, 125, 0.05) 100%)",
|
|
1246
|
+
display: "flex",
|
|
1247
|
+
alignItems: "center",
|
|
1248
|
+
justifyContent: "center"
|
|
1249
|
+
};
|
|
1250
|
+
var badgeStyle = {
|
|
1251
|
+
position: "absolute",
|
|
1252
|
+
bottom: -4,
|
|
1253
|
+
right: -4,
|
|
1254
|
+
width: 28,
|
|
1255
|
+
height: 28,
|
|
1256
|
+
borderRadius: "50%",
|
|
1257
|
+
background: "#ffffff",
|
|
1258
|
+
border: "2px solid #00bc7d",
|
|
1259
|
+
display: "flex",
|
|
1260
|
+
alignItems: "center",
|
|
1261
|
+
justifyContent: "center"
|
|
1262
|
+
};
|
|
1263
|
+
var successCircleStyle = {
|
|
1264
|
+
...circleStyle,
|
|
1265
|
+
background: "linear-gradient(135deg, rgba(0, 188, 125, 0.25) 0%, rgba(0, 188, 125, 0.1) 100%)",
|
|
1266
|
+
margin: "0 auto"
|
|
1267
|
+
};
|
|
1268
|
+
var errorCircleStyle = {
|
|
1269
|
+
width: 64,
|
|
1270
|
+
height: 64,
|
|
1271
|
+
borderRadius: "50%",
|
|
1272
|
+
background: "#fee2e2",
|
|
1273
|
+
color: "#b91c1c",
|
|
1274
|
+
fontSize: 32,
|
|
1275
|
+
fontWeight: 700,
|
|
1276
|
+
display: "flex",
|
|
1277
|
+
alignItems: "center",
|
|
1278
|
+
justifyContent: "center",
|
|
1279
|
+
margin: "0 auto"
|
|
1280
|
+
};
|
|
1281
|
+
var qrBoxStyle = {
|
|
1282
|
+
display: "flex",
|
|
1283
|
+
justifyContent: "center",
|
|
1284
|
+
padding: 12,
|
|
1285
|
+
background: "linear-gradient(135deg, #f9fafb 0%, rgba(243, 244, 246, 0.5) 100%)",
|
|
1286
|
+
borderRadius: 12
|
|
1287
|
+
};
|
|
1288
|
+
var alertBoxStyle = {
|
|
1289
|
+
background: "#fef3c7",
|
|
1290
|
+
border: "1px solid #fde68a",
|
|
1291
|
+
color: "#92400e",
|
|
1292
|
+
borderRadius: 8,
|
|
1293
|
+
padding: 12,
|
|
1294
|
+
fontSize: 14
|
|
1295
|
+
};
|
|
1296
|
+
var alertTextStyle = { margin: "4px 0 0", fontSize: 13, color: "#92400e" };
|
|
1297
|
+
var instructionsBoxStyle = {
|
|
1298
|
+
background: "linear-gradient(135deg, #f9fafb 0%, rgba(243, 244, 246, 0.5) 100%)",
|
|
1299
|
+
borderRadius: 8,
|
|
1300
|
+
padding: 16
|
|
1301
|
+
};
|
|
1302
|
+
var instructionsTitleStyle = { margin: "0 0 8px", fontSize: 14, fontWeight: 600, color: "#374151" };
|
|
1303
|
+
var instructionsListStyle = { margin: 0, padding: 0, listStyle: "none" };
|
|
1304
|
+
var instructionItemStyle = { display: "flex", gap: 8, fontSize: 13, color: "#4b5563", lineHeight: 1.5 };
|
|
1305
|
+
var bulletStyle = { color: "#00bc7d" };
|
|
1306
|
+
var previewBoxStyle = {
|
|
1307
|
+
borderRadius: 12,
|
|
1308
|
+
overflow: "hidden",
|
|
1309
|
+
border: "2px solid rgba(0, 188, 125, 0.4)",
|
|
1310
|
+
background: "#0f172a",
|
|
1311
|
+
display: "flex",
|
|
1312
|
+
justifyContent: "center"
|
|
1313
|
+
};
|
|
1314
|
+
var previewImgStyle = {
|
|
1315
|
+
width: "100%",
|
|
1316
|
+
maxHeight: 320,
|
|
1317
|
+
objectFit: "contain",
|
|
1318
|
+
background: "#0f172a"
|
|
1319
|
+
};
|
|
1320
|
+
var buttonsContainerStyle = {
|
|
1321
|
+
display: "flex",
|
|
1322
|
+
flexDirection: "column",
|
|
1323
|
+
gap: 8
|
|
1324
|
+
};
|
|
1325
|
+
var primaryButtonStyle = {
|
|
1326
|
+
display: "flex",
|
|
1327
|
+
alignItems: "center",
|
|
1328
|
+
justifyContent: "center",
|
|
1329
|
+
gap: 8,
|
|
1330
|
+
padding: "12px 16px",
|
|
1331
|
+
background: "#00bc7d",
|
|
1332
|
+
color: "#ffffff",
|
|
1333
|
+
border: "none",
|
|
1334
|
+
borderRadius: 8,
|
|
1335
|
+
fontSize: 14,
|
|
1336
|
+
fontWeight: 600,
|
|
1337
|
+
cursor: "pointer"
|
|
1338
|
+
};
|
|
1339
|
+
var secondaryButtonStyle = {
|
|
1340
|
+
...primaryButtonStyle,
|
|
1341
|
+
background: "#ffffff",
|
|
1342
|
+
color: "#374151",
|
|
1343
|
+
border: "2px solid #e5e7eb"
|
|
1344
|
+
};
|
|
1345
|
+
var cancelButtonStyle = {
|
|
1346
|
+
...primaryButtonStyle,
|
|
1347
|
+
background: "transparent",
|
|
1348
|
+
color: "#6b7280",
|
|
1349
|
+
border: "none"
|
|
1350
|
+
};
|
|
1351
|
+
var spinnerStyle = {
|
|
1352
|
+
width: 16,
|
|
1353
|
+
height: 16,
|
|
1354
|
+
border: "2px solid rgba(0, 188, 125, 0.25)",
|
|
1355
|
+
borderTopColor: "#00bc7d",
|
|
1356
|
+
borderRadius: "50%",
|
|
1357
|
+
animation: "spin .8s linear infinite"
|
|
1358
|
+
};
|
|
1359
|
+
var attemptsTextStyle = {
|
|
1360
|
+
margin: "8px 0 0",
|
|
1361
|
+
fontSize: 12,
|
|
1362
|
+
color: "#9ca3af",
|
|
1363
|
+
textAlign: "center"
|
|
1364
|
+
};
|
|
1365
|
+
var footerStyle = { padding: "12px 24px 16px", borderTop: "1px solid #f3f4f6" };
|
|
1366
|
+
var footerTextStyle = { margin: 0, fontSize: 12, color: "#9ca3af", textAlign: "center" };
|
|
1367
|
+
function LiveCamera({ onCapture, onCancel, message }) {
|
|
1368
|
+
const videoRef = React.useRef(null);
|
|
1369
|
+
const streamRef = React.useRef(null);
|
|
1370
|
+
const [ready, setReady] = React.useState(false);
|
|
1371
|
+
const [error, setError] = React.useState(null);
|
|
1372
|
+
React.useEffect(() => {
|
|
1373
|
+
let cancelled = false;
|
|
1374
|
+
async function init() {
|
|
1375
|
+
try {
|
|
1376
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
1377
|
+
throw new Error("Camera is not available in this browser");
|
|
1378
|
+
}
|
|
1379
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
1380
|
+
video: {
|
|
1381
|
+
facingMode: "user",
|
|
1382
|
+
width: { ideal: 1280 },
|
|
1383
|
+
height: { ideal: 1280 }
|
|
1384
|
+
},
|
|
1385
|
+
audio: false
|
|
1386
|
+
});
|
|
1387
|
+
if (cancelled) {
|
|
1388
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
streamRef.current = stream;
|
|
1392
|
+
const video = videoRef.current;
|
|
1393
|
+
if (video) {
|
|
1394
|
+
video.srcObject = stream;
|
|
1395
|
+
video.onloadedmetadata = () => {
|
|
1396
|
+
video.play().catch(() => {
|
|
1397
|
+
});
|
|
1398
|
+
setReady(true);
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
} catch (err) {
|
|
1402
|
+
setError(err instanceof Error ? err.message : "Unable to access camera");
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
init();
|
|
1406
|
+
return () => {
|
|
1407
|
+
cancelled = true;
|
|
1408
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
1409
|
+
streamRef.current = null;
|
|
1410
|
+
};
|
|
1411
|
+
}, []);
|
|
1412
|
+
const capture = () => {
|
|
1413
|
+
const video = videoRef.current;
|
|
1414
|
+
if (!video || !video.videoWidth) return;
|
|
1415
|
+
const size = Math.min(video.videoWidth, video.videoHeight);
|
|
1416
|
+
const sx = (video.videoWidth - size) / 2;
|
|
1417
|
+
const sy = (video.videoHeight - size) / 2;
|
|
1418
|
+
const canvas = document.createElement("canvas");
|
|
1419
|
+
canvas.width = size;
|
|
1420
|
+
canvas.height = size;
|
|
1421
|
+
const ctx = canvas.getContext("2d");
|
|
1422
|
+
if (!ctx) return;
|
|
1423
|
+
ctx.translate(size, 0);
|
|
1424
|
+
ctx.scale(-1, 1);
|
|
1425
|
+
ctx.drawImage(video, sx, sy, size, size, 0, 0, size, size);
|
|
1426
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
1427
|
+
streamRef.current = null;
|
|
1428
|
+
onCapture(canvas.toDataURL("image/jpeg", 0.9));
|
|
1429
|
+
};
|
|
1430
|
+
if (error) {
|
|
1431
|
+
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")));
|
|
1432
|
+
}
|
|
1433
|
+
return /* @__PURE__ */ React__default.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React__default.default.createElement("div", { style: videoFrameStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1434
|
+
"video",
|
|
1435
|
+
{
|
|
1436
|
+
ref: videoRef,
|
|
1437
|
+
playsInline: true,
|
|
1438
|
+
muted: true,
|
|
1439
|
+
autoPlay: true,
|
|
1440
|
+
style: videoStyle
|
|
1441
|
+
}
|
|
1442
|
+
), /* @__PURE__ */ React__default.default.createElement("div", { style: faceOverlayContainerStyle, "aria-hidden": "true" }, /* @__PURE__ */ React__default.default.createElement("div", { style: faceRingWrapStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: faceDashedRingStyle }), /* @__PURE__ */ React__default.default.createElement("div", { style: faceCircleStyle }))), message && /* @__PURE__ */ React__default.default.createElement("div", { style: liveMessagePillContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: liveMessagePillStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: liveMessagePillTextStyle }, message))), !ready && /* @__PURE__ */ React__default.default.createElement("div", { style: videoLoadingStyle }, "Starting camera\u2026")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement(
|
|
1141
1443
|
"button",
|
|
1142
1444
|
{
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
disabled: isProcessing,
|
|
1147
|
-
style: cancelButtonStyle
|
|
1445
|
+
style: ready ? primaryButtonStyle : { ...primaryButtonStyle, opacity: 0.5, cursor: "not-allowed" },
|
|
1446
|
+
onClick: capture,
|
|
1447
|
+
disabled: !ready
|
|
1148
1448
|
},
|
|
1149
|
-
"
|
|
1150
|
-
|
|
1449
|
+
/* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }),
|
|
1450
|
+
/* @__PURE__ */ React__default.default.createElement("span", null, "Capture")
|
|
1451
|
+
), /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Cancel")));
|
|
1151
1452
|
}
|
|
1453
|
+
var videoFrameStyle = {
|
|
1454
|
+
position: "relative",
|
|
1455
|
+
width: "100%",
|
|
1456
|
+
aspectRatio: "1 / 1",
|
|
1457
|
+
borderRadius: 12,
|
|
1458
|
+
overflow: "hidden",
|
|
1459
|
+
background: "#0f172a"
|
|
1460
|
+
};
|
|
1461
|
+
var videoStyle = {
|
|
1462
|
+
width: "100%",
|
|
1463
|
+
height: "100%",
|
|
1464
|
+
objectFit: "cover",
|
|
1465
|
+
transform: "scaleX(-1)"
|
|
1466
|
+
// mirror so the preview matches the user's POV
|
|
1467
|
+
};
|
|
1468
|
+
var faceOverlayContainerStyle = {
|
|
1469
|
+
position: "absolute",
|
|
1470
|
+
inset: 0,
|
|
1471
|
+
pointerEvents: "none",
|
|
1472
|
+
display: "flex",
|
|
1473
|
+
alignItems: "center",
|
|
1474
|
+
justifyContent: "center"
|
|
1475
|
+
};
|
|
1476
|
+
var faceRingWrapStyle = {
|
|
1477
|
+
position: "relative",
|
|
1478
|
+
width: "72%",
|
|
1479
|
+
aspectRatio: "1 / 1"
|
|
1480
|
+
};
|
|
1481
|
+
var faceDashedRingStyle = {
|
|
1482
|
+
position: "absolute",
|
|
1483
|
+
inset: -6,
|
|
1484
|
+
borderRadius: "50%",
|
|
1485
|
+
border: "6px dashed rgba(255, 255, 255, 0.4)",
|
|
1486
|
+
animation: "spin 10s linear infinite"
|
|
1487
|
+
};
|
|
1488
|
+
var faceCircleStyle = {
|
|
1489
|
+
position: "absolute",
|
|
1490
|
+
inset: 0,
|
|
1491
|
+
borderRadius: "50%",
|
|
1492
|
+
border: "4px solid #00bc7d",
|
|
1493
|
+
boxShadow: "0 0 0 1000px rgba(0, 0, 0, 0.5)"
|
|
1494
|
+
};
|
|
1495
|
+
var videoLoadingStyle = {
|
|
1496
|
+
position: "absolute",
|
|
1497
|
+
inset: 0,
|
|
1498
|
+
display: "flex",
|
|
1499
|
+
alignItems: "center",
|
|
1500
|
+
justifyContent: "center",
|
|
1501
|
+
color: "#e5e7eb",
|
|
1502
|
+
fontSize: 14,
|
|
1503
|
+
fontWeight: 500
|
|
1504
|
+
};
|
|
1505
|
+
var liveMessagePillContainerStyle = {
|
|
1506
|
+
position: "absolute",
|
|
1507
|
+
top: 12,
|
|
1508
|
+
left: "50%",
|
|
1509
|
+
transform: "translateX(-50%)",
|
|
1510
|
+
maxWidth: "84%",
|
|
1511
|
+
pointerEvents: "none"
|
|
1512
|
+
};
|
|
1513
|
+
var liveMessagePillStyle = {
|
|
1514
|
+
background: "rgba(0, 0, 0, 0.7)",
|
|
1515
|
+
backdropFilter: "blur(6px)",
|
|
1516
|
+
border: "1px solid rgba(255, 255, 255, 0.2)",
|
|
1517
|
+
borderRadius: 999,
|
|
1518
|
+
padding: "6px 12px",
|
|
1519
|
+
textAlign: "center",
|
|
1520
|
+
boxShadow: "0 10px 25px -5px rgba(0,0,0,.3)"
|
|
1521
|
+
};
|
|
1522
|
+
var liveMessagePillTextStyle = {
|
|
1523
|
+
margin: 0,
|
|
1524
|
+
fontSize: 12,
|
|
1525
|
+
fontWeight: 500,
|
|
1526
|
+
color: "#ffffff",
|
|
1527
|
+
animation: "pulse 2s ease-in-out infinite"
|
|
1528
|
+
};
|
|
1152
1529
|
var FaceCaptureModal_default = FaceCaptureModal;
|
|
1153
1530
|
|
|
1154
1531
|
// src/kyc/hooks.ts
|
|
@@ -1432,7 +1809,7 @@ function useKycPreferences(client, autoFetch = true) {
|
|
|
1432
1809
|
refresh: fetchPreferences
|
|
1433
1810
|
};
|
|
1434
1811
|
}
|
|
1435
|
-
function
|
|
1812
|
+
function fileToBase64(file) {
|
|
1436
1813
|
return new Promise((resolve, reject) => {
|
|
1437
1814
|
const reader = new FileReader();
|
|
1438
1815
|
reader.onload = () => {
|
|
@@ -1489,43 +1866,52 @@ function getRiskColor(risk) {
|
|
|
1489
1866
|
return colorMap[risk] || "#6b7280";
|
|
1490
1867
|
}
|
|
1491
1868
|
function useReuseKYCSubmission(client$1) {
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1869
|
+
const startSession = React.useCallback(
|
|
1870
|
+
(request) => client$1.createReuseKycSession(request),
|
|
1871
|
+
[client$1]
|
|
1872
|
+
);
|
|
1873
|
+
const verifyFace = React.useCallback(
|
|
1874
|
+
(session, opts = {}) => new Promise((resolve) => {
|
|
1494
1875
|
const container = document.createElement("div");
|
|
1495
1876
|
document.body.appendChild(container);
|
|
1496
1877
|
const root = client.createRoot(container);
|
|
1497
1878
|
const cleanup = () => {
|
|
1498
1879
|
root.unmount();
|
|
1499
|
-
|
|
1880
|
+
container.remove();
|
|
1500
1881
|
};
|
|
1501
1882
|
const modal = React.createElement(FaceCaptureModal_default, {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
token
|
|
1508
|
-
});
|
|
1509
|
-
resolve(resp);
|
|
1510
|
-
} catch (error) {
|
|
1511
|
-
reject(error instanceof Error ? error : new Error("Face verification failed"));
|
|
1512
|
-
} finally {
|
|
1513
|
-
cleanup();
|
|
1514
|
-
}
|
|
1515
|
-
},
|
|
1516
|
-
onCancel: () => {
|
|
1883
|
+
client: client$1,
|
|
1884
|
+
session,
|
|
1885
|
+
defaultDevice: opts.defaultDevice,
|
|
1886
|
+
renderQR: opts.renderQR,
|
|
1887
|
+
onComplete: (result) => {
|
|
1517
1888
|
cleanup();
|
|
1518
|
-
resolve(
|
|
1889
|
+
resolve(result);
|
|
1519
1890
|
}
|
|
1520
1891
|
});
|
|
1521
1892
|
root.render(modal);
|
|
1522
|
-
})
|
|
1523
|
-
|
|
1524
|
-
|
|
1893
|
+
}),
|
|
1894
|
+
[client$1]
|
|
1895
|
+
);
|
|
1896
|
+
const runFlow = React.useCallback(
|
|
1897
|
+
async (request, opts = {}) => {
|
|
1898
|
+
const session = await startSession(request);
|
|
1899
|
+
if (!session.is_required) {
|
|
1900
|
+
return { kind: "not_required", session };
|
|
1901
|
+
}
|
|
1902
|
+
const result = await verifyFace(session, opts);
|
|
1903
|
+
if (result === null) {
|
|
1904
|
+
return { kind: "cancelled", session };
|
|
1905
|
+
}
|
|
1906
|
+
return { kind: "completed", session, result };
|
|
1907
|
+
},
|
|
1908
|
+
[startSession, verifyFace]
|
|
1909
|
+
);
|
|
1910
|
+
return { startSession, verifyFace, runFlow };
|
|
1525
1911
|
}
|
|
1526
1912
|
|
|
1527
1913
|
exports.createDeviceFingerprint = createDeviceFingerprint;
|
|
1528
|
-
exports.fileToBase64 =
|
|
1914
|
+
exports.fileToBase64 = fileToBase64;
|
|
1529
1915
|
exports.formatKycStatus = formatKycStatus;
|
|
1530
1916
|
exports.getBrowserInfo = getBrowserInfo;
|
|
1531
1917
|
exports.getRiskColor = getRiskColor;
|