@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.
- package/dist/index.d.ts +19 -3
- package/dist/neoface-id-sdk.es.js +358 -200
- 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,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.
|
|
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;
|