@neofaceid/web-sdk 1.17.0 → 1.21.0

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.
@@ -3428,7 +3428,18 @@ const CALIBRATION = {
3428
3428
  YAW_LEFT_THRESHOLD: 15,
3429
3429
  YAW_RIGHT_THRESHOLD: -15,
3430
3430
  CAPTURE_HOLD_TIME: 1500,
3431
- DETECTION_INTERVAL: 200
3431
+ DETECTION_INTERVAL: 150,
3432
+ // Um pouco mais rápido para melhor resposta
3433
+ MIN_FACE_WIDTH: 160,
3434
+ // Rosto deve ocupar boa parte do oval (era ~120 implícito)
3435
+ MAX_FACE_WIDTH: 280,
3436
+ // Evita rosto "colado" na câmera
3437
+ CENTER_X_THRESHOLD: 0.15,
3438
+ // Rosto deve estar centralizado (desvio max 15%)
3439
+ CENTER_Y_THRESHOLD: 0.2,
3440
+ // Rosto deve estar centralizado verticalmente
3441
+ STEP_TIMEOUT: 2e4
3442
+ // 20 segundos para realizar cada movimento
3432
3443
  };
3433
3444
  function BiometricRegistrationModal({
3434
3445
  personData,
@@ -3446,11 +3457,13 @@ function BiometricRegistrationModal({
3446
3457
  const isCapturingRef = useRef(false);
3447
3458
  const currentStepRef = useRef("center");
3448
3459
  const watchdogTimerRef = useRef(null);
3460
+ const stepTimeoutTimerRef = useRef(null);
3449
3461
  const detectionStartedRef = useRef(false);
3450
3462
  const captureStateRef = useRef(null);
3451
3463
  const [state, dispatch] = useReducer(modalReducer, initialState);
3452
3464
  const [faceDetected, setFaceDetected] = useState(false);
3453
- const [_faceDistance, setFaceDistance] = useState("ok");
3465
+ const [faceDistance, setFaceDistance] = useState("ok");
3466
+ const [cameraReady, setCameraReady] = useState(false);
3454
3467
  useEffect(() => {
3455
3468
  if (state.status === "liveness") {
3456
3469
  const newStep = state.step;
@@ -3472,6 +3485,18 @@ function BiometricRegistrationModal({
3472
3485
  currentStepIndex: state.currentStepIndex,
3473
3486
  capturedPhotos: state.capturedPhotos
3474
3487
  };
3488
+ if (stepTimeoutTimerRef.current) {
3489
+ clearTimeout(stepTimeoutTimerRef.current);
3490
+ }
3491
+ if (state.step !== "complete") {
3492
+ stepTimeoutTimerRef.current = window.setTimeout(() => {
3493
+ dispatch({
3494
+ type: "ERROR",
3495
+ message: "Tempo limite atingido para o movimento. Por favor, tente novamente em um ambiente bem iluminado.",
3496
+ retryable: true
3497
+ });
3498
+ }, CALIBRATION.STEP_TIMEOUT);
3499
+ }
3475
3500
  }
3476
3501
  }, [
3477
3502
  state.status,
@@ -3512,6 +3537,18 @@ function BiometricRegistrationModal({
3512
3537
  return null;
3513
3538
  }
3514
3539
  };
3540
+ const checkFacePosition = (box, videoWidth, videoHeight) => {
3541
+ if (box.width < CALIBRATION.MIN_FACE_WIDTH) return "too-far";
3542
+ if (box.width > CALIBRATION.MAX_FACE_WIDTH) return "too-close";
3543
+ const faceCenterX = box.x + box.width / 2;
3544
+ const faceCenterY = box.y + box.height / 2;
3545
+ const deviationX = Math.abs(faceCenterX - videoWidth / 2) / videoWidth;
3546
+ const deviationY = Math.abs(faceCenterY - videoHeight / 2) / videoHeight;
3547
+ if (deviationX > CALIBRATION.CENTER_X_THRESHOLD || deviationY > CALIBRATION.CENTER_Y_THRESHOLD) {
3548
+ return "not-centered";
3549
+ }
3550
+ return "ok";
3551
+ };
3515
3552
  const checkPositionMatch = (pitch, yaw, step) => {
3516
3553
  switch (step) {
3517
3554
  case "center":
@@ -3605,6 +3642,10 @@ function BiometricRegistrationModal({
3605
3642
  clearInterval(progressIntervalRef.current);
3606
3643
  progressIntervalRef.current = null;
3607
3644
  }
3645
+ if (stepTimeoutTimerRef.current) {
3646
+ clearTimeout(stepTimeoutTimerRef.current);
3647
+ stepTimeoutTimerRef.current = null;
3648
+ }
3608
3649
  };
3609
3650
  }, []);
3610
3651
  useEffect(() => {
@@ -3652,7 +3693,11 @@ function BiometricRegistrationModal({
3652
3693
  }
3653
3694
  video.srcObject = stream;
3654
3695
  streamRef.current = stream;
3655
- await video.play();
3696
+ video.onloadedmetadata = () => {
3697
+ video.play().then(() => {
3698
+ if (mounted) setCameraReady(true);
3699
+ });
3700
+ };
3656
3701
  } catch (error) {
3657
3702
  if (!mounted) return;
3658
3703
  if (watchdogTimerRef.current) {
@@ -3714,22 +3759,27 @@ function BiometricRegistrationModal({
3714
3759
  const detection = await faceapi.detectSingleFace(
3715
3760
  videoRef.current,
3716
3761
  new faceapi.TinyFaceDetectorOptions({
3717
- inputSize: 160,
3718
- scoreThreshold: 0.4
3762
+ inputSize: 224,
3763
+ // Aumentado para detectar rostos menores/mais distantes
3764
+ scoreThreshold: 0.35
3765
+ // Ligeiramente mais sensível
3719
3766
  })
3720
3767
  ).withFaceLandmarks();
3721
3768
  if (!isRunning) return;
3722
3769
  const hasFace = !!detection;
3723
3770
  setFaceDetected(hasFace);
3724
3771
  if (hasFace && detection.detection) {
3725
- const box = detection.detection.box;
3726
- const faceWidth = box.width;
3727
- if (faceWidth > 250) {
3728
- setFaceDistance("too-close");
3729
- } else if (faceWidth < 120) {
3730
- setFaceDistance("too-far");
3731
- } else {
3732
- setFaceDistance("ok");
3772
+ const video2 = videoRef.current;
3773
+ const positionStatus = checkFacePosition(
3774
+ detection.detection.box,
3775
+ video2.videoWidth,
3776
+ video2.videoHeight
3777
+ );
3778
+ setFaceDistance(positionStatus);
3779
+ if (positionStatus !== "ok") {
3780
+ manageHoldAndCapture(false);
3781
+ requestAnimationFrame(detectFace2);
3782
+ return;
3733
3783
  }
3734
3784
  }
3735
3785
  if (hasFace && detection.landmarks) {
@@ -3911,26 +3961,25 @@ function BiometricRegistrationModal({
3911
3961
  const stepProgress = state.stepProgress;
3912
3962
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "liveness-container", children: [
3913
3963
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "progress-indicator", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "progress-text", children: progress }) }),
3914
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "video-wrapper", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3964
+ faceDetected && faceDistance !== "ok" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `status-badge ${faceDistance === "not-centered" ? "warning" : "error"}`, children: [
3965
+ faceDistance === "too-far" && "Aproxime-se mais",
3966
+ faceDistance === "too-close" && "Afaste-se um pouco",
3967
+ faceDistance === "not-centered" && "Centralize seu rosto"
3968
+ ] }),
3969
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "video-wrapper", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3915
3970
  "div",
3916
3971
  {
3917
- className: `video-circle ${faceDetected ? "face-detected" : ""} ${stepProgress > 0 ? "capturing" : ""}`,
3918
- children: [
3919
- /* @__PURE__ */ jsxRuntimeExports.jsx("video", { ref: videoRef, className: "video-feed", autoPlay: true, muted: true, playsInline: true }),
3920
- faceDetected && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "face-badge", children: [
3921
- /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3922
- "path",
3923
- {
3924
- d: "M5 10 L8 13 L15 6",
3925
- stroke: "currentColor",
3926
- strokeWidth: "2.5",
3927
- strokeLinecap: "round",
3928
- strokeLinejoin: "round"
3929
- }
3930
- ) }),
3931
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Rosto detectado" })
3932
- ] })
3933
- ]
3972
+ className: `video-circle ${faceDetected && faceDistance === "ok" ? "face-detected" : ""} ${stepProgress > 0 ? "capturing" : ""}`,
3973
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3974
+ "video",
3975
+ {
3976
+ ref: videoRef,
3977
+ className: `video-feed ${cameraReady ? "ready" : ""}`,
3978
+ autoPlay: true,
3979
+ muted: true,
3980
+ playsInline: true
3981
+ }
3982
+ )
3934
3983
  }
3935
3984
  ) }),
3936
3985
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "instruction-box", children: [
@@ -4387,8 +4436,9 @@ function BiometricRegistrationModal({
4387
4436
  border-radius: 50%;
4388
4437
  overflow: hidden;
4389
4438
  position: relative;
4390
- transition: all 0.3s ease;
4439
+ /* Removida transição 'all' para evitar salto de zoom no início */
4391
4440
  box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.3);
4441
+ background: #000;
4392
4442
  }
4393
4443
 
4394
4444
  .video-circle.face-detected {
@@ -4405,14 +4455,23 @@ function BiometricRegistrationModal({
4405
4455
  height: 100%;
4406
4456
  object-fit: cover;
4407
4457
  transform: scaleX(-1);
4458
+ opacity: 0;
4459
+ transition: opacity 0.4s ease;
4460
+ }
4461
+
4462
+ .video-feed.ready {
4463
+ opacity: 1;
4408
4464
  }
4409
4465
 
4410
4466
  .face-badge {
4467
+ display: none; /* Removido por ser redundante com o oval verde */
4468
+ }
4469
+
4470
+ .status-badge {
4411
4471
  position: absolute;
4412
- top: 20px;
4472
+ top: 72px; /* Logo abaixo do progress-indicator */
4413
4473
  left: 50%;
4414
4474
  transform: translateX(-50%);
4415
- background: rgba(16, 185, 129, 0.95);
4416
4475
  backdrop-filter: blur(10px);
4417
4476
  border-radius: 20px;
4418
4477
  padding: 8px 16px;
@@ -4423,22 +4482,30 @@ function BiometricRegistrationModal({
4423
4482
  font-size: 13px;
4424
4483
  font-weight: 600;
4425
4484
  border: 1px solid rgba(255, 255, 255, 0.3);
4426
- box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
4485
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
4427
4486
  animation: slideDown 0.3s ease-out;
4487
+ z-index: 10;
4488
+ white-space: nowrap;
4489
+ }
4490
+
4491
+ .status-badge.info {
4492
+ background: rgba(255, 255, 255, 0.15);
4493
+ color: white;
4494
+ border-color: rgba(255, 255, 255, 0.1);
4428
4495
  }
4429
4496
 
4430
- .face-badge.warning {
4497
+ .status-badge.warning {
4431
4498
  background: rgba(251, 191, 36, 0.95);
4432
- border: 1px solid rgba(255, 255, 255, 0.3);
4433
- box-shadow: 0 4px 12px rgba(251, 191, 36, 0.3);
4434
4499
  color: #78350f;
4500
+ border-color: rgba(0, 0, 0, 0.1);
4501
+ box-shadow: 0 4px 15px rgba(251, 191, 36, 0.2);
4435
4502
  }
4436
4503
 
4437
- .face-badge.error {
4504
+ .status-badge.error {
4438
4505
  background: rgba(239, 68, 68, 0.95);
4439
- border: 1px solid rgba(255, 255, 255, 0.3);
4440
- box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
4441
4506
  color: white;
4507
+ border-color: rgba(255, 255, 255, 0.2);
4508
+ box-shadow: 0 4px 15px rgba(239, 68, 68, 0.2);
4442
4509
  }
4443
4510
 
4444
4511
  .instruction-box {
@@ -6835,7 +6902,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
6835
6902
  initializeBiometricDetection,
6836
6903
  isAdvancedDetectionAvailable
6837
6904
  }, Symbol.toStringTag, { value: "Module" }));
6838
- const VERSION = "1.17.0";
6905
+ const VERSION = "1.21.0";
6839
6906
  const RELEASE_DATE = "2026-03-13";
6840
6907
  class OnboardingCaptureModal {
6841
6908
  constructor(options) {