@neofaceid/web-sdk 1.2.0 → 1.4.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.
@@ -1239,6 +1239,41 @@ const getOnboardingDetails = async (applicationToken, onboardingToken) => {
1239
1239
  throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1240
1240
  }
1241
1241
  };
1242
+ const validateOnboardingToken = async (applicationToken, onboardingToken) => {
1243
+ ensureSecureContext();
1244
+ const controller = createTimeoutController();
1245
+ try {
1246
+ const response = await fetch(`${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/`, {
1247
+ method: "GET",
1248
+ headers: {
1249
+ "X-App-Token": applicationToken
1250
+ },
1251
+ signal: controller.signal
1252
+ });
1253
+ if (!response.ok) {
1254
+ if (response.status === 401 || response.status === 403) {
1255
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
1256
+ }
1257
+ if (response.status === 404) {
1258
+ return false;
1259
+ }
1260
+ throw new NeoFaceError(`Falha ao validar token (${response.status})`, ErrorType.NETWORK);
1261
+ }
1262
+ const data = await response.json();
1263
+ return (data == null ? void 0 : data.is_valid) === true;
1264
+ } catch (error) {
1265
+ if (error instanceof Error) {
1266
+ if (error.name === "AbortError") {
1267
+ throw new NeoFaceError("Tempo de requisição excedido", ErrorType.NETWORK);
1268
+ }
1269
+ if (error instanceof NeoFaceError) {
1270
+ throw error;
1271
+ }
1272
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1273
+ }
1274
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1275
+ }
1276
+ };
1242
1277
  const completeOnboarding = async (applicationToken, onboardingToken, faceImage, documentImage) => {
1243
1278
  ensureSecureContext();
1244
1279
  const controller = createTimeoutController();
@@ -1290,6 +1325,82 @@ const completeOnboarding = async (applicationToken, onboardingToken, faceImage,
1290
1325
  throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1291
1326
  }
1292
1327
  };
1328
+ const completeOnboardingWithData = async (applicationToken, onboardingToken, personData, personImages, documentImage) => {
1329
+ ensureSecureContext();
1330
+ if (!personImages || personImages.length === 0) {
1331
+ throw new NeoFaceError("Pelo menos uma imagem da pessoa é obrigatória", ErrorType.VALIDATION_ERROR);
1332
+ }
1333
+ const controller = createTimeoutController();
1334
+ try {
1335
+ const compressedPersonImages = await Promise.all(
1336
+ personImages.map((img) => compressImage(img))
1337
+ );
1338
+ const compressedDocument = await compressImage(documentImage);
1339
+ const formData = new FormData();
1340
+ compressedPersonImages.forEach((img, index) => {
1341
+ formData.append("person_images", img, `person_${index + 1}.jpg`);
1342
+ });
1343
+ formData.append("document_image", compressedDocument, "document.jpg");
1344
+ if (personData.name) {
1345
+ formData.append("name", personData.name);
1346
+ }
1347
+ if (personData.birth_date) {
1348
+ formData.append("birth_date", personData.birth_date);
1349
+ }
1350
+ if (personData.cpf) {
1351
+ formData.append("cpf", personData.cpf);
1352
+ }
1353
+ if (personData.cnpj) {
1354
+ formData.append("cnpj", personData.cnpj);
1355
+ }
1356
+ if (personData.email) {
1357
+ formData.append("email", personData.email);
1358
+ }
1359
+ const response = await fetch(
1360
+ `${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/complete`,
1361
+ {
1362
+ method: "POST",
1363
+ headers: {
1364
+ "X-App-Token": applicationToken
1365
+ },
1366
+ body: formData,
1367
+ signal: controller.signal
1368
+ }
1369
+ );
1370
+ const data = await response.json();
1371
+ if (!response.ok) {
1372
+ if (response.status === 401 || response.status === 403) {
1373
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
1374
+ }
1375
+ if (response.status === 404) {
1376
+ throw new NeoFaceError("Link de onboarding não encontrado", ErrorType.NOT_FOUND);
1377
+ }
1378
+ throw new NeoFaceError((data == null ? void 0 : data.error) || (data == null ? void 0 : data.message) || "Falha ao concluir onboarding", ErrorType.NETWORK);
1379
+ }
1380
+ return {
1381
+ success: !!data.success,
1382
+ message: data.message || "Onboarding concluído com sucesso",
1383
+ person_id: data.person_id,
1384
+ identity_data_id: data.identity_data_id,
1385
+ confidence_score: data.confidence_score,
1386
+ processing_time: data.processing_time,
1387
+ liveness_passed: data.liveness_passed,
1388
+ document_verified: data.document_verified,
1389
+ person_created: data.person_created
1390
+ };
1391
+ } catch (error) {
1392
+ if (error instanceof Error) {
1393
+ if (error.name === "AbortError") {
1394
+ throw new NeoFaceError("Tempo de requisição excedido", ErrorType.NETWORK);
1395
+ }
1396
+ if (error instanceof NeoFaceError) {
1397
+ throw error;
1398
+ }
1399
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1400
+ }
1401
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1402
+ }
1403
+ };
1293
1404
  const recognize = async (image, applicationToken) => {
1294
1405
  ensureSecureContext();
1295
1406
  const controller = createTimeoutController();
@@ -1332,7 +1443,7 @@ const recognize = async (image, applicationToken) => {
1332
1443
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1333
1444
  }
1334
1445
  };
1335
- const loginWithBiometric = async (image, applicationToken) => {
1446
+ const loginWithBiometric$1 = async (image, applicationToken) => {
1336
1447
  var _a, _b, _c, _d, _e;
1337
1448
  ensureSecureContext();
1338
1449
  const controller = createTimeoutController();
@@ -1705,6 +1816,75 @@ const loginWithEmail = async (email, password, applicationToken) => {
1705
1816
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1706
1817
  }
1707
1818
  };
1819
+ const identifyPerson = async (image, applicationToken) => {
1820
+ var _a, _b;
1821
+ ensureSecureContext();
1822
+ const controller = createTimeoutController();
1823
+ const compressedImage = await compressImage(image);
1824
+ const base64Image = await new Promise((resolve, reject) => {
1825
+ const reader = new FileReader();
1826
+ reader.onload = () => {
1827
+ const result = reader.result;
1828
+ resolve(result.split(",")[1]);
1829
+ };
1830
+ reader.onerror = reject;
1831
+ reader.readAsDataURL(compressedImage);
1832
+ });
1833
+ try {
1834
+ const response = await fetch(`${API_BASE_URL}/api/v1/external/recognition/face/`, {
1835
+ method: "POST",
1836
+ headers: {
1837
+ "Content-Type": "application/json",
1838
+ "X-App-Token": applicationToken
1839
+ },
1840
+ body: JSON.stringify({
1841
+ image: base64Image,
1842
+ purpose: "SIMPLE_IDENTIFICATION_F"
1843
+ }),
1844
+ signal: controller.signal
1845
+ });
1846
+ if (!response.ok) {
1847
+ if (response.status === 401 || response.status === 403) {
1848
+ throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1849
+ }
1850
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1851
+ }
1852
+ const data = await response.json();
1853
+ if (!data.success) {
1854
+ throw new NeoFaceError(data.message || "Person identification failed", ErrorType.RECOGNITION_FAILED);
1855
+ }
1856
+ const personName = ((_a = data.person) == null ? void 0 : _a.name) || "";
1857
+ const birthDateStr = (_b = data.person) == null ? void 0 : _b.birth_date;
1858
+ if (!personName) {
1859
+ throw new NeoFaceError("Person name not found in response", ErrorType.PERSON_NOT_FOUND);
1860
+ }
1861
+ if (!birthDateStr) {
1862
+ throw new NeoFaceError("Birth date not found in response", ErrorType.PERSON_NOT_FOUND);
1863
+ }
1864
+ const birthDate = new Date(birthDateStr);
1865
+ const today = /* @__PURE__ */ new Date();
1866
+ let age = today.getFullYear() - birthDate.getFullYear();
1867
+ const monthDiff = today.getMonth() - birthDate.getMonth();
1868
+ if (monthDiff < 0 || monthDiff === 0 && today.getDate() < birthDate.getDate()) {
1869
+ age--;
1870
+ }
1871
+ return {
1872
+ name: personName,
1873
+ age
1874
+ };
1875
+ } catch (error) {
1876
+ if (error instanceof Error) {
1877
+ if (error.name === "AbortError") {
1878
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1879
+ }
1880
+ if (error instanceof NeoFaceError) {
1881
+ throw error;
1882
+ }
1883
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1884
+ }
1885
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1886
+ }
1887
+ };
1708
1888
  const modalReducer$1 = (state, action) => {
1709
1889
  switch (action.type) {
1710
1890
  case "PERMISSION_GRANTED":
@@ -3147,7 +3327,7 @@ function BiometricRegistrationModal({
3147
3327
  /* @__PURE__ */ jsxRuntimeExports.jsx("canvas", { ref: canvasRef })
3148
3328
  ] });
3149
3329
  }
3150
- class BiometricCaptureModal {
3330
+ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3151
3331
  constructor(options) {
3152
3332
  __publicField(this, "modal", null);
3153
3333
  __publicField(this, "video", null);
@@ -3646,97 +3826,1106 @@ class BiometricCaptureModal {
3646
3826
  `;
3647
3827
  document.head.appendChild(style);
3648
3828
  }
3649
- }
3650
- class EmailPasswordModal {
3651
- constructor(options) {
3652
- __publicField(this, "options");
3653
- __publicField(this, "modalElement", null);
3654
- this.options = options;
3655
- }
3656
- /**
3657
- * Abre o modal de login com email e senha.
3658
- */
3659
- async open() {
3660
- this.createModal();
3661
- document.body.appendChild(this.modalElement);
3662
- }
3663
- /**
3664
- * Fecha o modal.
3665
- */
3666
- close() {
3667
- if (this.modalElement) {
3668
- document.body.removeChild(this.modalElement);
3669
- this.modalElement = null;
3670
- }
3671
- if (this.options.onCancel) {
3672
- this.options.onCancel();
3673
- }
3674
- }
3675
- createModal() {
3676
- this.modalElement = document.createElement("div");
3677
- this.modalElement.className = "neoface-modal";
3678
- this.modalElement.innerHTML = `
3679
- <div class="modal-content">
3680
- <h2>${this.options.title || "Login com Email e Senha"}</h2>
3681
- <p>${this.options.subtitle || "Informe seus dados para login."}</p>
3682
- <form id="email-password-form">
3683
- <input type="email" id="email" placeholder="Email" required />
3684
- <input type="password" id="password" placeholder="Senha" required />
3685
- <button type="submit">Entrar</button>
3686
- </form>
3687
- <button class="cancel-button">Cancelar</button>
3688
- </div>
3689
- `;
3690
- const form = this.modalElement.querySelector("#email-password-form");
3691
- form.addEventListener("submit", this.handleSubmit.bind(this));
3692
- const cancelButton = this.modalElement.querySelector(".cancel-button");
3693
- cancelButton.addEventListener("click", this.close.bind(this));
3694
- }
3695
- async handleSubmit(event) {
3696
- event.preventDefault();
3697
- const emailInput = this.modalElement.querySelector("#email");
3698
- const passwordInput = this.modalElement.querySelector("#password");
3699
- const email = emailInput.value.trim();
3700
- const password = passwordInput.value.trim();
3701
- if (!email || !password) {
3702
- this.options.onError(new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR));
3703
- return;
3704
- }
3705
- try {
3706
- const result = await loginWithEmail(email, password, this.options.applicationToken);
3707
- this.options.onSuccess(result);
3708
- this.close();
3709
- } catch (error) {
3710
- this.options.onError(error);
3711
- }
3712
- }
3713
- }
3714
- async function startFaceLogin(options) {
3715
- try {
3716
- const isValidToken = await validateToken(options.applicationToken);
3717
- if (!isValidToken) {
3718
- options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
3719
- return;
3829
+ };
3830
+ function BiometricStatusOverlayComponent({ status, onClose }) {
3831
+ const [isVisible, setIsVisible] = useState(true);
3832
+ useEffect(() => {
3833
+ if (status === "success") {
3834
+ const timer = setTimeout(() => {
3835
+ setIsVisible(false);
3836
+ setTimeout(() => {
3837
+ onClose == null ? void 0 : onClose();
3838
+ }, 300);
3839
+ }, 800);
3840
+ return () => clearTimeout(timer);
3720
3841
  }
3721
- const captureOptions = {
3722
- mode: "face",
3723
- onSuccess: async (imageData, _detectedType) => {
3724
- try {
3725
- const imageBlob = await fetch(imageData).then((res) => res.blob());
3726
- const result = await loginWithBiometric(imageBlob, options.applicationToken);
3727
- options.onSuccess(result);
3728
- } catch (error) {
3729
- options.onError(error);
3842
+ }, [status, onClose]);
3843
+ if (!isVisible)
3844
+ return null;
3845
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3846
+ /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
3847
+ @keyframes fadeIn {
3848
+ from { opacity: 0; transform: scale(0.9); }
3849
+ to { opacity: 1; transform: scale(1); }
3730
3850
  }
3731
- },
3732
- onError: options.onError,
3733
- onCancel: options.onCancel,
3734
- countdown: options.countdown,
3735
- title: options.title || "Login Facial",
3736
- subtitle: options.subtitle || "Posicione seu rosto dentro do quadro para fazer login."
3737
- };
3738
- const modal = new BiometricCaptureModal(captureOptions);
3739
- await modal.open();
3851
+
3852
+ @keyframes fadeOut {
3853
+ from { opacity: 1; transform: scale(1); }
3854
+ to { opacity: 0; transform: scale(0.9); }
3855
+ }
3856
+
3857
+ @keyframes pulse {
3858
+ 0%, 100% {
3859
+ transform: scale(1);
3860
+ opacity: 1;
3861
+ }
3862
+ 50% {
3863
+ transform: scale(1.1);
3864
+ opacity: 0.9;
3865
+ }
3866
+ }
3867
+
3868
+ @keyframes spin {
3869
+ from { transform: rotate(0deg); }
3870
+ to { transform: rotate(360deg); }
3871
+ }
3872
+
3873
+ @keyframes checkmark {
3874
+ 0% {
3875
+ stroke-dashoffset: 100;
3876
+ opacity: 0;
3877
+ }
3878
+ 50% {
3879
+ opacity: 1;
3880
+ }
3881
+ 100% {
3882
+ stroke-dashoffset: 0;
3883
+ opacity: 1;
3884
+ }
3885
+ }
3886
+
3887
+ @keyframes errorShake {
3888
+ 0%, 100% { transform: translateX(0) scale(1); }
3889
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px) scale(1); }
3890
+ 20%, 40%, 60%, 80% { transform: translateX(4px) scale(1); }
3891
+ }
3892
+
3893
+ @keyframes scaleIn {
3894
+ 0% {
3895
+ transform: scale(0);
3896
+ opacity: 0;
3897
+ }
3898
+ 50% {
3899
+ transform: scale(1.1);
3900
+ }
3901
+ 100% {
3902
+ transform: scale(1);
3903
+ opacity: 1;
3904
+ }
3905
+ }
3906
+
3907
+ .neofaceid-overlay-container {
3908
+ position: fixed;
3909
+ top: 0;
3910
+ left: 0;
3911
+ right: 0;
3912
+ bottom: 0;
3913
+ display: flex;
3914
+ align-items: center;
3915
+ justify-content: center;
3916
+ z-index: 10000;
3917
+ pointer-events: none;
3918
+ animation: fadeIn 0.3s ease-out;
3919
+ }
3920
+
3921
+ .neofaceid-overlay-container.fade-out {
3922
+ animation: fadeOut 0.3s ease-out;
3923
+ }
3924
+
3925
+ .neofaceid-overlay-content {
3926
+ display: flex;
3927
+ flex-direction: column;
3928
+ align-items: center;
3929
+ justify-content: center;
3930
+ gap: 24px;
3931
+ pointer-events: auto;
3932
+ }
3933
+
3934
+ .neofaceid-logo-wrapper {
3935
+ position: relative;
3936
+ width: 120px;
3937
+ height: 120px;
3938
+ display: flex;
3939
+ align-items: center;
3940
+ justify-content: center;
3941
+ }
3942
+
3943
+ .neofaceid-logo {
3944
+ width: 80px;
3945
+ height: 80px;
3946
+ object-fit: contain;
3947
+ filter: drop-shadow(0 4px 12px rgba(124, 58, 237, 0.3));
3948
+ }
3949
+
3950
+ .neofaceid-logo.pulsing {
3951
+ animation: pulse 2s ease-in-out infinite;
3952
+ }
3953
+
3954
+ .neofaceid-status-icon {
3955
+ position: absolute;
3956
+ inset: 0;
3957
+ display: flex;
3958
+ align-items: center;
3959
+ justify-content: center;
3960
+ }
3961
+
3962
+ .neofaceid-success-icon {
3963
+ width: 60px;
3964
+ height: 60px;
3965
+ color: #10b981;
3966
+ animation: scaleIn 0.6s ease-out;
3967
+ }
3968
+
3969
+ .neofaceid-success-icon svg circle {
3970
+ stroke-dasharray: 100;
3971
+ stroke-dashoffset: 100;
3972
+ animation: checkmark 0.6s ease-out 0.2s forwards;
3973
+ }
3974
+
3975
+ .neofaceid-error-icon {
3976
+ width: 60px;
3977
+ height: 60px;
3978
+ color: #ef4444;
3979
+ animation: errorShake 0.5s ease-out, scaleIn 0.4s ease-out;
3980
+ }
3981
+
3982
+ /* Responsive */
3983
+ @media (max-width: 640px) {
3984
+ .neofaceid-logo-wrapper {
3985
+ width: 100px;
3986
+ height: 100px;
3987
+ }
3988
+
3989
+ .neofaceid-logo {
3990
+ width: 70px;
3991
+ height: 70px;
3992
+ }
3993
+ }
3994
+ ` }),
3995
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `neofaceid-overlay-container ${!isVisible ? "fade-out" : ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-overlay-content", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "neofaceid-logo-wrapper", children: [
3996
+ (status === "preparing" || status === "verifying") && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3997
+ "img",
3998
+ {
3999
+ src: "/public/logo-icone-no-backgorund.png",
4000
+ alt: "NeoFaceId",
4001
+ className: `neofaceid-logo ${status === "verifying" ? "pulsing" : ""}`,
4002
+ onError: (e) => {
4003
+ var _a;
4004
+ const img = e.target;
4005
+ img.style.display = "none";
4006
+ const fallback = document.createElement("div");
4007
+ fallback.innerHTML = `
4008
+ <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
4009
+ <circle cx="40" cy="40" r="35" fill="#7c3aed" opacity="0.2"/>
4010
+ <path d="M40 20 L40 60 M20 40 L60 40" stroke="#7c3aed" stroke-width="4" stroke-linecap="round"/>
4011
+ </svg>
4012
+ `;
4013
+ fallback.style.cssText = "width: 80px; height: 80px; display: flex; align-items: center; justify-content: center;";
4014
+ (_a = img.parentElement) == null ? void 0 : _a.appendChild(fallback);
4015
+ }
4016
+ }
4017
+ ) }),
4018
+ status === "success" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-status-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4019
+ "svg",
4020
+ {
4021
+ className: "neofaceid-success-icon",
4022
+ viewBox: "0 0 60 60",
4023
+ fill: "none",
4024
+ xmlns: "http://www.w3.org/2000/svg",
4025
+ children: [
4026
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4027
+ "circle",
4028
+ {
4029
+ cx: "30",
4030
+ cy: "30",
4031
+ r: "28",
4032
+ stroke: "currentColor",
4033
+ strokeWidth: "3",
4034
+ fill: "none"
4035
+ }
4036
+ ),
4037
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4038
+ "path",
4039
+ {
4040
+ d: "M15 30 L25 40 L45 20",
4041
+ stroke: "currentColor",
4042
+ strokeWidth: "3",
4043
+ strokeLinecap: "round",
4044
+ strokeLinejoin: "round",
4045
+ fill: "none"
4046
+ }
4047
+ )
4048
+ ]
4049
+ }
4050
+ ) }),
4051
+ status === "error" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-status-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4052
+ "svg",
4053
+ {
4054
+ className: "neofaceid-error-icon",
4055
+ viewBox: "0 0 60 60",
4056
+ fill: "none",
4057
+ xmlns: "http://www.w3.org/2000/svg",
4058
+ children: [
4059
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4060
+ "circle",
4061
+ {
4062
+ cx: "30",
4063
+ cy: "30",
4064
+ r: "28",
4065
+ stroke: "currentColor",
4066
+ strokeWidth: "3",
4067
+ fill: "none"
4068
+ }
4069
+ ),
4070
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4071
+ "path",
4072
+ {
4073
+ d: "M20 20 L40 40 M40 20 L20 40",
4074
+ stroke: "currentColor",
4075
+ strokeWidth: "3",
4076
+ strokeLinecap: "round"
4077
+ }
4078
+ )
4079
+ ]
4080
+ }
4081
+ ) })
4082
+ ] }) }) })
4083
+ ] });
4084
+ }
4085
+ class BiometricStatusOverlay {
4086
+ constructor() {
4087
+ __publicField(this, "container", null);
4088
+ __publicField(this, "root", null);
4089
+ __publicField(this, "currentStatus", "preparing");
4090
+ }
4091
+ /**
4092
+ * Cria e exibe o overlay
4093
+ */
4094
+ show(status = "preparing") {
4095
+ if (this.container) {
4096
+ this.updateStatus(status);
4097
+ return;
4098
+ }
4099
+ this.container = document.createElement("div");
4100
+ this.container.id = "neofaceid-status-overlay";
4101
+ document.body.appendChild(this.container);
4102
+ this.root = createRoot(this.container);
4103
+ this.currentStatus = status;
4104
+ this.render();
4105
+ }
4106
+ /**
4107
+ * Atualiza o status do overlay
4108
+ */
4109
+ updateStatus(status) {
4110
+ this.currentStatus = status;
4111
+ if (this.root) {
4112
+ this.render();
4113
+ }
4114
+ }
4115
+ /**
4116
+ * Fecha e remove o overlay
4117
+ */
4118
+ close() {
4119
+ if (this.container) {
4120
+ if (this.root) {
4121
+ this.root.render(
4122
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4123
+ BiometricStatusOverlayComponent,
4124
+ {
4125
+ status: this.currentStatus,
4126
+ onClose: () => {
4127
+ if (this.container && document.body.contains(this.container)) {
4128
+ document.body.removeChild(this.container);
4129
+ }
4130
+ this.container = null;
4131
+ this.root = null;
4132
+ }
4133
+ }
4134
+ )
4135
+ );
4136
+ }
4137
+ setTimeout(() => {
4138
+ if (this.container && document.body.contains(this.container)) {
4139
+ document.body.removeChild(this.container);
4140
+ }
4141
+ this.container = null;
4142
+ this.root = null;
4143
+ }, 300);
4144
+ }
4145
+ }
4146
+ /**
4147
+ * Renderiza o componente
4148
+ */
4149
+ render() {
4150
+ if (!this.root || !this.container)
4151
+ return;
4152
+ this.root.render(
4153
+ /* @__PURE__ */ jsxRuntimeExports.jsx(BiometricStatusOverlayComponent, { status: this.currentStatus, onClose: () => this.close() })
4154
+ );
4155
+ }
4156
+ }
4157
+ function FallbackPromptComponent({
4158
+ error,
4159
+ onRetry,
4160
+ onCredentials,
4161
+ onCancel
4162
+ }) {
4163
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
4164
+ /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
4165
+ @keyframes slideUp {
4166
+ from {
4167
+ opacity: 0;
4168
+ transform: translateY(20px);
4169
+ }
4170
+ to {
4171
+ opacity: 1;
4172
+ transform: translateY(0);
4173
+ }
4174
+ }
4175
+
4176
+ @keyframes fadeIn {
4177
+ from { opacity: 0; }
4178
+ to { opacity: 1; }
4179
+ }
4180
+
4181
+ .neofaceid-fallback-overlay {
4182
+ position: fixed;
4183
+ inset: 0;
4184
+ background: rgba(0, 0, 0, 0.6);
4185
+ backdrop-filter: blur(8px);
4186
+ display: flex;
4187
+ align-items: center;
4188
+ justify-content: center;
4189
+ z-index: 10001;
4190
+ padding: 20px;
4191
+ animation: fadeIn 0.3s ease-out;
4192
+ }
4193
+
4194
+ .neofaceid-fallback-prompt {
4195
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
4196
+ border-radius: 20px;
4197
+ padding: 32px;
4198
+ max-width: 400px;
4199
+ width: 100%;
4200
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
4201
+ animation: slideUp 0.4s ease-out;
4202
+ text-align: center;
4203
+ }
4204
+
4205
+ .neofaceid-fallback-icon {
4206
+ width: 64px;
4207
+ height: 64px;
4208
+ margin: 0 auto 20px;
4209
+ color: #ef4444;
4210
+ animation: scaleIn 0.4s ease-out;
4211
+ }
4212
+
4213
+ @keyframes scaleIn {
4214
+ 0% {
4215
+ transform: scale(0);
4216
+ opacity: 0;
4217
+ }
4218
+ 50% {
4219
+ transform: scale(1.1);
4220
+ }
4221
+ 100% {
4222
+ transform: scale(1);
4223
+ opacity: 1;
4224
+ }
4225
+ }
4226
+
4227
+ .neofaceid-fallback-title {
4228
+ font-size: 22px;
4229
+ font-weight: 700;
4230
+ color: white;
4231
+ margin: 0 0 12px 0;
4232
+ }
4233
+
4234
+ .neofaceid-fallback-message {
4235
+ font-size: 15px;
4236
+ color: rgba(255, 255, 255, 0.7);
4237
+ margin: 0 0 28px 0;
4238
+ line-height: 1.5;
4239
+ }
4240
+
4241
+ .neofaceid-fallback-actions {
4242
+ display: flex;
4243
+ flex-direction: column;
4244
+ gap: 12px;
4245
+ }
4246
+
4247
+ .neofaceid-fallback-button {
4248
+ padding: 14px 24px;
4249
+ border-radius: 12px;
4250
+ border: none;
4251
+ font-size: 16px;
4252
+ font-weight: 600;
4253
+ cursor: pointer;
4254
+ transition: all 0.2s;
4255
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4256
+ }
4257
+
4258
+ .neofaceid-fallback-button-primary {
4259
+ background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
4260
+ color: white;
4261
+ box-shadow: 0 4px 16px rgba(124, 58, 237, 0.3);
4262
+ }
4263
+
4264
+ .neofaceid-fallback-button-primary:hover {
4265
+ transform: translateY(-2px);
4266
+ box-shadow: 0 8px 24px rgba(124, 58, 237, 0.4);
4267
+ }
4268
+
4269
+ .neofaceid-fallback-button-primary:active {
4270
+ transform: translateY(0);
4271
+ }
4272
+
4273
+ .neofaceid-fallback-button-secondary {
4274
+ background: rgba(255, 255, 255, 0.1);
4275
+ color: white;
4276
+ border: 1px solid rgba(255, 255, 255, 0.2);
4277
+ }
4278
+
4279
+ .neofaceid-fallback-button-secondary:hover {
4280
+ background: rgba(255, 255, 255, 0.15);
4281
+ border-color: rgba(255, 255, 255, 0.3);
4282
+ }
4283
+
4284
+ .neofaceid-fallback-button-ghost {
4285
+ background: transparent;
4286
+ color: rgba(255, 255, 255, 0.7);
4287
+ padding: 10px;
4288
+ }
4289
+
4290
+ .neofaceid-fallback-button-ghost:hover {
4291
+ color: white;
4292
+ background: rgba(255, 255, 255, 0.05);
4293
+ }
4294
+
4295
+ @media (max-width: 640px) {
4296
+ .neofaceid-fallback-prompt {
4297
+ padding: 24px;
4298
+ border-radius: 16px;
4299
+ }
4300
+
4301
+ .neofaceid-fallback-title {
4302
+ font-size: 20px;
4303
+ }
4304
+ }
4305
+ ` }),
4306
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-fallback-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "neofaceid-fallback-prompt", children: [
4307
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-fallback-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 64 64", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
4308
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "32", cy: "32", r: "30", stroke: "currentColor", strokeWidth: "3", fill: "none" }),
4309
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4310
+ "path",
4311
+ {
4312
+ d: "M20 20 L44 44 M44 20 L20 44",
4313
+ stroke: "currentColor",
4314
+ strokeWidth: "3",
4315
+ strokeLinecap: "round"
4316
+ }
4317
+ )
4318
+ ] }) }),
4319
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "neofaceid-fallback-title", children: "Não foi possível reconhecer" }),
4320
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "neofaceid-fallback-message", children: (error == null ? void 0 : error.message) || "Não conseguimos reconhecer sua face. Tente novamente ou use email e senha." }),
4321
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "neofaceid-fallback-actions", children: [
4322
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4323
+ "button",
4324
+ {
4325
+ type: "button",
4326
+ className: "neofaceid-fallback-button neofaceid-fallback-button-primary",
4327
+ onClick: onRetry,
4328
+ children: "Tentar Novamente"
4329
+ }
4330
+ ),
4331
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4332
+ "button",
4333
+ {
4334
+ type: "button",
4335
+ className: "neofaceid-fallback-button neofaceid-fallback-button-secondary",
4336
+ onClick: onCredentials,
4337
+ children: "Entrar com Email e Senha"
4338
+ }
4339
+ ),
4340
+ onCancel && /* @__PURE__ */ jsxRuntimeExports.jsx(
4341
+ "button",
4342
+ {
4343
+ type: "button",
4344
+ className: "neofaceid-fallback-button neofaceid-fallback-button-ghost",
4345
+ onClick: onCancel,
4346
+ children: "Cancelar"
4347
+ }
4348
+ )
4349
+ ] })
4350
+ ] }) })
4351
+ ] });
4352
+ }
4353
+ class FallbackPrompt {
4354
+ constructor() {
4355
+ __publicField(this, "container", null);
4356
+ __publicField(this, "root", null);
4357
+ }
4358
+ /**
4359
+ * Exibe o prompt de fallback
4360
+ */
4361
+ show(error, onRetry, onCredentials, onCancel) {
4362
+ if (this.container) {
4363
+ return;
4364
+ }
4365
+ this.container = document.createElement("div");
4366
+ this.container.id = "neofaceid-fallback-prompt";
4367
+ document.body.appendChild(this.container);
4368
+ this.root = createRoot(this.container);
4369
+ this.render(error, onRetry, onCredentials, onCancel);
4370
+ }
4371
+ /**
4372
+ * Fecha o prompt
4373
+ */
4374
+ close() {
4375
+ if (this.container && document.body.contains(this.container)) {
4376
+ if (this.root) {
4377
+ this.root.unmount();
4378
+ }
4379
+ document.body.removeChild(this.container);
4380
+ this.container = null;
4381
+ this.root = null;
4382
+ }
4383
+ }
4384
+ /**
4385
+ * Renderiza o componente
4386
+ */
4387
+ render(error, onRetry, onCredentials, onCancel) {
4388
+ if (!this.root || !this.container)
4389
+ return;
4390
+ this.root.render(
4391
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4392
+ FallbackPromptComponent,
4393
+ {
4394
+ error,
4395
+ onRetry: () => {
4396
+ this.close();
4397
+ onRetry();
4398
+ },
4399
+ onCredentials: () => {
4400
+ this.close();
4401
+ onCredentials();
4402
+ },
4403
+ onCancel: () => {
4404
+ this.close();
4405
+ onCancel == null ? void 0 : onCancel();
4406
+ }
4407
+ }
4408
+ )
4409
+ );
4410
+ }
4411
+ }
4412
+ class EmailPasswordModal {
4413
+ constructor(options) {
4414
+ __publicField(this, "options");
4415
+ __publicField(this, "modalElement", null);
4416
+ this.options = options;
4417
+ }
4418
+ /**
4419
+ * Abre o modal de login com email e senha.
4420
+ */
4421
+ async open() {
4422
+ this.createModal();
4423
+ document.body.appendChild(this.modalElement);
4424
+ }
4425
+ /**
4426
+ * Fecha o modal.
4427
+ */
4428
+ close() {
4429
+ if (this.modalElement) {
4430
+ document.body.removeChild(this.modalElement);
4431
+ this.modalElement = null;
4432
+ }
4433
+ if (this.options.onCancel) {
4434
+ this.options.onCancel();
4435
+ }
4436
+ }
4437
+ createModal() {
4438
+ this.addStyles();
4439
+ this.modalElement = document.createElement("div");
4440
+ this.modalElement.className = "neoface-email-password-modal";
4441
+ this.modalElement.innerHTML = `
4442
+ <div class="neoface-email-modal-overlay">
4443
+ <div class="neoface-email-modal-content">
4444
+ <h2>${this.options.title || "Login com Email e Senha"}</h2>
4445
+ <p>${this.options.subtitle || "Informe seus dados para login."}</p>
4446
+ <form id="email-password-form">
4447
+ <input type="email" id="email" placeholder="Email" required />
4448
+ <input type="password" id="password" placeholder="Senha" required />
4449
+ <button type="submit" class="neoface-email-submit-btn">Entrar</button>
4450
+ </form>
4451
+ <button class="neoface-email-cancel-btn">Cancelar</button>
4452
+
4453
+ <!-- Branding -->
4454
+ <div class="neoface-email-branding">
4455
+ <img src="/public/logo-icone-no-backgorund.png" alt="NeoFaceId" class="neoface-email-brand-logo" onerror="this.style.display='none'" />
4456
+ <span>NeoFaceId by</span>
4457
+ <span class="neoface-email-brand-name">OCTA</span>
4458
+ </div>
4459
+ </div>
4460
+ </div>
4461
+ `;
4462
+ const form = this.modalElement.querySelector("#email-password-form");
4463
+ form.addEventListener("submit", this.handleSubmit.bind(this));
4464
+ const cancelButton = this.modalElement.querySelector(
4465
+ ".neoface-email-cancel-btn"
4466
+ );
4467
+ cancelButton.addEventListener("click", this.close.bind(this));
4468
+ }
4469
+ addStyles() {
4470
+ const styleId = "neoface-email-password-modal-styles";
4471
+ const existingStyles = document.getElementById(styleId);
4472
+ if (existingStyles) {
4473
+ return;
4474
+ }
4475
+ const style = document.createElement("style");
4476
+ style.id = styleId;
4477
+ style.textContent = `
4478
+ .neoface-email-password-modal {
4479
+ position: fixed;
4480
+ top: 0;
4481
+ left: 0;
4482
+ width: 100%;
4483
+ height: 100%;
4484
+ z-index: 10002;
4485
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4486
+ }
4487
+
4488
+ .neoface-email-modal-overlay {
4489
+ position: absolute;
4490
+ inset: 0;
4491
+ background: rgba(0, 0, 0, 0.6);
4492
+ backdrop-filter: blur(8px);
4493
+ display: flex;
4494
+ align-items: center;
4495
+ justify-content: center;
4496
+ padding: 20px;
4497
+ animation: fadeIn 0.3s ease-out;
4498
+ }
4499
+
4500
+ @keyframes fadeIn {
4501
+ from { opacity: 0; }
4502
+ to { opacity: 1; }
4503
+ }
4504
+
4505
+ @keyframes slideUp {
4506
+ from {
4507
+ opacity: 0;
4508
+ transform: translateY(20px);
4509
+ }
4510
+ to {
4511
+ opacity: 1;
4512
+ transform: translateY(0);
4513
+ }
4514
+ }
4515
+
4516
+ .neoface-email-modal-content {
4517
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
4518
+ border-radius: 20px;
4519
+ padding: 32px;
4520
+ max-width: 400px;
4521
+ width: 100%;
4522
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
4523
+ animation: slideUp 0.4s ease-out;
4524
+ }
4525
+
4526
+ .neoface-email-modal-content h2 {
4527
+ font-size: 24px;
4528
+ font-weight: 700;
4529
+ color: white;
4530
+ margin: 0 0 12px 0;
4531
+ text-align: center;
4532
+ }
4533
+
4534
+ .neoface-email-modal-content p {
4535
+ font-size: 15px;
4536
+ color: rgba(255, 255, 255, 0.7);
4537
+ margin: 0 0 24px 0;
4538
+ text-align: center;
4539
+ line-height: 1.5;
4540
+ }
4541
+
4542
+ .neoface-email-modal-content form {
4543
+ display: flex;
4544
+ flex-direction: column;
4545
+ gap: 16px;
4546
+ margin-bottom: 16px;
4547
+ }
4548
+
4549
+ .neoface-email-modal-content input {
4550
+ width: 100%;
4551
+ padding: 14px 16px;
4552
+ border: 2px solid rgba(255, 255, 255, 0.2);
4553
+ border-radius: 12px;
4554
+ font-size: 15px;
4555
+ background: rgba(255, 255, 255, 0.1);
4556
+ color: white;
4557
+ font-family: inherit;
4558
+ transition: all 0.2s;
4559
+ box-sizing: border-box;
4560
+ }
4561
+
4562
+ .neoface-email-modal-content input::placeholder {
4563
+ color: rgba(255, 255, 255, 0.5);
4564
+ }
4565
+
4566
+ .neoface-email-modal-content input:focus {
4567
+ outline: none;
4568
+ border-color: #7c3aed;
4569
+ box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.2);
4570
+ background: rgba(255, 255, 255, 0.15);
4571
+ }
4572
+
4573
+ .neoface-email-submit-btn {
4574
+ width: 100%;
4575
+ padding: 14px 24px;
4576
+ border-radius: 12px;
4577
+ border: none;
4578
+ background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
4579
+ color: white;
4580
+ font-size: 16px;
4581
+ font-weight: 600;
4582
+ cursor: pointer;
4583
+ transition: all 0.2s;
4584
+ box-shadow: 0 4px 16px rgba(124, 58, 237, 0.3);
4585
+ font-family: inherit;
4586
+ }
4587
+
4588
+ .neoface-email-submit-btn:hover {
4589
+ transform: translateY(-2px);
4590
+ box-shadow: 0 8px 24px rgba(124, 58, 237, 0.4);
4591
+ }
4592
+
4593
+ .neoface-email-submit-btn:active {
4594
+ transform: translateY(0);
4595
+ }
4596
+
4597
+ .neoface-email-submit-btn:disabled {
4598
+ opacity: 0.6;
4599
+ cursor: not-allowed;
4600
+ transform: none;
4601
+ }
4602
+
4603
+ .neoface-email-cancel-btn {
4604
+ width: 100%;
4605
+ padding: 12px 24px;
4606
+ border-radius: 12px;
4607
+ border: 1px solid rgba(255, 255, 255, 0.2);
4608
+ background: transparent;
4609
+ color: rgba(255, 255, 255, 0.7);
4610
+ font-size: 15px;
4611
+ font-weight: 600;
4612
+ cursor: pointer;
4613
+ transition: all 0.2s;
4614
+ font-family: inherit;
4615
+ }
4616
+
4617
+ .neoface-email-cancel-btn:hover {
4618
+ background: rgba(255, 255, 255, 0.1);
4619
+ color: white;
4620
+ border-color: rgba(255, 255, 255, 0.3);
4621
+ }
4622
+
4623
+ .neoface-email-branding {
4624
+ display: flex;
4625
+ align-items: center;
4626
+ justify-content: center;
4627
+ gap: 8px;
4628
+ margin-top: 32px;
4629
+ padding-top: 24px;
4630
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
4631
+ font-size: 13px;
4632
+ color: rgba(255, 255, 255, 0.5);
4633
+ }
4634
+
4635
+ .neoface-email-brand-logo {
4636
+ height: 20px;
4637
+ width: auto;
4638
+ }
4639
+
4640
+ .neoface-email-brand-name {
4641
+ font-weight: 600;
4642
+ font-family: 'SF Pro Display', -apple-system, system-ui, sans-serif;
4643
+ color: #ffffff;
4644
+ letter-spacing: 0.3px;
4645
+ }
4646
+
4647
+ @media (max-width: 640px) {
4648
+ .neoface-email-modal-content {
4649
+ padding: 24px;
4650
+ border-radius: 16px;
4651
+ }
4652
+
4653
+ .neoface-email-modal-content h2 {
4654
+ font-size: 20px;
4655
+ }
4656
+ }
4657
+ `;
4658
+ document.head.appendChild(style);
4659
+ }
4660
+ async handleSubmit(event) {
4661
+ event.preventDefault();
4662
+ const emailInput = this.modalElement.querySelector("#email");
4663
+ const passwordInput = this.modalElement.querySelector("#password");
4664
+ const email = emailInput.value.trim();
4665
+ const password = passwordInput.value.trim();
4666
+ if (!email || !password) {
4667
+ this.options.onError(
4668
+ new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR)
4669
+ );
4670
+ return;
4671
+ }
4672
+ try {
4673
+ const result = await loginWithEmail(email, password, this.options.applicationToken);
4674
+ this.options.onSuccess(result);
4675
+ this.close();
4676
+ } catch (error) {
4677
+ this.options.onError(error);
4678
+ }
4679
+ }
4680
+ }
4681
+ const MAX_ATTEMPTS = 3;
4682
+ const RETRY_DELAY = 500;
4683
+ async function detectFaceContinuously(video, minDetectionTime = 3e3, detectionInterval = 200) {
4684
+ return new Promise((resolve) => {
4685
+ let faceDetectedCount = 0;
4686
+ let totalChecks = 0;
4687
+ const requiredChecks = Math.ceil(minDetectionTime / detectionInterval);
4688
+ const minSuccessRate = 0.6;
4689
+ let timeoutId = null;
4690
+ let isResolved = false;
4691
+ const finish = (success) => {
4692
+ if (isResolved)
4693
+ return;
4694
+ isResolved = true;
4695
+ if (timeoutId) {
4696
+ clearTimeout(timeoutId);
4697
+ }
4698
+ resolve(success);
4699
+ };
4700
+ const checkFace = async () => {
4701
+ if (isResolved)
4702
+ return;
4703
+ try {
4704
+ const detection = await faceapi.detectSingleFace(
4705
+ video,
4706
+ new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.4 })
4707
+ );
4708
+ totalChecks++;
4709
+ if (detection) {
4710
+ faceDetectedCount++;
4711
+ }
4712
+ if (totalChecks >= requiredChecks) {
4713
+ const successRate = faceDetectedCount / totalChecks;
4714
+ finish(successRate >= minSuccessRate);
4715
+ return;
4716
+ }
4717
+ timeoutId = window.setTimeout(checkFace, detectionInterval);
4718
+ } catch (error) {
4719
+ totalChecks++;
4720
+ if (totalChecks >= requiredChecks) {
4721
+ const successRate = faceDetectedCount / totalChecks;
4722
+ finish(successRate >= minSuccessRate);
4723
+ } else {
4724
+ timeoutId = window.setTimeout(checkFace, detectionInterval);
4725
+ }
4726
+ }
4727
+ };
4728
+ const safetyTimeout = window.setTimeout(() => {
4729
+ if (!isResolved) {
4730
+ const successRate = totalChecks > 0 ? faceDetectedCount / totalChecks : 0;
4731
+ finish(successRate >= minSuccessRate);
4732
+ }
4733
+ }, minDetectionTime + 1e3);
4734
+ timeoutId = window.setTimeout(() => {
4735
+ checkFace();
4736
+ clearTimeout(safetyTimeout);
4737
+ }, detectionInterval);
4738
+ });
4739
+ }
4740
+ async function attemptLogin(applicationToken, overlay) {
4741
+ overlay.updateStatus("verifying");
4742
+ const video = document.createElement("video");
4743
+ video.style.position = "fixed";
4744
+ video.style.top = "-9999px";
4745
+ video.style.left = "-9999px";
4746
+ video.style.width = "1px";
4747
+ video.style.height = "1px";
4748
+ video.style.opacity = "0";
4749
+ video.setAttribute("autoplay", "");
4750
+ video.setAttribute("muted", "");
4751
+ video.setAttribute("playsinline", "");
4752
+ document.body.appendChild(video);
4753
+ let stream = null;
4754
+ try {
4755
+ stream = await navigator.mediaDevices.getUserMedia({
4756
+ video: {
4757
+ width: { ideal: 640 },
4758
+ height: { ideal: 480 },
4759
+ facingMode: "user"
4760
+ },
4761
+ audio: false
4762
+ });
4763
+ video.srcObject = stream;
4764
+ await video.play();
4765
+ await new Promise((resolve) => {
4766
+ if (video.readyState >= 2) {
4767
+ resolve(void 0);
4768
+ } else {
4769
+ video.onloadedmetadata = () => resolve(void 0);
4770
+ }
4771
+ });
4772
+ await new Promise((resolve) => setTimeout(resolve, 500));
4773
+ const faceDetected = await detectFaceContinuously(video, 3e3);
4774
+ if (!faceDetected) {
4775
+ if (stream) {
4776
+ stream.getTracks().forEach((track) => track.stop());
4777
+ }
4778
+ if (video.parentElement) {
4779
+ video.parentElement.removeChild(video);
4780
+ }
4781
+ throw new NeoFaceError("Rosto não detectado. Posicione seu rosto na frente da câmera.", ErrorType.VALIDATION_ERROR);
4782
+ }
4783
+ const canvas = document.createElement("canvas");
4784
+ canvas.width = video.videoWidth;
4785
+ canvas.height = video.videoHeight;
4786
+ const ctx = canvas.getContext("2d");
4787
+ if (!ctx) {
4788
+ if (stream) {
4789
+ stream.getTracks().forEach((track) => track.stop());
4790
+ }
4791
+ if (video.parentElement) {
4792
+ video.parentElement.removeChild(video);
4793
+ }
4794
+ throw new NeoFaceError("Erro ao capturar imagem", ErrorType.CAPTURE_ERROR);
4795
+ }
4796
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
4797
+ if (stream) {
4798
+ stream.getTracks().forEach((track) => track.stop());
4799
+ }
4800
+ if (video.parentElement) {
4801
+ video.parentElement.removeChild(video);
4802
+ }
4803
+ const imageBlob = await new Promise((resolve, reject) => {
4804
+ canvas.toBlob(
4805
+ (blob) => {
4806
+ canvas.remove();
4807
+ if (blob) {
4808
+ resolve(blob);
4809
+ } else {
4810
+ reject(new Error("Failed to capture image"));
4811
+ }
4812
+ },
4813
+ "image/jpeg",
4814
+ 0.85
4815
+ );
4816
+ });
4817
+ const result = await loginWithBiometric$1(imageBlob, applicationToken);
4818
+ if (!result.success) {
4819
+ throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
4820
+ }
4821
+ return result;
4822
+ } catch (error) {
4823
+ if (stream) {
4824
+ stream.getTracks().forEach((track) => track.stop());
4825
+ }
4826
+ if (video.parentElement) {
4827
+ video.parentElement.removeChild(video);
4828
+ }
4829
+ if (error instanceof NeoFaceError) {
4830
+ throw error;
4831
+ }
4832
+ throw new NeoFaceError(
4833
+ error instanceof Error ? error.message : "Erro desconhecido",
4834
+ ErrorType.UNKNOWN
4835
+ );
4836
+ }
4837
+ }
4838
+ async function executeBiometricLoginFlow(options) {
4839
+ const { applicationToken, onSuccess, onError, onFallbackRequest, onCancel } = options;
4840
+ const overlay = new BiometricStatusOverlay();
4841
+ const fallbackPrompt = new FallbackPrompt();
4842
+ let attempts = 0;
4843
+ let lastError;
4844
+ try {
4845
+ const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
4846
+ await Promise.all([
4847
+ faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
4848
+ faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
4849
+ ]);
4850
+ } catch (error) {
4851
+ console.warn("Face-api.js models not loaded, continuing without face detection");
4852
+ }
4853
+ overlay.show("preparing");
4854
+ const tryLogin = async () => {
4855
+ try {
4856
+ attempts++;
4857
+ const result = await attemptLogin(applicationToken, overlay);
4858
+ overlay.updateStatus("success");
4859
+ setTimeout(() => {
4860
+ overlay.close();
4861
+ onSuccess(result);
4862
+ }, 800);
4863
+ } catch (error) {
4864
+ lastError = error instanceof NeoFaceError ? error : new NeoFaceError(
4865
+ error instanceof Error ? error.message : "Erro desconhecido",
4866
+ ErrorType.UNKNOWN
4867
+ );
4868
+ overlay.updateStatus("error");
4869
+ if (attempts < MAX_ATTEMPTS) {
4870
+ setTimeout(() => {
4871
+ tryLogin();
4872
+ }, RETRY_DELAY);
4873
+ } else {
4874
+ setTimeout(() => {
4875
+ overlay.close();
4876
+ fallbackPrompt.show(
4877
+ lastError,
4878
+ () => {
4879
+ attempts = 0;
4880
+ overlay.show("preparing");
4881
+ setTimeout(() => tryLogin(), 500);
4882
+ },
4883
+ () => {
4884
+ if (onFallbackRequest) {
4885
+ onFallbackRequest();
4886
+ } else {
4887
+ onError(
4888
+ new NeoFaceError(
4889
+ "Por favor, use email e senha para fazer login",
4890
+ ErrorType.LOGIN_FAILED
4891
+ )
4892
+ );
4893
+ }
4894
+ },
4895
+ onCancel
4896
+ );
4897
+ }, 1e3);
4898
+ }
4899
+ }
4900
+ };
4901
+ setTimeout(() => {
4902
+ tryLogin();
4903
+ }, 300);
4904
+ }
4905
+ async function startFaceLogin(options) {
4906
+ try {
4907
+ const isValidToken = await validateToken(options.applicationToken);
4908
+ if (!isValidToken) {
4909
+ options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
4910
+ return;
4911
+ }
4912
+ await executeBiometricLoginFlow({
4913
+ applicationToken: options.applicationToken,
4914
+ onSuccess: options.onSuccess,
4915
+ onError: options.onError,
4916
+ onFallbackRequest: options.onFallbackRequest || (() => {
4917
+ const fallbackOptions = {
4918
+ applicationToken: options.applicationToken,
4919
+ onSuccess: options.onSuccess,
4920
+ onError: options.onError,
4921
+ title: "Login Alternativo",
4922
+ subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
4923
+ };
4924
+ const modal = new EmailPasswordModal(fallbackOptions);
4925
+ modal.open();
4926
+ }),
4927
+ onCancel: options.onCancel
4928
+ });
3740
4929
  } catch (error) {
3741
4930
  options.onError(
3742
4931
  new NeoFaceError(
@@ -4040,8 +5229,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
4040
5229
  initializeBiometricDetection,
4041
5230
  isAdvancedDetectionAvailable
4042
5231
  }, Symbol.toStringTag, { value: "Module" }));
4043
- const VERSION = "1.2.0";
4044
- const RELEASE_DATE = "2025-11-09";
5232
+ const VERSION = "1.4.0";
5233
+ const RELEASE_DATE = "2025-11-23";
4045
5234
  class OnboardingCaptureModal {
4046
5235
  constructor(options) {
4047
5236
  __publicField(this, "overlay", null);
@@ -4327,19 +5516,25 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
4327
5516
  });
4328
5517
  }
4329
5518
  export {
4330
- BiometricCaptureModal,
5519
+ BiometricCaptureModal$1 as BiometricCaptureModal,
4331
5520
  BiometricRegistrationModal,
5521
+ BiometricStatusOverlay,
5522
+ EmailPasswordModal,
4332
5523
  ErrorType,
4333
5524
  FaceCaptureModal,
5525
+ FallbackPrompt,
4334
5526
  NeoFaceError,
4335
5527
  RELEASE_DATE,
4336
5528
  VERSION,
4337
5529
  biometricLogin,
4338
5530
  biometricLoginWithFallback,
5531
+ completeOnboarding,
5532
+ completeOnboardingWithData,
4339
5533
  detectBiometricType,
5534
+ identifyPerson,
4340
5535
  initializeBiometricDetection,
4341
5536
  isAdvancedDetectionAvailable,
4342
- loginWithBiometric,
5537
+ loginWithBiometric$1 as loginWithBiometric,
4343
5538
  recognize,
4344
5539
  recognizeBiometric,
4345
5540
  recognizeByPurpose,
@@ -4352,6 +5547,7 @@ export {
4352
5547
  startFaceLogin,
4353
5548
  startHandLogin,
4354
5549
  startOnboarding,
5550
+ validateOnboardingToken,
4355
5551
  validateToken
4356
5552
  };
4357
5553
  //# sourceMappingURL=neoface-id-sdk.es.js.map