@neofaceid/web-sdk 1.3.0 → 1.4.2

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.
@@ -4,6 +4,7 @@ var __publicField = (obj, key, value) => {
4
4
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  return value;
6
6
  };
7
+ var _a;
7
8
  import require$$0$1, { useRef, useState, useCallback, useReducer, useEffect } from "react";
8
9
  import require$$0 from "react-dom";
9
10
  import * as faceapi from "face-api.js";
@@ -1006,7 +1007,7 @@ const useCamera = () => {
1006
1007
  };
1007
1008
  const startCamera = useCallback(
1008
1009
  async (constraints) => {
1009
- var _a;
1010
+ var _a2;
1010
1011
  console.log("🎬 [useCamera] startCamera chamado", {
1011
1012
  hasExistingStream: !!streamRef.current,
1012
1013
  isInitializing: isInitializingRef.current
@@ -1048,7 +1049,7 @@ const useCamera = () => {
1048
1049
  console.log("📋 [useCamera] Detalhes do erro:", {
1049
1050
  name: error.name,
1050
1051
  message: error.message,
1051
- stack: (_a = error.stack) == null ? void 0 : _a.substring(0, 200)
1052
+ stack: (_a2 = error.stack) == null ? void 0 : _a2.substring(0, 200)
1052
1053
  });
1053
1054
  if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
1054
1055
  throw new CameraPermissionDenied(
@@ -1135,10 +1136,11 @@ class NeoFaceError extends Error {
1135
1136
  this.type = type;
1136
1137
  }
1137
1138
  }
1138
- const API_BASE_URL = "https://core.neofaceid.com.br";
1139
+ const API_BASE_URL = ((_a = { "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false }) == null ? void 0 : _a.VITE_API_BASE_URL) || "http://localhost:8000";
1139
1140
  const REQUEST_TIMEOUT = 1e4;
1140
1141
  const ensureSecureContext = () => {
1141
- if (!window.isSecureContext) {
1142
+ const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname === "";
1143
+ if (!window.isSecureContext && !isLocalhost) {
1142
1144
  throw new NeoFaceError(
1143
1145
  "Secure context (HTTPS) is required for this operation",
1144
1146
  ErrorType.NETWORK
@@ -1184,7 +1186,7 @@ const validateToken = async (applicationToken) => {
1184
1186
  ensureSecureContext();
1185
1187
  const controller = createTimeoutController();
1186
1188
  try {
1187
- const response = await fetch(`${API_BASE_URL}/api/v1/external/auth/validate-token/`, {
1189
+ const response = await fetch(`${API_BASE_URL}/api/v1/auth/application/validate_token/`, {
1188
1190
  method: "POST",
1189
1191
  headers: {
1190
1192
  "Content-Type": "application/json",
@@ -1239,6 +1241,41 @@ const getOnboardingDetails = async (applicationToken, onboardingToken) => {
1239
1241
  throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1240
1242
  }
1241
1243
  };
1244
+ const validateOnboardingToken = async (applicationToken, onboardingToken) => {
1245
+ ensureSecureContext();
1246
+ const controller = createTimeoutController();
1247
+ try {
1248
+ const response = await fetch(`${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/`, {
1249
+ method: "GET",
1250
+ headers: {
1251
+ "X-App-Token": applicationToken
1252
+ },
1253
+ signal: controller.signal
1254
+ });
1255
+ if (!response.ok) {
1256
+ if (response.status === 401 || response.status === 403) {
1257
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
1258
+ }
1259
+ if (response.status === 404) {
1260
+ return false;
1261
+ }
1262
+ throw new NeoFaceError(`Falha ao validar token (${response.status})`, ErrorType.NETWORK);
1263
+ }
1264
+ const data = await response.json();
1265
+ return (data == null ? void 0 : data.is_valid) === true;
1266
+ } catch (error) {
1267
+ if (error instanceof Error) {
1268
+ if (error.name === "AbortError") {
1269
+ throw new NeoFaceError("Tempo de requisição excedido", ErrorType.NETWORK);
1270
+ }
1271
+ if (error instanceof NeoFaceError) {
1272
+ throw error;
1273
+ }
1274
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1275
+ }
1276
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1277
+ }
1278
+ };
1242
1279
  const completeOnboarding = async (applicationToken, onboardingToken, faceImage, documentImage) => {
1243
1280
  ensureSecureContext();
1244
1281
  const controller = createTimeoutController();
@@ -1290,6 +1327,82 @@ const completeOnboarding = async (applicationToken, onboardingToken, faceImage,
1290
1327
  throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1291
1328
  }
1292
1329
  };
1330
+ const completeOnboardingWithData = async (applicationToken, onboardingToken, personData, personImages, documentImage) => {
1331
+ ensureSecureContext();
1332
+ if (!personImages || personImages.length === 0) {
1333
+ throw new NeoFaceError("Pelo menos uma imagem da pessoa é obrigatória", ErrorType.VALIDATION_ERROR);
1334
+ }
1335
+ const controller = createTimeoutController();
1336
+ try {
1337
+ const compressedPersonImages = await Promise.all(
1338
+ personImages.map((img) => compressImage(img))
1339
+ );
1340
+ const compressedDocument = await compressImage(documentImage);
1341
+ const formData = new FormData();
1342
+ compressedPersonImages.forEach((img, index) => {
1343
+ formData.append("person_images", img, `person_${index + 1}.jpg`);
1344
+ });
1345
+ formData.append("document_image", compressedDocument, "document.jpg");
1346
+ if (personData.name) {
1347
+ formData.append("name", personData.name);
1348
+ }
1349
+ if (personData.birth_date) {
1350
+ formData.append("birth_date", personData.birth_date);
1351
+ }
1352
+ if (personData.cpf) {
1353
+ formData.append("cpf", personData.cpf);
1354
+ }
1355
+ if (personData.cnpj) {
1356
+ formData.append("cnpj", personData.cnpj);
1357
+ }
1358
+ if (personData.email) {
1359
+ formData.append("email", personData.email);
1360
+ }
1361
+ const response = await fetch(
1362
+ `${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/complete`,
1363
+ {
1364
+ method: "POST",
1365
+ headers: {
1366
+ "X-App-Token": applicationToken
1367
+ },
1368
+ body: formData,
1369
+ signal: controller.signal
1370
+ }
1371
+ );
1372
+ const data = await response.json();
1373
+ if (!response.ok) {
1374
+ if (response.status === 401 || response.status === 403) {
1375
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
1376
+ }
1377
+ if (response.status === 404) {
1378
+ throw new NeoFaceError("Link de onboarding não encontrado", ErrorType.NOT_FOUND);
1379
+ }
1380
+ throw new NeoFaceError((data == null ? void 0 : data.error) || (data == null ? void 0 : data.message) || "Falha ao concluir onboarding", ErrorType.NETWORK);
1381
+ }
1382
+ return {
1383
+ success: !!data.success,
1384
+ message: data.message || "Onboarding concluído com sucesso",
1385
+ person_id: data.person_id,
1386
+ identity_data_id: data.identity_data_id,
1387
+ confidence_score: data.confidence_score,
1388
+ processing_time: data.processing_time,
1389
+ liveness_passed: data.liveness_passed,
1390
+ document_verified: data.document_verified,
1391
+ person_created: data.person_created
1392
+ };
1393
+ } catch (error) {
1394
+ if (error instanceof Error) {
1395
+ if (error.name === "AbortError") {
1396
+ throw new NeoFaceError("Tempo de requisição excedido", ErrorType.NETWORK);
1397
+ }
1398
+ if (error instanceof NeoFaceError) {
1399
+ throw error;
1400
+ }
1401
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1402
+ }
1403
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1404
+ }
1405
+ };
1293
1406
  const recognize = async (image, applicationToken) => {
1294
1407
  ensureSecureContext();
1295
1408
  const controller = createTimeoutController();
@@ -1332,8 +1445,8 @@ const recognize = async (image, applicationToken) => {
1332
1445
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1333
1446
  }
1334
1447
  };
1335
- const loginWithBiometric = async (image, applicationToken) => {
1336
- var _a, _b, _c, _d, _e;
1448
+ const loginWithBiometric$1 = async (image, applicationToken) => {
1449
+ var _a2, _b, _c, _d, _e;
1337
1450
  ensureSecureContext();
1338
1451
  const controller = createTimeoutController();
1339
1452
  const compressedImage = await compressImage(image);
@@ -1363,7 +1476,7 @@ const loginWithBiometric = async (image, applicationToken) => {
1363
1476
  accessToken: data.accessToken,
1364
1477
  alias: data.alias,
1365
1478
  user: {
1366
- id: ((_a = data.user) == null ? void 0 : _a.id) || "",
1479
+ id: ((_a2 = data.user) == null ? void 0 : _a2.id) || "",
1367
1480
  email: ((_b = data.user) == null ? void 0 : _b.email) || "",
1368
1481
  role: ((_c = data.user) == null ? void 0 : _c.role) || "",
1369
1482
  validated: ((_d = data.user) == null ? void 0 : _d.validated) || false,
@@ -1576,6 +1689,7 @@ const registerPersonWithoutFace = async (personData, applicationToken) => {
1576
1689
  }
1577
1690
  };
1578
1691
  const registerPersonWithBiometric = async (personData, facePhotos, applicationToken) => {
1692
+ var _a2, _b, _c, _d, _e, _f, _g, _h;
1579
1693
  ensureSecureContext();
1580
1694
  if (!facePhotos || facePhotos.length === 0) {
1581
1695
  throw new NeoFaceError("At least one face photo is required for biometric registration", ErrorType.VALIDATION_ERROR);
@@ -1622,29 +1736,56 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
1622
1736
  }
1623
1737
  try {
1624
1738
  const errorData = await response.json();
1739
+ if (errorData.errors) {
1740
+ const errorMessages = Object.entries(errorData.errors).map(([field, messages]) => {
1741
+ const fieldName = field === "password" ? "senha" : field === "password_confirm" ? "confirmação de senha" : field;
1742
+ const messageList = Array.isArray(messages) ? messages.join(", ") : String(messages);
1743
+ return `${fieldName}: ${messageList}`;
1744
+ }).join("; ");
1745
+ throw new NeoFaceError(`Erro de validação: ${errorMessages}`, ErrorType.VALIDATION_ERROR);
1746
+ }
1625
1747
  if (errorData.error === "fraud_detected") {
1626
1748
  throw new NeoFaceError("Fraud detected in biometric images. Registration blocked.", ErrorType.VALIDATION_ERROR);
1627
1749
  }
1628
- if (errorData.error === "user_exists") {
1750
+ if (errorData.error === "user_exists" || errorData.error_code === "ALREADY_EXISTS") {
1629
1751
  throw new NeoFaceError("User with this email already exists", ErrorType.VALIDATION_ERROR);
1630
1752
  }
1631
1753
  if (errorData.error === "person_exists") {
1632
1754
  throw new NeoFaceError("Person with this CPF already exists", ErrorType.VALIDATION_ERROR);
1633
1755
  }
1634
- throw new NeoFaceError(errorData.message || `Server returned status ${response.status}`, ErrorType.NETWORK);
1756
+ const errorMessage = errorData.message || errorData.error || `Server returned status ${response.status}`;
1757
+ throw new NeoFaceError(errorMessage, ErrorType.VALIDATION_ERROR);
1635
1758
  } catch (parseError) {
1759
+ if (parseError instanceof NeoFaceError) {
1760
+ throw parseError;
1761
+ }
1636
1762
  throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1637
1763
  }
1638
1764
  }
1639
1765
  const data = await response.json();
1766
+ if (response.status === 202 && ((_a2 = data.data) == null ? void 0 : _a2.task_id)) {
1767
+ return {
1768
+ success: data.success,
1769
+ message: data.message || "Registration is being processed. You will receive an email when completed.",
1770
+ task_id: data.data.task_id,
1771
+ status: data.data.status || "processing",
1772
+ person_id: void 0,
1773
+ user_id: void 0,
1774
+ person_validated: void 0,
1775
+ face_processing: void 0,
1776
+ onboarding_link: void 0
1777
+ };
1778
+ }
1640
1779
  return {
1641
1780
  success: data.success,
1642
1781
  message: data.message,
1643
- person_id: data.person_id,
1644
- user_id: data.user_id,
1645
- person_validated: data.person_validated,
1646
- face_processing: data.face_processing,
1647
- onboarding_link: data.onboarding_link
1782
+ person_id: ((_b = data.data) == null ? void 0 : _b.person_id) || data.person_id,
1783
+ user_id: ((_c = data.data) == null ? void 0 : _c.user_id) || data.user_id,
1784
+ person_validated: ((_d = data.data) == null ? void 0 : _d.person_validated) || data.person_validated,
1785
+ face_processing: ((_e = data.data) == null ? void 0 : _e.face_processing) || data.face_processing,
1786
+ onboarding_link: ((_f = data.data) == null ? void 0 : _f.onboarding_link) || data.onboarding_link,
1787
+ task_id: (_g = data.data) == null ? void 0 : _g.task_id,
1788
+ status: (_h = data.data) == null ? void 0 : _h.status
1648
1789
  };
1649
1790
  } catch (error) {
1650
1791
  if (error instanceof Error) {
@@ -1706,7 +1847,7 @@ const loginWithEmail = async (email, password, applicationToken) => {
1706
1847
  }
1707
1848
  };
1708
1849
  const identifyPerson = async (image, applicationToken) => {
1709
- var _a, _b;
1850
+ var _a2, _b;
1710
1851
  ensureSecureContext();
1711
1852
  const controller = createTimeoutController();
1712
1853
  const compressedImage = await compressImage(image);
@@ -1742,7 +1883,7 @@ const identifyPerson = async (image, applicationToken) => {
1742
1883
  if (!data.success) {
1743
1884
  throw new NeoFaceError(data.message || "Person identification failed", ErrorType.RECOGNITION_FAILED);
1744
1885
  }
1745
- const personName = ((_a = data.person) == null ? void 0 : _a.name) || "";
1886
+ const personName = ((_a2 = data.person) == null ? void 0 : _a2.name) || "";
1746
1887
  const birthDateStr = (_b = data.person) == null ? void 0 : _b.birth_date;
1747
1888
  if (!personName) {
1748
1889
  throw new NeoFaceError("Person name not found in response", ErrorType.PERSON_NOT_FOUND);
@@ -2018,8 +2159,8 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
2018
2159
  canvasRef.current.height = videoRef.current.videoHeight;
2019
2160
  context.drawImage(videoRef.current, 0, 0, canvasRef.current.width, canvasRef.current.height);
2020
2161
  const blob = await new Promise((resolve, reject) => {
2021
- var _a;
2022
- (_a = canvasRef.current) == null ? void 0 : _a.toBlob(
2162
+ var _a2;
2163
+ (_a2 = canvasRef.current) == null ? void 0 : _a2.toBlob(
2023
2164
  (b) => {
2024
2165
  if (b)
2025
2166
  resolve(b);
@@ -2156,8 +2297,8 @@ function BiometricRegistrationModal({
2156
2297
  onClose,
2157
2298
  onSuccess,
2158
2299
  onError,
2159
- useRealApi = false
2160
- // 🚀 Por padrão, modo simulado
2300
+ useRealApi = true
2301
+ // Sempre usa API real em produção
2161
2302
  }) {
2162
2303
  const videoRef = useRef(null);
2163
2304
  const canvasRef = useRef(null);
@@ -2419,8 +2560,8 @@ function BiometricRegistrationModal({
2419
2560
  }
2420
2561
  };
2421
2562
  const startTimer = setTimeout(() => {
2422
- var _a;
2423
- if (isRunning && ((_a = videoRef.current) == null ? void 0 : _a.readyState) === 4) {
2563
+ var _a2;
2564
+ if (isRunning && ((_a2 = videoRef.current) == null ? void 0 : _a2.readyState) === 4) {
2424
2565
  detectFace2();
2425
2566
  }
2426
2567
  }, 1e3);
@@ -2481,60 +2622,19 @@ function BiometricRegistrationModal({
2481
2622
  };
2482
2623
  const processRegistration = async (photos) => {
2483
2624
  try {
2484
- if (useRealApi) {
2485
- console.log("🚀 Fazendo requisição REAL ao backend de produção...");
2486
- const result2 = await registerPersonWithBiometric(personData, photos, applicationToken);
2487
- const adaptedResult = {
2488
- success: result2.success,
2489
- message: result2.message,
2490
- person_id: result2.person_id,
2491
- user_id: result2.user_id,
2492
- person_validated: result2.person_validated,
2493
- face_processing: result2.face_processing,
2494
- onboarding_link: result2.onboarding_link
2495
- };
2496
- dispatch({ type: "SUCCESS" });
2497
- onSuccess(adaptedResult);
2498
- return;
2499
- }
2500
- console.log("🎭 Usando modo SIMULADO para testes...");
2501
- const facePhotosBase64 = await Promise.all(
2502
- photos.map(async (photo) => {
2503
- return new Promise((resolve, reject) => {
2504
- const reader = new FileReader();
2505
- reader.onload = () => {
2506
- const result2 = reader.result;
2507
- resolve(result2);
2508
- };
2509
- reader.onerror = reject;
2510
- reader.readAsDataURL(photo);
2511
- });
2512
- })
2513
- );
2514
- await new Promise((resolve) => setTimeout(resolve, 2e3));
2515
- const result = {
2516
- success: true,
2517
- message: "Registro biométrico realizado com sucesso",
2518
- person_id: "test-person-123",
2519
- user_id: "test-user-456",
2520
- person_validated: true,
2521
- face_processing: {
2522
- total_photos_captured: photos.length,
2523
- photo_sizes: photos.map((p, i) => ({
2524
- photo_index: i + 1,
2525
- size_kb: Math.round(p.size / 1024)
2526
- })),
2527
- // Mostra apenas preview dos primeiros 50 caracteres de cada foto em base64
2528
- photos_preview: facePhotosBase64.map((b64, i) => ({
2529
- photo_index: i + 1,
2530
- format: "data:image/jpeg;base64,...",
2531
- preview: b64.substring(0, 50) + "...",
2532
- full_length: b64.length
2533
- }))
2534
- }
2625
+ console.log("🚀 Fazendo requisição ao backend...");
2626
+ const result = await registerPersonWithBiometric(personData, photos, applicationToken);
2627
+ const adaptedResult = {
2628
+ success: result.success,
2629
+ message: result.message,
2630
+ person_id: result.person_id,
2631
+ user_id: result.user_id,
2632
+ person_validated: result.person_validated,
2633
+ face_processing: result.face_processing,
2634
+ onboarding_link: result.onboarding_link
2535
2635
  };
2536
2636
  dispatch({ type: "SUCCESS" });
2537
- onSuccess(result);
2637
+ onSuccess(adaptedResult);
2538
2638
  } catch (error) {
2539
2639
  const errorMessage = error instanceof Error ? error.message : "Erro desconhecido";
2540
2640
  dispatch({ type: "ERROR", message: errorMessage });
@@ -3216,7 +3316,7 @@ function BiometricRegistrationModal({
3216
3316
  /* @__PURE__ */ jsxRuntimeExports.jsx("canvas", { ref: canvasRef })
3217
3317
  ] });
3218
3318
  }
3219
- class BiometricCaptureModal {
3319
+ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3220
3320
  constructor(options) {
3221
3321
  __publicField(this, "modal", null);
3222
3322
  __publicField(this, "video", null);
@@ -3418,21 +3518,21 @@ class BiometricCaptureModal {
3418
3518
  const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
3419
3519
  const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
3420
3520
  closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
3421
- var _a, _b;
3521
+ var _a2, _b;
3422
3522
  this.close();
3423
- (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
3523
+ (_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
3424
3524
  });
3425
3525
  cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
3426
- var _a, _b;
3526
+ var _a2, _b;
3427
3527
  this.close();
3428
- (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
3528
+ (_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
3429
3529
  });
3430
3530
  captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
3431
3531
  this.captureImage();
3432
3532
  });
3433
3533
  this.modal.addEventListener("click", (e) => {
3434
- var _a, _b, _c;
3435
- if (e.target === ((_a = this.modal) == null ? void 0 : _a.querySelector(".neofaceid-modal-overlay"))) {
3534
+ var _a2, _b, _c;
3535
+ if (e.target === ((_a2 = this.modal) == null ? void 0 : _a2.querySelector(".neofaceid-modal-overlay"))) {
3436
3536
  this.close();
3437
3537
  (_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
3438
3538
  }
@@ -3715,52 +3815,837 @@ class BiometricCaptureModal {
3715
3815
  `;
3716
3816
  document.head.appendChild(style);
3717
3817
  }
3818
+ };
3819
+ function BiometricStatusOverlayComponent({ status, onClose }) {
3820
+ const [isVisible, setIsVisible] = useState(true);
3821
+ useEffect(() => {
3822
+ if (status === "success") {
3823
+ const timer = setTimeout(() => {
3824
+ setIsVisible(false);
3825
+ setTimeout(() => {
3826
+ onClose == null ? void 0 : onClose();
3827
+ }, 300);
3828
+ }, 800);
3829
+ return () => clearTimeout(timer);
3830
+ }
3831
+ }, [status, onClose]);
3832
+ if (!isVisible)
3833
+ return null;
3834
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3835
+ /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
3836
+ @keyframes fadeIn {
3837
+ from { opacity: 0; transform: scale(0.9); }
3838
+ to { opacity: 1; transform: scale(1); }
3839
+ }
3840
+
3841
+ @keyframes fadeOut {
3842
+ from { opacity: 1; transform: scale(1); }
3843
+ to { opacity: 0; transform: scale(0.9); }
3844
+ }
3845
+
3846
+ @keyframes pulse {
3847
+ 0%, 100% {
3848
+ transform: scale(1);
3849
+ opacity: 1;
3850
+ }
3851
+ 50% {
3852
+ transform: scale(1.1);
3853
+ opacity: 0.9;
3854
+ }
3855
+ }
3856
+
3857
+ @keyframes spin {
3858
+ from { transform: rotate(0deg); }
3859
+ to { transform: rotate(360deg); }
3860
+ }
3861
+
3862
+ @keyframes checkmark {
3863
+ 0% {
3864
+ stroke-dashoffset: 100;
3865
+ opacity: 0;
3866
+ }
3867
+ 50% {
3868
+ opacity: 1;
3869
+ }
3870
+ 100% {
3871
+ stroke-dashoffset: 0;
3872
+ opacity: 1;
3873
+ }
3874
+ }
3875
+
3876
+ @keyframes errorShake {
3877
+ 0%, 100% { transform: translateX(0) scale(1); }
3878
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px) scale(1); }
3879
+ 20%, 40%, 60%, 80% { transform: translateX(4px) scale(1); }
3880
+ }
3881
+
3882
+ @keyframes scaleIn {
3883
+ 0% {
3884
+ transform: scale(0);
3885
+ opacity: 0;
3886
+ }
3887
+ 50% {
3888
+ transform: scale(1.1);
3889
+ }
3890
+ 100% {
3891
+ transform: scale(1);
3892
+ opacity: 1;
3893
+ }
3894
+ }
3895
+
3896
+ .neofaceid-overlay-container {
3897
+ position: fixed;
3898
+ top: 0;
3899
+ left: 0;
3900
+ right: 0;
3901
+ bottom: 0;
3902
+ display: flex;
3903
+ align-items: center;
3904
+ justify-content: center;
3905
+ z-index: 10000;
3906
+ pointer-events: none;
3907
+ animation: fadeIn 0.3s ease-out;
3908
+ }
3909
+
3910
+ .neofaceid-overlay-container.fade-out {
3911
+ animation: fadeOut 0.3s ease-out;
3912
+ }
3913
+
3914
+ .neofaceid-overlay-content {
3915
+ display: flex;
3916
+ flex-direction: column;
3917
+ align-items: center;
3918
+ justify-content: center;
3919
+ gap: 24px;
3920
+ pointer-events: auto;
3921
+ }
3922
+
3923
+ .neofaceid-logo-wrapper {
3924
+ position: relative;
3925
+ width: 120px;
3926
+ height: 120px;
3927
+ display: flex;
3928
+ align-items: center;
3929
+ justify-content: center;
3930
+ }
3931
+
3932
+ .neofaceid-logo {
3933
+ width: 80px;
3934
+ height: 80px;
3935
+ object-fit: contain;
3936
+ filter: drop-shadow(0 4px 12px rgba(124, 58, 237, 0.3));
3937
+ }
3938
+
3939
+ .neofaceid-logo.pulsing {
3940
+ animation: pulse 2s ease-in-out infinite;
3941
+ }
3942
+
3943
+ .neofaceid-status-icon {
3944
+ position: absolute;
3945
+ inset: 0;
3946
+ display: flex;
3947
+ align-items: center;
3948
+ justify-content: center;
3949
+ }
3950
+
3951
+ .neofaceid-success-icon {
3952
+ width: 60px;
3953
+ height: 60px;
3954
+ color: #10b981;
3955
+ animation: scaleIn 0.6s ease-out;
3956
+ }
3957
+
3958
+ .neofaceid-success-icon svg circle {
3959
+ stroke-dasharray: 100;
3960
+ stroke-dashoffset: 100;
3961
+ animation: checkmark 0.6s ease-out 0.2s forwards;
3962
+ }
3963
+
3964
+ .neofaceid-error-icon {
3965
+ width: 60px;
3966
+ height: 60px;
3967
+ color: #ef4444;
3968
+ animation: errorShake 0.5s ease-out, scaleIn 0.4s ease-out;
3969
+ }
3970
+
3971
+ /* Responsive */
3972
+ @media (max-width: 640px) {
3973
+ .neofaceid-logo-wrapper {
3974
+ width: 100px;
3975
+ height: 100px;
3976
+ }
3977
+
3978
+ .neofaceid-logo {
3979
+ width: 70px;
3980
+ height: 70px;
3981
+ }
3982
+ }
3983
+ ` }),
3984
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `neofaceid-overlay-container ${!isVisible ? "fade-out" : ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-overlay-content", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "neofaceid-logo-wrapper", children: [
3985
+ (status === "preparing" || status === "verifying") && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3986
+ "img",
3987
+ {
3988
+ src: "/public/logo-icone-no-backgorund.png",
3989
+ alt: "NeoFaceId",
3990
+ className: `neofaceid-logo ${status === "verifying" ? "pulsing" : ""}`,
3991
+ onError: (e) => {
3992
+ var _a2;
3993
+ const img = e.target;
3994
+ img.style.display = "none";
3995
+ const fallback = document.createElement("div");
3996
+ fallback.innerHTML = `
3997
+ <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
3998
+ <circle cx="40" cy="40" r="35" fill="#7c3aed" opacity="0.2"/>
3999
+ <path d="M40 20 L40 60 M20 40 L60 40" stroke="#7c3aed" stroke-width="4" stroke-linecap="round"/>
4000
+ </svg>
4001
+ `;
4002
+ fallback.style.cssText = "width: 80px; height: 80px; display: flex; align-items: center; justify-content: center;";
4003
+ (_a2 = img.parentElement) == null ? void 0 : _a2.appendChild(fallback);
4004
+ }
4005
+ }
4006
+ ) }),
4007
+ status === "success" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-status-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4008
+ "svg",
4009
+ {
4010
+ className: "neofaceid-success-icon",
4011
+ viewBox: "0 0 60 60",
4012
+ fill: "none",
4013
+ xmlns: "http://www.w3.org/2000/svg",
4014
+ children: [
4015
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4016
+ "circle",
4017
+ {
4018
+ cx: "30",
4019
+ cy: "30",
4020
+ r: "28",
4021
+ stroke: "currentColor",
4022
+ strokeWidth: "3",
4023
+ fill: "none"
4024
+ }
4025
+ ),
4026
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4027
+ "path",
4028
+ {
4029
+ d: "M15 30 L25 40 L45 20",
4030
+ stroke: "currentColor",
4031
+ strokeWidth: "3",
4032
+ strokeLinecap: "round",
4033
+ strokeLinejoin: "round",
4034
+ fill: "none"
4035
+ }
4036
+ )
4037
+ ]
4038
+ }
4039
+ ) }),
4040
+ status === "error" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-status-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4041
+ "svg",
4042
+ {
4043
+ className: "neofaceid-error-icon",
4044
+ viewBox: "0 0 60 60",
4045
+ fill: "none",
4046
+ xmlns: "http://www.w3.org/2000/svg",
4047
+ children: [
4048
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4049
+ "circle",
4050
+ {
4051
+ cx: "30",
4052
+ cy: "30",
4053
+ r: "28",
4054
+ stroke: "currentColor",
4055
+ strokeWidth: "3",
4056
+ fill: "none"
4057
+ }
4058
+ ),
4059
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4060
+ "path",
4061
+ {
4062
+ d: "M20 20 L40 40 M40 20 L20 40",
4063
+ stroke: "currentColor",
4064
+ strokeWidth: "3",
4065
+ strokeLinecap: "round"
4066
+ }
4067
+ )
4068
+ ]
4069
+ }
4070
+ ) })
4071
+ ] }) }) })
4072
+ ] });
3718
4073
  }
3719
- class EmailPasswordModal {
3720
- constructor(options) {
3721
- __publicField(this, "options");
3722
- __publicField(this, "modalElement", null);
3723
- this.options = options;
4074
+ class BiometricStatusOverlay {
4075
+ constructor() {
4076
+ __publicField(this, "container", null);
4077
+ __publicField(this, "root", null);
4078
+ __publicField(this, "currentStatus", "preparing");
3724
4079
  }
3725
4080
  /**
3726
- * Abre o modal de login com email e senha.
4081
+ * Cria e exibe o overlay
3727
4082
  */
3728
- async open() {
3729
- this.createModal();
3730
- document.body.appendChild(this.modalElement);
4083
+ show(status = "preparing") {
4084
+ if (this.container) {
4085
+ this.updateStatus(status);
4086
+ return;
4087
+ }
4088
+ this.container = document.createElement("div");
4089
+ this.container.id = "neofaceid-status-overlay";
4090
+ document.body.appendChild(this.container);
4091
+ this.root = createRoot(this.container);
4092
+ this.currentStatus = status;
4093
+ this.render();
3731
4094
  }
3732
4095
  /**
3733
- * Fecha o modal.
4096
+ * Atualiza o status do overlay
3734
4097
  */
3735
- close() {
3736
- if (this.modalElement) {
3737
- document.body.removeChild(this.modalElement);
3738
- this.modalElement = null;
4098
+ updateStatus(status) {
4099
+ this.currentStatus = status;
4100
+ if (this.root) {
4101
+ this.render();
3739
4102
  }
3740
- if (this.options.onCancel) {
3741
- this.options.onCancel();
4103
+ }
4104
+ /**
4105
+ * Fecha e remove o overlay
4106
+ */
4107
+ close() {
4108
+ if (this.container) {
4109
+ if (this.root) {
4110
+ this.root.render(
4111
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4112
+ BiometricStatusOverlayComponent,
4113
+ {
4114
+ status: this.currentStatus,
4115
+ onClose: () => {
4116
+ if (this.container && document.body.contains(this.container)) {
4117
+ document.body.removeChild(this.container);
4118
+ }
4119
+ this.container = null;
4120
+ this.root = null;
4121
+ }
4122
+ }
4123
+ )
4124
+ );
4125
+ }
4126
+ setTimeout(() => {
4127
+ if (this.container && document.body.contains(this.container)) {
4128
+ document.body.removeChild(this.container);
4129
+ }
4130
+ this.container = null;
4131
+ this.root = null;
4132
+ }, 300);
3742
4133
  }
3743
4134
  }
3744
- createModal() {
3745
- this.modalElement = document.createElement("div");
3746
- this.modalElement.className = "neoface-modal";
3747
- this.modalElement.innerHTML = `
3748
- <div class="modal-content">
3749
- <h2>${this.options.title || "Login com Email e Senha"}</h2>
3750
- <p>${this.options.subtitle || "Informe seus dados para login."}</p>
3751
- <form id="email-password-form">
3752
- <input type="email" id="email" placeholder="Email" required />
3753
- <input type="password" id="password" placeholder="Senha" required />
3754
- <button type="submit">Entrar</button>
3755
- </form>
3756
- <button class="cancel-button">Cancelar</button>
3757
- </div>
4135
+ /**
4136
+ * Renderiza o componente
4137
+ */
4138
+ render() {
4139
+ if (!this.root || !this.container)
4140
+ return;
4141
+ this.root.render(
4142
+ /* @__PURE__ */ jsxRuntimeExports.jsx(BiometricStatusOverlayComponent, { status: this.currentStatus, onClose: () => this.close() })
4143
+ );
4144
+ }
4145
+ }
4146
+ function FallbackPromptComponent({
4147
+ error,
4148
+ onRetry,
4149
+ onCredentials,
4150
+ onCancel
4151
+ }) {
4152
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
4153
+ /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
4154
+ @keyframes slideUp {
4155
+ from {
4156
+ opacity: 0;
4157
+ transform: translateY(20px);
4158
+ }
4159
+ to {
4160
+ opacity: 1;
4161
+ transform: translateY(0);
4162
+ }
4163
+ }
4164
+
4165
+ @keyframes fadeIn {
4166
+ from { opacity: 0; }
4167
+ to { opacity: 1; }
4168
+ }
4169
+
4170
+ .neofaceid-fallback-overlay {
4171
+ position: fixed;
4172
+ inset: 0;
4173
+ background: rgba(0, 0, 0, 0.6);
4174
+ backdrop-filter: blur(8px);
4175
+ display: flex;
4176
+ align-items: center;
4177
+ justify-content: center;
4178
+ z-index: 10001;
4179
+ padding: 20px;
4180
+ animation: fadeIn 0.3s ease-out;
4181
+ }
4182
+
4183
+ .neofaceid-fallback-prompt {
4184
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
4185
+ border-radius: 20px;
4186
+ padding: 32px;
4187
+ max-width: 400px;
4188
+ width: 100%;
4189
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
4190
+ animation: slideUp 0.4s ease-out;
4191
+ text-align: center;
4192
+ }
4193
+
4194
+ .neofaceid-fallback-icon {
4195
+ width: 64px;
4196
+ height: 64px;
4197
+ margin: 0 auto 20px;
4198
+ color: #ef4444;
4199
+ animation: scaleIn 0.4s ease-out;
4200
+ }
4201
+
4202
+ @keyframes scaleIn {
4203
+ 0% {
4204
+ transform: scale(0);
4205
+ opacity: 0;
4206
+ }
4207
+ 50% {
4208
+ transform: scale(1.1);
4209
+ }
4210
+ 100% {
4211
+ transform: scale(1);
4212
+ opacity: 1;
4213
+ }
4214
+ }
4215
+
4216
+ .neofaceid-fallback-title {
4217
+ font-size: 22px;
4218
+ font-weight: 700;
4219
+ color: white;
4220
+ margin: 0 0 12px 0;
4221
+ }
4222
+
4223
+ .neofaceid-fallback-message {
4224
+ font-size: 15px;
4225
+ color: rgba(255, 255, 255, 0.7);
4226
+ margin: 0 0 28px 0;
4227
+ line-height: 1.5;
4228
+ }
4229
+
4230
+ .neofaceid-fallback-actions {
4231
+ display: flex;
4232
+ flex-direction: column;
4233
+ gap: 12px;
4234
+ }
4235
+
4236
+ .neofaceid-fallback-button {
4237
+ padding: 14px 24px;
4238
+ border-radius: 12px;
4239
+ border: none;
4240
+ font-size: 16px;
4241
+ font-weight: 600;
4242
+ cursor: pointer;
4243
+ transition: all 0.2s;
4244
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4245
+ }
4246
+
4247
+ .neofaceid-fallback-button-primary {
4248
+ background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
4249
+ color: white;
4250
+ box-shadow: 0 4px 16px rgba(124, 58, 237, 0.3);
4251
+ }
4252
+
4253
+ .neofaceid-fallback-button-primary:hover {
4254
+ transform: translateY(-2px);
4255
+ box-shadow: 0 8px 24px rgba(124, 58, 237, 0.4);
4256
+ }
4257
+
4258
+ .neofaceid-fallback-button-primary:active {
4259
+ transform: translateY(0);
4260
+ }
4261
+
4262
+ .neofaceid-fallback-button-secondary {
4263
+ background: rgba(255, 255, 255, 0.1);
4264
+ color: white;
4265
+ border: 1px solid rgba(255, 255, 255, 0.2);
4266
+ }
4267
+
4268
+ .neofaceid-fallback-button-secondary:hover {
4269
+ background: rgba(255, 255, 255, 0.15);
4270
+ border-color: rgba(255, 255, 255, 0.3);
4271
+ }
4272
+
4273
+ .neofaceid-fallback-button-ghost {
4274
+ background: transparent;
4275
+ color: rgba(255, 255, 255, 0.7);
4276
+ padding: 10px;
4277
+ }
4278
+
4279
+ .neofaceid-fallback-button-ghost:hover {
4280
+ color: white;
4281
+ background: rgba(255, 255, 255, 0.05);
4282
+ }
4283
+
4284
+ @media (max-width: 640px) {
4285
+ .neofaceid-fallback-prompt {
4286
+ padding: 24px;
4287
+ border-radius: 16px;
4288
+ }
4289
+
4290
+ .neofaceid-fallback-title {
4291
+ font-size: 20px;
4292
+ }
4293
+ }
4294
+ ` }),
4295
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-fallback-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "neofaceid-fallback-prompt", children: [
4296
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-fallback-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 64 64", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
4297
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "32", cy: "32", r: "30", stroke: "currentColor", strokeWidth: "3", fill: "none" }),
4298
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4299
+ "path",
4300
+ {
4301
+ d: "M20 20 L44 44 M44 20 L20 44",
4302
+ stroke: "currentColor",
4303
+ strokeWidth: "3",
4304
+ strokeLinecap: "round"
4305
+ }
4306
+ )
4307
+ ] }) }),
4308
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "neofaceid-fallback-title", children: "Não foi possível reconhecer" }),
4309
+ /* @__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." }),
4310
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "neofaceid-fallback-actions", children: [
4311
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4312
+ "button",
4313
+ {
4314
+ type: "button",
4315
+ className: "neofaceid-fallback-button neofaceid-fallback-button-primary",
4316
+ onClick: onRetry,
4317
+ children: "Tentar Novamente"
4318
+ }
4319
+ ),
4320
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4321
+ "button",
4322
+ {
4323
+ type: "button",
4324
+ className: "neofaceid-fallback-button neofaceid-fallback-button-secondary",
4325
+ onClick: onCredentials,
4326
+ children: "Entrar com Email e Senha"
4327
+ }
4328
+ ),
4329
+ onCancel && /* @__PURE__ */ jsxRuntimeExports.jsx(
4330
+ "button",
4331
+ {
4332
+ type: "button",
4333
+ className: "neofaceid-fallback-button neofaceid-fallback-button-ghost",
4334
+ onClick: onCancel,
4335
+ children: "Cancelar"
4336
+ }
4337
+ )
4338
+ ] })
4339
+ ] }) })
4340
+ ] });
4341
+ }
4342
+ class FallbackPrompt {
4343
+ constructor() {
4344
+ __publicField(this, "container", null);
4345
+ __publicField(this, "root", null);
4346
+ }
4347
+ /**
4348
+ * Exibe o prompt de fallback
4349
+ */
4350
+ show(error, onRetry, onCredentials, onCancel) {
4351
+ if (this.container) {
4352
+ return;
4353
+ }
4354
+ this.container = document.createElement("div");
4355
+ this.container.id = "neofaceid-fallback-prompt";
4356
+ document.body.appendChild(this.container);
4357
+ this.root = createRoot(this.container);
4358
+ this.render(error, onRetry, onCredentials, onCancel);
4359
+ }
4360
+ /**
4361
+ * Fecha o prompt
4362
+ */
4363
+ close() {
4364
+ if (this.container && document.body.contains(this.container)) {
4365
+ if (this.root) {
4366
+ this.root.unmount();
4367
+ }
4368
+ document.body.removeChild(this.container);
4369
+ this.container = null;
4370
+ this.root = null;
4371
+ }
4372
+ }
4373
+ /**
4374
+ * Renderiza o componente
4375
+ */
4376
+ render(error, onRetry, onCredentials, onCancel) {
4377
+ if (!this.root || !this.container)
4378
+ return;
4379
+ this.root.render(
4380
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4381
+ FallbackPromptComponent,
4382
+ {
4383
+ error,
4384
+ onRetry: () => {
4385
+ this.close();
4386
+ onRetry();
4387
+ },
4388
+ onCredentials: () => {
4389
+ this.close();
4390
+ onCredentials();
4391
+ },
4392
+ onCancel: () => {
4393
+ this.close();
4394
+ onCancel == null ? void 0 : onCancel();
4395
+ }
4396
+ }
4397
+ )
4398
+ );
4399
+ }
4400
+ }
4401
+ class EmailPasswordModal {
4402
+ constructor(options) {
4403
+ __publicField(this, "options");
4404
+ __publicField(this, "modalElement", null);
4405
+ this.options = options;
4406
+ }
4407
+ /**
4408
+ * Abre o modal de login com email e senha.
4409
+ */
4410
+ async open() {
4411
+ this.createModal();
4412
+ document.body.appendChild(this.modalElement);
4413
+ }
4414
+ /**
4415
+ * Fecha o modal.
4416
+ */
4417
+ close() {
4418
+ if (this.modalElement) {
4419
+ document.body.removeChild(this.modalElement);
4420
+ this.modalElement = null;
4421
+ }
4422
+ if (this.options.onCancel) {
4423
+ this.options.onCancel();
4424
+ }
4425
+ }
4426
+ createModal() {
4427
+ this.addStyles();
4428
+ this.modalElement = document.createElement("div");
4429
+ this.modalElement.className = "neoface-email-password-modal";
4430
+ this.modalElement.innerHTML = `
4431
+ <div class="neoface-email-modal-overlay">
4432
+ <div class="neoface-email-modal-content">
4433
+ <h2>${this.options.title || "Login com Email e Senha"}</h2>
4434
+ <p>${this.options.subtitle || "Informe seus dados para login."}</p>
4435
+ <form id="email-password-form">
4436
+ <input type="email" id="email" placeholder="Email" required />
4437
+ <input type="password" id="password" placeholder="Senha" required />
4438
+ <button type="submit" class="neoface-email-submit-btn">Entrar</button>
4439
+ </form>
4440
+ <button class="neoface-email-cancel-btn">Cancelar</button>
4441
+
4442
+ <!-- Branding -->
4443
+ <div class="neoface-email-branding">
4444
+ <img src="/public/logo-icone-no-backgorund.png" alt="NeoFaceId" class="neoface-email-brand-logo" onerror="this.style.display='none'" />
4445
+ <span>NeoFaceId by</span>
4446
+ <span class="neoface-email-brand-name">OCTA</span>
4447
+ </div>
4448
+ </div>
4449
+ </div>
3758
4450
  `;
3759
4451
  const form = this.modalElement.querySelector("#email-password-form");
3760
4452
  form.addEventListener("submit", this.handleSubmit.bind(this));
3761
- const cancelButton = this.modalElement.querySelector(".cancel-button");
4453
+ const cancelButton = this.modalElement.querySelector(
4454
+ ".neoface-email-cancel-btn"
4455
+ );
3762
4456
  cancelButton.addEventListener("click", this.close.bind(this));
3763
4457
  }
4458
+ addStyles() {
4459
+ const styleId = "neoface-email-password-modal-styles";
4460
+ const existingStyles = document.getElementById(styleId);
4461
+ if (existingStyles) {
4462
+ return;
4463
+ }
4464
+ const style = document.createElement("style");
4465
+ style.id = styleId;
4466
+ style.textContent = `
4467
+ .neoface-email-password-modal {
4468
+ position: fixed;
4469
+ top: 0;
4470
+ left: 0;
4471
+ width: 100%;
4472
+ height: 100%;
4473
+ z-index: 10002;
4474
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4475
+ }
4476
+
4477
+ .neoface-email-modal-overlay {
4478
+ position: absolute;
4479
+ inset: 0;
4480
+ background: rgba(0, 0, 0, 0.6);
4481
+ backdrop-filter: blur(8px);
4482
+ display: flex;
4483
+ align-items: center;
4484
+ justify-content: center;
4485
+ padding: 20px;
4486
+ animation: fadeIn 0.3s ease-out;
4487
+ }
4488
+
4489
+ @keyframes fadeIn {
4490
+ from { opacity: 0; }
4491
+ to { opacity: 1; }
4492
+ }
4493
+
4494
+ @keyframes slideUp {
4495
+ from {
4496
+ opacity: 0;
4497
+ transform: translateY(20px);
4498
+ }
4499
+ to {
4500
+ opacity: 1;
4501
+ transform: translateY(0);
4502
+ }
4503
+ }
4504
+
4505
+ .neoface-email-modal-content {
4506
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
4507
+ border-radius: 20px;
4508
+ padding: 32px;
4509
+ max-width: 400px;
4510
+ width: 100%;
4511
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
4512
+ animation: slideUp 0.4s ease-out;
4513
+ }
4514
+
4515
+ .neoface-email-modal-content h2 {
4516
+ font-size: 24px;
4517
+ font-weight: 700;
4518
+ color: white;
4519
+ margin: 0 0 12px 0;
4520
+ text-align: center;
4521
+ }
4522
+
4523
+ .neoface-email-modal-content p {
4524
+ font-size: 15px;
4525
+ color: rgba(255, 255, 255, 0.7);
4526
+ margin: 0 0 24px 0;
4527
+ text-align: center;
4528
+ line-height: 1.5;
4529
+ }
4530
+
4531
+ .neoface-email-modal-content form {
4532
+ display: flex;
4533
+ flex-direction: column;
4534
+ gap: 16px;
4535
+ margin-bottom: 16px;
4536
+ }
4537
+
4538
+ .neoface-email-modal-content input {
4539
+ width: 100%;
4540
+ padding: 14px 16px;
4541
+ border: 2px solid rgba(255, 255, 255, 0.2);
4542
+ border-radius: 12px;
4543
+ font-size: 15px;
4544
+ background: rgba(255, 255, 255, 0.1);
4545
+ color: white;
4546
+ font-family: inherit;
4547
+ transition: all 0.2s;
4548
+ box-sizing: border-box;
4549
+ }
4550
+
4551
+ .neoface-email-modal-content input::placeholder {
4552
+ color: rgba(255, 255, 255, 0.5);
4553
+ }
4554
+
4555
+ .neoface-email-modal-content input:focus {
4556
+ outline: none;
4557
+ border-color: #7c3aed;
4558
+ box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.2);
4559
+ background: rgba(255, 255, 255, 0.15);
4560
+ }
4561
+
4562
+ .neoface-email-submit-btn {
4563
+ width: 100%;
4564
+ padding: 14px 24px;
4565
+ border-radius: 12px;
4566
+ border: none;
4567
+ background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
4568
+ color: white;
4569
+ font-size: 16px;
4570
+ font-weight: 600;
4571
+ cursor: pointer;
4572
+ transition: all 0.2s;
4573
+ box-shadow: 0 4px 16px rgba(124, 58, 237, 0.3);
4574
+ font-family: inherit;
4575
+ }
4576
+
4577
+ .neoface-email-submit-btn:hover {
4578
+ transform: translateY(-2px);
4579
+ box-shadow: 0 8px 24px rgba(124, 58, 237, 0.4);
4580
+ }
4581
+
4582
+ .neoface-email-submit-btn:active {
4583
+ transform: translateY(0);
4584
+ }
4585
+
4586
+ .neoface-email-submit-btn:disabled {
4587
+ opacity: 0.6;
4588
+ cursor: not-allowed;
4589
+ transform: none;
4590
+ }
4591
+
4592
+ .neoface-email-cancel-btn {
4593
+ width: 100%;
4594
+ padding: 12px 24px;
4595
+ border-radius: 12px;
4596
+ border: 1px solid rgba(255, 255, 255, 0.2);
4597
+ background: transparent;
4598
+ color: rgba(255, 255, 255, 0.7);
4599
+ font-size: 15px;
4600
+ font-weight: 600;
4601
+ cursor: pointer;
4602
+ transition: all 0.2s;
4603
+ font-family: inherit;
4604
+ }
4605
+
4606
+ .neoface-email-cancel-btn:hover {
4607
+ background: rgba(255, 255, 255, 0.1);
4608
+ color: white;
4609
+ border-color: rgba(255, 255, 255, 0.3);
4610
+ }
4611
+
4612
+ .neoface-email-branding {
4613
+ display: flex;
4614
+ align-items: center;
4615
+ justify-content: center;
4616
+ gap: 8px;
4617
+ margin-top: 32px;
4618
+ padding-top: 24px;
4619
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
4620
+ font-size: 13px;
4621
+ color: rgba(255, 255, 255, 0.5);
4622
+ }
4623
+
4624
+ .neoface-email-brand-logo {
4625
+ height: 20px;
4626
+ width: auto;
4627
+ }
4628
+
4629
+ .neoface-email-brand-name {
4630
+ font-weight: 600;
4631
+ font-family: 'SF Pro Display', -apple-system, system-ui, sans-serif;
4632
+ color: #ffffff;
4633
+ letter-spacing: 0.3px;
4634
+ }
4635
+
4636
+ @media (max-width: 640px) {
4637
+ .neoface-email-modal-content {
4638
+ padding: 24px;
4639
+ border-radius: 16px;
4640
+ }
4641
+
4642
+ .neoface-email-modal-content h2 {
4643
+ font-size: 20px;
4644
+ }
4645
+ }
4646
+ `;
4647
+ document.head.appendChild(style);
4648
+ }
3764
4649
  async handleSubmit(event) {
3765
4650
  event.preventDefault();
3766
4651
  const emailInput = this.modalElement.querySelector("#email");
@@ -3768,7 +4653,9 @@ class EmailPasswordModal {
3768
4653
  const email = emailInput.value.trim();
3769
4654
  const password = passwordInput.value.trim();
3770
4655
  if (!email || !password) {
3771
- this.options.onError(new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR));
4656
+ this.options.onError(
4657
+ new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR)
4658
+ );
3772
4659
  return;
3773
4660
  }
3774
4661
  try {
@@ -3780,6 +4667,230 @@ class EmailPasswordModal {
3780
4667
  }
3781
4668
  }
3782
4669
  }
4670
+ const MAX_ATTEMPTS = 3;
4671
+ const RETRY_DELAY = 500;
4672
+ async function detectFaceContinuously(video, minDetectionTime = 3e3, detectionInterval = 200) {
4673
+ return new Promise((resolve) => {
4674
+ let faceDetectedCount = 0;
4675
+ let totalChecks = 0;
4676
+ const requiredChecks = Math.ceil(minDetectionTime / detectionInterval);
4677
+ const minSuccessRate = 0.6;
4678
+ let timeoutId = null;
4679
+ let isResolved = false;
4680
+ const finish = (success) => {
4681
+ if (isResolved)
4682
+ return;
4683
+ isResolved = true;
4684
+ if (timeoutId) {
4685
+ clearTimeout(timeoutId);
4686
+ }
4687
+ resolve(success);
4688
+ };
4689
+ const checkFace = async () => {
4690
+ if (isResolved)
4691
+ return;
4692
+ try {
4693
+ const detection = await faceapi.detectSingleFace(
4694
+ video,
4695
+ new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.4 })
4696
+ );
4697
+ totalChecks++;
4698
+ if (detection) {
4699
+ faceDetectedCount++;
4700
+ }
4701
+ if (totalChecks >= requiredChecks) {
4702
+ const successRate = faceDetectedCount / totalChecks;
4703
+ finish(successRate >= minSuccessRate);
4704
+ return;
4705
+ }
4706
+ timeoutId = window.setTimeout(checkFace, detectionInterval);
4707
+ } catch (error) {
4708
+ totalChecks++;
4709
+ if (totalChecks >= requiredChecks) {
4710
+ const successRate = faceDetectedCount / totalChecks;
4711
+ finish(successRate >= minSuccessRate);
4712
+ } else {
4713
+ timeoutId = window.setTimeout(checkFace, detectionInterval);
4714
+ }
4715
+ }
4716
+ };
4717
+ const safetyTimeout = window.setTimeout(() => {
4718
+ if (!isResolved) {
4719
+ const successRate = totalChecks > 0 ? faceDetectedCount / totalChecks : 0;
4720
+ finish(successRate >= minSuccessRate);
4721
+ }
4722
+ }, minDetectionTime + 1e3);
4723
+ timeoutId = window.setTimeout(() => {
4724
+ checkFace();
4725
+ clearTimeout(safetyTimeout);
4726
+ }, detectionInterval);
4727
+ });
4728
+ }
4729
+ async function attemptLogin(applicationToken, overlay) {
4730
+ overlay.updateStatus("verifying");
4731
+ const video = document.createElement("video");
4732
+ video.style.position = "fixed";
4733
+ video.style.top = "-9999px";
4734
+ video.style.left = "-9999px";
4735
+ video.style.width = "1px";
4736
+ video.style.height = "1px";
4737
+ video.style.opacity = "0";
4738
+ video.setAttribute("autoplay", "");
4739
+ video.setAttribute("muted", "");
4740
+ video.setAttribute("playsinline", "");
4741
+ document.body.appendChild(video);
4742
+ let stream = null;
4743
+ try {
4744
+ stream = await navigator.mediaDevices.getUserMedia({
4745
+ video: {
4746
+ width: { ideal: 640 },
4747
+ height: { ideal: 480 },
4748
+ facingMode: "user"
4749
+ },
4750
+ audio: false
4751
+ });
4752
+ video.srcObject = stream;
4753
+ await video.play();
4754
+ await new Promise((resolve) => {
4755
+ if (video.readyState >= 2) {
4756
+ resolve(void 0);
4757
+ } else {
4758
+ video.onloadedmetadata = () => resolve(void 0);
4759
+ }
4760
+ });
4761
+ await new Promise((resolve) => setTimeout(resolve, 500));
4762
+ const faceDetected = await detectFaceContinuously(video, 3e3);
4763
+ if (!faceDetected) {
4764
+ if (stream) {
4765
+ stream.getTracks().forEach((track) => track.stop());
4766
+ }
4767
+ if (video.parentElement) {
4768
+ video.parentElement.removeChild(video);
4769
+ }
4770
+ throw new NeoFaceError("Rosto não detectado. Posicione seu rosto na frente da câmera.", ErrorType.VALIDATION_ERROR);
4771
+ }
4772
+ const canvas = document.createElement("canvas");
4773
+ canvas.width = video.videoWidth;
4774
+ canvas.height = video.videoHeight;
4775
+ const ctx = canvas.getContext("2d");
4776
+ if (!ctx) {
4777
+ if (stream) {
4778
+ stream.getTracks().forEach((track) => track.stop());
4779
+ }
4780
+ if (video.parentElement) {
4781
+ video.parentElement.removeChild(video);
4782
+ }
4783
+ throw new NeoFaceError("Erro ao capturar imagem", ErrorType.CAPTURE_ERROR);
4784
+ }
4785
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
4786
+ if (stream) {
4787
+ stream.getTracks().forEach((track) => track.stop());
4788
+ }
4789
+ if (video.parentElement) {
4790
+ video.parentElement.removeChild(video);
4791
+ }
4792
+ const imageBlob = await new Promise((resolve, reject) => {
4793
+ canvas.toBlob(
4794
+ (blob) => {
4795
+ canvas.remove();
4796
+ if (blob) {
4797
+ resolve(blob);
4798
+ } else {
4799
+ reject(new Error("Failed to capture image"));
4800
+ }
4801
+ },
4802
+ "image/jpeg",
4803
+ 0.85
4804
+ );
4805
+ });
4806
+ const result = await loginWithBiometric$1(imageBlob, applicationToken);
4807
+ if (!result.success) {
4808
+ throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
4809
+ }
4810
+ return result;
4811
+ } catch (error) {
4812
+ if (stream) {
4813
+ stream.getTracks().forEach((track) => track.stop());
4814
+ }
4815
+ if (video.parentElement) {
4816
+ video.parentElement.removeChild(video);
4817
+ }
4818
+ if (error instanceof NeoFaceError) {
4819
+ throw error;
4820
+ }
4821
+ throw new NeoFaceError(
4822
+ error instanceof Error ? error.message : "Erro desconhecido",
4823
+ ErrorType.UNKNOWN
4824
+ );
4825
+ }
4826
+ }
4827
+ async function executeBiometricLoginFlow(options) {
4828
+ const { applicationToken, onSuccess, onError, onFallbackRequest, onCancel } = options;
4829
+ const overlay = new BiometricStatusOverlay();
4830
+ const fallbackPrompt = new FallbackPrompt();
4831
+ let attempts = 0;
4832
+ let lastError;
4833
+ try {
4834
+ const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
4835
+ await Promise.all([
4836
+ faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
4837
+ faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
4838
+ ]);
4839
+ } catch (error) {
4840
+ console.warn("Face-api.js models not loaded, continuing without face detection");
4841
+ }
4842
+ overlay.show("preparing");
4843
+ const tryLogin = async () => {
4844
+ try {
4845
+ attempts++;
4846
+ const result = await attemptLogin(applicationToken, overlay);
4847
+ overlay.updateStatus("success");
4848
+ setTimeout(() => {
4849
+ overlay.close();
4850
+ onSuccess(result);
4851
+ }, 800);
4852
+ } catch (error) {
4853
+ lastError = error instanceof NeoFaceError ? error : new NeoFaceError(
4854
+ error instanceof Error ? error.message : "Erro desconhecido",
4855
+ ErrorType.UNKNOWN
4856
+ );
4857
+ overlay.updateStatus("error");
4858
+ if (attempts < MAX_ATTEMPTS) {
4859
+ setTimeout(() => {
4860
+ tryLogin();
4861
+ }, RETRY_DELAY);
4862
+ } else {
4863
+ setTimeout(() => {
4864
+ overlay.close();
4865
+ fallbackPrompt.show(
4866
+ lastError,
4867
+ () => {
4868
+ attempts = 0;
4869
+ overlay.show("preparing");
4870
+ setTimeout(() => tryLogin(), 500);
4871
+ },
4872
+ () => {
4873
+ if (onFallbackRequest) {
4874
+ onFallbackRequest();
4875
+ } else {
4876
+ onError(
4877
+ new NeoFaceError(
4878
+ "Por favor, use email e senha para fazer login",
4879
+ ErrorType.LOGIN_FAILED
4880
+ )
4881
+ );
4882
+ }
4883
+ },
4884
+ onCancel
4885
+ );
4886
+ }, 1e3);
4887
+ }
4888
+ }
4889
+ };
4890
+ setTimeout(() => {
4891
+ tryLogin();
4892
+ }, 300);
4893
+ }
3783
4894
  async function startFaceLogin(options) {
3784
4895
  try {
3785
4896
  const isValidToken = await validateToken(options.applicationToken);
@@ -3787,25 +4898,23 @@ async function startFaceLogin(options) {
3787
4898
  options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
3788
4899
  return;
3789
4900
  }
3790
- const captureOptions = {
3791
- mode: "face",
3792
- onSuccess: async (imageData, _detectedType) => {
3793
- try {
3794
- const imageBlob = await fetch(imageData).then((res) => res.blob());
3795
- const result = await loginWithBiometric(imageBlob, options.applicationToken);
3796
- options.onSuccess(result);
3797
- } catch (error) {
3798
- options.onError(error);
3799
- }
3800
- },
4901
+ await executeBiometricLoginFlow({
4902
+ applicationToken: options.applicationToken,
4903
+ onSuccess: options.onSuccess,
3801
4904
  onError: options.onError,
3802
- onCancel: options.onCancel,
3803
- countdown: options.countdown,
3804
- title: options.title || "Login Facial",
3805
- subtitle: options.subtitle || "Posicione seu rosto dentro do quadro para fazer login."
3806
- };
3807
- const modal = new BiometricCaptureModal(captureOptions);
3808
- await modal.open();
4905
+ onFallbackRequest: options.onFallbackRequest || (() => {
4906
+ const fallbackOptions = {
4907
+ applicationToken: options.applicationToken,
4908
+ onSuccess: options.onSuccess,
4909
+ onError: options.onError,
4910
+ title: "Login Alternativo",
4911
+ subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
4912
+ };
4913
+ const modal = new EmailPasswordModal(fallbackOptions);
4914
+ modal.open();
4915
+ }),
4916
+ onCancel: options.onCancel
4917
+ });
3809
4918
  } catch (error) {
3810
4919
  options.onError(
3811
4920
  new NeoFaceError(
@@ -4109,8 +5218,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
4109
5218
  initializeBiometricDetection,
4110
5219
  isAdvancedDetectionAvailable
4111
5220
  }, Symbol.toStringTag, { value: "Module" }));
4112
- const VERSION = "1.3.0";
4113
- const RELEASE_DATE = "2025-11-17";
5221
+ const VERSION = "1.4.2";
5222
+ const RELEASE_DATE = "2025-11-23";
4114
5223
  class OnboardingCaptureModal {
4115
5224
  constructor(options) {
4116
5225
  __publicField(this, "overlay", null);
@@ -4376,8 +5485,8 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
4376
5485
  const modalElement = require$$0$1.createElement(BiometricRegistrationModal, {
4377
5486
  personData,
4378
5487
  applicationToken,
4379
- useRealApi: (options == null ? void 0 : options.useRealApi) || false,
4380
- // 🚀 Passa a flag para o modal
5488
+ useRealApi: (options == null ? void 0 : options.useRealApi) ?? true,
5489
+ // Sempre usa API real em produção
4381
5490
  onClose: cleanup,
4382
5491
  onSuccess: (result) => {
4383
5492
  cleanup();
@@ -4396,20 +5505,25 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
4396
5505
  });
4397
5506
  }
4398
5507
  export {
4399
- BiometricCaptureModal,
5508
+ BiometricCaptureModal$1 as BiometricCaptureModal,
4400
5509
  BiometricRegistrationModal,
5510
+ BiometricStatusOverlay,
5511
+ EmailPasswordModal,
4401
5512
  ErrorType,
4402
5513
  FaceCaptureModal,
5514
+ FallbackPrompt,
4403
5515
  NeoFaceError,
4404
5516
  RELEASE_DATE,
4405
5517
  VERSION,
4406
5518
  biometricLogin,
4407
5519
  biometricLoginWithFallback,
5520
+ completeOnboarding,
5521
+ completeOnboardingWithData,
4408
5522
  detectBiometricType,
4409
5523
  identifyPerson,
4410
5524
  initializeBiometricDetection,
4411
5525
  isAdvancedDetectionAvailable,
4412
- loginWithBiometric,
5526
+ loginWithBiometric$1 as loginWithBiometric,
4413
5527
  recognize,
4414
5528
  recognizeBiometric,
4415
5529
  recognizeByPurpose,
@@ -4422,6 +5536,7 @@ export {
4422
5536
  startFaceLogin,
4423
5537
  startHandLogin,
4424
5538
  startOnboarding,
5539
+ validateOnboardingToken,
4425
5540
  validateToken
4426
5541
  };
4427
5542
  //# sourceMappingURL=neoface-id-sdk.es.js.map