@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.
@@ -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, intervalMs);
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 detectFace2 = async () => {
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(detectFace2);
7841
+ requestAnimationFrame(detectFace);
7824
7842
  return;
7825
7843
  }
7826
7844
  const now = Date.now();
7827
7845
  if (now - lastDetectionTime < CALIBRATION.DETECTION_INTERVAL) {
7828
- requestAnimationFrame(detectFace2);
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(detectFace2);
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(detectFace2);
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
- detectFace2();
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 = "1.44.0";
8902
- const RELEASE_DATE = "2026-09-16";
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
- const DARK_THRESHOLD = 0.235;
10701
- const IMPROVEMENT_TARGET = 0.12;
10702
- const SAMPLE_INTERVAL_MS = 600;
10703
- const EVALUATION_DELAY_MS = 600;
10704
- const MIN_TOGGLE_MS = 3e3;
10705
- const OVERLAY_ID_PREFIX = "nfid-flash-";
10706
- function createScreenFlashController(getVideo, options = {}) {
10707
- const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD;
10708
- const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET;
10709
- const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
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
- return {
10785
- start() {
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
- if (evalTimer) {
10797
- clearTimeout(evalTimer);
10798
- evalTimer = null;
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
- setFlashOn(false);
10801
- darkStreak = 0;
10802
- preFlashBrightness = null;
10803
- },
10804
- destroy() {
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
- } catch (error) {
10841
- this.options.onError(
10842
- new NeoFaceError(
10843
- `Erro ao inicializar câmera: ${error.message}`,
10844
- ErrorType.CAMERA_ERROR
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">&times;</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-modal-overlay {
11092
- position: absolute;
10758
+ .neofaceid-overlay-container {
10759
+ position: fixed;
11093
10760
  top: 0;
11094
10761
  left: 0;
11095
- width: 100%;
11096
- height: 100%;
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
- padding: 20px;
11102
- box-sizing: border-box;
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-modal-content {
11106
- background: white;
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-modal-header {
11116
- padding: 20px;
11117
- border-bottom: 1px solid #e0e0e0;
10777
+ .neofaceid-overlay-content {
11118
10778
  display: flex;
11119
- justify-content: space-between;
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-modal-header h2 {
11124
- margin: 0;
11125
- font-size: 24px;
11126
- font-weight: 600;
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-close-btn:hover {
11151
- background-color: #f0f0f0;
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
- .neofaceid-modal-body {
11155
- padding: 20px;
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
- .neofaceid-subtitle {
11159
- margin: 0 0 20px 0;
11160
- color: #666;
11161
- font-size: 16px;
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 detectFace2 = async () => {
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
- detectFace2().then((hasFace) => {
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">&times;</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
- class NeoFaceID {
13382
- /**
13383
- * Creates a new NeoFaceID instance
13384
- * @param config Configuration object
13385
- * @throws NeoFaceError if signature format is invalid
13386
- */
13387
- constructor(config2) {
13388
- __publicField(this, "appToken");
13389
- __publicField(this, "signature");
13390
- __publicField(this, "sessionData");
13391
- this.appToken = config2.appToken;
13392
- this.signature = config2.signature;
13393
- this.sessionData = config2.sessionData;
13394
- if (this.signature && !this.validateSignatureFormat(this.signature)) {
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
- * Performs login recognition using biometric data
13455
- *
13456
- * This method is designed for external integrations that require
13457
- * signature validation and session management.
13458
- *
13459
- * @param options Recognition options
13460
- * @returns Promise that resolves to recognition result
13461
- * @throws NeoFaceError if recognition fails
13462
- *
13463
- * @example
13464
- * ```typescript
13465
- * const result = await sdk.loginRecognition({
13466
- * biometricData: await sdk.captureFaceFrames({
13467
- * numFrames: 5,
13468
- * livenessCheck: true
13469
- * }),
13470
- * typeOfIdentification: 'FACE',
13471
- * purpose: 'LOGIN',
13472
- * confidenceThreshold: 0.8
13473
- * });
13474
- *
13475
- * // Result includes signature and sessionId for callback validation
13476
- * console.log(result.signature, result.sessionId);
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
- success: true,
13509
- personName: result.personName,
13510
- email: result.email,
13511
- cpf: result.cpf,
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
- * Performs Proof of Life verification
13575
- *
13576
- * This method records a video from the camera, sends it to the backend
13577
- * for liveness detection and face recognition, and returns the result
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
- async proofOfLife(options = {}) {
13656
- const {
13657
- videoDurationMs = 3e3,
13658
- maxPollingAttempts = 60,
13659
- pollingIntervalMs = 1e3,
13660
- onRecordingProgress,
13661
- onTaskStatusChange
13662
- } = options;
13663
- try {
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,