@neofaceid/web-sdk 1.35.1 → 1.36.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,9 +1,9 @@
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, forwardRef } from "react";
4
+ import React, { useRef, useState, useCallback, useEffect, useReducer, forwardRef } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
6
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
7
7
  import * as faceapi from "face-api.js";
8
8
  class CameraPermissionDenied extends Error {
9
9
  constructor(message) {
@@ -6219,6 +6219,201 @@ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty(
6219
6219
  validateToken,
6220
6220
  videoToBase64
6221
6221
  }, Symbol.toStringTag, { value: "Module" }));
6222
+ function measureFrameBrightness(video, options = {}) {
6223
+ if (!video || video.videoWidth === 0 || video.videoHeight === 0) return 0;
6224
+ const sample = options.sample ?? 64;
6225
+ const vw = video.videoWidth;
6226
+ const vh = video.videoHeight;
6227
+ const roi = options.roi ?? centerRoi(vw, vh, 0.4);
6228
+ const canvas = options.scratch ?? document.createElement("canvas");
6229
+ canvas.width = sample;
6230
+ canvas.height = sample;
6231
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
6232
+ if (!ctx) return 0;
6233
+ try {
6234
+ ctx.drawImage(
6235
+ video,
6236
+ roi.x,
6237
+ roi.y,
6238
+ roi.width,
6239
+ roi.height,
6240
+ 0,
6241
+ 0,
6242
+ sample,
6243
+ sample
6244
+ );
6245
+ const data = ctx.getImageData(0, 0, sample, sample).data;
6246
+ return computeLumaMean(data);
6247
+ } catch {
6248
+ return 0;
6249
+ }
6250
+ }
6251
+ function centerRoi(vw, vh, fraction) {
6252
+ const w = Math.round(vw * fraction);
6253
+ const h = Math.round(vh * fraction);
6254
+ return {
6255
+ x: Math.round((vw - w) / 2),
6256
+ y: Math.round((vh - h) / 2),
6257
+ width: w,
6258
+ height: h
6259
+ };
6260
+ }
6261
+ function computeLumaMean(rgba) {
6262
+ const len = rgba.length;
6263
+ if (len === 0) return 0;
6264
+ let sum = 0;
6265
+ let n = 0;
6266
+ for (let i = 0; i < len; i += 4) {
6267
+ const r = rgba[i];
6268
+ const g = rgba[i + 1];
6269
+ const b = rgba[i + 2];
6270
+ sum += 0.2126 * r + 0.7152 * g + 0.0722 * b;
6271
+ n += 1;
6272
+ }
6273
+ if (n === 0) return 0;
6274
+ return sum / (n * 255);
6275
+ }
6276
+ const DARK_THRESHOLD$1 = 0.235;
6277
+ const IMPROVEMENT_TARGET$1 = 0.12;
6278
+ const SAMPLE_INTERVAL_MS$1 = 600;
6279
+ const EVALUATION_DELAY_MS$1 = 600;
6280
+ const MIN_TOGGLE_MS$1 = 3e3;
6281
+ function useScreenFlash(videoRef, options = {}) {
6282
+ const enabled = options.enabled ?? true;
6283
+ const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD$1;
6284
+ const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET$1;
6285
+ const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS$1;
6286
+ const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS$1;
6287
+ const [flashOn, setFlashOn] = useState(false);
6288
+ const [lastBrightness, setLastBrightness] = useState(0);
6289
+ const darkStreakRef = useRef(0);
6290
+ const lastToggleAtRef = useRef(0);
6291
+ const preFlashBrightnessRef = useRef(null);
6292
+ const flashOnRef = useRef(false);
6293
+ const scratchRef = useRef(null);
6294
+ useEffect(() => {
6295
+ flashOnRef.current = flashOn;
6296
+ }, [flashOn]);
6297
+ useEffect(() => {
6298
+ if (!enabled) {
6299
+ setFlashOn(false);
6300
+ return () => {
6301
+ };
6302
+ }
6303
+ if (typeof document !== "undefined" && !scratchRef.current) {
6304
+ scratchRef.current = document.createElement("canvas");
6305
+ }
6306
+ let timer = null;
6307
+ let evalTimer = null;
6308
+ let disposed = false;
6309
+ const tick = () => {
6310
+ if (disposed) return;
6311
+ const b = measureFrameBrightness(videoRef.current, {
6312
+ scratch: scratchRef.current ?? void 0
6313
+ });
6314
+ setLastBrightness(b);
6315
+ const now = Date.now();
6316
+ if (!flashOnRef.current) {
6317
+ if (b > 0 && b < darkThreshold) {
6318
+ darkStreakRef.current += 1;
6319
+ } else {
6320
+ darkStreakRef.current = 0;
6321
+ }
6322
+ if (darkStreakRef.current >= 2 && now - lastToggleAtRef.current >= minToggleMs) {
6323
+ preFlashBrightnessRef.current = b;
6324
+ lastToggleAtRef.current = now;
6325
+ flashOnRef.current = true;
6326
+ setFlashOn(true);
6327
+ darkStreakRef.current = 0;
6328
+ evalTimer = setTimeout(() => {
6329
+ if (disposed) return;
6330
+ const after = measureFrameBrightness(videoRef.current, {
6331
+ scratch: scratchRef.current ?? void 0
6332
+ });
6333
+ const gained = after - (preFlashBrightnessRef.current ?? 0);
6334
+ if (gained < improvementTarget) {
6335
+ lastToggleAtRef.current = Date.now();
6336
+ flashOnRef.current = false;
6337
+ setFlashOn(false);
6338
+ }
6339
+ preFlashBrightnessRef.current = null;
6340
+ }, EVALUATION_DELAY_MS$1);
6341
+ }
6342
+ }
6343
+ };
6344
+ timer = setInterval(tick, sampleIntervalMs);
6345
+ tick();
6346
+ return () => {
6347
+ disposed = true;
6348
+ if (timer) clearInterval(timer);
6349
+ if (evalTimer) clearTimeout(evalTimer);
6350
+ };
6351
+ }, [enabled, darkThreshold, improvementTarget, sampleIntervalMs, minToggleMs, videoRef]);
6352
+ return { flashOn, lastBrightness };
6353
+ }
6354
+ function ScreenFlashOverlay({
6355
+ active,
6356
+ zIndex = 10001,
6357
+ onDismiss
6358
+ }) {
6359
+ if (!active) return null;
6360
+ const overlayStyle = {
6361
+ position: "fixed",
6362
+ inset: 0,
6363
+ background: "#FFFFFF",
6364
+ zIndex,
6365
+ pointerEvents: "none",
6366
+ animation: "nfid-flash-in 200ms ease-out"
6367
+ };
6368
+ const hintStyle = {
6369
+ position: "fixed",
6370
+ top: 12,
6371
+ left: "50%",
6372
+ transform: "translateX(-50%)",
6373
+ zIndex: zIndex + 1,
6374
+ background: "rgba(10,19,32,.72)",
6375
+ color: "#FFFFFF",
6376
+ fontSize: 12,
6377
+ fontWeight: 600,
6378
+ letterSpacing: 0.2,
6379
+ padding: "6px 12px",
6380
+ borderRadius: 999,
6381
+ pointerEvents: onDismiss ? "auto" : "none",
6382
+ display: "flex",
6383
+ alignItems: "center",
6384
+ gap: 8
6385
+ };
6386
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6387
+ /* @__PURE__ */ jsx("style", { children: `
6388
+ @keyframes nfid-flash-in { from { opacity: 0 } to { opacity: 1 } }
6389
+ @media (prefers-reduced-motion: reduce) {
6390
+ .nfid-screen-flash { animation: none !important; }
6391
+ }
6392
+ ` }),
6393
+ /* @__PURE__ */ jsx("div", { className: "nfid-screen-flash", style: overlayStyle, "aria-hidden": "true" }),
6394
+ /* @__PURE__ */ jsxs("div", { className: "nfid-screen-flash-hint", style: hintStyle, role: "status", children: [
6395
+ "Aumentando a luz da tela",
6396
+ onDismiss && /* @__PURE__ */ jsx(
6397
+ "button",
6398
+ {
6399
+ type: "button",
6400
+ onClick: onDismiss,
6401
+ "aria-label": "Desligar iluminação assistiva",
6402
+ style: {
6403
+ background: "transparent",
6404
+ border: "none",
6405
+ color: "#FFFFFF",
6406
+ cursor: "pointer",
6407
+ fontSize: 14,
6408
+ lineHeight: 1,
6409
+ padding: "0 4px"
6410
+ },
6411
+ children: "×"
6412
+ }
6413
+ )
6414
+ ] })
6415
+ ] });
6416
+ }
6222
6417
  const modalReducer$1 = (state, action) => {
6223
6418
  switch (action.type) {
6224
6419
  case "PERMISSION_GRANTED":
@@ -6358,7 +6553,8 @@ function FaceCaptureModal({
6358
6553
  accessToken,
6359
6554
  onClose,
6360
6555
  onSuccess,
6361
- onCannotGesture
6556
+ onCannotGesture,
6557
+ autoLighting = true
6362
6558
  }) {
6363
6559
  const videoRef = useRef(null);
6364
6560
  const canvasRef = useRef(null);
@@ -6412,6 +6608,8 @@ function FaceCaptureModal({
6412
6608
  useEffect(() => {
6413
6609
  injectThemeStyles();
6414
6610
  }, []);
6611
+ const flashEnabled = autoLighting && (state.status === "ready" || state.status === "capturing");
6612
+ const { flashOn } = useScreenFlash(videoRef, { enabled: flashEnabled });
6415
6613
  useEffect(() => {
6416
6614
  const loadModels = async () => {
6417
6615
  try {
@@ -6500,6 +6698,7 @@ function FaceCaptureModal({
6500
6698
  setLivenessAttempts(0);
6501
6699
  };
6502
6700
  return /* @__PURE__ */ jsxs("div", { className: "neofaceid-root", style: styles.overlay, children: [
6701
+ /* @__PURE__ */ jsx(ScreenFlashOverlay, { active: flashOn }),
6503
6702
  /* @__PURE__ */ jsx("style", { children: keyframes }),
6504
6703
  /* @__PURE__ */ jsx("style", { children: mediaStyles }),
6505
6704
  /* @__PURE__ */ jsxs("div", { style: styles.modal, className: "modal", children: [
@@ -6985,7 +7184,8 @@ function BiometricRegistrationModal({
6985
7184
  onSuccess,
6986
7185
  onPhotosCaptured,
6987
7186
  onError,
6988
- onCannotGesture
7187
+ onCannotGesture,
7188
+ autoLighting = true
6989
7189
  }) {
6990
7190
  const videoRef = useRef(null);
6991
7191
  const canvasRef = useRef(null);
@@ -7007,6 +7207,8 @@ function BiometricRegistrationModal({
7007
7207
  useEffect(() => {
7008
7208
  injectThemeStyles();
7009
7209
  }, []);
7210
+ const flashEnabled = autoLighting && (state.status === "liveness" || state.status === "camera-loading");
7211
+ const { flashOn } = useScreenFlash(videoRef, { enabled: flashEnabled });
7010
7212
  useEffect(() => {
7011
7213
  if (state.status === "liveness") {
7012
7214
  const newStep = state.step;
@@ -8384,6 +8586,7 @@ function BiometricRegistrationModal({
8384
8586
  }
8385
8587
  }
8386
8588
  ` }),
8589
+ /* @__PURE__ */ jsx(ScreenFlashOverlay, { active: flashOn }),
8387
8590
  /* @__PURE__ */ jsx("div", { className: "neofaceid-root modal-overlay", children: /* @__PURE__ */ jsxs("div", { className: "modal-content", children: [
8388
8591
  /* @__PURE__ */ jsx("button", { type: "button", className: "close-button", onClick: onClose, children: "×" }),
8389
8592
  state.status === "liveness" && renderLivenessDetection(),
@@ -8770,6 +8973,122 @@ const DEFAULT_CONSENT_INFO = {
8770
8973
  privacyPolicyUrl: DEFAULT_PRIVACY_URL,
8771
8974
  retentionDays: DEFAULT_RETENTION_DAYS
8772
8975
  };
8976
+ const DARK_THRESHOLD = 0.235;
8977
+ const IMPROVEMENT_TARGET = 0.12;
8978
+ const SAMPLE_INTERVAL_MS = 600;
8979
+ const EVALUATION_DELAY_MS = 600;
8980
+ const MIN_TOGGLE_MS = 3e3;
8981
+ const OVERLAY_ID_PREFIX = "nfid-flash-";
8982
+ function createScreenFlashController(getVideo, options = {}) {
8983
+ const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD;
8984
+ const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET;
8985
+ const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
8986
+ const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS;
8987
+ const zIndex = options.zIndex ?? 10001;
8988
+ const overlayId = `${OVERLAY_ID_PREFIX}${Math.random().toString(36).slice(2, 9)}`;
8989
+ let interval = null;
8990
+ let evalTimer = null;
8991
+ let flashOn = false;
8992
+ let lastBrightness = 0;
8993
+ let darkStreak = 0;
8994
+ let lastToggleAt = 0;
8995
+ let preFlashBrightness = null;
8996
+ let overlayEl = null;
8997
+ let scratchCanvas = null;
8998
+ const showOverlay = () => {
8999
+ if (overlayEl || typeof document === "undefined") return;
9000
+ overlayEl = document.createElement("div");
9001
+ overlayEl.id = overlayId;
9002
+ overlayEl.setAttribute("aria-hidden", "true");
9003
+ overlayEl.style.cssText = `
9004
+ position: fixed; inset: 0; background: #FFFFFF;
9005
+ z-index: ${zIndex}; pointer-events: none;
9006
+ opacity: 0; transition: opacity 200ms ease-out;
9007
+ `;
9008
+ document.body.appendChild(overlayEl);
9009
+ requestAnimationFrame(() => {
9010
+ if (overlayEl) overlayEl.style.opacity = "1";
9011
+ });
9012
+ };
9013
+ const hideOverlay = () => {
9014
+ if (!overlayEl) return;
9015
+ const el = overlayEl;
9016
+ overlayEl = null;
9017
+ el.style.opacity = "0";
9018
+ setTimeout(() => {
9019
+ if (el.parentNode) el.parentNode.removeChild(el);
9020
+ }, 220);
9021
+ };
9022
+ const setFlashOn = (next) => {
9023
+ if (flashOn === next) return;
9024
+ flashOn = next;
9025
+ if (next) showOverlay();
9026
+ else hideOverlay();
9027
+ if (options.onChange) options.onChange(next, lastBrightness);
9028
+ };
9029
+ const tick = () => {
9030
+ if (typeof document !== "undefined" && !scratchCanvas) {
9031
+ scratchCanvas = document.createElement("canvas");
9032
+ }
9033
+ const b = measureFrameBrightness(getVideo(), {
9034
+ scratch: scratchCanvas ?? void 0
9035
+ });
9036
+ lastBrightness = b;
9037
+ const now = Date.now();
9038
+ if (!flashOn) {
9039
+ if (b > 0 && b < darkThreshold) darkStreak += 1;
9040
+ else darkStreak = 0;
9041
+ if (darkStreak >= 2 && now - lastToggleAt >= minToggleMs) {
9042
+ preFlashBrightness = b;
9043
+ lastToggleAt = now;
9044
+ darkStreak = 0;
9045
+ setFlashOn(true);
9046
+ evalTimer = setTimeout(() => {
9047
+ const after = measureFrameBrightness(getVideo(), {
9048
+ scratch: scratchCanvas ?? void 0
9049
+ });
9050
+ const gained = after - (preFlashBrightness ?? 0);
9051
+ if (gained < improvementTarget) {
9052
+ lastToggleAt = Date.now();
9053
+ setFlashOn(false);
9054
+ }
9055
+ preFlashBrightness = null;
9056
+ }, EVALUATION_DELAY_MS);
9057
+ }
9058
+ }
9059
+ };
9060
+ return {
9061
+ start() {
9062
+ if (options.enabled === false) return;
9063
+ if (interval) return;
9064
+ tick();
9065
+ interval = setInterval(tick, sampleIntervalMs);
9066
+ },
9067
+ stop() {
9068
+ if (interval) {
9069
+ clearInterval(interval);
9070
+ interval = null;
9071
+ }
9072
+ if (evalTimer) {
9073
+ clearTimeout(evalTimer);
9074
+ evalTimer = null;
9075
+ }
9076
+ setFlashOn(false);
9077
+ darkStreak = 0;
9078
+ preFlashBrightness = null;
9079
+ },
9080
+ destroy() {
9081
+ this.stop();
9082
+ scratchCanvas = null;
9083
+ },
9084
+ get flashOn() {
9085
+ return flashOn;
9086
+ },
9087
+ get lastBrightness() {
9088
+ return lastBrightness;
9089
+ }
9090
+ };
9091
+ }
8773
9092
  class BiometricCaptureModal {
8774
9093
  constructor(options) {
8775
9094
  __publicField(this, "modal", null);
@@ -8779,6 +9098,7 @@ class BiometricCaptureModal {
8779
9098
  __publicField(this, "options");
8780
9099
  __publicField(this, "countdownInterval", null);
8781
9100
  __publicField(this, "isCapturing", false);
9101
+ __publicField(this, "flashController", null);
8782
9102
  this.options = options;
8783
9103
  }
8784
9104
  /**
@@ -8789,6 +9109,10 @@ class BiometricCaptureModal {
8789
9109
  await this.createModal();
8790
9110
  await this.initializeCamera();
8791
9111
  this.enableCaptureButton();
9112
+ if (this.options.autoLighting !== false) {
9113
+ this.flashController = createScreenFlashController(() => this.video);
9114
+ this.flashController.start();
9115
+ }
8792
9116
  } catch (error) {
8793
9117
  this.options.onError(
8794
9118
  new NeoFaceError(
@@ -8974,6 +9298,10 @@ class BiometricCaptureModal {
8974
9298
  * Limpa recursos (câmera, intervalos, etc.)
8975
9299
  */
8976
9300
  cleanup() {
9301
+ if (this.flashController) {
9302
+ this.flashController.destroy();
9303
+ this.flashController = null;
9304
+ }
8977
9305
  if (this.stream) {
8978
9306
  this.stream.getTracks().forEach((track) => track.stop());
8979
9307
  this.stream = null;
@@ -10866,7 +11194,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
10866
11194
  initializeBiometricDetection,
10867
11195
  isAdvancedDetectionAvailable
10868
11196
  }, Symbol.toStringTag, { value: "Module" }));
10869
- const VERSION = "1.35.1";
11197
+ const VERSION = "1.36.0";
10870
11198
  const RELEASE_DATE = "2026-09-02";
10871
11199
  let cachedSession = null;
10872
11200
  function getCachedCaptureSession() {
@@ -11132,9 +11460,12 @@ class OnboardingCaptureModal {
11132
11460
  __publicField(this, "subtitle");
11133
11461
  __publicField(this, "faceBlob", null);
11134
11462
  __publicField(this, "documentBlob", null);
11463
+ __publicField(this, "autoLighting");
11464
+ __publicField(this, "flashController", null);
11135
11465
  this.countdownSeconds = (options == null ? void 0 : options.countdown) ?? 3;
11136
11466
  this.title = options == null ? void 0 : options.title;
11137
11467
  this.subtitle = options == null ? void 0 : options.subtitle;
11468
+ this.autoLighting = (options == null ? void 0 : options.autoLighting) !== false;
11138
11469
  }
11139
11470
  /**
11140
11471
  * Abre o modal, inicia câmera e gerencia o fluxo de captura dos dois passos.
@@ -11248,6 +11579,10 @@ class OnboardingCaptureModal {
11248
11579
  }
11249
11580
  this.video.srcObject = this.stream;
11250
11581
  await this.video.play();
11582
+ if (this.autoLighting && !this.flashController) {
11583
+ this.flashController = createScreenFlashController(() => this.video);
11584
+ this.flashController.start();
11585
+ }
11251
11586
  }
11252
11587
  /**
11253
11588
  * Executa a contagem regressiva e atualiza textos para captura de rosto.
@@ -11332,6 +11667,10 @@ class OnboardingCaptureModal {
11332
11667
  * Fecha o modal e libera a câmera.
11333
11668
  */
11334
11669
  async close() {
11670
+ if (this.flashController) {
11671
+ this.flashController.destroy();
11672
+ this.flashController = null;
11673
+ }
11335
11674
  if (this.stream) {
11336
11675
  this.stream.getTracks().forEach((t) => t.stop());
11337
11676
  this.stream = null;
@@ -12465,201 +12804,6 @@ function AuthorizePermissionDenied({
12465
12804
  ] })
12466
12805
  ] });
12467
12806
  }
12468
- function measureFrameBrightness(video, options = {}) {
12469
- if (!video || video.videoWidth === 0 || video.videoHeight === 0) return 0;
12470
- const sample = options.sample ?? 64;
12471
- const vw = video.videoWidth;
12472
- const vh = video.videoHeight;
12473
- const roi = options.roi ?? centerRoi(vw, vh, 0.4);
12474
- const canvas = options.scratch ?? document.createElement("canvas");
12475
- canvas.width = sample;
12476
- canvas.height = sample;
12477
- const ctx = canvas.getContext("2d", { willReadFrequently: true });
12478
- if (!ctx) return 0;
12479
- try {
12480
- ctx.drawImage(
12481
- video,
12482
- roi.x,
12483
- roi.y,
12484
- roi.width,
12485
- roi.height,
12486
- 0,
12487
- 0,
12488
- sample,
12489
- sample
12490
- );
12491
- const data = ctx.getImageData(0, 0, sample, sample).data;
12492
- return computeLumaMean(data);
12493
- } catch {
12494
- return 0;
12495
- }
12496
- }
12497
- function centerRoi(vw, vh, fraction) {
12498
- const w = Math.round(vw * fraction);
12499
- const h = Math.round(vh * fraction);
12500
- return {
12501
- x: Math.round((vw - w) / 2),
12502
- y: Math.round((vh - h) / 2),
12503
- width: w,
12504
- height: h
12505
- };
12506
- }
12507
- function computeLumaMean(rgba) {
12508
- const len = rgba.length;
12509
- if (len === 0) return 0;
12510
- let sum = 0;
12511
- let n = 0;
12512
- for (let i = 0; i < len; i += 4) {
12513
- const r = rgba[i];
12514
- const g = rgba[i + 1];
12515
- const b = rgba[i + 2];
12516
- sum += 0.2126 * r + 0.7152 * g + 0.0722 * b;
12517
- n += 1;
12518
- }
12519
- if (n === 0) return 0;
12520
- return sum / (n * 255);
12521
- }
12522
- const DARK_THRESHOLD = 0.235;
12523
- const IMPROVEMENT_TARGET = 0.12;
12524
- const SAMPLE_INTERVAL_MS = 600;
12525
- const EVALUATION_DELAY_MS = 600;
12526
- const MIN_TOGGLE_MS = 3e3;
12527
- function useScreenFlash(videoRef, options = {}) {
12528
- const enabled = options.enabled ?? true;
12529
- const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD;
12530
- const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET;
12531
- const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
12532
- const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS;
12533
- const [flashOn, setFlashOn] = useState(false);
12534
- const [lastBrightness, setLastBrightness] = useState(0);
12535
- const darkStreakRef = useRef(0);
12536
- const lastToggleAtRef = useRef(0);
12537
- const preFlashBrightnessRef = useRef(null);
12538
- const flashOnRef = useRef(false);
12539
- const scratchRef = useRef(null);
12540
- useEffect(() => {
12541
- flashOnRef.current = flashOn;
12542
- }, [flashOn]);
12543
- useEffect(() => {
12544
- if (!enabled) {
12545
- setFlashOn(false);
12546
- return () => {
12547
- };
12548
- }
12549
- if (typeof document !== "undefined" && !scratchRef.current) {
12550
- scratchRef.current = document.createElement("canvas");
12551
- }
12552
- let timer = null;
12553
- let evalTimer = null;
12554
- let disposed = false;
12555
- const tick = () => {
12556
- if (disposed) return;
12557
- const b = measureFrameBrightness(videoRef.current, {
12558
- scratch: scratchRef.current ?? void 0
12559
- });
12560
- setLastBrightness(b);
12561
- const now = Date.now();
12562
- if (!flashOnRef.current) {
12563
- if (b > 0 && b < darkThreshold) {
12564
- darkStreakRef.current += 1;
12565
- } else {
12566
- darkStreakRef.current = 0;
12567
- }
12568
- if (darkStreakRef.current >= 2 && now - lastToggleAtRef.current >= minToggleMs) {
12569
- preFlashBrightnessRef.current = b;
12570
- lastToggleAtRef.current = now;
12571
- flashOnRef.current = true;
12572
- setFlashOn(true);
12573
- darkStreakRef.current = 0;
12574
- evalTimer = setTimeout(() => {
12575
- if (disposed) return;
12576
- const after = measureFrameBrightness(videoRef.current, {
12577
- scratch: scratchRef.current ?? void 0
12578
- });
12579
- const gained = after - (preFlashBrightnessRef.current ?? 0);
12580
- if (gained < improvementTarget) {
12581
- lastToggleAtRef.current = Date.now();
12582
- flashOnRef.current = false;
12583
- setFlashOn(false);
12584
- }
12585
- preFlashBrightnessRef.current = null;
12586
- }, EVALUATION_DELAY_MS);
12587
- }
12588
- }
12589
- };
12590
- timer = setInterval(tick, sampleIntervalMs);
12591
- tick();
12592
- return () => {
12593
- disposed = true;
12594
- if (timer) clearInterval(timer);
12595
- if (evalTimer) clearTimeout(evalTimer);
12596
- };
12597
- }, [enabled, darkThreshold, improvementTarget, sampleIntervalMs, minToggleMs, videoRef]);
12598
- return { flashOn, lastBrightness };
12599
- }
12600
- function ScreenFlashOverlay({
12601
- active,
12602
- zIndex = 10001,
12603
- onDismiss
12604
- }) {
12605
- if (!active) return null;
12606
- const overlayStyle = {
12607
- position: "fixed",
12608
- inset: 0,
12609
- background: "#FFFFFF",
12610
- zIndex,
12611
- pointerEvents: "none",
12612
- animation: "nfid-flash-in 200ms ease-out"
12613
- };
12614
- const hintStyle = {
12615
- position: "fixed",
12616
- top: 12,
12617
- left: "50%",
12618
- transform: "translateX(-50%)",
12619
- zIndex: zIndex + 1,
12620
- background: "rgba(10,19,32,.72)",
12621
- color: "#FFFFFF",
12622
- fontSize: 12,
12623
- fontWeight: 600,
12624
- letterSpacing: 0.2,
12625
- padding: "6px 12px",
12626
- borderRadius: 999,
12627
- pointerEvents: onDismiss ? "auto" : "none",
12628
- display: "flex",
12629
- alignItems: "center",
12630
- gap: 8
12631
- };
12632
- return /* @__PURE__ */ jsxs(Fragment, { children: [
12633
- /* @__PURE__ */ jsx("style", { children: `
12634
- @keyframes nfid-flash-in { from { opacity: 0 } to { opacity: 1 } }
12635
- @media (prefers-reduced-motion: reduce) {
12636
- .nfid-screen-flash { animation: none !important; }
12637
- }
12638
- ` }),
12639
- /* @__PURE__ */ jsx("div", { className: "nfid-screen-flash", style: overlayStyle, "aria-hidden": "true" }),
12640
- /* @__PURE__ */ jsxs("div", { className: "nfid-screen-flash-hint", style: hintStyle, role: "status", children: [
12641
- "Aumentando a luz da tela",
12642
- onDismiss && /* @__PURE__ */ jsx(
12643
- "button",
12644
- {
12645
- type: "button",
12646
- onClick: onDismiss,
12647
- "aria-label": "Desligar iluminação assistiva",
12648
- style: {
12649
- background: "transparent",
12650
- border: "none",
12651
- color: "#FFFFFF",
12652
- cursor: "pointer",
12653
- fontSize: 14,
12654
- lineHeight: 1,
12655
- padding: "0 4px"
12656
- },
12657
- children: "×"
12658
- }
12659
- )
12660
- ] })
12661
- ] });
12662
- }
12663
12807
  const STYLE_ID = "neofaceid-authorize-styles";
12664
12808
  const GESTURE_TO_GLYPH = {
12665
12809
  center: "center",
@@ -12993,6 +13137,7 @@ class DocumentCaptureModal {
12993
13137
  __publicField(this, "options");
12994
13138
  __publicField(this, "selectedDocument", null);
12995
13139
  __publicField(this, "currentStep", "select");
13140
+ __publicField(this, "flashController", null);
12996
13141
  __publicField(this, "frontBlob", null);
12997
13142
  __publicField(this, "backBlob", null);
12998
13143
  this.options = {
@@ -13384,6 +13529,7 @@ class DocumentCaptureModal {
13384
13529
  this.video.playsInline = true;
13385
13530
  this.video.srcObject = this.stream;
13386
13531
  await this.video.play();
13532
+ this.startScreenFlash();
13387
13533
  }
13388
13534
  } catch (error) {
13389
13535
  console.error("[DocumentCapture] Camera error:", error);
@@ -13396,6 +13542,7 @@ class DocumentCaptureModal {
13396
13542
  if (this.video) {
13397
13543
  this.video.srcObject = this.stream;
13398
13544
  await this.video.play();
13545
+ this.startScreenFlash();
13399
13546
  }
13400
13547
  } catch (fallbackError) {
13401
13548
  (_c = this.resolvePromise) == null ? void 0 : _c.call(this, {
@@ -13410,11 +13557,22 @@ class DocumentCaptureModal {
13410
13557
  }
13411
13558
  }
13412
13559
  stopCamera() {
13560
+ if (this.flashController) {
13561
+ this.flashController.destroy();
13562
+ this.flashController = null;
13563
+ }
13413
13564
  if (this.stream) {
13414
13565
  this.stream.getTracks().forEach((track) => track.stop());
13415
13566
  this.stream = null;
13416
13567
  }
13417
13568
  }
13569
+ // NEO-425 · US14.11.a — screen flash assistivo. Chamado após câmera acordar.
13570
+ startScreenFlash() {
13571
+ if (this.options.autoLighting === false) return;
13572
+ if (this.flashController) return;
13573
+ this.flashController = createScreenFlashController(() => this.video);
13574
+ this.flashController.start();
13575
+ }
13418
13576
  async handleCapture() {
13419
13577
  var _a2;
13420
13578
  if (!this.video || !this.canvas) return;