@neofaceid/web-sdk 1.30.0 → 1.32.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.
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import React, { useRef, useState, useCallback, useReducer, useEffect } from "react";
4
+ import React, { useRef, useState, useCallback, useReducer, useEffect, forwardRef } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
7
7
  import * as faceapi from "face-api.js";
@@ -4362,6 +4362,12 @@ const INK = {
4362
4362
  900: "#111C2B",
4363
4363
  950: "#0A1320"
4364
4364
  };
4365
+ const SEMANTIC = {
4366
+ success: { fg: "#046C4E", bg: "#E7F7F0", solid: "#0E9F6E" },
4367
+ attention: { fg: "#6E5600", bg: "#FFF6D9", solid: "#F1BE00" },
4368
+ critical: { fg: "#9B1C1C", bg: "#FDEAEA", solid: "#E02424" },
4369
+ info: { fg: "#00449A", bg: "#EBF6FE", solid: "#0073F4" }
4370
+ };
4365
4371
  const LIGHT_TOKENS = {
4366
4372
  ground: INK[25],
4367
4373
  surface: "#FFFFFF",
@@ -4504,6 +4510,15 @@ function buildRootCss(theme) {
4504
4510
  .neofaceid-root :focus-visible {
4505
4511
  outline: 2px solid var(${CSS_VAR.focusRing});
4506
4512
  outline-offset: 2px;
4513
+ }
4514
+ @media (prefers-reduced-motion: reduce) {
4515
+ .neofaceid-root,
4516
+ .neofaceid-root * {
4517
+ animation-duration: 0.001ms !important;
4518
+ animation-iteration-count: 1 !important;
4519
+ transition-duration: 0.001ms !important;
4520
+ scroll-behavior: auto !important;
4521
+ }
4507
4522
  }`;
4508
4523
  }
4509
4524
  function injectScopedStyles$1(id, css) {
@@ -8773,7 +8788,7 @@ class BiometricCaptureModal {
8773
8788
  try {
8774
8789
  await this.createModal();
8775
8790
  await this.initializeCamera();
8776
- this.startCountdown();
8791
+ this.enableCaptureButton();
8777
8792
  } catch (error) {
8778
8793
  this.options.onError(
8779
8794
  new NeoFaceError(
@@ -8806,7 +8821,7 @@ class BiometricCaptureModal {
8806
8821
  <div class="neofaceid-modal-content">
8807
8822
  <div class="neofaceid-modal-header">
8808
8823
  <h2>${title}</h2>
8809
- <button class="neofaceid-close-btn" type="button">&times;</button>
8824
+ <button class="neofaceid-close-btn" type="button" aria-label="Fechar">&times;</button>
8810
8825
  </div>
8811
8826
  <div class="neofaceid-modal-body">
8812
8827
  <p class="neofaceid-subtitle">${subtitle}</p>
@@ -8815,9 +8830,6 @@ class BiometricCaptureModal {
8815
8830
  <canvas class="neofaceid-canvas" style="display: none;"></canvas>
8816
8831
  <div class="neofaceid-overlay">
8817
8832
  <div class="neofaceid-frame"></div>
8818
- <div class="neofaceid-countdown">
8819
- <span class="neofaceid-countdown-number">${this.options.countdown || 3}</span>
8820
- </div>
8821
8833
  </div>
8822
8834
  </div>
8823
8835
  <div class="neofaceid-status">
@@ -8828,6 +8840,9 @@ class BiometricCaptureModal {
8828
8840
  <button class="neofaceid-cancel-btn" type="button">Cancelar</button>
8829
8841
  <button class="neofaceid-capture-btn" type="button" disabled>Capturar</button>
8830
8842
  </div>
8843
+ <div class="neofaceid-seal-row">
8844
+ <span class="neofaceid-seal">Protegido por NeoFaceID</span>
8845
+ </div>
8831
8846
  </div>
8832
8847
  </div>
8833
8848
  `;
@@ -8864,42 +8879,24 @@ class BiometricCaptureModal {
8864
8879
  }
8865
8880
  }
8866
8881
  /**
8867
- * Inicia a contagem regressiva
8882
+ * NEO-413 · US14.6 (item 18) — a contagem "3, 2, 1" foi removida.
8883
+ * Agora habilita direto o botão "Capturar" assim que a câmera fica pronta.
8884
+ * Vivacidade fica com o desafio `runLivenessChallenge` (NEO-408), servidor decide.
8868
8885
  */
8869
- startCountdown() {
8886
+ enableCaptureButton() {
8870
8887
  if (!this.modal) return;
8871
- const countdownElement = this.modal.querySelector(".neofaceid-countdown-number");
8872
8888
  const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
8873
8889
  const statusText = this.modal.querySelector(".neofaceid-status-text");
8874
- let count = this.options.countdown || 3;
8875
- if (countdownElement) {
8876
- countdownElement.textContent = count.toString();
8890
+ if (captureBtn) {
8891
+ captureBtn.disabled = false;
8892
+ captureBtn.textContent = "Capturar";
8893
+ }
8894
+ if (statusText) {
8895
+ statusText.textContent = "Pronto para capturar";
8896
+ }
8897
+ if (this.options.mode === "auto") {
8898
+ setTimeout(() => this.captureImage(), 300);
8877
8899
  }
8878
- this.countdownInterval = window.setInterval(() => {
8879
- count--;
8880
- if (countdownElement) {
8881
- countdownElement.textContent = count.toString();
8882
- }
8883
- if (count <= 0) {
8884
- if (this.countdownInterval) {
8885
- clearInterval(this.countdownInterval);
8886
- this.countdownInterval = null;
8887
- }
8888
- if (countdownElement) {
8889
- countdownElement.style.display = "none";
8890
- }
8891
- if (captureBtn) {
8892
- captureBtn.disabled = false;
8893
- captureBtn.textContent = "Capturar Agora";
8894
- }
8895
- if (statusText) {
8896
- statusText.textContent = "Pronto para capturar!";
8897
- }
8898
- if (this.options.mode === "auto") {
8899
- setTimeout(() => this.captureImage(), 500);
8900
- }
8901
- }
8902
- }, 1e3);
8903
8900
  }
8904
8901
  /**
8905
8902
  * Captura a imagem da câmera
@@ -9081,18 +9078,22 @@ class BiometricCaptureModal {
9081
9078
  .neofaceid-close-btn {
9082
9079
  background: none;
9083
9080
  border: none;
9084
- font-size: 28px;
9081
+ font-size: 22px;
9085
9082
  cursor: pointer;
9086
9083
  color: #666;
9087
9084
  padding: 0;
9088
- width: 32px;
9089
- height: 32px;
9085
+ min-width: 44px;
9086
+ min-height: 44px;
9090
9087
  display: flex;
9091
9088
  align-items: center;
9092
9089
  justify-content: center;
9093
- border-radius: 50%;
9090
+ border-radius: 22px;
9094
9091
  transition: background-color 0.2s;
9095
9092
  }
9093
+ .neofaceid-close-btn:focus-visible {
9094
+ outline: 2px solid var(--nfid-focus-ring);
9095
+ outline-offset: 2px;
9096
+ }
9096
9097
 
9097
9098
  .neofaceid-close-btn:hover {
9098
9099
  background-color: #f0f0f0;
@@ -9150,24 +9151,18 @@ class BiometricCaptureModal {
9150
9151
  animation: pulse 2s infinite;
9151
9152
  }
9152
9153
 
9153
- .neofaceid-countdown {
9154
- position: absolute;
9155
- top: 20px;
9156
- right: 20px;
9157
- background: rgba(0, 0, 0, 0.7);
9158
- color: white;
9159
- padding: 10px;
9160
- border-radius: 50%;
9161
- width: 50px;
9162
- height: 50px;
9154
+ .neofaceid-seal-row {
9163
9155
  display: flex;
9164
- align-items: center;
9165
9156
  justify-content: center;
9157
+ padding: 12px 20px 16px;
9158
+ border-top: 1px solid var(--nfid-line, #E6ECF4);
9166
9159
  }
9167
-
9168
- .neofaceid-countdown-number {
9169
- font-size: 24px;
9170
- font-weight: bold;
9160
+ .neofaceid-seal {
9161
+ font-size: 11px;
9162
+ font-weight: 700;
9163
+ letter-spacing: 0.09em;
9164
+ text-transform: uppercase;
9165
+ color: var(--nfid-text-subtle, #617489);
9171
9166
  }
9172
9167
 
9173
9168
  .neofaceid-status {
@@ -10070,7 +10065,7 @@ class EmailPasswordModal {
10070
10065
  }
10071
10066
  }
10072
10067
  }
10073
- const MAX_ATTEMPTS = 2;
10068
+ const MAX_ATTEMPTS$1 = 2;
10074
10069
  const RETRY_DELAY = 500;
10075
10070
  const CAMERA_READY_DELAY = 200;
10076
10071
  const CAMERA_STABILITY_DELAY = 100;
@@ -10488,7 +10483,7 @@ async function executeBiometricLoginFlow(options) {
10488
10483
  ErrorType.UNKNOWN
10489
10484
  );
10490
10485
  overlay.updateStatus("error");
10491
- if (attempts < MAX_ATTEMPTS) {
10486
+ if (attempts < MAX_ATTEMPTS$1) {
10492
10487
  setTimeout(() => {
10493
10488
  tryLogin();
10494
10489
  }, RETRY_DELAY);
@@ -10871,7 +10866,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
10871
10866
  initializeBiometricDetection,
10872
10867
  isAdvancedDetectionAvailable
10873
10868
  }, Symbol.toStringTag, { value: "Module" }));
10874
- const VERSION = "1.30.0";
10869
+ const VERSION = "1.32.0";
10875
10870
  const RELEASE_DATE = "2026-09-02";
10876
10871
  let cachedSession = null;
10877
10872
  function getCachedCaptureSession() {
@@ -11185,6 +11180,9 @@ class OnboardingCaptureModal {
11185
11180
  <button class="neofaceid-cancel-btn">Cancelar</button>
11186
11181
  <button class="neofaceid-capture-btn" disabled>Capturar</button>
11187
11182
  </div>
11183
+ <div class="neofaceid-seal-row">
11184
+ <span class="neofaceid-seal">Protegido por NeoFaceID</span>
11185
+ </div>
11188
11186
  </div>
11189
11187
  `;
11190
11188
  injectThemeStyles();
@@ -11208,6 +11206,8 @@ class OnboardingCaptureModal {
11208
11206
  .neofaceid-root .neofaceid-cancel-btn { background: var(--nfid-surface-muted); color: var(--nfid-text-muted); border:none; border-radius:8px; padding:10px 18px; cursor:pointer; min-height: 44px; }
11209
11207
  .neofaceid-root .neofaceid-capture-btn { background: var(--nfid-action); color: var(--nfid-action-text); border:none; border-radius:8px; padding:10px 18px; cursor:pointer; min-height: 44px; }
11210
11208
  .neofaceid-root .neofaceid-capture-btn:disabled { background: var(--nfid-line); cursor:not-allowed; }
11209
+ .neofaceid-root .neofaceid-seal-row { display:flex; justify-content:center; padding:12px 20px 14px; border-top: 1px solid var(--nfid-line); }
11210
+ .neofaceid-root .neofaceid-seal { font-size:11px; font-weight:700; letter-spacing:0.09em; text-transform:uppercase; color: var(--nfid-text-subtle); }
11211
11211
  `
11212
11212
  );
11213
11213
  overlay.classList.add("neofaceid-root");
@@ -11828,6 +11828,924 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
11828
11828
  throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
11829
11829
  }
11830
11830
  };
11831
+ function Sheet({
11832
+ open,
11833
+ onDismiss,
11834
+ dismissOnOverlay = true,
11835
+ ariaLabel,
11836
+ ariaLabelledBy,
11837
+ children,
11838
+ className,
11839
+ style
11840
+ }) {
11841
+ useEffect(() => {
11842
+ injectThemeStyles$1();
11843
+ }, []);
11844
+ if (!open) return null;
11845
+ const handleOverlay = () => {
11846
+ if (dismissOnOverlay && onDismiss) onDismiss();
11847
+ };
11848
+ return /* @__PURE__ */ jsx(
11849
+ "div",
11850
+ {
11851
+ className: "neofaceid-root",
11852
+ role: "presentation",
11853
+ style: {
11854
+ position: "fixed",
11855
+ inset: 0,
11856
+ zIndex: 10001,
11857
+ display: "flex",
11858
+ alignItems: "center",
11859
+ justifyContent: "center",
11860
+ padding: 20,
11861
+ background: `var(${CSS_VAR.overlay})`,
11862
+ backdropFilter: "blur(3px)",
11863
+ WebkitBackdropFilter: "blur(3px)"
11864
+ },
11865
+ onClick: handleOverlay,
11866
+ children: /* @__PURE__ */ jsx(
11867
+ "div",
11868
+ {
11869
+ role: "dialog",
11870
+ "aria-modal": "true",
11871
+ "aria-label": ariaLabel,
11872
+ "aria-labelledby": ariaLabelledBy,
11873
+ className,
11874
+ onClick: (e) => e.stopPropagation(),
11875
+ style: {
11876
+ background: `var(${CSS_VAR.surface})`,
11877
+ color: `var(${CSS_VAR.text})`,
11878
+ borderRadius: `var(${CSS_VAR.radius}, 16px)`,
11879
+ padding: 28,
11880
+ maxWidth: 440,
11881
+ width: "100%",
11882
+ boxShadow: "0 1px 3px rgba(10,19,32,.08)",
11883
+ boxSizing: "border-box",
11884
+ ...style
11885
+ },
11886
+ children
11887
+ }
11888
+ )
11889
+ }
11890
+ );
11891
+ }
11892
+ const BASE_STYLE = {
11893
+ minHeight: 44,
11894
+ minWidth: 44,
11895
+ padding: "12px 20px",
11896
+ borderRadius: 16,
11897
+ border: "none",
11898
+ fontSize: 15,
11899
+ fontWeight: 600,
11900
+ fontFamily: "inherit",
11901
+ cursor: "pointer",
11902
+ transition: "background 120ms ease-out",
11903
+ display: "inline-flex",
11904
+ alignItems: "center",
11905
+ justifyContent: "center",
11906
+ gap: 8
11907
+ };
11908
+ const Button = forwardRef(
11909
+ ({ variant = "primary", style, type = "button", ...rest }, ref) => {
11910
+ const variantStyle = variant === "primary" ? {
11911
+ background: `var(${CSS_VAR.action})`,
11912
+ color: `var(${CSS_VAR.actionText})`
11913
+ } : variant === "secondary" ? {
11914
+ background: `var(${CSS_VAR.surfaceMuted})`,
11915
+ color: `var(${CSS_VAR.text})`
11916
+ } : {
11917
+ background: "transparent",
11918
+ color: `var(${CSS_VAR.textMuted})`
11919
+ };
11920
+ return (
11921
+ // eslint-disable-next-line react/button-has-type
11922
+ /* @__PURE__ */ jsx(
11923
+ "button",
11924
+ {
11925
+ ref,
11926
+ type,
11927
+ ...rest,
11928
+ style: { ...BASE_STYLE, ...variantStyle, ...style }
11929
+ }
11930
+ )
11931
+ );
11932
+ }
11933
+ );
11934
+ Button.displayName = "Button";
11935
+ function FocusFrame({
11936
+ size = 96,
11937
+ color,
11938
+ strokeWidth,
11939
+ glow = false,
11940
+ className,
11941
+ style
11942
+ }) {
11943
+ const stroke = strokeWidth ?? autoStroke(size);
11944
+ const arm = size * 0.28;
11945
+ const offset = stroke / 2;
11946
+ const stops = [
11947
+ { x: offset, y: offset, dx: 1, dy: 1 },
11948
+ { x: size - offset, y: offset, dx: -1, dy: 1 },
11949
+ { x: offset, y: size - offset, dx: 1, dy: -1 },
11950
+ { x: size - offset, y: size - offset, dx: -1, dy: -1 }
11951
+ ];
11952
+ return /* @__PURE__ */ jsx(
11953
+ "svg",
11954
+ {
11955
+ width: size,
11956
+ height: size,
11957
+ viewBox: `0 0 ${size} ${size}`,
11958
+ xmlns: "http://www.w3.org/2000/svg",
11959
+ className,
11960
+ style: {
11961
+ color: color ?? `var(${CSS_VAR.action})`,
11962
+ filter: glow ? "drop-shadow(0 0 6px rgba(0,176,248,.5))" : void 0,
11963
+ ...style
11964
+ },
11965
+ "aria-hidden": "true",
11966
+ children: stops.map((c, i) => /* @__PURE__ */ jsx(
11967
+ "path",
11968
+ {
11969
+ d: `M ${c.x + c.dx * arm} ${c.y} L ${c.x} ${c.y} L ${c.x} ${c.y + c.dy * arm}`,
11970
+ fill: "none",
11971
+ stroke: "currentColor",
11972
+ strokeWidth: stroke,
11973
+ strokeLinecap: "round"
11974
+ },
11975
+ i
11976
+ ))
11977
+ }
11978
+ );
11979
+ }
11980
+ function autoStroke(size) {
11981
+ if (size <= 24) return 2;
11982
+ if (size <= 48) return 2.5;
11983
+ return 3.4;
11984
+ }
11985
+ function ChallengeRing({
11986
+ progress,
11987
+ size = 268,
11988
+ children,
11989
+ className,
11990
+ style
11991
+ }) {
11992
+ const clamped = Math.max(0, Math.min(1, Number.isFinite(progress) ? progress : 0));
11993
+ const stroke = 4.2;
11994
+ const radius = size / 2 - stroke;
11995
+ const circumference = 2 * Math.PI * radius;
11996
+ const dashOffset = circumference * (1 - clamped);
11997
+ const frameSize = size + 24;
11998
+ return /* @__PURE__ */ jsxs(
11999
+ "div",
12000
+ {
12001
+ className,
12002
+ style: {
12003
+ position: "relative",
12004
+ width: frameSize,
12005
+ height: frameSize,
12006
+ display: "inline-flex",
12007
+ alignItems: "center",
12008
+ justifyContent: "center",
12009
+ ...style
12010
+ },
12011
+ children: [
12012
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(FocusFrame, { size: frameSize, glow: true }) }),
12013
+ /* @__PURE__ */ jsx(
12014
+ "div",
12015
+ {
12016
+ style: {
12017
+ position: "relative",
12018
+ width: size,
12019
+ height: size,
12020
+ borderRadius: "50%",
12021
+ overflow: "hidden",
12022
+ background: "linear-gradient(160deg, #274B6E, #0F2438, #05080F)"
12023
+ },
12024
+ children
12025
+ }
12026
+ ),
12027
+ /* @__PURE__ */ jsxs(
12028
+ "svg",
12029
+ {
12030
+ width: size,
12031
+ height: size,
12032
+ viewBox: `0 0 ${size} ${size}`,
12033
+ style: {
12034
+ position: "absolute",
12035
+ top: (frameSize - size) / 2,
12036
+ left: (frameSize - size) / 2,
12037
+ transform: "rotate(-90deg)",
12038
+ pointerEvents: "none"
12039
+ },
12040
+ "aria-hidden": "true",
12041
+ children: [
12042
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: "nfid-challenge-arc", x1: "0%", y1: "0%", x2: "100%", y2: "100%", children: [
12043
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#00B0F8" }),
12044
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#0073F4" })
12045
+ ] }) }),
12046
+ /* @__PURE__ */ jsx(
12047
+ "circle",
12048
+ {
12049
+ cx: size / 2,
12050
+ cy: size / 2,
12051
+ r: radius,
12052
+ stroke: "rgba(255,255,255,0.18)",
12053
+ strokeWidth: stroke,
12054
+ fill: "none"
12055
+ }
12056
+ ),
12057
+ /* @__PURE__ */ jsx(
12058
+ "circle",
12059
+ {
12060
+ cx: size / 2,
12061
+ cy: size / 2,
12062
+ r: radius,
12063
+ stroke: `var(${CSS_VAR.action}, url(#nfid-challenge-arc))`,
12064
+ strokeWidth: stroke,
12065
+ strokeLinecap: "round",
12066
+ strokeDasharray: circumference,
12067
+ strokeDashoffset: dashOffset,
12068
+ fill: "none",
12069
+ style: { transition: "stroke-dashoffset 200ms ease-out" }
12070
+ }
12071
+ )
12072
+ ]
12073
+ }
12074
+ )
12075
+ ]
12076
+ }
12077
+ );
12078
+ }
12079
+ function ChallengeGlyph({
12080
+ gesture,
12081
+ size = 48,
12082
+ color,
12083
+ className,
12084
+ style
12085
+ }) {
12086
+ const stroke = 3.4;
12087
+ return /* @__PURE__ */ jsxs(
12088
+ "svg",
12089
+ {
12090
+ width: size,
12091
+ height: size,
12092
+ viewBox: "0 0 48 48",
12093
+ xmlns: "http://www.w3.org/2000/svg",
12094
+ className,
12095
+ style: { color: color ?? `var(${CSS_VAR.action})`, ...style },
12096
+ role: "img",
12097
+ "aria-label": LABELS[gesture],
12098
+ children: [
12099
+ gesture === "blink" ? /* @__PURE__ */ jsx(BlinkGlyph, { stroke }) : /* @__PURE__ */ jsx(FaceGlyph, { stroke }),
12100
+ gesture !== "blink" && gesture !== "center" && /* @__PURE__ */ jsx(Chevron, { gesture, stroke })
12101
+ ]
12102
+ }
12103
+ );
12104
+ }
12105
+ const LABELS = {
12106
+ center: "Centralize o rosto",
12107
+ turn_right: "Vire o rosto para a direita",
12108
+ turn_left: "Vire o rosto para a esquerda",
12109
+ look_up: "Olhe para cima",
12110
+ look_down: "Olhe para baixo",
12111
+ blink: "Pisque os olhos",
12112
+ move_closer: "Aproxime-se da câmera"
12113
+ };
12114
+ function FaceGlyph({ stroke }) {
12115
+ return /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", children: [
12116
+ /* @__PURE__ */ jsx("path", { d: "M14 20c0-5.5 4.5-10 10-10s10 4.5 10 10v4c0 5.5-4.5 10-10 10s-10-4.5-10-10v-4z" }),
12117
+ /* @__PURE__ */ jsx("circle", { cx: "20", cy: "22", r: "1.2", fill: "currentColor", stroke: "none" }),
12118
+ /* @__PURE__ */ jsx("circle", { cx: "28", cy: "22", r: "1.2", fill: "currentColor", stroke: "none" }),
12119
+ /* @__PURE__ */ jsx("path", { d: "M21 28c1 1 2 1.5 3 1.5s2-.5 3-1.5" })
12120
+ ] });
12121
+ }
12122
+ function BlinkGlyph({ stroke }) {
12123
+ return /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", children: [
12124
+ /* @__PURE__ */ jsx("path", { d: "M8 24c4-4 8-6 16-6s12 2 16 6" }),
12125
+ /* @__PURE__ */ jsx("path", { d: "M12 26v3" }),
12126
+ /* @__PURE__ */ jsx("path", { d: "M18 27v3.5" }),
12127
+ /* @__PURE__ */ jsx("path", { d: "M24 27.5v4" }),
12128
+ /* @__PURE__ */ jsx("path", { d: "M30 27v3.5" }),
12129
+ /* @__PURE__ */ jsx("path", { d: "M36 26v3" })
12130
+ ] });
12131
+ }
12132
+ function Chevron({ gesture, stroke }) {
12133
+ const [start2, mid, end] = pointsFor(gesture);
12134
+ return /* @__PURE__ */ jsx(
12135
+ "path",
12136
+ {
12137
+ d: `M ${start2} L ${mid} L ${end}`,
12138
+ fill: "none",
12139
+ stroke: "currentColor",
12140
+ strokeWidth: stroke,
12141
+ strokeLinecap: "round",
12142
+ strokeLinejoin: "round"
12143
+ }
12144
+ );
12145
+ }
12146
+ function pointsFor(g) {
12147
+ switch (g) {
12148
+ case "turn_right":
12149
+ return ["40 18", "44 24", "40 30"];
12150
+ case "turn_left":
12151
+ return ["8 18", "4 24", "8 30"];
12152
+ case "look_up":
12153
+ return ["18 8", "24 4", "30 8"];
12154
+ case "look_down":
12155
+ return ["18 40", "24 44", "30 40"];
12156
+ case "move_closer":
12157
+ return ["18 40", "24 44", "30 40"];
12158
+ default:
12159
+ return ["24 24", "24 24", "24 24"];
12160
+ }
12161
+ }
12162
+ function StatusPill({ tone, children, icon, className }) {
12163
+ const palette = SEMANTIC[tone];
12164
+ return /* @__PURE__ */ jsxs(
12165
+ "span",
12166
+ {
12167
+ className,
12168
+ style: {
12169
+ display: "inline-flex",
12170
+ alignItems: "center",
12171
+ gap: 6,
12172
+ padding: "4px 10px",
12173
+ borderRadius: 9999,
12174
+ background: palette.bg,
12175
+ color: palette.fg,
12176
+ fontSize: 13,
12177
+ fontWeight: 600,
12178
+ fontFamily: "inherit",
12179
+ lineHeight: 1.3
12180
+ },
12181
+ children: [
12182
+ icon,
12183
+ children
12184
+ ]
12185
+ }
12186
+ );
12187
+ }
12188
+ const DEFAULT = {
12189
+ title: "Não deu certo desta vez",
12190
+ hint: "Tente se posicionar em um ambiente mais estável.",
12191
+ actions: [
12192
+ "Segure o dispositivo firme",
12193
+ "Fique de frente para uma luz suave"
12194
+ ]
12195
+ };
12196
+ const MAP = {
12197
+ LIVENESS_LOW: {
12198
+ title: "Precisamos ver com mais nitidez",
12199
+ hint: "A prova de vida veio abaixo do esperado.",
12200
+ actions: [
12201
+ "Faça o movimento com calma até o fim",
12202
+ "Aproxime o rosto sem cortar as bordas"
12203
+ ]
12204
+ },
12205
+ NO_FACE_DETECTED: {
12206
+ title: "Não vi seu rosto",
12207
+ hint: "A câmera não conseguiu identificar um rosto na moldura.",
12208
+ actions: [
12209
+ "Centralize o rosto no oval",
12210
+ "Afaste-se um pouco se estiver muito próximo"
12211
+ ]
12212
+ },
12213
+ LOW_LIGHT: {
12214
+ title: "A luz está baixa demais",
12215
+ hint: "Falta contraste para o algoritmo distinguir seu rosto.",
12216
+ actions: [
12217
+ "Fique de frente para uma janela ou lâmpada",
12218
+ "Evite luz vindo por trás — deixa o rosto em sombra"
12219
+ ]
12220
+ },
12221
+ LOOK_STRAIGHT: {
12222
+ title: "Olhe direto para a câmera",
12223
+ hint: "O rosto está um pouco de lado para reconhecimento facial.",
12224
+ actions: [
12225
+ "Fique de frente para a câmera",
12226
+ "Mantenha o queixo paralelo ao chão"
12227
+ ]
12228
+ },
12229
+ RECOGNITION_FAILED: {
12230
+ title: "Não consegui confirmar",
12231
+ hint: "A imagem não bate com o cadastro que temos.",
12232
+ actions: [
12233
+ "Retire óculos ou máscara temporariamente",
12234
+ "Faça em um lugar mais iluminado"
12235
+ ]
12236
+ },
12237
+ INVALID_TOKEN: {
12238
+ title: "Sessão expirada",
12239
+ hint: "A conexão com o serviço perdeu validade.",
12240
+ actions: [
12241
+ "Volte à tela anterior e tente novamente",
12242
+ "Se persistir, entre em contato com o suporte"
12243
+ ]
12244
+ },
12245
+ NETWORK: {
12246
+ title: "A conexão está oscilando",
12247
+ hint: "A imagem não chegou até nosso servidor.",
12248
+ actions: [
12249
+ "Verifique a rede do dispositivo",
12250
+ "Repita a captura em alguns segundos"
12251
+ ]
12252
+ }
12253
+ };
12254
+ function copyForErrorCode(code) {
12255
+ if (!code) return DEFAULT;
12256
+ const key = code.toUpperCase();
12257
+ return MAP[key] ?? DEFAULT;
12258
+ }
12259
+ function detectBrowser(ua = navigator.userAgent) {
12260
+ const s = ua.toLowerCase();
12261
+ if (s.includes("edg/")) return "edge";
12262
+ if (s.includes("firefox")) return "firefox";
12263
+ if (s.includes("chrome") && !s.includes("edg/")) return "chrome";
12264
+ if (s.includes("safari") && !s.includes("chrome")) return "safari";
12265
+ return "unknown";
12266
+ }
12267
+ const CAMERA_INSTRUCTIONS = {
12268
+ chrome: "Clique no cadeado da barra de endereço → Configurações do site → Câmera → Permitir. Recarregue a página.",
12269
+ edge: "Clique no cadeado da barra de endereço → Permissões do site → Câmera → Permitir. Recarregue a página.",
12270
+ firefox: 'Clique no ícone da câmera bloqueada na barra de endereço e escolha "Permitir". Recarregue a página.',
12271
+ safari: "Menu Safari → Configurações → Sites → Câmera → Permitir para este site. Recarregue a página.",
12272
+ unknown: 'Abra as configurações do navegador, procure por "câmera" e permita o acesso para este site. Depois recarregue a página.'
12273
+ };
12274
+ function cameraInstructionFor(browser) {
12275
+ return CAMERA_INSTRUCTIONS[browser];
12276
+ }
12277
+ function classifyMediaError(err) {
12278
+ if (!err || typeof err !== "object") return "UNKNOWN";
12279
+ const name = (err.name ?? "").toString();
12280
+ if (name === "NotAllowedError" || name === "SecurityError") return "PERMISSION_DENIED";
12281
+ if (name === "NotFoundError" || name === "OverconstrainedError") return "NO_CAMERA";
12282
+ if (name === "NotReadableError" || name === "TrackStartError") return "IN_USE";
12283
+ return "UNKNOWN";
12284
+ }
12285
+ const STYLE_ID$1 = "neofaceid-state-views-styles";
12286
+ const CSS = `
12287
+ .neofaceid-root .nfid-state {
12288
+ display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
12289
+ padding: 8px 8px 4px;
12290
+ }
12291
+ .neofaceid-root .nfid-state h2 {
12292
+ margin: 6px 0 0; font-size: 20px; font-weight: 700; color: var(--nfid-text); line-height: 1.25;
12293
+ letter-spacing: -0.015em;
12294
+ }
12295
+ .neofaceid-root .nfid-state p {
12296
+ margin: 0; font-size: 14px; color: var(--nfid-text-muted); line-height: 1.5;
12297
+ }
12298
+ .neofaceid-root .nfid-state-actions {
12299
+ display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 12px;
12300
+ }
12301
+ .neofaceid-root .nfid-state-actions--row {
12302
+ flex-direction: row; justify-content: center;
12303
+ }
12304
+ .neofaceid-root .nfid-state-icon-success {
12305
+ width: 64px; height: 64px; color: #0E9F6E;
12306
+ animation: nfidCelebrate 500ms cubic-bezier(.2,1.4,.4,1) both;
12307
+ }
12308
+ @keyframes nfidCelebrate {
12309
+ 0% { transform: scale(0.6); opacity: 0; }
12310
+ 60% { transform: scale(1.08); opacity: 1; }
12311
+ 100% { transform: scale(1); }
12312
+ }
12313
+ .neofaceid-root .nfid-state-warn-icon { width: 56px; height: 56px; color: #F1BE00; }
12314
+ .neofaceid-root .nfid-state-cameraoff-icon { width: 56px; height: 56px; color: var(--nfid-text-muted); }
12315
+ .neofaceid-root .nfid-state-hints {
12316
+ display: flex; flex-direction: column; gap: 6px; margin-top: 8px; text-align: left; width: 100%;
12317
+ }
12318
+ .neofaceid-root .nfid-state-hint {
12319
+ display: flex; gap: 8px; align-items: flex-start; font-size: 13px; color: var(--nfid-text);
12320
+ padding: 8px 12px; background: var(--nfid-surface-muted); border-radius: 12px;
12321
+ }
12322
+ .neofaceid-root .nfid-state-hint::before { content: "•"; color: var(--nfid-action); font-weight: 700; }
12323
+ .neofaceid-root .nfid-state-attempt {
12324
+ font-size: 12px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
12325
+ color: var(--nfid-text-subtle);
12326
+ }
12327
+ .neofaceid-root .nfid-state-techcode {
12328
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
12329
+ font-size: 11px; color: var(--nfid-text-subtle); margin-top: 8px;
12330
+ }
12331
+ `;
12332
+ function useStateStyles() {
12333
+ useEffect(() => {
12334
+ injectThemeStyles$1();
12335
+ injectScopedStyles$1(STYLE_ID$1, CSS);
12336
+ }, []);
12337
+ }
12338
+ function AuthorizeSuccess({ appName, onContinue }) {
12339
+ useStateStyles();
12340
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-state", role: "status", "aria-live": "polite", children: [
12341
+ /* @__PURE__ */ jsxs(
12342
+ "svg",
12343
+ {
12344
+ className: "nfid-state-icon-success",
12345
+ viewBox: "0 0 64 64",
12346
+ fill: "none",
12347
+ stroke: "currentColor",
12348
+ strokeWidth: "4",
12349
+ strokeLinecap: "round",
12350
+ strokeLinejoin: "round",
12351
+ "aria-hidden": "true",
12352
+ children: [
12353
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r: "28" }),
12354
+ /* @__PURE__ */ jsx("path", { d: "M20 33 L28 41 L44 24" })
12355
+ ]
12356
+ }
12357
+ ),
12358
+ /* @__PURE__ */ jsx("h2", { children: "Identidade confirmada" }),
12359
+ /* @__PURE__ */ jsxs("p", { children: [
12360
+ "Você já pode continuar no ",
12361
+ appName,
12362
+ "."
12363
+ ] }),
12364
+ /* @__PURE__ */ jsx("div", { className: "nfid-state-actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onContinue, children: "Continuar" }) })
12365
+ ] });
12366
+ }
12367
+ function formatHms(d) {
12368
+ const hh = String(d.getHours()).padStart(2, "0");
12369
+ const mm = String(d.getMinutes()).padStart(2, "0");
12370
+ const ss = String(d.getSeconds()).padStart(2, "0");
12371
+ return `${hh}:${mm}:${ss}`;
12372
+ }
12373
+ function AuthorizeFailure({
12374
+ errorCode,
12375
+ attempt,
12376
+ maxAttempts,
12377
+ timestamp,
12378
+ onRetry,
12379
+ onFallback
12380
+ }) {
12381
+ useStateStyles();
12382
+ const copy = copyForErrorCode(errorCode);
12383
+ const stamp = timestamp ?? /* @__PURE__ */ new Date();
12384
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-state", role: "alert", "aria-live": "assertive", children: [
12385
+ /* @__PURE__ */ jsxs(StatusPill, { tone: "attention", children: [
12386
+ "Tentativa ",
12387
+ attempt,
12388
+ " de ",
12389
+ maxAttempts
12390
+ ] }),
12391
+ /* @__PURE__ */ jsxs(
12392
+ "svg",
12393
+ {
12394
+ className: "nfid-state-warn-icon",
12395
+ viewBox: "0 0 64 64",
12396
+ fill: "none",
12397
+ stroke: "currentColor",
12398
+ strokeWidth: "3",
12399
+ strokeLinecap: "round",
12400
+ strokeLinejoin: "round",
12401
+ "aria-hidden": "true",
12402
+ children: [
12403
+ /* @__PURE__ */ jsx("path", { d: "M32 8 L58 54 H6 Z" }),
12404
+ /* @__PURE__ */ jsx("path", { d: "M32 26 V38" }),
12405
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "45", r: "2", fill: "currentColor" })
12406
+ ]
12407
+ }
12408
+ ),
12409
+ /* @__PURE__ */ jsx("h2", { children: copy.title }),
12410
+ /* @__PURE__ */ jsx("p", { children: copy.hint }),
12411
+ /* @__PURE__ */ jsxs("div", { className: "nfid-state-hints", "aria-label": "O que fazer", children: [
12412
+ /* @__PURE__ */ jsx("div", { className: "nfid-state-hint", children: copy.actions[0] }),
12413
+ /* @__PURE__ */ jsx("div", { className: "nfid-state-hint", children: copy.actions[1] })
12414
+ ] }),
12415
+ /* @__PURE__ */ jsxs("div", { className: "nfid-state-actions", children: [
12416
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onRetry, children: "Tentar de novo" }),
12417
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onFallback, children: "Verificar de outro jeito" })
12418
+ ] }),
12419
+ /* @__PURE__ */ jsxs("p", { className: "nfid-state-techcode", children: [
12420
+ (errorCode ?? "UNKNOWN").toUpperCase(),
12421
+ " · ",
12422
+ formatHms(stamp)
12423
+ ] })
12424
+ ] });
12425
+ }
12426
+ function AuthorizePermissionDenied({
12427
+ browser,
12428
+ onFallback,
12429
+ onCancel
12430
+ }) {
12431
+ useStateStyles();
12432
+ const detected = browser ?? detectBrowser();
12433
+ const instruction = cameraInstructionFor(detected);
12434
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-state", role: "alert", "aria-live": "assertive", children: [
12435
+ /* @__PURE__ */ jsxs(
12436
+ "svg",
12437
+ {
12438
+ className: "nfid-state-cameraoff-icon",
12439
+ viewBox: "0 0 24 24",
12440
+ fill: "none",
12441
+ stroke: "currentColor",
12442
+ strokeWidth: "1.75",
12443
+ strokeLinecap: "round",
12444
+ strokeLinejoin: "round",
12445
+ "aria-hidden": "true",
12446
+ children: [
12447
+ /* @__PURE__ */ jsx("path", { d: "m3 3 18 18" }),
12448
+ /* @__PURE__ */ jsx("path", { d: "M20.66 15.34A2 2 0 0 0 22 13.5V7.5a2 2 0 0 0-3.13-1.65L15 8.5" }),
12449
+ /* @__PURE__ */ jsx("path", { d: "M9 6H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1" })
12450
+ ]
12451
+ }
12452
+ ),
12453
+ /* @__PURE__ */ jsx("h2", { children: "Câmera bloqueada" }),
12454
+ /* @__PURE__ */ jsx("p", { children: "Precisamos da câmera para confirmar que é você." }),
12455
+ /* @__PURE__ */ jsx("p", { style: { marginTop: 8 }, children: instruction }),
12456
+ /* @__PURE__ */ jsxs("div", { className: "nfid-state-actions", children: [
12457
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onFallback, children: "Verificar de outro jeito" }),
12458
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Agora não" })
12459
+ ] })
12460
+ ] });
12461
+ }
12462
+ const STYLE_ID = "neofaceid-authorize-styles";
12463
+ const GESTURE_TO_GLYPH = {
12464
+ center: "center",
12465
+ turn_right: "turn_right",
12466
+ turn_left: "turn_left",
12467
+ look_up: "look_up",
12468
+ look_down: "look_down",
12469
+ blink: "blink",
12470
+ move_closer: "move_closer",
12471
+ smile: "blink",
12472
+ open_mouth: "blink"
12473
+ };
12474
+ const GESTURE_TO_INSTRUCTION = {
12475
+ center: "Centralize seu rosto no oval",
12476
+ turn_right: "Vire lentamente o rosto para a direita",
12477
+ turn_left: "Vire lentamente o rosto para a esquerda",
12478
+ look_up: "Olhe para cima",
12479
+ look_down: "Olhe para baixo",
12480
+ blink: "Pisque os olhos",
12481
+ move_closer: "Aproxime-se da câmera",
12482
+ smile: "Sorria para a câmera",
12483
+ open_mouth: "Abra a boca"
12484
+ };
12485
+ function formatCurrency(amount, locale) {
12486
+ try {
12487
+ return new Intl.NumberFormat(locale, {
12488
+ style: "currency",
12489
+ currency: "BRL"
12490
+ }).format(amount);
12491
+ } catch {
12492
+ return `R$ ${amount.toFixed(2)}`;
12493
+ }
12494
+ }
12495
+ const MAX_ATTEMPTS = 3;
12496
+ function AuthorizeModalComponent({
12497
+ applicationToken,
12498
+ appName,
12499
+ amount,
12500
+ onChallengeStart,
12501
+ onChallengeComplete,
12502
+ onSuccess,
12503
+ onError,
12504
+ onCancel,
12505
+ onClose
12506
+ }) {
12507
+ const videoRef = useRef(null);
12508
+ const [progress, setProgress] = useState(0);
12509
+ const [currentGesture, setCurrentGesture] = useState(null);
12510
+ const [phase, setPhase] = useState("preparing");
12511
+ const [attempt, setAttempt] = useState(1);
12512
+ const [failureCode, setFailureCode] = useState(void 0);
12513
+ const [failureAt, setFailureAt] = useState(void 0);
12514
+ const [runCounter, setRunCounter] = useState(0);
12515
+ const cancelRef = useRef(false);
12516
+ const locale = getLocale();
12517
+ const headerAppName = appName ?? getAppName() ?? "sua aplicação";
12518
+ const headerAmount = typeof amount === "number" && Number.isFinite(amount) ? formatCurrency(amount, locale) : null;
12519
+ useEffect(() => {
12520
+ injectThemeStyles$1();
12521
+ injectScopedStyles$1(
12522
+ STYLE_ID,
12523
+ `
12524
+ .neofaceid-root .nfid-authorize-header { display: flex; flex-direction: column; gap: 4px; margin-bottom: 16px; }
12525
+ .neofaceid-root .nfid-authorize-appname { font-size: 15px; font-weight: 700; color: var(--nfid-text); line-height: 1.2; }
12526
+ .neofaceid-root .nfid-authorize-amount { font-size: 13px; color: var(--nfid-text-muted); }
12527
+ .neofaceid-root .nfid-authorize-brand { font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var(--nfid-text-subtle); }
12528
+ .neofaceid-root .nfid-authorize-video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
12529
+ .neofaceid-root .nfid-authorize-instruction { margin: 20px 0 0; font-size: 17px; font-weight: 600; color: var(--nfid-text); text-align: center; line-height: 1.3; }
12530
+ .neofaceid-root .nfid-authorize-support { margin: 6px 0 0; font-size: 13px; color: var(--nfid-text-muted); text-align: center; }
12531
+ .neofaceid-root .nfid-authorize-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; align-items: center; }
12532
+ .neofaceid-root .nfid-authorize-footer { display: flex; justify-content: center; margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--nfid-line); }
12533
+ .neofaceid-root .nfid-authorize-seal { font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var(--nfid-text-subtle); }
12534
+ .neofaceid-root .nfid-authorize-close {
12535
+ position: absolute; top: 8px; right: 8px;
12536
+ min-width: 44px; min-height: 44px;
12537
+ background: transparent; border: none;
12538
+ display: inline-flex; align-items: center; justify-content: center;
12539
+ color: var(--nfid-text-muted); cursor: pointer; border-radius: 22px;
12540
+ }
12541
+ .neofaceid-root .nfid-authorize-close:hover { background: var(--nfid-surface-muted); }
12542
+ .neofaceid-root .nfid-authorize-close:focus-visible { outline: 2px solid var(--nfid-focus-ring); outline-offset: 2px; }
12543
+ @media (prefers-reduced-motion: reduce) {
12544
+ .neofaceid-root svg circle { transition: none !important; }
12545
+ }
12546
+ `
12547
+ );
12548
+ }, []);
12549
+ useEffect(() => {
12550
+ let stream = null;
12551
+ cancelRef.current = false;
12552
+ setPhase("preparing");
12553
+ setProgress(0);
12554
+ setCurrentGesture(null);
12555
+ async function run() {
12556
+ try {
12557
+ try {
12558
+ stream = await navigator.mediaDevices.getUserMedia({
12559
+ video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } }
12560
+ });
12561
+ } catch (mediaErr) {
12562
+ const kind = classifyMediaError(mediaErr);
12563
+ if (kind === "PERMISSION_DENIED") {
12564
+ setPhase("permission-denied");
12565
+ return;
12566
+ }
12567
+ throw new NeoFaceError(
12568
+ kind === "NO_CAMERA" ? "Câmera não encontrada" : kind === "IN_USE" ? "Câmera em uso por outro app" : "Falha ao acessar a câmera",
12569
+ ErrorType.CAMERA_ERROR
12570
+ );
12571
+ }
12572
+ if (videoRef.current) {
12573
+ videoRef.current.srcObject = stream;
12574
+ videoRef.current.play().catch(() => {
12575
+ });
12576
+ }
12577
+ setPhase("challenging");
12578
+ const result = await runLivenessChallenge({
12579
+ applicationToken,
12580
+ purpose: "authorization",
12581
+ collectFramesForGesture: async (gesture, index, total) => {
12582
+ if (cancelRef.current) throw new NeoFaceError("Cancelado", ErrorType.UNKNOWN);
12583
+ setCurrentGesture(gesture);
12584
+ setProgress(0);
12585
+ if (onChallengeStart) onChallengeStart(gesture, index, total);
12586
+ await progressiveHold(setProgress);
12587
+ if (onChallengeComplete) onChallengeComplete(gesture, index, total);
12588
+ const frame = await captureFrame(videoRef.current);
12589
+ return frame ? [frame] : [];
12590
+ }
12591
+ });
12592
+ if (stream) stream.getTracks().forEach((t) => t.stop());
12593
+ stream = null;
12594
+ if (result.success === false || result.liveness_passed === false) {
12595
+ setFailureCode("LIVENESS_LOW");
12596
+ setFailureAt(/* @__PURE__ */ new Date());
12597
+ setPhase("failure");
12598
+ return;
12599
+ }
12600
+ setPhase("success");
12601
+ if (onSuccess) onSuccess(result);
12602
+ } catch (err) {
12603
+ if (cancelRef.current) return;
12604
+ const nfe = err instanceof NeoFaceError ? err : new NeoFaceError(err instanceof Error ? err.message : "Falha", ErrorType.UNKNOWN);
12605
+ setFailureCode(nfe.type);
12606
+ setFailureAt(/* @__PURE__ */ new Date());
12607
+ setPhase("failure");
12608
+ if (onError) onError(nfe);
12609
+ } finally {
12610
+ if (stream) stream.getTracks().forEach((t) => t.stop());
12611
+ }
12612
+ }
12613
+ run();
12614
+ return () => {
12615
+ cancelRef.current = true;
12616
+ if (stream) stream.getTracks().forEach((t) => t.stop());
12617
+ };
12618
+ }, [runCounter]);
12619
+ const handleClose = () => {
12620
+ cancelRef.current = true;
12621
+ if (onCancel) onCancel();
12622
+ onClose();
12623
+ };
12624
+ const handleRetry = () => {
12625
+ if (attempt >= MAX_ATTEMPTS) {
12626
+ handleClose();
12627
+ return;
12628
+ }
12629
+ setAttempt((a) => a + 1);
12630
+ setFailureCode(void 0);
12631
+ setFailureAt(void 0);
12632
+ setRunCounter((c) => c + 1);
12633
+ };
12634
+ const handleFallback = () => {
12635
+ cancelRef.current = true;
12636
+ if (onError) {
12637
+ onError(
12638
+ new NeoFaceError(
12639
+ "Fluxo alternativo solicitado pelo usuário",
12640
+ ErrorType.CONSENT_DENIED
12641
+ )
12642
+ );
12643
+ }
12644
+ onClose();
12645
+ };
12646
+ const gestureKey = currentGesture ? GESTURE_TO_GLYPH[currentGesture.gesture_key] ?? "center" : "center";
12647
+ const instruction = currentGesture ? GESTURE_TO_INSTRUCTION[currentGesture.gesture_key] ?? "Siga a instrução" : "Preparando a câmera…";
12648
+ if (phase === "success") {
12649
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Identidade confirmada", children: /* @__PURE__ */ jsx(AuthorizeSuccess, { appName: headerAppName, onContinue: onClose }) });
12650
+ }
12651
+ if (phase === "permission-denied") {
12652
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Câmera bloqueada", children: /* @__PURE__ */ jsx(AuthorizePermissionDenied, { onFallback: handleFallback, onCancel: handleClose }) });
12653
+ }
12654
+ if (phase === "failure") {
12655
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Não foi possível confirmar", children: /* @__PURE__ */ jsx(
12656
+ AuthorizeFailure,
12657
+ {
12658
+ errorCode: failureCode,
12659
+ attempt,
12660
+ maxAttempts: MAX_ATTEMPTS,
12661
+ timestamp: failureAt,
12662
+ onRetry: handleRetry,
12663
+ onFallback: handleFallback
12664
+ }
12665
+ ) });
12666
+ }
12667
+ return /* @__PURE__ */ jsx(Sheet, { open: true, onDismiss: void 0, dismissOnOverlay: false, ariaLabel: "Autorização por biometria", children: /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
12668
+ /* @__PURE__ */ jsx(
12669
+ "button",
12670
+ {
12671
+ type: "button",
12672
+ className: "nfid-authorize-close",
12673
+ onClick: handleClose,
12674
+ "aria-label": "Cancelar autorização",
12675
+ children: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" }) })
12676
+ }
12677
+ ),
12678
+ /* @__PURE__ */ jsxs("div", { className: "nfid-authorize-header", children: [
12679
+ /* @__PURE__ */ jsx("span", { className: "nfid-authorize-appname", children: headerAppName }),
12680
+ headerAmount && /* @__PURE__ */ jsxs("span", { className: "nfid-authorize-amount", children: [
12681
+ "Autorizando ",
12682
+ headerAmount
12683
+ ] }),
12684
+ /* @__PURE__ */ jsx("span", { className: "nfid-authorize-brand", children: "NeoFaceID" })
12685
+ ] }),
12686
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx(ChallengeRing, { progress, size: 220, children: /* @__PURE__ */ jsx("video", { ref: videoRef, className: "nfid-authorize-video", autoPlay: true, muted: true, playsInline: true }) }) }),
12687
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 12, marginTop: 16 }, children: [
12688
+ /* @__PURE__ */ jsx(ChallengeGlyph, { gesture: gestureKey, size: 40 }),
12689
+ /* @__PURE__ */ jsx(
12690
+ "p",
12691
+ {
12692
+ className: "nfid-authorize-instruction",
12693
+ "aria-live": "assertive",
12694
+ style: { margin: 0 },
12695
+ children: instruction
12696
+ }
12697
+ )
12698
+ ] }),
12699
+ phase === "preparing" && /* @__PURE__ */ jsx("p", { className: "nfid-authorize-support", children: "Ligando sua câmera…" }),
12700
+ /* @__PURE__ */ jsx("div", { className: "nfid-authorize-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleClose, children: "Não consigo fazer esse movimento" }) }),
12701
+ /* @__PURE__ */ jsx("div", { className: "nfid-authorize-footer", children: /* @__PURE__ */ jsx("span", { className: "nfid-authorize-seal", children: "Protegido por NeoFaceID" }) })
12702
+ ] }) });
12703
+ }
12704
+ async function progressiveHold(setter) {
12705
+ const total = 1500;
12706
+ const start2 = Date.now();
12707
+ return new Promise((resolve) => {
12708
+ const tick = () => {
12709
+ const elapsed = Date.now() - start2;
12710
+ const p = Math.min(1, elapsed / total);
12711
+ setter(p);
12712
+ if (elapsed >= total) resolve();
12713
+ else setTimeout(tick, 50);
12714
+ };
12715
+ tick();
12716
+ });
12717
+ }
12718
+ async function captureFrame(video) {
12719
+ if (!video || video.videoWidth === 0) return null;
12720
+ const canvas = document.createElement("canvas");
12721
+ canvas.width = video.videoWidth;
12722
+ canvas.height = video.videoHeight;
12723
+ const ctx = canvas.getContext("2d");
12724
+ if (!ctx) return null;
12725
+ ctx.drawImage(video, 0, 0);
12726
+ return canvas.toDataURL("image/jpeg", 0.85);
12727
+ }
12728
+ async function authorize(options) {
12729
+ const consentAccepted = await requestConsent({
12730
+ appName: options.appName,
12731
+ flow: "verification"
12732
+ });
12733
+ if (!consentAccepted) {
12734
+ if (options.onError) {
12735
+ options.onError(new NeoFaceError("Consentimento recusado", ErrorType.CONSENT_DENIED));
12736
+ }
12737
+ return;
12738
+ }
12739
+ const container = document.createElement("div");
12740
+ container.id = "neofaceid-authorize-container";
12741
+ document.body.appendChild(container);
12742
+ const root = createRoot(container);
12743
+ const cleanup = () => {
12744
+ root.unmount();
12745
+ if (document.body.contains(container)) document.body.removeChild(container);
12746
+ };
12747
+ root.render(/* @__PURE__ */ jsx(AuthorizeModalComponent, { ...options, onClose: cleanup }));
12748
+ }
11831
12749
  const DOCUMENT_INFO = {
11832
12750
  RG: {
11833
12751
  name: "Documento de Identidade (RG / CIN / CPF)",
@@ -12986,6 +13904,7 @@ export {
12986
13904
  NeoFaceID,
12987
13905
  RELEASE_DATE,
12988
13906
  VERSION,
13907
+ authorize,
12989
13908
  authorizeOperation,
12990
13909
  biometricLogin,
12991
13910
  biometricLoginWithFallback,