@neofaceid/web-sdk 1.33.0 → 1.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -91,6 +91,14 @@ export declare interface AuthorizeOptions {
91
91
  onSuccess?: (result: LivenessChallengeResult) => void;
92
92
  onError?: (error: NeoFaceError) => void;
93
93
  onCancel?: () => void;
94
+ /**
95
+ * NEO-420 · US14.9 — "screen flash" assistivo: quando o frame está muito
96
+ * escuro, inverte o fundo para branco alto brilho para usar a tela como
97
+ * fonte de luz. Só ajuda quando faz diferença: se não subir o brilho,
98
+ * reverte. Nunca usado como sinal de liveness (essa decisão é do servidor).
99
+ * Default: `true`.
100
+ */
101
+ autoLighting?: boolean;
94
102
  }
95
103
 
96
104
  export declare class BiometricCaptureModal {
@@ -1626,7 +1634,7 @@ export declare const validateToken: (applicationToken: string) => Promise<boolea
1626
1634
  * MINOR: Incrementado quando adicionamos funcionalidades mantendo compatibilidade
1627
1635
  * PATCH: Incrementado quando corrigimos bugs mantendo compatibilidade
1628
1636
  */
1629
- export declare const VERSION = "1.33.0";
1637
+ export declare const VERSION = "1.34.1";
1630
1638
 
1631
1639
  /**
1632
1640
  * Executa `fn(sessionId)`. Se o servidor devolver 410 (sessão consumida/expirada),
@@ -10866,7 +10866,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
10866
10866
  initializeBiometricDetection,
10867
10867
  isAdvancedDetectionAvailable
10868
10868
  }, Symbol.toStringTag, { value: "Module" }));
10869
- const VERSION = "1.33.0";
10869
+ const VERSION = "1.34.1";
10870
10870
  const RELEASE_DATE = "2026-09-02";
10871
10871
  let cachedSession = null;
10872
10872
  function getCachedCaptureSession() {
@@ -12459,6 +12459,201 @@ function AuthorizePermissionDenied({
12459
12459
  ] })
12460
12460
  ] });
12461
12461
  }
12462
+ function measureFrameBrightness(video, options = {}) {
12463
+ if (!video || video.videoWidth === 0 || video.videoHeight === 0) return 0;
12464
+ const sample = options.sample ?? 64;
12465
+ const vw = video.videoWidth;
12466
+ const vh = video.videoHeight;
12467
+ const roi = options.roi ?? centerRoi(vw, vh, 0.4);
12468
+ const canvas = options.scratch ?? document.createElement("canvas");
12469
+ canvas.width = sample;
12470
+ canvas.height = sample;
12471
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
12472
+ if (!ctx) return 0;
12473
+ try {
12474
+ ctx.drawImage(
12475
+ video,
12476
+ roi.x,
12477
+ roi.y,
12478
+ roi.width,
12479
+ roi.height,
12480
+ 0,
12481
+ 0,
12482
+ sample,
12483
+ sample
12484
+ );
12485
+ const data = ctx.getImageData(0, 0, sample, sample).data;
12486
+ return computeLumaMean(data);
12487
+ } catch {
12488
+ return 0;
12489
+ }
12490
+ }
12491
+ function centerRoi(vw, vh, fraction) {
12492
+ const w = Math.round(vw * fraction);
12493
+ const h = Math.round(vh * fraction);
12494
+ return {
12495
+ x: Math.round((vw - w) / 2),
12496
+ y: Math.round((vh - h) / 2),
12497
+ width: w,
12498
+ height: h
12499
+ };
12500
+ }
12501
+ function computeLumaMean(rgba) {
12502
+ const len = rgba.length;
12503
+ if (len === 0) return 0;
12504
+ let sum = 0;
12505
+ let n = 0;
12506
+ for (let i = 0; i < len; i += 4) {
12507
+ const r = rgba[i];
12508
+ const g = rgba[i + 1];
12509
+ const b = rgba[i + 2];
12510
+ sum += 0.2126 * r + 0.7152 * g + 0.0722 * b;
12511
+ n += 1;
12512
+ }
12513
+ if (n === 0) return 0;
12514
+ return sum / (n * 255);
12515
+ }
12516
+ const DARK_THRESHOLD = 0.235;
12517
+ const IMPROVEMENT_TARGET = 0.12;
12518
+ const SAMPLE_INTERVAL_MS = 600;
12519
+ const EVALUATION_DELAY_MS = 600;
12520
+ const MIN_TOGGLE_MS = 3e3;
12521
+ function useScreenFlash(videoRef, options = {}) {
12522
+ const enabled = options.enabled ?? true;
12523
+ const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD;
12524
+ const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET;
12525
+ const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
12526
+ const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS;
12527
+ const [flashOn, setFlashOn] = useState(false);
12528
+ const [lastBrightness, setLastBrightness] = useState(0);
12529
+ const darkStreakRef = useRef(0);
12530
+ const lastToggleAtRef = useRef(0);
12531
+ const preFlashBrightnessRef = useRef(null);
12532
+ const flashOnRef = useRef(false);
12533
+ const scratchRef = useRef(null);
12534
+ useEffect(() => {
12535
+ flashOnRef.current = flashOn;
12536
+ }, [flashOn]);
12537
+ useEffect(() => {
12538
+ if (!enabled) {
12539
+ setFlashOn(false);
12540
+ return () => {
12541
+ };
12542
+ }
12543
+ if (typeof document !== "undefined" && !scratchRef.current) {
12544
+ scratchRef.current = document.createElement("canvas");
12545
+ }
12546
+ let timer = null;
12547
+ let evalTimer = null;
12548
+ let disposed = false;
12549
+ const tick = () => {
12550
+ if (disposed) return;
12551
+ const b = measureFrameBrightness(videoRef.current, {
12552
+ scratch: scratchRef.current ?? void 0
12553
+ });
12554
+ setLastBrightness(b);
12555
+ const now = Date.now();
12556
+ if (!flashOnRef.current) {
12557
+ if (b > 0 && b < darkThreshold) {
12558
+ darkStreakRef.current += 1;
12559
+ } else {
12560
+ darkStreakRef.current = 0;
12561
+ }
12562
+ if (darkStreakRef.current >= 2 && now - lastToggleAtRef.current >= minToggleMs) {
12563
+ preFlashBrightnessRef.current = b;
12564
+ lastToggleAtRef.current = now;
12565
+ flashOnRef.current = true;
12566
+ setFlashOn(true);
12567
+ darkStreakRef.current = 0;
12568
+ evalTimer = setTimeout(() => {
12569
+ if (disposed) return;
12570
+ const after = measureFrameBrightness(videoRef.current, {
12571
+ scratch: scratchRef.current ?? void 0
12572
+ });
12573
+ const gained = after - (preFlashBrightnessRef.current ?? 0);
12574
+ if (gained < improvementTarget) {
12575
+ lastToggleAtRef.current = Date.now();
12576
+ flashOnRef.current = false;
12577
+ setFlashOn(false);
12578
+ }
12579
+ preFlashBrightnessRef.current = null;
12580
+ }, EVALUATION_DELAY_MS);
12581
+ }
12582
+ }
12583
+ };
12584
+ timer = setInterval(tick, sampleIntervalMs);
12585
+ tick();
12586
+ return () => {
12587
+ disposed = true;
12588
+ if (timer) clearInterval(timer);
12589
+ if (evalTimer) clearTimeout(evalTimer);
12590
+ };
12591
+ }, [enabled, darkThreshold, improvementTarget, sampleIntervalMs, minToggleMs, videoRef]);
12592
+ return { flashOn, lastBrightness };
12593
+ }
12594
+ function ScreenFlashOverlay({
12595
+ active,
12596
+ zIndex = 10001,
12597
+ onDismiss
12598
+ }) {
12599
+ if (!active) return null;
12600
+ const overlayStyle = {
12601
+ position: "fixed",
12602
+ inset: 0,
12603
+ background: "#FFFFFF",
12604
+ zIndex,
12605
+ pointerEvents: "none",
12606
+ animation: "nfid-flash-in 200ms ease-out"
12607
+ };
12608
+ const hintStyle = {
12609
+ position: "fixed",
12610
+ top: 12,
12611
+ left: "50%",
12612
+ transform: "translateX(-50%)",
12613
+ zIndex: zIndex + 1,
12614
+ background: "rgba(10,19,32,.72)",
12615
+ color: "#FFFFFF",
12616
+ fontSize: 12,
12617
+ fontWeight: 600,
12618
+ letterSpacing: 0.2,
12619
+ padding: "6px 12px",
12620
+ borderRadius: 999,
12621
+ pointerEvents: onDismiss ? "auto" : "none",
12622
+ display: "flex",
12623
+ alignItems: "center",
12624
+ gap: 8
12625
+ };
12626
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
12627
+ /* @__PURE__ */ jsx("style", { children: `
12628
+ @keyframes nfid-flash-in { from { opacity: 0 } to { opacity: 1 } }
12629
+ @media (prefers-reduced-motion: reduce) {
12630
+ .nfid-screen-flash { animation: none !important; }
12631
+ }
12632
+ ` }),
12633
+ /* @__PURE__ */ jsx("div", { className: "nfid-screen-flash", style: overlayStyle, "aria-hidden": "true" }),
12634
+ /* @__PURE__ */ jsxs("div", { className: "nfid-screen-flash-hint", style: hintStyle, role: "status", children: [
12635
+ "Aumentando a luz da tela",
12636
+ onDismiss && /* @__PURE__ */ jsx(
12637
+ "button",
12638
+ {
12639
+ type: "button",
12640
+ onClick: onDismiss,
12641
+ "aria-label": "Desligar iluminação assistiva",
12642
+ style: {
12643
+ background: "transparent",
12644
+ border: "none",
12645
+ color: "#FFFFFF",
12646
+ cursor: "pointer",
12647
+ fontSize: 14,
12648
+ lineHeight: 1,
12649
+ padding: "0 4px"
12650
+ },
12651
+ children: "×"
12652
+ }
12653
+ )
12654
+ ] })
12655
+ ] });
12656
+ }
12462
12657
  const STYLE_ID = "neofaceid-authorize-styles";
12463
12658
  const GESTURE_TO_GLYPH = {
12464
12659
  center: "center",
@@ -12502,6 +12697,7 @@ function AuthorizeModalComponent({
12502
12697
  onSuccess,
12503
12698
  onError,
12504
12699
  onCancel,
12700
+ autoLighting = true,
12505
12701
  onClose
12506
12702
  }) {
12507
12703
  const videoRef = useRef(null);
@@ -12513,6 +12709,10 @@ function AuthorizeModalComponent({
12513
12709
  const [failureAt, setFailureAt] = useState(void 0);
12514
12710
  const [runCounter, setRunCounter] = useState(0);
12515
12711
  const cancelRef = useRef(false);
12712
+ const [flashDismissed, setFlashDismissed] = useState(false);
12713
+ const { flashOn } = useScreenFlash(videoRef, {
12714
+ enabled: autoLighting && !flashDismissed && phase === "challenging"
12715
+ });
12516
12716
  const locale = getLocale();
12517
12717
  const headerAppName = appName ?? getAppName() ?? "sua aplicação";
12518
12718
  const headerAmount = typeof amount === "number" && Number.isFinite(amount) ? formatCurrency(amount, locale) : null;
@@ -12664,42 +12864,51 @@ function AuthorizeModalComponent({
12664
12864
  }
12665
12865
  ) });
12666
12866
  }
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: [
12867
+ return /* @__PURE__ */ jsxs(Sheet, { open: true, onDismiss: void 0, dismissOnOverlay: false, ariaLabel: "Autorização por biometria", children: [
12668
12868
  /* @__PURE__ */ jsx(
12669
- "button",
12869
+ ScreenFlashOverlay,
12670
12870
  {
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" }) })
12871
+ active: flashOn,
12872
+ onDismiss: () => setFlashDismissed(true)
12676
12873
  }
12677
12874
  ),
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 }),
12875
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
12689
12876
  /* @__PURE__ */ jsx(
12690
- "p",
12877
+ "button",
12691
12878
  {
12692
- className: "nfid-authorize-instruction",
12693
- "aria-live": "assertive",
12694
- style: { margin: 0 },
12695
- children: instruction
12879
+ type: "button",
12880
+ className: "nfid-authorize-close",
12881
+ onClick: handleClose,
12882
+ "aria-label": "Cancelar autorização",
12883
+ 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" }) })
12696
12884
  }
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
- ] }) });
12885
+ ),
12886
+ /* @__PURE__ */ jsxs("div", { className: "nfid-authorize-header", children: [
12887
+ /* @__PURE__ */ jsx("span", { className: "nfid-authorize-appname", children: headerAppName }),
12888
+ headerAmount && /* @__PURE__ */ jsxs("span", { className: "nfid-authorize-amount", children: [
12889
+ "Autorizando ",
12890
+ headerAmount
12891
+ ] }),
12892
+ /* @__PURE__ */ jsx("span", { className: "nfid-authorize-brand", children: "NeoFaceID" })
12893
+ ] }),
12894
+ /* @__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 }) }) }),
12895
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 12, marginTop: 16 }, children: [
12896
+ /* @__PURE__ */ jsx(ChallengeGlyph, { gesture: gestureKey, size: 40 }),
12897
+ /* @__PURE__ */ jsx(
12898
+ "p",
12899
+ {
12900
+ className: "nfid-authorize-instruction",
12901
+ "aria-live": "assertive",
12902
+ style: { margin: 0 },
12903
+ children: instruction
12904
+ }
12905
+ )
12906
+ ] }),
12907
+ phase === "preparing" && /* @__PURE__ */ jsx("p", { className: "nfid-authorize-support", children: "Ligando sua câmera…" }),
12908
+ /* @__PURE__ */ jsx("div", { className: "nfid-authorize-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleClose, children: "Não consigo fazer esse movimento" }) }),
12909
+ /* @__PURE__ */ jsx("div", { className: "nfid-authorize-footer", children: /* @__PURE__ */ jsx("span", { className: "nfid-authorize-seal", children: "Protegido por NeoFaceID" }) })
12910
+ ] })
12911
+ ] });
12703
12912
  }
12704
12913
  async function progressiveHold(setter) {
12705
12914
  const total = 1500;