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/components.cjs.js
CHANGED
|
@@ -14,6 +14,12 @@ var React__default = /*#__PURE__*/_interopDefault(React);
|
|
|
14
14
|
// src/components/KycFlow.tsx
|
|
15
15
|
|
|
16
16
|
// src/services/kycApiService.ts
|
|
17
|
+
var COMPLETED_STEPS = {
|
|
18
|
+
INITIATED: "initiated",
|
|
19
|
+
FACE: "face_scan",
|
|
20
|
+
DOCS: "document_upload",
|
|
21
|
+
COMPLETED: "completed"
|
|
22
|
+
};
|
|
17
23
|
var KycApiService = class {
|
|
18
24
|
config;
|
|
19
25
|
constructor(config) {
|
|
@@ -131,6 +137,36 @@ var KycApiService = class {
|
|
|
131
137
|
throw new Error(`Document upload failed: ${message}`);
|
|
132
138
|
}
|
|
133
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Retry session - resets the session to allow face registration again
|
|
142
|
+
*/
|
|
143
|
+
async retrySession() {
|
|
144
|
+
const deviceType = this.config.deviceType || this.detectDeviceType();
|
|
145
|
+
try {
|
|
146
|
+
const response = await fetch(
|
|
147
|
+
`${this.config.apiBaseUrl}/api/v2/dashboard/merchant/onsite/session/${this.config.sessionId}/retry`,
|
|
148
|
+
{
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"x-server-key": this.config.serverKey,
|
|
152
|
+
"device-type": deviceType,
|
|
153
|
+
"Content-Type": "application/json"
|
|
154
|
+
},
|
|
155
|
+
credentials: "include"
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
const errorData = await response.json().catch(() => ({}));
|
|
160
|
+
const message = errorData?.message || `Retry failed with status ${response.status}`;
|
|
161
|
+
throw new Error(message);
|
|
162
|
+
}
|
|
163
|
+
const data = await response.json();
|
|
164
|
+
return data;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
const message = error?.message || "Retry failed";
|
|
167
|
+
throw new Error(`Retry failed: ${message}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
134
170
|
/**
|
|
135
171
|
* Check if session is active, throw error if not
|
|
136
172
|
*/
|
|
@@ -405,6 +441,7 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
405
441
|
const navigate = reactRouterDom.useNavigate();
|
|
406
442
|
const { apiService } = useKycContext();
|
|
407
443
|
const [sessionError, setSessionError] = React.useState(null);
|
|
444
|
+
const [kycCompleted, setKycCompleted] = React.useState(false);
|
|
408
445
|
const {
|
|
409
446
|
state,
|
|
410
447
|
setState,
|
|
@@ -420,6 +457,15 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
420
457
|
throw new Error("API service not initialized");
|
|
421
458
|
}
|
|
422
459
|
await apiService.uploadDocument(blob, docType);
|
|
460
|
+
try {
|
|
461
|
+
const statusResponse = await apiService.getSessionStatus();
|
|
462
|
+
const { completed_steps, status } = statusResponse.data;
|
|
463
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
464
|
+
setKycCompleted(true);
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error("Error checking completion status:", error);
|
|
468
|
+
}
|
|
423
469
|
},
|
|
424
470
|
onUpload: (file, docType) => {
|
|
425
471
|
if (onComplete) {
|
|
@@ -436,7 +482,24 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
436
482
|
const checkSession = async () => {
|
|
437
483
|
if (!apiService) return;
|
|
438
484
|
try {
|
|
439
|
-
await apiService.
|
|
485
|
+
const statusResponse = await apiService.getSessionStatus();
|
|
486
|
+
const { completed_steps, next_step, status } = statusResponse.data;
|
|
487
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
488
|
+
setKycCompleted(true);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (status !== "ACTIVE") {
|
|
492
|
+
throw new Error("Session expired or inactive");
|
|
493
|
+
}
|
|
494
|
+
if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
|
|
495
|
+
if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
|
|
496
|
+
setKycCompleted(true);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
|
|
501
|
+
console.warn("Face scan not completed, but in document upload modal");
|
|
502
|
+
}
|
|
440
503
|
setSessionError(null);
|
|
441
504
|
} catch (error) {
|
|
442
505
|
const message = error.message || "Session expired or inactive";
|
|
@@ -448,6 +511,14 @@ function DocumentUploadModal({ onComplete }) {
|
|
|
448
511
|
};
|
|
449
512
|
checkSession();
|
|
450
513
|
}, [apiService, navigate]);
|
|
514
|
+
if (kycCompleted) {
|
|
515
|
+
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: [
|
|
516
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
|
|
517
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
|
|
518
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
|
|
519
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
|
|
520
|
+
] }) }) });
|
|
521
|
+
}
|
|
451
522
|
if (sessionError) {
|
|
452
523
|
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: [
|
|
453
524
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
|
|
@@ -882,7 +953,8 @@ var FaceMeshService = class {
|
|
|
882
953
|
}
|
|
883
954
|
}
|
|
884
955
|
} else if (state.stage === "DONE") {
|
|
885
|
-
|
|
956
|
+
const reducedThreshold = 0.08;
|
|
957
|
+
if (absYaw < reducedThreshold) {
|
|
886
958
|
state.centerHold += 1;
|
|
887
959
|
if (state.centerHold >= holdFramesCenter && !state.snapTriggered) {
|
|
888
960
|
state.snapTriggered = true;
|
|
@@ -896,11 +968,7 @@ var FaceMeshService = class {
|
|
|
896
968
|
} else {
|
|
897
969
|
state.centerHold = 0;
|
|
898
970
|
if (this.callbacks.onLivenessUpdate) {
|
|
899
|
-
|
|
900
|
-
this.callbacks.onLivenessUpdate(state.stage, "Center your face inside the circle");
|
|
901
|
-
} else {
|
|
902
|
-
this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
|
|
903
|
-
}
|
|
971
|
+
this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
|
|
904
972
|
}
|
|
905
973
|
}
|
|
906
974
|
}
|
|
@@ -1062,7 +1130,7 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1062
1130
|
}, []);
|
|
1063
1131
|
const handleFaceCapture = React.useCallback(async () => {
|
|
1064
1132
|
if (!videoRef.current) return;
|
|
1065
|
-
const
|
|
1133
|
+
const reducedThreshold = 0.08;
|
|
1066
1134
|
const currentAbsYaw = livenessStateRef.current.currentAbsYaw;
|
|
1067
1135
|
if (currentAbsYaw === null || currentAbsYaw === void 0) {
|
|
1068
1136
|
setState((prev) => ({
|
|
@@ -1071,7 +1139,7 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1071
1139
|
}));
|
|
1072
1140
|
return;
|
|
1073
1141
|
}
|
|
1074
|
-
if (currentAbsYaw >=
|
|
1142
|
+
if (currentAbsYaw >= reducedThreshold) {
|
|
1075
1143
|
setState((prev) => ({
|
|
1076
1144
|
...prev,
|
|
1077
1145
|
livenessInstruction: "Please look straight at the camera before capturing"
|
|
@@ -1287,6 +1355,9 @@ function FaceScanModal({ onComplete }) {
|
|
|
1287
1355
|
const { apiService } = useKycContext();
|
|
1288
1356
|
const [sessionError, setSessionError] = React.useState(null);
|
|
1289
1357
|
const [toast, setToast] = React.useState(null);
|
|
1358
|
+
const [showRetryButton, setShowRetryButton] = React.useState(false);
|
|
1359
|
+
const [isRetrying, setIsRetrying] = React.useState(false);
|
|
1360
|
+
const [kycCompleted, setKycCompleted] = React.useState(false);
|
|
1290
1361
|
const { videoRef, cameraReady, stopCamera } = useCamera();
|
|
1291
1362
|
const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
|
|
1292
1363
|
onFaceUpload: async (blob) => {
|
|
@@ -1299,10 +1370,12 @@ function FaceScanModal({ onComplete }) {
|
|
|
1299
1370
|
const errorMessage = error?.message || "";
|
|
1300
1371
|
const errorData = error?.errorData || {};
|
|
1301
1372
|
if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
|
|
1373
|
+
setShowRetryButton(true);
|
|
1302
1374
|
setToast({
|
|
1303
|
-
message: "Face already registered",
|
|
1375
|
+
message: "Face already registered. Click Retry to register again.",
|
|
1304
1376
|
type: "warning"
|
|
1305
1377
|
});
|
|
1378
|
+
setState((prev) => ({ ...prev, loading: false }));
|
|
1306
1379
|
return;
|
|
1307
1380
|
}
|
|
1308
1381
|
throw error;
|
|
@@ -1318,7 +1391,25 @@ function FaceScanModal({ onComplete }) {
|
|
|
1318
1391
|
const checkSession = async () => {
|
|
1319
1392
|
if (!apiService) return;
|
|
1320
1393
|
try {
|
|
1321
|
-
await apiService.
|
|
1394
|
+
const statusResponse = await apiService.getSessionStatus();
|
|
1395
|
+
const { completed_steps, next_step, status } = statusResponse.data;
|
|
1396
|
+
if (status !== "ACTIVE") {
|
|
1397
|
+
throw new Error("Session expired or inactive");
|
|
1398
|
+
}
|
|
1399
|
+
if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
|
|
1400
|
+
setKycCompleted(true);
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
|
|
1404
|
+
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
if (next_step !== COMPLETED_STEPS.FACE && next_step !== COMPLETED_STEPS.INITIATED) {
|
|
1408
|
+
if (next_step === COMPLETED_STEPS.DOCS) {
|
|
1409
|
+
setState((prev) => ({ ...prev, showDocumentUpload: true }));
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1322
1413
|
setSessionError(null);
|
|
1323
1414
|
} catch (error) {
|
|
1324
1415
|
const message = error.message || "Session expired or inactive";
|
|
@@ -1329,7 +1420,7 @@ function FaceScanModal({ onComplete }) {
|
|
|
1329
1420
|
}
|
|
1330
1421
|
};
|
|
1331
1422
|
checkSession();
|
|
1332
|
-
}, [apiService, navigate]);
|
|
1423
|
+
}, [apiService, navigate, setState]);
|
|
1333
1424
|
React.useEffect(() => {
|
|
1334
1425
|
setState((prev) => ({ ...prev, cameraReady }));
|
|
1335
1426
|
}, [cameraReady, setState]);
|
|
@@ -1356,7 +1447,47 @@ function FaceScanModal({ onComplete }) {
|
|
|
1356
1447
|
}
|
|
1357
1448
|
}
|
|
1358
1449
|
}, [cameraReady, apiService]);
|
|
1359
|
-
const handleRetry = () => {
|
|
1450
|
+
const handleRetry = async () => {
|
|
1451
|
+
if (!apiService || isRetrying) return;
|
|
1452
|
+
setIsRetrying(true);
|
|
1453
|
+
setShowRetryButton(false);
|
|
1454
|
+
setToast(null);
|
|
1455
|
+
try {
|
|
1456
|
+
await apiService.retrySession();
|
|
1457
|
+
stopCamera();
|
|
1458
|
+
setState({
|
|
1459
|
+
cameraReady: false,
|
|
1460
|
+
livenessStage: "CENTER",
|
|
1461
|
+
livenessReady: false,
|
|
1462
|
+
livenessFailed: false,
|
|
1463
|
+
modelLoading: true,
|
|
1464
|
+
modelLoaded: false,
|
|
1465
|
+
livenessInstruction: "Look straight at the camera",
|
|
1466
|
+
loading: false,
|
|
1467
|
+
allStepsCompleted: false,
|
|
1468
|
+
capturedImage: null,
|
|
1469
|
+
showDocumentUpload: false
|
|
1470
|
+
});
|
|
1471
|
+
refs.centerHold.current = 0;
|
|
1472
|
+
refs.leftHold.current = 0;
|
|
1473
|
+
refs.rightHold.current = 0;
|
|
1474
|
+
refs.snapTriggered.current = false;
|
|
1475
|
+
refs.lastResultsAt.current = 0;
|
|
1476
|
+
refs.modelLoaded.current = false;
|
|
1477
|
+
refs.livenessFailed.current = false;
|
|
1478
|
+
setTimeout(() => {
|
|
1479
|
+
window.location.reload();
|
|
1480
|
+
}, 500);
|
|
1481
|
+
} catch (error) {
|
|
1482
|
+
setIsRetrying(false);
|
|
1483
|
+
setShowRetryButton(true);
|
|
1484
|
+
setToast({
|
|
1485
|
+
message: error?.message || "Retry failed. Please try again.",
|
|
1486
|
+
type: "error"
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
const handleRestart = () => {
|
|
1360
1491
|
stopCamera();
|
|
1361
1492
|
setState({
|
|
1362
1493
|
cameraReady: false,
|
|
@@ -1382,6 +1513,14 @@ function FaceScanModal({ onComplete }) {
|
|
|
1382
1513
|
window.location.reload();
|
|
1383
1514
|
}, 100);
|
|
1384
1515
|
};
|
|
1516
|
+
if (kycCompleted) {
|
|
1517
|
+
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: [
|
|
1518
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
|
|
1519
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
|
|
1520
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
|
|
1521
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
|
|
1522
|
+
] }) }) });
|
|
1523
|
+
}
|
|
1385
1524
|
if (state.showDocumentUpload) {
|
|
1386
1525
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1387
1526
|
DocumentUploadModal_default,
|
|
@@ -1444,13 +1583,23 @@ function FaceScanModal({ onComplete }) {
|
|
|
1444
1583
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1445
1584
|
] })
|
|
1446
1585
|
] }),
|
|
1586
|
+
showRetryButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1587
|
+
"button",
|
|
1588
|
+
{
|
|
1589
|
+
type: "button",
|
|
1590
|
+
onClick: handleRetry,
|
|
1591
|
+
disabled: isRetrying || state.loading,
|
|
1592
|
+
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",
|
|
1593
|
+
children: isRetrying ? "Retrying..." : "Retry Face Registration"
|
|
1594
|
+
}
|
|
1595
|
+
),
|
|
1447
1596
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1448
1597
|
"button",
|
|
1449
1598
|
{
|
|
1450
1599
|
type: "button",
|
|
1451
|
-
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1600
|
+
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
|
|
1452
1601
|
onClick: handleFaceCapture,
|
|
1453
|
-
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"}`,
|
|
1602
|
+
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"}`,
|
|
1454
1603
|
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1455
1604
|
}
|
|
1456
1605
|
),
|
|
@@ -1458,9 +1607,9 @@ function FaceScanModal({ onComplete }) {
|
|
|
1458
1607
|
"button",
|
|
1459
1608
|
{
|
|
1460
1609
|
type: "button",
|
|
1461
|
-
onClick:
|
|
1462
|
-
disabled: state.loading,
|
|
1463
|
-
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]"}`,
|
|
1610
|
+
onClick: handleRestart,
|
|
1611
|
+
disabled: state.loading || isRetrying,
|
|
1612
|
+
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]"}`,
|
|
1464
1613
|
children: "Restart"
|
|
1465
1614
|
}
|
|
1466
1615
|
)
|