@neofaceid/web-sdk 1.14.0 → 1.15.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.
@@ -1652,6 +1652,110 @@ const loginWithBiometric = async (image, applicationToken) => {
1652
1652
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1653
1653
  }
1654
1654
  };
1655
+ const loginWithBiometricSSE = async (image, applicationToken) => {
1656
+ var _a;
1657
+ ensureSecureContext();
1658
+ const compressedImage = await compressImage(image);
1659
+ const arrayBuffer = await compressedImage.arrayBuffer();
1660
+ const base64Image = btoa(
1661
+ new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), "")
1662
+ );
1663
+ const controller = createTimeoutController();
1664
+ let response;
1665
+ try {
1666
+ response = await fetch(`${getApiBaseUrl()}/api/v1/auth/recognition/face/external/`, {
1667
+ method: "POST",
1668
+ headers: {
1669
+ "Content-Type": "application/json",
1670
+ "X-App-Token": applicationToken
1671
+ },
1672
+ body: JSON.stringify({ image: base64Image, purpose: "LOGIN" }),
1673
+ signal: controller.signal
1674
+ });
1675
+ } catch (error) {
1676
+ if (error instanceof Error) {
1677
+ if (error.name === "AbortError") {
1678
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1679
+ }
1680
+ if (error instanceof NeoFaceError) throw error;
1681
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1682
+ }
1683
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1684
+ }
1685
+ if (!response.ok) {
1686
+ if (response.status === 401 || response.status === 403) {
1687
+ throw new NeoFaceError("Invalid application token", ErrorType.INVALID_TOKEN);
1688
+ }
1689
+ throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
1690
+ }
1691
+ const initData = await response.json();
1692
+ if (!initData.success || !((_a = initData.data) == null ? void 0 : _a.task_id)) {
1693
+ throw new NeoFaceError(initData.message || "Failed to start login", ErrorType.LOGIN_FAILED);
1694
+ }
1695
+ const taskId = initData.data.task_id;
1696
+ return new Promise((resolve, reject) => {
1697
+ const sseUrl = `${getApiBaseUrl()}/api/v1/auth/recognition/face/external/login/events/${taskId}/`;
1698
+ const eventSource = new EventSource(sseUrl);
1699
+ const timeoutId = setTimeout(() => {
1700
+ eventSource.close();
1701
+ reject(new NeoFaceError("Login timeout", ErrorType.NETWORK));
1702
+ }, 31e4);
1703
+ const cleanup = () => {
1704
+ clearTimeout(timeoutId);
1705
+ eventSource.close();
1706
+ };
1707
+ eventSource.addEventListener("success", (event) => {
1708
+ var _a2, _b, _c, _d, _e, _f;
1709
+ cleanup();
1710
+ try {
1711
+ const data = JSON.parse(event.data);
1712
+ resolve({
1713
+ success: true,
1714
+ accessToken: data.token || data.access_token || "",
1715
+ alias: ((_a2 = data.user) == null ? void 0 : _a2.alias) || data.alias || "",
1716
+ user: {
1717
+ id: ((_b = data.user) == null ? void 0 : _b.id) || "",
1718
+ email: ((_c = data.user) == null ? void 0 : _c.email) || "",
1719
+ role: ((_d = data.user) == null ? void 0 : _d.role) || "",
1720
+ validated: ((_e = data.user) == null ? void 0 : _e.validated) ?? false,
1721
+ active: ((_f = data.user) == null ? void 0 : _f.active) ?? false
1722
+ },
1723
+ person: data.person ? { name: data.person.name || "", birth_date: data.person.birth_date || "" } : void 0,
1724
+ confidence_score: data.confidence_score,
1725
+ recognition_id: data.recognition_id ?? data.history_id
1726
+ });
1727
+ } catch {
1728
+ reject(new NeoFaceError("Erro ao processar resposta de login", ErrorType.API_ERROR));
1729
+ }
1730
+ });
1731
+ eventSource.addEventListener("failed", (event) => {
1732
+ cleanup();
1733
+ try {
1734
+ const data = JSON.parse(event.data);
1735
+ reject(new NeoFaceError(data.message || "Login failed", ErrorType.LOGIN_FAILED));
1736
+ } catch {
1737
+ reject(new NeoFaceError("Login failed", ErrorType.LOGIN_FAILED));
1738
+ }
1739
+ });
1740
+ eventSource.addEventListener("error", (event) => {
1741
+ cleanup();
1742
+ try {
1743
+ const data = JSON.parse(event.data || "{}");
1744
+ reject(new NeoFaceError(data.message || "Processing error", ErrorType.API_ERROR));
1745
+ } catch {
1746
+ reject(new NeoFaceError("Processing error", ErrorType.API_ERROR));
1747
+ }
1748
+ });
1749
+ eventSource.addEventListener("timeout", () => {
1750
+ cleanup();
1751
+ reject(new NeoFaceError("Login timeout", ErrorType.NETWORK));
1752
+ });
1753
+ eventSource.onerror = () => {
1754
+ cleanup();
1755
+ reject(new NeoFaceError("SSE connection error", ErrorType.NETWORK));
1756
+ };
1757
+ });
1758
+ };
1655
1759
  const recognizeBiometric = async (image, applicationToken, livenessCheck = true, confidenceThreshold = 0.8) => {
1656
1760
  ensureSecureContext();
1657
1761
  const controller = createTimeoutController();
@@ -5134,6 +5238,7 @@ class EmailPasswordModal {
5134
5238
  <div class="neoface-email-modal-content">
5135
5239
  <h2>${this.options.title || "Login com Email e Senha"}</h2>
5136
5240
  <p>${this.options.subtitle || "Informe seus dados para login."}</p>
5241
+ <div id="login-error" class="neoface-email-error" style="display:none"></div>
5137
5242
  <form id="email-password-form">
5138
5243
  <input type="email" id="email" placeholder="Email" required />
5139
5244
  <input type="password" id="password" placeholder="Senha" required />
@@ -5335,6 +5440,18 @@ class EmailPasswordModal {
5335
5440
  letter-spacing: 0.3px;
5336
5441
  }
5337
5442
 
5443
+ .neoface-email-error {
5444
+ background: rgba(239, 68, 68, 0.15);
5445
+ border: 1px solid rgba(239, 68, 68, 0.4);
5446
+ border-radius: 10px;
5447
+ color: #fca5a5;
5448
+ font-size: 14px;
5449
+ padding: 12px 16px;
5450
+ margin-bottom: 16px;
5451
+ text-align: center;
5452
+ animation: fadeIn 0.2s ease-out;
5453
+ }
5454
+
5338
5455
  @media (max-width: 640px) {
5339
5456
  .neoface-email-modal-content {
5340
5457
  padding: 24px;
@@ -5352,20 +5469,40 @@ class EmailPasswordModal {
5352
5469
  event.preventDefault();
5353
5470
  const emailInput = this.modalElement.querySelector("#email");
5354
5471
  const passwordInput = this.modalElement.querySelector("#password");
5472
+ const submitBtn = this.modalElement.querySelector(
5473
+ ".neoface-email-submit-btn"
5474
+ );
5475
+ const errorDiv = this.modalElement.querySelector("#login-error");
5476
+ const showError = (msg) => {
5477
+ errorDiv.textContent = msg;
5478
+ errorDiv.style.display = "block";
5479
+ };
5480
+ errorDiv.style.display = "none";
5481
+ errorDiv.textContent = "";
5355
5482
  const email = emailInput.value.trim();
5356
5483
  const password = passwordInput.value.trim();
5357
5484
  if (!email || !password) {
5358
- this.options.onError(
5359
- new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR)
5360
- );
5485
+ showError("Preencha o email e a senha para continuar.");
5361
5486
  return;
5362
5487
  }
5488
+ submitBtn.disabled = true;
5489
+ submitBtn.textContent = "Entrando...";
5363
5490
  try {
5364
5491
  const result = await loginWithEmail(email, password, this.options.applicationToken);
5365
5492
  this.options.onSuccess(result);
5366
5493
  this.close();
5367
5494
  } catch (error) {
5368
- this.options.onError(error);
5495
+ submitBtn.disabled = false;
5496
+ submitBtn.textContent = "Entrar";
5497
+ const isCredentialError = error instanceof NeoFaceError && (error.type === ErrorType.INVALID_TOKEN || error.type === ErrorType.API_ERROR && error.message.includes("400"));
5498
+ if (isCredentialError) {
5499
+ showError("Email ou senha incorretos. Verifique seus dados e tente novamente.");
5500
+ passwordInput.value = "";
5501
+ passwordInput.focus();
5502
+ } else {
5503
+ showError("Ocorreu um erro inesperado. Tente novamente mais tarde.");
5504
+ this.options.onError(error);
5505
+ }
5369
5506
  }
5370
5507
  }
5371
5508
  }
@@ -5507,7 +5644,12 @@ async function captureFaceSilently() {
5507
5644
  }
5508
5645
  }).catch((err) => {
5509
5646
  cleanup();
5510
- reject(new NeoFaceError("Erro ao reproduzir câmera: " + err.message, ErrorType.CAMERA_ERROR));
5647
+ reject(
5648
+ new NeoFaceError(
5649
+ "Erro ao reproduzir câmera: " + err.message,
5650
+ ErrorType.CAMERA_ERROR
5651
+ )
5652
+ );
5511
5653
  });
5512
5654
  } else {
5513
5655
  video.onloadedmetadata = () => setTimeout(tryCapture, CAMERA_READY_DELAY);
@@ -5649,7 +5791,10 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
5649
5791
  if (video.parentElement) {
5650
5792
  video.parentElement.removeChild(video);
5651
5793
  }
5652
- throw new NeoFaceError("Rosto não detectado. Posicione seu rosto na frente da câmera.", ErrorType.VALIDATION_ERROR);
5794
+ throw new NeoFaceError(
5795
+ "Rosto não detectado. Posicione seu rosto na frente da câmera.",
5796
+ ErrorType.VALIDATION_ERROR
5797
+ );
5653
5798
  }
5654
5799
  overlay.updateStatus("capturing");
5655
5800
  const canvas = document.createElement("canvas");
@@ -5687,7 +5832,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
5687
5832
  );
5688
5833
  });
5689
5834
  overlay.updateStatus("verifying");
5690
- const result = await loginWithBiometric(imageBlob, applicationToken);
5835
+ const result = await loginWithBiometricSSE(imageBlob, applicationToken);
5691
5836
  if (!result.success) {
5692
5837
  throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
5693
5838
  }
@@ -5709,7 +5854,14 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
5709
5854
  }
5710
5855
  }
5711
5856
  async function executeBiometricLoginFlow(options) {
5712
- const { applicationToken, onSuccess, onError, onFallbackRequest, onCancel, fastMode = false } = options;
5857
+ const {
5858
+ applicationToken,
5859
+ onSuccess,
5860
+ onError,
5861
+ onFallbackRequest,
5862
+ onCancel,
5863
+ fastMode = false
5864
+ } = options;
5713
5865
  const overlay = new BiometricStatusOverlay();
5714
5866
  const fallbackPrompt = new FallbackPrompt();
5715
5867
  let attempts = 0;
@@ -6103,7 +6255,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
6103
6255
  initializeBiometricDetection,
6104
6256
  isAdvancedDetectionAvailable
6105
6257
  }, Symbol.toStringTag, { value: "Module" }));
6106
- const VERSION = "1.14.0";
6258
+ const VERSION = "1.15.0";
6107
6259
  const RELEASE_DATE = "2026-03-04";
6108
6260
  class OnboardingCaptureModal {
6109
6261
  constructor(options) {