@neofaceid/web-sdk 1.15.0 → 1.17.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.
@@ -1623,7 +1623,7 @@ const loginWithBiometric = async (image, applicationToken) => {
1623
1623
  }
1624
1624
  return {
1625
1625
  success: true,
1626
- accessToken: data.accessToken,
1626
+ accessToken: data.accessToken || data.access_token || "",
1627
1627
  alias: data.alias,
1628
1628
  user: {
1629
1629
  id: ((_a = data.user) == null ? void 0 : _a.id) || "",
@@ -1633,6 +1633,7 @@ const loginWithBiometric = async (image, applicationToken) => {
1633
1633
  active: ((_e = data.user) == null ? void 0 : _e.active) || false
1634
1634
  },
1635
1635
  person: data.person ? {
1636
+ id: data.person.id || "",
1636
1637
  name: data.person.name || "",
1637
1638
  birth_date: data.person.birth_date || ""
1638
1639
  } : void 0,
@@ -1652,110 +1653,6 @@ const loginWithBiometric = async (image, applicationToken) => {
1652
1653
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1653
1654
  }
1654
1655
  };
1655
- const loginWithBiometricSSE = async (image, applicationToken) => {
1656
- var _a;
1657
- ensureSecureContext();
1658
- const compressedImage = await compressImage(image);
1659
- const arrayBuffer = await compressedImage.arrayBuffer();
1660
- const base64Image = btoa(
1661
- new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), "")
1662
- );
1663
- const controller = createTimeoutController();
1664
- let response;
1665
- try {
1666
- response = await fetch(`${getApiBaseUrl()}/api/v1/auth/recognition/face/external/`, {
1667
- method: "POST",
1668
- headers: {
1669
- "Content-Type": "application/json",
1670
- "X-App-Token": applicationToken
1671
- },
1672
- body: JSON.stringify({ image: base64Image, purpose: "LOGIN" }),
1673
- signal: controller.signal
1674
- });
1675
- } catch (error) {
1676
- if (error instanceof Error) {
1677
- if (error.name === "AbortError") {
1678
- throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1679
- }
1680
- if (error instanceof NeoFaceError) throw error;
1681
- throw new NeoFaceError(error.message, ErrorType.NETWORK);
1682
- }
1683
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1684
- }
1685
- if (!response.ok) {
1686
- if (response.status === 401 || response.status === 403) {
1687
- throw new NeoFaceError("Invalid application token", ErrorType.INVALID_TOKEN);
1688
- }
1689
- throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
1690
- }
1691
- const initData = await response.json();
1692
- if (!initData.success || !((_a = initData.data) == null ? void 0 : _a.task_id)) {
1693
- throw new NeoFaceError(initData.message || "Failed to start login", ErrorType.LOGIN_FAILED);
1694
- }
1695
- const taskId = initData.data.task_id;
1696
- return new Promise((resolve, reject) => {
1697
- const sseUrl = `${getApiBaseUrl()}/api/v1/auth/recognition/face/external/login/events/${taskId}/`;
1698
- const eventSource = new EventSource(sseUrl);
1699
- const timeoutId = setTimeout(() => {
1700
- eventSource.close();
1701
- reject(new NeoFaceError("Login timeout", ErrorType.NETWORK));
1702
- }, 31e4);
1703
- const cleanup = () => {
1704
- clearTimeout(timeoutId);
1705
- eventSource.close();
1706
- };
1707
- eventSource.addEventListener("success", (event) => {
1708
- var _a2, _b, _c, _d, _e, _f;
1709
- cleanup();
1710
- try {
1711
- const data = JSON.parse(event.data);
1712
- resolve({
1713
- success: true,
1714
- accessToken: data.token || data.access_token || "",
1715
- alias: ((_a2 = data.user) == null ? void 0 : _a2.alias) || data.alias || "",
1716
- user: {
1717
- id: ((_b = data.user) == null ? void 0 : _b.id) || "",
1718
- email: ((_c = data.user) == null ? void 0 : _c.email) || "",
1719
- role: ((_d = data.user) == null ? void 0 : _d.role) || "",
1720
- validated: ((_e = data.user) == null ? void 0 : _e.validated) ?? false,
1721
- active: ((_f = data.user) == null ? void 0 : _f.active) ?? false
1722
- },
1723
- person: data.person ? { name: data.person.name || "", birth_date: data.person.birth_date || "" } : void 0,
1724
- confidence_score: data.confidence_score,
1725
- recognition_id: data.recognition_id ?? data.history_id
1726
- });
1727
- } catch {
1728
- reject(new NeoFaceError("Erro ao processar resposta de login", ErrorType.API_ERROR));
1729
- }
1730
- });
1731
- eventSource.addEventListener("failed", (event) => {
1732
- cleanup();
1733
- try {
1734
- const data = JSON.parse(event.data);
1735
- reject(new NeoFaceError(data.message || "Login failed", ErrorType.LOGIN_FAILED));
1736
- } catch {
1737
- reject(new NeoFaceError("Login failed", ErrorType.LOGIN_FAILED));
1738
- }
1739
- });
1740
- eventSource.addEventListener("error", (event) => {
1741
- cleanup();
1742
- try {
1743
- const data = JSON.parse(event.data || "{}");
1744
- reject(new NeoFaceError(data.message || "Processing error", ErrorType.API_ERROR));
1745
- } catch {
1746
- reject(new NeoFaceError("Processing error", ErrorType.API_ERROR));
1747
- }
1748
- });
1749
- eventSource.addEventListener("timeout", () => {
1750
- cleanup();
1751
- reject(new NeoFaceError("Login timeout", ErrorType.NETWORK));
1752
- });
1753
- eventSource.onerror = () => {
1754
- cleanup();
1755
- reject(new NeoFaceError("SSE connection error", ErrorType.NETWORK));
1756
- };
1757
- });
1758
- };
1759
1656
  const recognizeBiometric = async (image, applicationToken, livenessCheck = true, confidenceThreshold = 0.8) => {
1760
1657
  ensureSecureContext();
1761
1658
  const controller = createTimeoutController();
@@ -1942,7 +1839,8 @@ const registerPersonWithoutFace = async (personData, applicationToken, options)
1942
1839
  cpf: personData.cpf,
1943
1840
  email: personData.email,
1944
1841
  password: personData.password,
1945
- password_confirm: personData.password
1842
+ password_confirm: personData.password,
1843
+ is_pep: personData.is_pep ?? false
1946
1844
  };
1947
1845
  if (documentFrontBase64) {
1948
1846
  body.document_images = [
@@ -1983,6 +1881,43 @@ const registerPersonWithoutFace = async (personData, applicationToken, options)
1983
1881
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
1984
1882
  }
1985
1883
  };
1884
+ const checkUserExistence = async (params) => {
1885
+ const controller = createTimeoutController();
1886
+ const queryParams = new URLSearchParams();
1887
+ if (params.email) queryParams.append("email", params.email);
1888
+ if (params.cpf) queryParams.append("cpf", params.cpf);
1889
+ try {
1890
+ const response = await fetch(
1891
+ `${getApiBaseUrl()}/api/v1/verify/user/?${queryParams.toString()}`,
1892
+ {
1893
+ method: "GET",
1894
+ headers: {
1895
+ "Content-Type": "application/json"
1896
+ },
1897
+ signal: controller.signal
1898
+ }
1899
+ );
1900
+ if (!response.ok) {
1901
+ const errorData = await response.json().catch(() => ({}));
1902
+ throw new NeoFaceError(
1903
+ errorData.message || `Server returned status ${response.status}`,
1904
+ ErrorType.NETWORK
1905
+ );
1906
+ }
1907
+ return await response.json();
1908
+ } catch (error) {
1909
+ if (error instanceof Error) {
1910
+ if (error.name === "AbortError") {
1911
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1912
+ }
1913
+ if (error instanceof NeoFaceError) {
1914
+ throw error;
1915
+ }
1916
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1917
+ }
1918
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
1919
+ }
1920
+ };
1986
1921
  const registerPersonWithBiometric = async (personData, facePhotos, applicationToken, options) => {
1987
1922
  var _a, _b, _c, _d, _e, _f, _g, _h;
1988
1923
  ensureSecureContext();
@@ -2038,7 +1973,8 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
2038
1973
  email: personData.email,
2039
1974
  password: personData.password,
2040
1975
  password_confirm: personData.password,
2041
- face_photos: facePhotosBase64
1976
+ face_photos: facePhotosBase64,
1977
+ is_pep: personData.is_pep ?? false
2042
1978
  };
2043
1979
  if (documentFrontBase64) {
2044
1980
  body.document_images = [
@@ -2132,7 +2068,6 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
2132
2068
  }
2133
2069
  };
2134
2070
  const loginWithEmail = async (email, password, applicationToken) => {
2135
- ensureSecureContext();
2136
2071
  const controller = createTimeoutController();
2137
2072
  try {
2138
2073
  const response = await fetch(`${getApiBaseUrl()}/api/v1/auth/login/`, {
@@ -2178,10 +2113,10 @@ const loginWithEmail = async (email, password, applicationToken) => {
2178
2113
  }
2179
2114
  };
2180
2115
  const identifyPerson = async (image, applicationToken) => {
2181
- var _a, _b;
2116
+ var _a, _b, _c, _d;
2182
2117
  ensureSecureContext();
2183
2118
  const controller = createTimeoutController();
2184
- const compressedImage = await compressImage(image);
2119
+ const compressedImage = await compressDocumentImage(image);
2185
2120
  const base64Image = await new Promise((resolve, reject) => {
2186
2121
  const reader = new FileReader();
2187
2122
  reader.onload = () => {
@@ -2192,7 +2127,7 @@ const identifyPerson = async (image, applicationToken) => {
2192
2127
  reader.readAsDataURL(compressedImage);
2193
2128
  });
2194
2129
  try {
2195
- const response = await fetch(`${getApiBaseUrl()}/api/v1/external/recognition/face/`, {
2130
+ const response = await fetch(`${getApiBaseUrl()}/api/v1/auth/recognition/face/external/`, {
2196
2131
  method: "POST",
2197
2132
  headers: {
2198
2133
  "Content-Type": "application/json",
@@ -2211,30 +2146,26 @@ const identifyPerson = async (image, applicationToken) => {
2211
2146
  throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
2212
2147
  }
2213
2148
  const data = await response.json();
2149
+ if (response.status === 202 || data.data && data.data.task_id) {
2150
+ return {
2151
+ success: true,
2152
+ taskId: ((_a = data.data) == null ? void 0 : _a.task_id) || data.task_id,
2153
+ status: ((_b = data.data) == null ? void 0 : _b.status) || "processing",
2154
+ message: data.message || "Identification scheduled"
2155
+ };
2156
+ }
2214
2157
  if (!data.success) {
2215
2158
  throw new NeoFaceError(
2216
2159
  data.message || "Person identification failed",
2217
2160
  ErrorType.RECOGNITION_FAILED
2218
2161
  );
2219
2162
  }
2220
- const personName = ((_a = data.person) == null ? void 0 : _a.name) || "";
2221
- const birthDateStr = (_b = data.person) == null ? void 0 : _b.birth_date;
2222
- if (!personName) {
2223
- throw new NeoFaceError("Person name not found in response", ErrorType.PERSON_NOT_FOUND);
2224
- }
2225
- if (!birthDateStr) {
2226
- throw new NeoFaceError("Birth date not found in response", ErrorType.PERSON_NOT_FOUND);
2227
- }
2228
- const birthDate = new Date(birthDateStr);
2229
- const today = /* @__PURE__ */ new Date();
2230
- let age = today.getFullYear() - birthDate.getFullYear();
2231
- const monthDiff = today.getMonth() - birthDate.getMonth();
2232
- if (monthDiff < 0 || monthDiff === 0 && today.getDate() < birthDate.getDate()) {
2233
- age--;
2234
- }
2235
2163
  return {
2236
- name: personName,
2237
- age
2164
+ success: true,
2165
+ person: {
2166
+ name: ((_c = data.person) == null ? void 0 : _c.name) || "",
2167
+ birth_date: ((_d = data.person) == null ? void 0 : _d.birth_date) || ""
2168
+ }
2238
2169
  };
2239
2170
  } catch (error) {
2240
2171
  if (error instanceof Error) {
@@ -2668,6 +2599,135 @@ const registerDocumentByImage = async (personId, jwtToken, images) => {
2668
2599
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
2669
2600
  }
2670
2601
  };
2602
+ const getTaskStatus = async (taskId) => {
2603
+ const controller = createTimeoutController();
2604
+ try {
2605
+ const response = await fetch(`${getApiBaseUrl()}/api/v1/tasks/status/?task_id=${taskId}`, {
2606
+ method: "GET",
2607
+ headers: {
2608
+ "Content-Type": "application/json"
2609
+ },
2610
+ signal: controller.signal
2611
+ });
2612
+ const data = await response.json();
2613
+ if (!response.ok) {
2614
+ throw new NeoFaceError(
2615
+ (data == null ? void 0 : data.message) || `API Error ${response.status}`,
2616
+ ErrorType.NETWORK_ERROR
2617
+ );
2618
+ }
2619
+ return {
2620
+ success: data.success,
2621
+ status: data.data.status,
2622
+ result: data.data.result,
2623
+ error: data.data.error
2624
+ };
2625
+ } catch (error) {
2626
+ if (error instanceof NeoFaceError) throw error;
2627
+ throw new NeoFaceError("Failed to fetch task status", ErrorType.NETWORK_ERROR);
2628
+ }
2629
+ };
2630
+ const identifyPersonAsync = async (image, applicationToken, options = {}) => {
2631
+ var _a, _b;
2632
+ const { maxRetries = 30, interval = 2e3 } = options;
2633
+ const initialResponse = await identifyPerson(image, applicationToken);
2634
+ if (initialResponse.person) {
2635
+ return {
2636
+ personFound: true,
2637
+ person: initialResponse.person
2638
+ };
2639
+ }
2640
+ const taskId = initialResponse.taskId;
2641
+ if (!taskId) {
2642
+ return { personFound: false };
2643
+ }
2644
+ for (let i = 0; i < maxRetries; i++) {
2645
+ const status = await getTaskStatus(taskId);
2646
+ if (status.status === "success") {
2647
+ if (status.result && status.result.success) {
2648
+ return {
2649
+ personFound: true,
2650
+ person: {
2651
+ name: ((_a = status.result.person) == null ? void 0 : _a.name) || "Pessoa identificada",
2652
+ birth_date: ((_b = status.result.person) == null ? void 0 : _b.birth_date) || ""
2653
+ }
2654
+ };
2655
+ }
2656
+ return { personFound: false };
2657
+ }
2658
+ if (status.status === "failure") {
2659
+ return { personFound: false };
2660
+ }
2661
+ await new Promise((resolve) => setTimeout(resolve, interval));
2662
+ }
2663
+ throw new NeoFaceError("Timed out waiting for identification", ErrorType.NETWORK_ERROR);
2664
+ };
2665
+ async function requestPasswordReset(email, applicationToken) {
2666
+ const baseUrl = getBaseUrl();
2667
+ const response = await fetch(`${baseUrl}/api/v1/user/password/reset/request/`, {
2668
+ method: "POST",
2669
+ headers: {
2670
+ "Content-Type": "application/json",
2671
+ "X-App-Token": applicationToken
2672
+ },
2673
+ body: JSON.stringify({ email })
2674
+ });
2675
+ if (!response.ok) {
2676
+ const errorData = await response.json();
2677
+ throw new NeoFaceError(
2678
+ errorData.message || "Erro ao solicitar recuperação de senha",
2679
+ ErrorType.API_ERROR
2680
+ );
2681
+ }
2682
+ return response.json();
2683
+ }
2684
+ async function confirmPasswordReset(token, new_password) {
2685
+ const baseUrl = getBaseUrl();
2686
+ const response = await fetch(`${baseUrl}/api/v1/user/password/reset/confirm/`, {
2687
+ method: "POST",
2688
+ headers: {
2689
+ "Content-Type": "application/json"
2690
+ },
2691
+ body: JSON.stringify({ token, new_password })
2692
+ });
2693
+ if (!response.ok) {
2694
+ const errorData = await response.json();
2695
+ throw new NeoFaceError(
2696
+ errorData.error || errorData.message || "Erro ao redefinir senha",
2697
+ ErrorType.API_ERROR
2698
+ );
2699
+ }
2700
+ return response.json();
2701
+ }
2702
+ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2703
+ __proto__: null,
2704
+ checkUserExistence,
2705
+ completeOnboarding,
2706
+ completeOnboardingWithData,
2707
+ confirmPasswordReset,
2708
+ getOnboardingDetails,
2709
+ getTaskStatus,
2710
+ identifyPerson,
2711
+ identifyPersonAsync,
2712
+ loginWithBiometric,
2713
+ loginWithEmail,
2714
+ pollTaskStatus,
2715
+ recognize,
2716
+ recognizeBiometric,
2717
+ recognizeByPurpose,
2718
+ recordFaceVideo,
2719
+ registerApplication,
2720
+ registerBiometric,
2721
+ registerDocumentByImage,
2722
+ registerPersonWithBiometric,
2723
+ registerPersonWithoutFace,
2724
+ requestPasswordReset,
2725
+ simpleIdentification,
2726
+ startProofOfLife,
2727
+ validateOnboardingToken,
2728
+ validateToken,
2729
+ videoToBase64
2730
+ }, Symbol.toStringTag, { value: "Module" }));
2671
2731
  const modalReducer$1 = (state, action) => {
2672
2732
  switch (action.type) {
2673
2733
  case "PERMISSION_GRANTED":
@@ -2961,6 +3021,336 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
2961
3021
  ] })
2962
3022
  ] });
2963
3023
  }
3024
+ class ForgotPasswordModal {
3025
+ constructor(applicationToken) {
3026
+ __publicField(this, "applicationToken");
3027
+ __publicField(this, "modalElement", null);
3028
+ this.applicationToken = applicationToken;
3029
+ }
3030
+ open() {
3031
+ if (this.modalElement) return;
3032
+ this.addStyles();
3033
+ this.modalElement = document.createElement("div");
3034
+ this.modalElement.className = "neoface-forgot-modal";
3035
+ this.renderEmailForm();
3036
+ document.body.appendChild(this.modalElement);
3037
+ }
3038
+ close() {
3039
+ if (this.modalElement && document.body.contains(this.modalElement)) {
3040
+ document.body.removeChild(this.modalElement);
3041
+ this.modalElement = null;
3042
+ }
3043
+ }
3044
+ renderEmailForm() {
3045
+ if (!this.modalElement) return;
3046
+ this.modalElement.innerHTML = `
3047
+ <div class="neoface-forgot-overlay">
3048
+ <div class="neoface-forgot-content">
3049
+ <div class="neoface-forgot-icon">
3050
+ <svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
3051
+ <circle cx="32" cy="32" r="28" stroke="#7c3aed" stroke-width="3" fill="none"/>
3052
+ <rect x="22" y="28" width="20" height="16" rx="3" stroke="#7c3aed" stroke-width="2.5" fill="none"/>
3053
+ <path d="M26 28v-4a6 6 0 0 1 12 0v4" stroke="#7c3aed" stroke-width="2.5" stroke-linecap="round"/>
3054
+ <circle cx="32" cy="36" r="2" fill="#7c3aed"/>
3055
+ </svg>
3056
+ </div>
3057
+
3058
+ <h2 class="neoface-forgot-title">Recuperar senha</h2>
3059
+ <p class="neoface-forgot-subtitle">Informe seu email e enviaremos um link para redefinir sua senha.</p>
3060
+
3061
+ <div id="neoface-forgot-error" class="neoface-forgot-error" style="display:none"></div>
3062
+
3063
+ <form id="neoface-forgot-form">
3064
+ <input
3065
+ type="email"
3066
+ id="neoface-forgot-email"
3067
+ class="neoface-forgot-input"
3068
+ placeholder="seu@email.com"
3069
+ autocomplete="email"
3070
+ required
3071
+ />
3072
+ <button type="submit" class="neoface-forgot-btn-primary">
3073
+ Enviar link
3074
+ </button>
3075
+ </form>
3076
+
3077
+ <button class="neoface-forgot-btn-ghost neoface-forgot-back">
3078
+ Voltar
3079
+ </button>
3080
+
3081
+ <div class="neoface-forgot-branding">
3082
+ <img src="/logo-icone-no-background.png" alt="NeoFaceId" class="neoface-forgot-brand-logo" onerror="this.style.display='none'" />
3083
+ <span>NeoFaceId by</span>
3084
+ <span class="neoface-forgot-brand-name">OCTA</span>
3085
+ </div>
3086
+ </div>
3087
+ </div>
3088
+ `;
3089
+ const form = this.modalElement.querySelector("#neoface-forgot-form");
3090
+ form.addEventListener("submit", this.handleSubmit.bind(this));
3091
+ const backBtn = this.modalElement.querySelector(".neoface-forgot-back");
3092
+ backBtn.addEventListener("click", this.close.bind(this));
3093
+ }
3094
+ renderSuccess() {
3095
+ if (!this.modalElement) return;
3096
+ const content = this.modalElement.querySelector(".neoface-forgot-content");
3097
+ content.innerHTML = `
3098
+ <div class="neoface-forgot-success-icon">
3099
+ <svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
3100
+ <circle cx="32" cy="32" r="28" stroke="#10b981" stroke-width="3" fill="none"/>
3101
+ <path d="M20 32 L28 40 L44 24" stroke="#10b981" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
3102
+ </svg>
3103
+ </div>
3104
+
3105
+ <h2 class="neoface-forgot-title">Email enviado!</h2>
3106
+ <p class="neoface-forgot-subtitle">
3107
+ Verifique sua caixa de entrada e siga as instruções para redefinir sua senha.
3108
+ <br/><br/>
3109
+ <span style="color: rgba(255,255,255,0.5); font-size: 13px;">Não recebeu? Verifique também sua pasta de spam.</span>
3110
+ </p>
3111
+
3112
+ <button class="neoface-forgot-btn-primary neoface-forgot-close-success" style="margin-top: 8px;">
3113
+ Fechar
3114
+ </button>
3115
+
3116
+ <div class="neoface-forgot-branding">
3117
+ <img src="/logo-icone-no-background.png" alt="NeoFaceId" class="neoface-forgot-brand-logo" onerror="this.style.display='none'" />
3118
+ <span>NeoFaceId by</span>
3119
+ <span class="neoface-forgot-brand-name">OCTA</span>
3120
+ </div>
3121
+ `;
3122
+ const closeBtn = content.querySelector(".neoface-forgot-close-success");
3123
+ closeBtn.addEventListener("click", this.close.bind(this));
3124
+ }
3125
+ async handleSubmit(e) {
3126
+ var _a, _b, _c, _d;
3127
+ e.preventDefault();
3128
+ const emailInput = (_a = this.modalElement) == null ? void 0 : _a.querySelector(
3129
+ "#neoface-forgot-email"
3130
+ );
3131
+ const submitBtn = (_b = this.modalElement) == null ? void 0 : _b.querySelector(
3132
+ ".neoface-forgot-btn-primary"
3133
+ );
3134
+ const errorEl = (_c = this.modalElement) == null ? void 0 : _c.querySelector("#neoface-forgot-error");
3135
+ const email = (_d = emailInput == null ? void 0 : emailInput.value) == null ? void 0 : _d.trim();
3136
+ if (!email) return;
3137
+ submitBtn.disabled = true;
3138
+ submitBtn.textContent = "Enviando...";
3139
+ errorEl.style.display = "none";
3140
+ try {
3141
+ await requestPasswordReset(email, this.applicationToken);
3142
+ this.renderSuccess();
3143
+ } catch (error) {
3144
+ submitBtn.disabled = false;
3145
+ submitBtn.textContent = "Enviar link";
3146
+ errorEl.style.display = "block";
3147
+ errorEl.textContent = error instanceof NeoFaceError ? error.message : "Erro ao enviar email. Tente novamente.";
3148
+ }
3149
+ }
3150
+ addStyles() {
3151
+ const styleId = "neoface-forgot-password-modal-styles";
3152
+ if (document.getElementById(styleId)) return;
3153
+ const style = document.createElement("style");
3154
+ style.id = styleId;
3155
+ style.textContent = `
3156
+ .neoface-forgot-modal {
3157
+ position: fixed;
3158
+ top: 0;
3159
+ left: 0;
3160
+ width: 100%;
3161
+ height: 100%;
3162
+ z-index: 10003;
3163
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
3164
+ }
3165
+
3166
+ .neoface-forgot-overlay {
3167
+ position: absolute;
3168
+ inset: 0;
3169
+ background: rgba(0, 0, 0, 0.65);
3170
+ backdrop-filter: blur(8px);
3171
+ display: flex;
3172
+ align-items: center;
3173
+ justify-content: center;
3174
+ padding: 20px;
3175
+ animation: neoface-forgot-fadeIn 0.3s ease-out;
3176
+ }
3177
+
3178
+ @keyframes neoface-forgot-fadeIn {
3179
+ from { opacity: 0; }
3180
+ to { opacity: 1; }
3181
+ }
3182
+
3183
+ @keyframes neoface-forgot-slideUp {
3184
+ from { opacity: 0; transform: translateY(20px); }
3185
+ to { opacity: 1; transform: translateY(0); }
3186
+ }
3187
+
3188
+ .neoface-forgot-content {
3189
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
3190
+ border-radius: 20px;
3191
+ padding: 32px;
3192
+ max-width: 400px;
3193
+ width: 100%;
3194
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
3195
+ animation: neoface-forgot-slideUp 0.4s ease-out;
3196
+ text-align: center;
3197
+ }
3198
+
3199
+ .neoface-forgot-icon {
3200
+ width: 64px;
3201
+ height: 64px;
3202
+ margin: 0 auto 20px;
3203
+ }
3204
+
3205
+ .neoface-forgot-success-icon {
3206
+ width: 64px;
3207
+ height: 64px;
3208
+ margin: 0 auto 20px;
3209
+ animation: neoface-forgot-scaleIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
3210
+ }
3211
+
3212
+ @keyframes neoface-forgot-scaleIn {
3213
+ 0% { transform: scale(0); opacity: 0; }
3214
+ 50% { transform: scale(1.1); }
3215
+ 100% { transform: scale(1); opacity: 1; }
3216
+ }
3217
+
3218
+ .neoface-forgot-title {
3219
+ font-size: 22px;
3220
+ font-weight: 700;
3221
+ color: white;
3222
+ margin: 0 0 10px 0;
3223
+ }
3224
+
3225
+ .neoface-forgot-subtitle {
3226
+ font-size: 15px;
3227
+ color: rgba(255, 255, 255, 0.7);
3228
+ margin: 0 0 24px 0;
3229
+ line-height: 1.5;
3230
+ }
3231
+
3232
+ .neoface-forgot-error {
3233
+ background: rgba(239, 68, 68, 0.15);
3234
+ border: 1px solid rgba(239, 68, 68, 0.4);
3235
+ color: #fca5a5;
3236
+ border-radius: 10px;
3237
+ padding: 10px 14px;
3238
+ font-size: 14px;
3239
+ margin-bottom: 16px;
3240
+ text-align: left;
3241
+ }
3242
+
3243
+ #neoface-forgot-form {
3244
+ display: flex;
3245
+ flex-direction: column;
3246
+ gap: 14px;
3247
+ margin-bottom: 12px;
3248
+ }
3249
+
3250
+ .neoface-forgot-input {
3251
+ width: 100%;
3252
+ padding: 14px 16px;
3253
+ border: 2px solid rgba(255, 255, 255, 0.2);
3254
+ border-radius: 12px;
3255
+ font-size: 15px;
3256
+ background: rgba(255, 255, 255, 0.08);
3257
+ color: white;
3258
+ font-family: inherit;
3259
+ transition: all 0.2s;
3260
+ box-sizing: border-box;
3261
+ }
3262
+
3263
+ .neoface-forgot-input::placeholder {
3264
+ color: rgba(255, 255, 255, 0.4);
3265
+ }
3266
+
3267
+ .neoface-forgot-input:focus {
3268
+ outline: none;
3269
+ border-color: #7c3aed;
3270
+ box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.2);
3271
+ background: rgba(255, 255, 255, 0.12);
3272
+ }
3273
+
3274
+ .neoface-forgot-btn-primary {
3275
+ width: 100%;
3276
+ padding: 14px 24px;
3277
+ border-radius: 12px;
3278
+ border: none;
3279
+ background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
3280
+ color: white;
3281
+ font-size: 16px;
3282
+ font-weight: 600;
3283
+ cursor: pointer;
3284
+ transition: all 0.2s;
3285
+ box-shadow: 0 4px 16px rgba(124, 58, 237, 0.3);
3286
+ font-family: inherit;
3287
+ }
3288
+
3289
+ .neoface-forgot-btn-primary:hover:not(:disabled) {
3290
+ transform: translateY(-2px);
3291
+ box-shadow: 0 8px 24px rgba(124, 58, 237, 0.4);
3292
+ }
3293
+
3294
+ .neoface-forgot-btn-primary:disabled {
3295
+ opacity: 0.6;
3296
+ cursor: not-allowed;
3297
+ }
3298
+
3299
+ .neoface-forgot-btn-ghost {
3300
+ background: transparent;
3301
+ border: none;
3302
+ color: rgba(255, 255, 255, 0.5);
3303
+ font-size: 14px;
3304
+ font-weight: 500;
3305
+ cursor: pointer;
3306
+ padding: 10px 16px;
3307
+ border-radius: 8px;
3308
+ transition: all 0.2s;
3309
+ font-family: inherit;
3310
+ width: 100%;
3311
+ }
3312
+
3313
+ .neoface-forgot-btn-ghost:hover {
3314
+ color: white;
3315
+ background: rgba(255, 255, 255, 0.05);
3316
+ }
3317
+
3318
+ .neoface-forgot-branding {
3319
+ display: flex;
3320
+ align-items: center;
3321
+ justify-content: center;
3322
+ gap: 6px;
3323
+ margin-top: 20px;
3324
+ color: rgba(255, 255, 255, 0.3);
3325
+ font-size: 12px;
3326
+ }
3327
+
3328
+ .neoface-forgot-brand-logo {
3329
+ width: 18px;
3330
+ height: 18px;
3331
+ object-fit: contain;
3332
+ opacity: 0.5;
3333
+ }
3334
+
3335
+ .neoface-forgot-brand-name {
3336
+ font-weight: 700;
3337
+ letter-spacing: 0.05em;
3338
+ }
3339
+
3340
+ @media (max-width: 640px) {
3341
+ .neoface-forgot-content {
3342
+ padding: 24px;
3343
+ border-radius: 16px;
3344
+ }
3345
+
3346
+ .neoface-forgot-title {
3347
+ font-size: 20px;
3348
+ }
3349
+ }
3350
+ `;
3351
+ document.head.appendChild(style);
3352
+ }
3353
+ }
2964
3354
  const modalReducer = (state, action) => {
2965
3355
  switch (action.type) {
2966
3356
  case "START_LIVENESS":
@@ -2995,10 +3385,10 @@ const modalReducer = (state, action) => {
2995
3385
  return state;
2996
3386
  case "CAMERA_LOADING":
2997
3387
  return { status: "camera-loading" };
2998
- case "START_PROCESSING":
2999
- return { status: "processing" };
3000
3388
  case "SUCCESS":
3001
3389
  return { status: "success" };
3390
+ case "DUPLICATE":
3391
+ return { status: "duplicate", personName: action.personName };
3002
3392
  case "ERROR":
3003
3393
  return { status: "error", message: action.message, retryable: action.retryable ?? true };
3004
3394
  case "RESET":
@@ -3468,7 +3858,24 @@ function BiometricRegistrationModal({
3468
3858
  );
3469
3859
  };
3470
3860
  const processRegistration = async (photos) => {
3861
+ var _a;
3471
3862
  try {
3863
+ dispatch({ type: "START_PROCESSING" });
3864
+ if (applicationToken && photos.length > 0) {
3865
+ try {
3866
+ const { identifyPersonAsync: identifyPersonAsync2 } = await Promise.resolve().then(() => api);
3867
+ const result2 = await identifyPersonAsync2(photos[0], applicationToken);
3868
+ if (result2.personFound) {
3869
+ dispatch({
3870
+ type: "DUPLICATE",
3871
+ personName: ((_a = result2.person) == null ? void 0 : _a.name) || "Pessoa identificada"
3872
+ });
3873
+ return;
3874
+ }
3875
+ } catch (idError) {
3876
+ console.warn("Erro silencioso na identificação preventiva:", idError);
3877
+ }
3878
+ }
3472
3879
  if (onPhotosCaptured) {
3473
3880
  dispatch({ type: "SUCCESS" });
3474
3881
  onPhotosCaptured(photos);
@@ -3624,6 +4031,75 @@ function BiometricRegistrationModal({
3624
4031
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: onPhotosCaptured ? "Suas capturas faciais foram realizadas" : "Sua biometria foi registrada com sucesso" }),
3625
4032
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary-button", onClick: onClose, children: "Continuar" })
3626
4033
  ] });
4034
+ const renderDuplicate = () => {
4035
+ if (state.status !== "duplicate") return null;
4036
+ const greeting = state.personName && state.personName !== "Pessoa identificada" ? `Olá, ${state.personName.split(" ")[0]}!` : "Olá!";
4037
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "result-container duplicate scale-in", children: [
4038
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { style: { marginTop: "0px" }, children: greeting }),
4039
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { marginBottom: "32px", fontSize: "15px" }, children: "Identificamos seu cadastro biométrico em nossa rede. Você já faz parte da NeoFaceId!" }),
4040
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "button-group-vertical", children: [
4041
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4042
+ "button",
4043
+ {
4044
+ className: "primary-button",
4045
+ onClick: () => {
4046
+ onClose();
4047
+ window.location.href = "/";
4048
+ },
4049
+ children: "Ir para o Login"
4050
+ }
4051
+ ),
4052
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4053
+ "button",
4054
+ {
4055
+ className: "secondary-button",
4056
+ style: { marginTop: "12px" },
4057
+ onClick: () => {
4058
+ onClose();
4059
+ new ForgotPasswordModal(applicationToken ?? "").open();
4060
+ },
4061
+ children: "Esqueci minha senha"
4062
+ }
4063
+ ),
4064
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "support-section", children: [
4065
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Não reconhece esta conta?" }),
4066
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4067
+ "button",
4068
+ {
4069
+ className: "ghost-button",
4070
+ onClick: () => window.open("https://support.neofaceid.com", "_blank"),
4071
+ children: "Falar com Suporte"
4072
+ }
4073
+ )
4074
+ ] })
4075
+ ] }),
4076
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
4077
+ "div",
4078
+ {
4079
+ className: "branding",
4080
+ style: {
4081
+ marginTop: "32px",
4082
+ paddingTop: "20px",
4083
+ borderTop: "1px solid rgba(255, 255, 255, 0.08)",
4084
+ width: "100%"
4085
+ },
4086
+ children: [
4087
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4088
+ "img",
4089
+ {
4090
+ src: "/logo-icone.png",
4091
+ alt: "NeoFaceId",
4092
+ className: "brand-logo",
4093
+ style: { height: "18px" }
4094
+ }
4095
+ ),
4096
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "NeoFaceId by" }),
4097
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "brand-name", children: "OCTA" })
4098
+ ]
4099
+ }
4100
+ )
4101
+ ] });
4102
+ };
3627
4103
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3628
4104
  /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
3629
4105
  @keyframes fadeIn {
@@ -4153,6 +4629,61 @@ function BiometricRegistrationModal({
4153
4629
  background: rgba(255, 255, 255, 0.05);
4154
4630
  }
4155
4631
 
4632
+ /* Duplicate Screen Styles */
4633
+ .profile-icon-wrapper {
4634
+ position: relative;
4635
+ display: inline-block;
4636
+ margin-bottom: 24px;
4637
+ }
4638
+
4639
+ .profile-main-icon {
4640
+ background: rgba(124, 58, 237, 0.1);
4641
+ color: #7c3aed;
4642
+ padding: 20px;
4643
+ border-radius: 50%;
4644
+ border: 2px solid rgba(124, 58, 237, 0.2);
4645
+ box-shadow: 0 0 30px rgba(124, 58, 237, 0.2);
4646
+ }
4647
+
4648
+ .profile-main-icon svg {
4649
+ width: 48px;
4650
+ height: 48px;
4651
+ }
4652
+
4653
+ .verified-badge {
4654
+ position: absolute;
4655
+ bottom: -4px;
4656
+ right: -4px;
4657
+ background: #10b981;
4658
+ border-radius: 50%;
4659
+ padding: 4px;
4660
+ border: 3px solid #16213e;
4661
+ width: 24px;
4662
+ height: 24px;
4663
+ display: flex;
4664
+ align-items: center;
4665
+ justify-content: center;
4666
+ }
4667
+
4668
+ .button-group-vertical {
4669
+ display: flex;
4670
+ flex-direction: column;
4671
+ width: 100%;
4672
+ gap: 8px;
4673
+ }
4674
+
4675
+ .support-section {
4676
+ margin-top: 24px;
4677
+ padding-top: 16px;
4678
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
4679
+ }
4680
+
4681
+ .support-section p {
4682
+ font-size: 13px;
4683
+ color: rgba(255, 255, 255, 0.4);
4684
+ margin-bottom: 8px;
4685
+ }
4686
+
4156
4687
  /* Canvas */
4157
4688
  canvas {
4158
4689
  display: none;
@@ -4190,7 +4721,8 @@ function BiometricRegistrationModal({
4190
4721
  state.status === "camera-loading" && renderCameraLoading(),
4191
4722
  state.status === "processing" && renderProcessing(),
4192
4723
  state.status === "error" && renderError(),
4193
- state.status === "success" && renderSuccess()
4724
+ state.status === "success" && renderSuccess(),
4725
+ state.status === "duplicate" && renderDuplicate()
4194
4726
  ] }) }),
4195
4727
  /* @__PURE__ */ jsxRuntimeExports.jsx("canvas", { ref: canvasRef })
4196
4728
  ] });
@@ -4778,6 +5310,7 @@ class BiometricStatusOverlay {
4778
5310
  /* Cores por estado */
4779
5311
  .neofaceid-state-preparing .neofaceid-logo { filter: drop-shadow(0 0 15px rgba(124, 58, 237, 0.4)); }
4780
5312
  .neofaceid-state-detecting .neofaceid-logo { filter: hue-rotate(180deg) drop-shadow(0 0 15px rgba(59, 130, 246, 0.6)); }
5313
+ .neofaceid-state-waiting-for-face .neofaceid-logo { filter: hue-rotate(30deg) drop-shadow(0 0 15px rgba(251, 146, 60, 0.6)); }
4781
5314
  .neofaceid-state-capturing .neofaceid-logo { filter: hue-rotate(45deg) drop-shadow(0 0 15px rgba(245, 158, 11, 0.6)); }
4782
5315
  .neofaceid-state-verifying .neofaceid-logo { filter: hue-rotate(90deg) drop-shadow(0 0 15px rgba(34, 197, 94, 0.6)); }
4783
5316
 
@@ -4851,6 +5384,8 @@ class BiometricStatusOverlay {
4851
5384
  return "Iniciando câmera";
4852
5385
  case "detecting":
4853
5386
  return "Posicione seu rosto";
5387
+ case "waiting-for-face":
5388
+ return "Aguardando rosto";
4854
5389
  case "capturing":
4855
5390
  return "Capturando";
4856
5391
  case "verifying":
@@ -4864,12 +5399,18 @@ class BiometricStatusOverlay {
4864
5399
  }
4865
5400
  }
4866
5401
  renderContent() {
4867
- const isPulsing = ["detecting", "verifying"].includes(this.currentStatus);
5402
+ const isPulsing = ["detecting", "waiting-for-face", "verifying"].includes(this.currentStatus);
4868
5403
  const showLogo = !["success", "error"].includes(this.currentStatus);
4869
5404
  const showSuccess = this.currentStatus === "success";
4870
5405
  const showError = this.currentStatus === "error";
4871
5406
  const statusText = this.getStatusText();
4872
- const showDots = ["preparing", "detecting", "capturing", "verifying"].includes(this.currentStatus);
5407
+ const showDots = [
5408
+ "preparing",
5409
+ "detecting",
5410
+ "waiting-for-face",
5411
+ "capturing",
5412
+ "verifying"
5413
+ ].includes(this.currentStatus);
4873
5414
  return `
4874
5415
  <style>${this.getStyles()}</style>
4875
5416
  <div class="neofaceid-overlay-container neofaceid-state-${this.currentStatus}">
@@ -4954,8 +5495,13 @@ function FallbackPromptComponent({
4954
5495
  error,
4955
5496
  onRetry,
4956
5497
  onCredentials,
4957
- onCancel
5498
+ onCancel,
5499
+ applicationToken
4958
5500
  }) {
5501
+ const handleForgotPassword = () => {
5502
+ const modal = new ForgotPasswordModal(applicationToken);
5503
+ modal.open();
5504
+ };
4959
5505
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
4960
5506
  /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
4961
5507
  @keyframes slideUp {
@@ -5133,6 +5679,15 @@ function FallbackPromptComponent({
5133
5679
  children: "Entrar com Email e Senha"
5134
5680
  }
5135
5681
  ),
5682
+ applicationToken && /* @__PURE__ */ jsxRuntimeExports.jsx(
5683
+ "button",
5684
+ {
5685
+ type: "button",
5686
+ className: "neofaceid-fallback-button neofaceid-fallback-button-ghost",
5687
+ onClick: handleForgotPassword,
5688
+ children: "Esqueceu sua senha?"
5689
+ }
5690
+ ),
5136
5691
  onCancel && /* @__PURE__ */ jsxRuntimeExports.jsx(
5137
5692
  "button",
5138
5693
  {
@@ -5154,7 +5709,7 @@ class FallbackPrompt {
5154
5709
  /**
5155
5710
  * Exibe o prompt de fallback
5156
5711
  */
5157
- show(error, onRetry, onCredentials, onCancel) {
5712
+ show(error, onRetry, onCredentials, onCancel, applicationToken) {
5158
5713
  if (this.container) {
5159
5714
  return;
5160
5715
  }
@@ -5162,7 +5717,7 @@ class FallbackPrompt {
5162
5717
  this.container.id = "neofaceid-fallback-prompt";
5163
5718
  document.body.appendChild(this.container);
5164
5719
  this.root = clientExports.createRoot(this.container);
5165
- this.render(error, onRetry, onCredentials, onCancel);
5720
+ this.render(error, onRetry, onCredentials, onCancel, applicationToken);
5166
5721
  }
5167
5722
  /**
5168
5723
  * Fecha o prompt
@@ -5180,7 +5735,7 @@ class FallbackPrompt {
5180
5735
  /**
5181
5736
  * Renderiza o componente
5182
5737
  */
5183
- render(error, onRetry, onCredentials, onCancel) {
5738
+ render(error, onRetry, onCredentials, onCancel, applicationToken) {
5184
5739
  if (!this.root || !this.container) return;
5185
5740
  this.root.render(
5186
5741
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -5195,6 +5750,7 @@ class FallbackPrompt {
5195
5750
  this.close();
5196
5751
  onCredentials();
5197
5752
  },
5753
+ applicationToken: applicationToken ?? "",
5198
5754
  onCancel: () => {
5199
5755
  this.close();
5200
5756
  onCancel == null ? void 0 : onCancel();
@@ -5669,12 +6225,14 @@ async function captureFaceSilently() {
5669
6225
  });
5670
6226
  });
5671
6227
  }
5672
- async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL) {
6228
+ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL, overlay) {
5673
6229
  return new Promise((resolve) => {
5674
6230
  let attempts = 0;
5675
6231
  const maxAttempts = Math.ceil(maxTime / interval);
5676
6232
  let timeoutId = null;
5677
6233
  let isResolved = false;
6234
+ let overlayShowingWaiting = false;
6235
+ const ATTEMPTS_BEFORE_WAITING = Math.ceil(3e3 / interval);
5678
6236
  const finish = (success) => {
5679
6237
  if (isResolved) return;
5680
6238
  isResolved = true;
@@ -5685,7 +6243,16 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
5685
6243
  if (isResolved) return;
5686
6244
  attempts++;
5687
6245
  if (!modelsLoaded) {
5688
- finish(true);
6246
+ if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
6247
+ overlay.updateStatus("waiting-for-face");
6248
+ overlayShowingWaiting = true;
6249
+ console.log("⏳ Models not loaded - waiting for face...");
6250
+ }
6251
+ if (attempts >= maxAttempts) {
6252
+ finish(false);
6253
+ return;
6254
+ }
6255
+ timeoutId = window.setTimeout(checkFace, interval);
5689
6256
  return;
5690
6257
  }
5691
6258
  try {
@@ -5694,21 +6261,43 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
5694
6261
  new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.3 })
5695
6262
  );
5696
6263
  if (detection) {
6264
+ if (overlay && overlayShowingWaiting) {
6265
+ overlay.updateStatus("detecting");
6266
+ overlayShowingWaiting = false;
6267
+ console.log("👤 Face appeared");
6268
+ }
6269
+ console.log("✅ Face detected!");
5697
6270
  finish(true);
5698
6271
  return;
5699
6272
  }
6273
+ if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
6274
+ overlay.updateStatus("waiting-for-face");
6275
+ overlayShowingWaiting = true;
6276
+ console.log("⏳ Waiting for face...");
6277
+ }
5700
6278
  if (attempts >= maxAttempts) {
6279
+ console.warn("⏱️ Face detection timeout - no face found");
5701
6280
  finish(false);
5702
6281
  return;
5703
6282
  }
5704
6283
  timeoutId = window.setTimeout(checkFace, interval);
5705
6284
  } catch (error) {
5706
- finish(true);
6285
+ console.error("❌ Face detection error:", error);
6286
+ if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
6287
+ overlay.updateStatus("waiting-for-face");
6288
+ overlayShowingWaiting = true;
6289
+ }
6290
+ if (attempts >= maxAttempts) {
6291
+ finish(false);
6292
+ return;
6293
+ }
6294
+ timeoutId = window.setTimeout(checkFace, interval);
5707
6295
  }
5708
6296
  };
5709
6297
  window.setTimeout(() => {
5710
6298
  if (!isResolved) {
5711
- finish(true);
6299
+ console.warn("⏱️ Face detection safety timeout reached");
6300
+ finish(false);
5712
6301
  }
5713
6302
  }, maxTime + 500);
5714
6303
  checkFace();
@@ -5782,7 +6371,12 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
5782
6371
  await new Promise((resolve) => setTimeout(resolve, CAMERA_STABILITY_DELAY));
5783
6372
  let faceDetected = true;
5784
6373
  if (!fastMode) {
5785
- faceDetected = await detectFaceQuickly(video);
6374
+ faceDetected = await detectFaceQuickly(
6375
+ video,
6376
+ FACE_DETECTION_TIME,
6377
+ DETECTION_INTERVAL,
6378
+ overlay
6379
+ );
5786
6380
  }
5787
6381
  if (!faceDetected) {
5788
6382
  if (stream) {
@@ -5792,7 +6386,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
5792
6386
  video.parentElement.removeChild(video);
5793
6387
  }
5794
6388
  throw new NeoFaceError(
5795
- "Rosto não detectado. Posicione seu rosto na frente da câmera.",
6389
+ "Nenhum rosto detectado. Por favor, posicione seu rosto na frente da câmera e tente novamente.",
5796
6390
  ErrorType.VALIDATION_ERROR
5797
6391
  );
5798
6392
  }
@@ -5832,7 +6426,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
5832
6426
  );
5833
6427
  });
5834
6428
  overlay.updateStatus("verifying");
5835
- const result = await loginWithBiometricSSE(imageBlob, applicationToken);
6429
+ const result = await loginWithBiometric(imageBlob, applicationToken);
5836
6430
  if (!result.success) {
5837
6431
  throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
5838
6432
  }
@@ -5912,7 +6506,8 @@ async function executeBiometricLoginFlow(options) {
5912
6506
  );
5913
6507
  }
5914
6508
  },
5915
- onCancel
6509
+ onCancel,
6510
+ applicationToken
5916
6511
  );
5917
6512
  }, 500);
5918
6513
  }
@@ -5930,11 +6525,6 @@ const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
5930
6525
  }, Symbol.toStringTag, { value: "Module" }));
5931
6526
  async function startFaceLogin(options) {
5932
6527
  try {
5933
- const isValidToken = await validateToken(options.applicationToken);
5934
- if (!isValidToken) {
5935
- options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
5936
- return;
5937
- }
5938
6528
  await executeBiometricLoginFlow({
5939
6529
  applicationToken: options.applicationToken,
5940
6530
  onSuccess: options.onSuccess,
@@ -5963,11 +6553,6 @@ async function startFaceLogin(options) {
5963
6553
  }
5964
6554
  async function startHandLogin(options) {
5965
6555
  try {
5966
- const isValidToken = await validateToken(options.applicationToken);
5967
- if (!isValidToken) {
5968
- options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
5969
- return;
5970
- }
5971
6556
  const captureOptions = {
5972
6557
  mode: "hand",
5973
6558
  onSuccess: async (imageData, _detectedType) => {
@@ -5998,11 +6583,6 @@ async function startHandLogin(options) {
5998
6583
  }
5999
6584
  async function startAutoLogin(options) {
6000
6585
  try {
6001
- const isValidToken = await validateToken(options.applicationToken);
6002
- if (!isValidToken) {
6003
- options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
6004
- return;
6005
- }
6006
6586
  const captureOptions = {
6007
6587
  mode: "auto",
6008
6588
  onSuccess: async (imageData, _detectedType) => {
@@ -6255,8 +6835,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
6255
6835
  initializeBiometricDetection,
6256
6836
  isAdvancedDetectionAvailable
6257
6837
  }, Symbol.toStringTag, { value: "Module" }));
6258
- const VERSION = "1.15.0";
6259
- const RELEASE_DATE = "2026-03-04";
6838
+ const VERSION = "1.17.0";
6839
+ const RELEASE_DATE = "2026-03-13";
6260
6840
  class OnboardingCaptureModal {
6261
6841
  constructor(options) {
6262
6842
  __publicField(this, "overlay", null);
@@ -6941,11 +7521,11 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
6941
7521
  };
6942
7522
  const DOCUMENT_INFO = {
6943
7523
  RG: {
6944
- name: "RG / Carteira de Identidade",
7524
+ name: "Documento de Identidade (RG / CIN / CPF)",
6945
7525
  icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M15 8h2M15 12h2M7 16h10"/></svg>',
6946
7526
  hasBack: true,
6947
- frontLabel: "Frente do RG",
6948
- backLabel: "Verso do RG"
7527
+ frontLabel: "Frente do Documento",
7528
+ backLabel: "Verso do Documento"
6949
7529
  },
6950
7530
  CNH: {
6951
7531
  name: "CNH / Carteira de Motorista",
@@ -6955,7 +7535,7 @@ const DOCUMENT_INFO = {
6955
7535
  backLabel: "Verso da CNH"
6956
7536
  },
6957
7537
  CPF: {
6958
- name: "CPF",
7538
+ name: "Cartão CPF (Apenas Frente)",
6959
7539
  icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/></svg>',
6960
7540
  hasBack: false,
6961
7541
  frontLabel: "Cartão CPF"
@@ -7049,7 +7629,7 @@ class DocumentCaptureModal {
7049
7629
 
7050
7630
  <div class="neoface-doc-body">
7051
7631
  <div class="neoface-doc-select-grid">
7052
- ${Object.entries(DOCUMENT_INFO).map(([type, info]) => `
7632
+ ${Object.entries(DOCUMENT_INFO).filter(([type]) => type !== "CPF").map(([type, info]) => `
7053
7633
  <button class="neoface-doc-select-option" data-type="${type}">
7054
7634
  <span class="neoface-doc-select-icon">${info.icon}</span>
7055
7635
  <span class="neoface-doc-select-name">${info.name}</span>
@@ -8055,6 +8635,7 @@ export {
8055
8635
  ErrorType,
8056
8636
  FaceCaptureModal,
8057
8637
  FallbackPrompt,
8638
+ ForgotPasswordModal,
8058
8639
  NeoFaceError,
8059
8640
  NeoFaceID,
8060
8641
  RELEASE_DATE,
@@ -8062,14 +8643,17 @@ export {
8062
8643
  authorizeOperation,
8063
8644
  biometricLogin,
8064
8645
  biometricLoginWithFallback,
8646
+ checkUserExistence,
8065
8647
  completeOnboarding,
8066
8648
  completeOnboardingWithData,
8649
+ confirmPasswordReset,
8067
8650
  detectBiometricType,
8068
8651
  getApplicationToken,
8069
8652
  getBaseUrl,
8070
8653
  getConfig,
8071
8654
  getEnvironment,
8072
8655
  identifyPerson,
8656
+ identifyPersonAsync,
8073
8657
  init,
8074
8658
  initializeBiometricDetection,
8075
8659
  isAdvancedDetectionAvailable,
@@ -8083,6 +8667,7 @@ export {
8083
8667
  registerBiometric,
8084
8668
  registerPersonWithBiometric,
8085
8669
  registerPersonWithoutFace,
8670
+ requestPasswordReset,
8086
8671
  simpleIdentification,
8087
8672
  start,
8088
8673
  startAutoLogin,