@neofaceid/web-sdk 1.44.0 → 2.0.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.
- package/README.md +27 -24
- package/dist/biometricDetection-8utmtR25.js +154 -0
- package/dist/index.d.ts +83 -327
- package/dist/neoface-id-sdk.es.js +783 -1192
- package/dist/neoface-id-sdk.umd.js +1 -1
- package/dist/qrcode-DYfGiWu6.js +1633 -0
- package/package.json +2 -4
|
@@ -6236,7 +6236,7 @@ const blobToBase64 = (blob) => new Promise((resolve, reject) => {
|
|
|
6236
6236
|
reader.onerror = () => reject(new NeoFaceError("Failed to convert image to base64", ErrorType.CAPTURE_ERROR));
|
|
6237
6237
|
reader.readAsDataURL(blob);
|
|
6238
6238
|
});
|
|
6239
|
-
const registerDocumentByImage = async (personId, jwtToken, images) => {
|
|
6239
|
+
const registerDocumentByImage$1 = async (personId, jwtToken, images) => {
|
|
6240
6240
|
var _a2, _b;
|
|
6241
6241
|
ensureSecureContext();
|
|
6242
6242
|
if (!personId) {
|
|
@@ -6409,7 +6409,7 @@ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty(
|
|
|
6409
6409
|
refreshSession,
|
|
6410
6410
|
registerApplication,
|
|
6411
6411
|
registerBiometric,
|
|
6412
|
-
registerDocumentByImage,
|
|
6412
|
+
registerDocumentByImage: registerDocumentByImage$1,
|
|
6413
6413
|
registerPersonWithBiometric,
|
|
6414
6414
|
registerPersonWithoutFace,
|
|
6415
6415
|
requestDeviceEnrollmentToken,
|
|
@@ -7815,17 +7815,17 @@ function BiometricRegistrationModal({
|
|
|
7815
7815
|
}
|
|
7816
7816
|
let isRunning = true;
|
|
7817
7817
|
let lastDetectionTime = 0;
|
|
7818
|
-
const
|
|
7818
|
+
const detectFace = async () => {
|
|
7819
7819
|
if (!isRunning || state.status !== "liveness") {
|
|
7820
7820
|
return;
|
|
7821
7821
|
}
|
|
7822
7822
|
if (isCapturingRef.current || currentStepRef.current === "complete") {
|
|
7823
|
-
requestAnimationFrame(
|
|
7823
|
+
requestAnimationFrame(detectFace);
|
|
7824
7824
|
return;
|
|
7825
7825
|
}
|
|
7826
7826
|
const now = Date.now();
|
|
7827
7827
|
if (now - lastDetectionTime < CALIBRATION.DETECTION_INTERVAL) {
|
|
7828
|
-
requestAnimationFrame(
|
|
7828
|
+
requestAnimationFrame(detectFace);
|
|
7829
7829
|
return;
|
|
7830
7830
|
}
|
|
7831
7831
|
lastDetectionTime = now;
|
|
@@ -7849,7 +7849,7 @@ function BiometricRegistrationModal({
|
|
|
7849
7849
|
}
|
|
7850
7850
|
if (positionStatus !== "ok") {
|
|
7851
7851
|
manageHoldAndCapture(false);
|
|
7852
|
-
requestAnimationFrame(
|
|
7852
|
+
requestAnimationFrame(detectFace);
|
|
7853
7853
|
return;
|
|
7854
7854
|
}
|
|
7855
7855
|
}
|
|
@@ -7874,7 +7874,7 @@ function BiometricRegistrationModal({
|
|
|
7874
7874
|
} catch (error) {
|
|
7875
7875
|
}
|
|
7876
7876
|
if (isRunning && state.status === "liveness") {
|
|
7877
|
-
requestAnimationFrame(
|
|
7877
|
+
requestAnimationFrame(detectFace);
|
|
7878
7878
|
}
|
|
7879
7879
|
};
|
|
7880
7880
|
const startDetection = () => {
|
|
@@ -7884,7 +7884,7 @@ function BiometricRegistrationModal({
|
|
|
7884
7884
|
clearTimeout(watchdogTimerRef.current);
|
|
7885
7885
|
watchdogTimerRef.current = null;
|
|
7886
7886
|
}
|
|
7887
|
-
|
|
7887
|
+
detectFace();
|
|
7888
7888
|
};
|
|
7889
7889
|
const video = videoRef.current;
|
|
7890
7890
|
if (video && video.readyState >= 2) {
|
|
@@ -8898,8 +8898,8 @@ function BiometricRegistrationModal({
|
|
|
8898
8898
|
/* @__PURE__ */ jsx("canvas", { ref: canvasRef })
|
|
8899
8899
|
] });
|
|
8900
8900
|
}
|
|
8901
|
-
const VERSION = "
|
|
8902
|
-
const RELEASE_DATE = "2026-09-
|
|
8901
|
+
const VERSION = "2.0.0";
|
|
8902
|
+
const RELEASE_DATE = "2026-09-19";
|
|
8903
8903
|
const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
|
|
8904
8904
|
const CONSENT_TRAIL_MAX_LIMIT = 100;
|
|
8905
8905
|
async function hashString(inputStr) {
|
|
@@ -10697,704 +10697,106 @@ function CameraOvalGuide({
|
|
|
10697
10697
|
}
|
|
10698
10698
|
);
|
|
10699
10699
|
}
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS;
|
|
10711
|
-
const zIndex = options.zIndex ?? 10001;
|
|
10712
|
-
const overlayId = `${OVERLAY_ID_PREFIX}${Math.random().toString(36).slice(2, 9)}`;
|
|
10713
|
-
let interval = null;
|
|
10714
|
-
let evalTimer = null;
|
|
10715
|
-
let flashOn = false;
|
|
10716
|
-
let lastBrightness = 0;
|
|
10717
|
-
let darkStreak = 0;
|
|
10718
|
-
let lastToggleAt = 0;
|
|
10719
|
-
let preFlashBrightness = null;
|
|
10720
|
-
let overlayEl = null;
|
|
10721
|
-
let scratchCanvas = null;
|
|
10722
|
-
const showOverlay = () => {
|
|
10723
|
-
if (overlayEl || typeof document === "undefined") return;
|
|
10724
|
-
overlayEl = document.createElement("div");
|
|
10725
|
-
overlayEl.id = overlayId;
|
|
10726
|
-
overlayEl.setAttribute("aria-hidden", "true");
|
|
10727
|
-
overlayEl.style.cssText = `
|
|
10728
|
-
position: fixed; inset: 0; background: #FFFFFF;
|
|
10729
|
-
z-index: ${zIndex}; pointer-events: none;
|
|
10730
|
-
opacity: 0; transition: opacity 200ms ease-out;
|
|
10731
|
-
`;
|
|
10732
|
-
document.body.appendChild(overlayEl);
|
|
10733
|
-
requestAnimationFrame(() => {
|
|
10734
|
-
if (overlayEl) overlayEl.style.opacity = "1";
|
|
10735
|
-
});
|
|
10736
|
-
};
|
|
10737
|
-
const hideOverlay = () => {
|
|
10738
|
-
if (!overlayEl) return;
|
|
10739
|
-
const el = overlayEl;
|
|
10740
|
-
overlayEl = null;
|
|
10741
|
-
el.style.opacity = "0";
|
|
10742
|
-
setTimeout(() => {
|
|
10743
|
-
if (el.parentNode) el.parentNode.removeChild(el);
|
|
10744
|
-
}, 220);
|
|
10745
|
-
};
|
|
10746
|
-
const setFlashOn = (next) => {
|
|
10747
|
-
if (flashOn === next) return;
|
|
10748
|
-
flashOn = next;
|
|
10749
|
-
if (next) showOverlay();
|
|
10750
|
-
else hideOverlay();
|
|
10751
|
-
if (options.onChange) options.onChange(next, lastBrightness);
|
|
10752
|
-
};
|
|
10753
|
-
const tick = () => {
|
|
10754
|
-
if (typeof document !== "undefined" && !scratchCanvas) {
|
|
10755
|
-
scratchCanvas = document.createElement("canvas");
|
|
10756
|
-
}
|
|
10757
|
-
const b = measureFrameBrightness(getVideo(), {
|
|
10758
|
-
scratch: scratchCanvas ?? void 0
|
|
10759
|
-
});
|
|
10760
|
-
lastBrightness = b;
|
|
10761
|
-
const now = Date.now();
|
|
10762
|
-
if (!flashOn) {
|
|
10763
|
-
if (b > 0 && b < darkThreshold) darkStreak += 1;
|
|
10764
|
-
else darkStreak = 0;
|
|
10765
|
-
if (darkStreak >= 2 && now - lastToggleAt >= minToggleMs) {
|
|
10766
|
-
preFlashBrightness = b;
|
|
10767
|
-
lastToggleAt = now;
|
|
10768
|
-
darkStreak = 0;
|
|
10769
|
-
setFlashOn(true);
|
|
10770
|
-
evalTimer = setTimeout(() => {
|
|
10771
|
-
const after = measureFrameBrightness(getVideo(), {
|
|
10772
|
-
scratch: scratchCanvas ?? void 0
|
|
10773
|
-
});
|
|
10774
|
-
const gained = after - (preFlashBrightness ?? 0);
|
|
10775
|
-
if (gained < improvementTarget) {
|
|
10776
|
-
lastToggleAt = Date.now();
|
|
10777
|
-
setFlashOn(false);
|
|
10778
|
-
}
|
|
10779
|
-
preFlashBrightness = null;
|
|
10780
|
-
}, EVALUATION_DELAY_MS);
|
|
10700
|
+
class BiometricStatusOverlay {
|
|
10701
|
+
constructor() {
|
|
10702
|
+
__publicField(this, "container", null);
|
|
10703
|
+
__publicField(this, "currentStatus", "preparing");
|
|
10704
|
+
}
|
|
10705
|
+
getStyles() {
|
|
10706
|
+
return `
|
|
10707
|
+
@keyframes neofaceid-fadeIn {
|
|
10708
|
+
from { opacity: 0; transform: scale(0.9); }
|
|
10709
|
+
to { opacity: 1; transform: scale(1); }
|
|
10781
10710
|
}
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
if (options.enabled === false) return;
|
|
10787
|
-
if (interval) return;
|
|
10788
|
-
tick();
|
|
10789
|
-
interval = setInterval(tick, sampleIntervalMs);
|
|
10790
|
-
},
|
|
10791
|
-
stop() {
|
|
10792
|
-
if (interval) {
|
|
10793
|
-
clearInterval(interval);
|
|
10794
|
-
interval = null;
|
|
10711
|
+
|
|
10712
|
+
@keyframes neofaceid-fadeOut {
|
|
10713
|
+
from { opacity: 1; transform: scale(1); }
|
|
10714
|
+
to { opacity: 0; transform: scale(0.9); }
|
|
10795
10715
|
}
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10716
|
+
|
|
10717
|
+
@keyframes neofaceid-pulse {
|
|
10718
|
+
0%, 100% {
|
|
10719
|
+
transform: scale(1);
|
|
10720
|
+
opacity: 1;
|
|
10721
|
+
}
|
|
10722
|
+
50% {
|
|
10723
|
+
transform: scale(1.05);
|
|
10724
|
+
opacity: 0.9;
|
|
10725
|
+
}
|
|
10799
10726
|
}
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
this.stop();
|
|
10806
|
-
scratchCanvas = null;
|
|
10807
|
-
},
|
|
10808
|
-
get flashOn() {
|
|
10809
|
-
return flashOn;
|
|
10810
|
-
},
|
|
10811
|
-
get lastBrightness() {
|
|
10812
|
-
return lastBrightness;
|
|
10813
|
-
}
|
|
10814
|
-
};
|
|
10815
|
-
}
|
|
10816
|
-
class BiometricCaptureModal {
|
|
10817
|
-
constructor(options) {
|
|
10818
|
-
__publicField(this, "modal", null);
|
|
10819
|
-
__publicField(this, "video", null);
|
|
10820
|
-
__publicField(this, "canvas", null);
|
|
10821
|
-
__publicField(this, "stream", null);
|
|
10822
|
-
__publicField(this, "options");
|
|
10823
|
-
__publicField(this, "countdownInterval", null);
|
|
10824
|
-
__publicField(this, "isCapturing", false);
|
|
10825
|
-
__publicField(this, "flashController", null);
|
|
10826
|
-
this.options = options;
|
|
10827
|
-
}
|
|
10828
|
-
/**
|
|
10829
|
-
* Abre o modal de captura biométrica
|
|
10830
|
-
*/
|
|
10831
|
-
async open() {
|
|
10832
|
-
try {
|
|
10833
|
-
await this.createModal();
|
|
10834
|
-
await this.initializeCamera();
|
|
10835
|
-
this.enableCaptureButton();
|
|
10836
|
-
if (this.options.autoLighting !== false) {
|
|
10837
|
-
this.flashController = createScreenFlashController(() => this.video);
|
|
10838
|
-
this.flashController.start();
|
|
10727
|
+
|
|
10728
|
+
@keyframes neofaceid-scaleIn {
|
|
10729
|
+
0% { transform: scale(0); opacity: 0; }
|
|
10730
|
+
50% { transform: scale(1.1); }
|
|
10731
|
+
100% { transform: scale(1); opacity: 1; }
|
|
10839
10732
|
}
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
)
|
|
10846
|
-
);
|
|
10847
|
-
}
|
|
10848
|
-
}
|
|
10849
|
-
/**
|
|
10850
|
-
* Fecha o modal e limpa recursos
|
|
10851
|
-
*/
|
|
10852
|
-
close() {
|
|
10853
|
-
this.cleanup();
|
|
10854
|
-
if (this.modal) {
|
|
10855
|
-
document.body.removeChild(this.modal);
|
|
10856
|
-
this.modal = null;
|
|
10857
|
-
}
|
|
10858
|
-
}
|
|
10859
|
-
/**
|
|
10860
|
-
* Cria a estrutura HTML do modal
|
|
10861
|
-
*/
|
|
10862
|
-
async createModal() {
|
|
10863
|
-
this.modal = document.createElement("div");
|
|
10864
|
-
this.modal.className = "neofaceid-root neofaceid-biometric-modal";
|
|
10865
|
-
const title = this.options.title || this.getDefaultTitle();
|
|
10866
|
-
const subtitle = this.options.subtitle || this.getDefaultSubtitle();
|
|
10867
|
-
this.modal.innerHTML = `
|
|
10868
|
-
<div class="neofaceid-modal-overlay">
|
|
10869
|
-
<div class="neofaceid-modal-content">
|
|
10870
|
-
<div class="neofaceid-modal-header">
|
|
10871
|
-
<h2>${title}</h2>
|
|
10872
|
-
<button class="neofaceid-close-btn" type="button" aria-label="Fechar">×</button>
|
|
10873
|
-
</div>
|
|
10874
|
-
<div class="neofaceid-modal-body">
|
|
10875
|
-
<p class="neofaceid-subtitle">${subtitle}</p>
|
|
10876
|
-
<div class="neofaceid-camera-container">
|
|
10877
|
-
<video class="neofaceid-video" autoplay muted playsinline></video>
|
|
10878
|
-
<canvas class="neofaceid-canvas" style="display: none;"></canvas>
|
|
10879
|
-
<div class="neofaceid-overlay">
|
|
10880
|
-
<div class="neofaceid-frame"></div>
|
|
10881
|
-
</div>
|
|
10882
|
-
</div>
|
|
10883
|
-
<div class="neofaceid-status">
|
|
10884
|
-
<p class="neofaceid-status-text">Posicione-se na frente da câmera</p>
|
|
10885
|
-
</div>
|
|
10886
|
-
</div>
|
|
10887
|
-
<div class="neofaceid-modal-footer">
|
|
10888
|
-
<button class="neofaceid-cancel-btn" type="button">Cancelar</button>
|
|
10889
|
-
<button class="neofaceid-capture-btn" type="button" disabled>Capturar</button>
|
|
10890
|
-
</div>
|
|
10891
|
-
<div class="neofaceid-seal-row">
|
|
10892
|
-
${renderBrandFooter()}
|
|
10893
|
-
</div>
|
|
10894
|
-
</div>
|
|
10895
|
-
</div>
|
|
10896
|
-
`;
|
|
10897
|
-
injectThemeStyles();
|
|
10898
|
-
this.addStyles();
|
|
10899
|
-
this.addEventListeners();
|
|
10900
|
-
document.body.appendChild(this.modal);
|
|
10901
|
-
}
|
|
10902
|
-
/**
|
|
10903
|
-
* Inicializa a câmera
|
|
10904
|
-
*/
|
|
10905
|
-
async initializeCamera() {
|
|
10906
|
-
if (!this.modal) throw new Error("Modal não inicializado");
|
|
10907
|
-
this.video = this.modal.querySelector(".neofaceid-video");
|
|
10908
|
-
this.canvas = this.modal.querySelector(".neofaceid-canvas");
|
|
10909
|
-
if (!this.video || !this.canvas) {
|
|
10910
|
-
throw new Error("Elementos de vídeo ou canvas não encontrados");
|
|
10911
|
-
}
|
|
10912
|
-
try {
|
|
10913
|
-
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
10914
|
-
video: {
|
|
10915
|
-
width: { ideal: 640 },
|
|
10916
|
-
height: { ideal: 480 },
|
|
10917
|
-
facingMode: "user"
|
|
10918
|
-
},
|
|
10919
|
-
audio: false
|
|
10920
|
-
});
|
|
10921
|
-
this.video.srcObject = this.stream;
|
|
10922
|
-
await this.video.play();
|
|
10923
|
-
this.canvas.width = this.video.videoWidth;
|
|
10924
|
-
this.canvas.height = this.video.videoHeight;
|
|
10925
|
-
} catch (error) {
|
|
10926
|
-
throw new Error(`Não foi possível acessar a câmera: ${error.message}`);
|
|
10927
|
-
}
|
|
10928
|
-
}
|
|
10929
|
-
/**
|
|
10930
|
-
* NEO-413 · US14.6 (item 18) — a contagem "3, 2, 1" foi removida.
|
|
10931
|
-
* Agora habilita direto o botão "Capturar" assim que a câmera fica pronta.
|
|
10932
|
-
* Vivacidade fica com o desafio `runLivenessChallenge` (NEO-408), servidor decide.
|
|
10933
|
-
*/
|
|
10934
|
-
enableCaptureButton() {
|
|
10935
|
-
if (!this.modal) return;
|
|
10936
|
-
const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
|
|
10937
|
-
const statusText = this.modal.querySelector(".neofaceid-status-text");
|
|
10938
|
-
if (captureBtn) {
|
|
10939
|
-
captureBtn.disabled = false;
|
|
10940
|
-
captureBtn.textContent = "Capturar";
|
|
10941
|
-
}
|
|
10942
|
-
if (statusText) {
|
|
10943
|
-
statusText.textContent = "Pronto para capturar";
|
|
10944
|
-
}
|
|
10945
|
-
if (this.options.mode === "auto") {
|
|
10946
|
-
setTimeout(() => this.captureImage(), 300);
|
|
10947
|
-
}
|
|
10948
|
-
}
|
|
10949
|
-
/**
|
|
10950
|
-
* Captura a imagem da câmera
|
|
10951
|
-
*/
|
|
10952
|
-
async captureImage() {
|
|
10953
|
-
if (this.isCapturing || !this.video || !this.canvas) return;
|
|
10954
|
-
this.isCapturing = true;
|
|
10955
|
-
try {
|
|
10956
|
-
const context = this.canvas.getContext("2d");
|
|
10957
|
-
if (!context) throw new Error("Não foi possível obter contexto do canvas");
|
|
10958
|
-
context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
|
10959
|
-
const imageData = this.canvas.toDataURL("image/jpeg", 0.8);
|
|
10960
|
-
const base64Data = imageData.split(",")[1];
|
|
10961
|
-
const dataUrl = `data:image/jpeg;base64,${base64Data}`;
|
|
10962
|
-
const detectedType = this.options.mode === "auto" ? await this.detectBiometricType(base64Data) : this.options.mode;
|
|
10963
|
-
this.close();
|
|
10964
|
-
this.options.onSuccess(dataUrl, detectedType);
|
|
10965
|
-
} catch (error) {
|
|
10966
|
-
this.options.onError(
|
|
10967
|
-
new NeoFaceError(
|
|
10968
|
-
`Erro ao capturar imagem: ${error.message}`,
|
|
10969
|
-
ErrorType.CAPTURE_ERROR
|
|
10970
|
-
)
|
|
10971
|
-
);
|
|
10972
|
-
} finally {
|
|
10973
|
-
this.isCapturing = false;
|
|
10974
|
-
}
|
|
10975
|
-
}
|
|
10976
|
-
/**
|
|
10977
|
-
* Detecta o tipo biométrico na imagem (face ou mão)
|
|
10978
|
-
*/
|
|
10979
|
-
async detectBiometricType(imageData) {
|
|
10980
|
-
try {
|
|
10981
|
-
const { detectBiometricType: detectBiometricType2 } = await Promise.resolve().then(() => biometricDetection);
|
|
10982
|
-
const result = await detectBiometricType2(imageData);
|
|
10983
|
-
if (result.type !== "unknown" && result.confidence > 0.6) {
|
|
10984
|
-
return result.type;
|
|
10985
|
-
}
|
|
10986
|
-
return "face";
|
|
10987
|
-
} catch (error) {
|
|
10988
|
-
console.warn("Erro na detecção automática, usando face como padrão:", error);
|
|
10989
|
-
return "face";
|
|
10990
|
-
}
|
|
10991
|
-
}
|
|
10992
|
-
/**
|
|
10993
|
-
* Adiciona event listeners aos elementos do modal
|
|
10994
|
-
*/
|
|
10995
|
-
addEventListeners() {
|
|
10996
|
-
if (!this.modal) return;
|
|
10997
|
-
const closeBtn = this.modal.querySelector(".neofaceid-close-btn");
|
|
10998
|
-
const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
|
|
10999
|
-
const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
|
|
11000
|
-
closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
|
|
11001
|
-
var _a2, _b;
|
|
11002
|
-
this.close();
|
|
11003
|
-
(_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
|
|
11004
|
-
});
|
|
11005
|
-
cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
|
|
11006
|
-
var _a2, _b;
|
|
11007
|
-
this.close();
|
|
11008
|
-
(_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
|
|
11009
|
-
});
|
|
11010
|
-
captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
|
|
11011
|
-
this.captureImage();
|
|
11012
|
-
});
|
|
11013
|
-
this.modal.addEventListener("click", (e) => {
|
|
11014
|
-
var _a2, _b, _c;
|
|
11015
|
-
if (e.target === ((_a2 = this.modal) == null ? void 0 : _a2.querySelector(".neofaceid-modal-overlay"))) {
|
|
11016
|
-
this.close();
|
|
11017
|
-
(_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
|
|
11018
|
-
}
|
|
11019
|
-
});
|
|
11020
|
-
}
|
|
11021
|
-
/**
|
|
11022
|
-
* Limpa recursos (câmera, intervalos, etc.)
|
|
11023
|
-
*/
|
|
11024
|
-
cleanup() {
|
|
11025
|
-
if (this.flashController) {
|
|
11026
|
-
this.flashController.destroy();
|
|
11027
|
-
this.flashController = null;
|
|
11028
|
-
}
|
|
11029
|
-
if (this.stream) {
|
|
11030
|
-
this.stream.getTracks().forEach((track) => track.stop());
|
|
11031
|
-
this.stream = null;
|
|
11032
|
-
}
|
|
11033
|
-
if (this.countdownInterval) {
|
|
11034
|
-
clearInterval(this.countdownInterval);
|
|
11035
|
-
this.countdownInterval = null;
|
|
11036
|
-
}
|
|
11037
|
-
this.video = null;
|
|
11038
|
-
this.canvas = null;
|
|
11039
|
-
this.isCapturing = false;
|
|
11040
|
-
}
|
|
11041
|
-
/**
|
|
11042
|
-
* Retorna o título padrão baseado no modo
|
|
11043
|
-
*/
|
|
11044
|
-
getDefaultTitle() {
|
|
11045
|
-
switch (this.options.mode) {
|
|
11046
|
-
case "face":
|
|
11047
|
-
return "Autenticação Facial";
|
|
11048
|
-
case "hand":
|
|
11049
|
-
return "Autenticação por Mão";
|
|
11050
|
-
case "auto":
|
|
11051
|
-
return "Autenticação Biométrica";
|
|
11052
|
-
default:
|
|
11053
|
-
return "Captura Biométrica";
|
|
11054
|
-
}
|
|
11055
|
-
}
|
|
11056
|
-
/**
|
|
11057
|
-
* Retorna o subtítulo padrão baseado no modo
|
|
11058
|
-
*/
|
|
11059
|
-
getDefaultSubtitle() {
|
|
11060
|
-
switch (this.options.mode) {
|
|
11061
|
-
case "face":
|
|
11062
|
-
return "Posicione seu rosto dentro do quadro e aguarde a captura automática.";
|
|
11063
|
-
case "hand":
|
|
11064
|
-
return "Posicione sua mão dentro do quadro e aguarde a captura automática.";
|
|
11065
|
-
case "auto":
|
|
11066
|
-
return "Posicione seu rosto ou mão dentro do quadro para autenticação automática.";
|
|
11067
|
-
default:
|
|
11068
|
-
return "Posicione-se dentro do quadro para captura.";
|
|
11069
|
-
}
|
|
11070
|
-
}
|
|
11071
|
-
/**
|
|
11072
|
-
* Adiciona estilos CSS ao modal
|
|
11073
|
-
*/
|
|
11074
|
-
addStyles() {
|
|
11075
|
-
const styleId = "neofaceid-biometric-modal-styles";
|
|
11076
|
-
const existingStyles = document.getElementById(styleId);
|
|
11077
|
-
if (existingStyles) {
|
|
11078
|
-
existingStyles.remove();
|
|
11079
|
-
}
|
|
11080
|
-
injectScopedStyles(styleId, `
|
|
11081
|
-
.neofaceid-biometric-modal {
|
|
11082
|
-
position: fixed;
|
|
11083
|
-
top: 0;
|
|
11084
|
-
left: 0;
|
|
11085
|
-
width: 100%;
|
|
11086
|
-
height: 100%;
|
|
11087
|
-
z-index: 10000;
|
|
11088
|
-
font-family: inherit;
|
|
10733
|
+
|
|
10734
|
+
@keyframes neofaceid-errorShake {
|
|
10735
|
+
0%, 100% { transform: translateX(0); }
|
|
10736
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
|
10737
|
+
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
|
11089
10738
|
}
|
|
11090
10739
|
|
|
11091
|
-
.neofaceid-
|
|
11092
|
-
position:
|
|
10740
|
+
.neofaceid-overlay-container {
|
|
10741
|
+
position: fixed;
|
|
11093
10742
|
top: 0;
|
|
11094
10743
|
left: 0;
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
background: rgba(0, 0, 0, 0.8);
|
|
10744
|
+
right: 0;
|
|
10745
|
+
bottom: 0;
|
|
11098
10746
|
display: flex;
|
|
11099
10747
|
align-items: center;
|
|
11100
10748
|
justify-content: center;
|
|
11101
|
-
|
|
11102
|
-
|
|
10749
|
+
z-index: 10000;
|
|
10750
|
+
pointer-events: none;
|
|
10751
|
+
animation: neofaceid-fadeIn 0.3s ease-out;
|
|
10752
|
+
font-family: inherit;
|
|
11103
10753
|
}
|
|
11104
10754
|
|
|
11105
|
-
.neofaceid-
|
|
11106
|
-
|
|
11107
|
-
border-radius: 12px;
|
|
11108
|
-
max-width: 600px;
|
|
11109
|
-
width: 100%;
|
|
11110
|
-
max-height: 90vh;
|
|
11111
|
-
overflow: hidden;
|
|
11112
|
-
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
|
10755
|
+
.neofaceid-overlay-container.fade-out {
|
|
10756
|
+
animation: neofaceid-fadeOut 0.3s ease-out forwards;
|
|
11113
10757
|
}
|
|
11114
10758
|
|
|
11115
|
-
.neofaceid-
|
|
11116
|
-
padding: 20px;
|
|
11117
|
-
border-bottom: 1px solid #e0e0e0;
|
|
10759
|
+
.neofaceid-overlay-content {
|
|
11118
10760
|
display: flex;
|
|
11119
|
-
|
|
10761
|
+
flex-direction: column;
|
|
11120
10762
|
align-items: center;
|
|
10763
|
+
justify-content: center;
|
|
10764
|
+
gap: 24px;
|
|
10765
|
+
pointer-events: auto;
|
|
11121
10766
|
}
|
|
11122
10767
|
|
|
11123
|
-
.neofaceid-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
color: #333;
|
|
11128
|
-
}
|
|
11129
|
-
|
|
11130
|
-
.neofaceid-close-btn {
|
|
11131
|
-
background: none;
|
|
11132
|
-
border: none;
|
|
11133
|
-
font-size: 22px;
|
|
11134
|
-
cursor: pointer;
|
|
11135
|
-
color: #666;
|
|
11136
|
-
padding: 0;
|
|
11137
|
-
min-width: 44px;
|
|
11138
|
-
min-height: 44px;
|
|
10768
|
+
.neofaceid-visual-wrapper {
|
|
10769
|
+
position: relative;
|
|
10770
|
+
width: 100px;
|
|
10771
|
+
height: 100px;
|
|
11139
10772
|
display: flex;
|
|
11140
10773
|
align-items: center;
|
|
11141
10774
|
justify-content: center;
|
|
11142
|
-
border-radius: 22px;
|
|
11143
|
-
transition: background-color 0.2s;
|
|
11144
|
-
}
|
|
11145
|
-
.neofaceid-close-btn:focus-visible {
|
|
11146
|
-
outline: 2px solid var(--nfid-focus-ring);
|
|
11147
|
-
outline-offset: 2px;
|
|
11148
10775
|
}
|
|
11149
10776
|
|
|
11150
|
-
.neofaceid-
|
|
11151
|
-
|
|
10777
|
+
.neofaceid-logo {
|
|
10778
|
+
width: 80px;
|
|
10779
|
+
height: 80px;
|
|
10780
|
+
object-fit: contain;
|
|
10781
|
+
transition: all 0.5s ease;
|
|
10782
|
+
filter: drop-shadow(0 4px 12px rgba(0, 89, 196, 0.3));
|
|
11152
10783
|
}
|
|
11153
10784
|
|
|
11154
|
-
|
|
11155
|
-
|
|
10785
|
+
/* Cores por estado */
|
|
10786
|
+
.neofaceid-state-preparing .neofaceid-logo { filter: drop-shadow(0 0 15px rgba(0, 89, 196, 0.4)); }
|
|
10787
|
+
.neofaceid-state-detecting .neofaceid-logo { filter: hue-rotate(180deg) drop-shadow(0 0 15px rgba(59, 130, 246, 0.6)); }
|
|
10788
|
+
.neofaceid-state-waiting-for-face .neofaceid-logo { filter: hue-rotate(30deg) drop-shadow(0 0 15px rgba(251, 146, 60, 0.6)); }
|
|
10789
|
+
.neofaceid-state-capturing .neofaceid-logo { filter: hue-rotate(45deg) drop-shadow(0 0 15px rgba(245, 158, 11, 0.6)); }
|
|
10790
|
+
.neofaceid-state-verifying .neofaceid-logo { filter: hue-rotate(90deg) drop-shadow(0 0 15px rgba(34, 197, 94, 0.6)); }
|
|
10791
|
+
|
|
10792
|
+
.neofaceid-logo.pulsing {
|
|
10793
|
+
animation: neofaceid-pulse 2s ease-in-out infinite;
|
|
11156
10794
|
}
|
|
11157
10795
|
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
line-height: 1.4;
|
|
11163
|
-
}
|
|
11164
|
-
|
|
11165
|
-
.neofaceid-camera-container {
|
|
11166
|
-
position: relative;
|
|
11167
|
-
background: #000;
|
|
11168
|
-
border-radius: 8px;
|
|
11169
|
-
overflow: hidden;
|
|
11170
|
-
aspect-ratio: 4/3;
|
|
11171
|
-
margin-bottom: 20px;
|
|
11172
|
-
}
|
|
11173
|
-
|
|
11174
|
-
.neofaceid-video {
|
|
11175
|
-
width: 100%;
|
|
11176
|
-
height: 100%;
|
|
11177
|
-
object-fit: cover;
|
|
11178
|
-
}
|
|
11179
|
-
|
|
11180
|
-
.neofaceid-canvas {
|
|
11181
|
-
position: absolute;
|
|
11182
|
-
top: 0;
|
|
11183
|
-
left: 0;
|
|
11184
|
-
}
|
|
11185
|
-
|
|
11186
|
-
.neofaceid-overlay {
|
|
11187
|
-
position: absolute;
|
|
11188
|
-
top: 0;
|
|
11189
|
-
left: 0;
|
|
11190
|
-
width: 100%;
|
|
11191
|
-
height: 100%;
|
|
11192
|
-
display: flex;
|
|
11193
|
-
align-items: center;
|
|
11194
|
-
justify-content: center;
|
|
11195
|
-
}
|
|
11196
|
-
|
|
11197
|
-
.neofaceid-frame {
|
|
11198
|
-
width: 200px;
|
|
11199
|
-
height: 200px;
|
|
11200
|
-
border: 3px solid #0E9F6E;
|
|
11201
|
-
border-radius: 50%;
|
|
11202
|
-
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
|
|
11203
|
-
animation: pulse 2s infinite;
|
|
11204
|
-
}
|
|
11205
|
-
|
|
11206
|
-
.neofaceid-seal-row {
|
|
11207
|
-
display: flex;
|
|
11208
|
-
justify-content: center;
|
|
11209
|
-
padding: 12px 20px 16px;
|
|
11210
|
-
border-top: 1px solid var(--nfid-line, #E6ECF4);
|
|
11211
|
-
}
|
|
11212
|
-
.neofaceid-seal {
|
|
11213
|
-
font-size: 10px;
|
|
11214
|
-
font-weight: 500;
|
|
11215
|
-
letter-spacing: 0.06em;
|
|
11216
|
-
text-transform: uppercase;
|
|
11217
|
-
color: var(--nfid-text-subtle, #617489);
|
|
11218
|
-
}
|
|
11219
|
-
|
|
11220
|
-
.neofaceid-status {
|
|
11221
|
-
text-align: center;
|
|
11222
|
-
margin-bottom: 20px;
|
|
11223
|
-
}
|
|
11224
|
-
|
|
11225
|
-
.neofaceid-status-text {
|
|
11226
|
-
margin: 0;
|
|
11227
|
-
color: #666;
|
|
11228
|
-
font-size: 16px;
|
|
11229
|
-
}
|
|
11230
|
-
|
|
11231
|
-
.neofaceid-modal-footer {
|
|
11232
|
-
padding: 20px;
|
|
11233
|
-
border-top: 1px solid #e0e0e0;
|
|
11234
|
-
display: flex;
|
|
11235
|
-
gap: 12px;
|
|
11236
|
-
justify-content: flex-end;
|
|
11237
|
-
}
|
|
11238
|
-
|
|
11239
|
-
.neofaceid-cancel-btn,
|
|
11240
|
-
.neofaceid-capture-btn {
|
|
11241
|
-
padding: 12px 24px;
|
|
11242
|
-
border: none;
|
|
11243
|
-
border-radius: 6px;
|
|
11244
|
-
font-size: 16px;
|
|
11245
|
-
font-weight: 500;
|
|
11246
|
-
cursor: pointer;
|
|
11247
|
-
transition: all 0.2s;
|
|
11248
|
-
}
|
|
11249
|
-
|
|
11250
|
-
.neofaceid-cancel-btn {
|
|
11251
|
-
background: #f5f5f5;
|
|
11252
|
-
color: #666;
|
|
11253
|
-
}
|
|
11254
|
-
|
|
11255
|
-
.neofaceid-cancel-btn:hover {
|
|
11256
|
-
background: #e0e0e0;
|
|
11257
|
-
}
|
|
11258
|
-
|
|
11259
|
-
.neofaceid-capture-btn {
|
|
11260
|
-
background: #0E9F6E;
|
|
11261
|
-
color: white;
|
|
11262
|
-
}
|
|
11263
|
-
|
|
11264
|
-
.neofaceid-capture-btn:hover:not(:disabled) {
|
|
11265
|
-
background: #45a049;
|
|
11266
|
-
}
|
|
11267
|
-
|
|
11268
|
-
.neofaceid-capture-btn:disabled {
|
|
11269
|
-
background: #ccc;
|
|
11270
|
-
cursor: not-allowed;
|
|
11271
|
-
}
|
|
11272
|
-
|
|
11273
|
-
@keyframes pulse {
|
|
11274
|
-
0% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
|
|
11275
|
-
50% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0.1); }
|
|
11276
|
-
100% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
|
|
11277
|
-
}
|
|
11278
|
-
|
|
11279
|
-
@media (max-width: 640px) {
|
|
11280
|
-
.neofaceid-modal-overlay {
|
|
11281
|
-
padding: 10px;
|
|
11282
|
-
}
|
|
11283
|
-
|
|
11284
|
-
.neofaceid-modal-header,
|
|
11285
|
-
.neofaceid-modal-body,
|
|
11286
|
-
.neofaceid-modal-footer {
|
|
11287
|
-
padding: 15px;
|
|
11288
|
-
}
|
|
11289
|
-
|
|
11290
|
-
.neofaceid-frame {
|
|
11291
|
-
width: 150px;
|
|
11292
|
-
height: 150px;
|
|
11293
|
-
}
|
|
11294
|
-
}
|
|
11295
|
-
`);
|
|
11296
|
-
}
|
|
11297
|
-
}
|
|
11298
|
-
class BiometricStatusOverlay {
|
|
11299
|
-
constructor() {
|
|
11300
|
-
__publicField(this, "container", null);
|
|
11301
|
-
__publicField(this, "currentStatus", "preparing");
|
|
11302
|
-
}
|
|
11303
|
-
getStyles() {
|
|
11304
|
-
return `
|
|
11305
|
-
@keyframes neofaceid-fadeIn {
|
|
11306
|
-
from { opacity: 0; transform: scale(0.9); }
|
|
11307
|
-
to { opacity: 1; transform: scale(1); }
|
|
11308
|
-
}
|
|
11309
|
-
|
|
11310
|
-
@keyframes neofaceid-fadeOut {
|
|
11311
|
-
from { opacity: 1; transform: scale(1); }
|
|
11312
|
-
to { opacity: 0; transform: scale(0.9); }
|
|
11313
|
-
}
|
|
11314
|
-
|
|
11315
|
-
@keyframes neofaceid-pulse {
|
|
11316
|
-
0%, 100% {
|
|
11317
|
-
transform: scale(1);
|
|
11318
|
-
opacity: 1;
|
|
11319
|
-
}
|
|
11320
|
-
50% {
|
|
11321
|
-
transform: scale(1.05);
|
|
11322
|
-
opacity: 0.9;
|
|
11323
|
-
}
|
|
11324
|
-
}
|
|
11325
|
-
|
|
11326
|
-
@keyframes neofaceid-scaleIn {
|
|
11327
|
-
0% { transform: scale(0); opacity: 0; }
|
|
11328
|
-
50% { transform: scale(1.1); }
|
|
11329
|
-
100% { transform: scale(1); opacity: 1; }
|
|
11330
|
-
}
|
|
11331
|
-
|
|
11332
|
-
@keyframes neofaceid-errorShake {
|
|
11333
|
-
0%, 100% { transform: translateX(0); }
|
|
11334
|
-
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
|
11335
|
-
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
|
11336
|
-
}
|
|
11337
|
-
|
|
11338
|
-
.neofaceid-overlay-container {
|
|
11339
|
-
position: fixed;
|
|
11340
|
-
top: 0;
|
|
11341
|
-
left: 0;
|
|
11342
|
-
right: 0;
|
|
11343
|
-
bottom: 0;
|
|
11344
|
-
display: flex;
|
|
11345
|
-
align-items: center;
|
|
11346
|
-
justify-content: center;
|
|
11347
|
-
z-index: 10000;
|
|
11348
|
-
pointer-events: none;
|
|
11349
|
-
animation: neofaceid-fadeIn 0.3s ease-out;
|
|
11350
|
-
font-family: inherit;
|
|
11351
|
-
}
|
|
11352
|
-
|
|
11353
|
-
.neofaceid-overlay-container.fade-out {
|
|
11354
|
-
animation: neofaceid-fadeOut 0.3s ease-out forwards;
|
|
11355
|
-
}
|
|
11356
|
-
|
|
11357
|
-
.neofaceid-overlay-content {
|
|
11358
|
-
display: flex;
|
|
11359
|
-
flex-direction: column;
|
|
11360
|
-
align-items: center;
|
|
11361
|
-
justify-content: center;
|
|
11362
|
-
gap: 24px;
|
|
11363
|
-
pointer-events: auto;
|
|
11364
|
-
}
|
|
11365
|
-
|
|
11366
|
-
.neofaceid-visual-wrapper {
|
|
11367
|
-
position: relative;
|
|
11368
|
-
width: 100px;
|
|
11369
|
-
height: 100px;
|
|
11370
|
-
display: flex;
|
|
11371
|
-
align-items: center;
|
|
11372
|
-
justify-content: center;
|
|
11373
|
-
}
|
|
11374
|
-
|
|
11375
|
-
.neofaceid-logo {
|
|
11376
|
-
width: 80px;
|
|
11377
|
-
height: 80px;
|
|
11378
|
-
object-fit: contain;
|
|
11379
|
-
transition: all 0.5s ease;
|
|
11380
|
-
filter: drop-shadow(0 4px 12px rgba(0, 89, 196, 0.3));
|
|
11381
|
-
}
|
|
11382
|
-
|
|
11383
|
-
/* Cores por estado */
|
|
11384
|
-
.neofaceid-state-preparing .neofaceid-logo { filter: drop-shadow(0 0 15px rgba(0, 89, 196, 0.4)); }
|
|
11385
|
-
.neofaceid-state-detecting .neofaceid-logo { filter: hue-rotate(180deg) drop-shadow(0 0 15px rgba(59, 130, 246, 0.6)); }
|
|
11386
|
-
.neofaceid-state-waiting-for-face .neofaceid-logo { filter: hue-rotate(30deg) drop-shadow(0 0 15px rgba(251, 146, 60, 0.6)); }
|
|
11387
|
-
.neofaceid-state-capturing .neofaceid-logo { filter: hue-rotate(45deg) drop-shadow(0 0 15px rgba(245, 158, 11, 0.6)); }
|
|
11388
|
-
.neofaceid-state-verifying .neofaceid-logo { filter: hue-rotate(90deg) drop-shadow(0 0 15px rgba(34, 197, 94, 0.6)); }
|
|
11389
|
-
|
|
11390
|
-
.neofaceid-logo.pulsing {
|
|
11391
|
-
animation: neofaceid-pulse 2s ease-in-out infinite;
|
|
11392
|
-
}
|
|
11393
|
-
|
|
11394
|
-
@keyframes neofaceid-dots {
|
|
11395
|
-
0%, 20% { opacity: 0; transform: translateY(0); }
|
|
11396
|
-
50% { opacity: 1; transform: translateY(-2px); }
|
|
11397
|
-
80%, 100% { opacity: 0; transform: translateY(0); }
|
|
10796
|
+
@keyframes neofaceid-dots {
|
|
10797
|
+
0%, 20% { opacity: 0; transform: translateY(0); }
|
|
10798
|
+
50% { opacity: 1; transform: translateY(-2px); }
|
|
10799
|
+
80%, 100% { opacity: 0; transform: translateY(0); }
|
|
11398
10800
|
}
|
|
11399
10801
|
|
|
11400
10802
|
.neofaceid-status-text {
|
|
@@ -12204,7 +11606,7 @@ async function captureFaceSilently() {
|
|
|
12204
11606
|
reject(new Error("Failed to get canvas context"));
|
|
12205
11607
|
return;
|
|
12206
11608
|
}
|
|
12207
|
-
const
|
|
11609
|
+
const detectFace = async () => {
|
|
12208
11610
|
if (!modelsLoaded) return true;
|
|
12209
11611
|
try {
|
|
12210
11612
|
const detection = await faceapi.detectSingleFace(
|
|
@@ -12216,7 +11618,7 @@ async function captureFaceSilently() {
|
|
|
12216
11618
|
return true;
|
|
12217
11619
|
}
|
|
12218
11620
|
};
|
|
12219
|
-
|
|
11621
|
+
detectFace().then((hasFace) => {
|
|
12220
11622
|
if (!hasFace && faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
|
|
12221
11623
|
faceDetectionAttempts++;
|
|
12222
11624
|
setTimeout(tryCapture, 150);
|
|
@@ -12550,16 +11952,614 @@ async function executeBiometricLoginFlow(options) {
|
|
|
12550
11952
|
);
|
|
12551
11953
|
}, 500);
|
|
12552
11954
|
}
|
|
12553
|
-
}
|
|
12554
|
-
};
|
|
12555
|
-
tryLogin();
|
|
11955
|
+
}
|
|
11956
|
+
};
|
|
11957
|
+
tryLogin();
|
|
11958
|
+
}
|
|
11959
|
+
const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
11960
|
+
__proto__: null,
|
|
11961
|
+
captureFaceSilently,
|
|
11962
|
+
executeBiometricLoginFlow,
|
|
11963
|
+
preloadFaceDetectionModels
|
|
11964
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
11965
|
+
const DARK_THRESHOLD = 0.235;
|
|
11966
|
+
const IMPROVEMENT_TARGET = 0.12;
|
|
11967
|
+
const SAMPLE_INTERVAL_MS = 600;
|
|
11968
|
+
const EVALUATION_DELAY_MS = 600;
|
|
11969
|
+
const MIN_TOGGLE_MS = 3e3;
|
|
11970
|
+
const OVERLAY_ID_PREFIX = "nfid-flash-";
|
|
11971
|
+
function createScreenFlashController(getVideo, options = {}) {
|
|
11972
|
+
const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD;
|
|
11973
|
+
const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET;
|
|
11974
|
+
const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
|
|
11975
|
+
const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS;
|
|
11976
|
+
const zIndex = options.zIndex ?? 10001;
|
|
11977
|
+
const overlayId = `${OVERLAY_ID_PREFIX}${Math.random().toString(36).slice(2, 9)}`;
|
|
11978
|
+
let interval = null;
|
|
11979
|
+
let evalTimer = null;
|
|
11980
|
+
let flashOn = false;
|
|
11981
|
+
let lastBrightness = 0;
|
|
11982
|
+
let darkStreak = 0;
|
|
11983
|
+
let lastToggleAt = 0;
|
|
11984
|
+
let preFlashBrightness = null;
|
|
11985
|
+
let overlayEl = null;
|
|
11986
|
+
let scratchCanvas = null;
|
|
11987
|
+
const showOverlay = () => {
|
|
11988
|
+
if (overlayEl || typeof document === "undefined") return;
|
|
11989
|
+
overlayEl = document.createElement("div");
|
|
11990
|
+
overlayEl.id = overlayId;
|
|
11991
|
+
overlayEl.setAttribute("aria-hidden", "true");
|
|
11992
|
+
overlayEl.style.cssText = `
|
|
11993
|
+
position: fixed; inset: 0; background: #FFFFFF;
|
|
11994
|
+
z-index: ${zIndex}; pointer-events: none;
|
|
11995
|
+
opacity: 0; transition: opacity 200ms ease-out;
|
|
11996
|
+
`;
|
|
11997
|
+
document.body.appendChild(overlayEl);
|
|
11998
|
+
requestAnimationFrame(() => {
|
|
11999
|
+
if (overlayEl) overlayEl.style.opacity = "1";
|
|
12000
|
+
});
|
|
12001
|
+
};
|
|
12002
|
+
const hideOverlay = () => {
|
|
12003
|
+
if (!overlayEl) return;
|
|
12004
|
+
const el = overlayEl;
|
|
12005
|
+
overlayEl = null;
|
|
12006
|
+
el.style.opacity = "0";
|
|
12007
|
+
setTimeout(() => {
|
|
12008
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
12009
|
+
}, 220);
|
|
12010
|
+
};
|
|
12011
|
+
const setFlashOn = (next) => {
|
|
12012
|
+
if (flashOn === next) return;
|
|
12013
|
+
flashOn = next;
|
|
12014
|
+
if (next) showOverlay();
|
|
12015
|
+
else hideOverlay();
|
|
12016
|
+
if (options.onChange) options.onChange(next, lastBrightness);
|
|
12017
|
+
};
|
|
12018
|
+
const tick = () => {
|
|
12019
|
+
if (typeof document !== "undefined" && !scratchCanvas) {
|
|
12020
|
+
scratchCanvas = document.createElement("canvas");
|
|
12021
|
+
}
|
|
12022
|
+
const b = measureFrameBrightness(getVideo(), {
|
|
12023
|
+
scratch: scratchCanvas ?? void 0
|
|
12024
|
+
});
|
|
12025
|
+
lastBrightness = b;
|
|
12026
|
+
const now = Date.now();
|
|
12027
|
+
if (!flashOn) {
|
|
12028
|
+
if (b > 0 && b < darkThreshold) darkStreak += 1;
|
|
12029
|
+
else darkStreak = 0;
|
|
12030
|
+
if (darkStreak >= 2 && now - lastToggleAt >= minToggleMs) {
|
|
12031
|
+
preFlashBrightness = b;
|
|
12032
|
+
lastToggleAt = now;
|
|
12033
|
+
darkStreak = 0;
|
|
12034
|
+
setFlashOn(true);
|
|
12035
|
+
evalTimer = setTimeout(() => {
|
|
12036
|
+
const after = measureFrameBrightness(getVideo(), {
|
|
12037
|
+
scratch: scratchCanvas ?? void 0
|
|
12038
|
+
});
|
|
12039
|
+
const gained = after - (preFlashBrightness ?? 0);
|
|
12040
|
+
if (gained < improvementTarget) {
|
|
12041
|
+
lastToggleAt = Date.now();
|
|
12042
|
+
setFlashOn(false);
|
|
12043
|
+
}
|
|
12044
|
+
preFlashBrightness = null;
|
|
12045
|
+
}, EVALUATION_DELAY_MS);
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
12048
|
+
};
|
|
12049
|
+
return {
|
|
12050
|
+
start() {
|
|
12051
|
+
if (options.enabled === false) return;
|
|
12052
|
+
if (interval) return;
|
|
12053
|
+
tick();
|
|
12054
|
+
interval = setInterval(tick, sampleIntervalMs);
|
|
12055
|
+
},
|
|
12056
|
+
stop() {
|
|
12057
|
+
if (interval) {
|
|
12058
|
+
clearInterval(interval);
|
|
12059
|
+
interval = null;
|
|
12060
|
+
}
|
|
12061
|
+
if (evalTimer) {
|
|
12062
|
+
clearTimeout(evalTimer);
|
|
12063
|
+
evalTimer = null;
|
|
12064
|
+
}
|
|
12065
|
+
setFlashOn(false);
|
|
12066
|
+
darkStreak = 0;
|
|
12067
|
+
preFlashBrightness = null;
|
|
12068
|
+
},
|
|
12069
|
+
destroy() {
|
|
12070
|
+
this.stop();
|
|
12071
|
+
scratchCanvas = null;
|
|
12072
|
+
},
|
|
12073
|
+
get flashOn() {
|
|
12074
|
+
return flashOn;
|
|
12075
|
+
},
|
|
12076
|
+
get lastBrightness() {
|
|
12077
|
+
return lastBrightness;
|
|
12078
|
+
}
|
|
12079
|
+
};
|
|
12080
|
+
}
|
|
12081
|
+
class BiometricCaptureModal {
|
|
12082
|
+
constructor(options) {
|
|
12083
|
+
__publicField(this, "modal", null);
|
|
12084
|
+
__publicField(this, "video", null);
|
|
12085
|
+
__publicField(this, "canvas", null);
|
|
12086
|
+
__publicField(this, "stream", null);
|
|
12087
|
+
__publicField(this, "options");
|
|
12088
|
+
__publicField(this, "countdownInterval", null);
|
|
12089
|
+
__publicField(this, "isCapturing", false);
|
|
12090
|
+
__publicField(this, "flashController", null);
|
|
12091
|
+
this.options = options;
|
|
12092
|
+
}
|
|
12093
|
+
/**
|
|
12094
|
+
* Abre o modal de captura biométrica
|
|
12095
|
+
*/
|
|
12096
|
+
async open() {
|
|
12097
|
+
try {
|
|
12098
|
+
await this.createModal();
|
|
12099
|
+
await this.initializeCamera();
|
|
12100
|
+
this.enableCaptureButton();
|
|
12101
|
+
if (this.options.autoLighting !== false) {
|
|
12102
|
+
this.flashController = createScreenFlashController(() => this.video);
|
|
12103
|
+
this.flashController.start();
|
|
12104
|
+
}
|
|
12105
|
+
} catch (error) {
|
|
12106
|
+
this.options.onError(
|
|
12107
|
+
new NeoFaceError(
|
|
12108
|
+
`Erro ao inicializar câmera: ${error.message}`,
|
|
12109
|
+
ErrorType.CAMERA_ERROR
|
|
12110
|
+
)
|
|
12111
|
+
);
|
|
12112
|
+
}
|
|
12113
|
+
}
|
|
12114
|
+
/**
|
|
12115
|
+
* Fecha o modal e limpa recursos
|
|
12116
|
+
*/
|
|
12117
|
+
close() {
|
|
12118
|
+
this.cleanup();
|
|
12119
|
+
if (this.modal) {
|
|
12120
|
+
document.body.removeChild(this.modal);
|
|
12121
|
+
this.modal = null;
|
|
12122
|
+
}
|
|
12123
|
+
}
|
|
12124
|
+
/**
|
|
12125
|
+
* Cria a estrutura HTML do modal
|
|
12126
|
+
*/
|
|
12127
|
+
async createModal() {
|
|
12128
|
+
this.modal = document.createElement("div");
|
|
12129
|
+
this.modal.className = "neofaceid-root neofaceid-biometric-modal";
|
|
12130
|
+
const title = this.options.title || this.getDefaultTitle();
|
|
12131
|
+
const subtitle = this.options.subtitle || this.getDefaultSubtitle();
|
|
12132
|
+
this.modal.innerHTML = `
|
|
12133
|
+
<div class="neofaceid-modal-overlay">
|
|
12134
|
+
<div class="neofaceid-modal-content">
|
|
12135
|
+
<div class="neofaceid-modal-header">
|
|
12136
|
+
<h2>${title}</h2>
|
|
12137
|
+
<button class="neofaceid-close-btn" type="button" aria-label="Fechar">×</button>
|
|
12138
|
+
</div>
|
|
12139
|
+
<div class="neofaceid-modal-body">
|
|
12140
|
+
<p class="neofaceid-subtitle">${subtitle}</p>
|
|
12141
|
+
<div class="neofaceid-camera-container">
|
|
12142
|
+
<video class="neofaceid-video" autoplay muted playsinline></video>
|
|
12143
|
+
<canvas class="neofaceid-canvas" style="display: none;"></canvas>
|
|
12144
|
+
<div class="neofaceid-overlay">
|
|
12145
|
+
<div class="neofaceid-frame"></div>
|
|
12146
|
+
</div>
|
|
12147
|
+
</div>
|
|
12148
|
+
<div class="neofaceid-status">
|
|
12149
|
+
<p class="neofaceid-status-text">Posicione-se na frente da câmera</p>
|
|
12150
|
+
</div>
|
|
12151
|
+
</div>
|
|
12152
|
+
<div class="neofaceid-modal-footer">
|
|
12153
|
+
<button class="neofaceid-cancel-btn" type="button">Cancelar</button>
|
|
12154
|
+
<button class="neofaceid-capture-btn" type="button" disabled>Capturar</button>
|
|
12155
|
+
</div>
|
|
12156
|
+
<div class="neofaceid-seal-row">
|
|
12157
|
+
${renderBrandFooter()}
|
|
12158
|
+
</div>
|
|
12159
|
+
</div>
|
|
12160
|
+
</div>
|
|
12161
|
+
`;
|
|
12162
|
+
injectThemeStyles();
|
|
12163
|
+
this.addStyles();
|
|
12164
|
+
this.addEventListeners();
|
|
12165
|
+
document.body.appendChild(this.modal);
|
|
12166
|
+
}
|
|
12167
|
+
/**
|
|
12168
|
+
* Inicializa a câmera
|
|
12169
|
+
*/
|
|
12170
|
+
async initializeCamera() {
|
|
12171
|
+
if (!this.modal) throw new Error("Modal não inicializado");
|
|
12172
|
+
this.video = this.modal.querySelector(".neofaceid-video");
|
|
12173
|
+
this.canvas = this.modal.querySelector(".neofaceid-canvas");
|
|
12174
|
+
if (!this.video || !this.canvas) {
|
|
12175
|
+
throw new Error("Elementos de vídeo ou canvas não encontrados");
|
|
12176
|
+
}
|
|
12177
|
+
try {
|
|
12178
|
+
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
12179
|
+
video: {
|
|
12180
|
+
width: { ideal: 640 },
|
|
12181
|
+
height: { ideal: 480 },
|
|
12182
|
+
facingMode: "user"
|
|
12183
|
+
},
|
|
12184
|
+
audio: false
|
|
12185
|
+
});
|
|
12186
|
+
this.video.srcObject = this.stream;
|
|
12187
|
+
await this.video.play();
|
|
12188
|
+
this.canvas.width = this.video.videoWidth;
|
|
12189
|
+
this.canvas.height = this.video.videoHeight;
|
|
12190
|
+
} catch (error) {
|
|
12191
|
+
throw new Error(`Não foi possível acessar a câmera: ${error.message}`);
|
|
12192
|
+
}
|
|
12193
|
+
}
|
|
12194
|
+
/**
|
|
12195
|
+
* NEO-413 · US14.6 (item 18) — a contagem "3, 2, 1" foi removida.
|
|
12196
|
+
* Agora habilita direto o botão "Capturar" assim que a câmera fica pronta.
|
|
12197
|
+
* Vivacidade fica com o desafio `runLivenessChallenge` (NEO-408), servidor decide.
|
|
12198
|
+
*/
|
|
12199
|
+
enableCaptureButton() {
|
|
12200
|
+
if (!this.modal) return;
|
|
12201
|
+
const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
|
|
12202
|
+
const statusText = this.modal.querySelector(".neofaceid-status-text");
|
|
12203
|
+
if (captureBtn) {
|
|
12204
|
+
captureBtn.disabled = false;
|
|
12205
|
+
captureBtn.textContent = "Capturar";
|
|
12206
|
+
}
|
|
12207
|
+
if (statusText) {
|
|
12208
|
+
statusText.textContent = "Pronto para capturar";
|
|
12209
|
+
}
|
|
12210
|
+
if (this.options.mode === "auto") {
|
|
12211
|
+
setTimeout(() => this.captureImage(), 300);
|
|
12212
|
+
}
|
|
12213
|
+
}
|
|
12214
|
+
/**
|
|
12215
|
+
* Captura a imagem da câmera
|
|
12216
|
+
*/
|
|
12217
|
+
async captureImage() {
|
|
12218
|
+
if (this.isCapturing || !this.video || !this.canvas) return;
|
|
12219
|
+
this.isCapturing = true;
|
|
12220
|
+
try {
|
|
12221
|
+
const context = this.canvas.getContext("2d");
|
|
12222
|
+
if (!context) throw new Error("Não foi possível obter contexto do canvas");
|
|
12223
|
+
context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
|
12224
|
+
const imageData = this.canvas.toDataURL("image/jpeg", 0.8);
|
|
12225
|
+
const base64Data = imageData.split(",")[1];
|
|
12226
|
+
const dataUrl = `data:image/jpeg;base64,${base64Data}`;
|
|
12227
|
+
const detectedType = this.options.mode === "auto" ? await this.detectBiometricType(base64Data) : this.options.mode;
|
|
12228
|
+
this.close();
|
|
12229
|
+
this.options.onSuccess(dataUrl, detectedType);
|
|
12230
|
+
} catch (error) {
|
|
12231
|
+
this.options.onError(
|
|
12232
|
+
new NeoFaceError(
|
|
12233
|
+
`Erro ao capturar imagem: ${error.message}`,
|
|
12234
|
+
ErrorType.CAPTURE_ERROR
|
|
12235
|
+
)
|
|
12236
|
+
);
|
|
12237
|
+
} finally {
|
|
12238
|
+
this.isCapturing = false;
|
|
12239
|
+
}
|
|
12240
|
+
}
|
|
12241
|
+
/**
|
|
12242
|
+
* Detecta o tipo biométrico na imagem (face ou mão)
|
|
12243
|
+
*/
|
|
12244
|
+
async detectBiometricType(imageData) {
|
|
12245
|
+
try {
|
|
12246
|
+
const { detectBiometricType } = await import("./biometricDetection-8utmtR25.js");
|
|
12247
|
+
const result = await detectBiometricType(imageData);
|
|
12248
|
+
if (result.type !== "unknown" && result.confidence > 0.6) {
|
|
12249
|
+
return result.type;
|
|
12250
|
+
}
|
|
12251
|
+
return "face";
|
|
12252
|
+
} catch (error) {
|
|
12253
|
+
console.warn("Erro na detecção automática, usando face como padrão:", error);
|
|
12254
|
+
return "face";
|
|
12255
|
+
}
|
|
12256
|
+
}
|
|
12257
|
+
/**
|
|
12258
|
+
* Adiciona event listeners aos elementos do modal
|
|
12259
|
+
*/
|
|
12260
|
+
addEventListeners() {
|
|
12261
|
+
if (!this.modal) return;
|
|
12262
|
+
const closeBtn = this.modal.querySelector(".neofaceid-close-btn");
|
|
12263
|
+
const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
|
|
12264
|
+
const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
|
|
12265
|
+
closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
|
|
12266
|
+
var _a2, _b;
|
|
12267
|
+
this.close();
|
|
12268
|
+
(_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
|
|
12269
|
+
});
|
|
12270
|
+
cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
|
|
12271
|
+
var _a2, _b;
|
|
12272
|
+
this.close();
|
|
12273
|
+
(_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
|
|
12274
|
+
});
|
|
12275
|
+
captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
|
|
12276
|
+
this.captureImage();
|
|
12277
|
+
});
|
|
12278
|
+
this.modal.addEventListener("click", (e) => {
|
|
12279
|
+
var _a2, _b, _c;
|
|
12280
|
+
if (e.target === ((_a2 = this.modal) == null ? void 0 : _a2.querySelector(".neofaceid-modal-overlay"))) {
|
|
12281
|
+
this.close();
|
|
12282
|
+
(_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
|
|
12283
|
+
}
|
|
12284
|
+
});
|
|
12285
|
+
}
|
|
12286
|
+
/**
|
|
12287
|
+
* Limpa recursos (câmera, intervalos, etc.)
|
|
12288
|
+
*/
|
|
12289
|
+
cleanup() {
|
|
12290
|
+
if (this.flashController) {
|
|
12291
|
+
this.flashController.destroy();
|
|
12292
|
+
this.flashController = null;
|
|
12293
|
+
}
|
|
12294
|
+
if (this.stream) {
|
|
12295
|
+
this.stream.getTracks().forEach((track) => track.stop());
|
|
12296
|
+
this.stream = null;
|
|
12297
|
+
}
|
|
12298
|
+
if (this.countdownInterval) {
|
|
12299
|
+
clearInterval(this.countdownInterval);
|
|
12300
|
+
this.countdownInterval = null;
|
|
12301
|
+
}
|
|
12302
|
+
this.video = null;
|
|
12303
|
+
this.canvas = null;
|
|
12304
|
+
this.isCapturing = false;
|
|
12305
|
+
}
|
|
12306
|
+
/**
|
|
12307
|
+
* Retorna o título padrão baseado no modo
|
|
12308
|
+
*/
|
|
12309
|
+
getDefaultTitle() {
|
|
12310
|
+
switch (this.options.mode) {
|
|
12311
|
+
case "face":
|
|
12312
|
+
return "Autenticação Facial";
|
|
12313
|
+
case "hand":
|
|
12314
|
+
return "Autenticação por Mão";
|
|
12315
|
+
case "auto":
|
|
12316
|
+
return "Autenticação Biométrica";
|
|
12317
|
+
default:
|
|
12318
|
+
return "Captura Biométrica";
|
|
12319
|
+
}
|
|
12320
|
+
}
|
|
12321
|
+
/**
|
|
12322
|
+
* Retorna o subtítulo padrão baseado no modo
|
|
12323
|
+
*/
|
|
12324
|
+
getDefaultSubtitle() {
|
|
12325
|
+
switch (this.options.mode) {
|
|
12326
|
+
case "face":
|
|
12327
|
+
return "Posicione seu rosto dentro do quadro e aguarde a captura automática.";
|
|
12328
|
+
case "hand":
|
|
12329
|
+
return "Posicione sua mão dentro do quadro e aguarde a captura automática.";
|
|
12330
|
+
case "auto":
|
|
12331
|
+
return "Posicione seu rosto ou mão dentro do quadro para autenticação automática.";
|
|
12332
|
+
default:
|
|
12333
|
+
return "Posicione-se dentro do quadro para captura.";
|
|
12334
|
+
}
|
|
12335
|
+
}
|
|
12336
|
+
/**
|
|
12337
|
+
* Adiciona estilos CSS ao modal
|
|
12338
|
+
*/
|
|
12339
|
+
addStyles() {
|
|
12340
|
+
const styleId = "neofaceid-biometric-modal-styles";
|
|
12341
|
+
const existingStyles = document.getElementById(styleId);
|
|
12342
|
+
if (existingStyles) {
|
|
12343
|
+
existingStyles.remove();
|
|
12344
|
+
}
|
|
12345
|
+
injectScopedStyles(styleId, `
|
|
12346
|
+
.neofaceid-biometric-modal {
|
|
12347
|
+
position: fixed;
|
|
12348
|
+
top: 0;
|
|
12349
|
+
left: 0;
|
|
12350
|
+
width: 100%;
|
|
12351
|
+
height: 100%;
|
|
12352
|
+
z-index: 10000;
|
|
12353
|
+
font-family: inherit;
|
|
12354
|
+
}
|
|
12355
|
+
|
|
12356
|
+
.neofaceid-modal-overlay {
|
|
12357
|
+
position: absolute;
|
|
12358
|
+
top: 0;
|
|
12359
|
+
left: 0;
|
|
12360
|
+
width: 100%;
|
|
12361
|
+
height: 100%;
|
|
12362
|
+
background: rgba(0, 0, 0, 0.8);
|
|
12363
|
+
display: flex;
|
|
12364
|
+
align-items: center;
|
|
12365
|
+
justify-content: center;
|
|
12366
|
+
padding: 20px;
|
|
12367
|
+
box-sizing: border-box;
|
|
12368
|
+
}
|
|
12369
|
+
|
|
12370
|
+
.neofaceid-modal-content {
|
|
12371
|
+
background: white;
|
|
12372
|
+
border-radius: 12px;
|
|
12373
|
+
max-width: 600px;
|
|
12374
|
+
width: 100%;
|
|
12375
|
+
max-height: 90vh;
|
|
12376
|
+
overflow: hidden;
|
|
12377
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
|
12378
|
+
}
|
|
12379
|
+
|
|
12380
|
+
.neofaceid-modal-header {
|
|
12381
|
+
padding: 20px;
|
|
12382
|
+
border-bottom: 1px solid #e0e0e0;
|
|
12383
|
+
display: flex;
|
|
12384
|
+
justify-content: space-between;
|
|
12385
|
+
align-items: center;
|
|
12386
|
+
}
|
|
12387
|
+
|
|
12388
|
+
.neofaceid-modal-header h2 {
|
|
12389
|
+
margin: 0;
|
|
12390
|
+
font-size: 24px;
|
|
12391
|
+
font-weight: 600;
|
|
12392
|
+
color: #333;
|
|
12393
|
+
}
|
|
12394
|
+
|
|
12395
|
+
.neofaceid-close-btn {
|
|
12396
|
+
background: none;
|
|
12397
|
+
border: none;
|
|
12398
|
+
font-size: 22px;
|
|
12399
|
+
cursor: pointer;
|
|
12400
|
+
color: #666;
|
|
12401
|
+
padding: 0;
|
|
12402
|
+
min-width: 44px;
|
|
12403
|
+
min-height: 44px;
|
|
12404
|
+
display: flex;
|
|
12405
|
+
align-items: center;
|
|
12406
|
+
justify-content: center;
|
|
12407
|
+
border-radius: 22px;
|
|
12408
|
+
transition: background-color 0.2s;
|
|
12409
|
+
}
|
|
12410
|
+
.neofaceid-close-btn:focus-visible {
|
|
12411
|
+
outline: 2px solid var(--nfid-focus-ring);
|
|
12412
|
+
outline-offset: 2px;
|
|
12413
|
+
}
|
|
12414
|
+
|
|
12415
|
+
.neofaceid-close-btn:hover {
|
|
12416
|
+
background-color: #f0f0f0;
|
|
12417
|
+
}
|
|
12418
|
+
|
|
12419
|
+
.neofaceid-modal-body {
|
|
12420
|
+
padding: 20px;
|
|
12421
|
+
}
|
|
12422
|
+
|
|
12423
|
+
.neofaceid-subtitle {
|
|
12424
|
+
margin: 0 0 20px 0;
|
|
12425
|
+
color: #666;
|
|
12426
|
+
font-size: 16px;
|
|
12427
|
+
line-height: 1.4;
|
|
12428
|
+
}
|
|
12429
|
+
|
|
12430
|
+
.neofaceid-camera-container {
|
|
12431
|
+
position: relative;
|
|
12432
|
+
background: #000;
|
|
12433
|
+
border-radius: 8px;
|
|
12434
|
+
overflow: hidden;
|
|
12435
|
+
aspect-ratio: 4/3;
|
|
12436
|
+
margin-bottom: 20px;
|
|
12437
|
+
}
|
|
12438
|
+
|
|
12439
|
+
.neofaceid-video {
|
|
12440
|
+
width: 100%;
|
|
12441
|
+
height: 100%;
|
|
12442
|
+
object-fit: cover;
|
|
12443
|
+
}
|
|
12444
|
+
|
|
12445
|
+
.neofaceid-canvas {
|
|
12446
|
+
position: absolute;
|
|
12447
|
+
top: 0;
|
|
12448
|
+
left: 0;
|
|
12449
|
+
}
|
|
12450
|
+
|
|
12451
|
+
.neofaceid-overlay {
|
|
12452
|
+
position: absolute;
|
|
12453
|
+
top: 0;
|
|
12454
|
+
left: 0;
|
|
12455
|
+
width: 100%;
|
|
12456
|
+
height: 100%;
|
|
12457
|
+
display: flex;
|
|
12458
|
+
align-items: center;
|
|
12459
|
+
justify-content: center;
|
|
12460
|
+
}
|
|
12461
|
+
|
|
12462
|
+
.neofaceid-frame {
|
|
12463
|
+
width: 200px;
|
|
12464
|
+
height: 200px;
|
|
12465
|
+
border: 3px solid #0E9F6E;
|
|
12466
|
+
border-radius: 50%;
|
|
12467
|
+
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
|
|
12468
|
+
animation: pulse 2s infinite;
|
|
12469
|
+
}
|
|
12470
|
+
|
|
12471
|
+
.neofaceid-seal-row {
|
|
12472
|
+
display: flex;
|
|
12473
|
+
justify-content: center;
|
|
12474
|
+
padding: 12px 20px 16px;
|
|
12475
|
+
border-top: 1px solid var(--nfid-line, #E6ECF4);
|
|
12476
|
+
}
|
|
12477
|
+
.neofaceid-seal {
|
|
12478
|
+
font-size: 10px;
|
|
12479
|
+
font-weight: 500;
|
|
12480
|
+
letter-spacing: 0.06em;
|
|
12481
|
+
text-transform: uppercase;
|
|
12482
|
+
color: var(--nfid-text-subtle, #617489);
|
|
12483
|
+
}
|
|
12484
|
+
|
|
12485
|
+
.neofaceid-status {
|
|
12486
|
+
text-align: center;
|
|
12487
|
+
margin-bottom: 20px;
|
|
12488
|
+
}
|
|
12489
|
+
|
|
12490
|
+
.neofaceid-status-text {
|
|
12491
|
+
margin: 0;
|
|
12492
|
+
color: #666;
|
|
12493
|
+
font-size: 16px;
|
|
12494
|
+
}
|
|
12495
|
+
|
|
12496
|
+
.neofaceid-modal-footer {
|
|
12497
|
+
padding: 20px;
|
|
12498
|
+
border-top: 1px solid #e0e0e0;
|
|
12499
|
+
display: flex;
|
|
12500
|
+
gap: 12px;
|
|
12501
|
+
justify-content: flex-end;
|
|
12502
|
+
}
|
|
12503
|
+
|
|
12504
|
+
.neofaceid-cancel-btn,
|
|
12505
|
+
.neofaceid-capture-btn {
|
|
12506
|
+
padding: 12px 24px;
|
|
12507
|
+
border: none;
|
|
12508
|
+
border-radius: 6px;
|
|
12509
|
+
font-size: 16px;
|
|
12510
|
+
font-weight: 500;
|
|
12511
|
+
cursor: pointer;
|
|
12512
|
+
transition: all 0.2s;
|
|
12513
|
+
}
|
|
12514
|
+
|
|
12515
|
+
.neofaceid-cancel-btn {
|
|
12516
|
+
background: #f5f5f5;
|
|
12517
|
+
color: #666;
|
|
12518
|
+
}
|
|
12519
|
+
|
|
12520
|
+
.neofaceid-cancel-btn:hover {
|
|
12521
|
+
background: #e0e0e0;
|
|
12522
|
+
}
|
|
12523
|
+
|
|
12524
|
+
.neofaceid-capture-btn {
|
|
12525
|
+
background: #0E9F6E;
|
|
12526
|
+
color: white;
|
|
12527
|
+
}
|
|
12528
|
+
|
|
12529
|
+
.neofaceid-capture-btn:hover:not(:disabled) {
|
|
12530
|
+
background: #45a049;
|
|
12531
|
+
}
|
|
12532
|
+
|
|
12533
|
+
.neofaceid-capture-btn:disabled {
|
|
12534
|
+
background: #ccc;
|
|
12535
|
+
cursor: not-allowed;
|
|
12536
|
+
}
|
|
12537
|
+
|
|
12538
|
+
@keyframes pulse {
|
|
12539
|
+
0% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
|
|
12540
|
+
50% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0.1); }
|
|
12541
|
+
100% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
|
|
12542
|
+
}
|
|
12543
|
+
|
|
12544
|
+
@media (max-width: 640px) {
|
|
12545
|
+
.neofaceid-modal-overlay {
|
|
12546
|
+
padding: 10px;
|
|
12547
|
+
}
|
|
12548
|
+
|
|
12549
|
+
.neofaceid-modal-header,
|
|
12550
|
+
.neofaceid-modal-body,
|
|
12551
|
+
.neofaceid-modal-footer {
|
|
12552
|
+
padding: 15px;
|
|
12553
|
+
}
|
|
12554
|
+
|
|
12555
|
+
.neofaceid-frame {
|
|
12556
|
+
width: 150px;
|
|
12557
|
+
height: 150px;
|
|
12558
|
+
}
|
|
12559
|
+
}
|
|
12560
|
+
`);
|
|
12561
|
+
}
|
|
12556
12562
|
}
|
|
12557
|
-
const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
12558
|
-
__proto__: null,
|
|
12559
|
-
captureFaceSilently,
|
|
12560
|
-
executeBiometricLoginFlow,
|
|
12561
|
-
preloadFaceDetectionModels
|
|
12562
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
12563
12563
|
const CONSENT_DENIED_MESSAGE$1 = "Consentimento recusado pelo usuário.";
|
|
12564
12564
|
function openPasswordFallback(options) {
|
|
12565
12565
|
if (!getAutoFallback().toPassword) {
|
|
@@ -12682,175 +12682,6 @@ async function startAutoLogin(options) {
|
|
|
12682
12682
|
);
|
|
12683
12683
|
}
|
|
12684
12684
|
}
|
|
12685
|
-
async function detectBiometricType(imageData) {
|
|
12686
|
-
try {
|
|
12687
|
-
const img = await loadImageFromBase64(imageData);
|
|
12688
|
-
const faceResult = await detectFace(img);
|
|
12689
|
-
if (faceResult.confidence > 0.7) {
|
|
12690
|
-
return {
|
|
12691
|
-
type: "face",
|
|
12692
|
-
confidence: faceResult.confidence,
|
|
12693
|
-
details: faceResult.details
|
|
12694
|
-
};
|
|
12695
|
-
}
|
|
12696
|
-
const handResult = await detectHand(img);
|
|
12697
|
-
if (handResult.confidence > 0.6) {
|
|
12698
|
-
return {
|
|
12699
|
-
type: "hand",
|
|
12700
|
-
confidence: handResult.confidence,
|
|
12701
|
-
details: handResult.details
|
|
12702
|
-
};
|
|
12703
|
-
}
|
|
12704
|
-
return {
|
|
12705
|
-
type: "unknown",
|
|
12706
|
-
confidence: Math.max(faceResult.confidence, handResult.confidence)
|
|
12707
|
-
};
|
|
12708
|
-
} catch (error) {
|
|
12709
|
-
console.warn("Erro na detecção biométrica:", error);
|
|
12710
|
-
return {
|
|
12711
|
-
type: "face",
|
|
12712
|
-
confidence: 0.5
|
|
12713
|
-
};
|
|
12714
|
-
}
|
|
12715
|
-
}
|
|
12716
|
-
async function detectFace(img) {
|
|
12717
|
-
try {
|
|
12718
|
-
if (typeof window !== "undefined" && window.faceapi) {
|
|
12719
|
-
const { faceapi: faceapi2 } = window;
|
|
12720
|
-
await loadFaceApiModels();
|
|
12721
|
-
const tinyOptions = new faceapi2.TinyFaceDetectorOptions({
|
|
12722
|
-
inputSize: 320,
|
|
12723
|
-
scoreThreshold: 0.5
|
|
12724
|
-
});
|
|
12725
|
-
const detections = await faceapi2.detectAllFaces(img, tinyOptions).withFaceLandmarks();
|
|
12726
|
-
if (detections && detections.length > 0) {
|
|
12727
|
-
const bestDetection = detections.reduce(
|
|
12728
|
-
(best, current) => current.detection.score > best.detection.score ? current : best
|
|
12729
|
-
);
|
|
12730
|
-
return {
|
|
12731
|
-
confidence: bestDetection.detection.score,
|
|
12732
|
-
details: {
|
|
12733
|
-
faces: detections.length,
|
|
12734
|
-
landmarks: bestDetection.landmarks,
|
|
12735
|
-
box: bestDetection.detection.box
|
|
12736
|
-
}
|
|
12737
|
-
};
|
|
12738
|
-
}
|
|
12739
|
-
}
|
|
12740
|
-
return { confidence: 0 };
|
|
12741
|
-
} catch (error) {
|
|
12742
|
-
console.warn("Erro na detecção facial:", error);
|
|
12743
|
-
return { confidence: 0 };
|
|
12744
|
-
}
|
|
12745
|
-
}
|
|
12746
|
-
async function detectHand(img) {
|
|
12747
|
-
try {
|
|
12748
|
-
const canvas = document.createElement("canvas");
|
|
12749
|
-
const ctx = canvas.getContext("2d");
|
|
12750
|
-
if (!ctx) {
|
|
12751
|
-
return { confidence: 0 };
|
|
12752
|
-
}
|
|
12753
|
-
canvas.width = img.width;
|
|
12754
|
-
canvas.height = img.height;
|
|
12755
|
-
ctx.drawImage(img, 0, 0);
|
|
12756
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
12757
|
-
const handFeatures = analyzeHandFeatures(imageData);
|
|
12758
|
-
return {
|
|
12759
|
-
confidence: handFeatures.confidence,
|
|
12760
|
-
details: handFeatures
|
|
12761
|
-
};
|
|
12762
|
-
} catch (error) {
|
|
12763
|
-
console.warn("Erro na detecção de mão:", error);
|
|
12764
|
-
return { confidence: 0 };
|
|
12765
|
-
}
|
|
12766
|
-
}
|
|
12767
|
-
function analyzeHandFeatures(imageData) {
|
|
12768
|
-
const { data, width, height } = imageData;
|
|
12769
|
-
let skinPixels = 0;
|
|
12770
|
-
let totalPixels = 0;
|
|
12771
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
12772
|
-
const r = data[i];
|
|
12773
|
-
const g = data[i + 1];
|
|
12774
|
-
const b = data[i + 2];
|
|
12775
|
-
if (isSkinColor(r, g, b)) {
|
|
12776
|
-
skinPixels++;
|
|
12777
|
-
}
|
|
12778
|
-
totalPixels++;
|
|
12779
|
-
}
|
|
12780
|
-
const skinRatio = skinPixels / totalPixels;
|
|
12781
|
-
const aspectRatio = width / height;
|
|
12782
|
-
const isHandAspectRatio = aspectRatio > 0.6 && aspectRatio < 1.8;
|
|
12783
|
-
let confidence = 0;
|
|
12784
|
-
if (skinRatio > 0.15 && skinRatio < 0.7) {
|
|
12785
|
-
confidence += 0.4 * (skinRatio / 0.7);
|
|
12786
|
-
}
|
|
12787
|
-
if (isHandAspectRatio) {
|
|
12788
|
-
confidence += 0.3;
|
|
12789
|
-
}
|
|
12790
|
-
const imageSize = width * height;
|
|
12791
|
-
if (imageSize > 1e4 && imageSize < 5e5) {
|
|
12792
|
-
confidence += 0.3;
|
|
12793
|
-
}
|
|
12794
|
-
return {
|
|
12795
|
-
confidence: Math.min(confidence, 0.8),
|
|
12796
|
-
// Máximo 80% para detecção básica
|
|
12797
|
-
skinRatio,
|
|
12798
|
-
aspectRatio,
|
|
12799
|
-
imageSize,
|
|
12800
|
-
skinPixels,
|
|
12801
|
-
totalPixels
|
|
12802
|
-
};
|
|
12803
|
-
}
|
|
12804
|
-
function isSkinColor(r, g, b) {
|
|
12805
|
-
const y = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
12806
|
-
const cb = -0.169 * r - 0.331 * g + 0.5 * b + 128;
|
|
12807
|
-
const cr = 0.5 * r - 0.419 * g - 0.081 * b + 128;
|
|
12808
|
-
return y > 80 && y < 255 && cb > 85 && cb < 135 && cr > 135 && cr < 180;
|
|
12809
|
-
}
|
|
12810
|
-
function loadImageFromBase64(base64Data) {
|
|
12811
|
-
return new Promise((resolve, reject) => {
|
|
12812
|
-
const img = new Image();
|
|
12813
|
-
img.onload = () => resolve(img);
|
|
12814
|
-
img.onerror = () => reject(new Error("Erro ao carregar imagem"));
|
|
12815
|
-
const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:image/jpeg;base64,${base64Data}`;
|
|
12816
|
-
img.src = dataUrl;
|
|
12817
|
-
});
|
|
12818
|
-
}
|
|
12819
|
-
async function loadFaceApiModels() {
|
|
12820
|
-
if (typeof window === "undefined" || !window.faceapi) {
|
|
12821
|
-
return;
|
|
12822
|
-
}
|
|
12823
|
-
const { faceapi: faceapi2 } = window;
|
|
12824
|
-
if (faceapi2.nets.tinyFaceDetector.isLoaded) {
|
|
12825
|
-
return;
|
|
12826
|
-
}
|
|
12827
|
-
try {
|
|
12828
|
-
await Promise.all([
|
|
12829
|
-
faceapi2.nets.tinyFaceDetector.loadFromUri("/models"),
|
|
12830
|
-
faceapi2.nets.faceLandmark68Net.loadFromUri("/models")
|
|
12831
|
-
]);
|
|
12832
|
-
} catch (error) {
|
|
12833
|
-
console.warn("Não foi possível carregar modelos do face-api.js:", error);
|
|
12834
|
-
}
|
|
12835
|
-
}
|
|
12836
|
-
async function initializeBiometricDetection() {
|
|
12837
|
-
try {
|
|
12838
|
-
if (typeof window !== "undefined") {
|
|
12839
|
-
await loadFaceApiModels();
|
|
12840
|
-
}
|
|
12841
|
-
} catch (error) {
|
|
12842
|
-
console.warn("Inicialização da detecção biométrica com limitações:", error);
|
|
12843
|
-
}
|
|
12844
|
-
}
|
|
12845
|
-
function isAdvancedDetectionAvailable() {
|
|
12846
|
-
return typeof window !== "undefined" && window.faceapi && window.faceapi.nets.tinyFaceDetector.isLoaded;
|
|
12847
|
-
}
|
|
12848
|
-
const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
12849
|
-
__proto__: null,
|
|
12850
|
-
detectBiometricType,
|
|
12851
|
-
initializeBiometricDetection,
|
|
12852
|
-
isAdvancedDetectionAvailable
|
|
12853
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
12854
12685
|
let cachedSession = null;
|
|
12855
12686
|
function getCachedCaptureSession() {
|
|
12856
12687
|
return cachedSession;
|
|
@@ -13378,342 +13209,103 @@ const startOnboarding = async (options) => {
|
|
|
13378
13209
|
onError(new NeoFaceError(message, ErrorType.UNKNOWN));
|
|
13379
13210
|
}
|
|
13380
13211
|
};
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
throw new NeoFaceError(
|
|
13396
|
-
"Invalid signature format. Expected HMAC-SHA256 hex string (minimum 64 characters)",
|
|
13397
|
-
ErrorType.VALIDATION_ERROR
|
|
13398
|
-
);
|
|
13399
|
-
}
|
|
13400
|
-
if (this.sessionData) {
|
|
13401
|
-
if (!this.sessionData.email || !this.sessionData.cpf || !this.sessionData.sessionId) {
|
|
13402
|
-
throw new NeoFaceError(
|
|
13403
|
-
"Session data must include email, cpf, and sessionId",
|
|
13404
|
-
ErrorType.VALIDATION_ERROR
|
|
13405
|
-
);
|
|
13406
|
-
}
|
|
13407
|
-
}
|
|
13408
|
-
}
|
|
13409
|
-
/**
|
|
13410
|
-
* Validates the format of a signature
|
|
13411
|
-
* HMAC-SHA256 produces a 64-character hexadecimal string
|
|
13412
|
-
* @param signature The signature to validate
|
|
13413
|
-
* @returns true if valid, false otherwise
|
|
13414
|
-
*/
|
|
13415
|
-
validateSignatureFormat(signature) {
|
|
13416
|
-
return /^[a-f0-9]{64,}$/i.test(signature);
|
|
13417
|
-
}
|
|
13418
|
-
/**
|
|
13419
|
-
* Captures multiple face frames for biometric recognition
|
|
13420
|
-
* @param options Capture options
|
|
13421
|
-
* @returns Promise that resolves to an array of image blobs
|
|
13422
|
-
* @throws NeoFaceError if capture fails
|
|
13423
|
-
*/
|
|
13424
|
-
async captureFaceFrames(options) {
|
|
13425
|
-
const { numFrames, livenessCheck } = options;
|
|
13426
|
-
if (numFrames < 1 || numFrames > 10) {
|
|
13427
|
-
throw new NeoFaceError(
|
|
13428
|
-
"Number of frames must be between 1 and 10",
|
|
13429
|
-
ErrorType.VALIDATION_ERROR
|
|
13430
|
-
);
|
|
13431
|
-
}
|
|
13432
|
-
const frames = [];
|
|
13433
|
-
try {
|
|
13434
|
-
const { captureFaceSilently: captureFaceSilently2 } = await Promise.resolve().then(() => biometricLoginFlow);
|
|
13435
|
-
for (let i = 0; i < numFrames; i++) {
|
|
13436
|
-
const frame = await captureFaceSilently2();
|
|
13437
|
-
frames.push(frame);
|
|
13438
|
-
if (livenessCheck && i < numFrames - 1) {
|
|
13439
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
13440
|
-
}
|
|
13441
|
-
}
|
|
13442
|
-
return frames;
|
|
13443
|
-
} catch (error) {
|
|
13444
|
-
if (error instanceof NeoFaceError) {
|
|
13445
|
-
throw error;
|
|
13446
|
-
}
|
|
13447
|
-
throw new NeoFaceError(
|
|
13448
|
-
`Failed to capture face frames: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13449
|
-
ErrorType.CAPTURE_ERROR
|
|
13450
|
-
);
|
|
13451
|
-
}
|
|
13212
|
+
async function proofOfLife(options = {}) {
|
|
13213
|
+
const {
|
|
13214
|
+
videoDurationMs = 3e3,
|
|
13215
|
+
maxPollingAttempts = 60,
|
|
13216
|
+
pollingIntervalMs = 1e3,
|
|
13217
|
+
onRecordingProgress,
|
|
13218
|
+
onTaskStatusChange
|
|
13219
|
+
} = options;
|
|
13220
|
+
const applicationToken = options.applicationToken ?? getApplicationToken();
|
|
13221
|
+
if (!applicationToken) {
|
|
13222
|
+
throw new NeoFaceError(
|
|
13223
|
+
"proofOfLife exige applicationToken — passe nas opções ou em init({ applicationToken }).",
|
|
13224
|
+
ErrorType.INVALID_TOKEN
|
|
13225
|
+
);
|
|
13452
13226
|
}
|
|
13453
|
-
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
*/
|
|
13479
|
-
async loginRecognition(options) {
|
|
13480
|
-
const { biometricData, purpose, confidenceThreshold = 0.8 } = options;
|
|
13481
|
-
const imageBlobs = Array.isArray(biometricData) ? biometricData : [biometricData];
|
|
13482
|
-
if (imageBlobs.length === 0) {
|
|
13483
|
-
throw new NeoFaceError(
|
|
13484
|
-
"At least one biometric data frame is required",
|
|
13485
|
-
ErrorType.VALIDATION_ERROR
|
|
13486
|
-
);
|
|
13487
|
-
}
|
|
13488
|
-
const primaryImage = imageBlobs[0];
|
|
13489
|
-
try {
|
|
13490
|
-
const result = await recognizeByPurpose(
|
|
13491
|
-
primaryImage,
|
|
13492
|
-
this.appToken,
|
|
13493
|
-
purpose,
|
|
13494
|
-
confidenceThreshold,
|
|
13495
|
-
this.signature,
|
|
13496
|
-
this.sessionData
|
|
13497
|
-
);
|
|
13498
|
-
if (!result.success) {
|
|
13499
|
-
throw new NeoFaceError("Recognition failed", ErrorType.RECOGNITION_FAILED);
|
|
13500
|
-
}
|
|
13501
|
-
if (!result.personName || !result.email || !result.cpf) {
|
|
13502
|
-
throw new NeoFaceError(
|
|
13503
|
-
"Incomplete recognition result: missing personName, email, or cpf",
|
|
13504
|
-
ErrorType.RECOGNITION_FAILED
|
|
13505
|
-
);
|
|
13506
|
-
}
|
|
13227
|
+
try {
|
|
13228
|
+
const videoBlob = await recordFaceVideo({
|
|
13229
|
+
durationMs: videoDurationMs,
|
|
13230
|
+
onProgress: onRecordingProgress
|
|
13231
|
+
});
|
|
13232
|
+
const videoBase64 = await videoToBase64(videoBlob);
|
|
13233
|
+
const started = await startProofOfLife(videoBase64, applicationToken);
|
|
13234
|
+
if (onTaskStatusChange) onTaskStatusChange("processing", 0);
|
|
13235
|
+
const taskResult = await pollTaskStatus(started.taskId, applicationToken, {
|
|
13236
|
+
maxAttempts: maxPollingAttempts,
|
|
13237
|
+
intervalMs: pollingIntervalMs,
|
|
13238
|
+
onStatusChange: onTaskStatusChange
|
|
13239
|
+
});
|
|
13240
|
+
const base = {
|
|
13241
|
+
isLive: (taskResult == null ? void 0 : taskResult.is_live) || false,
|
|
13242
|
+
livenessScore: (taskResult == null ? void 0 : taskResult.liveness_score) || 0,
|
|
13243
|
+
faceRecognitionScore: (taskResult == null ? void 0 : taskResult.face_recognition_score) || 0,
|
|
13244
|
+
combinedConfidence: (taskResult == null ? void 0 : taskResult.combined_confidence) || 0,
|
|
13245
|
+
personId: taskResult == null ? void 0 : taskResult.person_id,
|
|
13246
|
+
personalData: (taskResult == null ? void 0 : taskResult.personal_data) || [],
|
|
13247
|
+
processingTime: (taskResult == null ? void 0 : taskResult.processing_time) || 0,
|
|
13248
|
+
historyId: started.historyId,
|
|
13249
|
+
taskId: started.taskId
|
|
13250
|
+
};
|
|
13251
|
+
if (!taskResult || !taskResult.success) {
|
|
13507
13252
|
return {
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
signature: result.signature,
|
|
13513
|
-
sessionId: result.sessionId,
|
|
13514
|
-
confidenceScore: result.confidenceScore,
|
|
13515
|
-
accessToken: result.accessToken
|
|
13253
|
+
...base,
|
|
13254
|
+
success: false,
|
|
13255
|
+
error: (taskResult == null ? void 0 : taskResult.error) || "Verification failed",
|
|
13256
|
+
message: (taskResult == null ? void 0 : taskResult.message) || "Proof of life verification failed"
|
|
13516
13257
|
};
|
|
13517
|
-
} catch (error) {
|
|
13518
|
-
if (error instanceof NeoFaceError) {
|
|
13519
|
-
throw error;
|
|
13520
|
-
}
|
|
13521
|
-
throw new NeoFaceError(
|
|
13522
|
-
`Login recognition failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13523
|
-
ErrorType.RECOGNITION_FAILED
|
|
13524
|
-
);
|
|
13525
|
-
}
|
|
13526
|
-
}
|
|
13527
|
-
/**
|
|
13528
|
-
* Register a new application for a consumer
|
|
13529
|
-
*
|
|
13530
|
-
* This method allows authenticated users to register new applications
|
|
13531
|
-
* that will receive their own app_token for API access.
|
|
13532
|
-
*
|
|
13533
|
-
* @param jwtToken JWT authentication token from logged user
|
|
13534
|
-
* @param consumerId Consumer ID (UUID) who will own the application
|
|
13535
|
-
* @param applicationData Application registration data
|
|
13536
|
-
* @returns Promise with registration result including app_token
|
|
13537
|
-
* @throws NeoFaceError if registration fails
|
|
13538
|
-
*
|
|
13539
|
-
* @example
|
|
13540
|
-
* ```typescript
|
|
13541
|
-
* const sdk = new NeoFaceID({
|
|
13542
|
-
* appToken: 'your-application-token'
|
|
13543
|
-
* });
|
|
13544
|
-
*
|
|
13545
|
-
* const result = await sdk.registerApplication(
|
|
13546
|
-
* userJwtToken,
|
|
13547
|
-
* consumerUuid,
|
|
13548
|
-
* {
|
|
13549
|
-
* applicationName: 'My New App',
|
|
13550
|
-
* domain: 'example.com',
|
|
13551
|
-
* acceptOnlyEmailWithSameDomain: true
|
|
13552
|
-
* }
|
|
13553
|
-
* );
|
|
13554
|
-
*
|
|
13555
|
-
* // Use the generated app_token for the new application
|
|
13556
|
-
* console.log('New App Token:', result.application.app_token);
|
|
13557
|
-
* ```
|
|
13558
|
-
*/
|
|
13559
|
-
async registerApplication(jwtToken, consumerId, applicationData) {
|
|
13560
|
-
try {
|
|
13561
|
-
const result = await registerApplication(jwtToken, consumerId, applicationData);
|
|
13562
|
-
return result;
|
|
13563
|
-
} catch (error) {
|
|
13564
|
-
if (error instanceof NeoFaceError) {
|
|
13565
|
-
throw error;
|
|
13566
|
-
}
|
|
13567
|
-
throw new NeoFaceError(
|
|
13568
|
-
`Application registration failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13569
|
-
ErrorType.VALIDATION_ERROR
|
|
13570
|
-
);
|
|
13571
13258
|
}
|
|
13259
|
+
return { ...base, success: true, message: "Proof of life verification successful" };
|
|
13260
|
+
} catch (error) {
|
|
13261
|
+
if (error instanceof NeoFaceError) throw error;
|
|
13262
|
+
throw new NeoFaceError(
|
|
13263
|
+
`Proof of life failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13264
|
+
ErrorType.RECOGNITION_FAILED
|
|
13265
|
+
);
|
|
13572
13266
|
}
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
* with personal data filtered by purpose.
|
|
13579
|
-
*
|
|
13580
|
-
* The process is asynchronous:
|
|
13581
|
-
* 1. Records video from the camera (default 3 seconds)
|
|
13582
|
-
* 2. Converts video to base64 and sends to backend
|
|
13583
|
-
* 3. Backend processes liveness detection and face recognition
|
|
13584
|
-
* 4. Polls for task completion
|
|
13585
|
-
* 5. Returns personal data of the identified person
|
|
13586
|
-
*
|
|
13587
|
-
* @param options Configuration options for the proof of life process
|
|
13588
|
-
* @returns Promise that resolves to ProofOfLifeResult
|
|
13589
|
-
* @throws NeoFaceError if verification fails
|
|
13590
|
-
*
|
|
13591
|
-
* @example
|
|
13592
|
-
* ```typescript
|
|
13593
|
-
* const sdk = new NeoFaceID({
|
|
13594
|
-
* appToken: 'your-application-token'
|
|
13595
|
-
* });
|
|
13596
|
-
*
|
|
13597
|
-
* const result = await sdk.proofOfLife({
|
|
13598
|
-
* videoDurationMs: 3000, // 3 seconds
|
|
13599
|
-
* onRecordingProgress: (progress) => {
|
|
13600
|
-
* console.log(`Recording: ${progress}%`);
|
|
13601
|
-
* },
|
|
13602
|
-
* onTaskStatusChange: (status, progress) => {
|
|
13603
|
-
* console.log(`Status: ${status}, Progress: ${progress}%`);
|
|
13604
|
-
* }
|
|
13605
|
-
* });
|
|
13606
|
-
*
|
|
13607
|
-
* if (result.success && result.isLive) {
|
|
13608
|
-
* console.log('Person verified:', result.personalData);
|
|
13609
|
-
* console.log('Liveness score:', result.livenessScore);
|
|
13610
|
-
* console.log('Face recognition score:', result.faceRecognitionScore);
|
|
13611
|
-
* }
|
|
13612
|
-
* ```
|
|
13613
|
-
*/
|
|
13614
|
-
/**
|
|
13615
|
-
* Registers document images for an already-registered donor person.
|
|
13616
|
-
*
|
|
13617
|
-
* Opens the document capture UI (front + optional back), then submits
|
|
13618
|
-
* the images to the backend for extraction via DocExt.
|
|
13619
|
-
* The backend processes this asynchronously — use the returned `taskId`
|
|
13620
|
-
* to poll status if needed.
|
|
13621
|
-
*
|
|
13622
|
-
* @param personId UUID of the donor's person record
|
|
13623
|
-
* @param jwtToken JWT Bearer token of the authenticated donor
|
|
13624
|
-
* @param options Optional capture configuration
|
|
13625
|
-
* @returns Promise with task_id and processing status
|
|
13626
|
-
* @throws NeoFaceError if capture is cancelled, validation fails, or API call fails
|
|
13627
|
-
*
|
|
13628
|
-
* @example
|
|
13629
|
-
* ```typescript
|
|
13630
|
-
* const sdk = new NeoFaceID({ appToken: 'your-token' });
|
|
13631
|
-
*
|
|
13632
|
-
* const result = await sdk.registerDocumentByImage(personId, userJwtToken);
|
|
13633
|
-
* console.log('Task ID:', result.taskId); // poll for completion
|
|
13634
|
-
* ```
|
|
13635
|
-
*/
|
|
13636
|
-
async registerDocumentByImage(personId, jwtToken, options = {}) {
|
|
13637
|
-
const { DocumentCaptureModal: DocumentCaptureModal2 } = await Promise.resolve().then(() => DocumentCaptureModal$1);
|
|
13638
|
-
const modal = new DocumentCaptureModal2({
|
|
13639
|
-
useBackCamera: options.useBackCamera ?? true,
|
|
13640
|
-
preSelectedDocument: options.preSelectedDocument
|
|
13641
|
-
});
|
|
13642
|
-
const captureResult = await modal.open();
|
|
13643
|
-
if (!captureResult.success || !captureResult.frontImage) {
|
|
13644
|
-
throw new NeoFaceError(
|
|
13645
|
-
captureResult.error === "cancelled" ? "Document capture was cancelled by the user" : `Document capture failed: ${captureResult.error ?? "unknown error"}`,
|
|
13646
|
-
ErrorType.CAPTURE_ERROR
|
|
13647
|
-
);
|
|
13648
|
-
}
|
|
13649
|
-
const images = [captureResult.frontImage];
|
|
13650
|
-
if (captureResult.backImage) {
|
|
13651
|
-
images.push(captureResult.backImage);
|
|
13652
|
-
}
|
|
13653
|
-
return registerDocumentByImage(personId, jwtToken, images);
|
|
13267
|
+
}
|
|
13268
|
+
async function captureFaceFrames(options) {
|
|
13269
|
+
const { numFrames, livenessCheck } = options;
|
|
13270
|
+
if (numFrames < 1 || numFrames > 10) {
|
|
13271
|
+
throw new NeoFaceError("Number of frames must be between 1 and 10", ErrorType.VALIDATION_ERROR);
|
|
13654
13272
|
}
|
|
13655
|
-
|
|
13656
|
-
const {
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
const videoBlob = await recordFaceVideo({
|
|
13665
|
-
durationMs: videoDurationMs,
|
|
13666
|
-
onProgress: onRecordingProgress
|
|
13667
|
-
});
|
|
13668
|
-
const videoBase64 = await videoToBase64(videoBlob);
|
|
13669
|
-
const startResult = await startProofOfLife(videoBase64, this.appToken);
|
|
13670
|
-
if (onTaskStatusChange) {
|
|
13671
|
-
onTaskStatusChange("processing", 0);
|
|
13672
|
-
}
|
|
13673
|
-
const taskResult = await pollTaskStatus(startResult.taskId, this.appToken, {
|
|
13674
|
-
maxAttempts: maxPollingAttempts,
|
|
13675
|
-
intervalMs: pollingIntervalMs,
|
|
13676
|
-
onStatusChange: onTaskStatusChange
|
|
13677
|
-
});
|
|
13678
|
-
if (!taskResult || !taskResult.success) {
|
|
13679
|
-
return {
|
|
13680
|
-
success: false,
|
|
13681
|
-
isLive: (taskResult == null ? void 0 : taskResult.is_live) || false,
|
|
13682
|
-
livenessScore: (taskResult == null ? void 0 : taskResult.liveness_score) || 0,
|
|
13683
|
-
faceRecognitionScore: (taskResult == null ? void 0 : taskResult.face_recognition_score) || 0,
|
|
13684
|
-
combinedConfidence: (taskResult == null ? void 0 : taskResult.combined_confidence) || 0,
|
|
13685
|
-
personId: taskResult == null ? void 0 : taskResult.person_id,
|
|
13686
|
-
personalData: (taskResult == null ? void 0 : taskResult.personal_data) || [],
|
|
13687
|
-
processingTime: (taskResult == null ? void 0 : taskResult.processing_time) || 0,
|
|
13688
|
-
historyId: startResult.historyId,
|
|
13689
|
-
taskId: startResult.taskId,
|
|
13690
|
-
error: (taskResult == null ? void 0 : taskResult.error) || "Verification failed",
|
|
13691
|
-
message: (taskResult == null ? void 0 : taskResult.message) || "Proof of life verification failed"
|
|
13692
|
-
};
|
|
13693
|
-
}
|
|
13694
|
-
return {
|
|
13695
|
-
success: true,
|
|
13696
|
-
isLive: taskResult.is_live || false,
|
|
13697
|
-
livenessScore: taskResult.liveness_score || 0,
|
|
13698
|
-
faceRecognitionScore: taskResult.face_recognition_score || 0,
|
|
13699
|
-
combinedConfidence: taskResult.combined_confidence || 0,
|
|
13700
|
-
personId: taskResult.person_id,
|
|
13701
|
-
personalData: taskResult.personal_data || [],
|
|
13702
|
-
processingTime: taskResult.processing_time || 0,
|
|
13703
|
-
historyId: startResult.historyId,
|
|
13704
|
-
taskId: startResult.taskId,
|
|
13705
|
-
message: "Proof of life verification successful"
|
|
13706
|
-
};
|
|
13707
|
-
} catch (error) {
|
|
13708
|
-
if (error instanceof NeoFaceError) {
|
|
13709
|
-
throw error;
|
|
13273
|
+
try {
|
|
13274
|
+
const { captureFaceSilently: captureFaceSilently2 } = await Promise.resolve().then(() => biometricLoginFlow);
|
|
13275
|
+
const frames = [];
|
|
13276
|
+
for (let i = 0; i < numFrames; i += 1) {
|
|
13277
|
+
frames.push(await captureFaceSilently2());
|
|
13278
|
+
if (livenessCheck && i < numFrames - 1) {
|
|
13279
|
+
await new Promise((resolve) => {
|
|
13280
|
+
setTimeout(resolve, 300);
|
|
13281
|
+
});
|
|
13710
13282
|
}
|
|
13711
|
-
throw new NeoFaceError(
|
|
13712
|
-
`Proof of life failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13713
|
-
ErrorType.RECOGNITION_FAILED
|
|
13714
|
-
);
|
|
13715
13283
|
}
|
|
13284
|
+
return frames;
|
|
13285
|
+
} catch (error) {
|
|
13286
|
+
if (error instanceof NeoFaceError) throw error;
|
|
13287
|
+
throw new NeoFaceError(
|
|
13288
|
+
`Failed to capture face frames: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13289
|
+
ErrorType.CAPTURE_ERROR
|
|
13290
|
+
);
|
|
13291
|
+
}
|
|
13292
|
+
}
|
|
13293
|
+
async function registerDocumentByImage(personId, jwtToken, options = {}) {
|
|
13294
|
+
const { DocumentCaptureModal: DocumentCaptureModal2 } = await Promise.resolve().then(() => DocumentCaptureModal$1);
|
|
13295
|
+
const modal = new DocumentCaptureModal2({
|
|
13296
|
+
useBackCamera: options.useBackCamera ?? true,
|
|
13297
|
+
preSelectedDocument: options.preSelectedDocument
|
|
13298
|
+
});
|
|
13299
|
+
const captured = await modal.open();
|
|
13300
|
+
if (!captured.success || !captured.frontImage) {
|
|
13301
|
+
throw new NeoFaceError(
|
|
13302
|
+
captured.error === "cancelled" ? "Document capture was cancelled by the user" : `Document capture failed: ${captured.error ?? "unknown error"}`,
|
|
13303
|
+
ErrorType.CAPTURE_ERROR
|
|
13304
|
+
);
|
|
13716
13305
|
}
|
|
13306
|
+
const images = [captured.frontImage];
|
|
13307
|
+
if (captured.backImage) images.push(captured.backImage);
|
|
13308
|
+
return registerDocumentByImage$1(personId, jwtToken, images);
|
|
13717
13309
|
}
|
|
13718
13310
|
const PHOTO_INSTRUCTIONS = [
|
|
13719
13311
|
"Olhe diretamente para a câmera",
|
|
@@ -15831,7 +15423,6 @@ function startLivenessCapture(applicationToken, callbacks, options) {
|
|
|
15831
15423
|
});
|
|
15832
15424
|
}
|
|
15833
15425
|
export {
|
|
15834
|
-
BiometricCaptureModal,
|
|
15835
15426
|
BiometricRegistrationModal,
|
|
15836
15427
|
BiometricStatusOverlay,
|
|
15837
15428
|
CameraOvalGuide,
|
|
@@ -15845,19 +15436,18 @@ export {
|
|
|
15845
15436
|
FallbackPrompt,
|
|
15846
15437
|
ForgotPasswordModal,
|
|
15847
15438
|
NeoFaceError,
|
|
15848
|
-
NeoFaceID,
|
|
15849
15439
|
RELEASE_DATE,
|
|
15850
15440
|
VERSION,
|
|
15851
15441
|
authorize,
|
|
15852
15442
|
authorizeOperation,
|
|
15853
15443
|
awaitPushApproval,
|
|
15444
|
+
captureFaceFrames,
|
|
15854
15445
|
checkUserExistence,
|
|
15855
15446
|
clearConsentTrail,
|
|
15856
15447
|
completeOnboarding,
|
|
15857
15448
|
completeOnboardingWithData,
|
|
15858
15449
|
confirmPasswordReset,
|
|
15859
15450
|
consumeSseStream,
|
|
15860
|
-
detectBiometricType,
|
|
15861
15451
|
getAccent,
|
|
15862
15452
|
getAppName,
|
|
15863
15453
|
getApplicationToken,
|
|
@@ -15875,19 +15465,20 @@ export {
|
|
|
15875
15465
|
identifyPerson,
|
|
15876
15466
|
identifyPersonAsync,
|
|
15877
15467
|
init,
|
|
15878
|
-
initializeBiometricDetection,
|
|
15879
|
-
isAdvancedDetectionAvailable,
|
|
15880
15468
|
isInitialized,
|
|
15881
15469
|
loginWithBiometric,
|
|
15882
15470
|
loginWithEmail,
|
|
15883
15471
|
openCaptureSession,
|
|
15884
15472
|
preloadFaceDetectionModels,
|
|
15473
|
+
proofOfLife,
|
|
15885
15474
|
recognize,
|
|
15886
15475
|
recognizeBiometric,
|
|
15887
15476
|
recognizeByPurpose,
|
|
15888
15477
|
recordConsent,
|
|
15889
15478
|
refreshSession,
|
|
15479
|
+
registerApplication,
|
|
15890
15480
|
registerBiometric,
|
|
15481
|
+
registerDocumentByImage,
|
|
15891
15482
|
registerPersonWithBiometric,
|
|
15892
15483
|
registerPersonWithoutFace,
|
|
15893
15484
|
requestConsent,
|