astra-sdk-web 1.1.8 → 1.1.10
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/astra-sdk.cjs.js +167 -18
- package/dist/astra-sdk.cjs.js.map +1 -1
- package/dist/astra-sdk.css +19 -0
- package/dist/astra-sdk.css.map +1 -1
- package/dist/astra-sdk.d.cts +4 -4
- package/dist/astra-sdk.es.js +167 -18
- package/dist/astra-sdk.es.js.map +1 -1
- package/dist/components.cjs.js +167 -18
- package/dist/components.cjs.js.map +1 -1
- package/dist/components.css +19 -0
- package/dist/components.css.map +1 -1
- package/dist/components.es.js +167 -18
- package/dist/components.es.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/package.json +1 -1
- package/src/features/faceScan/hooks/useFaceScan.ts +3 -3
- package/src/pages/DocumentUploadModal.tsx +63 -1
- package/src/pages/FaceScanModal.tsx +123 -9
- package/src/services/faceMeshService.ts +5 -7
- package/src/services/kycApiService.ts +41 -0
package/dist/astra-sdk.cjs.js
CHANGED
|
@@ -219,6 +219,12 @@ function mergeConfig(userConfig) {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// src/services/kycApiService.ts
|
|
222
|
+
var COMPLETED_STEPS = {
|
|
223
|
+
INITIATED: "initiated",
|
|
224
|
+
FACE: "face_scan",
|
|
225
|
+
DOCS: "document_upload",
|
|
226
|
+
COMPLETED: "completed"
|
|
227
|
+
};
|
|
222
228
|
var KycApiService = class {
|
|
223
229
|
config;
|
|
224
230
|
constructor(config) {
|
|
@@ -336,6 +342,36 @@ var KycApiService = class {
|
|
|
336
342
|
throw new Error(`Document upload failed: ${message}`);
|
|
337
343
|
}
|
|
338
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Retry session - resets the session to allow face registration again
|
|
347
|
+
*/
|
|
348
|
+
async retrySession() {
|
|
349
|
+
const deviceType = this.config.deviceType || this.detectDeviceType();
|
|
350
|
+
try {
|
|
351
|
+
const response = await fetch(
|
|
352
|
+
`${this.config.apiBaseUrl}/api/v2/dashboard/merchant/onsite/session/${this.config.sessionId}/retry`,
|
|
353
|
+
{
|
|
354
|
+
method: "POST",
|
|
355
|
+
headers: {
|
|
356
|
+
"x-server-key": this.config.serverKey,
|
|
357
|
+
"device-type": deviceType,
|
|
358
|
+
"Content-Type": "application/json"
|
|
359
|
+
},
|
|
360
|
+
credentials: "include"
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
if (!response.ok) {
|
|
364
|
+
const errorData = await response.json().catch(() => ({}));
|
|
365
|
+
const message = errorData?.message || `Retry failed with status ${response.status}`;
|
|
366
|
+
throw new Error(message);
|
|
367
|
+
}
|
|
368
|
+
const data = await response.json();
|
|
369
|
+
return data;
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const message = error?.message || "Retry failed";
|
|
372
|
+
throw new Error(`Retry failed: ${message}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
339
375
|
/**
|
|
340
376
|
* Check if session is active, throw error if not
|
|
341
377
|
*/
|
|
@@ -610,6 +646,7 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
610
646
|
const navigate = reactRouterDom.useNavigate();
|
|
611
647
|
const { apiService } = useKycContext();
|
|
612
648
|
const [sessionError, setSessionError] = React.useState(null);
|
|
649
|
+
const [kycCompleted, setKycCompleted] = React.useState(false);
|
|
613
650
|
const {
|
|
614
651
|
state,
|
|
615
652
|
setState,
|
|
@@ -625,6 +662,15 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
625
662
|
throw new Error("API service not initialized");
|
|
626
663
|
}
|
|
627
664
|
await apiService.uploadDocument(blob, docType);
|
|
665
|
+
try {
|
|
666
|
+
const statusResponse = await apiService.getSessionStatus();
|
|
667
|
+
const { completed_steps, status } = statusResponse.data;
|
|
668
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
669
|
+
setKycCompleted(true);
|
|
670
|
+
}
|
|
671
|
+
} catch (error) {
|
|
672
|
+
console.error("Error checking completion status:", error);
|
|
673
|
+
}
|
|
628
674
|
},
|
|
629
675
|
onUpload: (file, docType) => {
|
|
630
676
|
if (onComplete) {
|
|
@@ -641,7 +687,24 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
641
687
|
const checkSession = async () => {
|
|
642
688
|
if (!apiService) return;
|
|
643
689
|
try {
|
|
644
|
-
await apiService.
|
|
690
|
+
const statusResponse = await apiService.getSessionStatus();
|
|
691
|
+
const { completed_steps, next_step, status } = statusResponse.data;
|
|
692
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
693
|
+
setKycCompleted(true);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (status !== "ACTIVE") {
|
|
697
|
+
throw new Error("Session expired or inactive");
|
|
698
|
+
}
|
|
699
|
+
if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
|
|
700
|
+
if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
|
|
701
|
+
setKycCompleted(true);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
|
|
706
|
+
console.warn("Face scan not completed, but in document upload modal");
|
|
707
|
+
}
|
|
645
708
|
setSessionError(null);
|
|
646
709
|
} catch (error) {
|
|
647
710
|
const message = error.message || "Session expired or inactive";
|
|
@@ -653,6 +716,14 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
653
716
|
};
|
|
654
717
|
checkSession();
|
|
655
718
|
}, [apiService, navigate]);
|
|
719
|
+
if (kycCompleted) {
|
|
720
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black p-5 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
721
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
|
|
722
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
|
|
723
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
|
|
724
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
|
|
725
|
+
] }) }) });
|
|
726
|
+
}
|
|
656
727
|
if (sessionError) {
|
|
657
728
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black p-4 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
658
729
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
|
|
@@ -1087,7 +1158,8 @@ var FaceMeshService = class {
|
|
|
1087
1158
|
}
|
|
1088
1159
|
}
|
|
1089
1160
|
} else if (state.stage === "DONE") {
|
|
1090
|
-
|
|
1161
|
+
const reducedThreshold = 0.08;
|
|
1162
|
+
if (absYaw < reducedThreshold) {
|
|
1091
1163
|
state.centerHold += 1;
|
|
1092
1164
|
if (state.centerHold >= holdFramesCenter && !state.snapTriggered) {
|
|
1093
1165
|
state.snapTriggered = true;
|
|
@@ -1101,11 +1173,7 @@ var FaceMeshService = class {
|
|
|
1101
1173
|
} else {
|
|
1102
1174
|
state.centerHold = 0;
|
|
1103
1175
|
if (this.callbacks.onLivenessUpdate) {
|
|
1104
|
-
|
|
1105
|
-
this.callbacks.onLivenessUpdate(state.stage, "Center your face inside the circle");
|
|
1106
|
-
} else {
|
|
1107
|
-
this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
|
|
1108
|
-
}
|
|
1176
|
+
this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
|
|
1109
1177
|
}
|
|
1110
1178
|
}
|
|
1111
1179
|
}
|
|
@@ -1267,7 +1335,7 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1267
1335
|
}, []);
|
|
1268
1336
|
const handleFaceCapture = React.useCallback(async () => {
|
|
1269
1337
|
if (!videoRef.current) return;
|
|
1270
|
-
const
|
|
1338
|
+
const reducedThreshold = 0.08;
|
|
1271
1339
|
const currentAbsYaw = livenessStateRef.current.currentAbsYaw;
|
|
1272
1340
|
if (currentAbsYaw === null || currentAbsYaw === void 0) {
|
|
1273
1341
|
setState((prev) => ({
|
|
@@ -1276,7 +1344,7 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1276
1344
|
}));
|
|
1277
1345
|
return;
|
|
1278
1346
|
}
|
|
1279
|
-
if (currentAbsYaw >=
|
|
1347
|
+
if (currentAbsYaw >= reducedThreshold) {
|
|
1280
1348
|
setState((prev) => ({
|
|
1281
1349
|
...prev,
|
|
1282
1350
|
livenessInstruction: "Please look straight at the camera before capturing"
|
|
@@ -1492,6 +1560,9 @@ function FaceScanModal({ onComplete }) {
|
|
|
1492
1560
|
const { apiService } = useKycContext();
|
|
1493
1561
|
const [sessionError, setSessionError] = React.useState(null);
|
|
1494
1562
|
const [toast, setToast] = React.useState(null);
|
|
1563
|
+
const [showRetryButton, setShowRetryButton] = React.useState(false);
|
|
1564
|
+
const [isRetrying, setIsRetrying] = React.useState(false);
|
|
1565
|
+
const [kycCompleted, setKycCompleted] = React.useState(false);
|
|
1495
1566
|
const { videoRef, cameraReady, stopCamera } = useCamera();
|
|
1496
1567
|
const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
|
|
1497
1568
|
onFaceUpload: async (blob) => {
|
|
@@ -1504,10 +1575,12 @@ function FaceScanModal({ onComplete }) {
|
|
|
1504
1575
|
const errorMessage = error?.message || "";
|
|
1505
1576
|
const errorData = error?.errorData || {};
|
|
1506
1577
|
if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
|
|
1578
|
+
setShowRetryButton(true);
|
|
1507
1579
|
setToast({
|
|
1508
|
-
message: "Face already registered",
|
|
1580
|
+
message: "Face already registered. Click Retry to register again.",
|
|
1509
1581
|
type: "warning"
|
|
1510
1582
|
});
|
|
1583
|
+
setState((prev) => ({ ...prev, loading: false }));
|
|
1511
1584
|
return;
|
|
1512
1585
|
}
|
|
1513
1586
|
throw error;
|
|
@@ -1523,7 +1596,25 @@ function FaceScanModal({ onComplete }) {
|
|
|
1523
1596
|
const checkSession = async () => {
|
|
1524
1597
|
if (!apiService) return;
|
|
1525
1598
|
try {
|
|
1526
|
-
await apiService.
|
|
1599
|
+
const statusResponse = await apiService.getSessionStatus();
|
|
1600
|
+
const { completed_steps, next_step, status } = statusResponse.data;
|
|
1601
|
+
if (status !== "ACTIVE") {
|
|
1602
|
+
throw new Error("Session expired or inactive");
|
|
1603
|
+
}
|
|
1604
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
1605
|
+
setKycCompleted(true);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
|
|
1609
|
+
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
if (next_step !== COMPLETED_STEPS.FACE && next_step !== COMPLETED_STEPS.INITIATED) {
|
|
1613
|
+
if (next_step === COMPLETED_STEPS.DOCS) {
|
|
1614
|
+
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1527
1618
|
setSessionError(null);
|
|
1528
1619
|
} catch (error) {
|
|
1529
1620
|
const message = error.message || "Session expired or inactive";
|
|
@@ -1534,7 +1625,7 @@ function FaceScanModal({ onComplete }) {
|
|
|
1534
1625
|
}
|
|
1535
1626
|
};
|
|
1536
1627
|
checkSession();
|
|
1537
|
-
}, [apiService, navigate]);
|
|
1628
|
+
}, [apiService, navigate, setState]);
|
|
1538
1629
|
React.useEffect(() => {
|
|
1539
1630
|
setState((prev) => ({ ...prev, cameraReady }));
|
|
1540
1631
|
}, [cameraReady, setState]);
|
|
@@ -1561,7 +1652,47 @@ function FaceScanModal({ onComplete }) {
|
|
|
1561
1652
|
}
|
|
1562
1653
|
}
|
|
1563
1654
|
}, [cameraReady, apiService]);
|
|
1564
|
-
const handleRetry = () => {
|
|
1655
|
+
const handleRetry = async () => {
|
|
1656
|
+
if (!apiService || isRetrying) return;
|
|
1657
|
+
setIsRetrying(true);
|
|
1658
|
+
setShowRetryButton(false);
|
|
1659
|
+
setToast(null);
|
|
1660
|
+
try {
|
|
1661
|
+
await apiService.retrySession();
|
|
1662
|
+
stopCamera();
|
|
1663
|
+
setState({
|
|
1664
|
+
cameraReady: false,
|
|
1665
|
+
livenessStage: "CENTER",
|
|
1666
|
+
livenessReady: false,
|
|
1667
|
+
livenessFailed: false,
|
|
1668
|
+
modelLoading: true,
|
|
1669
|
+
modelLoaded: false,
|
|
1670
|
+
livenessInstruction: "Look straight at the camera",
|
|
1671
|
+
loading: false,
|
|
1672
|
+
allStepsCompleted: false,
|
|
1673
|
+
capturedImage: null,
|
|
1674
|
+
showDocumentUpload: false
|
|
1675
|
+
});
|
|
1676
|
+
refs.centerHold.current = 0;
|
|
1677
|
+
refs.leftHold.current = 0;
|
|
1678
|
+
refs.rightHold.current = 0;
|
|
1679
|
+
refs.snapTriggered.current = false;
|
|
1680
|
+
refs.lastResultsAt.current = 0;
|
|
1681
|
+
refs.modelLoaded.current = false;
|
|
1682
|
+
refs.livenessFailed.current = false;
|
|
1683
|
+
setTimeout(() => {
|
|
1684
|
+
window.location.reload();
|
|
1685
|
+
}, 500);
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
setIsRetrying(false);
|
|
1688
|
+
setShowRetryButton(true);
|
|
1689
|
+
setToast({
|
|
1690
|
+
message: error?.message || "Retry failed. Please try again.",
|
|
1691
|
+
type: "error"
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
const handleRestart = () => {
|
|
1565
1696
|
stopCamera();
|
|
1566
1697
|
setState({
|
|
1567
1698
|
cameraReady: false,
|
|
@@ -1587,6 +1718,14 @@ function FaceScanModal({ onComplete }) {
|
|
|
1587
1718
|
window.location.reload();
|
|
1588
1719
|
}, 100);
|
|
1589
1720
|
};
|
|
1721
|
+
if (kycCompleted) {
|
|
1722
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black p-5 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
1723
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
|
|
1724
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
|
|
1725
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
|
|
1726
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
|
|
1727
|
+
] }) }) });
|
|
1728
|
+
}
|
|
1590
1729
|
if (state.showDocumentUpload) {
|
|
1591
1730
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1592
1731
|
DocumentUploadModal_default,
|
|
@@ -1649,13 +1788,23 @@ function FaceScanModal({ onComplete }) {
|
|
|
1649
1788
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1650
1789
|
] })
|
|
1651
1790
|
] }),
|
|
1791
|
+
showRetryButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1792
|
+
"button",
|
|
1793
|
+
{
|
|
1794
|
+
type: "button",
|
|
1795
|
+
onClick: handleRetry,
|
|
1796
|
+
disabled: isRetrying || state.loading,
|
|
1797
|
+
className: "py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors bg-[#f59e0b] text-[#0b0f17] cursor-pointer hover:bg-[#d97706] disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1798
|
+
children: isRetrying ? "Retrying..." : "Retry Face Registration"
|
|
1799
|
+
}
|
|
1800
|
+
),
|
|
1652
1801
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1653
1802
|
"button",
|
|
1654
1803
|
{
|
|
1655
1804
|
type: "button",
|
|
1656
|
-
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1805
|
+
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
|
|
1657
1806
|
onClick: handleFaceCapture,
|
|
1658
|
-
className: `py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors ${state.cameraReady && !state.loading && (state.livenessFailed || state.livenessStage === "DONE") ? "bg-[#22c55e] text-[#0b0f17] cursor-pointer hover:bg-[#16a34a]" : "bg-[#374151] text-[#e5e7eb] cursor-not-allowed"}`,
|
|
1807
|
+
className: `py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors ${state.cameraReady && !state.loading && (state.livenessFailed || state.livenessStage === "DONE") && !showRetryButton ? "bg-[#22c55e] text-[#0b0f17] cursor-pointer hover:bg-[#16a34a]" : "bg-[#374151] text-[#e5e7eb] cursor-not-allowed"}`,
|
|
1659
1808
|
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1660
1809
|
}
|
|
1661
1810
|
),
|
|
@@ -1663,9 +1812,9 @@ function FaceScanModal({ onComplete }) {
|
|
|
1663
1812
|
"button",
|
|
1664
1813
|
{
|
|
1665
1814
|
type: "button",
|
|
1666
|
-
onClick:
|
|
1667
|
-
disabled: state.loading,
|
|
1668
|
-
className: `py-3 px-4 rounded-[10px] text-[15px] font-semibold border-none w-full transition-colors ${state.loading ? "bg-[#374151] text-[#e5e7eb] cursor-not-allowed opacity-50" : "bg-[#374151] text-[#e5e7eb] cursor-pointer hover:bg-[#4b5563]"}`,
|
|
1815
|
+
onClick: handleRestart,
|
|
1816
|
+
disabled: state.loading || isRetrying,
|
|
1817
|
+
className: `py-3 px-4 rounded-[10px] text-[15px] font-semibold border-none w-full transition-colors ${state.loading || isRetrying ? "bg-[#374151] text-[#e5e7eb] cursor-not-allowed opacity-50" : "bg-[#374151] text-[#e5e7eb] cursor-pointer hover:bg-[#4b5563]"}`,
|
|
1669
1818
|
children: "Restart"
|
|
1670
1819
|
}
|
|
1671
1820
|
)
|