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/components.cjs.js
CHANGED
|
@@ -89,15 +89,21 @@ var KycApiService = class {
|
|
|
89
89
|
);
|
|
90
90
|
if (!response.ok) {
|
|
91
91
|
const errorData = await response.json().catch(() => ({}));
|
|
92
|
-
const message = errorData?.message || `Face upload failed with status ${response.status}`;
|
|
92
|
+
const message = errorData?.message || errorData?.errorData?.message || `Face upload failed with status ${response.status}`;
|
|
93
93
|
const error = new Error(message);
|
|
94
94
|
error.statusCode = response.status;
|
|
95
95
|
error.errorData = errorData;
|
|
96
|
+
if (errorData.statusCode) {
|
|
97
|
+
error.statusCode = errorData.statusCode;
|
|
98
|
+
}
|
|
96
99
|
throw error;
|
|
97
100
|
}
|
|
98
101
|
const data = await response.json();
|
|
99
102
|
return data;
|
|
100
103
|
} catch (error) {
|
|
104
|
+
if (error.message && !error.message.includes("Face upload failed")) {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
101
107
|
const message = error?.message || "Face upload failed";
|
|
102
108
|
throw new Error(`Face upload failed: ${message}`);
|
|
103
109
|
}
|
|
@@ -137,6 +143,36 @@ var KycApiService = class {
|
|
|
137
143
|
throw new Error(`Document upload failed: ${message}`);
|
|
138
144
|
}
|
|
139
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Retry session - resets the session to allow face registration again
|
|
148
|
+
*/
|
|
149
|
+
async retrySession() {
|
|
150
|
+
const deviceType = this.config.deviceType || this.detectDeviceType();
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(
|
|
153
|
+
`${this.config.apiBaseUrl}/api/v2/dashboard/merchant/onsite/session/${this.config.sessionId}/retry`,
|
|
154
|
+
{
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: {
|
|
157
|
+
"x-server-key": this.config.serverKey,
|
|
158
|
+
"device-type": deviceType,
|
|
159
|
+
"Content-Type": "application/json"
|
|
160
|
+
},
|
|
161
|
+
credentials: "include"
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const errorData = await response.json().catch(() => ({}));
|
|
166
|
+
const message = errorData?.message || `Retry failed with status ${response.status}`;
|
|
167
|
+
throw new Error(message);
|
|
168
|
+
}
|
|
169
|
+
const data = await response.json();
|
|
170
|
+
return data;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
const message = error?.message || "Retry failed";
|
|
173
|
+
throw new Error(`Retry failed: ${message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
140
176
|
/**
|
|
141
177
|
* Check if session is active, throw error if not
|
|
142
178
|
*/
|
|
@@ -411,6 +447,7 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
411
447
|
const navigate = reactRouterDom.useNavigate();
|
|
412
448
|
const { apiService } = useKycContext();
|
|
413
449
|
const [sessionError, setSessionError] = React.useState(null);
|
|
450
|
+
const [kycCompleted, setKycCompleted] = React.useState(false);
|
|
414
451
|
const {
|
|
415
452
|
state,
|
|
416
453
|
setState,
|
|
@@ -426,6 +463,15 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
426
463
|
throw new Error("API service not initialized");
|
|
427
464
|
}
|
|
428
465
|
await apiService.uploadDocument(blob, docType);
|
|
466
|
+
try {
|
|
467
|
+
const statusResponse = await apiService.getSessionStatus();
|
|
468
|
+
const { completed_steps, status } = statusResponse.data;
|
|
469
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
470
|
+
setKycCompleted(true);
|
|
471
|
+
}
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.error("Error checking completion status:", error);
|
|
474
|
+
}
|
|
429
475
|
},
|
|
430
476
|
onUpload: (file, docType) => {
|
|
431
477
|
if (onComplete) {
|
|
@@ -444,11 +490,18 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
444
490
|
try {
|
|
445
491
|
const statusResponse = await apiService.getSessionStatus();
|
|
446
492
|
const { completed_steps, next_step, status } = statusResponse.data;
|
|
493
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
494
|
+
setKycCompleted(true);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
447
497
|
if (status !== "ACTIVE") {
|
|
448
498
|
throw new Error("Session expired or inactive");
|
|
449
499
|
}
|
|
450
500
|
if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
|
|
451
|
-
|
|
501
|
+
if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
|
|
502
|
+
setKycCompleted(true);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
452
505
|
}
|
|
453
506
|
if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
|
|
454
507
|
console.warn("Face scan not completed, but in document upload modal");
|
|
@@ -464,6 +517,14 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
464
517
|
};
|
|
465
518
|
checkSession();
|
|
466
519
|
}, [apiService, navigate]);
|
|
520
|
+
if (kycCompleted) {
|
|
521
|
+
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: [
|
|
522
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
|
|
523
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
|
|
524
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
|
|
525
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
|
|
526
|
+
] }) }) });
|
|
527
|
+
}
|
|
467
528
|
if (sessionError) {
|
|
468
529
|
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: [
|
|
469
530
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
|
|
@@ -1114,8 +1175,29 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1114
1175
|
livenessInstruction: "Face captured and uploaded successfully!",
|
|
1115
1176
|
loading: false
|
|
1116
1177
|
}));
|
|
1178
|
+
if (callbacks?.onFaceCaptureComplete) {
|
|
1179
|
+
callbacks.onFaceCaptureComplete(dataUrl);
|
|
1180
|
+
}
|
|
1181
|
+
setTimeout(() => {
|
|
1182
|
+
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1183
|
+
}, 500);
|
|
1117
1184
|
} catch (uploadError) {
|
|
1118
|
-
|
|
1185
|
+
if (uploadError.message === "FACE_ALREADY_REGISTERED" || uploadError.isFaceAlreadyRegistered) {
|
|
1186
|
+
setState((prev) => ({
|
|
1187
|
+
...prev,
|
|
1188
|
+
loading: false,
|
|
1189
|
+
allStepsCompleted: false,
|
|
1190
|
+
showDocumentUpload: false,
|
|
1191
|
+
livenessInstruction: "Face already registered. Please click Retry to register again."
|
|
1192
|
+
}));
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
setState((prev) => ({
|
|
1196
|
+
...prev,
|
|
1197
|
+
livenessInstruction: uploadError.message || "Error capturing image. Please try again.",
|
|
1198
|
+
loading: false
|
|
1199
|
+
}));
|
|
1200
|
+
throw uploadError;
|
|
1119
1201
|
}
|
|
1120
1202
|
} else {
|
|
1121
1203
|
setState((prev) => ({
|
|
@@ -1125,13 +1207,13 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1125
1207
|
livenessInstruction: "Face captured successfully!",
|
|
1126
1208
|
loading: false
|
|
1127
1209
|
}));
|
|
1210
|
+
if (callbacks?.onFaceCaptureComplete) {
|
|
1211
|
+
callbacks.onFaceCaptureComplete(dataUrl);
|
|
1212
|
+
}
|
|
1213
|
+
setTimeout(() => {
|
|
1214
|
+
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1215
|
+
}, 500);
|
|
1128
1216
|
}
|
|
1129
|
-
if (callbacks?.onFaceCaptureComplete) {
|
|
1130
|
-
callbacks.onFaceCaptureComplete(dataUrl);
|
|
1131
|
-
}
|
|
1132
|
-
setTimeout(() => {
|
|
1133
|
-
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1134
|
-
}, 500);
|
|
1135
1217
|
} catch (err) {
|
|
1136
1218
|
console.error("Error capturing image:", err);
|
|
1137
1219
|
setState((prev) => ({
|
|
@@ -1300,6 +1382,9 @@ function FaceScanModal({ onComplete }) {
|
|
|
1300
1382
|
const { apiService } = useKycContext();
|
|
1301
1383
|
const [sessionError, setSessionError] = React.useState(null);
|
|
1302
1384
|
const [toast, setToast] = React.useState(null);
|
|
1385
|
+
const [showRetryButton, setShowRetryButton] = React.useState(false);
|
|
1386
|
+
const [isRetrying, setIsRetrying] = React.useState(false);
|
|
1387
|
+
const [kycCompleted, setKycCompleted] = React.useState(false);
|
|
1303
1388
|
const { videoRef, cameraReady, stopCamera } = useCamera();
|
|
1304
1389
|
const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
|
|
1305
1390
|
onFaceUpload: async (blob) => {
|
|
@@ -1311,12 +1396,18 @@ function FaceScanModal({ onComplete }) {
|
|
|
1311
1396
|
} catch (error) {
|
|
1312
1397
|
const errorMessage = error?.message || "";
|
|
1313
1398
|
const errorData = error?.errorData || {};
|
|
1314
|
-
|
|
1399
|
+
const statusCode = error?.statusCode;
|
|
1400
|
+
const isFaceAlreadyRegistered = errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || statusCode === 500 && errorMessage.includes("Face already registered");
|
|
1401
|
+
if (isFaceAlreadyRegistered) {
|
|
1402
|
+
setShowRetryButton(true);
|
|
1315
1403
|
setToast({
|
|
1316
|
-
message: "Face already registered",
|
|
1404
|
+
message: "Face already registered. Click Retry to register again.",
|
|
1317
1405
|
type: "warning"
|
|
1318
1406
|
});
|
|
1319
|
-
|
|
1407
|
+
setState((prev) => ({ ...prev, loading: false, allStepsCompleted: false, showDocumentUpload: false }));
|
|
1408
|
+
const faceRegisteredError = new Error("FACE_ALREADY_REGISTERED");
|
|
1409
|
+
faceRegisteredError.isFaceAlreadyRegistered = true;
|
|
1410
|
+
throw faceRegisteredError;
|
|
1320
1411
|
}
|
|
1321
1412
|
throw error;
|
|
1322
1413
|
}
|
|
@@ -1336,6 +1427,10 @@ function FaceScanModal({ onComplete }) {
|
|
|
1336
1427
|
if (status !== "ACTIVE") {
|
|
1337
1428
|
throw new Error("Session expired or inactive");
|
|
1338
1429
|
}
|
|
1430
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
1431
|
+
setKycCompleted(true);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1339
1434
|
if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
|
|
1340
1435
|
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1341
1436
|
return;
|
|
@@ -1383,7 +1478,47 @@ function FaceScanModal({ onComplete }) {
|
|
|
1383
1478
|
}
|
|
1384
1479
|
}
|
|
1385
1480
|
}, [cameraReady, apiService]);
|
|
1386
|
-
const handleRetry = () => {
|
|
1481
|
+
const handleRetry = async () => {
|
|
1482
|
+
if (!apiService || isRetrying) return;
|
|
1483
|
+
setIsRetrying(true);
|
|
1484
|
+
setShowRetryButton(false);
|
|
1485
|
+
setToast(null);
|
|
1486
|
+
try {
|
|
1487
|
+
await apiService.retrySession();
|
|
1488
|
+
stopCamera();
|
|
1489
|
+
setState({
|
|
1490
|
+
cameraReady: false,
|
|
1491
|
+
livenessStage: "CENTER",
|
|
1492
|
+
livenessReady: false,
|
|
1493
|
+
livenessFailed: false,
|
|
1494
|
+
modelLoading: true,
|
|
1495
|
+
modelLoaded: false,
|
|
1496
|
+
livenessInstruction: "Look straight at the camera",
|
|
1497
|
+
loading: false,
|
|
1498
|
+
allStepsCompleted: false,
|
|
1499
|
+
capturedImage: null,
|
|
1500
|
+
showDocumentUpload: false
|
|
1501
|
+
});
|
|
1502
|
+
refs.centerHold.current = 0;
|
|
1503
|
+
refs.leftHold.current = 0;
|
|
1504
|
+
refs.rightHold.current = 0;
|
|
1505
|
+
refs.snapTriggered.current = false;
|
|
1506
|
+
refs.lastResultsAt.current = 0;
|
|
1507
|
+
refs.modelLoaded.current = false;
|
|
1508
|
+
refs.livenessFailed.current = false;
|
|
1509
|
+
setTimeout(() => {
|
|
1510
|
+
window.location.reload();
|
|
1511
|
+
}, 500);
|
|
1512
|
+
} catch (error) {
|
|
1513
|
+
setIsRetrying(false);
|
|
1514
|
+
setShowRetryButton(true);
|
|
1515
|
+
setToast({
|
|
1516
|
+
message: error?.message || "Retry failed. Please try again.",
|
|
1517
|
+
type: "error"
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
const handleRestart = () => {
|
|
1387
1522
|
stopCamera();
|
|
1388
1523
|
setState({
|
|
1389
1524
|
cameraReady: false,
|
|
@@ -1409,6 +1544,14 @@ function FaceScanModal({ onComplete }) {
|
|
|
1409
1544
|
window.location.reload();
|
|
1410
1545
|
}, 100);
|
|
1411
1546
|
};
|
|
1547
|
+
if (kycCompleted) {
|
|
1548
|
+
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: [
|
|
1549
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
|
|
1550
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
|
|
1551
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
|
|
1552
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
|
|
1553
|
+
] }) }) });
|
|
1554
|
+
}
|
|
1412
1555
|
if (state.showDocumentUpload) {
|
|
1413
1556
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1414
1557
|
DocumentUploadModal_default,
|
|
@@ -1471,13 +1614,23 @@ function FaceScanModal({ onComplete }) {
|
|
|
1471
1614
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1472
1615
|
] })
|
|
1473
1616
|
] }),
|
|
1617
|
+
showRetryButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1618
|
+
"button",
|
|
1619
|
+
{
|
|
1620
|
+
type: "button",
|
|
1621
|
+
onClick: handleRetry,
|
|
1622
|
+
disabled: isRetrying || state.loading,
|
|
1623
|
+
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",
|
|
1624
|
+
children: isRetrying ? "Retrying..." : "Retry Face Registration"
|
|
1625
|
+
}
|
|
1626
|
+
),
|
|
1474
1627
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1475
1628
|
"button",
|
|
1476
1629
|
{
|
|
1477
1630
|
type: "button",
|
|
1478
|
-
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1631
|
+
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
|
|
1479
1632
|
onClick: handleFaceCapture,
|
|
1480
|
-
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"}`,
|
|
1633
|
+
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"}`,
|
|
1481
1634
|
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1482
1635
|
}
|
|
1483
1636
|
),
|
|
@@ -1485,9 +1638,9 @@ function FaceScanModal({ onComplete }) {
|
|
|
1485
1638
|
"button",
|
|
1486
1639
|
{
|
|
1487
1640
|
type: "button",
|
|
1488
|
-
onClick:
|
|
1489
|
-
disabled: state.loading,
|
|
1490
|
-
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]"}`,
|
|
1641
|
+
onClick: handleRestart,
|
|
1642
|
+
disabled: state.loading || isRetrying,
|
|
1643
|
+
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]"}`,
|
|
1491
1644
|
children: "Restart"
|
|
1492
1645
|
}
|
|
1493
1646
|
)
|