@neofaceid/web-sdk 1.30.0 → 1.31.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";
@@ -4504,6 +4504,15 @@ function buildRootCss(theme) {
4504
4504
  .neofaceid-root :focus-visible {
4505
4505
  outline: 2px solid var(${CSS_VAR.focusRing});
4506
4506
  outline-offset: 2px;
4507
+ }
4508
+ @media (prefers-reduced-motion: reduce) {
4509
+ .neofaceid-root,
4510
+ .neofaceid-root * {
4511
+ animation-duration: 0.001ms !important;
4512
+ animation-iteration-count: 1 !important;
4513
+ transition-duration: 0.001ms !important;
4514
+ scroll-behavior: auto !important;
4515
+ }
4507
4516
  }`;
4508
4517
  }
4509
4518
  function injectScopedStyles$1(id, css) {
@@ -8773,7 +8782,7 @@ class BiometricCaptureModal {
8773
8782
  try {
8774
8783
  await this.createModal();
8775
8784
  await this.initializeCamera();
8776
- this.startCountdown();
8785
+ this.enableCaptureButton();
8777
8786
  } catch (error) {
8778
8787
  this.options.onError(
8779
8788
  new NeoFaceError(
@@ -8806,7 +8815,7 @@ class BiometricCaptureModal {
8806
8815
  <div class="neofaceid-modal-content">
8807
8816
  <div class="neofaceid-modal-header">
8808
8817
  <h2>${title}</h2>
8809
- <button class="neofaceid-close-btn" type="button">&times;</button>
8818
+ <button class="neofaceid-close-btn" type="button" aria-label="Fechar">&times;</button>
8810
8819
  </div>
8811
8820
  <div class="neofaceid-modal-body">
8812
8821
  <p class="neofaceid-subtitle">${subtitle}</p>
@@ -8815,9 +8824,6 @@ class BiometricCaptureModal {
8815
8824
  <canvas class="neofaceid-canvas" style="display: none;"></canvas>
8816
8825
  <div class="neofaceid-overlay">
8817
8826
  <div class="neofaceid-frame"></div>
8818
- <div class="neofaceid-countdown">
8819
- <span class="neofaceid-countdown-number">${this.options.countdown || 3}</span>
8820
- </div>
8821
8827
  </div>
8822
8828
  </div>
8823
8829
  <div class="neofaceid-status">
@@ -8828,6 +8834,9 @@ class BiometricCaptureModal {
8828
8834
  <button class="neofaceid-cancel-btn" type="button">Cancelar</button>
8829
8835
  <button class="neofaceid-capture-btn" type="button" disabled>Capturar</button>
8830
8836
  </div>
8837
+ <div class="neofaceid-seal-row">
8838
+ <span class="neofaceid-seal">Protegido por NeoFaceID</span>
8839
+ </div>
8831
8840
  </div>
8832
8841
  </div>
8833
8842
  `;
@@ -8864,42 +8873,24 @@ class BiometricCaptureModal {
8864
8873
  }
8865
8874
  }
8866
8875
  /**
8867
- * Inicia a contagem regressiva
8876
+ * NEO-413 · US14.6 (item 18) — a contagem "3, 2, 1" foi removida.
8877
+ * Agora habilita direto o botão "Capturar" assim que a câmera fica pronta.
8878
+ * Vivacidade fica com o desafio `runLivenessChallenge` (NEO-408), servidor decide.
8868
8879
  */
8869
- startCountdown() {
8880
+ enableCaptureButton() {
8870
8881
  if (!this.modal) return;
8871
- const countdownElement = this.modal.querySelector(".neofaceid-countdown-number");
8872
8882
  const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
8873
8883
  const statusText = this.modal.querySelector(".neofaceid-status-text");
8874
- let count = this.options.countdown || 3;
8875
- if (countdownElement) {
8876
- countdownElement.textContent = count.toString();
8884
+ if (captureBtn) {
8885
+ captureBtn.disabled = false;
8886
+ captureBtn.textContent = "Capturar";
8887
+ }
8888
+ if (statusText) {
8889
+ statusText.textContent = "Pronto para capturar";
8890
+ }
8891
+ if (this.options.mode === "auto") {
8892
+ setTimeout(() => this.captureImage(), 300);
8877
8893
  }
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
8894
  }
8904
8895
  /**
8905
8896
  * Captura a imagem da câmera
@@ -9081,18 +9072,22 @@ class BiometricCaptureModal {
9081
9072
  .neofaceid-close-btn {
9082
9073
  background: none;
9083
9074
  border: none;
9084
- font-size: 28px;
9075
+ font-size: 22px;
9085
9076
  cursor: pointer;
9086
9077
  color: #666;
9087
9078
  padding: 0;
9088
- width: 32px;
9089
- height: 32px;
9079
+ min-width: 44px;
9080
+ min-height: 44px;
9090
9081
  display: flex;
9091
9082
  align-items: center;
9092
9083
  justify-content: center;
9093
- border-radius: 50%;
9084
+ border-radius: 22px;
9094
9085
  transition: background-color 0.2s;
9095
9086
  }
9087
+ .neofaceid-close-btn:focus-visible {
9088
+ outline: 2px solid var(--nfid-focus-ring);
9089
+ outline-offset: 2px;
9090
+ }
9096
9091
 
9097
9092
  .neofaceid-close-btn:hover {
9098
9093
  background-color: #f0f0f0;
@@ -9150,24 +9145,18 @@ class BiometricCaptureModal {
9150
9145
  animation: pulse 2s infinite;
9151
9146
  }
9152
9147
 
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;
9148
+ .neofaceid-seal-row {
9163
9149
  display: flex;
9164
- align-items: center;
9165
9150
  justify-content: center;
9151
+ padding: 12px 20px 16px;
9152
+ border-top: 1px solid var(--nfid-line, #E6ECF4);
9166
9153
  }
9167
-
9168
- .neofaceid-countdown-number {
9169
- font-size: 24px;
9170
- font-weight: bold;
9154
+ .neofaceid-seal {
9155
+ font-size: 11px;
9156
+ font-weight: 700;
9157
+ letter-spacing: 0.09em;
9158
+ text-transform: uppercase;
9159
+ color: var(--nfid-text-subtle, #617489);
9171
9160
  }
9172
9161
 
9173
9162
  .neofaceid-status {
@@ -10871,7 +10860,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
10871
10860
  initializeBiometricDetection,
10872
10861
  isAdvancedDetectionAvailable
10873
10862
  }, Symbol.toStringTag, { value: "Module" }));
10874
- const VERSION = "1.30.0";
10863
+ const VERSION = "1.31.0";
10875
10864
  const RELEASE_DATE = "2026-09-02";
10876
10865
  let cachedSession = null;
10877
10866
  function getCachedCaptureSession() {
@@ -11185,6 +11174,9 @@ class OnboardingCaptureModal {
11185
11174
  <button class="neofaceid-cancel-btn">Cancelar</button>
11186
11175
  <button class="neofaceid-capture-btn" disabled>Capturar</button>
11187
11176
  </div>
11177
+ <div class="neofaceid-seal-row">
11178
+ <span class="neofaceid-seal">Protegido por NeoFaceID</span>
11179
+ </div>
11188
11180
  </div>
11189
11181
  `;
11190
11182
  injectThemeStyles();
@@ -11208,6 +11200,8 @@ class OnboardingCaptureModal {
11208
11200
  .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
11201
  .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
11202
  .neofaceid-root .neofaceid-capture-btn:disabled { background: var(--nfid-line); cursor:not-allowed; }
11203
+ .neofaceid-root .neofaceid-seal-row { display:flex; justify-content:center; padding:12px 20px 14px; border-top: 1px solid var(--nfid-line); }
11204
+ .neofaceid-root .neofaceid-seal { font-size:11px; font-weight:700; letter-spacing:0.09em; text-transform:uppercase; color: var(--nfid-text-subtle); }
11211
11205
  `
11212
11206
  );
11213
11207
  overlay.classList.add("neofaceid-root");
@@ -11828,6 +11822,552 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
11828
11822
  throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
11829
11823
  }
11830
11824
  };
11825
+ function Sheet({
11826
+ open,
11827
+ onDismiss,
11828
+ dismissOnOverlay = true,
11829
+ ariaLabel,
11830
+ ariaLabelledBy,
11831
+ children,
11832
+ className,
11833
+ style
11834
+ }) {
11835
+ useEffect(() => {
11836
+ injectThemeStyles$1();
11837
+ }, []);
11838
+ if (!open) return null;
11839
+ const handleOverlay = () => {
11840
+ if (dismissOnOverlay && onDismiss) onDismiss();
11841
+ };
11842
+ return /* @__PURE__ */ jsx(
11843
+ "div",
11844
+ {
11845
+ className: "neofaceid-root",
11846
+ role: "presentation",
11847
+ style: {
11848
+ position: "fixed",
11849
+ inset: 0,
11850
+ zIndex: 10001,
11851
+ display: "flex",
11852
+ alignItems: "center",
11853
+ justifyContent: "center",
11854
+ padding: 20,
11855
+ background: `var(${CSS_VAR.overlay})`,
11856
+ backdropFilter: "blur(3px)",
11857
+ WebkitBackdropFilter: "blur(3px)"
11858
+ },
11859
+ onClick: handleOverlay,
11860
+ children: /* @__PURE__ */ jsx(
11861
+ "div",
11862
+ {
11863
+ role: "dialog",
11864
+ "aria-modal": "true",
11865
+ "aria-label": ariaLabel,
11866
+ "aria-labelledby": ariaLabelledBy,
11867
+ className,
11868
+ onClick: (e) => e.stopPropagation(),
11869
+ style: {
11870
+ background: `var(${CSS_VAR.surface})`,
11871
+ color: `var(${CSS_VAR.text})`,
11872
+ borderRadius: `var(${CSS_VAR.radius}, 16px)`,
11873
+ padding: 28,
11874
+ maxWidth: 440,
11875
+ width: "100%",
11876
+ boxShadow: "0 1px 3px rgba(10,19,32,.08)",
11877
+ boxSizing: "border-box",
11878
+ ...style
11879
+ },
11880
+ children
11881
+ }
11882
+ )
11883
+ }
11884
+ );
11885
+ }
11886
+ const BASE_STYLE = {
11887
+ minHeight: 44,
11888
+ minWidth: 44,
11889
+ padding: "12px 20px",
11890
+ borderRadius: 16,
11891
+ border: "none",
11892
+ fontSize: 15,
11893
+ fontWeight: 600,
11894
+ fontFamily: "inherit",
11895
+ cursor: "pointer",
11896
+ transition: "background 120ms ease-out",
11897
+ display: "inline-flex",
11898
+ alignItems: "center",
11899
+ justifyContent: "center",
11900
+ gap: 8
11901
+ };
11902
+ const Button = forwardRef(
11903
+ ({ variant = "primary", style, type = "button", ...rest }, ref) => {
11904
+ const variantStyle = variant === "primary" ? {
11905
+ background: `var(${CSS_VAR.action})`,
11906
+ color: `var(${CSS_VAR.actionText})`
11907
+ } : variant === "secondary" ? {
11908
+ background: `var(${CSS_VAR.surfaceMuted})`,
11909
+ color: `var(${CSS_VAR.text})`
11910
+ } : {
11911
+ background: "transparent",
11912
+ color: `var(${CSS_VAR.textMuted})`
11913
+ };
11914
+ return (
11915
+ // eslint-disable-next-line react/button-has-type
11916
+ /* @__PURE__ */ jsx(
11917
+ "button",
11918
+ {
11919
+ ref,
11920
+ type,
11921
+ ...rest,
11922
+ style: { ...BASE_STYLE, ...variantStyle, ...style }
11923
+ }
11924
+ )
11925
+ );
11926
+ }
11927
+ );
11928
+ Button.displayName = "Button";
11929
+ function FocusFrame({
11930
+ size = 96,
11931
+ color,
11932
+ strokeWidth,
11933
+ glow = false,
11934
+ className,
11935
+ style
11936
+ }) {
11937
+ const stroke = strokeWidth ?? autoStroke(size);
11938
+ const arm = size * 0.28;
11939
+ const offset = stroke / 2;
11940
+ const stops = [
11941
+ { x: offset, y: offset, dx: 1, dy: 1 },
11942
+ { x: size - offset, y: offset, dx: -1, dy: 1 },
11943
+ { x: offset, y: size - offset, dx: 1, dy: -1 },
11944
+ { x: size - offset, y: size - offset, dx: -1, dy: -1 }
11945
+ ];
11946
+ return /* @__PURE__ */ jsx(
11947
+ "svg",
11948
+ {
11949
+ width: size,
11950
+ height: size,
11951
+ viewBox: `0 0 ${size} ${size}`,
11952
+ xmlns: "http://www.w3.org/2000/svg",
11953
+ className,
11954
+ style: {
11955
+ color: color ?? `var(${CSS_VAR.action})`,
11956
+ filter: glow ? "drop-shadow(0 0 6px rgba(0,176,248,.5))" : void 0,
11957
+ ...style
11958
+ },
11959
+ "aria-hidden": "true",
11960
+ children: stops.map((c, i) => /* @__PURE__ */ jsx(
11961
+ "path",
11962
+ {
11963
+ d: `M ${c.x + c.dx * arm} ${c.y} L ${c.x} ${c.y} L ${c.x} ${c.y + c.dy * arm}`,
11964
+ fill: "none",
11965
+ stroke: "currentColor",
11966
+ strokeWidth: stroke,
11967
+ strokeLinecap: "round"
11968
+ },
11969
+ i
11970
+ ))
11971
+ }
11972
+ );
11973
+ }
11974
+ function autoStroke(size) {
11975
+ if (size <= 24) return 2;
11976
+ if (size <= 48) return 2.5;
11977
+ return 3.4;
11978
+ }
11979
+ function ChallengeRing({
11980
+ progress,
11981
+ size = 268,
11982
+ children,
11983
+ className,
11984
+ style
11985
+ }) {
11986
+ const clamped = Math.max(0, Math.min(1, Number.isFinite(progress) ? progress : 0));
11987
+ const stroke = 4.2;
11988
+ const radius = size / 2 - stroke;
11989
+ const circumference = 2 * Math.PI * radius;
11990
+ const dashOffset = circumference * (1 - clamped);
11991
+ const frameSize = size + 24;
11992
+ return /* @__PURE__ */ jsxs(
11993
+ "div",
11994
+ {
11995
+ className,
11996
+ style: {
11997
+ position: "relative",
11998
+ width: frameSize,
11999
+ height: frameSize,
12000
+ display: "inline-flex",
12001
+ alignItems: "center",
12002
+ justifyContent: "center",
12003
+ ...style
12004
+ },
12005
+ children: [
12006
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(FocusFrame, { size: frameSize, glow: true }) }),
12007
+ /* @__PURE__ */ jsx(
12008
+ "div",
12009
+ {
12010
+ style: {
12011
+ position: "relative",
12012
+ width: size,
12013
+ height: size,
12014
+ borderRadius: "50%",
12015
+ overflow: "hidden",
12016
+ background: "linear-gradient(160deg, #274B6E, #0F2438, #05080F)"
12017
+ },
12018
+ children
12019
+ }
12020
+ ),
12021
+ /* @__PURE__ */ jsxs(
12022
+ "svg",
12023
+ {
12024
+ width: size,
12025
+ height: size,
12026
+ viewBox: `0 0 ${size} ${size}`,
12027
+ style: {
12028
+ position: "absolute",
12029
+ top: (frameSize - size) / 2,
12030
+ left: (frameSize - size) / 2,
12031
+ transform: "rotate(-90deg)",
12032
+ pointerEvents: "none"
12033
+ },
12034
+ "aria-hidden": "true",
12035
+ children: [
12036
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: "nfid-challenge-arc", x1: "0%", y1: "0%", x2: "100%", y2: "100%", children: [
12037
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#00B0F8" }),
12038
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#0073F4" })
12039
+ ] }) }),
12040
+ /* @__PURE__ */ jsx(
12041
+ "circle",
12042
+ {
12043
+ cx: size / 2,
12044
+ cy: size / 2,
12045
+ r: radius,
12046
+ stroke: "rgba(255,255,255,0.18)",
12047
+ strokeWidth: stroke,
12048
+ fill: "none"
12049
+ }
12050
+ ),
12051
+ /* @__PURE__ */ jsx(
12052
+ "circle",
12053
+ {
12054
+ cx: size / 2,
12055
+ cy: size / 2,
12056
+ r: radius,
12057
+ stroke: `var(${CSS_VAR.action}, url(#nfid-challenge-arc))`,
12058
+ strokeWidth: stroke,
12059
+ strokeLinecap: "round",
12060
+ strokeDasharray: circumference,
12061
+ strokeDashoffset: dashOffset,
12062
+ fill: "none",
12063
+ style: { transition: "stroke-dashoffset 200ms ease-out" }
12064
+ }
12065
+ )
12066
+ ]
12067
+ }
12068
+ )
12069
+ ]
12070
+ }
12071
+ );
12072
+ }
12073
+ function ChallengeGlyph({
12074
+ gesture,
12075
+ size = 48,
12076
+ color,
12077
+ className,
12078
+ style
12079
+ }) {
12080
+ const stroke = 3.4;
12081
+ return /* @__PURE__ */ jsxs(
12082
+ "svg",
12083
+ {
12084
+ width: size,
12085
+ height: size,
12086
+ viewBox: "0 0 48 48",
12087
+ xmlns: "http://www.w3.org/2000/svg",
12088
+ className,
12089
+ style: { color: color ?? `var(${CSS_VAR.action})`, ...style },
12090
+ role: "img",
12091
+ "aria-label": LABELS[gesture],
12092
+ children: [
12093
+ gesture === "blink" ? /* @__PURE__ */ jsx(BlinkGlyph, { stroke }) : /* @__PURE__ */ jsx(FaceGlyph, { stroke }),
12094
+ gesture !== "blink" && gesture !== "center" && /* @__PURE__ */ jsx(Chevron, { gesture, stroke })
12095
+ ]
12096
+ }
12097
+ );
12098
+ }
12099
+ const LABELS = {
12100
+ center: "Centralize o rosto",
12101
+ turn_right: "Vire o rosto para a direita",
12102
+ turn_left: "Vire o rosto para a esquerda",
12103
+ look_up: "Olhe para cima",
12104
+ look_down: "Olhe para baixo",
12105
+ blink: "Pisque os olhos",
12106
+ move_closer: "Aproxime-se da câmera"
12107
+ };
12108
+ function FaceGlyph({ stroke }) {
12109
+ return /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", children: [
12110
+ /* @__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" }),
12111
+ /* @__PURE__ */ jsx("circle", { cx: "20", cy: "22", r: "1.2", fill: "currentColor", stroke: "none" }),
12112
+ /* @__PURE__ */ jsx("circle", { cx: "28", cy: "22", r: "1.2", fill: "currentColor", stroke: "none" }),
12113
+ /* @__PURE__ */ jsx("path", { d: "M21 28c1 1 2 1.5 3 1.5s2-.5 3-1.5" })
12114
+ ] });
12115
+ }
12116
+ function BlinkGlyph({ stroke }) {
12117
+ return /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", children: [
12118
+ /* @__PURE__ */ jsx("path", { d: "M8 24c4-4 8-6 16-6s12 2 16 6" }),
12119
+ /* @__PURE__ */ jsx("path", { d: "M12 26v3" }),
12120
+ /* @__PURE__ */ jsx("path", { d: "M18 27v3.5" }),
12121
+ /* @__PURE__ */ jsx("path", { d: "M24 27.5v4" }),
12122
+ /* @__PURE__ */ jsx("path", { d: "M30 27v3.5" }),
12123
+ /* @__PURE__ */ jsx("path", { d: "M36 26v3" })
12124
+ ] });
12125
+ }
12126
+ function Chevron({ gesture, stroke }) {
12127
+ const [start2, mid, end] = pointsFor(gesture);
12128
+ return /* @__PURE__ */ jsx(
12129
+ "path",
12130
+ {
12131
+ d: `M ${start2} L ${mid} L ${end}`,
12132
+ fill: "none",
12133
+ stroke: "currentColor",
12134
+ strokeWidth: stroke,
12135
+ strokeLinecap: "round",
12136
+ strokeLinejoin: "round"
12137
+ }
12138
+ );
12139
+ }
12140
+ function pointsFor(g) {
12141
+ switch (g) {
12142
+ case "turn_right":
12143
+ return ["40 18", "44 24", "40 30"];
12144
+ case "turn_left":
12145
+ return ["8 18", "4 24", "8 30"];
12146
+ case "look_up":
12147
+ return ["18 8", "24 4", "30 8"];
12148
+ case "look_down":
12149
+ return ["18 40", "24 44", "30 40"];
12150
+ case "move_closer":
12151
+ return ["18 40", "24 44", "30 40"];
12152
+ default:
12153
+ return ["24 24", "24 24", "24 24"];
12154
+ }
12155
+ }
12156
+ const STYLE_ID = "neofaceid-authorize-styles";
12157
+ const GESTURE_TO_GLYPH = {
12158
+ center: "center",
12159
+ turn_right: "turn_right",
12160
+ turn_left: "turn_left",
12161
+ look_up: "look_up",
12162
+ look_down: "look_down",
12163
+ blink: "blink",
12164
+ move_closer: "move_closer",
12165
+ smile: "blink",
12166
+ open_mouth: "blink"
12167
+ };
12168
+ const GESTURE_TO_INSTRUCTION = {
12169
+ center: "Centralize seu rosto no oval",
12170
+ turn_right: "Vire lentamente o rosto para a direita",
12171
+ turn_left: "Vire lentamente o rosto para a esquerda",
12172
+ look_up: "Olhe para cima",
12173
+ look_down: "Olhe para baixo",
12174
+ blink: "Pisque os olhos",
12175
+ move_closer: "Aproxime-se da câmera",
12176
+ smile: "Sorria para a câmera",
12177
+ open_mouth: "Abra a boca"
12178
+ };
12179
+ function formatCurrency(amount, locale) {
12180
+ try {
12181
+ return new Intl.NumberFormat(locale, {
12182
+ style: "currency",
12183
+ currency: "BRL"
12184
+ }).format(amount);
12185
+ } catch {
12186
+ return `R$ ${amount.toFixed(2)}`;
12187
+ }
12188
+ }
12189
+ function AuthorizeModalComponent({
12190
+ applicationToken,
12191
+ appName,
12192
+ amount,
12193
+ onChallengeStart,
12194
+ onChallengeComplete,
12195
+ onSuccess,
12196
+ onError,
12197
+ onCancel,
12198
+ onClose
12199
+ }) {
12200
+ const videoRef = useRef(null);
12201
+ const [progress, setProgress] = useState(0);
12202
+ const [currentGesture, setCurrentGesture] = useState(null);
12203
+ const [phase, setPhase] = useState("preparing");
12204
+ const cancelRef = useRef(false);
12205
+ const locale = getLocale();
12206
+ const headerAppName = appName ?? getAppName() ?? "sua aplicação";
12207
+ const headerAmount = typeof amount === "number" && Number.isFinite(amount) ? formatCurrency(amount, locale) : null;
12208
+ useEffect(() => {
12209
+ injectThemeStyles$1();
12210
+ injectScopedStyles$1(
12211
+ STYLE_ID,
12212
+ `
12213
+ .neofaceid-root .nfid-authorize-header { display: flex; flex-direction: column; gap: 4px; margin-bottom: 16px; }
12214
+ .neofaceid-root .nfid-authorize-appname { font-size: 15px; font-weight: 700; color: var(--nfid-text); line-height: 1.2; }
12215
+ .neofaceid-root .nfid-authorize-amount { font-size: 13px; color: var(--nfid-text-muted); }
12216
+ .neofaceid-root .nfid-authorize-brand { font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var(--nfid-text-subtle); }
12217
+ .neofaceid-root .nfid-authorize-video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
12218
+ .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; }
12219
+ .neofaceid-root .nfid-authorize-support { margin: 6px 0 0; font-size: 13px; color: var(--nfid-text-muted); text-align: center; }
12220
+ .neofaceid-root .nfid-authorize-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; align-items: center; }
12221
+ .neofaceid-root .nfid-authorize-footer { display: flex; justify-content: center; margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--nfid-line); }
12222
+ .neofaceid-root .nfid-authorize-seal { font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var(--nfid-text-subtle); }
12223
+ .neofaceid-root .nfid-authorize-close {
12224
+ position: absolute; top: 8px; right: 8px;
12225
+ min-width: 44px; min-height: 44px;
12226
+ background: transparent; border: none;
12227
+ display: inline-flex; align-items: center; justify-content: center;
12228
+ color: var(--nfid-text-muted); cursor: pointer; border-radius: 22px;
12229
+ }
12230
+ .neofaceid-root .nfid-authorize-close:hover { background: var(--nfid-surface-muted); }
12231
+ .neofaceid-root .nfid-authorize-close:focus-visible { outline: 2px solid var(--nfid-focus-ring); outline-offset: 2px; }
12232
+ @media (prefers-reduced-motion: reduce) {
12233
+ .neofaceid-root svg circle { transition: none !important; }
12234
+ }
12235
+ `
12236
+ );
12237
+ }, []);
12238
+ useEffect(() => {
12239
+ let stream = null;
12240
+ async function run() {
12241
+ try {
12242
+ stream = await navigator.mediaDevices.getUserMedia({
12243
+ video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } }
12244
+ });
12245
+ if (videoRef.current) {
12246
+ videoRef.current.srcObject = stream;
12247
+ videoRef.current.play().catch(() => {
12248
+ });
12249
+ }
12250
+ setPhase("challenging");
12251
+ const result = await runLivenessChallenge({
12252
+ applicationToken,
12253
+ purpose: "authorization",
12254
+ collectFramesForGesture: async (gesture, index, total) => {
12255
+ if (cancelRef.current) throw new NeoFaceError("Cancelado", ErrorType.UNKNOWN);
12256
+ setCurrentGesture(gesture);
12257
+ setProgress(0);
12258
+ if (onChallengeStart) onChallengeStart(gesture, index, total);
12259
+ await progressiveHold(setProgress);
12260
+ if (onChallengeComplete) onChallengeComplete(gesture, index, total);
12261
+ const frame = await captureFrame(videoRef.current);
12262
+ return frame ? [frame] : [];
12263
+ }
12264
+ });
12265
+ setPhase("done");
12266
+ if (onSuccess) onSuccess(result);
12267
+ onClose();
12268
+ } catch (err) {
12269
+ const nfe = err instanceof NeoFaceError ? err : new NeoFaceError(err instanceof Error ? err.message : "Falha", ErrorType.UNKNOWN);
12270
+ if (onError) onError(nfe);
12271
+ onClose();
12272
+ } finally {
12273
+ if (stream) stream.getTracks().forEach((t) => t.stop());
12274
+ }
12275
+ }
12276
+ run();
12277
+ return () => {
12278
+ cancelRef.current = true;
12279
+ if (stream) stream.getTracks().forEach((t) => t.stop());
12280
+ };
12281
+ }, []);
12282
+ const handleClose = () => {
12283
+ cancelRef.current = true;
12284
+ if (onCancel) onCancel();
12285
+ onClose();
12286
+ };
12287
+ const gestureKey = currentGesture ? GESTURE_TO_GLYPH[currentGesture.gesture_key] ?? "center" : "center";
12288
+ const instruction = currentGesture ? GESTURE_TO_INSTRUCTION[currentGesture.gesture_key] ?? "Siga a instrução" : "Preparando a câmera…";
12289
+ return /* @__PURE__ */ jsx(Sheet, { open: true, onDismiss: void 0, dismissOnOverlay: false, ariaLabel: "Autorização por biometria", children: /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
12290
+ /* @__PURE__ */ jsx(
12291
+ "button",
12292
+ {
12293
+ type: "button",
12294
+ className: "nfid-authorize-close",
12295
+ onClick: handleClose,
12296
+ "aria-label": "Cancelar autorização",
12297
+ 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" }) })
12298
+ }
12299
+ ),
12300
+ /* @__PURE__ */ jsxs("div", { className: "nfid-authorize-header", children: [
12301
+ /* @__PURE__ */ jsx("span", { className: "nfid-authorize-appname", children: headerAppName }),
12302
+ headerAmount && /* @__PURE__ */ jsxs("span", { className: "nfid-authorize-amount", children: [
12303
+ "Autorizando ",
12304
+ headerAmount
12305
+ ] }),
12306
+ /* @__PURE__ */ jsx("span", { className: "nfid-authorize-brand", children: "NeoFaceID" })
12307
+ ] }),
12308
+ /* @__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 }) }) }),
12309
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 12, marginTop: 16 }, children: [
12310
+ /* @__PURE__ */ jsx(ChallengeGlyph, { gesture: gestureKey, size: 40 }),
12311
+ /* @__PURE__ */ jsx(
12312
+ "p",
12313
+ {
12314
+ className: "nfid-authorize-instruction",
12315
+ "aria-live": "assertive",
12316
+ style: { margin: 0 },
12317
+ children: instruction
12318
+ }
12319
+ )
12320
+ ] }),
12321
+ phase === "preparing" && /* @__PURE__ */ jsx("p", { className: "nfid-authorize-support", children: "Ligando sua câmera…" }),
12322
+ /* @__PURE__ */ jsx("div", { className: "nfid-authorize-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleClose, children: "Não consigo fazer esse movimento" }) }),
12323
+ /* @__PURE__ */ jsx("div", { className: "nfid-authorize-footer", children: /* @__PURE__ */ jsx("span", { className: "nfid-authorize-seal", children: "Protegido por NeoFaceID" }) })
12324
+ ] }) });
12325
+ }
12326
+ async function progressiveHold(setter) {
12327
+ const total = 1500;
12328
+ const start2 = Date.now();
12329
+ return new Promise((resolve) => {
12330
+ const tick = () => {
12331
+ const elapsed = Date.now() - start2;
12332
+ const p = Math.min(1, elapsed / total);
12333
+ setter(p);
12334
+ if (elapsed >= total) resolve();
12335
+ else setTimeout(tick, 50);
12336
+ };
12337
+ tick();
12338
+ });
12339
+ }
12340
+ async function captureFrame(video) {
12341
+ if (!video || video.videoWidth === 0) return null;
12342
+ const canvas = document.createElement("canvas");
12343
+ canvas.width = video.videoWidth;
12344
+ canvas.height = video.videoHeight;
12345
+ const ctx = canvas.getContext("2d");
12346
+ if (!ctx) return null;
12347
+ ctx.drawImage(video, 0, 0);
12348
+ return canvas.toDataURL("image/jpeg", 0.85);
12349
+ }
12350
+ async function authorize(options) {
12351
+ const consentAccepted = await requestConsent({
12352
+ appName: options.appName,
12353
+ flow: "verification"
12354
+ });
12355
+ if (!consentAccepted) {
12356
+ if (options.onError) {
12357
+ options.onError(new NeoFaceError("Consentimento recusado", ErrorType.CONSENT_DENIED));
12358
+ }
12359
+ return;
12360
+ }
12361
+ const container = document.createElement("div");
12362
+ container.id = "neofaceid-authorize-container";
12363
+ document.body.appendChild(container);
12364
+ const root = createRoot(container);
12365
+ const cleanup = () => {
12366
+ root.unmount();
12367
+ if (document.body.contains(container)) document.body.removeChild(container);
12368
+ };
12369
+ root.render(/* @__PURE__ */ jsx(AuthorizeModalComponent, { ...options, onClose: cleanup }));
12370
+ }
11831
12371
  const DOCUMENT_INFO = {
11832
12372
  RG: {
11833
12373
  name: "Documento de Identidade (RG / CIN / CPF)",
@@ -12986,6 +13526,7 @@ export {
12986
13526
  NeoFaceID,
12987
13527
  RELEASE_DATE,
12988
13528
  VERSION,
13529
+ authorize,
12989
13530
  authorizeOperation,
12990
13531
  biometricLogin,
12991
13532
  biometricLoginWithFallback,