@neofaceid/web-sdk 1.35.1 → 1.36.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 +20 -4
- package/dist/neoface-id-sdk.es.js +359 -218
- package/dist/neoface-id-sdk.umd.js +1 -1
- package/package.json +1 -1
|
@@ -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,
|
|
4
|
+
import React, { useRef, useState, useCallback, useEffect, useReducer, forwardRef } from "react";
|
|
5
5
|
import { createRoot } from "react-dom/client";
|
|
6
|
-
import { jsxs,
|
|
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,16 +6553,15 @@ 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);
|
|
6365
6561
|
const streamRef = useRef(null);
|
|
6366
6562
|
const faceDetectionTimeoutRef = useRef(null);
|
|
6367
|
-
const livenessCheckRef = useRef(null);
|
|
6368
6563
|
const [state, dispatch] = useReducer(modalReducer$1, { status: "requestingPermission" });
|
|
6369
6564
|
const [faceDetected, setFaceDetected] = useState(false);
|
|
6370
|
-
const [livenessAttempts, setLivenessAttempts] = useState(0);
|
|
6371
6565
|
const { startCamera, stopCamera } = useCamera();
|
|
6372
6566
|
const isFaceCentered = (detection) => {
|
|
6373
6567
|
if (!videoRef.current) return false;
|
|
@@ -6379,14 +6573,6 @@ function FaceCaptureModal({
|
|
|
6379
6573
|
const centerHeight = videoHeight * 0.4;
|
|
6380
6574
|
return box.x + box.width / 2 > centerX - centerWidth / 2 && box.x + box.width / 2 < centerX + centerWidth / 2 && box.y + box.height / 2 > centerY - centerHeight / 2 && box.y + box.height / 2 < centerY + centerHeight / 2;
|
|
6381
6575
|
};
|
|
6382
|
-
const startLivenessCheck = () => {
|
|
6383
|
-
livenessCheckRef.current = window.setTimeout(() => {
|
|
6384
|
-
if (state.status === "ready") {
|
|
6385
|
-
setLivenessAttempts((prev) => prev + 1);
|
|
6386
|
-
dispatch({ type: "START_CAPTURE" });
|
|
6387
|
-
}
|
|
6388
|
-
}, 3e3);
|
|
6389
|
-
};
|
|
6390
6576
|
const startFaceDetection = () => {
|
|
6391
6577
|
if (!videoRef.current) return;
|
|
6392
6578
|
const detectFaces = async () => {
|
|
@@ -6399,9 +6585,6 @@ function FaceCaptureModal({
|
|
|
6399
6585
|
const isOneFace = detections.length === 1;
|
|
6400
6586
|
const isCentered = isOneFace && isFaceCentered(detections[0]);
|
|
6401
6587
|
setFaceDetected(isOneFace && isCentered);
|
|
6402
|
-
if (isOneFace && isCentered && livenessAttempts === 0) {
|
|
6403
|
-
startLivenessCheck();
|
|
6404
|
-
}
|
|
6405
6588
|
} catch (error) {
|
|
6406
6589
|
console.error("Face detection error:", error);
|
|
6407
6590
|
}
|
|
@@ -6412,6 +6595,8 @@ function FaceCaptureModal({
|
|
|
6412
6595
|
useEffect(() => {
|
|
6413
6596
|
injectThemeStyles();
|
|
6414
6597
|
}, []);
|
|
6598
|
+
const flashEnabled = autoLighting && (state.status === "ready" || state.status === "capturing");
|
|
6599
|
+
const { flashOn } = useScreenFlash(videoRef, { enabled: flashEnabled });
|
|
6415
6600
|
useEffect(() => {
|
|
6416
6601
|
const loadModels = async () => {
|
|
6417
6602
|
try {
|
|
@@ -6428,9 +6613,6 @@ function FaceCaptureModal({
|
|
|
6428
6613
|
if (faceDetectionTimeoutRef.current) {
|
|
6429
6614
|
window.clearTimeout(faceDetectionTimeoutRef.current);
|
|
6430
6615
|
}
|
|
6431
|
-
if (livenessCheckRef.current) {
|
|
6432
|
-
window.clearInterval(livenessCheckRef.current);
|
|
6433
|
-
}
|
|
6434
6616
|
};
|
|
6435
6617
|
}, []);
|
|
6436
6618
|
useEffect(() => {
|
|
@@ -6497,9 +6679,9 @@ function FaceCaptureModal({
|
|
|
6497
6679
|
};
|
|
6498
6680
|
const handleRetry = () => {
|
|
6499
6681
|
dispatch({ type: "RESET" });
|
|
6500
|
-
setLivenessAttempts(0);
|
|
6501
6682
|
};
|
|
6502
6683
|
return /* @__PURE__ */ jsxs("div", { className: "neofaceid-root", style: styles.overlay, children: [
|
|
6684
|
+
/* @__PURE__ */ jsx(ScreenFlashOverlay, { active: flashOn }),
|
|
6503
6685
|
/* @__PURE__ */ jsx("style", { children: keyframes }),
|
|
6504
6686
|
/* @__PURE__ */ jsx("style", { children: mediaStyles }),
|
|
6505
6687
|
/* @__PURE__ */ jsxs("div", { style: styles.modal, className: "modal", children: [
|
|
@@ -6985,7 +7167,8 @@ function BiometricRegistrationModal({
|
|
|
6985
7167
|
onSuccess,
|
|
6986
7168
|
onPhotosCaptured,
|
|
6987
7169
|
onError,
|
|
6988
|
-
onCannotGesture
|
|
7170
|
+
onCannotGesture,
|
|
7171
|
+
autoLighting = true
|
|
6989
7172
|
}) {
|
|
6990
7173
|
const videoRef = useRef(null);
|
|
6991
7174
|
const canvasRef = useRef(null);
|
|
@@ -7007,6 +7190,8 @@ function BiometricRegistrationModal({
|
|
|
7007
7190
|
useEffect(() => {
|
|
7008
7191
|
injectThemeStyles();
|
|
7009
7192
|
}, []);
|
|
7193
|
+
const flashEnabled = autoLighting && (state.status === "liveness" || state.status === "camera-loading");
|
|
7194
|
+
const { flashOn } = useScreenFlash(videoRef, { enabled: flashEnabled });
|
|
7010
7195
|
useEffect(() => {
|
|
7011
7196
|
if (state.status === "liveness") {
|
|
7012
7197
|
const newStep = state.step;
|
|
@@ -8384,6 +8569,7 @@ function BiometricRegistrationModal({
|
|
|
8384
8569
|
}
|
|
8385
8570
|
}
|
|
8386
8571
|
` }),
|
|
8572
|
+
/* @__PURE__ */ jsx(ScreenFlashOverlay, { active: flashOn }),
|
|
8387
8573
|
/* @__PURE__ */ jsx("div", { className: "neofaceid-root modal-overlay", children: /* @__PURE__ */ jsxs("div", { className: "modal-content", children: [
|
|
8388
8574
|
/* @__PURE__ */ jsx("button", { type: "button", className: "close-button", onClick: onClose, children: "×" }),
|
|
8389
8575
|
state.status === "liveness" && renderLivenessDetection(),
|
|
@@ -8770,6 +8956,122 @@ const DEFAULT_CONSENT_INFO = {
|
|
|
8770
8956
|
privacyPolicyUrl: DEFAULT_PRIVACY_URL,
|
|
8771
8957
|
retentionDays: DEFAULT_RETENTION_DAYS
|
|
8772
8958
|
};
|
|
8959
|
+
const DARK_THRESHOLD = 0.235;
|
|
8960
|
+
const IMPROVEMENT_TARGET = 0.12;
|
|
8961
|
+
const SAMPLE_INTERVAL_MS = 600;
|
|
8962
|
+
const EVALUATION_DELAY_MS = 600;
|
|
8963
|
+
const MIN_TOGGLE_MS = 3e3;
|
|
8964
|
+
const OVERLAY_ID_PREFIX = "nfid-flash-";
|
|
8965
|
+
function createScreenFlashController(getVideo, options = {}) {
|
|
8966
|
+
const darkThreshold = options.darkThreshold ?? DARK_THRESHOLD;
|
|
8967
|
+
const improvementTarget = options.improvementTarget ?? IMPROVEMENT_TARGET;
|
|
8968
|
+
const sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
|
|
8969
|
+
const minToggleMs = options.minToggleMs ?? MIN_TOGGLE_MS;
|
|
8970
|
+
const zIndex = options.zIndex ?? 10001;
|
|
8971
|
+
const overlayId = `${OVERLAY_ID_PREFIX}${Math.random().toString(36).slice(2, 9)}`;
|
|
8972
|
+
let interval = null;
|
|
8973
|
+
let evalTimer = null;
|
|
8974
|
+
let flashOn = false;
|
|
8975
|
+
let lastBrightness = 0;
|
|
8976
|
+
let darkStreak = 0;
|
|
8977
|
+
let lastToggleAt = 0;
|
|
8978
|
+
let preFlashBrightness = null;
|
|
8979
|
+
let overlayEl = null;
|
|
8980
|
+
let scratchCanvas = null;
|
|
8981
|
+
const showOverlay = () => {
|
|
8982
|
+
if (overlayEl || typeof document === "undefined") return;
|
|
8983
|
+
overlayEl = document.createElement("div");
|
|
8984
|
+
overlayEl.id = overlayId;
|
|
8985
|
+
overlayEl.setAttribute("aria-hidden", "true");
|
|
8986
|
+
overlayEl.style.cssText = `
|
|
8987
|
+
position: fixed; inset: 0; background: #FFFFFF;
|
|
8988
|
+
z-index: ${zIndex}; pointer-events: none;
|
|
8989
|
+
opacity: 0; transition: opacity 200ms ease-out;
|
|
8990
|
+
`;
|
|
8991
|
+
document.body.appendChild(overlayEl);
|
|
8992
|
+
requestAnimationFrame(() => {
|
|
8993
|
+
if (overlayEl) overlayEl.style.opacity = "1";
|
|
8994
|
+
});
|
|
8995
|
+
};
|
|
8996
|
+
const hideOverlay = () => {
|
|
8997
|
+
if (!overlayEl) return;
|
|
8998
|
+
const el = overlayEl;
|
|
8999
|
+
overlayEl = null;
|
|
9000
|
+
el.style.opacity = "0";
|
|
9001
|
+
setTimeout(() => {
|
|
9002
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
9003
|
+
}, 220);
|
|
9004
|
+
};
|
|
9005
|
+
const setFlashOn = (next) => {
|
|
9006
|
+
if (flashOn === next) return;
|
|
9007
|
+
flashOn = next;
|
|
9008
|
+
if (next) showOverlay();
|
|
9009
|
+
else hideOverlay();
|
|
9010
|
+
if (options.onChange) options.onChange(next, lastBrightness);
|
|
9011
|
+
};
|
|
9012
|
+
const tick = () => {
|
|
9013
|
+
if (typeof document !== "undefined" && !scratchCanvas) {
|
|
9014
|
+
scratchCanvas = document.createElement("canvas");
|
|
9015
|
+
}
|
|
9016
|
+
const b = measureFrameBrightness(getVideo(), {
|
|
9017
|
+
scratch: scratchCanvas ?? void 0
|
|
9018
|
+
});
|
|
9019
|
+
lastBrightness = b;
|
|
9020
|
+
const now = Date.now();
|
|
9021
|
+
if (!flashOn) {
|
|
9022
|
+
if (b > 0 && b < darkThreshold) darkStreak += 1;
|
|
9023
|
+
else darkStreak = 0;
|
|
9024
|
+
if (darkStreak >= 2 && now - lastToggleAt >= minToggleMs) {
|
|
9025
|
+
preFlashBrightness = b;
|
|
9026
|
+
lastToggleAt = now;
|
|
9027
|
+
darkStreak = 0;
|
|
9028
|
+
setFlashOn(true);
|
|
9029
|
+
evalTimer = setTimeout(() => {
|
|
9030
|
+
const after = measureFrameBrightness(getVideo(), {
|
|
9031
|
+
scratch: scratchCanvas ?? void 0
|
|
9032
|
+
});
|
|
9033
|
+
const gained = after - (preFlashBrightness ?? 0);
|
|
9034
|
+
if (gained < improvementTarget) {
|
|
9035
|
+
lastToggleAt = Date.now();
|
|
9036
|
+
setFlashOn(false);
|
|
9037
|
+
}
|
|
9038
|
+
preFlashBrightness = null;
|
|
9039
|
+
}, EVALUATION_DELAY_MS);
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9042
|
+
};
|
|
9043
|
+
return {
|
|
9044
|
+
start() {
|
|
9045
|
+
if (options.enabled === false) return;
|
|
9046
|
+
if (interval) return;
|
|
9047
|
+
tick();
|
|
9048
|
+
interval = setInterval(tick, sampleIntervalMs);
|
|
9049
|
+
},
|
|
9050
|
+
stop() {
|
|
9051
|
+
if (interval) {
|
|
9052
|
+
clearInterval(interval);
|
|
9053
|
+
interval = null;
|
|
9054
|
+
}
|
|
9055
|
+
if (evalTimer) {
|
|
9056
|
+
clearTimeout(evalTimer);
|
|
9057
|
+
evalTimer = null;
|
|
9058
|
+
}
|
|
9059
|
+
setFlashOn(false);
|
|
9060
|
+
darkStreak = 0;
|
|
9061
|
+
preFlashBrightness = null;
|
|
9062
|
+
},
|
|
9063
|
+
destroy() {
|
|
9064
|
+
this.stop();
|
|
9065
|
+
scratchCanvas = null;
|
|
9066
|
+
},
|
|
9067
|
+
get flashOn() {
|
|
9068
|
+
return flashOn;
|
|
9069
|
+
},
|
|
9070
|
+
get lastBrightness() {
|
|
9071
|
+
return lastBrightness;
|
|
9072
|
+
}
|
|
9073
|
+
};
|
|
9074
|
+
}
|
|
8773
9075
|
class BiometricCaptureModal {
|
|
8774
9076
|
constructor(options) {
|
|
8775
9077
|
__publicField(this, "modal", null);
|
|
@@ -8779,6 +9081,7 @@ class BiometricCaptureModal {
|
|
|
8779
9081
|
__publicField(this, "options");
|
|
8780
9082
|
__publicField(this, "countdownInterval", null);
|
|
8781
9083
|
__publicField(this, "isCapturing", false);
|
|
9084
|
+
__publicField(this, "flashController", null);
|
|
8782
9085
|
this.options = options;
|
|
8783
9086
|
}
|
|
8784
9087
|
/**
|
|
@@ -8789,6 +9092,10 @@ class BiometricCaptureModal {
|
|
|
8789
9092
|
await this.createModal();
|
|
8790
9093
|
await this.initializeCamera();
|
|
8791
9094
|
this.enableCaptureButton();
|
|
9095
|
+
if (this.options.autoLighting !== false) {
|
|
9096
|
+
this.flashController = createScreenFlashController(() => this.video);
|
|
9097
|
+
this.flashController.start();
|
|
9098
|
+
}
|
|
8792
9099
|
} catch (error) {
|
|
8793
9100
|
this.options.onError(
|
|
8794
9101
|
new NeoFaceError(
|
|
@@ -8974,6 +9281,10 @@ class BiometricCaptureModal {
|
|
|
8974
9281
|
* Limpa recursos (câmera, intervalos, etc.)
|
|
8975
9282
|
*/
|
|
8976
9283
|
cleanup() {
|
|
9284
|
+
if (this.flashController) {
|
|
9285
|
+
this.flashController.destroy();
|
|
9286
|
+
this.flashController = null;
|
|
9287
|
+
}
|
|
8977
9288
|
if (this.stream) {
|
|
8978
9289
|
this.stream.getTracks().forEach((track) => track.stop());
|
|
8979
9290
|
this.stream = null;
|
|
@@ -10866,8 +11177,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
|
|
|
10866
11177
|
initializeBiometricDetection,
|
|
10867
11178
|
isAdvancedDetectionAvailable
|
|
10868
11179
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
10869
|
-
const VERSION = "1.
|
|
10870
|
-
const RELEASE_DATE = "2026-09-
|
|
11180
|
+
const VERSION = "1.36.1";
|
|
11181
|
+
const RELEASE_DATE = "2026-09-03";
|
|
10871
11182
|
let cachedSession = null;
|
|
10872
11183
|
function getCachedCaptureSession() {
|
|
10873
11184
|
return cachedSession;
|
|
@@ -11132,9 +11443,12 @@ class OnboardingCaptureModal {
|
|
|
11132
11443
|
__publicField(this, "subtitle");
|
|
11133
11444
|
__publicField(this, "faceBlob", null);
|
|
11134
11445
|
__publicField(this, "documentBlob", null);
|
|
11446
|
+
__publicField(this, "autoLighting");
|
|
11447
|
+
__publicField(this, "flashController", null);
|
|
11135
11448
|
this.countdownSeconds = (options == null ? void 0 : options.countdown) ?? 3;
|
|
11136
11449
|
this.title = options == null ? void 0 : options.title;
|
|
11137
11450
|
this.subtitle = options == null ? void 0 : options.subtitle;
|
|
11451
|
+
this.autoLighting = (options == null ? void 0 : options.autoLighting) !== false;
|
|
11138
11452
|
}
|
|
11139
11453
|
/**
|
|
11140
11454
|
* Abre o modal, inicia câmera e gerencia o fluxo de captura dos dois passos.
|
|
@@ -11248,6 +11562,10 @@ class OnboardingCaptureModal {
|
|
|
11248
11562
|
}
|
|
11249
11563
|
this.video.srcObject = this.stream;
|
|
11250
11564
|
await this.video.play();
|
|
11565
|
+
if (this.autoLighting && !this.flashController) {
|
|
11566
|
+
this.flashController = createScreenFlashController(() => this.video);
|
|
11567
|
+
this.flashController.start();
|
|
11568
|
+
}
|
|
11251
11569
|
}
|
|
11252
11570
|
/**
|
|
11253
11571
|
* Executa a contagem regressiva e atualiza textos para captura de rosto.
|
|
@@ -11332,6 +11650,10 @@ class OnboardingCaptureModal {
|
|
|
11332
11650
|
* Fecha o modal e libera a câmera.
|
|
11333
11651
|
*/
|
|
11334
11652
|
async close() {
|
|
11653
|
+
if (this.flashController) {
|
|
11654
|
+
this.flashController.destroy();
|
|
11655
|
+
this.flashController = null;
|
|
11656
|
+
}
|
|
11335
11657
|
if (this.stream) {
|
|
11336
11658
|
this.stream.getTracks().forEach((t) => t.stop());
|
|
11337
11659
|
this.stream = null;
|
|
@@ -12465,201 +12787,6 @@ function AuthorizePermissionDenied({
|
|
|
12465
12787
|
] })
|
|
12466
12788
|
] });
|
|
12467
12789
|
}
|
|
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
12790
|
const STYLE_ID = "neofaceid-authorize-styles";
|
|
12664
12791
|
const GESTURE_TO_GLYPH = {
|
|
12665
12792
|
center: "center",
|
|
@@ -12993,6 +13120,7 @@ class DocumentCaptureModal {
|
|
|
12993
13120
|
__publicField(this, "options");
|
|
12994
13121
|
__publicField(this, "selectedDocument", null);
|
|
12995
13122
|
__publicField(this, "currentStep", "select");
|
|
13123
|
+
__publicField(this, "flashController", null);
|
|
12996
13124
|
__publicField(this, "frontBlob", null);
|
|
12997
13125
|
__publicField(this, "backBlob", null);
|
|
12998
13126
|
this.options = {
|
|
@@ -13384,6 +13512,7 @@ class DocumentCaptureModal {
|
|
|
13384
13512
|
this.video.playsInline = true;
|
|
13385
13513
|
this.video.srcObject = this.stream;
|
|
13386
13514
|
await this.video.play();
|
|
13515
|
+
this.startScreenFlash();
|
|
13387
13516
|
}
|
|
13388
13517
|
} catch (error) {
|
|
13389
13518
|
console.error("[DocumentCapture] Camera error:", error);
|
|
@@ -13396,6 +13525,7 @@ class DocumentCaptureModal {
|
|
|
13396
13525
|
if (this.video) {
|
|
13397
13526
|
this.video.srcObject = this.stream;
|
|
13398
13527
|
await this.video.play();
|
|
13528
|
+
this.startScreenFlash();
|
|
13399
13529
|
}
|
|
13400
13530
|
} catch (fallbackError) {
|
|
13401
13531
|
(_c = this.resolvePromise) == null ? void 0 : _c.call(this, {
|
|
@@ -13410,11 +13540,22 @@ class DocumentCaptureModal {
|
|
|
13410
13540
|
}
|
|
13411
13541
|
}
|
|
13412
13542
|
stopCamera() {
|
|
13543
|
+
if (this.flashController) {
|
|
13544
|
+
this.flashController.destroy();
|
|
13545
|
+
this.flashController = null;
|
|
13546
|
+
}
|
|
13413
13547
|
if (this.stream) {
|
|
13414
13548
|
this.stream.getTracks().forEach((track) => track.stop());
|
|
13415
13549
|
this.stream = null;
|
|
13416
13550
|
}
|
|
13417
13551
|
}
|
|
13552
|
+
// NEO-425 · US14.11.a — screen flash assistivo. Chamado após câmera acordar.
|
|
13553
|
+
startScreenFlash() {
|
|
13554
|
+
if (this.options.autoLighting === false) return;
|
|
13555
|
+
if (this.flashController) return;
|
|
13556
|
+
this.flashController = createScreenFlashController(() => this.video);
|
|
13557
|
+
this.flashController.start();
|
|
13558
|
+
}
|
|
13418
13559
|
async handleCapture() {
|
|
13419
13560
|
var _a2;
|
|
13420
13561
|
if (!this.video || !this.canvas) return;
|