@neofaceid/web-sdk 1.10.0 → 1.13.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.
@@ -1124,7 +1124,7 @@ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
1124
1124
  class NeoFaceError extends Error {
1125
1125
  /**
1126
1126
  * Constrói um erro do SDK com mensagem e tipo categórico.
1127
- * @param message Mensagem descritiva do erro
1127
+ * @param message Mensagem descritiva do erro (pode ser técnica)
1128
1128
  * @param type Tipo categórico do erro
1129
1129
  */
1130
1130
  constructor(message, type) {
@@ -1133,6 +1133,35 @@ class NeoFaceError extends Error {
1133
1133
  this.name = type;
1134
1134
  this.type = type;
1135
1135
  }
1136
+ /**
1137
+ * Retorna uma mensagem amigável para o usuário final, ocultando termos técnicos.
1138
+ */
1139
+ getFriendlyMessage() {
1140
+ if (/[áéíóúãõç]/i.test(this.message)) {
1141
+ return this.message;
1142
+ }
1143
+ switch (this.type) {
1144
+ case "NetworkError":
1145
+ if (this.message.includes("timeout")) return "A conexão demorou muito. Tente novamente.";
1146
+ return "Não foi possível conectar ao servidor. Verifique sua internet.";
1147
+ case "InvalidTokenError":
1148
+ return "Falha na autenticação do aplicativo. Entre em contato com o suporte.";
1149
+ case "CameraError":
1150
+ case "NoCameraError":
1151
+ return "Não conseguimos acessar sua câmera. Verifique as permissões do navegador.";
1152
+ case "LoginFailedError":
1153
+ case "RecognitionFailedError":
1154
+ return "Não foi possível reconhecer seu rosto. Tente se posicionar em um local mais iluminado.";
1155
+ case "ValidationError":
1156
+ return "Dados inválidos ou rosto não detectado corretamente.";
1157
+ case "ApiError":
1158
+ if (this.message.includes("400")) return "Requisição inválida. Tente novamente.";
1159
+ if (this.message.includes("500")) return "Ocorreu um erro interno no sistema. Tente mais tarde.";
1160
+ return "O serviço encontrou um problema inesperado.";
1161
+ default:
1162
+ return "Ocorreu um erro inesperado. Por favor, tente novamente.";
1163
+ }
1164
+ }
1136
1165
  }
1137
1166
  const __vite_import_meta_env__ = {};
1138
1167
  const ENVIRONMENT_URLS = {
@@ -1291,15 +1320,18 @@ const validateToken = async (applicationToken) => {
1291
1320
  body: JSON.stringify({}),
1292
1321
  signal: controller.signal
1293
1322
  });
1323
+ if (!response.ok) {
1324
+ throw new NeoFaceError(`API Error ${response.status}`, ErrorType.INVALID_TOKEN);
1325
+ }
1294
1326
  return response.ok;
1295
1327
  } catch (error) {
1296
1328
  if (error instanceof Error) {
1297
1329
  if (error.name === "AbortError") {
1298
- throw new NeoFaceError("Request timed out", ErrorType.NETWORK_ERROR);
1330
+ throw new NeoFaceError("Request timeout", ErrorType.NETWORK_ERROR);
1299
1331
  }
1300
1332
  throw new NeoFaceError(error.message, ErrorType.NETWORK_ERROR);
1301
1333
  }
1302
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1334
+ throw new NeoFaceError("Unknown error", ErrorType.NETWORK_ERROR);
1303
1335
  }
1304
1336
  };
1305
1337
  const getOnboardingDetails = async (applicationToken, onboardingToken) => {
@@ -1554,7 +1586,7 @@ const recognize = async (image, applicationToken) => {
1554
1586
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1555
1587
  }
1556
1588
  };
1557
- const loginWithBiometric$1 = async (image, applicationToken) => {
1589
+ const loginWithBiometric = async (image, applicationToken) => {
1558
1590
  var _a, _b, _c, _d, _e;
1559
1591
  ensureSecureContext();
1560
1592
  const controller = createTimeoutController();
@@ -1578,9 +1610,12 @@ const loginWithBiometric$1 = async (image, applicationToken) => {
1578
1610
  });
1579
1611
  if (!response.ok) {
1580
1612
  if (response.status === 401 || response.status === 403) {
1581
- throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1613
+ throw new NeoFaceError("Invalid application token", ErrorType.INVALID_TOKEN);
1582
1614
  }
1583
- throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1615
+ if (response.status >= 500) {
1616
+ throw new NeoFaceError(`Server Error ${response.status}`, ErrorType.API_ERROR);
1617
+ }
1618
+ throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
1584
1619
  }
1585
1620
  const data = await response.json();
1586
1621
  if (!data.success) {
@@ -2003,9 +2038,9 @@ const loginWithEmail = async (email, password, applicationToken) => {
2003
2038
  });
2004
2039
  if (!response.ok) {
2005
2040
  if (response.status === 401 || response.status === 403) {
2006
- throw new NeoFaceError("Invalid credentials or application token", ErrorType.INVALID_TOKEN);
2041
+ throw new NeoFaceError("Email ou senha inválidos", ErrorType.INVALID_TOKEN);
2007
2042
  }
2008
- throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
2043
+ throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
2009
2044
  }
2010
2045
  const data = await response.json();
2011
2046
  return {
@@ -2773,12 +2808,14 @@ const modalReducer = (state, action) => {
2773
2808
  };
2774
2809
  }
2775
2810
  return state;
2811
+ case "CAMERA_LOADING":
2812
+ return { status: "camera-loading" };
2776
2813
  case "START_PROCESSING":
2777
2814
  return { status: "processing" };
2778
2815
  case "SUCCESS":
2779
2816
  return { status: "success" };
2780
2817
  case "ERROR":
2781
- return { status: "error", message: action.message };
2818
+ return { status: "error", message: action.message, retryable: action.retryable ?? true };
2782
2819
  case "RESET":
2783
2820
  return initialState;
2784
2821
  default:
@@ -2824,9 +2861,7 @@ function BiometricRegistrationModal({
2824
2861
  onClose,
2825
2862
  onSuccess,
2826
2863
  onPhotosCaptured,
2827
- onError,
2828
- useRealApi = true
2829
- // Sempre usa API real em produção
2864
+ onError
2830
2865
  }) {
2831
2866
  const videoRef = useRef(null);
2832
2867
  const canvasRef = useRef(null);
@@ -2835,10 +2870,12 @@ function BiometricRegistrationModal({
2835
2870
  const progressIntervalRef = useRef(null);
2836
2871
  const isCapturingRef = useRef(false);
2837
2872
  const currentStepRef = useRef("center");
2873
+ const watchdogTimerRef = useRef(null);
2874
+ const detectionStartedRef = useRef(false);
2838
2875
  const captureStateRef = useRef(null);
2839
2876
  const [state, dispatch] = useReducer(modalReducer, initialState);
2840
2877
  const [faceDetected, setFaceDetected] = useState(false);
2841
- const [faceDistance, setFaceDistance] = useState("ok");
2878
+ const [_faceDistance, setFaceDistance] = useState("ok");
2842
2879
  useEffect(() => {
2843
2880
  if (state.status === "liveness") {
2844
2881
  const newStep = state.step;
@@ -2954,20 +2991,66 @@ function BiometricRegistrationModal({
2954
2991
  const loadModels = async () => {
2955
2992
  try {
2956
2993
  const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
2957
- await Promise.all([
2958
- faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
2959
- faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
2994
+ const timeout = new Promise(
2995
+ (_, reject) => setTimeout(() => reject(new Error("Timeout ao carregar modelos")), 1e4)
2996
+ );
2997
+ await Promise.race([
2998
+ Promise.all([
2999
+ faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
3000
+ faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
3001
+ ]),
3002
+ timeout
2960
3003
  ]);
2961
3004
  } catch (error) {
2962
- dispatch({ type: "ERROR", message: "Erro ao carregar modelos de IA" });
3005
+ dispatch({
3006
+ type: "ERROR",
3007
+ message: "Não foi possível carregar os modelos de IA. Verifique sua conexão e tente novamente.",
3008
+ retryable: true
3009
+ });
2963
3010
  }
2964
3011
  };
2965
3012
  loadModels();
2966
3013
  }, []);
3014
+ useEffect(() => {
3015
+ return () => {
3016
+ if (streamRef.current) {
3017
+ streamRef.current.getTracks().forEach((track) => {
3018
+ track.stop();
3019
+ });
3020
+ streamRef.current = null;
3021
+ }
3022
+ if (videoRef.current) {
3023
+ videoRef.current.srcObject = null;
3024
+ }
3025
+ if (watchdogTimerRef.current) {
3026
+ clearTimeout(watchdogTimerRef.current);
3027
+ watchdogTimerRef.current = null;
3028
+ }
3029
+ if (progressIntervalRef.current) {
3030
+ clearInterval(progressIntervalRef.current);
3031
+ progressIntervalRef.current = null;
3032
+ }
3033
+ };
3034
+ }, []);
2967
3035
  useEffect(() => {
2968
3036
  let mounted = true;
2969
3037
  const initCamera = async () => {
2970
3038
  if (state.status !== "liveness" || streamRef.current) return;
3039
+ detectionStartedRef.current = false;
3040
+ if (watchdogTimerRef.current) {
3041
+ clearTimeout(watchdogTimerRef.current);
3042
+ watchdogTimerRef.current = null;
3043
+ }
3044
+ watchdogTimerRef.current = window.setTimeout(() => {
3045
+ if (!mounted) return;
3046
+ if (detectionStartedRef.current === false) {
3047
+ dispatch({
3048
+ type: "ERROR",
3049
+ message: "A câmera não respondeu a tempo. Verifique se ela está disponível e tente novamente.",
3050
+ retryable: true
3051
+ });
3052
+ }
3053
+ }, 2e4);
2971
3054
  try {
2972
3055
  const stream = await navigator.mediaDevices.getUserMedia({
2973
3056
  video: {
@@ -2997,13 +3080,30 @@ function BiometricRegistrationModal({
2997
3080
  await video.play();
2998
3081
  } catch (error) {
2999
3082
  if (!mounted) return;
3000
- const errorMessage = error instanceof Error ? error.message : "Erro ao acessar câmera";
3001
- dispatch({ type: "ERROR", message: errorMessage });
3083
+ if (watchdogTimerRef.current) {
3084
+ clearTimeout(watchdogTimerRef.current);
3085
+ watchdogTimerRef.current = null;
3086
+ }
3087
+ let errorMessage = error instanceof Error ? error.message : "Erro ao acessar câmera";
3088
+ if (error instanceof DOMException) {
3089
+ if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
3090
+ errorMessage = "Permissão de câmera negada. Permita o acesso à câmera nas configurações do navegador e tente novamente.";
3091
+ } else if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
3092
+ errorMessage = "Nenhuma câmera encontrada. Conecte uma câmera e tente novamente.";
3093
+ } else if (error.name === "NotReadableError") {
3094
+ errorMessage = "A câmera está sendo usada por outro programa. Feche-o e tente novamente.";
3095
+ }
3096
+ }
3097
+ dispatch({ type: "ERROR", message: errorMessage, retryable: true });
3002
3098
  }
3003
3099
  };
3004
3100
  initCamera();
3005
3101
  return () => {
3006
3102
  mounted = false;
3103
+ if (watchdogTimerRef.current) {
3104
+ clearTimeout(watchdogTimerRef.current);
3105
+ watchdogTimerRef.current = null;
3106
+ }
3007
3107
  if (streamRef.current) {
3008
3108
  streamRef.current.getTracks().forEach((track) => track.stop());
3009
3109
  streamRef.current = null;
@@ -3083,21 +3183,59 @@ function BiometricRegistrationModal({
3083
3183
  requestAnimationFrame(detectFace2);
3084
3184
  }
3085
3185
  };
3086
- const startTimer = setTimeout(() => {
3087
- var _a;
3088
- if (isRunning && ((_a = videoRef.current) == null ? void 0 : _a.readyState) === 4) {
3089
- detectFace2();
3090
- }
3091
- }, 1e3);
3092
- return () => {
3093
- isRunning = false;
3094
- clearTimeout(startTimer);
3095
- if (progressIntervalRef.current) {
3096
- clearInterval(progressIntervalRef.current);
3097
- progressIntervalRef.current = null;
3098
- }
3099
- holdStartTimeRef.current = null;
3186
+ const startDetection = () => {
3187
+ if (!isRunning || !videoRef.current) return;
3188
+ detectionStartedRef.current = true;
3189
+ if (watchdogTimerRef.current) {
3190
+ clearTimeout(watchdogTimerRef.current);
3191
+ watchdogTimerRef.current = null;
3192
+ }
3193
+ detectFace2();
3100
3194
  };
3195
+ const video = videoRef.current;
3196
+ if (video && video.readyState >= 2) {
3197
+ const startTimer = setTimeout(startDetection, 200);
3198
+ return () => {
3199
+ isRunning = false;
3200
+ clearTimeout(startTimer);
3201
+ if (progressIntervalRef.current) {
3202
+ clearInterval(progressIntervalRef.current);
3203
+ progressIntervalRef.current = null;
3204
+ }
3205
+ holdStartTimeRef.current = null;
3206
+ };
3207
+ } else {
3208
+ const onCanPlay = () => {
3209
+ if (isRunning) startDetection();
3210
+ };
3211
+ let pollCount = 0;
3212
+ const MAX_POLLS = 50;
3213
+ const pollTimer = window.setInterval(() => {
3214
+ pollCount++;
3215
+ if (!isRunning) {
3216
+ clearInterval(pollTimer);
3217
+ return;
3218
+ }
3219
+ if (videoRef.current && videoRef.current.readyState >= 2) {
3220
+ clearInterval(pollTimer);
3221
+ videoRef.current.removeEventListener("canplay", onCanPlay);
3222
+ startDetection();
3223
+ } else if (pollCount >= MAX_POLLS) {
3224
+ clearInterval(pollTimer);
3225
+ }
3226
+ }, 300);
3227
+ video == null ? void 0 : video.addEventListener("canplay", onCanPlay, { once: true });
3228
+ return () => {
3229
+ isRunning = false;
3230
+ clearInterval(pollTimer);
3231
+ video == null ? void 0 : video.removeEventListener("canplay", onCanPlay);
3232
+ if (progressIntervalRef.current) {
3233
+ clearInterval(progressIntervalRef.current);
3234
+ progressIntervalRef.current = null;
3235
+ }
3236
+ holdStartTimeRef.current = null;
3237
+ };
3238
+ }
3101
3239
  }, [state.status]);
3102
3240
  const capturePhoto = async () => {
3103
3241
  if (!videoRef.current || !canvasRef.current || isCapturingRef.current) {
@@ -3252,14 +3390,37 @@ function BiometricRegistrationModal({
3252
3390
  }
3253
3391
  )
3254
3392
  ] }) }),
3255
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Não foi possível concluir" }),
3393
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Não foi possível processar" }),
3256
3394
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: state.message }),
3257
3395
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "button-group", children: [
3258
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "secondary-button", onClick: () => dispatch({ type: "RESET" }), children: "Tentar Novamente" }),
3396
+ state.retryable && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "secondary-button", onClick: () => dispatch({ type: "RESET" }), children: "Tentar Novamente" }),
3259
3397
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ghost-button", onClick: onClose, children: "Cancelar" })
3260
3398
  ] })
3261
3399
  ] });
3262
3400
  };
3401
+ const renderCameraLoading = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "processing-container", children: [
3402
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "spinner-wrapper", children: [
3403
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "spinner" }),
3404
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "spinner-ring" })
3405
+ ] }),
3406
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Inicializando câmera" }),
3407
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Aguarde enquanto preparamos a câmera..." }),
3408
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3409
+ "div",
3410
+ {
3411
+ className: "branding",
3412
+ style: {
3413
+ marginTop: "40px",
3414
+ paddingTop: "24px",
3415
+ borderTop: "1px solid rgba(255, 255, 255, 0.1)"
3416
+ },
3417
+ children: [
3418
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "NeoFaceId by" }),
3419
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "brand-name", children: "OCTA" })
3420
+ ]
3421
+ }
3422
+ )
3423
+ ] });
3263
3424
  const renderSuccess = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "result-container success", children: [
3264
3425
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "result-icon success-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "60", height: "60", viewBox: "0 0 60 60", fill: "none", children: [
3265
3426
  /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "30", cy: "30", r: "28", stroke: "currentColor", strokeWidth: "3" }),
@@ -3841,6 +4002,7 @@ function BiometricRegistrationModal({
3841
4002
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "modal-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "modal-content", children: [
3842
4003
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "close-button", onClick: onClose, children: "×" }),
3843
4004
  state.status === "liveness" && renderLivenessDetection(),
4005
+ state.status === "camera-loading" && renderCameraLoading(),
3844
4006
  state.status === "processing" && renderProcessing(),
3845
4007
  state.status === "error" && renderError(),
3846
4008
  state.status === "success" && renderSuccess()
@@ -3848,7 +4010,7 @@ function BiometricRegistrationModal({
3848
4010
  /* @__PURE__ */ jsxRuntimeExports.jsx("canvas", { ref: canvasRef })
3849
4011
  ] });
3850
4012
  }
3851
- let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
4013
+ class BiometricCaptureModal {
3852
4014
  constructor(options) {
3853
4015
  __publicField(this, "modal", null);
3854
4016
  __publicField(this, "video", null);
@@ -4342,7 +4504,7 @@ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
4342
4504
  `;
4343
4505
  document.head.appendChild(style);
4344
4506
  }
4345
- };
4507
+ }
4346
4508
  class BiometricStatusOverlay {
4347
4509
  constructor() {
4348
4510
  __publicField(this, "container", null);
@@ -4366,43 +4528,21 @@ class BiometricStatusOverlay {
4366
4528
  opacity: 1;
4367
4529
  }
4368
4530
  50% {
4369
- transform: scale(1.1);
4531
+ transform: scale(1.05);
4370
4532
  opacity: 0.9;
4371
4533
  }
4372
4534
  }
4373
4535
 
4374
- @keyframes neofaceid-checkmark {
4375
- 0% {
4376
- stroke-dashoffset: 100;
4377
- opacity: 0;
4378
- }
4379
- 50% {
4380
- opacity: 1;
4381
- }
4382
- 100% {
4383
- stroke-dashoffset: 0;
4384
- opacity: 1;
4385
- }
4536
+ @keyframes neofaceid-scaleIn {
4537
+ 0% { transform: scale(0); opacity: 0; }
4538
+ 50% { transform: scale(1.1); }
4539
+ 100% { transform: scale(1); opacity: 1; }
4386
4540
  }
4387
4541
 
4388
4542
  @keyframes neofaceid-errorShake {
4389
- 0%, 100% { transform: translateX(0) scale(1); }
4390
- 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px) scale(1); }
4391
- 20%, 40%, 60%, 80% { transform: translateX(4px) scale(1); }
4392
- }
4393
-
4394
- @keyframes neofaceid-scaleIn {
4395
- 0% {
4396
- transform: scale(0);
4397
- opacity: 0;
4398
- }
4399
- 50% {
4400
- transform: scale(1.1);
4401
- }
4402
- 100% {
4403
- transform: scale(1);
4404
- opacity: 1;
4405
- }
4543
+ 0%, 100% { transform: translateX(0); }
4544
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
4545
+ 20%, 40%, 60%, 80% { transform: translateX(4px); }
4406
4546
  }
4407
4547
 
4408
4548
  .neofaceid-overlay-container {
@@ -4417,6 +4557,7 @@ class BiometricStatusOverlay {
4417
4557
  z-index: 10000;
4418
4558
  pointer-events: none;
4419
4559
  animation: neofaceid-fadeIn 0.3s ease-out;
4560
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
4420
4561
  }
4421
4562
 
4422
4563
  .neofaceid-overlay-container.fade-out {
@@ -4432,10 +4573,10 @@ class BiometricStatusOverlay {
4432
4573
  pointer-events: auto;
4433
4574
  }
4434
4575
 
4435
- .neofaceid-logo-wrapper {
4576
+ .neofaceid-visual-wrapper {
4436
4577
  position: relative;
4437
- width: 120px;
4438
- height: 120px;
4578
+ width: 100px;
4579
+ height: 100px;
4439
4580
  display: flex;
4440
4581
  align-items: center;
4441
4582
  justify-content: center;
@@ -4445,13 +4586,57 @@ class BiometricStatusOverlay {
4445
4586
  width: 80px;
4446
4587
  height: 80px;
4447
4588
  object-fit: contain;
4589
+ transition: all 0.5s ease;
4448
4590
  filter: drop-shadow(0 4px 12px rgba(124, 58, 237, 0.3));
4449
4591
  }
4450
4592
 
4593
+ /* Cores por estado */
4594
+ .neofaceid-state-preparing .neofaceid-logo { filter: drop-shadow(0 0 15px rgba(124, 58, 237, 0.4)); }
4595
+ .neofaceid-state-detecting .neofaceid-logo { filter: hue-rotate(180deg) drop-shadow(0 0 15px rgba(59, 130, 246, 0.6)); }
4596
+ .neofaceid-state-capturing .neofaceid-logo { filter: hue-rotate(45deg) drop-shadow(0 0 15px rgba(245, 158, 11, 0.6)); }
4597
+ .neofaceid-state-verifying .neofaceid-logo { filter: hue-rotate(90deg) drop-shadow(0 0 15px rgba(34, 197, 94, 0.6)); }
4598
+
4451
4599
  .neofaceid-logo.pulsing {
4452
4600
  animation: neofaceid-pulse 2s ease-in-out infinite;
4453
4601
  }
4454
4602
 
4603
+ @keyframes neofaceid-dots {
4604
+ 0%, 20% { opacity: 0; transform: translateY(0); }
4605
+ 50% { opacity: 1; transform: translateY(-2px); }
4606
+ 80%, 100% { opacity: 0; transform: translateY(0); }
4607
+ }
4608
+
4609
+ .neofaceid-status-text {
4610
+ font-size: 15px;
4611
+ font-weight: 500;
4612
+ color: #ffffff;
4613
+ text-align: center;
4614
+ padding: 10px 24px;
4615
+ background: rgba(15, 23, 42, 0.8);
4616
+ backdrop-filter: blur(8px);
4617
+ border-radius: 100px;
4618
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
4619
+ border: 1px solid rgba(255, 255, 255, 0.1);
4620
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
4621
+ animation: neofaceid-fadeIn 0.3s ease-out;
4622
+ display: flex;
4623
+ align-items: center;
4624
+ gap: 2px;
4625
+ }
4626
+
4627
+ .neofaceid-dots {
4628
+ display: inline-flex;
4629
+ margin-left: 2px;
4630
+ }
4631
+
4632
+ .neofaceid-dot {
4633
+ animation: neofaceid-dots 1.5s infinite;
4634
+ opacity: 0;
4635
+ }
4636
+
4637
+ .neofaceid-dot:nth-child(2) { animation-delay: 0.2s; }
4638
+ .neofaceid-dot:nth-child(3) { animation-delay: 0.4s; }
4639
+
4455
4640
  .neofaceid-status-icon {
4456
4641
  position: absolute;
4457
4642
  inset: 0;
@@ -4461,42 +4646,50 @@ class BiometricStatusOverlay {
4461
4646
  }
4462
4647
 
4463
4648
  .neofaceid-success-icon {
4464
- width: 60px;
4465
- height: 60px;
4649
+ width: 80px;
4650
+ height: 80px;
4466
4651
  color: #10b981;
4467
- animation: neofaceid-scaleIn 0.6s ease-out;
4652
+ animation: neofaceid-scaleIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
4468
4653
  }
4469
4654
 
4470
4655
  .neofaceid-error-icon {
4471
- width: 60px;
4472
- height: 60px;
4656
+ width: 80px;
4657
+ height: 80px;
4473
4658
  color: #ef4444;
4474
- animation: neofaceid-errorShake 0.5s ease-out, neofaceid-scaleIn 0.4s ease-out;
4475
- }
4476
-
4477
- @media (max-width: 640px) {
4478
- .neofaceid-logo-wrapper {
4479
- width: 100px;
4480
- height: 100px;
4481
- }
4482
-
4483
- .neofaceid-logo {
4484
- width: 70px;
4485
- height: 70px;
4486
- }
4659
+ animation: neofaceid-errorShake 0.5s linear, neofaceid-scaleIn 0.4s ease-out;
4487
4660
  }
4488
4661
  `;
4489
4662
  }
4663
+ getStatusText() {
4664
+ switch (this.currentStatus) {
4665
+ case "preparing":
4666
+ return "Iniciando câmera";
4667
+ case "detecting":
4668
+ return "Posicione seu rosto";
4669
+ case "capturing":
4670
+ return "Capturando";
4671
+ case "verifying":
4672
+ return "Verificando identidade";
4673
+ case "success":
4674
+ return "Identificado!";
4675
+ case "error":
4676
+ return "Falha na identificação";
4677
+ default:
4678
+ return "";
4679
+ }
4680
+ }
4490
4681
  renderContent() {
4491
- const isPulsing = this.currentStatus === "verifying";
4492
- const showLogo = this.currentStatus === "preparing" || this.currentStatus === "verifying";
4682
+ const isPulsing = ["detecting", "verifying"].includes(this.currentStatus);
4683
+ const showLogo = !["success", "error"].includes(this.currentStatus);
4493
4684
  const showSuccess = this.currentStatus === "success";
4494
4685
  const showError = this.currentStatus === "error";
4686
+ const statusText = this.getStatusText();
4687
+ const showDots = ["preparing", "detecting", "capturing", "verifying"].includes(this.currentStatus);
4495
4688
  return `
4496
4689
  <style>${this.getStyles()}</style>
4497
- <div class="neofaceid-overlay-container">
4690
+ <div class="neofaceid-overlay-container neofaceid-state-${this.currentStatus}">
4498
4691
  <div class="neofaceid-overlay-content">
4499
- <div class="neofaceid-logo-wrapper">
4692
+ <div class="neofaceid-visual-wrapper">
4500
4693
  ${showLogo ? `
4501
4694
  <img
4502
4695
  src="/logo-icone-no-background.png"
@@ -4524,13 +4717,22 @@ class BiometricStatusOverlay {
4524
4717
  </div>
4525
4718
  ` : ""}
4526
4719
  </div>
4720
+
4721
+ ${statusText ? `
4722
+ <div class="neofaceid-status-text">
4723
+ ${statusText}${showDots ? `
4724
+ <span class="neofaceid-dots">
4725
+ <span class="neofaceid-dot">.</span>
4726
+ <span class="neofaceid-dot">.</span>
4727
+ <span class="neofaceid-dot">.</span>
4728
+ </span>
4729
+ ` : ""}
4730
+ </div>
4731
+ ` : ""}
4527
4732
  </div>
4528
4733
  </div>
4529
4734
  `;
4530
4735
  }
4531
- /**
4532
- * Cria e exibe o overlay
4533
- */
4534
4736
  show(status = "preparing") {
4535
4737
  if (this.container) {
4536
4738
  this.updateStatus(status);
@@ -4542,18 +4744,12 @@ class BiometricStatusOverlay {
4542
4744
  this.container.innerHTML = this.renderContent();
4543
4745
  document.body.appendChild(this.container);
4544
4746
  }
4545
- /**
4546
- * Atualiza o status do overlay
4547
- */
4548
4747
  updateStatus(status) {
4549
4748
  this.currentStatus = status;
4550
4749
  if (this.container) {
4551
4750
  this.container.innerHTML = this.renderContent();
4552
4751
  }
4553
4752
  }
4554
- /**
4555
- * Fecha e remove o overlay
4556
- */
4557
4753
  close() {
4558
4754
  if (this.container) {
4559
4755
  const overlayElement = this.container.querySelector(".neofaceid-overlay-container");
@@ -4732,7 +4928,7 @@ function FallbackPromptComponent({
4732
4928
  )
4733
4929
  ] }) }),
4734
4930
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "neofaceid-fallback-title", children: "Não foi possível reconhecer" }),
4735
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "neofaceid-fallback-message", children: (error == null ? void 0 : error.message) || "Não conseguimos reconhecer sua face. Tente novamente ou use email e senha." }),
4931
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "neofaceid-fallback-message", children: (error == null ? void 0 : error.getFriendlyMessage()) || "Não conseguimos reconhecer sua face. Tente novamente ou use email e senha." }),
4736
4932
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "neofaceid-fallback-actions", children: [
4737
4933
  /* @__PURE__ */ jsxRuntimeExports.jsx(
4738
4934
  "button",
@@ -5272,8 +5468,12 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
5272
5468
  checkFace();
5273
5469
  });
5274
5470
  }
5275
- async function attemptLogin(applicationToken, overlay, fastMode = false) {
5276
- overlay.updateStatus("verifying");
5471
+ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry = false) {
5472
+ if (!isRetry) {
5473
+ overlay.updateStatus("preparing");
5474
+ } else {
5475
+ overlay.updateStatus("detecting");
5476
+ }
5277
5477
  const video = document.createElement("video");
5278
5478
  video.style.position = "fixed";
5279
5479
  video.style.top = "-9999px";
@@ -5304,6 +5504,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false) {
5304
5504
  video.onloadedmetadata = () => resolve(void 0);
5305
5505
  }
5306
5506
  });
5507
+ overlay.updateStatus("detecting");
5307
5508
  await new Promise((resolve) => setTimeout(resolve, CAMERA_STABILITY_DELAY));
5308
5509
  let faceDetected = true;
5309
5510
  if (!fastMode) {
@@ -5318,6 +5519,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false) {
5318
5519
  }
5319
5520
  throw new NeoFaceError("Rosto não detectado. Posicione seu rosto na frente da câmera.", ErrorType.VALIDATION_ERROR);
5320
5521
  }
5522
+ overlay.updateStatus("capturing");
5321
5523
  const canvas = document.createElement("canvas");
5322
5524
  canvas.width = video.videoWidth;
5323
5525
  canvas.height = video.videoHeight;
@@ -5352,7 +5554,8 @@ async function attemptLogin(applicationToken, overlay, fastMode = false) {
5352
5554
  0.85
5353
5555
  );
5354
5556
  });
5355
- const result = await loginWithBiometric$1(imageBlob, applicationToken);
5557
+ overlay.updateStatus("verifying");
5558
+ const result = await loginWithBiometric(imageBlob, applicationToken);
5356
5559
  if (!result.success) {
5357
5560
  throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
5358
5561
  }
@@ -5387,7 +5590,7 @@ async function executeBiometricLoginFlow(options) {
5387
5590
  const tryLogin = async () => {
5388
5591
  try {
5389
5592
  attempts++;
5390
- const result = await attemptLogin(applicationToken, overlay, fastMode);
5593
+ const result = await attemptLogin(applicationToken, overlay, fastMode, attempts > 1);
5391
5594
  overlay.updateStatus("success");
5392
5595
  setTimeout(() => {
5393
5596
  overlay.close();
@@ -5768,8 +5971,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
5768
5971
  initializeBiometricDetection,
5769
5972
  isAdvancedDetectionAvailable
5770
5973
  }, Symbol.toStringTag, { value: "Module" }));
5771
- const VERSION = "1.10.0";
5772
- const RELEASE_DATE = "2026-01-21";
5974
+ const VERSION = "1.13.0";
5975
+ const RELEASE_DATE = "2026-02-20";
5773
5976
  class OnboardingCaptureModal {
5774
5977
  constructor(options) {
5775
5978
  __publicField(this, "overlay", null);
@@ -5995,11 +6198,9 @@ class NeoFaceID {
5995
6198
  */
5996
6199
  constructor(config) {
5997
6200
  __publicField(this, "appToken");
5998
- __publicField(this, "baseUrl");
5999
6201
  __publicField(this, "signature");
6000
6202
  __publicField(this, "sessionData");
6001
6203
  this.appToken = config.appToken;
6002
- this.baseUrl = config.baseUrl || getBaseUrl();
6003
6204
  this.signature = config.signature;
6004
6205
  this.sessionData = config.sessionData;
6005
6206
  if (this.signature && !this.validateSignatureFormat(this.signature)) {
@@ -6088,7 +6289,7 @@ class NeoFaceID {
6088
6289
  * ```
6089
6290
  */
6090
6291
  async loginRecognition(options) {
6091
- const { biometricData, typeOfIdentification, purpose, confidenceThreshold = 0.8 } = options;
6292
+ const { biometricData, purpose, confidenceThreshold = 0.8 } = options;
6092
6293
  const imageBlobs = Array.isArray(biometricData) ? biometricData : [biometricData];
6093
6294
  if (imageBlobs.length === 0) {
6094
6295
  throw new NeoFaceError(
@@ -7431,7 +7632,12 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
7431
7632
  const container = document.createElement("div");
7432
7633
  container.id = "neoface-biometric-registration-container";
7433
7634
  document.body.appendChild(container);
7635
+ let rootRef = null;
7434
7636
  const cleanup = () => {
7637
+ if (rootRef) {
7638
+ rootRef.unmount();
7639
+ rootRef = null;
7640
+ }
7435
7641
  if (document.body.contains(container)) {
7436
7642
  document.body.removeChild(container);
7437
7643
  }
@@ -7441,7 +7647,6 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
7441
7647
  personData,
7442
7648
  applicationToken,
7443
7649
  useRealApi: (options == null ? void 0 : options.useRealApi) ?? true,
7444
- // Sempre usa API real em produção
7445
7650
  onClose: cleanup,
7446
7651
  onSuccess: (result) => {
7447
7652
  cleanup();
@@ -7452,8 +7657,8 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
7452
7657
  callbacks.onError("BIOMETRIC_REGISTRATION_ERROR", error);
7453
7658
  }
7454
7659
  });
7455
- const root = clientExports.createRoot(container);
7456
- root.render(modalElement);
7660
+ rootRef = clientExports.createRoot(container);
7661
+ rootRef.render(modalElement);
7457
7662
  }).catch((error) => {
7458
7663
  cleanup();
7459
7664
  callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
@@ -7463,16 +7668,29 @@ function startLivenessCapture(applicationToken, callbacks) {
7463
7668
  const container = document.createElement("div");
7464
7669
  container.id = "neoface-liveness-capture-container";
7465
7670
  document.body.appendChild(container);
7671
+ let completed = false;
7672
+ let rootRef = null;
7466
7673
  const cleanup = () => {
7674
+ if (rootRef) {
7675
+ rootRef.unmount();
7676
+ rootRef = null;
7677
+ }
7467
7678
  if (document.body.contains(container)) {
7468
7679
  document.body.removeChild(container);
7469
7680
  }
7470
7681
  };
7682
+ const handleClose = () => {
7683
+ cleanup();
7684
+ if (!completed && callbacks.onCancel) {
7685
+ callbacks.onCancel();
7686
+ }
7687
+ };
7471
7688
  validateToken(applicationToken).then(() => {
7472
7689
  const modalElement = React.createElement(BiometricRegistrationModal, {
7473
7690
  applicationToken,
7474
- onClose: cleanup,
7691
+ onClose: handleClose,
7475
7692
  onPhotosCaptured: (photos) => {
7693
+ completed = true;
7476
7694
  callbacks.onSuccess(photos);
7477
7695
  },
7478
7696
  onError: (error) => {
@@ -7480,15 +7698,15 @@ function startLivenessCapture(applicationToken, callbacks) {
7480
7698
  callbacks.onError("LIVENESS_CAPTURE_ERROR", error);
7481
7699
  }
7482
7700
  });
7483
- const root = clientExports.createRoot(container);
7484
- root.render(modalElement);
7701
+ rootRef = clientExports.createRoot(container);
7702
+ rootRef.render(modalElement);
7485
7703
  }).catch((error) => {
7486
7704
  cleanup();
7487
7705
  callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
7488
7706
  });
7489
7707
  }
7490
7708
  export {
7491
- BiometricCaptureModal$1 as BiometricCaptureModal,
7709
+ BiometricCaptureModal,
7492
7710
  BiometricRegistrationModal,
7493
7711
  BiometricStatusOverlay,
7494
7712
  DocumentCaptureModal,
@@ -7516,7 +7734,7 @@ export {
7516
7734
  initializeBiometricDetection,
7517
7735
  isAdvancedDetectionAvailable,
7518
7736
  isInitialized,
7519
- loginWithBiometric$1 as loginWithBiometric,
7737
+ loginWithBiometric,
7520
7738
  loginWithEmail,
7521
7739
  preloadFaceDetectionModels,
7522
7740
  recognize,