@neofaceid/web-sdk 1.4.0 → 1.6.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.
@@ -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(
@@ -1117,6 +1118,7 @@ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
1117
1118
  ErrorType2["PERSON_NOT_FOUND"] = "PersonNotFoundError";
1118
1119
  ErrorType2["INITIALIZATION_ERROR"] = "InitializationError";
1119
1120
  ErrorType2["CAMERA_ERROR"] = "CameraError";
1121
+ ErrorType2["NO_CAMERA"] = "NoCameraError";
1120
1122
  ErrorType2["CAPTURE_ERROR"] = "CaptureError";
1121
1123
  ErrorType2["NOT_FOUND"] = "NotFoundError";
1122
1124
  ErrorType2["UNKNOWN"] = "UnknownError";
@@ -1135,10 +1137,11 @@ class NeoFaceError extends Error {
1135
1137
  this.type = type;
1136
1138
  }
1137
1139
  }
1138
- const API_BASE_URL = "https://core.neofaceid.com.br";
1140
+ 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
1141
  const REQUEST_TIMEOUT = 1e4;
1140
1142
  const ensureSecureContext = () => {
1141
- if (!window.isSecureContext) {
1143
+ const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname === "";
1144
+ if (!window.isSecureContext && !isLocalhost) {
1142
1145
  throw new NeoFaceError(
1143
1146
  "Secure context (HTTPS) is required for this operation",
1144
1147
  ErrorType.NETWORK
@@ -1184,7 +1187,7 @@ const validateToken = async (applicationToken) => {
1184
1187
  ensureSecureContext();
1185
1188
  const controller = createTimeoutController();
1186
1189
  try {
1187
- const response = await fetch(`${API_BASE_URL}/api/v1/external/auth/validate-token/`, {
1190
+ const response = await fetch(`${API_BASE_URL}/api/v1/auth/application/validate_token/`, {
1188
1191
  method: "POST",
1189
1192
  headers: {
1190
1193
  "Content-Type": "application/json",
@@ -1444,7 +1447,7 @@ const recognize = async (image, applicationToken) => {
1444
1447
  }
1445
1448
  };
1446
1449
  const loginWithBiometric$1 = async (image, applicationToken) => {
1447
- var _a, _b, _c, _d, _e;
1450
+ var _a2, _b, _c, _d, _e;
1448
1451
  ensureSecureContext();
1449
1452
  const controller = createTimeoutController();
1450
1453
  const compressedImage = await compressImage(image);
@@ -1474,7 +1477,7 @@ const loginWithBiometric$1 = async (image, applicationToken) => {
1474
1477
  accessToken: data.accessToken,
1475
1478
  alias: data.alias,
1476
1479
  user: {
1477
- id: ((_a = data.user) == null ? void 0 : _a.id) || "",
1480
+ id: ((_a2 = data.user) == null ? void 0 : _a2.id) || "",
1478
1481
  email: ((_b = data.user) == null ? void 0 : _b.email) || "",
1479
1482
  role: ((_c = data.user) == null ? void 0 : _c.role) || "",
1480
1483
  validated: ((_d = data.user) == null ? void 0 : _d.validated) || false,
@@ -1526,7 +1529,7 @@ const recognizeBiometric = async (image, applicationToken, livenessCheck = true,
1526
1529
  const data = await response.json();
1527
1530
  return {
1528
1531
  success: data.success,
1529
- personId: data.person_id,
1532
+ // personId is intentionally NOT returned for security reasons
1530
1533
  confidenceScore: data.confidence_score,
1531
1534
  accessToken: data.access_token,
1532
1535
  payload: data.payload
@@ -1590,7 +1593,8 @@ const simpleIdentification = async (documentType, documentNumber, applicationTok
1590
1593
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1591
1594
  }
1592
1595
  };
1593
- const recognizeByPurpose = async (image, applicationToken, purpose, confidenceThreshold = 0.8) => {
1596
+ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceThreshold = 0.8, signature, sessionData) => {
1597
+ var _a2;
1594
1598
  ensureSecureContext();
1595
1599
  const controller = createTimeoutController();
1596
1600
  const compressedImage = await compressImage(image);
@@ -1604,18 +1608,22 @@ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceTh
1604
1608
  reader.readAsDataURL(compressedImage);
1605
1609
  });
1606
1610
  try {
1611
+ const requestBody = {
1612
+ biometric_data: base64Image,
1613
+ type_of_identification: "FACE",
1614
+ purpose,
1615
+ confidence_threshold: confidenceThreshold
1616
+ };
1617
+ if (signature) {
1618
+ requestBody.signature = signature;
1619
+ }
1607
1620
  const response = await fetch(`${API_BASE_URL}/api/v1/external/recognition/purpose/`, {
1608
1621
  method: "POST",
1609
1622
  headers: {
1610
1623
  "Content-Type": "application/json",
1611
1624
  "X-App-Token": applicationToken
1612
1625
  },
1613
- body: JSON.stringify({
1614
- biometric_data: base64Image,
1615
- type_of_identification: "FACE",
1616
- purpose,
1617
- confidence_threshold: confidenceThreshold
1618
- }),
1626
+ body: JSON.stringify(requestBody),
1619
1627
  signal: controller.signal
1620
1628
  });
1621
1629
  if (!response.ok) {
@@ -1627,10 +1635,16 @@ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceTh
1627
1635
  const data = await response.json();
1628
1636
  return {
1629
1637
  success: data.success,
1630
- personId: data.person_id,
1638
+ personName: data.person_name || ((_a2 = data.person) == null ? void 0 : _a2.name),
1639
+ email: data.email || (sessionData == null ? void 0 : sessionData.email),
1640
+ cpf: data.cpf || (sessionData == null ? void 0 : sessionData.cpf),
1631
1641
  confidenceScore: data.confidence_score,
1632
1642
  accessToken: data.access_token,
1633
- payload: data.payload
1643
+ payload: data.payload,
1644
+ // Return signature and sessionId for external integrations
1645
+ signature,
1646
+ sessionId: sessionData == null ? void 0 : sessionData.sessionId
1647
+ // Note: personId is intentionally NOT returned for security reasons
1634
1648
  };
1635
1649
  } catch (error) {
1636
1650
  if (error instanceof Error) {
@@ -1687,6 +1701,7 @@ const registerPersonWithoutFace = async (personData, applicationToken) => {
1687
1701
  }
1688
1702
  };
1689
1703
  const registerPersonWithBiometric = async (personData, facePhotos, applicationToken) => {
1704
+ var _a2, _b, _c, _d, _e, _f, _g, _h;
1690
1705
  ensureSecureContext();
1691
1706
  if (!facePhotos || facePhotos.length === 0) {
1692
1707
  throw new NeoFaceError("At least one face photo is required for biometric registration", ErrorType.VALIDATION_ERROR);
@@ -1733,29 +1748,56 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
1733
1748
  }
1734
1749
  try {
1735
1750
  const errorData = await response.json();
1751
+ if (errorData.errors) {
1752
+ const errorMessages = Object.entries(errorData.errors).map(([field, messages]) => {
1753
+ const fieldName = field === "password" ? "senha" : field === "password_confirm" ? "confirmação de senha" : field;
1754
+ const messageList = Array.isArray(messages) ? messages.join(", ") : String(messages);
1755
+ return `${fieldName}: ${messageList}`;
1756
+ }).join("; ");
1757
+ throw new NeoFaceError(`Erro de validação: ${errorMessages}`, ErrorType.VALIDATION_ERROR);
1758
+ }
1736
1759
  if (errorData.error === "fraud_detected") {
1737
1760
  throw new NeoFaceError("Fraud detected in biometric images. Registration blocked.", ErrorType.VALIDATION_ERROR);
1738
1761
  }
1739
- if (errorData.error === "user_exists") {
1762
+ if (errorData.error === "user_exists" || errorData.error_code === "ALREADY_EXISTS") {
1740
1763
  throw new NeoFaceError("User with this email already exists", ErrorType.VALIDATION_ERROR);
1741
1764
  }
1742
1765
  if (errorData.error === "person_exists") {
1743
1766
  throw new NeoFaceError("Person with this CPF already exists", ErrorType.VALIDATION_ERROR);
1744
1767
  }
1745
- throw new NeoFaceError(errorData.message || `Server returned status ${response.status}`, ErrorType.NETWORK);
1768
+ const errorMessage = errorData.message || errorData.error || `Server returned status ${response.status}`;
1769
+ throw new NeoFaceError(errorMessage, ErrorType.VALIDATION_ERROR);
1746
1770
  } catch (parseError) {
1771
+ if (parseError instanceof NeoFaceError) {
1772
+ throw parseError;
1773
+ }
1747
1774
  throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1748
1775
  }
1749
1776
  }
1750
1777
  const data = await response.json();
1778
+ if (response.status === 202 && ((_a2 = data.data) == null ? void 0 : _a2.task_id)) {
1779
+ return {
1780
+ success: data.success,
1781
+ message: data.message || "Registration is being processed. You will receive an email when completed.",
1782
+ task_id: data.data.task_id,
1783
+ status: data.data.status || "processing",
1784
+ person_id: void 0,
1785
+ user_id: void 0,
1786
+ person_validated: void 0,
1787
+ face_processing: void 0,
1788
+ onboarding_link: void 0
1789
+ };
1790
+ }
1751
1791
  return {
1752
1792
  success: data.success,
1753
1793
  message: data.message,
1754
- person_id: data.person_id,
1755
- user_id: data.user_id,
1756
- person_validated: data.person_validated,
1757
- face_processing: data.face_processing,
1758
- onboarding_link: data.onboarding_link
1794
+ person_id: ((_b = data.data) == null ? void 0 : _b.person_id) || data.person_id,
1795
+ user_id: ((_c = data.data) == null ? void 0 : _c.user_id) || data.user_id,
1796
+ person_validated: ((_d = data.data) == null ? void 0 : _d.person_validated) || data.person_validated,
1797
+ face_processing: ((_e = data.data) == null ? void 0 : _e.face_processing) || data.face_processing,
1798
+ onboarding_link: ((_f = data.data) == null ? void 0 : _f.onboarding_link) || data.onboarding_link,
1799
+ task_id: (_g = data.data) == null ? void 0 : _g.task_id,
1800
+ status: (_h = data.data) == null ? void 0 : _h.status
1759
1801
  };
1760
1802
  } catch (error) {
1761
1803
  if (error instanceof Error) {
@@ -1817,7 +1859,7 @@ const loginWithEmail = async (email, password, applicationToken) => {
1817
1859
  }
1818
1860
  };
1819
1861
  const identifyPerson = async (image, applicationToken) => {
1820
- var _a, _b;
1862
+ var _a2, _b;
1821
1863
  ensureSecureContext();
1822
1864
  const controller = createTimeoutController();
1823
1865
  const compressedImage = await compressImage(image);
@@ -1853,7 +1895,7 @@ const identifyPerson = async (image, applicationToken) => {
1853
1895
  if (!data.success) {
1854
1896
  throw new NeoFaceError(data.message || "Person identification failed", ErrorType.RECOGNITION_FAILED);
1855
1897
  }
1856
- const personName = ((_a = data.person) == null ? void 0 : _a.name) || "";
1898
+ const personName = ((_a2 = data.person) == null ? void 0 : _a2.name) || "";
1857
1899
  const birthDateStr = (_b = data.person) == null ? void 0 : _b.birth_date;
1858
1900
  if (!personName) {
1859
1901
  throw new NeoFaceError("Person name not found in response", ErrorType.PERSON_NOT_FOUND);
@@ -1885,6 +1927,79 @@ const identifyPerson = async (image, applicationToken) => {
1885
1927
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1886
1928
  }
1887
1929
  };
1930
+ const registerBiometric = async (personId, faceImage, applicationToken) => {
1931
+ ensureSecureContext();
1932
+ if (!personId) {
1933
+ throw new NeoFaceError("Person ID is required", ErrorType.VALIDATION_ERROR);
1934
+ }
1935
+ if (!faceImage) {
1936
+ throw new NeoFaceError("Face image is required", ErrorType.VALIDATION_ERROR);
1937
+ }
1938
+ const controller = createTimeoutController();
1939
+ const compressedImage = await compressImage(faceImage);
1940
+ const base64Image = await new Promise((resolve, reject) => {
1941
+ const reader = new FileReader();
1942
+ reader.onload = () => {
1943
+ const result = reader.result;
1944
+ resolve(result.split(",")[1]);
1945
+ };
1946
+ reader.onerror = reject;
1947
+ reader.readAsDataURL(compressedImage);
1948
+ });
1949
+ try {
1950
+ const response = await fetch(`${API_BASE_URL}/api/v1/identity-data/`, {
1951
+ method: "POST",
1952
+ headers: {
1953
+ "Content-Type": "application/json",
1954
+ "X-App-Token": applicationToken
1955
+ },
1956
+ body: JSON.stringify({
1957
+ person: personId,
1958
+ type_of_identification: "FACE",
1959
+ biometric_data: base64Image
1960
+ }),
1961
+ signal: controller.signal
1962
+ });
1963
+ if (!response.ok) {
1964
+ if (response.status === 401 || response.status === 403) {
1965
+ throw new NeoFaceError("Invalid or expired application token, or insufficient permissions", ErrorType.INVALID_TOKEN);
1966
+ }
1967
+ if (response.status === 400) {
1968
+ try {
1969
+ const errorData = await response.json();
1970
+ const errorMessage = errorData.message || errorData.error || "Invalid biometric data";
1971
+ throw new NeoFaceError(errorMessage, ErrorType.VALIDATION_ERROR);
1972
+ } catch (parseError) {
1973
+ if (parseError instanceof NeoFaceError) {
1974
+ throw parseError;
1975
+ }
1976
+ throw new NeoFaceError("Invalid biometric data", ErrorType.VALIDATION_ERROR);
1977
+ }
1978
+ }
1979
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1980
+ }
1981
+ const data = await response.json();
1982
+ return {
1983
+ success: true,
1984
+ message: "Biometric data registered successfully",
1985
+ identity_data_id: data.id,
1986
+ type_of_identification: data.type_of_identification,
1987
+ have_biometric_data: data.have_biometric_data,
1988
+ created_at: data.created_at
1989
+ };
1990
+ } catch (error) {
1991
+ if (error instanceof Error) {
1992
+ if (error.name === "AbortError") {
1993
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1994
+ }
1995
+ if (error instanceof NeoFaceError) {
1996
+ throw error;
1997
+ }
1998
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1999
+ }
2000
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
2001
+ }
2002
+ };
1888
2003
  const modalReducer$1 = (state, action) => {
1889
2004
  switch (action.type) {
1890
2005
  case "PERMISSION_GRANTED":
@@ -2129,8 +2244,8 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
2129
2244
  canvasRef.current.height = videoRef.current.videoHeight;
2130
2245
  context.drawImage(videoRef.current, 0, 0, canvasRef.current.width, canvasRef.current.height);
2131
2246
  const blob = await new Promise((resolve, reject) => {
2132
- var _a;
2133
- (_a = canvasRef.current) == null ? void 0 : _a.toBlob(
2247
+ var _a2;
2248
+ (_a2 = canvasRef.current) == null ? void 0 : _a2.toBlob(
2134
2249
  (b) => {
2135
2250
  if (b)
2136
2251
  resolve(b);
@@ -2267,8 +2382,8 @@ function BiometricRegistrationModal({
2267
2382
  onClose,
2268
2383
  onSuccess,
2269
2384
  onError,
2270
- useRealApi = false
2271
- // 🚀 Por padrão, modo simulado
2385
+ useRealApi = true
2386
+ // Sempre usa API real em produção
2272
2387
  }) {
2273
2388
  const videoRef = useRef(null);
2274
2389
  const canvasRef = useRef(null);
@@ -2530,8 +2645,8 @@ function BiometricRegistrationModal({
2530
2645
  }
2531
2646
  };
2532
2647
  const startTimer = setTimeout(() => {
2533
- var _a;
2534
- if (isRunning && ((_a = videoRef.current) == null ? void 0 : _a.readyState) === 4) {
2648
+ var _a2;
2649
+ if (isRunning && ((_a2 = videoRef.current) == null ? void 0 : _a2.readyState) === 4) {
2535
2650
  detectFace2();
2536
2651
  }
2537
2652
  }, 1e3);
@@ -2592,60 +2707,19 @@ function BiometricRegistrationModal({
2592
2707
  };
2593
2708
  const processRegistration = async (photos) => {
2594
2709
  try {
2595
- if (useRealApi) {
2596
- console.log("🚀 Fazendo requisição REAL ao backend de produção...");
2597
- const result2 = await registerPersonWithBiometric(personData, photos, applicationToken);
2598
- const adaptedResult = {
2599
- success: result2.success,
2600
- message: result2.message,
2601
- person_id: result2.person_id,
2602
- user_id: result2.user_id,
2603
- person_validated: result2.person_validated,
2604
- face_processing: result2.face_processing,
2605
- onboarding_link: result2.onboarding_link
2606
- };
2607
- dispatch({ type: "SUCCESS" });
2608
- onSuccess(adaptedResult);
2609
- return;
2610
- }
2611
- console.log("🎭 Usando modo SIMULADO para testes...");
2612
- const facePhotosBase64 = await Promise.all(
2613
- photos.map(async (photo) => {
2614
- return new Promise((resolve, reject) => {
2615
- const reader = new FileReader();
2616
- reader.onload = () => {
2617
- const result2 = reader.result;
2618
- resolve(result2);
2619
- };
2620
- reader.onerror = reject;
2621
- reader.readAsDataURL(photo);
2622
- });
2623
- })
2624
- );
2625
- await new Promise((resolve) => setTimeout(resolve, 2e3));
2626
- const result = {
2627
- success: true,
2628
- message: "Registro biométrico realizado com sucesso",
2629
- person_id: "test-person-123",
2630
- user_id: "test-user-456",
2631
- person_validated: true,
2632
- face_processing: {
2633
- total_photos_captured: photos.length,
2634
- photo_sizes: photos.map((p, i) => ({
2635
- photo_index: i + 1,
2636
- size_kb: Math.round(p.size / 1024)
2637
- })),
2638
- // Mostra apenas preview dos primeiros 50 caracteres de cada foto em base64
2639
- photos_preview: facePhotosBase64.map((b64, i) => ({
2640
- photo_index: i + 1,
2641
- format: "data:image/jpeg;base64,...",
2642
- preview: b64.substring(0, 50) + "...",
2643
- full_length: b64.length
2644
- }))
2645
- }
2710
+ console.log("🚀 Fazendo requisição ao backend...");
2711
+ const result = await registerPersonWithBiometric(personData, photos, applicationToken);
2712
+ const adaptedResult = {
2713
+ success: result.success,
2714
+ message: result.message,
2715
+ person_id: result.person_id,
2716
+ user_id: result.user_id,
2717
+ person_validated: result.person_validated,
2718
+ face_processing: result.face_processing,
2719
+ onboarding_link: result.onboarding_link
2646
2720
  };
2647
2721
  dispatch({ type: "SUCCESS" });
2648
- onSuccess(result);
2722
+ onSuccess(adaptedResult);
2649
2723
  } catch (error) {
2650
2724
  const errorMessage = error instanceof Error ? error.message : "Erro desconhecido";
2651
2725
  dispatch({ type: "ERROR", message: errorMessage });
@@ -3529,21 +3603,21 @@ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3529
3603
  const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
3530
3604
  const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
3531
3605
  closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
3532
- var _a, _b;
3606
+ var _a2, _b;
3533
3607
  this.close();
3534
- (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
3608
+ (_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
3535
3609
  });
3536
3610
  cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
3537
- var _a, _b;
3611
+ var _a2, _b;
3538
3612
  this.close();
3539
- (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
3613
+ (_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
3540
3614
  });
3541
3615
  captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
3542
3616
  this.captureImage();
3543
3617
  });
3544
3618
  this.modal.addEventListener("click", (e) => {
3545
- var _a, _b, _c;
3546
- if (e.target === ((_a = this.modal) == null ? void 0 : _a.querySelector(".neofaceid-modal-overlay"))) {
3619
+ var _a2, _b, _c;
3620
+ if (e.target === ((_a2 = this.modal) == null ? void 0 : _a2.querySelector(".neofaceid-modal-overlay"))) {
3547
3621
  this.close();
3548
3622
  (_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
3549
3623
  }
@@ -4000,7 +4074,7 @@ function BiometricStatusOverlayComponent({ status, onClose }) {
4000
4074
  alt: "NeoFaceId",
4001
4075
  className: `neofaceid-logo ${status === "verifying" ? "pulsing" : ""}`,
4002
4076
  onError: (e) => {
4003
- var _a;
4077
+ var _a2;
4004
4078
  const img = e.target;
4005
4079
  img.style.display = "none";
4006
4080
  const fallback = document.createElement("div");
@@ -4011,7 +4085,7 @@ function BiometricStatusOverlayComponent({ status, onClose }) {
4011
4085
  </svg>
4012
4086
  `;
4013
4087
  fallback.style.cssText = "width: 80px; height: 80px; display: flex; align-items: center; justify-content: center;";
4014
- (_a = img.parentElement) == null ? void 0 : _a.appendChild(fallback);
4088
+ (_a2 = img.parentElement) == null ? void 0 : _a2.appendChild(fallback);
4015
4089
  }
4016
4090
  }
4017
4091
  ) }),
@@ -4680,6 +4754,116 @@ class EmailPasswordModal {
4680
4754
  }
4681
4755
  const MAX_ATTEMPTS = 3;
4682
4756
  const RETRY_DELAY = 500;
4757
+ async function captureFaceSilently() {
4758
+ return new Promise((resolve, reject) => {
4759
+ const video = document.createElement("video");
4760
+ video.style.position = "fixed";
4761
+ video.style.top = "-9999px";
4762
+ video.style.left = "-9999px";
4763
+ video.style.width = "1px";
4764
+ video.style.height = "1px";
4765
+ video.style.opacity = "0";
4766
+ video.setAttribute("autoplay", "");
4767
+ video.setAttribute("muted", "");
4768
+ video.setAttribute("playsinline", "");
4769
+ const canvas = document.createElement("canvas");
4770
+ canvas.style.display = "none";
4771
+ let stream = null;
4772
+ let faceDetectionAttempts = 0;
4773
+ const MAX_FACE_DETECTION_ATTEMPTS = 10;
4774
+ const cleanup = () => {
4775
+ if (stream) {
4776
+ stream.getTracks().forEach((track) => track.stop());
4777
+ }
4778
+ if (video.parentElement) {
4779
+ video.parentElement.removeChild(video);
4780
+ }
4781
+ if (canvas.parentElement) {
4782
+ canvas.parentElement.removeChild(canvas);
4783
+ }
4784
+ };
4785
+ document.body.appendChild(video);
4786
+ document.body.appendChild(canvas);
4787
+ navigator.mediaDevices.getUserMedia({
4788
+ video: {
4789
+ width: { ideal: 640 },
4790
+ height: { ideal: 480 },
4791
+ facingMode: "user"
4792
+ },
4793
+ audio: false
4794
+ }).then((mediaStream) => {
4795
+ stream = mediaStream;
4796
+ video.srcObject = stream;
4797
+ video.play();
4798
+ const tryCapture = () => {
4799
+ if (!video.videoWidth || !video.videoHeight) {
4800
+ if (faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
4801
+ faceDetectionAttempts++;
4802
+ setTimeout(tryCapture, 100);
4803
+ return;
4804
+ }
4805
+ cleanup();
4806
+ reject(new NeoFaceError("Câmera não está pronta", ErrorType.CAMERA_ERROR));
4807
+ return;
4808
+ }
4809
+ canvas.width = video.videoWidth;
4810
+ canvas.height = video.videoHeight;
4811
+ const ctx = canvas.getContext("2d");
4812
+ if (!ctx) {
4813
+ cleanup();
4814
+ reject(new Error("Failed to get canvas context"));
4815
+ return;
4816
+ }
4817
+ const detectFace2 = async () => {
4818
+ try {
4819
+ const detection = await faceapi.detectSingleFace(
4820
+ video,
4821
+ new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.4 })
4822
+ );
4823
+ return !!detection;
4824
+ } catch {
4825
+ return false;
4826
+ }
4827
+ };
4828
+ detectFace2().then((hasFace) => {
4829
+ if (!hasFace && faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
4830
+ faceDetectionAttempts++;
4831
+ setTimeout(tryCapture, 200);
4832
+ return;
4833
+ }
4834
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
4835
+ canvas.toBlob(
4836
+ (blob) => {
4837
+ cleanup();
4838
+ if (blob) {
4839
+ resolve(blob);
4840
+ } else {
4841
+ reject(new Error("Failed to capture image"));
4842
+ }
4843
+ },
4844
+ "image/jpeg",
4845
+ 0.85
4846
+ );
4847
+ });
4848
+ };
4849
+ video.onloadedmetadata = () => {
4850
+ setTimeout(tryCapture, 800);
4851
+ };
4852
+ video.onerror = () => {
4853
+ cleanup();
4854
+ reject(new NeoFaceError("Erro ao acessar câmera", ErrorType.CAMERA_ERROR));
4855
+ };
4856
+ }).catch((error) => {
4857
+ cleanup();
4858
+ reject(
4859
+ new NeoFaceError(
4860
+ "Não foi possível acessar a câmera: " + error.message,
4861
+ ErrorType.CAMERA_ERROR
4862
+ )
4863
+ );
4864
+ });
4865
+ });
4866
+ }
4683
4867
  async function detectFaceContinuously(video, minDetectionTime = 3e3, detectionInterval = 200) {
4684
4868
  return new Promise((resolve) => {
4685
4869
  let faceDetectedCount = 0;
@@ -4902,6 +5086,11 @@ async function executeBiometricLoginFlow(options) {
4902
5086
  tryLogin();
4903
5087
  }, 300);
4904
5088
  }
5089
+ const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
5090
+ __proto__: null,
5091
+ captureFaceSilently,
5092
+ executeBiometricLoginFlow
5093
+ }, Symbol.toStringTag, { value: "Module" }));
4905
5094
  async function startFaceLogin(options) {
4906
5095
  try {
4907
5096
  const isValidToken = await validateToken(options.applicationToken);
@@ -5229,8 +5418,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
5229
5418
  initializeBiometricDetection,
5230
5419
  isAdvancedDetectionAvailable
5231
5420
  }, Symbol.toStringTag, { value: "Module" }));
5232
- const VERSION = "1.4.0";
5233
- const RELEASE_DATE = "2025-11-23";
5421
+ const VERSION = "1.6.0";
5422
+ const RELEASE_DATE = "2025-12-01";
5234
5423
  class OnboardingCaptureModal {
5235
5424
  constructor(options) {
5236
5425
  __publicField(this, "overlay", null);
@@ -5454,6 +5643,284 @@ const startOnboarding = async (options) => {
5454
5643
  onError(new NeoFaceError(message, ErrorType.UNKNOWN));
5455
5644
  }
5456
5645
  };
5646
+ class NeoFaceID {
5647
+ /**
5648
+ * Creates a new NeoFaceID instance
5649
+ * @param config Configuration object
5650
+ * @throws NeoFaceError if signature format is invalid
5651
+ */
5652
+ constructor(config) {
5653
+ __publicField(this, "appToken");
5654
+ __publicField(this, "baseUrl");
5655
+ __publicField(this, "signature");
5656
+ __publicField(this, "sessionData");
5657
+ var _a2;
5658
+ this.appToken = config.appToken;
5659
+ this.baseUrl = config.baseUrl || (((_a2 = { "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false }) == null ? void 0 : _a2.VITE_API_BASE_URL) || "http://localhost:8000");
5660
+ this.signature = config.signature;
5661
+ this.sessionData = config.sessionData;
5662
+ if (this.signature && !this.validateSignatureFormat(this.signature)) {
5663
+ throw new NeoFaceError(
5664
+ "Invalid signature format. Expected HMAC-SHA256 hex string (minimum 64 characters)",
5665
+ ErrorType.VALIDATION_ERROR
5666
+ );
5667
+ }
5668
+ if (this.sessionData) {
5669
+ if (!this.sessionData.email || !this.sessionData.cpf || !this.sessionData.sessionId) {
5670
+ throw new NeoFaceError(
5671
+ "Session data must include email, cpf, and sessionId",
5672
+ ErrorType.VALIDATION_ERROR
5673
+ );
5674
+ }
5675
+ }
5676
+ }
5677
+ /**
5678
+ * Validates the format of a signature
5679
+ * HMAC-SHA256 produces a 64-character hexadecimal string
5680
+ * @param signature The signature to validate
5681
+ * @returns true if valid, false otherwise
5682
+ */
5683
+ validateSignatureFormat(signature) {
5684
+ return /^[a-f0-9]{64,}$/i.test(signature);
5685
+ }
5686
+ /**
5687
+ * Captures multiple face frames for biometric recognition
5688
+ * @param options Capture options
5689
+ * @returns Promise that resolves to an array of image blobs
5690
+ * @throws NeoFaceError if capture fails
5691
+ */
5692
+ async captureFaceFrames(options) {
5693
+ const { numFrames, livenessCheck } = options;
5694
+ if (numFrames < 1 || numFrames > 10) {
5695
+ throw new NeoFaceError(
5696
+ "Number of frames must be between 1 and 10",
5697
+ ErrorType.VALIDATION_ERROR
5698
+ );
5699
+ }
5700
+ const frames = [];
5701
+ try {
5702
+ const { captureFaceSilently: captureFaceSilently2 } = await Promise.resolve().then(() => biometricLoginFlow);
5703
+ for (let i = 0; i < numFrames; i++) {
5704
+ const frame = await captureFaceSilently2();
5705
+ frames.push(frame);
5706
+ if (livenessCheck && i < numFrames - 1) {
5707
+ await new Promise((resolve) => setTimeout(resolve, 300));
5708
+ }
5709
+ }
5710
+ return frames;
5711
+ } catch (error) {
5712
+ if (error instanceof NeoFaceError) {
5713
+ throw error;
5714
+ }
5715
+ throw new NeoFaceError(
5716
+ `Failed to capture face frames: ${error instanceof Error ? error.message : "Unknown error"}`,
5717
+ ErrorType.CAPTURE_ERROR
5718
+ );
5719
+ }
5720
+ }
5721
+ /**
5722
+ * Performs login recognition using biometric data
5723
+ *
5724
+ * This method is designed for external integrations that require
5725
+ * signature validation and session management.
5726
+ *
5727
+ * @param options Recognition options
5728
+ * @returns Promise that resolves to recognition result
5729
+ * @throws NeoFaceError if recognition fails
5730
+ *
5731
+ * @example
5732
+ * ```typescript
5733
+ * const result = await sdk.loginRecognition({
5734
+ * biometricData: await sdk.captureFaceFrames({
5735
+ * numFrames: 5,
5736
+ * livenessCheck: true
5737
+ * }),
5738
+ * typeOfIdentification: 'FACE',
5739
+ * purpose: 'LOGIN',
5740
+ * confidenceThreshold: 0.8
5741
+ * });
5742
+ *
5743
+ * // Result includes signature and sessionId for callback validation
5744
+ * console.log(result.signature, result.sessionId);
5745
+ * ```
5746
+ */
5747
+ async loginRecognition(options) {
5748
+ const {
5749
+ biometricData,
5750
+ typeOfIdentification,
5751
+ purpose,
5752
+ confidenceThreshold = 0.8
5753
+ } = options;
5754
+ const imageBlobs = Array.isArray(biometricData) ? biometricData : [biometricData];
5755
+ if (imageBlobs.length === 0) {
5756
+ throw new NeoFaceError(
5757
+ "At least one biometric data frame is required",
5758
+ ErrorType.VALIDATION_ERROR
5759
+ );
5760
+ }
5761
+ const primaryImage = imageBlobs[0];
5762
+ try {
5763
+ const result = await recognizeByPurpose(
5764
+ primaryImage,
5765
+ this.appToken,
5766
+ purpose,
5767
+ confidenceThreshold,
5768
+ this.signature,
5769
+ this.sessionData
5770
+ );
5771
+ if (!result.success) {
5772
+ throw new NeoFaceError(
5773
+ "Recognition failed",
5774
+ ErrorType.RECOGNITION_FAILED
5775
+ );
5776
+ }
5777
+ if (!result.personName || !result.email || !result.cpf) {
5778
+ throw new NeoFaceError(
5779
+ "Incomplete recognition result: missing personName, email, or cpf",
5780
+ ErrorType.RECOGNITION_FAILED
5781
+ );
5782
+ }
5783
+ return {
5784
+ success: true,
5785
+ personName: result.personName,
5786
+ email: result.email,
5787
+ cpf: result.cpf,
5788
+ signature: result.signature,
5789
+ sessionId: result.sessionId,
5790
+ confidenceScore: result.confidenceScore,
5791
+ accessToken: result.accessToken
5792
+ };
5793
+ } catch (error) {
5794
+ if (error instanceof NeoFaceError) {
5795
+ throw error;
5796
+ }
5797
+ throw new NeoFaceError(
5798
+ `Login recognition failed: ${error instanceof Error ? error.message : "Unknown error"}`,
5799
+ ErrorType.RECOGNITION_FAILED
5800
+ );
5801
+ }
5802
+ }
5803
+ }
5804
+ const PHOTO_INSTRUCTIONS = [
5805
+ "Olhe diretamente para a câmera",
5806
+ "Vire levemente a cabeça para a esquerda",
5807
+ "Vire levemente a cabeça para a direita",
5808
+ "Mostre o polegar para cima (👍) e olhe para a câmera"
5809
+ ];
5810
+ const TOTAL_PHOTOS = 4;
5811
+ const authorizeOperation = async (applicationToken, cpf, options) => {
5812
+ if (!applicationToken) {
5813
+ throw new NeoFaceError("Application token is required", ErrorType.VALIDATION_ERROR);
5814
+ }
5815
+ if (!cpf) {
5816
+ throw new NeoFaceError("CPF is required", ErrorType.VALIDATION_ERROR);
5817
+ }
5818
+ const cpfClean = cpf.replace(/\D/g, "");
5819
+ if (cpfClean.length !== 11) {
5820
+ throw new NeoFaceError("Invalid CPF format", ErrorType.VALIDATION_ERROR);
5821
+ }
5822
+ const capturedPhotos = [];
5823
+ const captureDelay = (options == null ? void 0 : options.captureDelay) || 2e3;
5824
+ try {
5825
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
5826
+ throw new NeoFaceError("Camera not available", ErrorType.NO_CAMERA);
5827
+ }
5828
+ const stream = await navigator.mediaDevices.getUserMedia({
5829
+ video: {
5830
+ facingMode: "user",
5831
+ width: { ideal: 1280 },
5832
+ height: { ideal: 720 }
5833
+ }
5834
+ });
5835
+ const video = document.createElement("video");
5836
+ video.srcObject = stream;
5837
+ video.autoplay = true;
5838
+ video.playsInline = true;
5839
+ await new Promise((resolve) => {
5840
+ video.onloadedmetadata = () => {
5841
+ video.play();
5842
+ resolve();
5843
+ };
5844
+ });
5845
+ const canvas = document.createElement("canvas");
5846
+ canvas.width = video.videoWidth;
5847
+ canvas.height = video.videoHeight;
5848
+ const ctx = canvas.getContext("2d");
5849
+ if (!ctx) {
5850
+ throw new NeoFaceError("Failed to get canvas context", ErrorType.CAPTURE_ERROR);
5851
+ }
5852
+ for (let i = 0; i < TOTAL_PHOTOS; i++) {
5853
+ const instruction = PHOTO_INSTRUCTIONS[i];
5854
+ if (options == null ? void 0 : options.onProgress) {
5855
+ options.onProgress(i + 1, TOTAL_PHOTOS, instruction);
5856
+ }
5857
+ if (i > 0) {
5858
+ await new Promise((resolve) => setTimeout(resolve, captureDelay));
5859
+ }
5860
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
5861
+ const blob = await new Promise((resolve, reject) => {
5862
+ canvas.toBlob(
5863
+ (blob2) => {
5864
+ if (blob2) {
5865
+ resolve(blob2);
5866
+ } else {
5867
+ reject(new Error("Failed to capture photo"));
5868
+ }
5869
+ },
5870
+ "image/jpeg",
5871
+ 0.9
5872
+ );
5873
+ });
5874
+ capturedPhotos.push(blob);
5875
+ if (options == null ? void 0 : options.onPhotoTaken) {
5876
+ options.onPhotoTaken(i + 1, blob);
5877
+ }
5878
+ }
5879
+ stream.getTracks().forEach((track) => track.stop());
5880
+ const lastPhoto = capturedPhotos[TOTAL_PHOTOS - 1];
5881
+ const sessionData = {
5882
+ cpf: cpfClean,
5883
+ email: "",
5884
+ // Not required for authorization
5885
+ sessionId: `auth_${Date.now()}_${Math.random().toString(36).substring(7)}`
5886
+ };
5887
+ const recognitionResult = await recognizeByPurpose(
5888
+ lastPhoto,
5889
+ applicationToken,
5890
+ "AUTHORIZATION",
5891
+ 0.8,
5892
+ // Confidence threshold
5893
+ void 0,
5894
+ // No signature for now
5895
+ sessionData
5896
+ );
5897
+ if (!recognitionResult.success) {
5898
+ throw new NeoFaceError(
5899
+ "Authorization failed: Person not recognized",
5900
+ ErrorType.RECOGNITION_FAILED
5901
+ );
5902
+ }
5903
+ return {
5904
+ success: true,
5905
+ authorizationHash: sessionData.sessionId,
5906
+ // TEMPORARY: Using sessionId as hash
5907
+ name: recognitionResult.personName,
5908
+ birthDate: void 0,
5909
+ // Should come from backend
5910
+ cpf: recognitionResult.cpf || cpfClean,
5911
+ confidenceScore: recognitionResult.confidenceScore,
5912
+ message: "Authorization successful"
5913
+ };
5914
+ } catch (error) {
5915
+ if (error instanceof NeoFaceError) {
5916
+ throw error;
5917
+ }
5918
+ if (error instanceof Error) {
5919
+ throw new NeoFaceError(error.message, ErrorType.CAPTURE_ERROR);
5920
+ }
5921
+ throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
5922
+ }
5923
+ };
5457
5924
  function start(applicationToken, callbacks) {
5458
5925
  const container = document.createElement("div");
5459
5926
  container.id = "neoface-modal-container";
@@ -5496,8 +5963,8 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
5496
5963
  const modalElement = require$$0$1.createElement(BiometricRegistrationModal, {
5497
5964
  personData,
5498
5965
  applicationToken,
5499
- useRealApi: (options == null ? void 0 : options.useRealApi) || false,
5500
- // 🚀 Passa a flag para o modal
5966
+ useRealApi: (options == null ? void 0 : options.useRealApi) ?? true,
5967
+ // Sempre usa API real em produção
5501
5968
  onClose: cleanup,
5502
5969
  onSuccess: (result) => {
5503
5970
  cleanup();
@@ -5524,8 +5991,10 @@ export {
5524
5991
  FaceCaptureModal,
5525
5992
  FallbackPrompt,
5526
5993
  NeoFaceError,
5994
+ NeoFaceID,
5527
5995
  RELEASE_DATE,
5528
5996
  VERSION,
5997
+ authorizeOperation,
5529
5998
  biometricLogin,
5530
5999
  biometricLoginWithFallback,
5531
6000
  completeOnboarding,
@@ -5538,6 +6007,7 @@ export {
5538
6007
  recognize,
5539
6008
  recognizeBiometric,
5540
6009
  recognizeByPurpose,
6010
+ registerBiometric,
5541
6011
  registerPersonWithBiometric,
5542
6012
  registerPersonWithoutFace,
5543
6013
  simpleIdentification,