@neofaceid/web-sdk 1.44.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6236,7 +6236,7 @@ const blobToBase64 = (blob) => new Promise((resolve, reject) => {
6236
6236
  reader.onerror = () => reject(new NeoFaceError("Failed to convert image to base64", ErrorType.CAPTURE_ERROR));
6237
6237
  reader.readAsDataURL(blob);
6238
6238
  });
6239
- const registerDocumentByImage = async (personId, jwtToken, images) => {
6239
+ const registerDocumentByImage$1 = async (personId, jwtToken, images) => {
6240
6240
  var _a2, _b;
6241
6241
  ensureSecureContext();
6242
6242
  if (!personId) {
@@ -6409,7 +6409,7 @@ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty(
6409
6409
  refreshSession,
6410
6410
  registerApplication,
6411
6411
  registerBiometric,
6412
- registerDocumentByImage,
6412
+ registerDocumentByImage: registerDocumentByImage$1,
6413
6413
  registerPersonWithBiometric,
6414
6414
  registerPersonWithoutFace,
6415
6415
  requestDeviceEnrollmentToken,
@@ -7815,17 +7815,17 @@ function BiometricRegistrationModal({
7815
7815
  }
7816
7816
  let isRunning = true;
7817
7817
  let lastDetectionTime = 0;
7818
- const detectFace2 = async () => {
7818
+ const detectFace = async () => {
7819
7819
  if (!isRunning || state.status !== "liveness") {
7820
7820
  return;
7821
7821
  }
7822
7822
  if (isCapturingRef.current || currentStepRef.current === "complete") {
7823
- requestAnimationFrame(detectFace2);
7823
+ requestAnimationFrame(detectFace);
7824
7824
  return;
7825
7825
  }
7826
7826
  const now = Date.now();
7827
7827
  if (now - lastDetectionTime < CALIBRATION.DETECTION_INTERVAL) {
7828
- requestAnimationFrame(detectFace2);
7828
+ requestAnimationFrame(detectFace);
7829
7829
  return;
7830
7830
  }
7831
7831
  lastDetectionTime = now;
@@ -7849,7 +7849,7 @@ function BiometricRegistrationModal({
7849
7849
  }
7850
7850
  if (positionStatus !== "ok") {
7851
7851
  manageHoldAndCapture(false);
7852
- requestAnimationFrame(detectFace2);
7852
+ requestAnimationFrame(detectFace);
7853
7853
  return;
7854
7854
  }
7855
7855
  }
@@ -7874,7 +7874,7 @@ function BiometricRegistrationModal({
7874
7874
  } catch (error) {
7875
7875
  }
7876
7876
  if (isRunning && state.status === "liveness") {
7877
- requestAnimationFrame(detectFace2);
7877
+ requestAnimationFrame(detectFace);
7878
7878
  }
7879
7879
  };
7880
7880
  const startDetection = () => {
@@ -7884,7 +7884,7 @@ function BiometricRegistrationModal({
7884
7884
  clearTimeout(watchdogTimerRef.current);
7885
7885
  watchdogTimerRef.current = null;
7886
7886
  }
7887
- detectFace2();
7887
+ detectFace();
7888
7888
  };
7889
7889
  const video = videoRef.current;
7890
7890
  if (video && video.readyState >= 2) {
@@ -8898,8 +8898,8 @@ function BiometricRegistrationModal({
8898
8898
  /* @__PURE__ */ jsx("canvas", { ref: canvasRef })
8899
8899
  ] });
8900
8900
  }
8901
- const VERSION = "1.44.0";
8902
- const RELEASE_DATE = "2026-09-16";
8901
+ const VERSION = "2.0.0";
8902
+ const RELEASE_DATE = "2026-09-19";
8903
8903
  const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
8904
8904
  const CONSENT_TRAIL_MAX_LIMIT = 100;
8905
8905
  async function hashString(inputStr) {
@@ -10697,704 +10697,106 @@ function CameraOvalGuide({
10697
10697
  }
10698
10698
  );
10699
10699
  }
10700
- 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);
10700
+ class BiometricStatusOverlay {
10701
+ constructor() {
10702
+ __publicField(this, "container", null);
10703
+ __publicField(this, "currentStatus", "preparing");
10704
+ }
10705
+ getStyles() {
10706
+ return `
10707
+ @keyframes neofaceid-fadeIn {
10708
+ from { opacity: 0; transform: scale(0.9); }
10709
+ to { opacity: 1; transform: scale(1); }
10781
10710
  }
10782
- }
10783
- };
10784
- 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;
10711
+
10712
+ @keyframes neofaceid-fadeOut {
10713
+ from { opacity: 1; transform: scale(1); }
10714
+ to { opacity: 0; transform: scale(0.9); }
10795
10715
  }
10796
- if (evalTimer) {
10797
- clearTimeout(evalTimer);
10798
- evalTimer = null;
10716
+
10717
+ @keyframes neofaceid-pulse {
10718
+ 0%, 100% {
10719
+ transform: scale(1);
10720
+ opacity: 1;
10721
+ }
10722
+ 50% {
10723
+ transform: scale(1.05);
10724
+ opacity: 0.9;
10725
+ }
10799
10726
  }
10800
- 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();
10727
+
10728
+ @keyframes neofaceid-scaleIn {
10729
+ 0% { transform: scale(0); opacity: 0; }
10730
+ 50% { transform: scale(1.1); }
10731
+ 100% { transform: scale(1); opacity: 1; }
10839
10732
  }
10840
- } 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;
10733
+
10734
+ @keyframes neofaceid-errorShake {
10735
+ 0%, 100% { transform: translateX(0); }
10736
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
10737
+ 20%, 40%, 60%, 80% { transform: translateX(4px); }
11089
10738
  }
11090
10739
 
11091
- .neofaceid-modal-overlay {
11092
- position: absolute;
10740
+ .neofaceid-overlay-container {
10741
+ position: fixed;
11093
10742
  top: 0;
11094
10743
  left: 0;
11095
- width: 100%;
11096
- height: 100%;
11097
- background: rgba(0, 0, 0, 0.8);
10744
+ right: 0;
10745
+ bottom: 0;
11098
10746
  display: flex;
11099
10747
  align-items: center;
11100
10748
  justify-content: center;
11101
- padding: 20px;
11102
- box-sizing: border-box;
10749
+ z-index: 10000;
10750
+ pointer-events: none;
10751
+ animation: neofaceid-fadeIn 0.3s ease-out;
10752
+ font-family: inherit;
11103
10753
  }
11104
10754
 
11105
- .neofaceid-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);
10755
+ .neofaceid-overlay-container.fade-out {
10756
+ animation: neofaceid-fadeOut 0.3s ease-out forwards;
11113
10757
  }
11114
10758
 
11115
- .neofaceid-modal-header {
11116
- padding: 20px;
11117
- border-bottom: 1px solid #e0e0e0;
10759
+ .neofaceid-overlay-content {
11118
10760
  display: flex;
11119
- justify-content: space-between;
10761
+ flex-direction: column;
11120
10762
  align-items: center;
10763
+ justify-content: center;
10764
+ gap: 24px;
10765
+ pointer-events: auto;
11121
10766
  }
11122
10767
 
11123
- .neofaceid-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;
10768
+ .neofaceid-visual-wrapper {
10769
+ position: relative;
10770
+ width: 100px;
10771
+ height: 100px;
11139
10772
  display: flex;
11140
10773
  align-items: center;
11141
10774
  justify-content: center;
11142
- border-radius: 22px;
11143
- transition: background-color 0.2s;
11144
- }
11145
- .neofaceid-close-btn:focus-visible {
11146
- outline: 2px solid var(--nfid-focus-ring);
11147
- outline-offset: 2px;
11148
10775
  }
11149
10776
 
11150
- .neofaceid-close-btn:hover {
11151
- background-color: #f0f0f0;
10777
+ .neofaceid-logo {
10778
+ width: 80px;
10779
+ height: 80px;
10780
+ object-fit: contain;
10781
+ transition: all 0.5s ease;
10782
+ filter: drop-shadow(0 4px 12px rgba(0, 89, 196, 0.3));
11152
10783
  }
11153
10784
 
11154
- .neofaceid-modal-body {
11155
- padding: 20px;
10785
+ /* Cores por estado */
10786
+ .neofaceid-state-preparing .neofaceid-logo { filter: drop-shadow(0 0 15px rgba(0, 89, 196, 0.4)); }
10787
+ .neofaceid-state-detecting .neofaceid-logo { filter: hue-rotate(180deg) drop-shadow(0 0 15px rgba(59, 130, 246, 0.6)); }
10788
+ .neofaceid-state-waiting-for-face .neofaceid-logo { filter: hue-rotate(30deg) drop-shadow(0 0 15px rgba(251, 146, 60, 0.6)); }
10789
+ .neofaceid-state-capturing .neofaceid-logo { filter: hue-rotate(45deg) drop-shadow(0 0 15px rgba(245, 158, 11, 0.6)); }
10790
+ .neofaceid-state-verifying .neofaceid-logo { filter: hue-rotate(90deg) drop-shadow(0 0 15px rgba(34, 197, 94, 0.6)); }
10791
+
10792
+ .neofaceid-logo.pulsing {
10793
+ animation: neofaceid-pulse 2s ease-in-out infinite;
11156
10794
  }
11157
10795
 
11158
- .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); }
10796
+ @keyframes neofaceid-dots {
10797
+ 0%, 20% { opacity: 0; transform: translateY(0); }
10798
+ 50% { opacity: 1; transform: translateY(-2px); }
10799
+ 80%, 100% { opacity: 0; transform: translateY(0); }
11398
10800
  }
11399
10801
 
11400
10802
  .neofaceid-status-text {
@@ -12204,7 +11606,7 @@ async function captureFaceSilently() {
12204
11606
  reject(new Error("Failed to get canvas context"));
12205
11607
  return;
12206
11608
  }
12207
- const detectFace2 = async () => {
11609
+ const detectFace = async () => {
12208
11610
  if (!modelsLoaded) return true;
12209
11611
  try {
12210
11612
  const detection = await faceapi.detectSingleFace(
@@ -12216,7 +11618,7 @@ async function captureFaceSilently() {
12216
11618
  return true;
12217
11619
  }
12218
11620
  };
12219
- detectFace2().then((hasFace) => {
11621
+ detectFace().then((hasFace) => {
12220
11622
  if (!hasFace && faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
12221
11623
  faceDetectionAttempts++;
12222
11624
  setTimeout(tryCapture, 150);
@@ -12550,16 +11952,614 @@ async function executeBiometricLoginFlow(options) {
12550
11952
  );
12551
11953
  }, 500);
12552
11954
  }
12553
- }
12554
- };
12555
- tryLogin();
11955
+ }
11956
+ };
11957
+ tryLogin();
11958
+ }
11959
+ const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
11960
+ __proto__: null,
11961
+ captureFaceSilently,
11962
+ executeBiometricLoginFlow,
11963
+ preloadFaceDetectionModels
11964
+ }, Symbol.toStringTag, { value: "Module" }));
11965
+ const DARK_THRESHOLD = 0.235;
11966
+ const IMPROVEMENT_TARGET = 0.12;
11967
+ const SAMPLE_INTERVAL_MS = 600;
11968
+ const EVALUATION_DELAY_MS = 600;
11969
+ const MIN_TOGGLE_MS = 3e3;
11970
+ const OVERLAY_ID_PREFIX = "nfid-flash-";
11971
+ function createScreenFlashController(getVideo, options = {}) {
11972
+ const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD;
11973
+ const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET;
11974
+ const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
11975
+ const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS;
11976
+ const zIndex = options.zIndex ?? 10001;
11977
+ const overlayId = `${OVERLAY_ID_PREFIX}${Math.random().toString(36).slice(2, 9)}`;
11978
+ let interval = null;
11979
+ let evalTimer = null;
11980
+ let flashOn = false;
11981
+ let lastBrightness = 0;
11982
+ let darkStreak = 0;
11983
+ let lastToggleAt = 0;
11984
+ let preFlashBrightness = null;
11985
+ let overlayEl = null;
11986
+ let scratchCanvas = null;
11987
+ const showOverlay = () => {
11988
+ if (overlayEl || typeof document === "undefined") return;
11989
+ overlayEl = document.createElement("div");
11990
+ overlayEl.id = overlayId;
11991
+ overlayEl.setAttribute("aria-hidden", "true");
11992
+ overlayEl.style.cssText = `
11993
+ position: fixed; inset: 0; background: #FFFFFF;
11994
+ z-index: ${zIndex}; pointer-events: none;
11995
+ opacity: 0; transition: opacity 200ms ease-out;
11996
+ `;
11997
+ document.body.appendChild(overlayEl);
11998
+ requestAnimationFrame(() => {
11999
+ if (overlayEl) overlayEl.style.opacity = "1";
12000
+ });
12001
+ };
12002
+ const hideOverlay = () => {
12003
+ if (!overlayEl) return;
12004
+ const el = overlayEl;
12005
+ overlayEl = null;
12006
+ el.style.opacity = "0";
12007
+ setTimeout(() => {
12008
+ if (el.parentNode) el.parentNode.removeChild(el);
12009
+ }, 220);
12010
+ };
12011
+ const setFlashOn = (next) => {
12012
+ if (flashOn === next) return;
12013
+ flashOn = next;
12014
+ if (next) showOverlay();
12015
+ else hideOverlay();
12016
+ if (options.onChange) options.onChange(next, lastBrightness);
12017
+ };
12018
+ const tick = () => {
12019
+ if (typeof document !== "undefined" && !scratchCanvas) {
12020
+ scratchCanvas = document.createElement("canvas");
12021
+ }
12022
+ const b = measureFrameBrightness(getVideo(), {
12023
+ scratch: scratchCanvas ?? void 0
12024
+ });
12025
+ lastBrightness = b;
12026
+ const now = Date.now();
12027
+ if (!flashOn) {
12028
+ if (b > 0 && b < darkThreshold) darkStreak += 1;
12029
+ else darkStreak = 0;
12030
+ if (darkStreak >= 2 && now - lastToggleAt >= minToggleMs) {
12031
+ preFlashBrightness = b;
12032
+ lastToggleAt = now;
12033
+ darkStreak = 0;
12034
+ setFlashOn(true);
12035
+ evalTimer = setTimeout(() => {
12036
+ const after = measureFrameBrightness(getVideo(), {
12037
+ scratch: scratchCanvas ?? void 0
12038
+ });
12039
+ const gained = after - (preFlashBrightness ?? 0);
12040
+ if (gained < improvementTarget) {
12041
+ lastToggleAt = Date.now();
12042
+ setFlashOn(false);
12043
+ }
12044
+ preFlashBrightness = null;
12045
+ }, EVALUATION_DELAY_MS);
12046
+ }
12047
+ }
12048
+ };
12049
+ return {
12050
+ start() {
12051
+ if (options.enabled === false) return;
12052
+ if (interval) return;
12053
+ tick();
12054
+ interval = setInterval(tick, sampleIntervalMs);
12055
+ },
12056
+ stop() {
12057
+ if (interval) {
12058
+ clearInterval(interval);
12059
+ interval = null;
12060
+ }
12061
+ if (evalTimer) {
12062
+ clearTimeout(evalTimer);
12063
+ evalTimer = null;
12064
+ }
12065
+ setFlashOn(false);
12066
+ darkStreak = 0;
12067
+ preFlashBrightness = null;
12068
+ },
12069
+ destroy() {
12070
+ this.stop();
12071
+ scratchCanvas = null;
12072
+ },
12073
+ get flashOn() {
12074
+ return flashOn;
12075
+ },
12076
+ get lastBrightness() {
12077
+ return lastBrightness;
12078
+ }
12079
+ };
12080
+ }
12081
+ class BiometricCaptureModal {
12082
+ constructor(options) {
12083
+ __publicField(this, "modal", null);
12084
+ __publicField(this, "video", null);
12085
+ __publicField(this, "canvas", null);
12086
+ __publicField(this, "stream", null);
12087
+ __publicField(this, "options");
12088
+ __publicField(this, "countdownInterval", null);
12089
+ __publicField(this, "isCapturing", false);
12090
+ __publicField(this, "flashController", null);
12091
+ this.options = options;
12092
+ }
12093
+ /**
12094
+ * Abre o modal de captura biométrica
12095
+ */
12096
+ async open() {
12097
+ try {
12098
+ await this.createModal();
12099
+ await this.initializeCamera();
12100
+ this.enableCaptureButton();
12101
+ if (this.options.autoLighting !== false) {
12102
+ this.flashController = createScreenFlashController(() => this.video);
12103
+ this.flashController.start();
12104
+ }
12105
+ } catch (error) {
12106
+ this.options.onError(
12107
+ new NeoFaceError(
12108
+ `Erro ao inicializar câmera: ${error.message}`,
12109
+ ErrorType.CAMERA_ERROR
12110
+ )
12111
+ );
12112
+ }
12113
+ }
12114
+ /**
12115
+ * Fecha o modal e limpa recursos
12116
+ */
12117
+ close() {
12118
+ this.cleanup();
12119
+ if (this.modal) {
12120
+ document.body.removeChild(this.modal);
12121
+ this.modal = null;
12122
+ }
12123
+ }
12124
+ /**
12125
+ * Cria a estrutura HTML do modal
12126
+ */
12127
+ async createModal() {
12128
+ this.modal = document.createElement("div");
12129
+ this.modal.className = "neofaceid-root neofaceid-biometric-modal";
12130
+ const title = this.options.title || this.getDefaultTitle();
12131
+ const subtitle = this.options.subtitle || this.getDefaultSubtitle();
12132
+ this.modal.innerHTML = `
12133
+ <div class="neofaceid-modal-overlay">
12134
+ <div class="neofaceid-modal-content">
12135
+ <div class="neofaceid-modal-header">
12136
+ <h2>${title}</h2>
12137
+ <button class="neofaceid-close-btn" type="button" aria-label="Fechar">&times;</button>
12138
+ </div>
12139
+ <div class="neofaceid-modal-body">
12140
+ <p class="neofaceid-subtitle">${subtitle}</p>
12141
+ <div class="neofaceid-camera-container">
12142
+ <video class="neofaceid-video" autoplay muted playsinline></video>
12143
+ <canvas class="neofaceid-canvas" style="display: none;"></canvas>
12144
+ <div class="neofaceid-overlay">
12145
+ <div class="neofaceid-frame"></div>
12146
+ </div>
12147
+ </div>
12148
+ <div class="neofaceid-status">
12149
+ <p class="neofaceid-status-text">Posicione-se na frente da câmera</p>
12150
+ </div>
12151
+ </div>
12152
+ <div class="neofaceid-modal-footer">
12153
+ <button class="neofaceid-cancel-btn" type="button">Cancelar</button>
12154
+ <button class="neofaceid-capture-btn" type="button" disabled>Capturar</button>
12155
+ </div>
12156
+ <div class="neofaceid-seal-row">
12157
+ ${renderBrandFooter()}
12158
+ </div>
12159
+ </div>
12160
+ </div>
12161
+ `;
12162
+ injectThemeStyles();
12163
+ this.addStyles();
12164
+ this.addEventListeners();
12165
+ document.body.appendChild(this.modal);
12166
+ }
12167
+ /**
12168
+ * Inicializa a câmera
12169
+ */
12170
+ async initializeCamera() {
12171
+ if (!this.modal) throw new Error("Modal não inicializado");
12172
+ this.video = this.modal.querySelector(".neofaceid-video");
12173
+ this.canvas = this.modal.querySelector(".neofaceid-canvas");
12174
+ if (!this.video || !this.canvas) {
12175
+ throw new Error("Elementos de vídeo ou canvas não encontrados");
12176
+ }
12177
+ try {
12178
+ this.stream = await navigator.mediaDevices.getUserMedia({
12179
+ video: {
12180
+ width: { ideal: 640 },
12181
+ height: { ideal: 480 },
12182
+ facingMode: "user"
12183
+ },
12184
+ audio: false
12185
+ });
12186
+ this.video.srcObject = this.stream;
12187
+ await this.video.play();
12188
+ this.canvas.width = this.video.videoWidth;
12189
+ this.canvas.height = this.video.videoHeight;
12190
+ } catch (error) {
12191
+ throw new Error(`Não foi possível acessar a câmera: ${error.message}`);
12192
+ }
12193
+ }
12194
+ /**
12195
+ * NEO-413 · US14.6 (item 18) — a contagem "3, 2, 1" foi removida.
12196
+ * Agora habilita direto o botão "Capturar" assim que a câmera fica pronta.
12197
+ * Vivacidade fica com o desafio `runLivenessChallenge` (NEO-408), servidor decide.
12198
+ */
12199
+ enableCaptureButton() {
12200
+ if (!this.modal) return;
12201
+ const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
12202
+ const statusText = this.modal.querySelector(".neofaceid-status-text");
12203
+ if (captureBtn) {
12204
+ captureBtn.disabled = false;
12205
+ captureBtn.textContent = "Capturar";
12206
+ }
12207
+ if (statusText) {
12208
+ statusText.textContent = "Pronto para capturar";
12209
+ }
12210
+ if (this.options.mode === "auto") {
12211
+ setTimeout(() => this.captureImage(), 300);
12212
+ }
12213
+ }
12214
+ /**
12215
+ * Captura a imagem da câmera
12216
+ */
12217
+ async captureImage() {
12218
+ if (this.isCapturing || !this.video || !this.canvas) return;
12219
+ this.isCapturing = true;
12220
+ try {
12221
+ const context = this.canvas.getContext("2d");
12222
+ if (!context) throw new Error("Não foi possível obter contexto do canvas");
12223
+ context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
12224
+ const imageData = this.canvas.toDataURL("image/jpeg", 0.8);
12225
+ const base64Data = imageData.split(",")[1];
12226
+ const dataUrl = `data:image/jpeg;base64,${base64Data}`;
12227
+ const detectedType = this.options.mode === "auto" ? await this.detectBiometricType(base64Data) : this.options.mode;
12228
+ this.close();
12229
+ this.options.onSuccess(dataUrl, detectedType);
12230
+ } catch (error) {
12231
+ this.options.onError(
12232
+ new NeoFaceError(
12233
+ `Erro ao capturar imagem: ${error.message}`,
12234
+ ErrorType.CAPTURE_ERROR
12235
+ )
12236
+ );
12237
+ } finally {
12238
+ this.isCapturing = false;
12239
+ }
12240
+ }
12241
+ /**
12242
+ * Detecta o tipo biométrico na imagem (face ou mão)
12243
+ */
12244
+ async detectBiometricType(imageData) {
12245
+ try {
12246
+ const { detectBiometricType } = await import("./biometricDetection-8utmtR25.js");
12247
+ const result = await detectBiometricType(imageData);
12248
+ if (result.type !== "unknown" && result.confidence > 0.6) {
12249
+ return result.type;
12250
+ }
12251
+ return "face";
12252
+ } catch (error) {
12253
+ console.warn("Erro na detecção automática, usando face como padrão:", error);
12254
+ return "face";
12255
+ }
12256
+ }
12257
+ /**
12258
+ * Adiciona event listeners aos elementos do modal
12259
+ */
12260
+ addEventListeners() {
12261
+ if (!this.modal) return;
12262
+ const closeBtn = this.modal.querySelector(".neofaceid-close-btn");
12263
+ const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
12264
+ const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
12265
+ closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
12266
+ var _a2, _b;
12267
+ this.close();
12268
+ (_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
12269
+ });
12270
+ cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
12271
+ var _a2, _b;
12272
+ this.close();
12273
+ (_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
12274
+ });
12275
+ captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
12276
+ this.captureImage();
12277
+ });
12278
+ this.modal.addEventListener("click", (e) => {
12279
+ var _a2, _b, _c;
12280
+ if (e.target === ((_a2 = this.modal) == null ? void 0 : _a2.querySelector(".neofaceid-modal-overlay"))) {
12281
+ this.close();
12282
+ (_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
12283
+ }
12284
+ });
12285
+ }
12286
+ /**
12287
+ * Limpa recursos (câmera, intervalos, etc.)
12288
+ */
12289
+ cleanup() {
12290
+ if (this.flashController) {
12291
+ this.flashController.destroy();
12292
+ this.flashController = null;
12293
+ }
12294
+ if (this.stream) {
12295
+ this.stream.getTracks().forEach((track) => track.stop());
12296
+ this.stream = null;
12297
+ }
12298
+ if (this.countdownInterval) {
12299
+ clearInterval(this.countdownInterval);
12300
+ this.countdownInterval = null;
12301
+ }
12302
+ this.video = null;
12303
+ this.canvas = null;
12304
+ this.isCapturing = false;
12305
+ }
12306
+ /**
12307
+ * Retorna o título padrão baseado no modo
12308
+ */
12309
+ getDefaultTitle() {
12310
+ switch (this.options.mode) {
12311
+ case "face":
12312
+ return "Autenticação Facial";
12313
+ case "hand":
12314
+ return "Autenticação por Mão";
12315
+ case "auto":
12316
+ return "Autenticação Biométrica";
12317
+ default:
12318
+ return "Captura Biométrica";
12319
+ }
12320
+ }
12321
+ /**
12322
+ * Retorna o subtítulo padrão baseado no modo
12323
+ */
12324
+ getDefaultSubtitle() {
12325
+ switch (this.options.mode) {
12326
+ case "face":
12327
+ return "Posicione seu rosto dentro do quadro e aguarde a captura automática.";
12328
+ case "hand":
12329
+ return "Posicione sua mão dentro do quadro e aguarde a captura automática.";
12330
+ case "auto":
12331
+ return "Posicione seu rosto ou mão dentro do quadro para autenticação automática.";
12332
+ default:
12333
+ return "Posicione-se dentro do quadro para captura.";
12334
+ }
12335
+ }
12336
+ /**
12337
+ * Adiciona estilos CSS ao modal
12338
+ */
12339
+ addStyles() {
12340
+ const styleId = "neofaceid-biometric-modal-styles";
12341
+ const existingStyles = document.getElementById(styleId);
12342
+ if (existingStyles) {
12343
+ existingStyles.remove();
12344
+ }
12345
+ injectScopedStyles(styleId, `
12346
+ .neofaceid-biometric-modal {
12347
+ position: fixed;
12348
+ top: 0;
12349
+ left: 0;
12350
+ width: 100%;
12351
+ height: 100%;
12352
+ z-index: 10000;
12353
+ font-family: inherit;
12354
+ }
12355
+
12356
+ .neofaceid-modal-overlay {
12357
+ position: absolute;
12358
+ top: 0;
12359
+ left: 0;
12360
+ width: 100%;
12361
+ height: 100%;
12362
+ background: rgba(0, 0, 0, 0.8);
12363
+ display: flex;
12364
+ align-items: center;
12365
+ justify-content: center;
12366
+ padding: 20px;
12367
+ box-sizing: border-box;
12368
+ }
12369
+
12370
+ .neofaceid-modal-content {
12371
+ background: white;
12372
+ border-radius: 12px;
12373
+ max-width: 600px;
12374
+ width: 100%;
12375
+ max-height: 90vh;
12376
+ overflow: hidden;
12377
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
12378
+ }
12379
+
12380
+ .neofaceid-modal-header {
12381
+ padding: 20px;
12382
+ border-bottom: 1px solid #e0e0e0;
12383
+ display: flex;
12384
+ justify-content: space-between;
12385
+ align-items: center;
12386
+ }
12387
+
12388
+ .neofaceid-modal-header h2 {
12389
+ margin: 0;
12390
+ font-size: 24px;
12391
+ font-weight: 600;
12392
+ color: #333;
12393
+ }
12394
+
12395
+ .neofaceid-close-btn {
12396
+ background: none;
12397
+ border: none;
12398
+ font-size: 22px;
12399
+ cursor: pointer;
12400
+ color: #666;
12401
+ padding: 0;
12402
+ min-width: 44px;
12403
+ min-height: 44px;
12404
+ display: flex;
12405
+ align-items: center;
12406
+ justify-content: center;
12407
+ border-radius: 22px;
12408
+ transition: background-color 0.2s;
12409
+ }
12410
+ .neofaceid-close-btn:focus-visible {
12411
+ outline: 2px solid var(--nfid-focus-ring);
12412
+ outline-offset: 2px;
12413
+ }
12414
+
12415
+ .neofaceid-close-btn:hover {
12416
+ background-color: #f0f0f0;
12417
+ }
12418
+
12419
+ .neofaceid-modal-body {
12420
+ padding: 20px;
12421
+ }
12422
+
12423
+ .neofaceid-subtitle {
12424
+ margin: 0 0 20px 0;
12425
+ color: #666;
12426
+ font-size: 16px;
12427
+ line-height: 1.4;
12428
+ }
12429
+
12430
+ .neofaceid-camera-container {
12431
+ position: relative;
12432
+ background: #000;
12433
+ border-radius: 8px;
12434
+ overflow: hidden;
12435
+ aspect-ratio: 4/3;
12436
+ margin-bottom: 20px;
12437
+ }
12438
+
12439
+ .neofaceid-video {
12440
+ width: 100%;
12441
+ height: 100%;
12442
+ object-fit: cover;
12443
+ }
12444
+
12445
+ .neofaceid-canvas {
12446
+ position: absolute;
12447
+ top: 0;
12448
+ left: 0;
12449
+ }
12450
+
12451
+ .neofaceid-overlay {
12452
+ position: absolute;
12453
+ top: 0;
12454
+ left: 0;
12455
+ width: 100%;
12456
+ height: 100%;
12457
+ display: flex;
12458
+ align-items: center;
12459
+ justify-content: center;
12460
+ }
12461
+
12462
+ .neofaceid-frame {
12463
+ width: 200px;
12464
+ height: 200px;
12465
+ border: 3px solid #0E9F6E;
12466
+ border-radius: 50%;
12467
+ box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
12468
+ animation: pulse 2s infinite;
12469
+ }
12470
+
12471
+ .neofaceid-seal-row {
12472
+ display: flex;
12473
+ justify-content: center;
12474
+ padding: 12px 20px 16px;
12475
+ border-top: 1px solid var(--nfid-line, #E6ECF4);
12476
+ }
12477
+ .neofaceid-seal {
12478
+ font-size: 10px;
12479
+ font-weight: 500;
12480
+ letter-spacing: 0.06em;
12481
+ text-transform: uppercase;
12482
+ color: var(--nfid-text-subtle, #617489);
12483
+ }
12484
+
12485
+ .neofaceid-status {
12486
+ text-align: center;
12487
+ margin-bottom: 20px;
12488
+ }
12489
+
12490
+ .neofaceid-status-text {
12491
+ margin: 0;
12492
+ color: #666;
12493
+ font-size: 16px;
12494
+ }
12495
+
12496
+ .neofaceid-modal-footer {
12497
+ padding: 20px;
12498
+ border-top: 1px solid #e0e0e0;
12499
+ display: flex;
12500
+ gap: 12px;
12501
+ justify-content: flex-end;
12502
+ }
12503
+
12504
+ .neofaceid-cancel-btn,
12505
+ .neofaceid-capture-btn {
12506
+ padding: 12px 24px;
12507
+ border: none;
12508
+ border-radius: 6px;
12509
+ font-size: 16px;
12510
+ font-weight: 500;
12511
+ cursor: pointer;
12512
+ transition: all 0.2s;
12513
+ }
12514
+
12515
+ .neofaceid-cancel-btn {
12516
+ background: #f5f5f5;
12517
+ color: #666;
12518
+ }
12519
+
12520
+ .neofaceid-cancel-btn:hover {
12521
+ background: #e0e0e0;
12522
+ }
12523
+
12524
+ .neofaceid-capture-btn {
12525
+ background: #0E9F6E;
12526
+ color: white;
12527
+ }
12528
+
12529
+ .neofaceid-capture-btn:hover:not(:disabled) {
12530
+ background: #45a049;
12531
+ }
12532
+
12533
+ .neofaceid-capture-btn:disabled {
12534
+ background: #ccc;
12535
+ cursor: not-allowed;
12536
+ }
12537
+
12538
+ @keyframes pulse {
12539
+ 0% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
12540
+ 50% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0.1); }
12541
+ 100% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
12542
+ }
12543
+
12544
+ @media (max-width: 640px) {
12545
+ .neofaceid-modal-overlay {
12546
+ padding: 10px;
12547
+ }
12548
+
12549
+ .neofaceid-modal-header,
12550
+ .neofaceid-modal-body,
12551
+ .neofaceid-modal-footer {
12552
+ padding: 15px;
12553
+ }
12554
+
12555
+ .neofaceid-frame {
12556
+ width: 150px;
12557
+ height: 150px;
12558
+ }
12559
+ }
12560
+ `);
12561
+ }
12556
12562
  }
12557
- const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
12558
- __proto__: null,
12559
- captureFaceSilently,
12560
- executeBiometricLoginFlow,
12561
- preloadFaceDetectionModels
12562
- }, Symbol.toStringTag, { value: "Module" }));
12563
12563
  const CONSENT_DENIED_MESSAGE$1 = "Consentimento recusado pelo usuário.";
12564
12564
  function openPasswordFallback(options) {
12565
12565
  if (!getAutoFallback().toPassword) {
@@ -12682,175 +12682,6 @@ async function startAutoLogin(options) {
12682
12682
  );
12683
12683
  }
12684
12684
  }
12685
- async function detectBiometricType(imageData) {
12686
- try {
12687
- const img = await loadImageFromBase64(imageData);
12688
- const faceResult = await detectFace(img);
12689
- if (faceResult.confidence > 0.7) {
12690
- return {
12691
- type: "face",
12692
- confidence: faceResult.confidence,
12693
- details: faceResult.details
12694
- };
12695
- }
12696
- const handResult = await detectHand(img);
12697
- if (handResult.confidence > 0.6) {
12698
- return {
12699
- type: "hand",
12700
- confidence: handResult.confidence,
12701
- details: handResult.details
12702
- };
12703
- }
12704
- return {
12705
- type: "unknown",
12706
- confidence: Math.max(faceResult.confidence, handResult.confidence)
12707
- };
12708
- } catch (error) {
12709
- console.warn("Erro na detecção biométrica:", error);
12710
- return {
12711
- type: "face",
12712
- confidence: 0.5
12713
- };
12714
- }
12715
- }
12716
- async function detectFace(img) {
12717
- try {
12718
- if (typeof window !== "undefined" && window.faceapi) {
12719
- const { faceapi: faceapi2 } = window;
12720
- await loadFaceApiModels();
12721
- const tinyOptions = new faceapi2.TinyFaceDetectorOptions({
12722
- inputSize: 320,
12723
- scoreThreshold: 0.5
12724
- });
12725
- const detections = await faceapi2.detectAllFaces(img, tinyOptions).withFaceLandmarks();
12726
- if (detections && detections.length > 0) {
12727
- const bestDetection = detections.reduce(
12728
- (best, current) => current.detection.score > best.detection.score ? current : best
12729
- );
12730
- return {
12731
- confidence: bestDetection.detection.score,
12732
- details: {
12733
- faces: detections.length,
12734
- landmarks: bestDetection.landmarks,
12735
- box: bestDetection.detection.box
12736
- }
12737
- };
12738
- }
12739
- }
12740
- return { confidence: 0 };
12741
- } catch (error) {
12742
- console.warn("Erro na detecção facial:", error);
12743
- return { confidence: 0 };
12744
- }
12745
- }
12746
- async function detectHand(img) {
12747
- try {
12748
- const canvas = document.createElement("canvas");
12749
- const ctx = canvas.getContext("2d");
12750
- if (!ctx) {
12751
- return { confidence: 0 };
12752
- }
12753
- canvas.width = img.width;
12754
- canvas.height = img.height;
12755
- ctx.drawImage(img, 0, 0);
12756
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
12757
- const handFeatures = analyzeHandFeatures(imageData);
12758
- return {
12759
- confidence: handFeatures.confidence,
12760
- details: handFeatures
12761
- };
12762
- } catch (error) {
12763
- console.warn("Erro na detecção de mão:", error);
12764
- return { confidence: 0 };
12765
- }
12766
- }
12767
- function analyzeHandFeatures(imageData) {
12768
- const { data, width, height } = imageData;
12769
- let skinPixels = 0;
12770
- let totalPixels = 0;
12771
- for (let i = 0; i < data.length; i += 4) {
12772
- const r = data[i];
12773
- const g = data[i + 1];
12774
- const b = data[i + 2];
12775
- if (isSkinColor(r, g, b)) {
12776
- skinPixels++;
12777
- }
12778
- totalPixels++;
12779
- }
12780
- const skinRatio = skinPixels / totalPixels;
12781
- const aspectRatio = width / height;
12782
- const isHandAspectRatio = aspectRatio > 0.6 && aspectRatio < 1.8;
12783
- let confidence = 0;
12784
- if (skinRatio > 0.15 && skinRatio < 0.7) {
12785
- confidence += 0.4 * (skinRatio / 0.7);
12786
- }
12787
- if (isHandAspectRatio) {
12788
- confidence += 0.3;
12789
- }
12790
- const imageSize = width * height;
12791
- if (imageSize > 1e4 && imageSize < 5e5) {
12792
- confidence += 0.3;
12793
- }
12794
- return {
12795
- confidence: Math.min(confidence, 0.8),
12796
- // Máximo 80% para detecção básica
12797
- skinRatio,
12798
- aspectRatio,
12799
- imageSize,
12800
- skinPixels,
12801
- totalPixels
12802
- };
12803
- }
12804
- function isSkinColor(r, g, b) {
12805
- const y = 0.299 * r + 0.587 * g + 0.114 * b;
12806
- const cb = -0.169 * r - 0.331 * g + 0.5 * b + 128;
12807
- const cr = 0.5 * r - 0.419 * g - 0.081 * b + 128;
12808
- return y > 80 && y < 255 && cb > 85 && cb < 135 && cr > 135 && cr < 180;
12809
- }
12810
- function loadImageFromBase64(base64Data) {
12811
- return new Promise((resolve, reject) => {
12812
- const img = new Image();
12813
- img.onload = () => resolve(img);
12814
- img.onerror = () => reject(new Error("Erro ao carregar imagem"));
12815
- const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:image/jpeg;base64,${base64Data}`;
12816
- img.src = dataUrl;
12817
- });
12818
- }
12819
- async function loadFaceApiModels() {
12820
- if (typeof window === "undefined" || !window.faceapi) {
12821
- return;
12822
- }
12823
- const { faceapi: faceapi2 } = window;
12824
- if (faceapi2.nets.tinyFaceDetector.isLoaded) {
12825
- return;
12826
- }
12827
- try {
12828
- await Promise.all([
12829
- faceapi2.nets.tinyFaceDetector.loadFromUri("/models"),
12830
- faceapi2.nets.faceLandmark68Net.loadFromUri("/models")
12831
- ]);
12832
- } catch (error) {
12833
- console.warn("Não foi possível carregar modelos do face-api.js:", error);
12834
- }
12835
- }
12836
- async function initializeBiometricDetection() {
12837
- try {
12838
- if (typeof window !== "undefined") {
12839
- await loadFaceApiModels();
12840
- }
12841
- } catch (error) {
12842
- console.warn("Inicialização da detecção biométrica com limitações:", error);
12843
- }
12844
- }
12845
- function isAdvancedDetectionAvailable() {
12846
- return typeof window !== "undefined" && window.faceapi && window.faceapi.nets.tinyFaceDetector.isLoaded;
12847
- }
12848
- const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
12849
- __proto__: null,
12850
- detectBiometricType,
12851
- initializeBiometricDetection,
12852
- isAdvancedDetectionAvailable
12853
- }, Symbol.toStringTag, { value: "Module" }));
12854
12685
  let cachedSession = null;
12855
12686
  function getCachedCaptureSession() {
12856
12687
  return cachedSession;
@@ -13378,342 +13209,103 @@ const startOnboarding = async (options) => {
13378
13209
  onError(new NeoFaceError(message, ErrorType.UNKNOWN));
13379
13210
  }
13380
13211
  };
13381
- 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
- }
13212
+ async function proofOfLife(options = {}) {
13213
+ const {
13214
+ videoDurationMs = 3e3,
13215
+ maxPollingAttempts = 60,
13216
+ pollingIntervalMs = 1e3,
13217
+ onRecordingProgress,
13218
+ onTaskStatusChange
13219
+ } = options;
13220
+ const applicationToken = options.applicationToken ?? getApplicationToken();
13221
+ if (!applicationToken) {
13222
+ throw new NeoFaceError(
13223
+ "proofOfLife exige applicationToken — passe nas opções ou em init({ applicationToken }).",
13224
+ ErrorType.INVALID_TOKEN
13225
+ );
13452
13226
  }
13453
- /**
13454
- * 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
- }
13227
+ try {
13228
+ const videoBlob = await recordFaceVideo({
13229
+ durationMs: videoDurationMs,
13230
+ onProgress: onRecordingProgress
13231
+ });
13232
+ const videoBase64 = await videoToBase64(videoBlob);
13233
+ const started = await startProofOfLife(videoBase64, applicationToken);
13234
+ if (onTaskStatusChange) onTaskStatusChange("processing", 0);
13235
+ const taskResult = await pollTaskStatus(started.taskId, applicationToken, {
13236
+ maxAttempts: maxPollingAttempts,
13237
+ intervalMs: pollingIntervalMs,
13238
+ onStatusChange: onTaskStatusChange
13239
+ });
13240
+ const base = {
13241
+ isLive: (taskResult == null ? void 0 : taskResult.is_live) || false,
13242
+ livenessScore: (taskResult == null ? void 0 : taskResult.liveness_score) || 0,
13243
+ faceRecognitionScore: (taskResult == null ? void 0 : taskResult.face_recognition_score) || 0,
13244
+ combinedConfidence: (taskResult == null ? void 0 : taskResult.combined_confidence) || 0,
13245
+ personId: taskResult == null ? void 0 : taskResult.person_id,
13246
+ personalData: (taskResult == null ? void 0 : taskResult.personal_data) || [],
13247
+ processingTime: (taskResult == null ? void 0 : taskResult.processing_time) || 0,
13248
+ historyId: started.historyId,
13249
+ taskId: started.taskId
13250
+ };
13251
+ if (!taskResult || !taskResult.success) {
13507
13252
  return {
13508
- 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
13253
+ ...base,
13254
+ success: false,
13255
+ error: (taskResult == null ? void 0 : taskResult.error) || "Verification failed",
13256
+ message: (taskResult == null ? void 0 : taskResult.message) || "Proof of life verification failed"
13516
13257
  };
13517
- } catch (error) {
13518
- if (error instanceof NeoFaceError) {
13519
- throw error;
13520
- }
13521
- throw new NeoFaceError(
13522
- `Login recognition failed: ${error instanceof Error ? error.message : "Unknown error"}`,
13523
- ErrorType.RECOGNITION_FAILED
13524
- );
13525
- }
13526
- }
13527
- /**
13528
- * Register a new application for a consumer
13529
- *
13530
- * This method allows authenticated users to register new applications
13531
- * that will receive their own app_token for API access.
13532
- *
13533
- * @param jwtToken JWT authentication token from logged user
13534
- * @param consumerId Consumer ID (UUID) who will own the application
13535
- * @param applicationData Application registration data
13536
- * @returns Promise with registration result including app_token
13537
- * @throws NeoFaceError if registration fails
13538
- *
13539
- * @example
13540
- * ```typescript
13541
- * const sdk = new NeoFaceID({
13542
- * appToken: 'your-application-token'
13543
- * });
13544
- *
13545
- * const result = await sdk.registerApplication(
13546
- * userJwtToken,
13547
- * consumerUuid,
13548
- * {
13549
- * applicationName: 'My New App',
13550
- * domain: 'example.com',
13551
- * acceptOnlyEmailWithSameDomain: true
13552
- * }
13553
- * );
13554
- *
13555
- * // Use the generated app_token for the new application
13556
- * console.log('New App Token:', result.application.app_token);
13557
- * ```
13558
- */
13559
- async registerApplication(jwtToken, consumerId, applicationData) {
13560
- try {
13561
- const result = await registerApplication(jwtToken, consumerId, applicationData);
13562
- return result;
13563
- } catch (error) {
13564
- if (error instanceof NeoFaceError) {
13565
- throw error;
13566
- }
13567
- throw new NeoFaceError(
13568
- `Application registration failed: ${error instanceof Error ? error.message : "Unknown error"}`,
13569
- ErrorType.VALIDATION_ERROR
13570
- );
13571
13258
  }
13259
+ return { ...base, success: true, message: "Proof of life verification successful" };
13260
+ } catch (error) {
13261
+ if (error instanceof NeoFaceError) throw error;
13262
+ throw new NeoFaceError(
13263
+ `Proof of life failed: ${error instanceof Error ? error.message : "Unknown error"}`,
13264
+ ErrorType.RECOGNITION_FAILED
13265
+ );
13572
13266
  }
13573
- /**
13574
- * 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);
13267
+ }
13268
+ async function captureFaceFrames(options) {
13269
+ const { numFrames, livenessCheck } = options;
13270
+ if (numFrames < 1 || numFrames > 10) {
13271
+ throw new NeoFaceError("Number of frames must be between 1 and 10", ErrorType.VALIDATION_ERROR);
13654
13272
  }
13655
- 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;
13273
+ try {
13274
+ const { captureFaceSilently: captureFaceSilently2 } = await Promise.resolve().then(() => biometricLoginFlow);
13275
+ const frames = [];
13276
+ for (let i = 0; i < numFrames; i += 1) {
13277
+ frames.push(await captureFaceSilently2());
13278
+ if (livenessCheck && i < numFrames - 1) {
13279
+ await new Promise((resolve) => {
13280
+ setTimeout(resolve, 300);
13281
+ });
13710
13282
  }
13711
- throw new NeoFaceError(
13712
- `Proof of life failed: ${error instanceof Error ? error.message : "Unknown error"}`,
13713
- ErrorType.RECOGNITION_FAILED
13714
- );
13715
13283
  }
13284
+ return frames;
13285
+ } catch (error) {
13286
+ if (error instanceof NeoFaceError) throw error;
13287
+ throw new NeoFaceError(
13288
+ `Failed to capture face frames: ${error instanceof Error ? error.message : "Unknown error"}`,
13289
+ ErrorType.CAPTURE_ERROR
13290
+ );
13291
+ }
13292
+ }
13293
+ async function registerDocumentByImage(personId, jwtToken, options = {}) {
13294
+ const { DocumentCaptureModal: DocumentCaptureModal2 } = await Promise.resolve().then(() => DocumentCaptureModal$1);
13295
+ const modal = new DocumentCaptureModal2({
13296
+ useBackCamera: options.useBackCamera ?? true,
13297
+ preSelectedDocument: options.preSelectedDocument
13298
+ });
13299
+ const captured = await modal.open();
13300
+ if (!captured.success || !captured.frontImage) {
13301
+ throw new NeoFaceError(
13302
+ captured.error === "cancelled" ? "Document capture was cancelled by the user" : `Document capture failed: ${captured.error ?? "unknown error"}`,
13303
+ ErrorType.CAPTURE_ERROR
13304
+ );
13716
13305
  }
13306
+ const images = [captured.frontImage];
13307
+ if (captured.backImage) images.push(captured.backImage);
13308
+ return registerDocumentByImage$1(personId, jwtToken, images);
13717
13309
  }
13718
13310
  const PHOTO_INSTRUCTIONS = [
13719
13311
  "Olhe diretamente para a câmera",
@@ -15831,7 +15423,6 @@ function startLivenessCapture(applicationToken, callbacks, options) {
15831
15423
  });
15832
15424
  }
15833
15425
  export {
15834
- BiometricCaptureModal,
15835
15426
  BiometricRegistrationModal,
15836
15427
  BiometricStatusOverlay,
15837
15428
  CameraOvalGuide,
@@ -15845,19 +15436,18 @@ export {
15845
15436
  FallbackPrompt,
15846
15437
  ForgotPasswordModal,
15847
15438
  NeoFaceError,
15848
- NeoFaceID,
15849
15439
  RELEASE_DATE,
15850
15440
  VERSION,
15851
15441
  authorize,
15852
15442
  authorizeOperation,
15853
15443
  awaitPushApproval,
15444
+ captureFaceFrames,
15854
15445
  checkUserExistence,
15855
15446
  clearConsentTrail,
15856
15447
  completeOnboarding,
15857
15448
  completeOnboardingWithData,
15858
15449
  confirmPasswordReset,
15859
15450
  consumeSseStream,
15860
- detectBiometricType,
15861
15451
  getAccent,
15862
15452
  getAppName,
15863
15453
  getApplicationToken,
@@ -15875,19 +15465,20 @@ export {
15875
15465
  identifyPerson,
15876
15466
  identifyPersonAsync,
15877
15467
  init,
15878
- initializeBiometricDetection,
15879
- isAdvancedDetectionAvailable,
15880
15468
  isInitialized,
15881
15469
  loginWithBiometric,
15882
15470
  loginWithEmail,
15883
15471
  openCaptureSession,
15884
15472
  preloadFaceDetectionModels,
15473
+ proofOfLife,
15885
15474
  recognize,
15886
15475
  recognizeBiometric,
15887
15476
  recognizeByPurpose,
15888
15477
  recordConsent,
15889
15478
  refreshSession,
15479
+ registerApplication,
15890
15480
  registerBiometric,
15481
+ registerDocumentByImage,
15891
15482
  registerPersonWithBiometric,
15892
15483
  registerPersonWithoutFace,
15893
15484
  requestConsent,