vesant-sdk 2.0.0-dev.51108af → 2.0.0-dev.93ecc12
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-CIEa7xYG.d.mts → client-BwkGrRW9.d.mts} +1 -1
- package/dist/{client-BJ87_Vv5.d.ts → client-CqSx0lAG.d.ts} +3 -3
- package/dist/{client-IAOGCBfm.d.mts → client-Dq0NMaXT.d.mts} +3 -3
- package/dist/{client-Bvp-f05-.d.ts → client-DvobLmsc.d.ts} +1 -1
- package/dist/compliance/index.d.mts +4 -4
- package/dist/compliance/index.d.ts +4 -4
- package/dist/decisions/index.d.mts +1 -1
- package/dist/decisions/index.d.ts +1 -1
- package/dist/geolocation/index.d.mts +3 -3
- package/dist/geolocation/index.d.ts +3 -3
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +54 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -10
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +3 -3
- package/dist/kyc/core.d.ts +3 -3
- package/dist/kyc/core.js +54 -10
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +54 -10
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +253 -21
- package/dist/kyc/index.d.ts +253 -21
- package/dist/kyc/index.js +54 -10
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +54 -10
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +41 -6
- package/dist/react.d.ts +41 -6
- package/dist/react.js +603 -272
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +603 -272
- package/dist/react.mjs.map +1 -1
- package/dist/risk-profile/index.d.mts +3 -3
- package/dist/risk-profile/index.d.ts +3 -3
- package/dist/scores/index.d.mts +1 -1
- package/dist/scores/index.d.ts +1 -1
- package/dist/{types-C4Zx0d_u.d.mts → types-BOFaMQxI.d.mts} +1 -1
- package/dist/{types-QUCWam16.d.mts → types-CBQRNL-l.d.mts} +10 -3
- package/dist/{types-QUCWam16.d.ts → types-CBQRNL-l.d.ts} +10 -3
- package/dist/{types-2utj53GK.d.ts → types-UGyDl1fd.d.ts} +1 -1
- package/dist/webhooks/index.d.mts +1 -1
- package/dist/webhooks/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -885,277 +885,599 @@ var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id
|
|
|
885
885
|
var Upload = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNyAxN0gxNy4wMU0xNS42IDE0SDE4QzE4LjkzMTkgMTQgMTkuMzk3OCAxNCAxOS43NjU0IDE0LjE1MjJDMjAuMjU1NCAxNC4zNTUyIDIwLjY0NDggMTQuNzQ0NiAyMC44NDc4IDE1LjIzNDZDMjEgMTUuNjAyMiAyMSAxNi4wNjgxIDIxIDE3QzIxIDE3LjkzMTkgMjEgMTguMzk3OCAyMC44NDc4IDE4Ljc2NTRDMjAuNjQ0OCAxOS4yNTU0IDIwLjI1NTQgMTkuNjQ0OCAxOS43NjU0IDE5Ljg0NzhDMTkuMzk3OCAyMCAxOC45MzE5IDIwIDE4IDIwSDZDNS4wNjgxMiAyMCA0LjYwMjE4IDIwIDQuMjM0NjMgMTkuODQ3OEMzLjc0NDU4IDE5LjY0NDggMy4zNTUyMyAxOS4yNTU0IDMuMTUyMjQgMTguNzY1NEMzIDE4LjM5NzggMyAxNy45MzE5IDMgMTdDMyAxNi4wNjgxIDMgMTUuNjAyMiAzLjE1MjI0IDE1LjIzNDZDMy4zNTUyMyAxNC43NDQ2IDMuNzQ0NTggMTQuMzU1MiA0LjIzNDYzIDE0LjE1MjJDNC42MDIxOCAxNCA1LjA2ODEyIDE0IDYgMTRIOC40TTEyIDE1VjRNMTIgNEwxNSA3TTEyIDRMOSA3IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+DQo8L3N2Zz4=";
|
|
886
886
|
|
|
887
887
|
// src/kyc/FaceCaptureModal.tsx
|
|
888
|
-
var
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
888
|
+
var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
889
|
+
var LIVENESS_MESSAGES = [
|
|
890
|
+
"Align your face in the oval",
|
|
891
|
+
"Face too far. Please, move closer to the camera",
|
|
892
|
+
"Looking good! Stay still...",
|
|
893
|
+
"Processing liveness..."
|
|
894
|
+
];
|
|
895
|
+
function headerSubtitle(stageKind) {
|
|
896
|
+
switch (stageKind) {
|
|
897
|
+
case "qr":
|
|
898
|
+
return "Use your phone to continue";
|
|
899
|
+
case "accepted":
|
|
900
|
+
return "All set";
|
|
901
|
+
case "declined":
|
|
902
|
+
return "Verification didn't match \u2014 try once more";
|
|
903
|
+
case "max_attempts":
|
|
904
|
+
return "We couldn't verify you";
|
|
905
|
+
default:
|
|
906
|
+
return "Please capture a clear photo of your face";
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function defaultRenderQR(payload) {
|
|
910
|
+
const url = `https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=${encodeURIComponent(payload)}`;
|
|
911
|
+
return /* @__PURE__ */ React.createElement("img", { src: url, alt: "QR code", width: 220, height: 220 });
|
|
912
|
+
}
|
|
896
913
|
function FaceCaptureModal({
|
|
897
|
-
|
|
898
|
-
|
|
914
|
+
client,
|
|
915
|
+
session,
|
|
916
|
+
onComplete,
|
|
917
|
+
defaultDevice,
|
|
918
|
+
renderQR
|
|
899
919
|
}) {
|
|
900
|
-
const
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
920
|
+
const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
|
|
921
|
+
const initialStage = (() => {
|
|
922
|
+
const choice = defaultDevice ?? (isMobile ? "this" : "ask");
|
|
923
|
+
if (choice === "this") return { kind: "capture" };
|
|
924
|
+
if (choice === "mobile") return { kind: "qr", mobileConnected: false };
|
|
925
|
+
return { kind: "choose" };
|
|
926
|
+
})();
|
|
927
|
+
const [stage, setStage] = useState(initialStage);
|
|
928
|
+
const [attempts, setAttempts] = useState(session.attempts);
|
|
929
|
+
const maxAttempts = session.max_attempts || 1;
|
|
930
|
+
const [qrDeclinedReason, setQrDeclinedReason] = useState(null);
|
|
931
|
+
const [captureMode, setCaptureMode] = useState("idle");
|
|
932
|
+
const [capturedPreview, setCapturedPreview] = useState(null);
|
|
933
|
+
const [capturedBase64, setCapturedBase64] = useState(null);
|
|
934
|
+
const [livenessMessage, setLivenessMessage] = useState(LIVENESS_MESSAGES[0]);
|
|
935
|
+
const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
|
|
936
|
+
const cancelledRef = useRef(false);
|
|
937
|
+
const submittingRef = useRef(false);
|
|
938
|
+
useEffect(() => {
|
|
939
|
+
if (stage.kind !== "qr") return;
|
|
940
|
+
let stopped = false;
|
|
941
|
+
const intervalMs = 2e3;
|
|
942
|
+
const deadline = Date.now() + 15 * 60 * 1e3;
|
|
943
|
+
let mobileSeen = stage.mobileConnected;
|
|
944
|
+
const tick = async () => {
|
|
945
|
+
if (stopped || cancelledRef.current) return;
|
|
946
|
+
try {
|
|
947
|
+
if (mobileSeen) {
|
|
948
|
+
const result = await client.getReuseKycSessionStatus(session.reference);
|
|
949
|
+
if (result.status === "accepted") {
|
|
950
|
+
setStage({ kind: "accepted", result });
|
|
951
|
+
stopped = true;
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (result.status === "declined") {
|
|
955
|
+
setAttempts(maxAttempts - (result.data?.retries_remaining ?? 0));
|
|
956
|
+
if (result.data?.retry_limit_exceeded) {
|
|
957
|
+
setStage({ kind: "max_attempts", result });
|
|
958
|
+
stopped = true;
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (result.declined_reason) {
|
|
962
|
+
setQrDeclinedReason(result.declined_reason);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
} else {
|
|
966
|
+
const handoff = await client.getHandoffSession(session.token);
|
|
967
|
+
if (handoff.mobile_connected) {
|
|
968
|
+
mobileSeen = true;
|
|
969
|
+
setStage({ kind: "qr", mobileConnected: true });
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
} catch {
|
|
973
|
+
}
|
|
974
|
+
if (Date.now() < deadline) {
|
|
975
|
+
setTimeout(tick, intervalMs);
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
tick();
|
|
979
|
+
return () => {
|
|
980
|
+
stopped = true;
|
|
981
|
+
};
|
|
982
|
+
}, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
|
|
983
|
+
const applyResult = (result) => {
|
|
984
|
+
if (result.status === "accepted") {
|
|
985
|
+
setStage({ kind: "accepted", result });
|
|
986
|
+
return true;
|
|
918
987
|
}
|
|
988
|
+
if (result.status === "declined") {
|
|
989
|
+
const nextAttempts = maxAttempts - (result.data?.retries_remaining ?? 0);
|
|
990
|
+
setAttempts(nextAttempts);
|
|
991
|
+
setCapturedPreview(null);
|
|
992
|
+
setCapturedBase64(null);
|
|
993
|
+
if (result.data?.retry_limit_exceeded) {
|
|
994
|
+
setStage({ kind: "max_attempts", result });
|
|
995
|
+
} else {
|
|
996
|
+
setStage({ kind: "declined", result });
|
|
997
|
+
}
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
return false;
|
|
919
1001
|
};
|
|
920
|
-
const
|
|
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
|
-
const visualGuideStyle = {
|
|
979
|
-
position: "relative"
|
|
980
|
-
};
|
|
981
|
-
const circleStyle = {
|
|
982
|
-
width: "128px",
|
|
983
|
-
height: "128px",
|
|
984
|
-
borderRadius: "50%",
|
|
985
|
-
background: "linear-gradient(135deg, rgba(0, 188, 125, 0.2) 0%, rgba(0, 188, 125, 0.05) 100%)",
|
|
986
|
-
display: "flex",
|
|
987
|
-
alignItems: "center",
|
|
988
|
-
justifyContent: "center"
|
|
989
|
-
};
|
|
990
|
-
const badgeStyle = {
|
|
991
|
-
position: "absolute",
|
|
992
|
-
top: "-4px",
|
|
993
|
-
right: "-4px",
|
|
994
|
-
width: "32px",
|
|
995
|
-
height: "32px",
|
|
996
|
-
background: "#ffffff",
|
|
997
|
-
borderRadius: "50%",
|
|
998
|
-
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
|
999
|
-
display: "flex",
|
|
1000
|
-
alignItems: "center",
|
|
1001
|
-
justifyContent: "center"
|
|
1002
|
-
};
|
|
1003
|
-
const instructionsBoxStyle = {
|
|
1004
|
-
background: "linear-gradient(135deg, #f9fafb 0%, rgba(243, 244, 246, 0.5) 100%)",
|
|
1005
|
-
borderRadius: "12px",
|
|
1006
|
-
padding: "16px",
|
|
1007
|
-
marginBottom: "24px"
|
|
1008
|
-
};
|
|
1009
|
-
const instructionsTitleStyle = {
|
|
1010
|
-
fontSize: "14px",
|
|
1011
|
-
fontWeight: 500,
|
|
1012
|
-
color: "#111827",
|
|
1013
|
-
marginBottom: "8px"
|
|
1014
|
-
};
|
|
1015
|
-
const instructionsListStyle = {
|
|
1016
|
-
fontSize: "12px",
|
|
1017
|
-
color: "#6b7280",
|
|
1018
|
-
listStyle: "none",
|
|
1019
|
-
padding: 0,
|
|
1020
|
-
margin: 0
|
|
1021
|
-
};
|
|
1022
|
-
const instructionItemStyle = {
|
|
1023
|
-
display: "flex",
|
|
1024
|
-
alignItems: "flex-start",
|
|
1025
|
-
marginBottom: "6px"
|
|
1026
|
-
};
|
|
1027
|
-
const bulletStyle = {
|
|
1028
|
-
color: "#00bc7d",
|
|
1029
|
-
marginRight: "8px",
|
|
1030
|
-
flexShrink: 0
|
|
1031
|
-
};
|
|
1032
|
-
const buttonsContainerStyle = {
|
|
1033
|
-
display: "flex",
|
|
1034
|
-
flexDirection: "column",
|
|
1035
|
-
gap: "12px"
|
|
1002
|
+
const runSubmit = async (proof) => {
|
|
1003
|
+
if (submittingRef.current) return;
|
|
1004
|
+
submittingRef.current = true;
|
|
1005
|
+
setStage({ kind: "submitting" });
|
|
1006
|
+
let settled = false;
|
|
1007
|
+
const ref = session.reference;
|
|
1008
|
+
try {
|
|
1009
|
+
const postPromise = (async () => {
|
|
1010
|
+
try {
|
|
1011
|
+
const data = await client.submitReuseKycSession({
|
|
1012
|
+
token: session.token,
|
|
1013
|
+
reference: ref,
|
|
1014
|
+
proof
|
|
1015
|
+
});
|
|
1016
|
+
return { ok: true, data };
|
|
1017
|
+
} catch (err2) {
|
|
1018
|
+
return { ok: false, error: err2 };
|
|
1019
|
+
}
|
|
1020
|
+
})();
|
|
1021
|
+
const pollPromise = (async () => {
|
|
1022
|
+
await new Promise((r) => setTimeout(r, 12e3));
|
|
1023
|
+
const deadline = Date.now() + 12e4;
|
|
1024
|
+
while (Date.now() < deadline) {
|
|
1025
|
+
if (settled || cancelledRef.current) return null;
|
|
1026
|
+
try {
|
|
1027
|
+
const r = await client.getReuseKycSessionStatus(ref);
|
|
1028
|
+
if (r.status === "accepted" || r.status === "declined") return r;
|
|
1029
|
+
} catch {
|
|
1030
|
+
}
|
|
1031
|
+
if (settled || cancelledRef.current) return null;
|
|
1032
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
1033
|
+
}
|
|
1034
|
+
return null;
|
|
1035
|
+
})();
|
|
1036
|
+
const winner = await Promise.race([
|
|
1037
|
+
postPromise.then(
|
|
1038
|
+
(r) => r.ok ? { from: "post", data: r.data } : { from: "post_err" }
|
|
1039
|
+
),
|
|
1040
|
+
pollPromise.then(
|
|
1041
|
+
(data) => data ? { from: "poll", data } : { from: "poll_done" }
|
|
1042
|
+
)
|
|
1043
|
+
]);
|
|
1044
|
+
settled = true;
|
|
1045
|
+
if (winner.from === "post" && applyResult(winner.data)) return;
|
|
1046
|
+
if (winner.from === "poll" && applyResult(winner.data)) return;
|
|
1047
|
+
const [postResult, pollResult] = await Promise.all([postPromise, pollPromise]);
|
|
1048
|
+
if (postResult.ok && applyResult(postResult.data)) return;
|
|
1049
|
+
if (pollResult && applyResult(pollResult)) return;
|
|
1050
|
+
const err = postResult.ok ? null : postResult.error;
|
|
1051
|
+
setStage({
|
|
1052
|
+
kind: "error",
|
|
1053
|
+
message: err instanceof Error ? err.message : "Submission failed. Please try again."
|
|
1054
|
+
});
|
|
1055
|
+
} finally {
|
|
1056
|
+
settled = true;
|
|
1057
|
+
submittingRef.current = false;
|
|
1058
|
+
}
|
|
1036
1059
|
};
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
padding: "14px 24px",
|
|
1043
|
-
borderRadius: "12px",
|
|
1044
|
-
border: "none",
|
|
1045
|
-
cursor: isProcessing ? "not-allowed" : "pointer",
|
|
1046
|
-
transition: "all 0.2s",
|
|
1047
|
-
display: "flex",
|
|
1048
|
-
alignItems: "center",
|
|
1049
|
-
justifyContent: "center",
|
|
1050
|
-
gap: "12px",
|
|
1051
|
-
boxShadow: isProcessing ? "none" : isHoveringPrimary ? "0 20px 25px -5px rgba(0, 188, 125, 0.3)" : "0 10px 15px -3px rgba(0, 188, 125, 0.2)",
|
|
1052
|
-
fontSize: "16px"
|
|
1060
|
+
const handleCameraCapture = (dataUrl) => {
|
|
1061
|
+
const raw = dataUrl.split(",")[1] ?? "";
|
|
1062
|
+
setCapturedPreview(dataUrl);
|
|
1063
|
+
setCapturedBase64(raw);
|
|
1064
|
+
setCaptureMode("preview");
|
|
1053
1065
|
};
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
padding: "14px 24px",
|
|
1060
|
-
borderRadius: "12px",
|
|
1061
|
-
border: "2px solid #e5e7eb",
|
|
1062
|
-
cursor: isProcessing ? "not-allowed" : "pointer",
|
|
1063
|
-
transition: "all 0.2s",
|
|
1064
|
-
fontSize: "16px"
|
|
1066
|
+
const handleCameraCancel = () => setCaptureMode("idle");
|
|
1067
|
+
const handleRetake = () => {
|
|
1068
|
+
setCapturedPreview(null);
|
|
1069
|
+
setCapturedBase64(null);
|
|
1070
|
+
setCaptureMode("live");
|
|
1065
1071
|
};
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1072
|
+
const handleConfirmSubmit = () => {
|
|
1073
|
+
if (!capturedBase64) return;
|
|
1074
|
+
void runSubmit(capturedBase64);
|
|
1068
1075
|
};
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
color: "#9ca3af",
|
|
1073
|
-
margin: 0
|
|
1076
|
+
const handleOpenCamera = () => {
|
|
1077
|
+
if (submittingRef.current) return;
|
|
1078
|
+
setCaptureMode("live");
|
|
1074
1079
|
};
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
borderRadius: "50%",
|
|
1081
|
-
animation: "spin 0.6s linear infinite"
|
|
1080
|
+
const handleRetakeAfterDecline = () => {
|
|
1081
|
+
setCapturedPreview(null);
|
|
1082
|
+
setCapturedBase64(null);
|
|
1083
|
+
setCaptureMode("live");
|
|
1084
|
+
setStage({ kind: "capture" });
|
|
1082
1085
|
};
|
|
1083
|
-
|
|
1084
|
-
|
|
1086
|
+
useEffect(() => {
|
|
1087
|
+
if (captureMode !== "live") return;
|
|
1088
|
+
setLivenessMessage(LIVENESS_MESSAGES[0]);
|
|
1089
|
+
let i = 0;
|
|
1090
|
+
const interval = setInterval(() => {
|
|
1091
|
+
i = (i + 1) % LIVENESS_MESSAGES.length;
|
|
1092
|
+
setLivenessMessage(LIVENESS_MESSAGES[i]);
|
|
1093
|
+
}, 3e3);
|
|
1094
|
+
return () => clearInterval(interval);
|
|
1095
|
+
}, [captureMode]);
|
|
1096
|
+
const close = (result) => {
|
|
1097
|
+
cancelledRef.current = true;
|
|
1098
|
+
onComplete(result);
|
|
1085
1099
|
};
|
|
1086
|
-
const
|
|
1087
|
-
@keyframes fadeIn {
|
|
1088
|
-
from {
|
|
1089
|
-
opacity: 0;
|
|
1090
|
-
}
|
|
1091
|
-
to {
|
|
1092
|
-
opacity: 1;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
@keyframes zoomIn {
|
|
1097
|
-
from {
|
|
1098
|
-
opacity: 0;
|
|
1099
|
-
transform: scale(0.95);
|
|
1100
|
-
}
|
|
1101
|
-
to {
|
|
1102
|
-
opacity: 1;
|
|
1103
|
-
transform: scale(1);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
@keyframes spin {
|
|
1108
|
-
from {
|
|
1109
|
-
transform: rotate(0deg);
|
|
1110
|
-
}
|
|
1111
|
-
to {
|
|
1112
|
-
transform: rotate(360deg);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
`);
|
|
1116
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, styleTag, /* @__PURE__ */ React.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React.createElement("div", { style: modalStyle }, /* @__PURE__ */ React.createElement("div", { style: headerStyle }, /* @__PURE__ */ React.createElement(
|
|
1100
|
+
const renderChoose = () => /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React.createElement("div", { style: circleStyle }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })))), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "How would you like to verify?"), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement(
|
|
1117
1101
|
"button",
|
|
1118
1102
|
{
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1103
|
+
style: primaryButtonStyle,
|
|
1104
|
+
onClick: () => setStage({ kind: "capture" })
|
|
1105
|
+
},
|
|
1106
|
+
/* @__PURE__ */ React.createElement("img", { src: isMobile ? Camera : Upload, alt: "", width: 20, height: 20 }),
|
|
1107
|
+
/* @__PURE__ */ React.createElement("span", null, "Continue on this device")
|
|
1108
|
+
), /* @__PURE__ */ React.createElement(
|
|
1109
|
+
"button",
|
|
1110
|
+
{
|
|
1111
|
+
style: secondaryButtonStyle,
|
|
1112
|
+
onClick: () => setStage({ kind: "qr", mobileConnected: false })
|
|
1124
1113
|
},
|
|
1125
|
-
|
|
1126
|
-
), /* @__PURE__ */ React.createElement("
|
|
1127
|
-
|
|
1114
|
+
"Continue on mobile (scan QR)"
|
|
1115
|
+
), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
|
|
1116
|
+
const renderQRStage = (mobileConnected) => /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: qrBoxStyle }, (renderQR ?? defaultRenderQR)(qrPayload)), /* @__PURE__ */ React.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.createElement("div", { style: { ...spinnerStyle, margin: "12px auto" } }), qrDeclinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Last attempt was declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, qrDeclinedReason), /* @__PURE__ */ React.createElement("p", { style: { ...alertTextStyle, marginTop: 4 } }, "Try again on your phone. Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts, ".")), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement(
|
|
1117
|
+
"button",
|
|
1128
1118
|
{
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1119
|
+
style: secondaryButtonStyle,
|
|
1120
|
+
onClick: () => setStage({ kind: "capture" })
|
|
1121
|
+
},
|
|
1122
|
+
"Use this device instead"
|
|
1123
|
+
), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
|
|
1124
|
+
const renderCapture = (declinedReason) => {
|
|
1125
|
+
if (captureMode === "live") {
|
|
1126
|
+
return /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement(
|
|
1127
|
+
LiveCamera,
|
|
1128
|
+
{
|
|
1129
|
+
onCapture: handleCameraCapture,
|
|
1130
|
+
onCancel: handleCameraCancel,
|
|
1131
|
+
message: livenessMessage
|
|
1132
|
+
}
|
|
1133
|
+
), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1134
|
+
}
|
|
1135
|
+
if (captureMode === "preview" && capturedPreview) {
|
|
1136
|
+
return /* @__PURE__ */ React.createElement("div", { style: contentStyle }, declinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "Looks good? Submit this selfie or retake it."), /* @__PURE__ */ React.createElement("div", { style: previewBoxStyle }, /* @__PURE__ */ React.createElement("img", { src: capturedPreview, alt: "Captured selfie preview", style: previewImgStyle })), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleConfirmSubmit }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, "Submit")), /* @__PURE__ */ React.createElement("button", { style: secondaryButtonStyle, onClick: handleRetake }, "Retake"), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1136
1137
|
}
|
|
1137
|
-
|
|
1138
|
+
return /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React.createElement("div", { style: circleStyle }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React.createElement("div", { style: badgeStyle }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, declinedReason ? "Try again" : "Open Camera")), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
|
|
1139
|
+
};
|
|
1140
|
+
const renderSubmitting = () => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "Verifying your photo\u2026"), /* @__PURE__ */ React.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
|
|
1141
|
+
const renderAccepted = (result) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: successCircleStyle }, /* @__PURE__ */ React.createElement("img", { src: Done, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Verified"), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, "Your identity has been confirmed. You can continue."), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Continue")));
|
|
1142
|
+
const renderDeclined = (result) => /* @__PURE__ */ React.createElement("div", { style: contentStyle }, /* @__PURE__ */ React.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React.createElement("span", null, "Retake")), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
|
|
1143
|
+
const renderMaxAttempts = (result) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Maximum attempts reached"), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React.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.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
|
|
1144
|
+
const renderError = (message) => /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Something went wrong"), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, message), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try again"), /* @__PURE__ */ React.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
|
|
1145
|
+
const body = (() => {
|
|
1146
|
+
switch (stage.kind) {
|
|
1147
|
+
case "choose":
|
|
1148
|
+
return renderChoose();
|
|
1149
|
+
case "qr":
|
|
1150
|
+
return renderQRStage(stage.mobileConnected);
|
|
1151
|
+
case "capture":
|
|
1152
|
+
return renderCapture(stage.declinedReason);
|
|
1153
|
+
case "submitting":
|
|
1154
|
+
return renderSubmitting();
|
|
1155
|
+
case "accepted":
|
|
1156
|
+
return renderAccepted(stage.result);
|
|
1157
|
+
case "declined":
|
|
1158
|
+
return renderDeclined(stage.result);
|
|
1159
|
+
case "max_attempts":
|
|
1160
|
+
return renderMaxAttempts(stage.result);
|
|
1161
|
+
case "error":
|
|
1162
|
+
return renderError(stage.message);
|
|
1163
|
+
}
|
|
1164
|
+
})();
|
|
1165
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("style", null, keyframes), /* @__PURE__ */ React.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React.createElement("div", { style: modalStyle }, /* @__PURE__ */ React.createElement("div", { style: headerStyle }, /* @__PURE__ */ React.createElement(
|
|
1138
1166
|
"button",
|
|
1139
1167
|
{
|
|
1140
|
-
onClick: () =>
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
disabled: isProcessing,
|
|
1144
|
-
style: primaryButtonStyle
|
|
1168
|
+
onClick: () => close(null),
|
|
1169
|
+
style: closeButtonStyle,
|
|
1170
|
+
"aria-label": "Close"
|
|
1145
1171
|
},
|
|
1146
|
-
|
|
1147
|
-
), /* @__PURE__ */ React.createElement(
|
|
1172
|
+
/* @__PURE__ */ React.createElement("img", { src: Close, alt: "", width: 16, height: 16 })
|
|
1173
|
+
), /* @__PURE__ */ React.createElement("h2", { style: titleStyle }, "Face Verification"), /* @__PURE__ */ React.createElement("p", { style: subtitleStyle }, headerSubtitle(stage.kind))), body, /* @__PURE__ */ React.createElement("div", { style: footerStyle }, /* @__PURE__ */ React.createElement("p", { style: footerTextStyle }, "Your photo is securely processed and used only for verification.")))));
|
|
1174
|
+
}
|
|
1175
|
+
var keyframes = `
|
|
1176
|
+
@keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
|
|
1177
|
+
@keyframes zoomIn { from { transform: scale(.96); opacity: 0 } to { transform: scale(1); opacity: 1 } }
|
|
1178
|
+
@keyframes spin { to { transform: rotate(360deg) } }
|
|
1179
|
+
`;
|
|
1180
|
+
var overlayStyle = {
|
|
1181
|
+
position: "fixed",
|
|
1182
|
+
inset: 0,
|
|
1183
|
+
background: "rgba(0,0,0,.6)",
|
|
1184
|
+
backdropFilter: "blur(4px)",
|
|
1185
|
+
display: "flex",
|
|
1186
|
+
alignItems: "center",
|
|
1187
|
+
justifyContent: "center",
|
|
1188
|
+
zIndex: 999999,
|
|
1189
|
+
padding: 16,
|
|
1190
|
+
animation: "fadeIn .2s ease-out"
|
|
1191
|
+
};
|
|
1192
|
+
var modalStyle = {
|
|
1193
|
+
background: "#fff",
|
|
1194
|
+
borderRadius: 16,
|
|
1195
|
+
boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
|
|
1196
|
+
width: "100%",
|
|
1197
|
+
maxWidth: 448,
|
|
1198
|
+
animation: "zoomIn .2s ease-out"
|
|
1199
|
+
};
|
|
1200
|
+
var headerStyle = {
|
|
1201
|
+
position: "relative",
|
|
1202
|
+
padding: "24px 24px 16px",
|
|
1203
|
+
borderBottom: "1px solid #f3f4f6"
|
|
1204
|
+
};
|
|
1205
|
+
var closeButtonStyle = {
|
|
1206
|
+
position: "absolute",
|
|
1207
|
+
top: 16,
|
|
1208
|
+
right: 16,
|
|
1209
|
+
padding: 8,
|
|
1210
|
+
background: "transparent",
|
|
1211
|
+
border: "none",
|
|
1212
|
+
borderRadius: 8,
|
|
1213
|
+
cursor: "pointer"
|
|
1214
|
+
};
|
|
1215
|
+
var titleStyle = { margin: "0 0 4px", fontSize: 18, fontWeight: 600, color: "#111827" };
|
|
1216
|
+
var subtitleStyle = { margin: 0, fontSize: 14, color: "#6b7280" };
|
|
1217
|
+
var contentStyle = {
|
|
1218
|
+
padding: 24,
|
|
1219
|
+
display: "flex",
|
|
1220
|
+
flexDirection: "column",
|
|
1221
|
+
gap: 16
|
|
1222
|
+
};
|
|
1223
|
+
var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
|
|
1224
|
+
var visualGuideStyle = { position: "relative" };
|
|
1225
|
+
var circleStyle = {
|
|
1226
|
+
width: 96,
|
|
1227
|
+
height: 96,
|
|
1228
|
+
borderRadius: "50%",
|
|
1229
|
+
background: "linear-gradient(135deg, rgba(0, 188, 125, 0.2) 0%, rgba(0, 188, 125, 0.05) 100%)",
|
|
1230
|
+
display: "flex",
|
|
1231
|
+
alignItems: "center",
|
|
1232
|
+
justifyContent: "center"
|
|
1233
|
+
};
|
|
1234
|
+
var badgeStyle = {
|
|
1235
|
+
position: "absolute",
|
|
1236
|
+
bottom: -4,
|
|
1237
|
+
right: -4,
|
|
1238
|
+
width: 28,
|
|
1239
|
+
height: 28,
|
|
1240
|
+
borderRadius: "50%",
|
|
1241
|
+
background: "#ffffff",
|
|
1242
|
+
border: "2px solid #00bc7d",
|
|
1243
|
+
display: "flex",
|
|
1244
|
+
alignItems: "center",
|
|
1245
|
+
justifyContent: "center"
|
|
1246
|
+
};
|
|
1247
|
+
var successCircleStyle = {
|
|
1248
|
+
...circleStyle,
|
|
1249
|
+
background: "linear-gradient(135deg, rgba(0, 188, 125, 0.25) 0%, rgba(0, 188, 125, 0.1) 100%)",
|
|
1250
|
+
margin: "0 auto"
|
|
1251
|
+
};
|
|
1252
|
+
var errorCircleStyle = {
|
|
1253
|
+
width: 64,
|
|
1254
|
+
height: 64,
|
|
1255
|
+
borderRadius: "50%",
|
|
1256
|
+
background: "#fee2e2",
|
|
1257
|
+
color: "#b91c1c",
|
|
1258
|
+
fontSize: 32,
|
|
1259
|
+
fontWeight: 700,
|
|
1260
|
+
display: "flex",
|
|
1261
|
+
alignItems: "center",
|
|
1262
|
+
justifyContent: "center",
|
|
1263
|
+
margin: "0 auto"
|
|
1264
|
+
};
|
|
1265
|
+
var qrBoxStyle = {
|
|
1266
|
+
display: "flex",
|
|
1267
|
+
justifyContent: "center",
|
|
1268
|
+
padding: 12,
|
|
1269
|
+
background: "linear-gradient(135deg, #f9fafb 0%, rgba(243, 244, 246, 0.5) 100%)",
|
|
1270
|
+
borderRadius: 12
|
|
1271
|
+
};
|
|
1272
|
+
var alertBoxStyle = {
|
|
1273
|
+
background: "#fef3c7",
|
|
1274
|
+
border: "1px solid #fde68a",
|
|
1275
|
+
color: "#92400e",
|
|
1276
|
+
borderRadius: 8,
|
|
1277
|
+
padding: 12,
|
|
1278
|
+
fontSize: 14
|
|
1279
|
+
};
|
|
1280
|
+
var alertTextStyle = { margin: "4px 0 0", fontSize: 13, color: "#92400e" };
|
|
1281
|
+
var instructionsBoxStyle = {
|
|
1282
|
+
background: "linear-gradient(135deg, #f9fafb 0%, rgba(243, 244, 246, 0.5) 100%)",
|
|
1283
|
+
borderRadius: 8,
|
|
1284
|
+
padding: 16
|
|
1285
|
+
};
|
|
1286
|
+
var instructionsTitleStyle = { margin: "0 0 8px", fontSize: 14, fontWeight: 600, color: "#374151" };
|
|
1287
|
+
var instructionsListStyle = { margin: 0, padding: 0, listStyle: "none" };
|
|
1288
|
+
var instructionItemStyle = { display: "flex", gap: 8, fontSize: 13, color: "#4b5563", lineHeight: 1.5 };
|
|
1289
|
+
var bulletStyle = { color: "#00bc7d" };
|
|
1290
|
+
var previewBoxStyle = {
|
|
1291
|
+
borderRadius: 12,
|
|
1292
|
+
overflow: "hidden",
|
|
1293
|
+
border: "2px solid rgba(0, 188, 125, 0.4)",
|
|
1294
|
+
background: "#0f172a",
|
|
1295
|
+
display: "flex",
|
|
1296
|
+
justifyContent: "center"
|
|
1297
|
+
};
|
|
1298
|
+
var previewImgStyle = {
|
|
1299
|
+
width: "100%",
|
|
1300
|
+
maxHeight: 320,
|
|
1301
|
+
objectFit: "contain",
|
|
1302
|
+
background: "#0f172a"
|
|
1303
|
+
};
|
|
1304
|
+
var buttonsContainerStyle = {
|
|
1305
|
+
display: "flex",
|
|
1306
|
+
flexDirection: "column",
|
|
1307
|
+
gap: 8
|
|
1308
|
+
};
|
|
1309
|
+
var primaryButtonStyle = {
|
|
1310
|
+
display: "flex",
|
|
1311
|
+
alignItems: "center",
|
|
1312
|
+
justifyContent: "center",
|
|
1313
|
+
gap: 8,
|
|
1314
|
+
padding: "12px 16px",
|
|
1315
|
+
background: "#00bc7d",
|
|
1316
|
+
color: "#ffffff",
|
|
1317
|
+
border: "none",
|
|
1318
|
+
borderRadius: 8,
|
|
1319
|
+
fontSize: 14,
|
|
1320
|
+
fontWeight: 600,
|
|
1321
|
+
cursor: "pointer"
|
|
1322
|
+
};
|
|
1323
|
+
var secondaryButtonStyle = {
|
|
1324
|
+
...primaryButtonStyle,
|
|
1325
|
+
background: "#ffffff",
|
|
1326
|
+
color: "#374151",
|
|
1327
|
+
border: "2px solid #e5e7eb"
|
|
1328
|
+
};
|
|
1329
|
+
var cancelButtonStyle = {
|
|
1330
|
+
...primaryButtonStyle,
|
|
1331
|
+
background: "transparent",
|
|
1332
|
+
color: "#6b7280",
|
|
1333
|
+
border: "none"
|
|
1334
|
+
};
|
|
1335
|
+
var spinnerStyle = {
|
|
1336
|
+
width: 16,
|
|
1337
|
+
height: 16,
|
|
1338
|
+
border: "2px solid rgba(0, 188, 125, 0.25)",
|
|
1339
|
+
borderTopColor: "#00bc7d",
|
|
1340
|
+
borderRadius: "50%",
|
|
1341
|
+
animation: "spin .8s linear infinite"
|
|
1342
|
+
};
|
|
1343
|
+
var attemptsTextStyle = {
|
|
1344
|
+
margin: "8px 0 0",
|
|
1345
|
+
fontSize: 12,
|
|
1346
|
+
color: "#9ca3af",
|
|
1347
|
+
textAlign: "center"
|
|
1348
|
+
};
|
|
1349
|
+
var footerStyle = { padding: "12px 24px 16px", borderTop: "1px solid #f3f4f6" };
|
|
1350
|
+
var footerTextStyle = { margin: 0, fontSize: 12, color: "#9ca3af", textAlign: "center" };
|
|
1351
|
+
function LiveCamera({ onCapture, onCancel, message }) {
|
|
1352
|
+
const videoRef = useRef(null);
|
|
1353
|
+
const streamRef = useRef(null);
|
|
1354
|
+
const [ready, setReady] = useState(false);
|
|
1355
|
+
const [error, setError] = useState(null);
|
|
1356
|
+
useEffect(() => {
|
|
1357
|
+
let cancelled = false;
|
|
1358
|
+
async function init() {
|
|
1359
|
+
try {
|
|
1360
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
1361
|
+
throw new Error("Camera is not available in this browser");
|
|
1362
|
+
}
|
|
1363
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
1364
|
+
video: {
|
|
1365
|
+
facingMode: "user",
|
|
1366
|
+
width: { ideal: 1280 },
|
|
1367
|
+
height: { ideal: 1280 }
|
|
1368
|
+
},
|
|
1369
|
+
audio: false
|
|
1370
|
+
});
|
|
1371
|
+
if (cancelled) {
|
|
1372
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
streamRef.current = stream;
|
|
1376
|
+
const video = videoRef.current;
|
|
1377
|
+
if (video) {
|
|
1378
|
+
video.srcObject = stream;
|
|
1379
|
+
video.onloadedmetadata = () => {
|
|
1380
|
+
video.play().catch(() => {
|
|
1381
|
+
});
|
|
1382
|
+
setReady(true);
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
setError(err instanceof Error ? err.message : "Unable to access camera");
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
init();
|
|
1390
|
+
return () => {
|
|
1391
|
+
cancelled = true;
|
|
1392
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
1393
|
+
streamRef.current = null;
|
|
1394
|
+
};
|
|
1395
|
+
}, []);
|
|
1396
|
+
const capture = () => {
|
|
1397
|
+
const video = videoRef.current;
|
|
1398
|
+
if (!video || !video.videoWidth) return;
|
|
1399
|
+
const size = Math.min(video.videoWidth, video.videoHeight);
|
|
1400
|
+
const sx = (video.videoWidth - size) / 2;
|
|
1401
|
+
const sy = (video.videoHeight - size) / 2;
|
|
1402
|
+
const canvas = document.createElement("canvas");
|
|
1403
|
+
canvas.width = size;
|
|
1404
|
+
canvas.height = size;
|
|
1405
|
+
const ctx = canvas.getContext("2d");
|
|
1406
|
+
if (!ctx) return;
|
|
1407
|
+
ctx.translate(size, 0);
|
|
1408
|
+
ctx.scale(-1, 1);
|
|
1409
|
+
ctx.drawImage(video, sx, sy, size, size, 0, 0, size, size);
|
|
1410
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
1411
|
+
streamRef.current = null;
|
|
1412
|
+
onCapture(canvas.toDataURL("image/jpeg", 0.9));
|
|
1413
|
+
};
|
|
1414
|
+
if (error) {
|
|
1415
|
+
return /* @__PURE__ */ React.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center", padding: 0 } }, /* @__PURE__ */ React.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React.createElement("h3", { style: titleStyle }, "Camera unavailable"), /* @__PURE__ */ React.createElement("p", { style: alertTextStyle }, error), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Back")));
|
|
1416
|
+
}
|
|
1417
|
+
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement("div", { style: videoFrameStyle }, /* @__PURE__ */ React.createElement(
|
|
1418
|
+
"video",
|
|
1419
|
+
{
|
|
1420
|
+
ref: videoRef,
|
|
1421
|
+
playsInline: true,
|
|
1422
|
+
muted: true,
|
|
1423
|
+
autoPlay: true,
|
|
1424
|
+
style: videoStyle
|
|
1425
|
+
}
|
|
1426
|
+
), /* @__PURE__ */ React.createElement("div", { style: faceOvalOverlayStyle, "aria-hidden": "true" }), !ready && /* @__PURE__ */ React.createElement("div", { style: videoLoadingStyle }, "Starting camera\u2026")), message && /* @__PURE__ */ React.createElement("p", { style: livenessMessageStyle }, message), /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React.createElement(
|
|
1148
1427
|
"button",
|
|
1149
1428
|
{
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
disabled: isProcessing,
|
|
1154
|
-
style: cancelButtonStyle
|
|
1429
|
+
style: ready ? primaryButtonStyle : { ...primaryButtonStyle, opacity: 0.5, cursor: "not-allowed" },
|
|
1430
|
+
onClick: capture,
|
|
1431
|
+
disabled: !ready
|
|
1155
1432
|
},
|
|
1156
|
-
"
|
|
1157
|
-
|
|
1433
|
+
/* @__PURE__ */ React.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }),
|
|
1434
|
+
/* @__PURE__ */ React.createElement("span", null, "Capture")
|
|
1435
|
+
), /* @__PURE__ */ React.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Cancel")));
|
|
1158
1436
|
}
|
|
1437
|
+
var videoFrameStyle = {
|
|
1438
|
+
position: "relative",
|
|
1439
|
+
width: "100%",
|
|
1440
|
+
aspectRatio: "1 / 1",
|
|
1441
|
+
borderRadius: 12,
|
|
1442
|
+
overflow: "hidden",
|
|
1443
|
+
background: "#0f172a"
|
|
1444
|
+
};
|
|
1445
|
+
var videoStyle = {
|
|
1446
|
+
width: "100%",
|
|
1447
|
+
height: "100%",
|
|
1448
|
+
objectFit: "cover",
|
|
1449
|
+
transform: "scaleX(-1)"
|
|
1450
|
+
// mirror so the preview matches the user's POV
|
|
1451
|
+
};
|
|
1452
|
+
var faceOvalOverlayStyle = {
|
|
1453
|
+
position: "absolute",
|
|
1454
|
+
inset: 0,
|
|
1455
|
+
pointerEvents: "none",
|
|
1456
|
+
background: "radial-gradient(ellipse 38% 50% at center, transparent 60%, rgba(15, 23, 42, 0.55) 62%)",
|
|
1457
|
+
border: "2px solid rgba(0, 188, 125, 0.6)",
|
|
1458
|
+
borderRadius: 12
|
|
1459
|
+
};
|
|
1460
|
+
var videoLoadingStyle = {
|
|
1461
|
+
position: "absolute",
|
|
1462
|
+
inset: 0,
|
|
1463
|
+
display: "flex",
|
|
1464
|
+
alignItems: "center",
|
|
1465
|
+
justifyContent: "center",
|
|
1466
|
+
color: "#e5e7eb",
|
|
1467
|
+
fontSize: 14,
|
|
1468
|
+
fontWeight: 500
|
|
1469
|
+
};
|
|
1470
|
+
var livenessMessageStyle = {
|
|
1471
|
+
margin: 0,
|
|
1472
|
+
textAlign: "center",
|
|
1473
|
+
fontSize: 13,
|
|
1474
|
+
fontWeight: 500,
|
|
1475
|
+
color: "#374151",
|
|
1476
|
+
background: "rgba(0, 188, 125, 0.08)",
|
|
1477
|
+
border: "1px solid rgba(0, 188, 125, 0.25)",
|
|
1478
|
+
borderRadius: 8,
|
|
1479
|
+
padding: "8px 12px"
|
|
1480
|
+
};
|
|
1159
1481
|
var FaceCaptureModal_default = FaceCaptureModal;
|
|
1160
1482
|
|
|
1161
1483
|
// src/kyc/hooks.ts
|
|
@@ -1439,7 +1761,7 @@ function useKycPreferences(client, autoFetch = true) {
|
|
|
1439
1761
|
refresh: fetchPreferences
|
|
1440
1762
|
};
|
|
1441
1763
|
}
|
|
1442
|
-
function
|
|
1764
|
+
function fileToBase64(file) {
|
|
1443
1765
|
return new Promise((resolve, reject) => {
|
|
1444
1766
|
const reader = new FileReader();
|
|
1445
1767
|
reader.onload = () => {
|
|
@@ -1496,41 +1818,50 @@ function getRiskColor(risk) {
|
|
|
1496
1818
|
return colorMap[risk] || "#6b7280";
|
|
1497
1819
|
}
|
|
1498
1820
|
function useReuseKYCSubmission(client) {
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1821
|
+
const startSession = useCallback(
|
|
1822
|
+
(request) => client.createReuseKycSession(request),
|
|
1823
|
+
[client]
|
|
1824
|
+
);
|
|
1825
|
+
const verifyFace = useCallback(
|
|
1826
|
+
(session, opts = {}) => new Promise((resolve) => {
|
|
1501
1827
|
const container = document.createElement("div");
|
|
1502
1828
|
document.body.appendChild(container);
|
|
1503
1829
|
const root = createRoot(container);
|
|
1504
1830
|
const cleanup = () => {
|
|
1505
1831
|
root.unmount();
|
|
1506
|
-
|
|
1832
|
+
container.remove();
|
|
1507
1833
|
};
|
|
1508
1834
|
const modal = createElement(FaceCaptureModal_default, {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
token
|
|
1515
|
-
});
|
|
1516
|
-
resolve(resp);
|
|
1517
|
-
} catch (error) {
|
|
1518
|
-
reject(error instanceof Error ? error : new Error("Face verification failed"));
|
|
1519
|
-
} finally {
|
|
1520
|
-
cleanup();
|
|
1521
|
-
}
|
|
1522
|
-
},
|
|
1523
|
-
onCancel: () => {
|
|
1835
|
+
client,
|
|
1836
|
+
session,
|
|
1837
|
+
defaultDevice: opts.defaultDevice,
|
|
1838
|
+
renderQR: opts.renderQR,
|
|
1839
|
+
onComplete: (result) => {
|
|
1524
1840
|
cleanup();
|
|
1525
|
-
resolve(
|
|
1841
|
+
resolve(result);
|
|
1526
1842
|
}
|
|
1527
1843
|
});
|
|
1528
1844
|
root.render(modal);
|
|
1529
|
-
})
|
|
1530
|
-
|
|
1531
|
-
|
|
1845
|
+
}),
|
|
1846
|
+
[client]
|
|
1847
|
+
);
|
|
1848
|
+
const runFlow = useCallback(
|
|
1849
|
+
async (request, opts = {}) => {
|
|
1850
|
+
const session = await startSession(request);
|
|
1851
|
+
if (!session.is_required) {
|
|
1852
|
+
return { kind: "not_required", session };
|
|
1853
|
+
}
|
|
1854
|
+
const result = await verifyFace(session, opts);
|
|
1855
|
+
if (result === null) {
|
|
1856
|
+
return { kind: "cancelled", session };
|
|
1857
|
+
}
|
|
1858
|
+
return { kind: "completed", session, result };
|
|
1859
|
+
},
|
|
1860
|
+
[startSession, verifyFace]
|
|
1861
|
+
);
|
|
1862
|
+
return { startSession, verifyFace, runFlow };
|
|
1532
1863
|
}
|
|
1533
1864
|
|
|
1534
|
-
export { createDeviceFingerprint,
|
|
1865
|
+
export { createDeviceFingerprint, fileToBase64, formatKycStatus, getBrowserInfo, getRiskColor, getStatusColor, isValidFileSize, isValidFileType, useCipherText, useCustomerProfile, useGeolocation, useKycAlerts, useKycOverview, useKycPreferences, useKycRequests, useKycSubmission, useLocationCapture, useLocationRequests, useLoginVerification, useRegistration, useReuseKYCSubmission, useTransactionVerification };
|
|
1535
1866
|
//# sourceMappingURL=react.mjs.map
|
|
1536
1867
|
//# sourceMappingURL=react.mjs.map
|