astra-sdk-web 1.1.9 → 1.1.11
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 +171 -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 -0
- package/dist/astra-sdk.es.js +171 -18
- package/dist/astra-sdk.es.js.map +1 -1
- package/dist/components.cjs.js +171 -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 +171 -18
- package/dist/components.es.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/package.json +1 -1
- package/src/features/faceScan/hooks/useFaceScan.ts +37 -10
- package/src/pages/DocumentUploadModal.tsx +44 -2
- package/src/pages/FaceScanModal.tsx +110 -11
- package/src/services/kycApiService.ts +43 -1
package/dist/astra-sdk.cjs.js
CHANGED
|
@@ -294,15 +294,21 @@ var KycApiService = class {
|
|
|
294
294
|
);
|
|
295
295
|
if (!response.ok) {
|
|
296
296
|
const errorData = await response.json().catch(() => ({}));
|
|
297
|
-
const message = errorData?.message || `Face upload failed with status ${response.status}`;
|
|
297
|
+
const message = errorData?.message || errorData?.errorData?.message || `Face upload failed with status ${response.status}`;
|
|
298
298
|
const error = new Error(message);
|
|
299
299
|
error.statusCode = response.status;
|
|
300
300
|
error.errorData = errorData;
|
|
301
|
+
if (errorData.statusCode) {
|
|
302
|
+
error.statusCode = errorData.statusCode;
|
|
303
|
+
}
|
|
301
304
|
throw error;
|
|
302
305
|
}
|
|
303
306
|
const data = await response.json();
|
|
304
307
|
return data;
|
|
305
308
|
} catch (error) {
|
|
309
|
+
if (error.message && !error.message.includes("Face upload failed")) {
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
306
312
|
const message = error?.message || "Face upload failed";
|
|
307
313
|
throw new Error(`Face upload failed: ${message}`);
|
|
308
314
|
}
|
|
@@ -342,6 +348,36 @@ var KycApiService = class {
|
|
|
342
348
|
throw new Error(`Document upload failed: ${message}`);
|
|
343
349
|
}
|
|
344
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Retry session - resets the session to allow face registration again
|
|
353
|
+
*/
|
|
354
|
+
async retrySession() {
|
|
355
|
+
const deviceType = this.config.deviceType || this.detectDeviceType();
|
|
356
|
+
try {
|
|
357
|
+
const response = await fetch(
|
|
358
|
+
`${this.config.apiBaseUrl}/api/v2/dashboard/merchant/onsite/session/${this.config.sessionId}/retry`,
|
|
359
|
+
{
|
|
360
|
+
method: "POST",
|
|
361
|
+
headers: {
|
|
362
|
+
"x-server-key": this.config.serverKey,
|
|
363
|
+
"device-type": deviceType,
|
|
364
|
+
"Content-Type": "application/json"
|
|
365
|
+
},
|
|
366
|
+
credentials: "include"
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
if (!response.ok) {
|
|
370
|
+
const errorData = await response.json().catch(() => ({}));
|
|
371
|
+
const message = errorData?.message || `Retry failed with status ${response.status}`;
|
|
372
|
+
throw new Error(message);
|
|
373
|
+
}
|
|
374
|
+
const data = await response.json();
|
|
375
|
+
return data;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
const message = error?.message || "Retry failed";
|
|
378
|
+
throw new Error(`Retry failed: ${message}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
345
381
|
/**
|
|
346
382
|
* Check if session is active, throw error if not
|
|
347
383
|
*/
|
|
@@ -616,6 +652,7 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
616
652
|
const navigate = reactRouterDom.useNavigate();
|
|
617
653
|
const { apiService } = useKycContext();
|
|
618
654
|
const [sessionError, setSessionError] = React.useState(null);
|
|
655
|
+
const [kycCompleted, setKycCompleted] = React.useState(false);
|
|
619
656
|
const {
|
|
620
657
|
state,
|
|
621
658
|
setState,
|
|
@@ -631,6 +668,15 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
631
668
|
throw new Error("API service not initialized");
|
|
632
669
|
}
|
|
633
670
|
await apiService.uploadDocument(blob, docType);
|
|
671
|
+
try {
|
|
672
|
+
const statusResponse = await apiService.getSessionStatus();
|
|
673
|
+
const { completed_steps, status } = statusResponse.data;
|
|
674
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
675
|
+
setKycCompleted(true);
|
|
676
|
+
}
|
|
677
|
+
} catch (error) {
|
|
678
|
+
console.error("Error checking completion status:", error);
|
|
679
|
+
}
|
|
634
680
|
},
|
|
635
681
|
onUpload: (file, docType) => {
|
|
636
682
|
if (onComplete) {
|
|
@@ -649,11 +695,18 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
649
695
|
try {
|
|
650
696
|
const statusResponse = await apiService.getSessionStatus();
|
|
651
697
|
const { completed_steps, next_step, status } = statusResponse.data;
|
|
698
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
699
|
+
setKycCompleted(true);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
652
702
|
if (status !== "ACTIVE") {
|
|
653
703
|
throw new Error("Session expired or inactive");
|
|
654
704
|
}
|
|
655
705
|
if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
|
|
656
|
-
|
|
706
|
+
if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
|
|
707
|
+
setKycCompleted(true);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
657
710
|
}
|
|
658
711
|
if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
|
|
659
712
|
console.warn("Face scan not completed, but in document upload modal");
|
|
@@ -669,6 +722,14 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
669
722
|
};
|
|
670
723
|
checkSession();
|
|
671
724
|
}, [apiService, navigate]);
|
|
725
|
+
if (kycCompleted) {
|
|
726
|
+
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: [
|
|
727
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
|
|
728
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
|
|
729
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
|
|
730
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
|
|
731
|
+
] }) }) });
|
|
732
|
+
}
|
|
672
733
|
if (sessionError) {
|
|
673
734
|
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: [
|
|
674
735
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
|
|
@@ -1319,8 +1380,29 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1319
1380
|
livenessInstruction: "Face captured and uploaded successfully!",
|
|
1320
1381
|
loading: false
|
|
1321
1382
|
}));
|
|
1383
|
+
if (callbacks?.onFaceCaptureComplete) {
|
|
1384
|
+
callbacks.onFaceCaptureComplete(dataUrl);
|
|
1385
|
+
}
|
|
1386
|
+
setTimeout(() => {
|
|
1387
|
+
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1388
|
+
}, 500);
|
|
1322
1389
|
} catch (uploadError) {
|
|
1323
|
-
|
|
1390
|
+
if (uploadError.message === "FACE_ALREADY_REGISTERED" || uploadError.isFaceAlreadyRegistered) {
|
|
1391
|
+
setState((prev) => ({
|
|
1392
|
+
...prev,
|
|
1393
|
+
loading: false,
|
|
1394
|
+
allStepsCompleted: false,
|
|
1395
|
+
showDocumentUpload: false,
|
|
1396
|
+
livenessInstruction: "Face already registered. Please click Retry to register again."
|
|
1397
|
+
}));
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
setState((prev) => ({
|
|
1401
|
+
...prev,
|
|
1402
|
+
livenessInstruction: uploadError.message || "Error capturing image. Please try again.",
|
|
1403
|
+
loading: false
|
|
1404
|
+
}));
|
|
1405
|
+
throw uploadError;
|
|
1324
1406
|
}
|
|
1325
1407
|
} else {
|
|
1326
1408
|
setState((prev) => ({
|
|
@@ -1330,13 +1412,13 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1330
1412
|
livenessInstruction: "Face captured successfully!",
|
|
1331
1413
|
loading: false
|
|
1332
1414
|
}));
|
|
1415
|
+
if (callbacks?.onFaceCaptureComplete) {
|
|
1416
|
+
callbacks.onFaceCaptureComplete(dataUrl);
|
|
1417
|
+
}
|
|
1418
|
+
setTimeout(() => {
|
|
1419
|
+
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1420
|
+
}, 500);
|
|
1333
1421
|
}
|
|
1334
|
-
if (callbacks?.onFaceCaptureComplete) {
|
|
1335
|
-
callbacks.onFaceCaptureComplete(dataUrl);
|
|
1336
|
-
}
|
|
1337
|
-
setTimeout(() => {
|
|
1338
|
-
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1339
|
-
}, 500);
|
|
1340
1422
|
} catch (err) {
|
|
1341
1423
|
console.error("Error capturing image:", err);
|
|
1342
1424
|
setState((prev) => ({
|
|
@@ -1505,6 +1587,9 @@ function FaceScanModal({ onComplete }) {
|
|
|
1505
1587
|
const { apiService } = useKycContext();
|
|
1506
1588
|
const [sessionError, setSessionError] = React.useState(null);
|
|
1507
1589
|
const [toast, setToast] = React.useState(null);
|
|
1590
|
+
const [showRetryButton, setShowRetryButton] = React.useState(false);
|
|
1591
|
+
const [isRetrying, setIsRetrying] = React.useState(false);
|
|
1592
|
+
const [kycCompleted, setKycCompleted] = React.useState(false);
|
|
1508
1593
|
const { videoRef, cameraReady, stopCamera } = useCamera();
|
|
1509
1594
|
const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
|
|
1510
1595
|
onFaceUpload: async (blob) => {
|
|
@@ -1516,12 +1601,18 @@ function FaceScanModal({ onComplete }) {
|
|
|
1516
1601
|
} catch (error) {
|
|
1517
1602
|
const errorMessage = error?.message || "";
|
|
1518
1603
|
const errorData = error?.errorData || {};
|
|
1519
|
-
|
|
1604
|
+
const statusCode = error?.statusCode;
|
|
1605
|
+
const isFaceAlreadyRegistered = errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || statusCode === 500 && errorMessage.includes("Face already registered");
|
|
1606
|
+
if (isFaceAlreadyRegistered) {
|
|
1607
|
+
setShowRetryButton(true);
|
|
1520
1608
|
setToast({
|
|
1521
|
-
message: "Face already registered",
|
|
1609
|
+
message: "Face already registered. Click Retry to register again.",
|
|
1522
1610
|
type: "warning"
|
|
1523
1611
|
});
|
|
1524
|
-
|
|
1612
|
+
setState((prev) => ({ ...prev, loading: false, allStepsCompleted: false, showDocumentUpload: false }));
|
|
1613
|
+
const faceRegisteredError = new Error("FACE_ALREADY_REGISTERED");
|
|
1614
|
+
faceRegisteredError.isFaceAlreadyRegistered = true;
|
|
1615
|
+
throw faceRegisteredError;
|
|
1525
1616
|
}
|
|
1526
1617
|
throw error;
|
|
1527
1618
|
}
|
|
@@ -1541,6 +1632,10 @@ function FaceScanModal({ onComplete }) {
|
|
|
1541
1632
|
if (status !== "ACTIVE") {
|
|
1542
1633
|
throw new Error("Session expired or inactive");
|
|
1543
1634
|
}
|
|
1635
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
1636
|
+
setKycCompleted(true);
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1544
1639
|
if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
|
|
1545
1640
|
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1546
1641
|
return;
|
|
@@ -1588,7 +1683,47 @@ function FaceScanModal({ onComplete }) {
|
|
|
1588
1683
|
}
|
|
1589
1684
|
}
|
|
1590
1685
|
}, [cameraReady, apiService]);
|
|
1591
|
-
const handleRetry = () => {
|
|
1686
|
+
const handleRetry = async () => {
|
|
1687
|
+
if (!apiService || isRetrying) return;
|
|
1688
|
+
setIsRetrying(true);
|
|
1689
|
+
setShowRetryButton(false);
|
|
1690
|
+
setToast(null);
|
|
1691
|
+
try {
|
|
1692
|
+
await apiService.retrySession();
|
|
1693
|
+
stopCamera();
|
|
1694
|
+
setState({
|
|
1695
|
+
cameraReady: false,
|
|
1696
|
+
livenessStage: "CENTER",
|
|
1697
|
+
livenessReady: false,
|
|
1698
|
+
livenessFailed: false,
|
|
1699
|
+
modelLoading: true,
|
|
1700
|
+
modelLoaded: false,
|
|
1701
|
+
livenessInstruction: "Look straight at the camera",
|
|
1702
|
+
loading: false,
|
|
1703
|
+
allStepsCompleted: false,
|
|
1704
|
+
capturedImage: null,
|
|
1705
|
+
showDocumentUpload: false
|
|
1706
|
+
});
|
|
1707
|
+
refs.centerHold.current = 0;
|
|
1708
|
+
refs.leftHold.current = 0;
|
|
1709
|
+
refs.rightHold.current = 0;
|
|
1710
|
+
refs.snapTriggered.current = false;
|
|
1711
|
+
refs.lastResultsAt.current = 0;
|
|
1712
|
+
refs.modelLoaded.current = false;
|
|
1713
|
+
refs.livenessFailed.current = false;
|
|
1714
|
+
setTimeout(() => {
|
|
1715
|
+
window.location.reload();
|
|
1716
|
+
}, 500);
|
|
1717
|
+
} catch (error) {
|
|
1718
|
+
setIsRetrying(false);
|
|
1719
|
+
setShowRetryButton(true);
|
|
1720
|
+
setToast({
|
|
1721
|
+
message: error?.message || "Retry failed. Please try again.",
|
|
1722
|
+
type: "error"
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
const handleRestart = () => {
|
|
1592
1727
|
stopCamera();
|
|
1593
1728
|
setState({
|
|
1594
1729
|
cameraReady: false,
|
|
@@ -1614,6 +1749,14 @@ function FaceScanModal({ onComplete }) {
|
|
|
1614
1749
|
window.location.reload();
|
|
1615
1750
|
}, 100);
|
|
1616
1751
|
};
|
|
1752
|
+
if (kycCompleted) {
|
|
1753
|
+
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: [
|
|
1754
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
|
|
1755
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
|
|
1756
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
|
|
1757
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
|
|
1758
|
+
] }) }) });
|
|
1759
|
+
}
|
|
1617
1760
|
if (state.showDocumentUpload) {
|
|
1618
1761
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1619
1762
|
DocumentUploadModal_default,
|
|
@@ -1676,13 +1819,23 @@ function FaceScanModal({ onComplete }) {
|
|
|
1676
1819
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1677
1820
|
] })
|
|
1678
1821
|
] }),
|
|
1822
|
+
showRetryButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1823
|
+
"button",
|
|
1824
|
+
{
|
|
1825
|
+
type: "button",
|
|
1826
|
+
onClick: handleRetry,
|
|
1827
|
+
disabled: isRetrying || state.loading,
|
|
1828
|
+
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",
|
|
1829
|
+
children: isRetrying ? "Retrying..." : "Retry Face Registration"
|
|
1830
|
+
}
|
|
1831
|
+
),
|
|
1679
1832
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1680
1833
|
"button",
|
|
1681
1834
|
{
|
|
1682
1835
|
type: "button",
|
|
1683
|
-
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1836
|
+
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
|
|
1684
1837
|
onClick: handleFaceCapture,
|
|
1685
|
-
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"}`,
|
|
1838
|
+
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"}`,
|
|
1686
1839
|
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1687
1840
|
}
|
|
1688
1841
|
),
|
|
@@ -1690,9 +1843,9 @@ function FaceScanModal({ onComplete }) {
|
|
|
1690
1843
|
"button",
|
|
1691
1844
|
{
|
|
1692
1845
|
type: "button",
|
|
1693
|
-
onClick:
|
|
1694
|
-
disabled: state.loading,
|
|
1695
|
-
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]"}`,
|
|
1846
|
+
onClick: handleRestart,
|
|
1847
|
+
disabled: state.loading || isRetrying,
|
|
1848
|
+
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]"}`,
|
|
1696
1849
|
children: "Restart"
|
|
1697
1850
|
}
|
|
1698
1851
|
)
|