@neofaceid/web-sdk 1.35.0 → 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 +368 -204
- 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() {
|
|
@@ -10894,7 +11222,9 @@ async function openCaptureSession({
|
|
|
10894
11222
|
"Content-Type": "application/json",
|
|
10895
11223
|
"X-App-Token": applicationToken
|
|
10896
11224
|
},
|
|
10897
|
-
|
|
11225
|
+
// NEO-424 · backend real aceita apenas valores em UPPERCASE
|
|
11226
|
+
// (`AUTHORIZATION`, `LOGIN`, `ONBOARDING`, …). API pública mantém lowercase.
|
|
11227
|
+
body: JSON.stringify({ purpose: String(purpose).toUpperCase() })
|
|
10898
11228
|
});
|
|
10899
11229
|
} catch (err) {
|
|
10900
11230
|
const message = err instanceof Error ? err.message : "Erro de rede";
|
|
@@ -10912,16 +11242,20 @@ async function openCaptureSession({
|
|
|
10912
11242
|
const raw = await response.json().catch(() => null);
|
|
10913
11243
|
const payload = (raw == null ? void 0 : raw.data) ?? raw;
|
|
10914
11244
|
const sessionId = payload == null ? void 0 : payload.session_id;
|
|
10915
|
-
|
|
10916
|
-
if (!sessionId || !expiresAt) {
|
|
11245
|
+
if (!sessionId) {
|
|
10917
11246
|
throw new NeoFaceError(
|
|
10918
|
-
"Resposta inválida ao abrir sessão de captura (
|
|
11247
|
+
"Resposta inválida ao abrir sessão de captura (session_id ausente)",
|
|
10919
11248
|
ErrorType.API_ERROR
|
|
10920
11249
|
);
|
|
10921
11250
|
}
|
|
11251
|
+
const expiresAt = typeof (payload == null ? void 0 : payload.expires_at) === "string" && payload.expires_at.length > 0 ? payload.expires_at : deriveExpiresAt(payload == null ? void 0 : payload.expires_in);
|
|
10922
11252
|
cachedSession = { session_id: sessionId, expires_at: expiresAt };
|
|
10923
11253
|
return cachedSession;
|
|
10924
11254
|
}
|
|
11255
|
+
function deriveExpiresAt(expiresIn) {
|
|
11256
|
+
const seconds = typeof expiresIn === "number" && Number.isFinite(expiresIn) ? expiresIn : 0;
|
|
11257
|
+
return new Date(Date.now() + seconds * 1e3).toISOString();
|
|
11258
|
+
}
|
|
10925
11259
|
async function withCaptureSession(params, fn) {
|
|
10926
11260
|
const session = cachedSession ?? await openCaptureSession(params);
|
|
10927
11261
|
try {
|
|
@@ -11126,9 +11460,12 @@ class OnboardingCaptureModal {
|
|
|
11126
11460
|
__publicField(this, "subtitle");
|
|
11127
11461
|
__publicField(this, "faceBlob", null);
|
|
11128
11462
|
__publicField(this, "documentBlob", null);
|
|
11463
|
+
__publicField(this, "autoLighting");
|
|
11464
|
+
__publicField(this, "flashController", null);
|
|
11129
11465
|
this.countdownSeconds = (options == null ? void 0 : options.countdown) ?? 3;
|
|
11130
11466
|
this.title = options == null ? void 0 : options.title;
|
|
11131
11467
|
this.subtitle = options == null ? void 0 : options.subtitle;
|
|
11468
|
+
this.autoLighting = (options == null ? void 0 : options.autoLighting) !== false;
|
|
11132
11469
|
}
|
|
11133
11470
|
/**
|
|
11134
11471
|
* Abre o modal, inicia câmera e gerencia o fluxo de captura dos dois passos.
|
|
@@ -11242,6 +11579,10 @@ class OnboardingCaptureModal {
|
|
|
11242
11579
|
}
|
|
11243
11580
|
this.video.srcObject = this.stream;
|
|
11244
11581
|
await this.video.play();
|
|
11582
|
+
if (this.autoLighting && !this.flashController) {
|
|
11583
|
+
this.flashController = createScreenFlashController(() => this.video);
|
|
11584
|
+
this.flashController.start();
|
|
11585
|
+
}
|
|
11245
11586
|
}
|
|
11246
11587
|
/**
|
|
11247
11588
|
* Executa a contagem regressiva e atualiza textos para captura de rosto.
|
|
@@ -11326,6 +11667,10 @@ class OnboardingCaptureModal {
|
|
|
11326
11667
|
* Fecha o modal e libera a câmera.
|
|
11327
11668
|
*/
|
|
11328
11669
|
async close() {
|
|
11670
|
+
if (this.flashController) {
|
|
11671
|
+
this.flashController.destroy();
|
|
11672
|
+
this.flashController = null;
|
|
11673
|
+
}
|
|
11329
11674
|
if (this.stream) {
|
|
11330
11675
|
this.stream.getTracks().forEach((t) => t.stop());
|
|
11331
11676
|
this.stream = null;
|
|
@@ -12459,201 +12804,6 @@ function AuthorizePermissionDenied({
|
|
|
12459
12804
|
] })
|
|
12460
12805
|
] });
|
|
12461
12806
|
}
|
|
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
|
-
}
|
|
12657
12807
|
const STYLE_ID = "neofaceid-authorize-styles";
|
|
12658
12808
|
const GESTURE_TO_GLYPH = {
|
|
12659
12809
|
center: "center",
|
|
@@ -12987,6 +13137,7 @@ class DocumentCaptureModal {
|
|
|
12987
13137
|
__publicField(this, "options");
|
|
12988
13138
|
__publicField(this, "selectedDocument", null);
|
|
12989
13139
|
__publicField(this, "currentStep", "select");
|
|
13140
|
+
__publicField(this, "flashController", null);
|
|
12990
13141
|
__publicField(this, "frontBlob", null);
|
|
12991
13142
|
__publicField(this, "backBlob", null);
|
|
12992
13143
|
this.options = {
|
|
@@ -13378,6 +13529,7 @@ class DocumentCaptureModal {
|
|
|
13378
13529
|
this.video.playsInline = true;
|
|
13379
13530
|
this.video.srcObject = this.stream;
|
|
13380
13531
|
await this.video.play();
|
|
13532
|
+
this.startScreenFlash();
|
|
13381
13533
|
}
|
|
13382
13534
|
} catch (error) {
|
|
13383
13535
|
console.error("[DocumentCapture] Camera error:", error);
|
|
@@ -13390,6 +13542,7 @@ class DocumentCaptureModal {
|
|
|
13390
13542
|
if (this.video) {
|
|
13391
13543
|
this.video.srcObject = this.stream;
|
|
13392
13544
|
await this.video.play();
|
|
13545
|
+
this.startScreenFlash();
|
|
13393
13546
|
}
|
|
13394
13547
|
} catch (fallbackError) {
|
|
13395
13548
|
(_c = this.resolvePromise) == null ? void 0 : _c.call(this, {
|
|
@@ -13404,11 +13557,22 @@ class DocumentCaptureModal {
|
|
|
13404
13557
|
}
|
|
13405
13558
|
}
|
|
13406
13559
|
stopCamera() {
|
|
13560
|
+
if (this.flashController) {
|
|
13561
|
+
this.flashController.destroy();
|
|
13562
|
+
this.flashController = null;
|
|
13563
|
+
}
|
|
13407
13564
|
if (this.stream) {
|
|
13408
13565
|
this.stream.getTracks().forEach((track) => track.stop());
|
|
13409
13566
|
this.stream = null;
|
|
13410
13567
|
}
|
|
13411
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
|
+
}
|
|
13412
13576
|
async handleCapture() {
|
|
13413
13577
|
var _a2;
|
|
13414
13578
|
if (!this.video || !this.canvas) return;
|