@neofaceid/web-sdk 1.15.2 → 1.19.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,
@@ -1838,7 +1839,8 @@ const registerPersonWithoutFace = async (personData, applicationToken, options)
1838
1839
  cpf: personData.cpf,
1839
1840
  email: personData.email,
1840
1841
  password: personData.password,
1841
- password_confirm: personData.password
1842
+ password_confirm: personData.password,
1843
+ is_pep: personData.is_pep ?? false
1842
1844
  };
1843
1845
  if (documentFrontBase64) {
1844
1846
  body.document_images = [
@@ -1879,6 +1881,43 @@ const registerPersonWithoutFace = async (personData, applicationToken, options)
1879
1881
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
1880
1882
  }
1881
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
+ };
1882
1921
  const registerPersonWithBiometric = async (personData, facePhotos, applicationToken, options) => {
1883
1922
  var _a, _b, _c, _d, _e, _f, _g, _h;
1884
1923
  ensureSecureContext();
@@ -1934,7 +1973,8 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
1934
1973
  email: personData.email,
1935
1974
  password: personData.password,
1936
1975
  password_confirm: personData.password,
1937
- face_photos: facePhotosBase64
1976
+ face_photos: facePhotosBase64,
1977
+ is_pep: personData.is_pep ?? false
1938
1978
  };
1939
1979
  if (documentFrontBase64) {
1940
1980
  body.document_images = [
@@ -2028,7 +2068,6 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
2028
2068
  }
2029
2069
  };
2030
2070
  const loginWithEmail = async (email, password, applicationToken) => {
2031
- ensureSecureContext();
2032
2071
  const controller = createTimeoutController();
2033
2072
  try {
2034
2073
  const response = await fetch(`${getApiBaseUrl()}/api/v1/auth/login/`, {
@@ -2074,10 +2113,10 @@ const loginWithEmail = async (email, password, applicationToken) => {
2074
2113
  }
2075
2114
  };
2076
2115
  const identifyPerson = async (image, applicationToken) => {
2077
- var _a, _b;
2116
+ var _a, _b, _c, _d;
2078
2117
  ensureSecureContext();
2079
2118
  const controller = createTimeoutController();
2080
- const compressedImage = await compressImage(image);
2119
+ const compressedImage = await compressDocumentImage(image);
2081
2120
  const base64Image = await new Promise((resolve, reject) => {
2082
2121
  const reader = new FileReader();
2083
2122
  reader.onload = () => {
@@ -2088,7 +2127,7 @@ const identifyPerson = async (image, applicationToken) => {
2088
2127
  reader.readAsDataURL(compressedImage);
2089
2128
  });
2090
2129
  try {
2091
- const response = await fetch(`${getApiBaseUrl()}/api/v1/external/recognition/face/`, {
2130
+ const response = await fetch(`${getApiBaseUrl()}/api/v1/auth/recognition/face/external/`, {
2092
2131
  method: "POST",
2093
2132
  headers: {
2094
2133
  "Content-Type": "application/json",
@@ -2107,30 +2146,26 @@ const identifyPerson = async (image, applicationToken) => {
2107
2146
  throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
2108
2147
  }
2109
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
+ }
2110
2157
  if (!data.success) {
2111
2158
  throw new NeoFaceError(
2112
2159
  data.message || "Person identification failed",
2113
2160
  ErrorType.RECOGNITION_FAILED
2114
2161
  );
2115
2162
  }
2116
- const personName = ((_a = data.person) == null ? void 0 : _a.name) || "";
2117
- const birthDateStr = (_b = data.person) == null ? void 0 : _b.birth_date;
2118
- if (!personName) {
2119
- throw new NeoFaceError("Person name not found in response", ErrorType.PERSON_NOT_FOUND);
2120
- }
2121
- if (!birthDateStr) {
2122
- throw new NeoFaceError("Birth date not found in response", ErrorType.PERSON_NOT_FOUND);
2123
- }
2124
- const birthDate = new Date(birthDateStr);
2125
- const today = /* @__PURE__ */ new Date();
2126
- let age = today.getFullYear() - birthDate.getFullYear();
2127
- const monthDiff = today.getMonth() - birthDate.getMonth();
2128
- if (monthDiff < 0 || monthDiff === 0 && today.getDate() < birthDate.getDate()) {
2129
- age--;
2130
- }
2131
2163
  return {
2132
- name: personName,
2133
- 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
+ }
2134
2169
  };
2135
2170
  } catch (error) {
2136
2171
  if (error instanceof Error) {
@@ -2564,6 +2599,135 @@ const registerDocumentByImage = async (personId, jwtToken, images) => {
2564
2599
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
2565
2600
  }
2566
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" }));
2567
2731
  const modalReducer$1 = (state, action) => {
2568
2732
  switch (action.type) {
2569
2733
  case "PERMISSION_GRANTED":
@@ -2857,6 +3021,336 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
2857
3021
  ] })
2858
3022
  ] });
2859
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
+ }
2860
3354
  const modalReducer = (state, action) => {
2861
3355
  switch (action.type) {
2862
3356
  case "START_LIVENESS":
@@ -2891,10 +3385,10 @@ const modalReducer = (state, action) => {
2891
3385
  return state;
2892
3386
  case "CAMERA_LOADING":
2893
3387
  return { status: "camera-loading" };
2894
- case "START_PROCESSING":
2895
- return { status: "processing" };
2896
3388
  case "SUCCESS":
2897
3389
  return { status: "success" };
3390
+ case "DUPLICATE":
3391
+ return { status: "duplicate", personName: action.personName };
2898
3392
  case "ERROR":
2899
3393
  return { status: "error", message: action.message, retryable: action.retryable ?? true };
2900
3394
  case "RESET":
@@ -2934,7 +3428,16 @@ const CALIBRATION = {
2934
3428
  YAW_LEFT_THRESHOLD: 15,
2935
3429
  YAW_RIGHT_THRESHOLD: -15,
2936
3430
  CAPTURE_HOLD_TIME: 1500,
2937
- DETECTION_INTERVAL: 200
3431
+ DETECTION_INTERVAL: 150,
3432
+ // Um pouco mais rápido para melhor resposta
3433
+ MIN_FACE_WIDTH: 160,
3434
+ // Rosto deve ocupar boa parte do oval (era ~120 implícito)
3435
+ MAX_FACE_WIDTH: 280,
3436
+ // Evita rosto "colado" na câmera
3437
+ CENTER_X_THRESHOLD: 0.15,
3438
+ // Rosto deve estar centralizado (desvio max 15%)
3439
+ CENTER_Y_THRESHOLD: 0.2
3440
+ // Rosto deve estar centralizado verticalmente
2938
3441
  };
2939
3442
  function BiometricRegistrationModal({
2940
3443
  personData,
@@ -2956,7 +3459,8 @@ function BiometricRegistrationModal({
2956
3459
  const captureStateRef = useRef(null);
2957
3460
  const [state, dispatch] = useReducer(modalReducer, initialState);
2958
3461
  const [faceDetected, setFaceDetected] = useState(false);
2959
- const [_faceDistance, setFaceDistance] = useState("ok");
3462
+ const [faceDistance, setFaceDistance] = useState("ok");
3463
+ const [cameraReady, setCameraReady] = useState(false);
2960
3464
  useEffect(() => {
2961
3465
  if (state.status === "liveness") {
2962
3466
  const newStep = state.step;
@@ -3018,6 +3522,18 @@ function BiometricRegistrationModal({
3018
3522
  return null;
3019
3523
  }
3020
3524
  };
3525
+ const checkFacePosition = (box, videoWidth, videoHeight) => {
3526
+ if (box.width < CALIBRATION.MIN_FACE_WIDTH) return "too-far";
3527
+ if (box.width > CALIBRATION.MAX_FACE_WIDTH) return "too-close";
3528
+ const faceCenterX = box.x + box.width / 2;
3529
+ const faceCenterY = box.y + box.height / 2;
3530
+ const deviationX = Math.abs(faceCenterX - videoWidth / 2) / videoWidth;
3531
+ const deviationY = Math.abs(faceCenterY - videoHeight / 2) / videoHeight;
3532
+ if (deviationX > CALIBRATION.CENTER_X_THRESHOLD || deviationY > CALIBRATION.CENTER_Y_THRESHOLD) {
3533
+ return "not-centered";
3534
+ }
3535
+ return "ok";
3536
+ };
3021
3537
  const checkPositionMatch = (pitch, yaw, step) => {
3022
3538
  switch (step) {
3023
3539
  case "center":
@@ -3158,7 +3674,11 @@ function BiometricRegistrationModal({
3158
3674
  }
3159
3675
  video.srcObject = stream;
3160
3676
  streamRef.current = stream;
3161
- await video.play();
3677
+ video.onloadedmetadata = () => {
3678
+ video.play().then(() => {
3679
+ if (mounted) setCameraReady(true);
3680
+ });
3681
+ };
3162
3682
  } catch (error) {
3163
3683
  if (!mounted) return;
3164
3684
  if (watchdogTimerRef.current) {
@@ -3228,14 +3748,17 @@ function BiometricRegistrationModal({
3228
3748
  const hasFace = !!detection;
3229
3749
  setFaceDetected(hasFace);
3230
3750
  if (hasFace && detection.detection) {
3231
- const box = detection.detection.box;
3232
- const faceWidth = box.width;
3233
- if (faceWidth > 250) {
3234
- setFaceDistance("too-close");
3235
- } else if (faceWidth < 120) {
3236
- setFaceDistance("too-far");
3237
- } else {
3238
- setFaceDistance("ok");
3751
+ const video2 = videoRef.current;
3752
+ const positionStatus = checkFacePosition(
3753
+ detection.detection.box,
3754
+ video2.videoWidth,
3755
+ video2.videoHeight
3756
+ );
3757
+ setFaceDistance(positionStatus);
3758
+ if (positionStatus !== "ok") {
3759
+ manageHoldAndCapture(false);
3760
+ requestAnimationFrame(detectFace2);
3761
+ return;
3239
3762
  }
3240
3763
  }
3241
3764
  if (hasFace && detection.landmarks) {
@@ -3364,7 +3887,24 @@ function BiometricRegistrationModal({
3364
3887
  );
3365
3888
  };
3366
3889
  const processRegistration = async (photos) => {
3890
+ var _a;
3367
3891
  try {
3892
+ dispatch({ type: "START_PROCESSING" });
3893
+ if (applicationToken && photos.length > 0) {
3894
+ try {
3895
+ const { identifyPersonAsync: identifyPersonAsync2 } = await Promise.resolve().then(() => api);
3896
+ const result2 = await identifyPersonAsync2(photos[0], applicationToken);
3897
+ if (result2.personFound) {
3898
+ dispatch({
3899
+ type: "DUPLICATE",
3900
+ personName: ((_a = result2.person) == null ? void 0 : _a.name) || "Pessoa identificada"
3901
+ });
3902
+ return;
3903
+ }
3904
+ } catch (idError) {
3905
+ console.warn("Erro silencioso na identificação preventiva:", idError);
3906
+ }
3907
+ }
3368
3908
  if (onPhotosCaptured) {
3369
3909
  dispatch({ type: "SUCCESS" });
3370
3910
  onPhotosCaptured(photos);
@@ -3400,12 +3940,26 @@ function BiometricRegistrationModal({
3400
3940
  const stepProgress = state.stepProgress;
3401
3941
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "liveness-container", children: [
3402
3942
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "progress-indicator", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "progress-text", children: progress }) }),
3943
+ faceDetected && faceDistance !== "ok" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `face-badge ${faceDistance === "not-centered" ? "warning" : "error"}`, children: [
3944
+ faceDistance === "too-far" && "Aproxime-se mais",
3945
+ faceDistance === "too-close" && "Rosto muito perto",
3946
+ faceDistance === "not-centered" && "Centralize seu rosto"
3947
+ ] }),
3403
3948
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "video-wrapper", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3404
3949
  "div",
3405
3950
  {
3406
3951
  className: `video-circle ${faceDetected ? "face-detected" : ""} ${stepProgress > 0 ? "capturing" : ""}`,
3407
3952
  children: [
3408
- /* @__PURE__ */ jsxRuntimeExports.jsx("video", { ref: videoRef, className: "video-feed", autoPlay: true, muted: true, playsInline: true }),
3953
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3954
+ "video",
3955
+ {
3956
+ ref: videoRef,
3957
+ className: `video-feed ${cameraReady ? "ready" : ""}`,
3958
+ autoPlay: true,
3959
+ muted: true,
3960
+ playsInline: true
3961
+ }
3962
+ ),
3409
3963
  faceDetected && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "face-badge", children: [
3410
3964
  /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3411
3965
  "path",
@@ -3520,6 +4074,75 @@ function BiometricRegistrationModal({
3520
4074
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: onPhotosCaptured ? "Suas capturas faciais foram realizadas" : "Sua biometria foi registrada com sucesso" }),
3521
4075
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary-button", onClick: onClose, children: "Continuar" })
3522
4076
  ] });
4077
+ const renderDuplicate = () => {
4078
+ if (state.status !== "duplicate") return null;
4079
+ const greeting = state.personName && state.personName !== "Pessoa identificada" ? `Olá, ${state.personName.split(" ")[0]}!` : "Olá!";
4080
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "result-container duplicate scale-in", children: [
4081
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { style: { marginTop: "0px" }, children: greeting }),
4082
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { marginBottom: "32px", fontSize: "15px" }, children: "Identificamos seu cadastro biométrico em nossa rede. Você já faz parte da NeoFaceId!" }),
4083
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "button-group-vertical", children: [
4084
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4085
+ "button",
4086
+ {
4087
+ className: "primary-button",
4088
+ onClick: () => {
4089
+ onClose();
4090
+ window.location.href = "/";
4091
+ },
4092
+ children: "Ir para o Login"
4093
+ }
4094
+ ),
4095
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4096
+ "button",
4097
+ {
4098
+ className: "secondary-button",
4099
+ style: { marginTop: "12px" },
4100
+ onClick: () => {
4101
+ onClose();
4102
+ new ForgotPasswordModal(applicationToken ?? "").open();
4103
+ },
4104
+ children: "Esqueci minha senha"
4105
+ }
4106
+ ),
4107
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "support-section", children: [
4108
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Não reconhece esta conta?" }),
4109
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4110
+ "button",
4111
+ {
4112
+ className: "ghost-button",
4113
+ onClick: () => window.open("https://support.neofaceid.com", "_blank"),
4114
+ children: "Falar com Suporte"
4115
+ }
4116
+ )
4117
+ ] })
4118
+ ] }),
4119
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
4120
+ "div",
4121
+ {
4122
+ className: "branding",
4123
+ style: {
4124
+ marginTop: "32px",
4125
+ paddingTop: "20px",
4126
+ borderTop: "1px solid rgba(255, 255, 255, 0.08)",
4127
+ width: "100%"
4128
+ },
4129
+ children: [
4130
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4131
+ "img",
4132
+ {
4133
+ src: "/logo-icone.png",
4134
+ alt: "NeoFaceId",
4135
+ className: "brand-logo",
4136
+ style: { height: "18px" }
4137
+ }
4138
+ ),
4139
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "NeoFaceId by" }),
4140
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "brand-name", children: "OCTA" })
4141
+ ]
4142
+ }
4143
+ )
4144
+ ] });
4145
+ };
3523
4146
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3524
4147
  /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
3525
4148
  @keyframes fadeIn {
@@ -3807,8 +4430,9 @@ function BiometricRegistrationModal({
3807
4430
  border-radius: 50%;
3808
4431
  overflow: hidden;
3809
4432
  position: relative;
3810
- transition: all 0.3s ease;
4433
+ /* Removida transição 'all' para evitar salto de zoom no início */
3811
4434
  box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.3);
4435
+ background: #000;
3812
4436
  }
3813
4437
 
3814
4438
  .video-circle.face-detected {
@@ -3825,6 +4449,12 @@ function BiometricRegistrationModal({
3825
4449
  height: 100%;
3826
4450
  object-fit: cover;
3827
4451
  transform: scaleX(-1);
4452
+ opacity: 0;
4453
+ transition: opacity 0.4s ease;
4454
+ }
4455
+
4456
+ .video-feed.ready {
4457
+ opacity: 1;
3828
4458
  }
3829
4459
 
3830
4460
  .face-badge {
@@ -4049,6 +4679,61 @@ function BiometricRegistrationModal({
4049
4679
  background: rgba(255, 255, 255, 0.05);
4050
4680
  }
4051
4681
 
4682
+ /* Duplicate Screen Styles */
4683
+ .profile-icon-wrapper {
4684
+ position: relative;
4685
+ display: inline-block;
4686
+ margin-bottom: 24px;
4687
+ }
4688
+
4689
+ .profile-main-icon {
4690
+ background: rgba(124, 58, 237, 0.1);
4691
+ color: #7c3aed;
4692
+ padding: 20px;
4693
+ border-radius: 50%;
4694
+ border: 2px solid rgba(124, 58, 237, 0.2);
4695
+ box-shadow: 0 0 30px rgba(124, 58, 237, 0.2);
4696
+ }
4697
+
4698
+ .profile-main-icon svg {
4699
+ width: 48px;
4700
+ height: 48px;
4701
+ }
4702
+
4703
+ .verified-badge {
4704
+ position: absolute;
4705
+ bottom: -4px;
4706
+ right: -4px;
4707
+ background: #10b981;
4708
+ border-radius: 50%;
4709
+ padding: 4px;
4710
+ border: 3px solid #16213e;
4711
+ width: 24px;
4712
+ height: 24px;
4713
+ display: flex;
4714
+ align-items: center;
4715
+ justify-content: center;
4716
+ }
4717
+
4718
+ .button-group-vertical {
4719
+ display: flex;
4720
+ flex-direction: column;
4721
+ width: 100%;
4722
+ gap: 8px;
4723
+ }
4724
+
4725
+ .support-section {
4726
+ margin-top: 24px;
4727
+ padding-top: 16px;
4728
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
4729
+ }
4730
+
4731
+ .support-section p {
4732
+ font-size: 13px;
4733
+ color: rgba(255, 255, 255, 0.4);
4734
+ margin-bottom: 8px;
4735
+ }
4736
+
4052
4737
  /* Canvas */
4053
4738
  canvas {
4054
4739
  display: none;
@@ -4086,7 +4771,8 @@ function BiometricRegistrationModal({
4086
4771
  state.status === "camera-loading" && renderCameraLoading(),
4087
4772
  state.status === "processing" && renderProcessing(),
4088
4773
  state.status === "error" && renderError(),
4089
- state.status === "success" && renderSuccess()
4774
+ state.status === "success" && renderSuccess(),
4775
+ state.status === "duplicate" && renderDuplicate()
4090
4776
  ] }) }),
4091
4777
  /* @__PURE__ */ jsxRuntimeExports.jsx("canvas", { ref: canvasRef })
4092
4778
  ] });
@@ -4674,6 +5360,7 @@ class BiometricStatusOverlay {
4674
5360
  /* Cores por estado */
4675
5361
  .neofaceid-state-preparing .neofaceid-logo { filter: drop-shadow(0 0 15px rgba(124, 58, 237, 0.4)); }
4676
5362
  .neofaceid-state-detecting .neofaceid-logo { filter: hue-rotate(180deg) drop-shadow(0 0 15px rgba(59, 130, 246, 0.6)); }
5363
+ .neofaceid-state-waiting-for-face .neofaceid-logo { filter: hue-rotate(30deg) drop-shadow(0 0 15px rgba(251, 146, 60, 0.6)); }
4677
5364
  .neofaceid-state-capturing .neofaceid-logo { filter: hue-rotate(45deg) drop-shadow(0 0 15px rgba(245, 158, 11, 0.6)); }
4678
5365
  .neofaceid-state-verifying .neofaceid-logo { filter: hue-rotate(90deg) drop-shadow(0 0 15px rgba(34, 197, 94, 0.6)); }
4679
5366
 
@@ -4747,6 +5434,8 @@ class BiometricStatusOverlay {
4747
5434
  return "Iniciando câmera";
4748
5435
  case "detecting":
4749
5436
  return "Posicione seu rosto";
5437
+ case "waiting-for-face":
5438
+ return "Aguardando rosto";
4750
5439
  case "capturing":
4751
5440
  return "Capturando";
4752
5441
  case "verifying":
@@ -4760,12 +5449,18 @@ class BiometricStatusOverlay {
4760
5449
  }
4761
5450
  }
4762
5451
  renderContent() {
4763
- const isPulsing = ["detecting", "verifying"].includes(this.currentStatus);
5452
+ const isPulsing = ["detecting", "waiting-for-face", "verifying"].includes(this.currentStatus);
4764
5453
  const showLogo = !["success", "error"].includes(this.currentStatus);
4765
5454
  const showSuccess = this.currentStatus === "success";
4766
5455
  const showError = this.currentStatus === "error";
4767
5456
  const statusText = this.getStatusText();
4768
- const showDots = ["preparing", "detecting", "capturing", "verifying"].includes(this.currentStatus);
5457
+ const showDots = [
5458
+ "preparing",
5459
+ "detecting",
5460
+ "waiting-for-face",
5461
+ "capturing",
5462
+ "verifying"
5463
+ ].includes(this.currentStatus);
4769
5464
  return `
4770
5465
  <style>${this.getStyles()}</style>
4771
5466
  <div class="neofaceid-overlay-container neofaceid-state-${this.currentStatus}">
@@ -4850,8 +5545,13 @@ function FallbackPromptComponent({
4850
5545
  error,
4851
5546
  onRetry,
4852
5547
  onCredentials,
4853
- onCancel
5548
+ onCancel,
5549
+ applicationToken
4854
5550
  }) {
5551
+ const handleForgotPassword = () => {
5552
+ const modal = new ForgotPasswordModal(applicationToken);
5553
+ modal.open();
5554
+ };
4855
5555
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
4856
5556
  /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
4857
5557
  @keyframes slideUp {
@@ -5029,6 +5729,15 @@ function FallbackPromptComponent({
5029
5729
  children: "Entrar com Email e Senha"
5030
5730
  }
5031
5731
  ),
5732
+ applicationToken && /* @__PURE__ */ jsxRuntimeExports.jsx(
5733
+ "button",
5734
+ {
5735
+ type: "button",
5736
+ className: "neofaceid-fallback-button neofaceid-fallback-button-ghost",
5737
+ onClick: handleForgotPassword,
5738
+ children: "Esqueceu sua senha?"
5739
+ }
5740
+ ),
5032
5741
  onCancel && /* @__PURE__ */ jsxRuntimeExports.jsx(
5033
5742
  "button",
5034
5743
  {
@@ -5050,7 +5759,7 @@ class FallbackPrompt {
5050
5759
  /**
5051
5760
  * Exibe o prompt de fallback
5052
5761
  */
5053
- show(error, onRetry, onCredentials, onCancel) {
5762
+ show(error, onRetry, onCredentials, onCancel, applicationToken) {
5054
5763
  if (this.container) {
5055
5764
  return;
5056
5765
  }
@@ -5058,7 +5767,7 @@ class FallbackPrompt {
5058
5767
  this.container.id = "neofaceid-fallback-prompt";
5059
5768
  document.body.appendChild(this.container);
5060
5769
  this.root = clientExports.createRoot(this.container);
5061
- this.render(error, onRetry, onCredentials, onCancel);
5770
+ this.render(error, onRetry, onCredentials, onCancel, applicationToken);
5062
5771
  }
5063
5772
  /**
5064
5773
  * Fecha o prompt
@@ -5076,7 +5785,7 @@ class FallbackPrompt {
5076
5785
  /**
5077
5786
  * Renderiza o componente
5078
5787
  */
5079
- render(error, onRetry, onCredentials, onCancel) {
5788
+ render(error, onRetry, onCredentials, onCancel, applicationToken) {
5080
5789
  if (!this.root || !this.container) return;
5081
5790
  this.root.render(
5082
5791
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -5091,6 +5800,7 @@ class FallbackPrompt {
5091
5800
  this.close();
5092
5801
  onCredentials();
5093
5802
  },
5803
+ applicationToken: applicationToken ?? "",
5094
5804
  onCancel: () => {
5095
5805
  this.close();
5096
5806
  onCancel == null ? void 0 : onCancel();
@@ -5565,12 +6275,14 @@ async function captureFaceSilently() {
5565
6275
  });
5566
6276
  });
5567
6277
  }
5568
- async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL) {
6278
+ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL, overlay) {
5569
6279
  return new Promise((resolve) => {
5570
6280
  let attempts = 0;
5571
6281
  const maxAttempts = Math.ceil(maxTime / interval);
5572
6282
  let timeoutId = null;
5573
6283
  let isResolved = false;
6284
+ let overlayShowingWaiting = false;
6285
+ const ATTEMPTS_BEFORE_WAITING = Math.ceil(3e3 / interval);
5574
6286
  const finish = (success) => {
5575
6287
  if (isResolved) return;
5576
6288
  isResolved = true;
@@ -5581,7 +6293,16 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
5581
6293
  if (isResolved) return;
5582
6294
  attempts++;
5583
6295
  if (!modelsLoaded) {
5584
- finish(true);
6296
+ if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
6297
+ overlay.updateStatus("waiting-for-face");
6298
+ overlayShowingWaiting = true;
6299
+ console.log("⏳ Models not loaded - waiting for face...");
6300
+ }
6301
+ if (attempts >= maxAttempts) {
6302
+ finish(false);
6303
+ return;
6304
+ }
6305
+ timeoutId = window.setTimeout(checkFace, interval);
5585
6306
  return;
5586
6307
  }
5587
6308
  try {
@@ -5590,21 +6311,43 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
5590
6311
  new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.3 })
5591
6312
  );
5592
6313
  if (detection) {
6314
+ if (overlay && overlayShowingWaiting) {
6315
+ overlay.updateStatus("detecting");
6316
+ overlayShowingWaiting = false;
6317
+ console.log("👤 Face appeared");
6318
+ }
6319
+ console.log("✅ Face detected!");
5593
6320
  finish(true);
5594
6321
  return;
5595
6322
  }
6323
+ if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
6324
+ overlay.updateStatus("waiting-for-face");
6325
+ overlayShowingWaiting = true;
6326
+ console.log("⏳ Waiting for face...");
6327
+ }
5596
6328
  if (attempts >= maxAttempts) {
6329
+ console.warn("⏱️ Face detection timeout - no face found");
5597
6330
  finish(false);
5598
6331
  return;
5599
6332
  }
5600
6333
  timeoutId = window.setTimeout(checkFace, interval);
5601
6334
  } catch (error) {
5602
- finish(true);
6335
+ console.error("❌ Face detection error:", error);
6336
+ if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
6337
+ overlay.updateStatus("waiting-for-face");
6338
+ overlayShowingWaiting = true;
6339
+ }
6340
+ if (attempts >= maxAttempts) {
6341
+ finish(false);
6342
+ return;
6343
+ }
6344
+ timeoutId = window.setTimeout(checkFace, interval);
5603
6345
  }
5604
6346
  };
5605
6347
  window.setTimeout(() => {
5606
6348
  if (!isResolved) {
5607
- finish(true);
6349
+ console.warn("⏱️ Face detection safety timeout reached");
6350
+ finish(false);
5608
6351
  }
5609
6352
  }, maxTime + 500);
5610
6353
  checkFace();
@@ -5678,7 +6421,12 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
5678
6421
  await new Promise((resolve) => setTimeout(resolve, CAMERA_STABILITY_DELAY));
5679
6422
  let faceDetected = true;
5680
6423
  if (!fastMode) {
5681
- faceDetected = await detectFaceQuickly(video);
6424
+ faceDetected = await detectFaceQuickly(
6425
+ video,
6426
+ FACE_DETECTION_TIME,
6427
+ DETECTION_INTERVAL,
6428
+ overlay
6429
+ );
5682
6430
  }
5683
6431
  if (!faceDetected) {
5684
6432
  if (stream) {
@@ -5688,7 +6436,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
5688
6436
  video.parentElement.removeChild(video);
5689
6437
  }
5690
6438
  throw new NeoFaceError(
5691
- "Rosto não detectado. Posicione seu rosto na frente da câmera.",
6439
+ "Nenhum rosto detectado. Por favor, posicione seu rosto na frente da câmera e tente novamente.",
5692
6440
  ErrorType.VALIDATION_ERROR
5693
6441
  );
5694
6442
  }
@@ -5808,7 +6556,8 @@ async function executeBiometricLoginFlow(options) {
5808
6556
  );
5809
6557
  }
5810
6558
  },
5811
- onCancel
6559
+ onCancel,
6560
+ applicationToken
5812
6561
  );
5813
6562
  }, 500);
5814
6563
  }
@@ -6136,8 +6885,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
6136
6885
  initializeBiometricDetection,
6137
6886
  isAdvancedDetectionAvailable
6138
6887
  }, Symbol.toStringTag, { value: "Module" }));
6139
- const VERSION = "1.15.2";
6140
- const RELEASE_DATE = "2026-03-04";
6888
+ const VERSION = "1.19.0";
6889
+ const RELEASE_DATE = "2026-03-13";
6141
6890
  class OnboardingCaptureModal {
6142
6891
  constructor(options) {
6143
6892
  __publicField(this, "overlay", null);
@@ -6822,11 +7571,11 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
6822
7571
  };
6823
7572
  const DOCUMENT_INFO = {
6824
7573
  RG: {
6825
- name: "RG / Carteira de Identidade",
7574
+ name: "Documento de Identidade (RG / CIN / CPF)",
6826
7575
  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>',
6827
7576
  hasBack: true,
6828
- frontLabel: "Frente do RG",
6829
- backLabel: "Verso do RG"
7577
+ frontLabel: "Frente do Documento",
7578
+ backLabel: "Verso do Documento"
6830
7579
  },
6831
7580
  CNH: {
6832
7581
  name: "CNH / Carteira de Motorista",
@@ -6836,7 +7585,7 @@ const DOCUMENT_INFO = {
6836
7585
  backLabel: "Verso da CNH"
6837
7586
  },
6838
7587
  CPF: {
6839
- name: "CPF",
7588
+ name: "Cartão CPF (Apenas Frente)",
6840
7589
  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>',
6841
7590
  hasBack: false,
6842
7591
  frontLabel: "Cartão CPF"
@@ -6930,7 +7679,7 @@ class DocumentCaptureModal {
6930
7679
 
6931
7680
  <div class="neoface-doc-body">
6932
7681
  <div class="neoface-doc-select-grid">
6933
- ${Object.entries(DOCUMENT_INFO).map(([type, info]) => `
7682
+ ${Object.entries(DOCUMENT_INFO).filter(([type]) => type !== "CPF").map(([type, info]) => `
6934
7683
  <button class="neoface-doc-select-option" data-type="${type}">
6935
7684
  <span class="neoface-doc-select-icon">${info.icon}</span>
6936
7685
  <span class="neoface-doc-select-name">${info.name}</span>
@@ -7936,6 +8685,7 @@ export {
7936
8685
  ErrorType,
7937
8686
  FaceCaptureModal,
7938
8687
  FallbackPrompt,
8688
+ ForgotPasswordModal,
7939
8689
  NeoFaceError,
7940
8690
  NeoFaceID,
7941
8691
  RELEASE_DATE,
@@ -7943,14 +8693,17 @@ export {
7943
8693
  authorizeOperation,
7944
8694
  biometricLogin,
7945
8695
  biometricLoginWithFallback,
8696
+ checkUserExistence,
7946
8697
  completeOnboarding,
7947
8698
  completeOnboardingWithData,
8699
+ confirmPasswordReset,
7948
8700
  detectBiometricType,
7949
8701
  getApplicationToken,
7950
8702
  getBaseUrl,
7951
8703
  getConfig,
7952
8704
  getEnvironment,
7953
8705
  identifyPerson,
8706
+ identifyPersonAsync,
7954
8707
  init,
7955
8708
  initializeBiometricDetection,
7956
8709
  isAdvancedDetectionAvailable,
@@ -7964,6 +8717,7 @@ export {
7964
8717
  registerBiometric,
7965
8718
  registerPersonWithBiometric,
7966
8719
  registerPersonWithoutFace,
8720
+ requestPasswordReset,
7967
8721
  simpleIdentification,
7968
8722
  start,
7969
8723
  startAutoLogin,