@neofaceid/web-sdk 1.3.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();
@@ -3216,7 +3327,7 @@ function BiometricRegistrationModal({
3216
3327
  /* @__PURE__ */ jsxRuntimeExports.jsx("canvas", { ref: canvasRef })
3217
3328
  ] });
3218
3329
  }
3219
- class BiometricCaptureModal {
3330
+ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3220
3331
  constructor(options) {
3221
3332
  __publicField(this, "modal", null);
3222
3333
  __publicField(this, "video", null);
@@ -3715,60 +3826,847 @@ class BiometricCaptureModal {
3715
3826
  `;
3716
3827
  document.head.appendChild(style);
3717
3828
  }
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);
3841
+ }
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); }
3850
+ }
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
+ ] });
3718
4084
  }
3719
- class EmailPasswordModal {
3720
- constructor(options) {
3721
- __publicField(this, "options");
3722
- __publicField(this, "modalElement", null);
3723
- this.options = options;
4085
+ class BiometricStatusOverlay {
4086
+ constructor() {
4087
+ __publicField(this, "container", null);
4088
+ __publicField(this, "root", null);
4089
+ __publicField(this, "currentStatus", "preparing");
3724
4090
  }
3725
4091
  /**
3726
- * Abre o modal de login com email e senha.
4092
+ * Cria e exibe o overlay
3727
4093
  */
3728
- async open() {
3729
- this.createModal();
3730
- document.body.appendChild(this.modalElement);
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();
3731
4105
  }
3732
4106
  /**
3733
- * Fecha o modal.
4107
+ * Atualiza o status do overlay
3734
4108
  */
3735
- close() {
3736
- if (this.modalElement) {
3737
- document.body.removeChild(this.modalElement);
3738
- this.modalElement = null;
4109
+ updateStatus(status) {
4110
+ this.currentStatus = status;
4111
+ if (this.root) {
4112
+ this.render();
3739
4113
  }
3740
- if (this.options.onCancel) {
3741
- this.options.onCancel();
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);
3742
4144
  }
3743
4145
  }
3744
- createModal() {
3745
- this.modalElement = document.createElement("div");
3746
- this.modalElement.className = "neoface-modal";
3747
- this.modalElement.innerHTML = `
3748
- <div class="modal-content">
3749
- <h2>${this.options.title || "Login com Email e Senha"}</h2>
3750
- <p>${this.options.subtitle || "Informe seus dados para login."}</p>
3751
- <form id="email-password-form">
3752
- <input type="email" id="email" placeholder="Email" required />
3753
- <input type="password" id="password" placeholder="Senha" required />
3754
- <button type="submit">Entrar</button>
3755
- </form>
3756
- <button class="cancel-button">Cancelar</button>
3757
- </div>
3758
- `;
3759
- const form = this.modalElement.querySelector("#email-password-form");
3760
- form.addEventListener("submit", this.handleSubmit.bind(this));
3761
- const cancelButton = this.modalElement.querySelector(".cancel-button");
3762
- cancelButton.addEventListener("click", this.close.bind(this));
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
+ );
3763
4155
  }
3764
- async handleSubmit(event) {
3765
- event.preventDefault();
3766
- const emailInput = this.modalElement.querySelector("#email");
3767
- const passwordInput = this.modalElement.querySelector("#password");
3768
- const email = emailInput.value.trim();
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();
3769
4665
  const password = passwordInput.value.trim();
3770
4666
  if (!email || !password) {
3771
- this.options.onError(new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR));
4667
+ this.options.onError(
4668
+ new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR)
4669
+ );
3772
4670
  return;
3773
4671
  }
3774
4672
  try {
@@ -3780,6 +4678,230 @@ class EmailPasswordModal {
3780
4678
  }
3781
4679
  }
3782
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
+ }
3783
4905
  async function startFaceLogin(options) {
3784
4906
  try {
3785
4907
  const isValidToken = await validateToken(options.applicationToken);
@@ -3787,25 +4909,23 @@ async function startFaceLogin(options) {
3787
4909
  options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
3788
4910
  return;
3789
4911
  }
3790
- const captureOptions = {
3791
- mode: "face",
3792
- onSuccess: async (imageData, _detectedType) => {
3793
- try {
3794
- const imageBlob = await fetch(imageData).then((res) => res.blob());
3795
- const result = await loginWithBiometric(imageBlob, options.applicationToken);
3796
- options.onSuccess(result);
3797
- } catch (error) {
3798
- options.onError(error);
3799
- }
3800
- },
4912
+ await executeBiometricLoginFlow({
4913
+ applicationToken: options.applicationToken,
4914
+ onSuccess: options.onSuccess,
3801
4915
  onError: options.onError,
3802
- onCancel: options.onCancel,
3803
- countdown: options.countdown,
3804
- title: options.title || "Login Facial",
3805
- subtitle: options.subtitle || "Posicione seu rosto dentro do quadro para fazer login."
3806
- };
3807
- const modal = new BiometricCaptureModal(captureOptions);
3808
- await modal.open();
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
+ });
3809
4929
  } catch (error) {
3810
4930
  options.onError(
3811
4931
  new NeoFaceError(
@@ -4109,8 +5229,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
4109
5229
  initializeBiometricDetection,
4110
5230
  isAdvancedDetectionAvailable
4111
5231
  }, Symbol.toStringTag, { value: "Module" }));
4112
- const VERSION = "1.3.0";
4113
- const RELEASE_DATE = "2025-11-17";
5232
+ const VERSION = "1.4.0";
5233
+ const RELEASE_DATE = "2025-11-23";
4114
5234
  class OnboardingCaptureModal {
4115
5235
  constructor(options) {
4116
5236
  __publicField(this, "overlay", null);
@@ -4396,20 +5516,25 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
4396
5516
  });
4397
5517
  }
4398
5518
  export {
4399
- BiometricCaptureModal,
5519
+ BiometricCaptureModal$1 as BiometricCaptureModal,
4400
5520
  BiometricRegistrationModal,
5521
+ BiometricStatusOverlay,
5522
+ EmailPasswordModal,
4401
5523
  ErrorType,
4402
5524
  FaceCaptureModal,
5525
+ FallbackPrompt,
4403
5526
  NeoFaceError,
4404
5527
  RELEASE_DATE,
4405
5528
  VERSION,
4406
5529
  biometricLogin,
4407
5530
  biometricLoginWithFallback,
5531
+ completeOnboarding,
5532
+ completeOnboardingWithData,
4408
5533
  detectBiometricType,
4409
5534
  identifyPerson,
4410
5535
  initializeBiometricDetection,
4411
5536
  isAdvancedDetectionAvailable,
4412
- loginWithBiometric,
5537
+ loginWithBiometric$1 as loginWithBiometric,
4413
5538
  recognize,
4414
5539
  recognizeBiometric,
4415
5540
  recognizeByPurpose,
@@ -4422,6 +5547,7 @@ export {
4422
5547
  startFaceLogin,
4423
5548
  startHandLogin,
4424
5549
  startOnboarding,
5550
+ validateOnboardingToken,
4425
5551
  validateToken
4426
5552
  };
4427
5553
  //# sourceMappingURL=neoface-id-sdk.es.js.map