@neofaceid/web-sdk 1.44.0 → 2.0.1
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 +804 -1195
- package/dist/neoface-id-sdk.umd.js +1 -1
- package/dist/qrcode-DYfGiWu6.js +1633 -0
- package/package.json +2 -4
|
@@ -6095,9 +6095,22 @@ const recordFaceVideo = async (options = {}) => {
|
|
|
6095
6095
|
const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
|
|
6096
6096
|
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
6097
6097
|
const { maxAttempts = 60, intervalMs = 1e3, onStatusChange } = options;
|
|
6098
|
-
const wait = () => new Promise((resolve) => {
|
|
6099
|
-
setTimeout(resolve,
|
|
6100
|
-
});
|
|
6098
|
+
const wait = (ms = intervalMs) => new Promise((resolve) => {
|
|
6099
|
+
setTimeout(resolve, ms);
|
|
6100
|
+
});
|
|
6101
|
+
const retryAfterMs = (response) => {
|
|
6102
|
+
var _a3, _b2;
|
|
6103
|
+
const raw = (_b2 = (_a3 = response.headers) == null ? void 0 : _a3.get) == null ? void 0 : _b2.call(_a3, "Retry-After");
|
|
6104
|
+
if (!raw) return intervalMs;
|
|
6105
|
+
const seconds = Number(raw);
|
|
6106
|
+
if (Number.isFinite(seconds) && seconds > 0) return seconds * 1e3;
|
|
6107
|
+
const date2 = Date.parse(raw);
|
|
6108
|
+
if (Number.isFinite(date2)) {
|
|
6109
|
+
const delta = date2 - Date.now();
|
|
6110
|
+
if (delta > 0) return delta;
|
|
6111
|
+
}
|
|
6112
|
+
return intervalMs;
|
|
6113
|
+
};
|
|
6101
6114
|
let attempts = 0;
|
|
6102
6115
|
let lastTransient = "";
|
|
6103
6116
|
while (attempts < maxAttempts) {
|
|
@@ -6121,6 +6134,11 @@ const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
|
|
|
6121
6134
|
await wait();
|
|
6122
6135
|
continue;
|
|
6123
6136
|
}
|
|
6137
|
+
if (response.status === 429) {
|
|
6138
|
+
lastTransient = "HTTP 429";
|
|
6139
|
+
await wait(retryAfterMs(response));
|
|
6140
|
+
continue;
|
|
6141
|
+
}
|
|
6124
6142
|
if (response.status === 401 || response.status === 403) {
|
|
6125
6143
|
throw new NeoFaceError("Invalid application token", ErrorType.INVALID_TOKEN);
|
|
6126
6144
|
}
|
|
@@ -6236,7 +6254,7 @@ const blobToBase64 = (blob) => new Promise((resolve, reject) => {
|
|
|
6236
6254
|
reader.onerror = () => reject(new NeoFaceError("Failed to convert image to base64", ErrorType.CAPTURE_ERROR));
|
|
6237
6255
|
reader.readAsDataURL(blob);
|
|
6238
6256
|
});
|
|
6239
|
-
const registerDocumentByImage = async (personId, jwtToken, images) => {
|
|
6257
|
+
const registerDocumentByImage$1 = async (personId, jwtToken, images) => {
|
|
6240
6258
|
var _a2, _b;
|
|
6241
6259
|
ensureSecureContext();
|
|
6242
6260
|
if (!personId) {
|
|
@@ -6409,7 +6427,7 @@ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty(
|
|
|
6409
6427
|
refreshSession,
|
|
6410
6428
|
registerApplication,
|
|
6411
6429
|
registerBiometric,
|
|
6412
|
-
registerDocumentByImage,
|
|
6430
|
+
registerDocumentByImage: registerDocumentByImage$1,
|
|
6413
6431
|
registerPersonWithBiometric,
|
|
6414
6432
|
registerPersonWithoutFace,
|
|
6415
6433
|
requestDeviceEnrollmentToken,
|
|
@@ -7815,17 +7833,17 @@ function BiometricRegistrationModal({
|
|
|
7815
7833
|
}
|
|
7816
7834
|
let isRunning = true;
|
|
7817
7835
|
let lastDetectionTime = 0;
|
|
7818
|
-
const
|
|
7836
|
+
const detectFace = async () => {
|
|
7819
7837
|
if (!isRunning || state.status !== "liveness") {
|
|
7820
7838
|
return;
|
|
7821
7839
|
}
|
|
7822
7840
|
if (isCapturingRef.current || currentStepRef.current === "complete") {
|
|
7823
|
-
requestAnimationFrame(
|
|
7841
|
+
requestAnimationFrame(detectFace);
|
|
7824
7842
|
return;
|
|
7825
7843
|
}
|
|
7826
7844
|
const now = Date.now();
|
|
7827
7845
|
if (now - lastDetectionTime < CALIBRATION.DETECTION_INTERVAL) {
|
|
7828
|
-
requestAnimationFrame(
|
|
7846
|
+
requestAnimationFrame(detectFace);
|
|
7829
7847
|
return;
|
|
7830
7848
|
}
|
|
7831
7849
|
lastDetectionTime = now;
|
|
@@ -7849,7 +7867,7 @@ function BiometricRegistrationModal({
|
|
|
7849
7867
|
}
|
|
7850
7868
|
if (positionStatus !== "ok") {
|
|
7851
7869
|
manageHoldAndCapture(false);
|
|
7852
|
-
requestAnimationFrame(
|
|
7870
|
+
requestAnimationFrame(detectFace);
|
|
7853
7871
|
return;
|
|
7854
7872
|
}
|
|
7855
7873
|
}
|
|
@@ -7874,7 +7892,7 @@ function BiometricRegistrationModal({
|
|
|
7874
7892
|
} catch (error) {
|
|
7875
7893
|
}
|
|
7876
7894
|
if (isRunning && state.status === "liveness") {
|
|
7877
|
-
requestAnimationFrame(
|
|
7895
|
+
requestAnimationFrame(detectFace);
|
|
7878
7896
|
}
|
|
7879
7897
|
};
|
|
7880
7898
|
const startDetection = () => {
|
|
@@ -7884,7 +7902,7 @@ function BiometricRegistrationModal({
|
|
|
7884
7902
|
clearTimeout(watchdogTimerRef.current);
|
|
7885
7903
|
watchdogTimerRef.current = null;
|
|
7886
7904
|
}
|
|
7887
|
-
|
|
7905
|
+
detectFace();
|
|
7888
7906
|
};
|
|
7889
7907
|
const video = videoRef.current;
|
|
7890
7908
|
if (video && video.readyState >= 2) {
|
|
@@ -8898,8 +8916,8 @@ function BiometricRegistrationModal({
|
|
|
8898
8916
|
/* @__PURE__ */ jsx("canvas", { ref: canvasRef })
|
|
8899
8917
|
] });
|
|
8900
8918
|
}
|
|
8901
|
-
const VERSION = "
|
|
8902
|
-
const RELEASE_DATE = "2026-09-
|
|
8919
|
+
const VERSION = "2.0.1";
|
|
8920
|
+
const RELEASE_DATE = "2026-09-20";
|
|
8903
8921
|
const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
|
|
8904
8922
|
const CONSENT_TRAIL_MAX_LIMIT = 100;
|
|
8905
8923
|
async function hashString(inputStr) {
|
|
@@ -10697,704 +10715,106 @@ function CameraOvalGuide({
|
|
|
10697
10715
|
}
|
|
10698
10716
|
);
|
|
10699
10717
|
}
|
|
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);
|
|
10718
|
+
class BiometricStatusOverlay {
|
|
10719
|
+
constructor() {
|
|
10720
|
+
__publicField(this, "container", null);
|
|
10721
|
+
__publicField(this, "currentStatus", "preparing");
|
|
10722
|
+
}
|
|
10723
|
+
getStyles() {
|
|
10724
|
+
return `
|
|
10725
|
+
@keyframes neofaceid-fadeIn {
|
|
10726
|
+
from { opacity: 0; transform: scale(0.9); }
|
|
10727
|
+
to { opacity: 1; transform: scale(1); }
|
|
10781
10728
|
}
|
|
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;
|
|
10729
|
+
|
|
10730
|
+
@keyframes neofaceid-fadeOut {
|
|
10731
|
+
from { opacity: 1; transform: scale(1); }
|
|
10732
|
+
to { opacity: 0; transform: scale(0.9); }
|
|
10795
10733
|
}
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10734
|
+
|
|
10735
|
+
@keyframes neofaceid-pulse {
|
|
10736
|
+
0%, 100% {
|
|
10737
|
+
transform: scale(1);
|
|
10738
|
+
opacity: 1;
|
|
10739
|
+
}
|
|
10740
|
+
50% {
|
|
10741
|
+
transform: scale(1.05);
|
|
10742
|
+
opacity: 0.9;
|
|
10743
|
+
}
|
|
10799
10744
|
}
|
|
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();
|
|
10745
|
+
|
|
10746
|
+
@keyframes neofaceid-scaleIn {
|
|
10747
|
+
0% { transform: scale(0); opacity: 0; }
|
|
10748
|
+
50% { transform: scale(1.1); }
|
|
10749
|
+
100% { transform: scale(1); opacity: 1; }
|
|
10839
10750
|
}
|
|
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;
|
|
10751
|
+
|
|
10752
|
+
@keyframes neofaceid-errorShake {
|
|
10753
|
+
0%, 100% { transform: translateX(0); }
|
|
10754
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
|
10755
|
+
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
|
11089
10756
|
}
|
|
11090
10757
|
|
|
11091
|
-
.neofaceid-
|
|
11092
|
-
position:
|
|
10758
|
+
.neofaceid-overlay-container {
|
|
10759
|
+
position: fixed;
|
|
11093
10760
|
top: 0;
|
|
11094
10761
|
left: 0;
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
background: rgba(0, 0, 0, 0.8);
|
|
10762
|
+
right: 0;
|
|
10763
|
+
bottom: 0;
|
|
11098
10764
|
display: flex;
|
|
11099
10765
|
align-items: center;
|
|
11100
10766
|
justify-content: center;
|
|
11101
|
-
|
|
11102
|
-
|
|
10767
|
+
z-index: 10000;
|
|
10768
|
+
pointer-events: none;
|
|
10769
|
+
animation: neofaceid-fadeIn 0.3s ease-out;
|
|
10770
|
+
font-family: inherit;
|
|
11103
10771
|
}
|
|
11104
10772
|
|
|
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);
|
|
10773
|
+
.neofaceid-overlay-container.fade-out {
|
|
10774
|
+
animation: neofaceid-fadeOut 0.3s ease-out forwards;
|
|
11113
10775
|
}
|
|
11114
10776
|
|
|
11115
|
-
.neofaceid-
|
|
11116
|
-
padding: 20px;
|
|
11117
|
-
border-bottom: 1px solid #e0e0e0;
|
|
10777
|
+
.neofaceid-overlay-content {
|
|
11118
10778
|
display: flex;
|
|
11119
|
-
|
|
10779
|
+
flex-direction: column;
|
|
11120
10780
|
align-items: center;
|
|
10781
|
+
justify-content: center;
|
|
10782
|
+
gap: 24px;
|
|
10783
|
+
pointer-events: auto;
|
|
11121
10784
|
}
|
|
11122
10785
|
|
|
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;
|
|
10786
|
+
.neofaceid-visual-wrapper {
|
|
10787
|
+
position: relative;
|
|
10788
|
+
width: 100px;
|
|
10789
|
+
height: 100px;
|
|
11139
10790
|
display: flex;
|
|
11140
10791
|
align-items: center;
|
|
11141
10792
|
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
10793
|
}
|
|
11149
10794
|
|
|
11150
|
-
.neofaceid-
|
|
11151
|
-
|
|
10795
|
+
.neofaceid-logo {
|
|
10796
|
+
width: 80px;
|
|
10797
|
+
height: 80px;
|
|
10798
|
+
object-fit: contain;
|
|
10799
|
+
transition: all 0.5s ease;
|
|
10800
|
+
filter: drop-shadow(0 4px 12px rgba(0, 89, 196, 0.3));
|
|
11152
10801
|
}
|
|
11153
10802
|
|
|
11154
|
-
|
|
11155
|
-
|
|
10803
|
+
/* Cores por estado */
|
|
10804
|
+
.neofaceid-state-preparing .neofaceid-logo { filter: drop-shadow(0 0 15px rgba(0, 89, 196, 0.4)); }
|
|
10805
|
+
.neofaceid-state-detecting .neofaceid-logo { filter: hue-rotate(180deg) drop-shadow(0 0 15px rgba(59, 130, 246, 0.6)); }
|
|
10806
|
+
.neofaceid-state-waiting-for-face .neofaceid-logo { filter: hue-rotate(30deg) drop-shadow(0 0 15px rgba(251, 146, 60, 0.6)); }
|
|
10807
|
+
.neofaceid-state-capturing .neofaceid-logo { filter: hue-rotate(45deg) drop-shadow(0 0 15px rgba(245, 158, 11, 0.6)); }
|
|
10808
|
+
.neofaceid-state-verifying .neofaceid-logo { filter: hue-rotate(90deg) drop-shadow(0 0 15px rgba(34, 197, 94, 0.6)); }
|
|
10809
|
+
|
|
10810
|
+
.neofaceid-logo.pulsing {
|
|
10811
|
+
animation: neofaceid-pulse 2s ease-in-out infinite;
|
|
11156
10812
|
}
|
|
11157
10813
|
|
|
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); }
|
|
10814
|
+
@keyframes neofaceid-dots {
|
|
10815
|
+
0%, 20% { opacity: 0; transform: translateY(0); }
|
|
10816
|
+
50% { opacity: 1; transform: translateY(-2px); }
|
|
10817
|
+
80%, 100% { opacity: 0; transform: translateY(0); }
|
|
11398
10818
|
}
|
|
11399
10819
|
|
|
11400
10820
|
.neofaceid-status-text {
|
|
@@ -12204,7 +11624,7 @@ async function captureFaceSilently() {
|
|
|
12204
11624
|
reject(new Error("Failed to get canvas context"));
|
|
12205
11625
|
return;
|
|
12206
11626
|
}
|
|
12207
|
-
const
|
|
11627
|
+
const detectFace = async () => {
|
|
12208
11628
|
if (!modelsLoaded) return true;
|
|
12209
11629
|
try {
|
|
12210
11630
|
const detection = await faceapi.detectSingleFace(
|
|
@@ -12216,7 +11636,7 @@ async function captureFaceSilently() {
|
|
|
12216
11636
|
return true;
|
|
12217
11637
|
}
|
|
12218
11638
|
};
|
|
12219
|
-
|
|
11639
|
+
detectFace().then((hasFace) => {
|
|
12220
11640
|
if (!hasFace && faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
|
|
12221
11641
|
faceDetectionAttempts++;
|
|
12222
11642
|
setTimeout(tryCapture, 150);
|
|
@@ -12550,16 +11970,614 @@ async function executeBiometricLoginFlow(options) {
|
|
|
12550
11970
|
);
|
|
12551
11971
|
}, 500);
|
|
12552
11972
|
}
|
|
12553
|
-
}
|
|
12554
|
-
};
|
|
12555
|
-
tryLogin();
|
|
11973
|
+
}
|
|
11974
|
+
};
|
|
11975
|
+
tryLogin();
|
|
11976
|
+
}
|
|
11977
|
+
const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
11978
|
+
__proto__: null,
|
|
11979
|
+
captureFaceSilently,
|
|
11980
|
+
executeBiometricLoginFlow,
|
|
11981
|
+
preloadFaceDetectionModels
|
|
11982
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
11983
|
+
const DARK_THRESHOLD = 0.235;
|
|
11984
|
+
const IMPROVEMENT_TARGET = 0.12;
|
|
11985
|
+
const SAMPLE_INTERVAL_MS = 600;
|
|
11986
|
+
const EVALUATION_DELAY_MS = 600;
|
|
11987
|
+
const MIN_TOGGLE_MS = 3e3;
|
|
11988
|
+
const OVERLAY_ID_PREFIX = "nfid-flash-";
|
|
11989
|
+
function createScreenFlashController(getVideo, options = {}) {
|
|
11990
|
+
const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD;
|
|
11991
|
+
const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET;
|
|
11992
|
+
const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
|
|
11993
|
+
const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS;
|
|
11994
|
+
const zIndex = options.zIndex ?? 10001;
|
|
11995
|
+
const overlayId = `${OVERLAY_ID_PREFIX}${Math.random().toString(36).slice(2, 9)}`;
|
|
11996
|
+
let interval = null;
|
|
11997
|
+
let evalTimer = null;
|
|
11998
|
+
let flashOn = false;
|
|
11999
|
+
let lastBrightness = 0;
|
|
12000
|
+
let darkStreak = 0;
|
|
12001
|
+
let lastToggleAt = 0;
|
|
12002
|
+
let preFlashBrightness = null;
|
|
12003
|
+
let overlayEl = null;
|
|
12004
|
+
let scratchCanvas = null;
|
|
12005
|
+
const showOverlay = () => {
|
|
12006
|
+
if (overlayEl || typeof document === "undefined") return;
|
|
12007
|
+
overlayEl = document.createElement("div");
|
|
12008
|
+
overlayEl.id = overlayId;
|
|
12009
|
+
overlayEl.setAttribute("aria-hidden", "true");
|
|
12010
|
+
overlayEl.style.cssText = `
|
|
12011
|
+
position: fixed; inset: 0; background: #FFFFFF;
|
|
12012
|
+
z-index: ${zIndex}; pointer-events: none;
|
|
12013
|
+
opacity: 0; transition: opacity 200ms ease-out;
|
|
12014
|
+
`;
|
|
12015
|
+
document.body.appendChild(overlayEl);
|
|
12016
|
+
requestAnimationFrame(() => {
|
|
12017
|
+
if (overlayEl) overlayEl.style.opacity = "1";
|
|
12018
|
+
});
|
|
12019
|
+
};
|
|
12020
|
+
const hideOverlay = () => {
|
|
12021
|
+
if (!overlayEl) return;
|
|
12022
|
+
const el = overlayEl;
|
|
12023
|
+
overlayEl = null;
|
|
12024
|
+
el.style.opacity = "0";
|
|
12025
|
+
setTimeout(() => {
|
|
12026
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
12027
|
+
}, 220);
|
|
12028
|
+
};
|
|
12029
|
+
const setFlashOn = (next) => {
|
|
12030
|
+
if (flashOn === next) return;
|
|
12031
|
+
flashOn = next;
|
|
12032
|
+
if (next) showOverlay();
|
|
12033
|
+
else hideOverlay();
|
|
12034
|
+
if (options.onChange) options.onChange(next, lastBrightness);
|
|
12035
|
+
};
|
|
12036
|
+
const tick = () => {
|
|
12037
|
+
if (typeof document !== "undefined" && !scratchCanvas) {
|
|
12038
|
+
scratchCanvas = document.createElement("canvas");
|
|
12039
|
+
}
|
|
12040
|
+
const b = measureFrameBrightness(getVideo(), {
|
|
12041
|
+
scratch: scratchCanvas ?? void 0
|
|
12042
|
+
});
|
|
12043
|
+
lastBrightness = b;
|
|
12044
|
+
const now = Date.now();
|
|
12045
|
+
if (!flashOn) {
|
|
12046
|
+
if (b > 0 && b < darkThreshold) darkStreak += 1;
|
|
12047
|
+
else darkStreak = 0;
|
|
12048
|
+
if (darkStreak >= 2 && now - lastToggleAt >= minToggleMs) {
|
|
12049
|
+
preFlashBrightness = b;
|
|
12050
|
+
lastToggleAt = now;
|
|
12051
|
+
darkStreak = 0;
|
|
12052
|
+
setFlashOn(true);
|
|
12053
|
+
evalTimer = setTimeout(() => {
|
|
12054
|
+
const after = measureFrameBrightness(getVideo(), {
|
|
12055
|
+
scratch: scratchCanvas ?? void 0
|
|
12056
|
+
});
|
|
12057
|
+
const gained = after - (preFlashBrightness ?? 0);
|
|
12058
|
+
if (gained < improvementTarget) {
|
|
12059
|
+
lastToggleAt = Date.now();
|
|
12060
|
+
setFlashOn(false);
|
|
12061
|
+
}
|
|
12062
|
+
preFlashBrightness = null;
|
|
12063
|
+
}, EVALUATION_DELAY_MS);
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
12066
|
+
};
|
|
12067
|
+
return {
|
|
12068
|
+
start() {
|
|
12069
|
+
if (options.enabled === false) return;
|
|
12070
|
+
if (interval) return;
|
|
12071
|
+
tick();
|
|
12072
|
+
interval = setInterval(tick, sampleIntervalMs);
|
|
12073
|
+
},
|
|
12074
|
+
stop() {
|
|
12075
|
+
if (interval) {
|
|
12076
|
+
clearInterval(interval);
|
|
12077
|
+
interval = null;
|
|
12078
|
+
}
|
|
12079
|
+
if (evalTimer) {
|
|
12080
|
+
clearTimeout(evalTimer);
|
|
12081
|
+
evalTimer = null;
|
|
12082
|
+
}
|
|
12083
|
+
setFlashOn(false);
|
|
12084
|
+
darkStreak = 0;
|
|
12085
|
+
preFlashBrightness = null;
|
|
12086
|
+
},
|
|
12087
|
+
destroy() {
|
|
12088
|
+
this.stop();
|
|
12089
|
+
scratchCanvas = null;
|
|
12090
|
+
},
|
|
12091
|
+
get flashOn() {
|
|
12092
|
+
return flashOn;
|
|
12093
|
+
},
|
|
12094
|
+
get lastBrightness() {
|
|
12095
|
+
return lastBrightness;
|
|
12096
|
+
}
|
|
12097
|
+
};
|
|
12098
|
+
}
|
|
12099
|
+
class BiometricCaptureModal {
|
|
12100
|
+
constructor(options) {
|
|
12101
|
+
__publicField(this, "modal", null);
|
|
12102
|
+
__publicField(this, "video", null);
|
|
12103
|
+
__publicField(this, "canvas", null);
|
|
12104
|
+
__publicField(this, "stream", null);
|
|
12105
|
+
__publicField(this, "options");
|
|
12106
|
+
__publicField(this, "countdownInterval", null);
|
|
12107
|
+
__publicField(this, "isCapturing", false);
|
|
12108
|
+
__publicField(this, "flashController", null);
|
|
12109
|
+
this.options = options;
|
|
12110
|
+
}
|
|
12111
|
+
/**
|
|
12112
|
+
* Abre o modal de captura biométrica
|
|
12113
|
+
*/
|
|
12114
|
+
async open() {
|
|
12115
|
+
try {
|
|
12116
|
+
await this.createModal();
|
|
12117
|
+
await this.initializeCamera();
|
|
12118
|
+
this.enableCaptureButton();
|
|
12119
|
+
if (this.options.autoLighting !== false) {
|
|
12120
|
+
this.flashController = createScreenFlashController(() => this.video);
|
|
12121
|
+
this.flashController.start();
|
|
12122
|
+
}
|
|
12123
|
+
} catch (error) {
|
|
12124
|
+
this.options.onError(
|
|
12125
|
+
new NeoFaceError(
|
|
12126
|
+
`Erro ao inicializar câmera: ${error.message}`,
|
|
12127
|
+
ErrorType.CAMERA_ERROR
|
|
12128
|
+
)
|
|
12129
|
+
);
|
|
12130
|
+
}
|
|
12131
|
+
}
|
|
12132
|
+
/**
|
|
12133
|
+
* Fecha o modal e limpa recursos
|
|
12134
|
+
*/
|
|
12135
|
+
close() {
|
|
12136
|
+
this.cleanup();
|
|
12137
|
+
if (this.modal) {
|
|
12138
|
+
document.body.removeChild(this.modal);
|
|
12139
|
+
this.modal = null;
|
|
12140
|
+
}
|
|
12141
|
+
}
|
|
12142
|
+
/**
|
|
12143
|
+
* Cria a estrutura HTML do modal
|
|
12144
|
+
*/
|
|
12145
|
+
async createModal() {
|
|
12146
|
+
this.modal = document.createElement("div");
|
|
12147
|
+
this.modal.className = "neofaceid-root neofaceid-biometric-modal";
|
|
12148
|
+
const title = this.options.title || this.getDefaultTitle();
|
|
12149
|
+
const subtitle = this.options.subtitle || this.getDefaultSubtitle();
|
|
12150
|
+
this.modal.innerHTML = `
|
|
12151
|
+
<div class="neofaceid-modal-overlay">
|
|
12152
|
+
<div class="neofaceid-modal-content">
|
|
12153
|
+
<div class="neofaceid-modal-header">
|
|
12154
|
+
<h2>${title}</h2>
|
|
12155
|
+
<button class="neofaceid-close-btn" type="button" aria-label="Fechar">×</button>
|
|
12156
|
+
</div>
|
|
12157
|
+
<div class="neofaceid-modal-body">
|
|
12158
|
+
<p class="neofaceid-subtitle">${subtitle}</p>
|
|
12159
|
+
<div class="neofaceid-camera-container">
|
|
12160
|
+
<video class="neofaceid-video" autoplay muted playsinline></video>
|
|
12161
|
+
<canvas class="neofaceid-canvas" style="display: none;"></canvas>
|
|
12162
|
+
<div class="neofaceid-overlay">
|
|
12163
|
+
<div class="neofaceid-frame"></div>
|
|
12164
|
+
</div>
|
|
12165
|
+
</div>
|
|
12166
|
+
<div class="neofaceid-status">
|
|
12167
|
+
<p class="neofaceid-status-text">Posicione-se na frente da câmera</p>
|
|
12168
|
+
</div>
|
|
12169
|
+
</div>
|
|
12170
|
+
<div class="neofaceid-modal-footer">
|
|
12171
|
+
<button class="neofaceid-cancel-btn" type="button">Cancelar</button>
|
|
12172
|
+
<button class="neofaceid-capture-btn" type="button" disabled>Capturar</button>
|
|
12173
|
+
</div>
|
|
12174
|
+
<div class="neofaceid-seal-row">
|
|
12175
|
+
${renderBrandFooter()}
|
|
12176
|
+
</div>
|
|
12177
|
+
</div>
|
|
12178
|
+
</div>
|
|
12179
|
+
`;
|
|
12180
|
+
injectThemeStyles();
|
|
12181
|
+
this.addStyles();
|
|
12182
|
+
this.addEventListeners();
|
|
12183
|
+
document.body.appendChild(this.modal);
|
|
12184
|
+
}
|
|
12185
|
+
/**
|
|
12186
|
+
* Inicializa a câmera
|
|
12187
|
+
*/
|
|
12188
|
+
async initializeCamera() {
|
|
12189
|
+
if (!this.modal) throw new Error("Modal não inicializado");
|
|
12190
|
+
this.video = this.modal.querySelector(".neofaceid-video");
|
|
12191
|
+
this.canvas = this.modal.querySelector(".neofaceid-canvas");
|
|
12192
|
+
if (!this.video || !this.canvas) {
|
|
12193
|
+
throw new Error("Elementos de vídeo ou canvas não encontrados");
|
|
12194
|
+
}
|
|
12195
|
+
try {
|
|
12196
|
+
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
12197
|
+
video: {
|
|
12198
|
+
width: { ideal: 640 },
|
|
12199
|
+
height: { ideal: 480 },
|
|
12200
|
+
facingMode: "user"
|
|
12201
|
+
},
|
|
12202
|
+
audio: false
|
|
12203
|
+
});
|
|
12204
|
+
this.video.srcObject = this.stream;
|
|
12205
|
+
await this.video.play();
|
|
12206
|
+
this.canvas.width = this.video.videoWidth;
|
|
12207
|
+
this.canvas.height = this.video.videoHeight;
|
|
12208
|
+
} catch (error) {
|
|
12209
|
+
throw new Error(`Não foi possível acessar a câmera: ${error.message}`);
|
|
12210
|
+
}
|
|
12211
|
+
}
|
|
12212
|
+
/**
|
|
12213
|
+
* NEO-413 · US14.6 (item 18) — a contagem "3, 2, 1" foi removida.
|
|
12214
|
+
* Agora habilita direto o botão "Capturar" assim que a câmera fica pronta.
|
|
12215
|
+
* Vivacidade fica com o desafio `runLivenessChallenge` (NEO-408), servidor decide.
|
|
12216
|
+
*/
|
|
12217
|
+
enableCaptureButton() {
|
|
12218
|
+
if (!this.modal) return;
|
|
12219
|
+
const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
|
|
12220
|
+
const statusText = this.modal.querySelector(".neofaceid-status-text");
|
|
12221
|
+
if (captureBtn) {
|
|
12222
|
+
captureBtn.disabled = false;
|
|
12223
|
+
captureBtn.textContent = "Capturar";
|
|
12224
|
+
}
|
|
12225
|
+
if (statusText) {
|
|
12226
|
+
statusText.textContent = "Pronto para capturar";
|
|
12227
|
+
}
|
|
12228
|
+
if (this.options.mode === "auto") {
|
|
12229
|
+
setTimeout(() => this.captureImage(), 300);
|
|
12230
|
+
}
|
|
12231
|
+
}
|
|
12232
|
+
/**
|
|
12233
|
+
* Captura a imagem da câmera
|
|
12234
|
+
*/
|
|
12235
|
+
async captureImage() {
|
|
12236
|
+
if (this.isCapturing || !this.video || !this.canvas) return;
|
|
12237
|
+
this.isCapturing = true;
|
|
12238
|
+
try {
|
|
12239
|
+
const context = this.canvas.getContext("2d");
|
|
12240
|
+
if (!context) throw new Error("Não foi possível obter contexto do canvas");
|
|
12241
|
+
context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
|
12242
|
+
const imageData = this.canvas.toDataURL("image/jpeg", 0.8);
|
|
12243
|
+
const base64Data = imageData.split(",")[1];
|
|
12244
|
+
const dataUrl = `data:image/jpeg;base64,${base64Data}`;
|
|
12245
|
+
const detectedType = this.options.mode === "auto" ? await this.detectBiometricType(base64Data) : this.options.mode;
|
|
12246
|
+
this.close();
|
|
12247
|
+
this.options.onSuccess(dataUrl, detectedType);
|
|
12248
|
+
} catch (error) {
|
|
12249
|
+
this.options.onError(
|
|
12250
|
+
new NeoFaceError(
|
|
12251
|
+
`Erro ao capturar imagem: ${error.message}`,
|
|
12252
|
+
ErrorType.CAPTURE_ERROR
|
|
12253
|
+
)
|
|
12254
|
+
);
|
|
12255
|
+
} finally {
|
|
12256
|
+
this.isCapturing = false;
|
|
12257
|
+
}
|
|
12258
|
+
}
|
|
12259
|
+
/**
|
|
12260
|
+
* Detecta o tipo biométrico na imagem (face ou mão)
|
|
12261
|
+
*/
|
|
12262
|
+
async detectBiometricType(imageData) {
|
|
12263
|
+
try {
|
|
12264
|
+
const { detectBiometricType } = await import("./biometricDetection-8utmtR25.js");
|
|
12265
|
+
const result = await detectBiometricType(imageData);
|
|
12266
|
+
if (result.type !== "unknown" && result.confidence > 0.6) {
|
|
12267
|
+
return result.type;
|
|
12268
|
+
}
|
|
12269
|
+
return "face";
|
|
12270
|
+
} catch (error) {
|
|
12271
|
+
console.warn("Erro na detecção automática, usando face como padrão:", error);
|
|
12272
|
+
return "face";
|
|
12273
|
+
}
|
|
12274
|
+
}
|
|
12275
|
+
/**
|
|
12276
|
+
* Adiciona event listeners aos elementos do modal
|
|
12277
|
+
*/
|
|
12278
|
+
addEventListeners() {
|
|
12279
|
+
if (!this.modal) return;
|
|
12280
|
+
const closeBtn = this.modal.querySelector(".neofaceid-close-btn");
|
|
12281
|
+
const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
|
|
12282
|
+
const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
|
|
12283
|
+
closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
|
|
12284
|
+
var _a2, _b;
|
|
12285
|
+
this.close();
|
|
12286
|
+
(_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
|
|
12287
|
+
});
|
|
12288
|
+
cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
|
|
12289
|
+
var _a2, _b;
|
|
12290
|
+
this.close();
|
|
12291
|
+
(_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
|
|
12292
|
+
});
|
|
12293
|
+
captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
|
|
12294
|
+
this.captureImage();
|
|
12295
|
+
});
|
|
12296
|
+
this.modal.addEventListener("click", (e) => {
|
|
12297
|
+
var _a2, _b, _c;
|
|
12298
|
+
if (e.target === ((_a2 = this.modal) == null ? void 0 : _a2.querySelector(".neofaceid-modal-overlay"))) {
|
|
12299
|
+
this.close();
|
|
12300
|
+
(_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
|
|
12301
|
+
}
|
|
12302
|
+
});
|
|
12303
|
+
}
|
|
12304
|
+
/**
|
|
12305
|
+
* Limpa recursos (câmera, intervalos, etc.)
|
|
12306
|
+
*/
|
|
12307
|
+
cleanup() {
|
|
12308
|
+
if (this.flashController) {
|
|
12309
|
+
this.flashController.destroy();
|
|
12310
|
+
this.flashController = null;
|
|
12311
|
+
}
|
|
12312
|
+
if (this.stream) {
|
|
12313
|
+
this.stream.getTracks().forEach((track) => track.stop());
|
|
12314
|
+
this.stream = null;
|
|
12315
|
+
}
|
|
12316
|
+
if (this.countdownInterval) {
|
|
12317
|
+
clearInterval(this.countdownInterval);
|
|
12318
|
+
this.countdownInterval = null;
|
|
12319
|
+
}
|
|
12320
|
+
this.video = null;
|
|
12321
|
+
this.canvas = null;
|
|
12322
|
+
this.isCapturing = false;
|
|
12323
|
+
}
|
|
12324
|
+
/**
|
|
12325
|
+
* Retorna o título padrão baseado no modo
|
|
12326
|
+
*/
|
|
12327
|
+
getDefaultTitle() {
|
|
12328
|
+
switch (this.options.mode) {
|
|
12329
|
+
case "face":
|
|
12330
|
+
return "Autenticação Facial";
|
|
12331
|
+
case "hand":
|
|
12332
|
+
return "Autenticação por Mão";
|
|
12333
|
+
case "auto":
|
|
12334
|
+
return "Autenticação Biométrica";
|
|
12335
|
+
default:
|
|
12336
|
+
return "Captura Biométrica";
|
|
12337
|
+
}
|
|
12338
|
+
}
|
|
12339
|
+
/**
|
|
12340
|
+
* Retorna o subtítulo padrão baseado no modo
|
|
12341
|
+
*/
|
|
12342
|
+
getDefaultSubtitle() {
|
|
12343
|
+
switch (this.options.mode) {
|
|
12344
|
+
case "face":
|
|
12345
|
+
return "Posicione seu rosto dentro do quadro e aguarde a captura automática.";
|
|
12346
|
+
case "hand":
|
|
12347
|
+
return "Posicione sua mão dentro do quadro e aguarde a captura automática.";
|
|
12348
|
+
case "auto":
|
|
12349
|
+
return "Posicione seu rosto ou mão dentro do quadro para autenticação automática.";
|
|
12350
|
+
default:
|
|
12351
|
+
return "Posicione-se dentro do quadro para captura.";
|
|
12352
|
+
}
|
|
12353
|
+
}
|
|
12354
|
+
/**
|
|
12355
|
+
* Adiciona estilos CSS ao modal
|
|
12356
|
+
*/
|
|
12357
|
+
addStyles() {
|
|
12358
|
+
const styleId = "neofaceid-biometric-modal-styles";
|
|
12359
|
+
const existingStyles = document.getElementById(styleId);
|
|
12360
|
+
if (existingStyles) {
|
|
12361
|
+
existingStyles.remove();
|
|
12362
|
+
}
|
|
12363
|
+
injectScopedStyles(styleId, `
|
|
12364
|
+
.neofaceid-biometric-modal {
|
|
12365
|
+
position: fixed;
|
|
12366
|
+
top: 0;
|
|
12367
|
+
left: 0;
|
|
12368
|
+
width: 100%;
|
|
12369
|
+
height: 100%;
|
|
12370
|
+
z-index: 10000;
|
|
12371
|
+
font-family: inherit;
|
|
12372
|
+
}
|
|
12373
|
+
|
|
12374
|
+
.neofaceid-modal-overlay {
|
|
12375
|
+
position: absolute;
|
|
12376
|
+
top: 0;
|
|
12377
|
+
left: 0;
|
|
12378
|
+
width: 100%;
|
|
12379
|
+
height: 100%;
|
|
12380
|
+
background: rgba(0, 0, 0, 0.8);
|
|
12381
|
+
display: flex;
|
|
12382
|
+
align-items: center;
|
|
12383
|
+
justify-content: center;
|
|
12384
|
+
padding: 20px;
|
|
12385
|
+
box-sizing: border-box;
|
|
12386
|
+
}
|
|
12387
|
+
|
|
12388
|
+
.neofaceid-modal-content {
|
|
12389
|
+
background: white;
|
|
12390
|
+
border-radius: 12px;
|
|
12391
|
+
max-width: 600px;
|
|
12392
|
+
width: 100%;
|
|
12393
|
+
max-height: 90vh;
|
|
12394
|
+
overflow: hidden;
|
|
12395
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
|
12396
|
+
}
|
|
12397
|
+
|
|
12398
|
+
.neofaceid-modal-header {
|
|
12399
|
+
padding: 20px;
|
|
12400
|
+
border-bottom: 1px solid #e0e0e0;
|
|
12401
|
+
display: flex;
|
|
12402
|
+
justify-content: space-between;
|
|
12403
|
+
align-items: center;
|
|
12404
|
+
}
|
|
12405
|
+
|
|
12406
|
+
.neofaceid-modal-header h2 {
|
|
12407
|
+
margin: 0;
|
|
12408
|
+
font-size: 24px;
|
|
12409
|
+
font-weight: 600;
|
|
12410
|
+
color: #333;
|
|
12411
|
+
}
|
|
12412
|
+
|
|
12413
|
+
.neofaceid-close-btn {
|
|
12414
|
+
background: none;
|
|
12415
|
+
border: none;
|
|
12416
|
+
font-size: 22px;
|
|
12417
|
+
cursor: pointer;
|
|
12418
|
+
color: #666;
|
|
12419
|
+
padding: 0;
|
|
12420
|
+
min-width: 44px;
|
|
12421
|
+
min-height: 44px;
|
|
12422
|
+
display: flex;
|
|
12423
|
+
align-items: center;
|
|
12424
|
+
justify-content: center;
|
|
12425
|
+
border-radius: 22px;
|
|
12426
|
+
transition: background-color 0.2s;
|
|
12427
|
+
}
|
|
12428
|
+
.neofaceid-close-btn:focus-visible {
|
|
12429
|
+
outline: 2px solid var(--nfid-focus-ring);
|
|
12430
|
+
outline-offset: 2px;
|
|
12431
|
+
}
|
|
12432
|
+
|
|
12433
|
+
.neofaceid-close-btn:hover {
|
|
12434
|
+
background-color: #f0f0f0;
|
|
12435
|
+
}
|
|
12436
|
+
|
|
12437
|
+
.neofaceid-modal-body {
|
|
12438
|
+
padding: 20px;
|
|
12439
|
+
}
|
|
12440
|
+
|
|
12441
|
+
.neofaceid-subtitle {
|
|
12442
|
+
margin: 0 0 20px 0;
|
|
12443
|
+
color: #666;
|
|
12444
|
+
font-size: 16px;
|
|
12445
|
+
line-height: 1.4;
|
|
12446
|
+
}
|
|
12447
|
+
|
|
12448
|
+
.neofaceid-camera-container {
|
|
12449
|
+
position: relative;
|
|
12450
|
+
background: #000;
|
|
12451
|
+
border-radius: 8px;
|
|
12452
|
+
overflow: hidden;
|
|
12453
|
+
aspect-ratio: 4/3;
|
|
12454
|
+
margin-bottom: 20px;
|
|
12455
|
+
}
|
|
12456
|
+
|
|
12457
|
+
.neofaceid-video {
|
|
12458
|
+
width: 100%;
|
|
12459
|
+
height: 100%;
|
|
12460
|
+
object-fit: cover;
|
|
12461
|
+
}
|
|
12462
|
+
|
|
12463
|
+
.neofaceid-canvas {
|
|
12464
|
+
position: absolute;
|
|
12465
|
+
top: 0;
|
|
12466
|
+
left: 0;
|
|
12467
|
+
}
|
|
12468
|
+
|
|
12469
|
+
.neofaceid-overlay {
|
|
12470
|
+
position: absolute;
|
|
12471
|
+
top: 0;
|
|
12472
|
+
left: 0;
|
|
12473
|
+
width: 100%;
|
|
12474
|
+
height: 100%;
|
|
12475
|
+
display: flex;
|
|
12476
|
+
align-items: center;
|
|
12477
|
+
justify-content: center;
|
|
12478
|
+
}
|
|
12479
|
+
|
|
12480
|
+
.neofaceid-frame {
|
|
12481
|
+
width: 200px;
|
|
12482
|
+
height: 200px;
|
|
12483
|
+
border: 3px solid #0E9F6E;
|
|
12484
|
+
border-radius: 50%;
|
|
12485
|
+
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
|
|
12486
|
+
animation: pulse 2s infinite;
|
|
12487
|
+
}
|
|
12488
|
+
|
|
12489
|
+
.neofaceid-seal-row {
|
|
12490
|
+
display: flex;
|
|
12491
|
+
justify-content: center;
|
|
12492
|
+
padding: 12px 20px 16px;
|
|
12493
|
+
border-top: 1px solid var(--nfid-line, #E6ECF4);
|
|
12494
|
+
}
|
|
12495
|
+
.neofaceid-seal {
|
|
12496
|
+
font-size: 10px;
|
|
12497
|
+
font-weight: 500;
|
|
12498
|
+
letter-spacing: 0.06em;
|
|
12499
|
+
text-transform: uppercase;
|
|
12500
|
+
color: var(--nfid-text-subtle, #617489);
|
|
12501
|
+
}
|
|
12502
|
+
|
|
12503
|
+
.neofaceid-status {
|
|
12504
|
+
text-align: center;
|
|
12505
|
+
margin-bottom: 20px;
|
|
12506
|
+
}
|
|
12507
|
+
|
|
12508
|
+
.neofaceid-status-text {
|
|
12509
|
+
margin: 0;
|
|
12510
|
+
color: #666;
|
|
12511
|
+
font-size: 16px;
|
|
12512
|
+
}
|
|
12513
|
+
|
|
12514
|
+
.neofaceid-modal-footer {
|
|
12515
|
+
padding: 20px;
|
|
12516
|
+
border-top: 1px solid #e0e0e0;
|
|
12517
|
+
display: flex;
|
|
12518
|
+
gap: 12px;
|
|
12519
|
+
justify-content: flex-end;
|
|
12520
|
+
}
|
|
12521
|
+
|
|
12522
|
+
.neofaceid-cancel-btn,
|
|
12523
|
+
.neofaceid-capture-btn {
|
|
12524
|
+
padding: 12px 24px;
|
|
12525
|
+
border: none;
|
|
12526
|
+
border-radius: 6px;
|
|
12527
|
+
font-size: 16px;
|
|
12528
|
+
font-weight: 500;
|
|
12529
|
+
cursor: pointer;
|
|
12530
|
+
transition: all 0.2s;
|
|
12531
|
+
}
|
|
12532
|
+
|
|
12533
|
+
.neofaceid-cancel-btn {
|
|
12534
|
+
background: #f5f5f5;
|
|
12535
|
+
color: #666;
|
|
12536
|
+
}
|
|
12537
|
+
|
|
12538
|
+
.neofaceid-cancel-btn:hover {
|
|
12539
|
+
background: #e0e0e0;
|
|
12540
|
+
}
|
|
12541
|
+
|
|
12542
|
+
.neofaceid-capture-btn {
|
|
12543
|
+
background: #0E9F6E;
|
|
12544
|
+
color: white;
|
|
12545
|
+
}
|
|
12546
|
+
|
|
12547
|
+
.neofaceid-capture-btn:hover:not(:disabled) {
|
|
12548
|
+
background: #45a049;
|
|
12549
|
+
}
|
|
12550
|
+
|
|
12551
|
+
.neofaceid-capture-btn:disabled {
|
|
12552
|
+
background: #ccc;
|
|
12553
|
+
cursor: not-allowed;
|
|
12554
|
+
}
|
|
12555
|
+
|
|
12556
|
+
@keyframes pulse {
|
|
12557
|
+
0% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
|
|
12558
|
+
50% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0.1); }
|
|
12559
|
+
100% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
|
|
12560
|
+
}
|
|
12561
|
+
|
|
12562
|
+
@media (max-width: 640px) {
|
|
12563
|
+
.neofaceid-modal-overlay {
|
|
12564
|
+
padding: 10px;
|
|
12565
|
+
}
|
|
12566
|
+
|
|
12567
|
+
.neofaceid-modal-header,
|
|
12568
|
+
.neofaceid-modal-body,
|
|
12569
|
+
.neofaceid-modal-footer {
|
|
12570
|
+
padding: 15px;
|
|
12571
|
+
}
|
|
12572
|
+
|
|
12573
|
+
.neofaceid-frame {
|
|
12574
|
+
width: 150px;
|
|
12575
|
+
height: 150px;
|
|
12576
|
+
}
|
|
12577
|
+
}
|
|
12578
|
+
`);
|
|
12579
|
+
}
|
|
12556
12580
|
}
|
|
12557
|
-
const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
12558
|
-
__proto__: null,
|
|
12559
|
-
captureFaceSilently,
|
|
12560
|
-
executeBiometricLoginFlow,
|
|
12561
|
-
preloadFaceDetectionModels
|
|
12562
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
12563
12581
|
const CONSENT_DENIED_MESSAGE$1 = "Consentimento recusado pelo usuário.";
|
|
12564
12582
|
function openPasswordFallback(options) {
|
|
12565
12583
|
if (!getAutoFallback().toPassword) {
|
|
@@ -12682,175 +12700,6 @@ async function startAutoLogin(options) {
|
|
|
12682
12700
|
);
|
|
12683
12701
|
}
|
|
12684
12702
|
}
|
|
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
12703
|
let cachedSession = null;
|
|
12855
12704
|
function getCachedCaptureSession() {
|
|
12856
12705
|
return cachedSession;
|
|
@@ -13378,342 +13227,103 @@ const startOnboarding = async (options) => {
|
|
|
13378
13227
|
onError(new NeoFaceError(message, ErrorType.UNKNOWN));
|
|
13379
13228
|
}
|
|
13380
13229
|
};
|
|
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
|
-
}
|
|
13230
|
+
async function proofOfLife(options = {}) {
|
|
13231
|
+
const {
|
|
13232
|
+
videoDurationMs = 3e3,
|
|
13233
|
+
maxPollingAttempts = 60,
|
|
13234
|
+
pollingIntervalMs = 1e3,
|
|
13235
|
+
onRecordingProgress,
|
|
13236
|
+
onTaskStatusChange
|
|
13237
|
+
} = options;
|
|
13238
|
+
const applicationToken = options.applicationToken ?? getApplicationToken();
|
|
13239
|
+
if (!applicationToken) {
|
|
13240
|
+
throw new NeoFaceError(
|
|
13241
|
+
"proofOfLife exige applicationToken — passe nas opções ou em init({ applicationToken }).",
|
|
13242
|
+
ErrorType.INVALID_TOKEN
|
|
13243
|
+
);
|
|
13452
13244
|
}
|
|
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
|
-
}
|
|
13245
|
+
try {
|
|
13246
|
+
const videoBlob = await recordFaceVideo({
|
|
13247
|
+
durationMs: videoDurationMs,
|
|
13248
|
+
onProgress: onRecordingProgress
|
|
13249
|
+
});
|
|
13250
|
+
const videoBase64 = await videoToBase64(videoBlob);
|
|
13251
|
+
const started = await startProofOfLife(videoBase64, applicationToken);
|
|
13252
|
+
if (onTaskStatusChange) onTaskStatusChange("processing", 0);
|
|
13253
|
+
const taskResult = await pollTaskStatus(started.taskId, applicationToken, {
|
|
13254
|
+
maxAttempts: maxPollingAttempts,
|
|
13255
|
+
intervalMs: pollingIntervalMs,
|
|
13256
|
+
onStatusChange: onTaskStatusChange
|
|
13257
|
+
});
|
|
13258
|
+
const base = {
|
|
13259
|
+
isLive: (taskResult == null ? void 0 : taskResult.is_live) || false,
|
|
13260
|
+
livenessScore: (taskResult == null ? void 0 : taskResult.liveness_score) || 0,
|
|
13261
|
+
faceRecognitionScore: (taskResult == null ? void 0 : taskResult.face_recognition_score) || 0,
|
|
13262
|
+
combinedConfidence: (taskResult == null ? void 0 : taskResult.combined_confidence) || 0,
|
|
13263
|
+
personId: taskResult == null ? void 0 : taskResult.person_id,
|
|
13264
|
+
personalData: (taskResult == null ? void 0 : taskResult.personal_data) || [],
|
|
13265
|
+
processingTime: (taskResult == null ? void 0 : taskResult.processing_time) || 0,
|
|
13266
|
+
historyId: started.historyId,
|
|
13267
|
+
taskId: started.taskId
|
|
13268
|
+
};
|
|
13269
|
+
if (!taskResult || !taskResult.success) {
|
|
13507
13270
|
return {
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
signature: result.signature,
|
|
13513
|
-
sessionId: result.sessionId,
|
|
13514
|
-
confidenceScore: result.confidenceScore,
|
|
13515
|
-
accessToken: result.accessToken
|
|
13271
|
+
...base,
|
|
13272
|
+
success: false,
|
|
13273
|
+
error: (taskResult == null ? void 0 : taskResult.error) || "Verification failed",
|
|
13274
|
+
message: (taskResult == null ? void 0 : taskResult.message) || "Proof of life verification failed"
|
|
13516
13275
|
};
|
|
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
13276
|
}
|
|
13277
|
+
return { ...base, success: true, message: "Proof of life verification successful" };
|
|
13278
|
+
} catch (error) {
|
|
13279
|
+
if (error instanceof NeoFaceError) throw error;
|
|
13280
|
+
throw new NeoFaceError(
|
|
13281
|
+
`Proof of life failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13282
|
+
ErrorType.RECOGNITION_FAILED
|
|
13283
|
+
);
|
|
13572
13284
|
}
|
|
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);
|
|
13285
|
+
}
|
|
13286
|
+
async function captureFaceFrames(options) {
|
|
13287
|
+
const { numFrames, livenessCheck } = options;
|
|
13288
|
+
if (numFrames < 1 || numFrames > 10) {
|
|
13289
|
+
throw new NeoFaceError("Number of frames must be between 1 and 10", ErrorType.VALIDATION_ERROR);
|
|
13654
13290
|
}
|
|
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;
|
|
13291
|
+
try {
|
|
13292
|
+
const { captureFaceSilently: captureFaceSilently2 } = await Promise.resolve().then(() => biometricLoginFlow);
|
|
13293
|
+
const frames = [];
|
|
13294
|
+
for (let i = 0; i < numFrames; i += 1) {
|
|
13295
|
+
frames.push(await captureFaceSilently2());
|
|
13296
|
+
if (livenessCheck && i < numFrames - 1) {
|
|
13297
|
+
await new Promise((resolve) => {
|
|
13298
|
+
setTimeout(resolve, 300);
|
|
13299
|
+
});
|
|
13710
13300
|
}
|
|
13711
|
-
throw new NeoFaceError(
|
|
13712
|
-
`Proof of life failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13713
|
-
ErrorType.RECOGNITION_FAILED
|
|
13714
|
-
);
|
|
13715
13301
|
}
|
|
13302
|
+
return frames;
|
|
13303
|
+
} catch (error) {
|
|
13304
|
+
if (error instanceof NeoFaceError) throw error;
|
|
13305
|
+
throw new NeoFaceError(
|
|
13306
|
+
`Failed to capture face frames: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
13307
|
+
ErrorType.CAPTURE_ERROR
|
|
13308
|
+
);
|
|
13309
|
+
}
|
|
13310
|
+
}
|
|
13311
|
+
async function registerDocumentByImage(personId, jwtToken, options = {}) {
|
|
13312
|
+
const { DocumentCaptureModal: DocumentCaptureModal2 } = await Promise.resolve().then(() => DocumentCaptureModal$1);
|
|
13313
|
+
const modal = new DocumentCaptureModal2({
|
|
13314
|
+
useBackCamera: options.useBackCamera ?? true,
|
|
13315
|
+
preSelectedDocument: options.preSelectedDocument
|
|
13316
|
+
});
|
|
13317
|
+
const captured = await modal.open();
|
|
13318
|
+
if (!captured.success || !captured.frontImage) {
|
|
13319
|
+
throw new NeoFaceError(
|
|
13320
|
+
captured.error === "cancelled" ? "Document capture was cancelled by the user" : `Document capture failed: ${captured.error ?? "unknown error"}`,
|
|
13321
|
+
ErrorType.CAPTURE_ERROR
|
|
13322
|
+
);
|
|
13716
13323
|
}
|
|
13324
|
+
const images = [captured.frontImage];
|
|
13325
|
+
if (captured.backImage) images.push(captured.backImage);
|
|
13326
|
+
return registerDocumentByImage$1(personId, jwtToken, images);
|
|
13717
13327
|
}
|
|
13718
13328
|
const PHOTO_INSTRUCTIONS = [
|
|
13719
13329
|
"Olhe diretamente para a câmera",
|
|
@@ -15831,7 +15441,6 @@ function startLivenessCapture(applicationToken, callbacks, options) {
|
|
|
15831
15441
|
});
|
|
15832
15442
|
}
|
|
15833
15443
|
export {
|
|
15834
|
-
BiometricCaptureModal,
|
|
15835
15444
|
BiometricRegistrationModal,
|
|
15836
15445
|
BiometricStatusOverlay,
|
|
15837
15446
|
CameraOvalGuide,
|
|
@@ -15845,19 +15454,18 @@ export {
|
|
|
15845
15454
|
FallbackPrompt,
|
|
15846
15455
|
ForgotPasswordModal,
|
|
15847
15456
|
NeoFaceError,
|
|
15848
|
-
NeoFaceID,
|
|
15849
15457
|
RELEASE_DATE,
|
|
15850
15458
|
VERSION,
|
|
15851
15459
|
authorize,
|
|
15852
15460
|
authorizeOperation,
|
|
15853
15461
|
awaitPushApproval,
|
|
15462
|
+
captureFaceFrames,
|
|
15854
15463
|
checkUserExistence,
|
|
15855
15464
|
clearConsentTrail,
|
|
15856
15465
|
completeOnboarding,
|
|
15857
15466
|
completeOnboardingWithData,
|
|
15858
15467
|
confirmPasswordReset,
|
|
15859
15468
|
consumeSseStream,
|
|
15860
|
-
detectBiometricType,
|
|
15861
15469
|
getAccent,
|
|
15862
15470
|
getAppName,
|
|
15863
15471
|
getApplicationToken,
|
|
@@ -15875,19 +15483,20 @@ export {
|
|
|
15875
15483
|
identifyPerson,
|
|
15876
15484
|
identifyPersonAsync,
|
|
15877
15485
|
init,
|
|
15878
|
-
initializeBiometricDetection,
|
|
15879
|
-
isAdvancedDetectionAvailable,
|
|
15880
15486
|
isInitialized,
|
|
15881
15487
|
loginWithBiometric,
|
|
15882
15488
|
loginWithEmail,
|
|
15883
15489
|
openCaptureSession,
|
|
15884
15490
|
preloadFaceDetectionModels,
|
|
15491
|
+
proofOfLife,
|
|
15885
15492
|
recognize,
|
|
15886
15493
|
recognizeBiometric,
|
|
15887
15494
|
recognizeByPurpose,
|
|
15888
15495
|
recordConsent,
|
|
15889
15496
|
refreshSession,
|
|
15497
|
+
registerApplication,
|
|
15890
15498
|
registerBiometric,
|
|
15499
|
+
registerDocumentByImage,
|
|
15891
15500
|
registerPersonWithBiometric,
|
|
15892
15501
|
registerPersonWithoutFace,
|
|
15893
15502
|
requestConsent,
|