@neofaceid/web-sdk 1.14.0 → 1.15.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.
|
@@ -1652,6 +1652,110 @@ const loginWithBiometric = async (image, applicationToken) => {
|
|
|
1652
1652
|
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
|
|
1653
1653
|
}
|
|
1654
1654
|
};
|
|
1655
|
+
const loginWithBiometricSSE = async (image, applicationToken) => {
|
|
1656
|
+
var _a;
|
|
1657
|
+
ensureSecureContext();
|
|
1658
|
+
const compressedImage = await compressImage(image);
|
|
1659
|
+
const arrayBuffer = await compressedImage.arrayBuffer();
|
|
1660
|
+
const base64Image = btoa(
|
|
1661
|
+
new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), "")
|
|
1662
|
+
);
|
|
1663
|
+
const controller = createTimeoutController();
|
|
1664
|
+
let response;
|
|
1665
|
+
try {
|
|
1666
|
+
response = await fetch(`${getApiBaseUrl()}/api/v1/auth/recognition/face/external/`, {
|
|
1667
|
+
method: "POST",
|
|
1668
|
+
headers: {
|
|
1669
|
+
"Content-Type": "application/json",
|
|
1670
|
+
"X-App-Token": applicationToken
|
|
1671
|
+
},
|
|
1672
|
+
body: JSON.stringify({ image: base64Image, purpose: "LOGIN" }),
|
|
1673
|
+
signal: controller.signal
|
|
1674
|
+
});
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
if (error instanceof Error) {
|
|
1677
|
+
if (error.name === "AbortError") {
|
|
1678
|
+
throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
|
|
1679
|
+
}
|
|
1680
|
+
if (error instanceof NeoFaceError) throw error;
|
|
1681
|
+
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
1682
|
+
}
|
|
1683
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
|
|
1684
|
+
}
|
|
1685
|
+
if (!response.ok) {
|
|
1686
|
+
if (response.status === 401 || response.status === 403) {
|
|
1687
|
+
throw new NeoFaceError("Invalid application token", ErrorType.INVALID_TOKEN);
|
|
1688
|
+
}
|
|
1689
|
+
throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
|
|
1690
|
+
}
|
|
1691
|
+
const initData = await response.json();
|
|
1692
|
+
if (!initData.success || !((_a = initData.data) == null ? void 0 : _a.task_id)) {
|
|
1693
|
+
throw new NeoFaceError(initData.message || "Failed to start login", ErrorType.LOGIN_FAILED);
|
|
1694
|
+
}
|
|
1695
|
+
const taskId = initData.data.task_id;
|
|
1696
|
+
return new Promise((resolve, reject) => {
|
|
1697
|
+
const sseUrl = `${getApiBaseUrl()}/api/v1/auth/recognition/face/external/login/events/${taskId}/`;
|
|
1698
|
+
const eventSource = new EventSource(sseUrl);
|
|
1699
|
+
const timeoutId = setTimeout(() => {
|
|
1700
|
+
eventSource.close();
|
|
1701
|
+
reject(new NeoFaceError("Login timeout", ErrorType.NETWORK));
|
|
1702
|
+
}, 31e4);
|
|
1703
|
+
const cleanup = () => {
|
|
1704
|
+
clearTimeout(timeoutId);
|
|
1705
|
+
eventSource.close();
|
|
1706
|
+
};
|
|
1707
|
+
eventSource.addEventListener("success", (event) => {
|
|
1708
|
+
var _a2, _b, _c, _d, _e, _f;
|
|
1709
|
+
cleanup();
|
|
1710
|
+
try {
|
|
1711
|
+
const data = JSON.parse(event.data);
|
|
1712
|
+
resolve({
|
|
1713
|
+
success: true,
|
|
1714
|
+
accessToken: data.token || data.access_token || "",
|
|
1715
|
+
alias: ((_a2 = data.user) == null ? void 0 : _a2.alias) || data.alias || "",
|
|
1716
|
+
user: {
|
|
1717
|
+
id: ((_b = data.user) == null ? void 0 : _b.id) || "",
|
|
1718
|
+
email: ((_c = data.user) == null ? void 0 : _c.email) || "",
|
|
1719
|
+
role: ((_d = data.user) == null ? void 0 : _d.role) || "",
|
|
1720
|
+
validated: ((_e = data.user) == null ? void 0 : _e.validated) ?? false,
|
|
1721
|
+
active: ((_f = data.user) == null ? void 0 : _f.active) ?? false
|
|
1722
|
+
},
|
|
1723
|
+
person: data.person ? { name: data.person.name || "", birth_date: data.person.birth_date || "" } : void 0,
|
|
1724
|
+
confidence_score: data.confidence_score,
|
|
1725
|
+
recognition_id: data.recognition_id ?? data.history_id
|
|
1726
|
+
});
|
|
1727
|
+
} catch {
|
|
1728
|
+
reject(new NeoFaceError("Erro ao processar resposta de login", ErrorType.API_ERROR));
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
eventSource.addEventListener("failed", (event) => {
|
|
1732
|
+
cleanup();
|
|
1733
|
+
try {
|
|
1734
|
+
const data = JSON.parse(event.data);
|
|
1735
|
+
reject(new NeoFaceError(data.message || "Login failed", ErrorType.LOGIN_FAILED));
|
|
1736
|
+
} catch {
|
|
1737
|
+
reject(new NeoFaceError("Login failed", ErrorType.LOGIN_FAILED));
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
eventSource.addEventListener("error", (event) => {
|
|
1741
|
+
cleanup();
|
|
1742
|
+
try {
|
|
1743
|
+
const data = JSON.parse(event.data || "{}");
|
|
1744
|
+
reject(new NeoFaceError(data.message || "Processing error", ErrorType.API_ERROR));
|
|
1745
|
+
} catch {
|
|
1746
|
+
reject(new NeoFaceError("Processing error", ErrorType.API_ERROR));
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
eventSource.addEventListener("timeout", () => {
|
|
1750
|
+
cleanup();
|
|
1751
|
+
reject(new NeoFaceError("Login timeout", ErrorType.NETWORK));
|
|
1752
|
+
});
|
|
1753
|
+
eventSource.onerror = () => {
|
|
1754
|
+
cleanup();
|
|
1755
|
+
reject(new NeoFaceError("SSE connection error", ErrorType.NETWORK));
|
|
1756
|
+
};
|
|
1757
|
+
});
|
|
1758
|
+
};
|
|
1655
1759
|
const recognizeBiometric = async (image, applicationToken, livenessCheck = true, confidenceThreshold = 0.8) => {
|
|
1656
1760
|
ensureSecureContext();
|
|
1657
1761
|
const controller = createTimeoutController();
|
|
@@ -5134,6 +5238,7 @@ class EmailPasswordModal {
|
|
|
5134
5238
|
<div class="neoface-email-modal-content">
|
|
5135
5239
|
<h2>${this.options.title || "Login com Email e Senha"}</h2>
|
|
5136
5240
|
<p>${this.options.subtitle || "Informe seus dados para login."}</p>
|
|
5241
|
+
<div id="login-error" class="neoface-email-error" style="display:none"></div>
|
|
5137
5242
|
<form id="email-password-form">
|
|
5138
5243
|
<input type="email" id="email" placeholder="Email" required />
|
|
5139
5244
|
<input type="password" id="password" placeholder="Senha" required />
|
|
@@ -5335,6 +5440,18 @@ class EmailPasswordModal {
|
|
|
5335
5440
|
letter-spacing: 0.3px;
|
|
5336
5441
|
}
|
|
5337
5442
|
|
|
5443
|
+
.neoface-email-error {
|
|
5444
|
+
background: rgba(239, 68, 68, 0.15);
|
|
5445
|
+
border: 1px solid rgba(239, 68, 68, 0.4);
|
|
5446
|
+
border-radius: 10px;
|
|
5447
|
+
color: #fca5a5;
|
|
5448
|
+
font-size: 14px;
|
|
5449
|
+
padding: 12px 16px;
|
|
5450
|
+
margin-bottom: 16px;
|
|
5451
|
+
text-align: center;
|
|
5452
|
+
animation: fadeIn 0.2s ease-out;
|
|
5453
|
+
}
|
|
5454
|
+
|
|
5338
5455
|
@media (max-width: 640px) {
|
|
5339
5456
|
.neoface-email-modal-content {
|
|
5340
5457
|
padding: 24px;
|
|
@@ -5352,20 +5469,40 @@ class EmailPasswordModal {
|
|
|
5352
5469
|
event.preventDefault();
|
|
5353
5470
|
const emailInput = this.modalElement.querySelector("#email");
|
|
5354
5471
|
const passwordInput = this.modalElement.querySelector("#password");
|
|
5472
|
+
const submitBtn = this.modalElement.querySelector(
|
|
5473
|
+
".neoface-email-submit-btn"
|
|
5474
|
+
);
|
|
5475
|
+
const errorDiv = this.modalElement.querySelector("#login-error");
|
|
5476
|
+
const showError = (msg) => {
|
|
5477
|
+
errorDiv.textContent = msg;
|
|
5478
|
+
errorDiv.style.display = "block";
|
|
5479
|
+
};
|
|
5480
|
+
errorDiv.style.display = "none";
|
|
5481
|
+
errorDiv.textContent = "";
|
|
5355
5482
|
const email = emailInput.value.trim();
|
|
5356
5483
|
const password = passwordInput.value.trim();
|
|
5357
5484
|
if (!email || !password) {
|
|
5358
|
-
|
|
5359
|
-
new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR)
|
|
5360
|
-
);
|
|
5485
|
+
showError("Preencha o email e a senha para continuar.");
|
|
5361
5486
|
return;
|
|
5362
5487
|
}
|
|
5488
|
+
submitBtn.disabled = true;
|
|
5489
|
+
submitBtn.textContent = "Entrando...";
|
|
5363
5490
|
try {
|
|
5364
5491
|
const result = await loginWithEmail(email, password, this.options.applicationToken);
|
|
5365
5492
|
this.options.onSuccess(result);
|
|
5366
5493
|
this.close();
|
|
5367
5494
|
} catch (error) {
|
|
5368
|
-
|
|
5495
|
+
submitBtn.disabled = false;
|
|
5496
|
+
submitBtn.textContent = "Entrar";
|
|
5497
|
+
const isCredentialError = error instanceof NeoFaceError && (error.type === ErrorType.INVALID_TOKEN || error.type === ErrorType.API_ERROR && error.message.includes("400"));
|
|
5498
|
+
if (isCredentialError) {
|
|
5499
|
+
showError("Email ou senha incorretos. Verifique seus dados e tente novamente.");
|
|
5500
|
+
passwordInput.value = "";
|
|
5501
|
+
passwordInput.focus();
|
|
5502
|
+
} else {
|
|
5503
|
+
showError("Ocorreu um erro inesperado. Tente novamente mais tarde.");
|
|
5504
|
+
this.options.onError(error);
|
|
5505
|
+
}
|
|
5369
5506
|
}
|
|
5370
5507
|
}
|
|
5371
5508
|
}
|
|
@@ -5507,7 +5644,12 @@ async function captureFaceSilently() {
|
|
|
5507
5644
|
}
|
|
5508
5645
|
}).catch((err) => {
|
|
5509
5646
|
cleanup();
|
|
5510
|
-
reject(
|
|
5647
|
+
reject(
|
|
5648
|
+
new NeoFaceError(
|
|
5649
|
+
"Erro ao reproduzir câmera: " + err.message,
|
|
5650
|
+
ErrorType.CAMERA_ERROR
|
|
5651
|
+
)
|
|
5652
|
+
);
|
|
5511
5653
|
});
|
|
5512
5654
|
} else {
|
|
5513
5655
|
video.onloadedmetadata = () => setTimeout(tryCapture, CAMERA_READY_DELAY);
|
|
@@ -5649,7 +5791,10 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
5649
5791
|
if (video.parentElement) {
|
|
5650
5792
|
video.parentElement.removeChild(video);
|
|
5651
5793
|
}
|
|
5652
|
-
throw new NeoFaceError(
|
|
5794
|
+
throw new NeoFaceError(
|
|
5795
|
+
"Rosto não detectado. Posicione seu rosto na frente da câmera.",
|
|
5796
|
+
ErrorType.VALIDATION_ERROR
|
|
5797
|
+
);
|
|
5653
5798
|
}
|
|
5654
5799
|
overlay.updateStatus("capturing");
|
|
5655
5800
|
const canvas = document.createElement("canvas");
|
|
@@ -5687,7 +5832,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
5687
5832
|
);
|
|
5688
5833
|
});
|
|
5689
5834
|
overlay.updateStatus("verifying");
|
|
5690
|
-
const result = await
|
|
5835
|
+
const result = await loginWithBiometricSSE(imageBlob, applicationToken);
|
|
5691
5836
|
if (!result.success) {
|
|
5692
5837
|
throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
|
|
5693
5838
|
}
|
|
@@ -5709,7 +5854,14 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
5709
5854
|
}
|
|
5710
5855
|
}
|
|
5711
5856
|
async function executeBiometricLoginFlow(options) {
|
|
5712
|
-
const {
|
|
5857
|
+
const {
|
|
5858
|
+
applicationToken,
|
|
5859
|
+
onSuccess,
|
|
5860
|
+
onError,
|
|
5861
|
+
onFallbackRequest,
|
|
5862
|
+
onCancel,
|
|
5863
|
+
fastMode = false
|
|
5864
|
+
} = options;
|
|
5713
5865
|
const overlay = new BiometricStatusOverlay();
|
|
5714
5866
|
const fallbackPrompt = new FallbackPrompt();
|
|
5715
5867
|
let attempts = 0;
|
|
@@ -6103,7 +6255,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
|
|
|
6103
6255
|
initializeBiometricDetection,
|
|
6104
6256
|
isAdvancedDetectionAvailable
|
|
6105
6257
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
6106
|
-
const VERSION = "1.
|
|
6258
|
+
const VERSION = "1.15.0";
|
|
6107
6259
|
const RELEASE_DATE = "2026-03-04";
|
|
6108
6260
|
class OnboardingCaptureModal {
|
|
6109
6261
|
constructor(options) {
|