astra-sdk-web 1.1.7 → 1.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astra-sdk-web",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "Official Astra SDK for JavaScript/TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/astra-sdk.cjs.js",
@@ -49,6 +49,9 @@ export function useFaceScan(
49
49
  lastResultsAt: 0,
50
50
  stage: 'CENTER' as LivenessStage,
51
51
  livenessReady: false,
52
+ currentYaw: null as number | null,
53
+ currentAbsYaw: null as number | null,
54
+ livenessCompleted: false,
52
55
  });
53
56
 
54
57
  // Sync refs with state
@@ -74,6 +77,27 @@ export function useFaceScan(
74
77
 
75
78
  const handleFaceCapture = useCallback(async () => {
76
79
  if (!videoRef.current) return;
80
+
81
+ // Check if face is straight before capturing
82
+ const centerThreshold = 0.05;
83
+ const currentAbsYaw = livenessStateRef.current.currentAbsYaw;
84
+
85
+ if (currentAbsYaw === null || currentAbsYaw === undefined) {
86
+ setState(prev => ({
87
+ ...prev,
88
+ livenessInstruction: "Please position your face in front of the camera",
89
+ }));
90
+ return;
91
+ }
92
+
93
+ if (currentAbsYaw >= centerThreshold) {
94
+ setState(prev => ({
95
+ ...prev,
96
+ livenessInstruction: "Please look straight at the camera before capturing",
97
+ }));
98
+ return;
99
+ }
100
+
77
101
  setState(prev => ({ ...prev, loading: true }));
78
102
  try {
79
103
  const video = videoRef.current;
@@ -28,7 +28,6 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
28
28
  try {
29
29
  await apiService.uploadFaceScan(blob);
30
30
  } catch (error: any) {
31
- // Check if it's a "Face already registered" error
32
31
  const errorMessage = error?.message || '';
33
32
  const errorData = (error as any)?.errorData || {};
34
33
 
@@ -39,13 +38,11 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
39
38
  (error as any)?.statusCode === 500 && errorMessage.includes('Face')
40
39
  ) {
41
40
  setToast({
42
- message: 'Face has already been registered for this session. Proceeding to document upload.',
41
+ message: 'Face already registered',
43
42
  type: 'warning',
44
43
  });
45
- // Don't throw error - allow flow to continue to document upload
46
44
  return;
47
45
  }
48
- // Re-throw other errors
49
46
  throw error;
50
47
  }
51
48
  },
@@ -56,7 +53,6 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
56
53
  },
57
54
  });
58
55
 
59
- // Check session status on mount
60
56
  useEffect(() => {
61
57
  const checkSession = async () => {
62
58
  if (!apiService) return;
@@ -67,7 +63,6 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
67
63
  } catch (error: any) {
68
64
  const message = error.message || 'Session expired or inactive';
69
65
  setSessionError(message);
70
- // Redirect to QR page after showing error
71
66
  setTimeout(() => {
72
67
  navigate('/qr', { replace: true });
73
68
  }, 2000);
@@ -77,15 +72,12 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
77
72
  checkSession();
78
73
  }, [apiService, navigate]);
79
74
 
80
- // Sync camera ready state
81
75
  useEffect(() => {
82
76
  setState(prev => ({ ...prev, cameraReady }));
83
77
  }, [cameraReady, setState]);
84
78
 
85
- // Log session info and call status API when camera opens
86
79
  useEffect(() => {
87
80
  if (cameraReady && apiService) {
88
- // Get config from apiService to log session info
89
81
  const config = apiService.getConfig();
90
82
  if (config) {
91
83
  console.log('=== Camera Opened ===');
@@ -94,7 +86,6 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
94
86
  console.log('API Base URL:', config.apiBaseUrl);
95
87
  console.log('Device Type:', config.deviceType || 'auto-detected');
96
88
 
97
- // Call status API
98
89
  apiService.getSessionStatus()
99
90
  .then((statusResponse) => {
100
91
  console.log('=== Session Status API Response ===');
@@ -152,8 +143,7 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
152
143
  />
153
144
  );
154
145
  }
155
-
156
- // Show session error if present
146
+
157
147
  if (sessionError) {
158
148
  return (
159
149
  <div className="fixed inset-0 bg-black p-5 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar">
@@ -19,6 +19,9 @@ export interface LivenessState {
19
19
  lastResultsAt: number;
20
20
  stage: LivenessStage;
21
21
  livenessReady: boolean;
22
+ currentYaw: number | null;
23
+ currentAbsYaw: number | null;
24
+ livenessCompleted: boolean;
22
25
  }
23
26
 
24
27
  export class FaceMeshService {
@@ -111,6 +114,10 @@ export class FaceMeshService {
111
114
 
112
115
  this.processLiveness(faceOnCanvas, w, h);
113
116
  } else {
117
+ // Reset face orientation when no face is detected
118
+ this.livenessStateRef.current.currentYaw = null;
119
+ this.livenessStateRef.current.currentAbsYaw = null;
120
+
114
121
  const vid = this.videoRef.current as HTMLVideoElement | null;
115
122
  if (vid) {
116
123
  const vidW = Math.max(1, vid?.videoWidth || displayW);
@@ -160,6 +167,10 @@ export class FaceMeshService {
160
167
  const yaw = (nT.x - midX) / Math.max(1e-6, faceWidth);
161
168
  const absYaw = Math.abs(yaw);
162
169
 
170
+ // Store current face orientation for capture validation
171
+ this.livenessStateRef.current.currentYaw = yaw;
172
+ this.livenessStateRef.current.currentAbsYaw = absYaw;
173
+
163
174
  const xs = faceOnCanvas.map(p => p.x), ys = faceOnCanvas.map(p => p.y);
164
175
  const minX = Math.min(...xs) * w, maxX = Math.max(...xs) * w;
165
176
  const minY = Math.min(...ys) * h, maxY = Math.max(...ys) * h;
@@ -192,11 +203,14 @@ export class FaceMeshService {
192
203
  } else if (absYaw < centerThreshold) {
193
204
  state.centerHold += 1;
194
205
  if (state.centerHold >= holdFramesCenter) {
195
- const newStage: LivenessStage = "LEFT";
196
- state.stage = newStage;
197
- state.centerHold = 0;
198
- if (this.callbacks.onLivenessUpdate) {
199
- this.callbacks.onLivenessUpdate(newStage, "Turn your face LEFT");
206
+ // Only transition to LEFT if liveness check hasn't been completed yet
207
+ if (!state.livenessCompleted) {
208
+ const newStage: LivenessStage = "LEFT";
209
+ state.stage = newStage;
210
+ state.centerHold = 0;
211
+ if (this.callbacks.onLivenessUpdate) {
212
+ this.callbacks.onLivenessUpdate(newStage, "Turn your face LEFT");
213
+ }
200
214
  }
201
215
  }
202
216
  } else {
@@ -235,16 +249,13 @@ export class FaceMeshService {
235
249
  state.rightHold += 1;
236
250
  if (state.rightHold >= holdFramesTurn) {
237
251
  state.rightHold = 0;
238
- if (!state.snapTriggered) {
239
- state.snapTriggered = true;
240
- const newStage: LivenessStage = "DONE";
241
- state.stage = newStage;
242
- if (this.callbacks.onLivenessUpdate) {
243
- this.callbacks.onLivenessUpdate(newStage, "Capturing...");
244
- }
245
- if (this.callbacks.onCaptureTrigger) {
246
- this.callbacks.onCaptureTrigger();
247
- }
252
+ // Mark liveness as completed and transition to DONE stage
253
+ state.livenessCompleted = true;
254
+ const newStage: LivenessStage = "DONE";
255
+ state.stage = newStage;
256
+ state.centerHold = 0;
257
+ if (this.callbacks.onLivenessUpdate) {
258
+ this.callbacks.onLivenessUpdate(newStage, "Great! Now look straight at the camera");
248
259
  }
249
260
  }
250
261
  } else {
@@ -253,6 +264,29 @@ export class FaceMeshService {
253
264
  this.callbacks.onLivenessUpdate(state.stage, yaw < -leftThreshold ? "You're facing left. Turn RIGHT" : "Turn a bit more RIGHT");
254
265
  }
255
266
  }
267
+ } else if (state.stage === "DONE") {
268
+ // In DONE stage, wait for face to be straight before capturing
269
+ if (absYaw < centerThreshold && insideGuide) {
270
+ state.centerHold += 1;
271
+ if (state.centerHold >= holdFramesCenter && !state.snapTriggered) {
272
+ state.snapTriggered = true;
273
+ if (this.callbacks.onLivenessUpdate) {
274
+ this.callbacks.onLivenessUpdate(state.stage, "Capturing...");
275
+ }
276
+ if (this.callbacks.onCaptureTrigger) {
277
+ this.callbacks.onCaptureTrigger();
278
+ }
279
+ }
280
+ } else {
281
+ state.centerHold = 0;
282
+ if (this.callbacks.onLivenessUpdate) {
283
+ if (!insideGuide) {
284
+ this.callbacks.onLivenessUpdate(state.stage, "Center your face inside the circle");
285
+ } else {
286
+ this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
287
+ }
288
+ }
289
+ }
256
290
  }
257
291
  }
258
292
  }