@neofaceid/web-sdk 1.11.1 → 1.13.2
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.
|
@@ -2288,13 +2288,30 @@ const registerApplication = async (jwtToken, consumerId, applicationData) => {
|
|
|
2288
2288
|
};
|
|
2289
2289
|
const recordFaceVideo = async (options = {}) => {
|
|
2290
2290
|
ensureSecureContext();
|
|
2291
|
-
const { durationMs = 3e3,
|
|
2291
|
+
const { durationMs = 3e3, onProgress } = options;
|
|
2292
|
+
const getSupportedMimeType = () => {
|
|
2293
|
+
const types = [
|
|
2294
|
+
"video/mp4;codecs=avc1",
|
|
2295
|
+
// iOS Safari (prioritário quando disponível)
|
|
2296
|
+
"video/mp4",
|
|
2297
|
+
// iOS Safari fallback
|
|
2298
|
+
"video/webm;codecs=vp9",
|
|
2299
|
+
// Chrome/Firefox
|
|
2300
|
+
"video/webm;codecs=vp8",
|
|
2301
|
+
// Chrome/Firefox fallback
|
|
2302
|
+
"video/webm"
|
|
2303
|
+
// Android Chrome
|
|
2304
|
+
];
|
|
2305
|
+
for (const type of types) {
|
|
2306
|
+
if (MediaRecorder.isTypeSupported(type)) return type;
|
|
2307
|
+
}
|
|
2308
|
+
return "";
|
|
2309
|
+
};
|
|
2292
2310
|
return new Promise(async (resolve, reject) => {
|
|
2293
|
-
let
|
|
2294
|
-
let mediaRecorder = null;
|
|
2311
|
+
let cameraStream = null;
|
|
2295
2312
|
const chunks = [];
|
|
2296
2313
|
try {
|
|
2297
|
-
|
|
2314
|
+
cameraStream = await navigator.mediaDevices.getUserMedia({
|
|
2298
2315
|
video: {
|
|
2299
2316
|
facingMode: "user",
|
|
2300
2317
|
width: { ideal: 640 },
|
|
@@ -2302,38 +2319,33 @@ const recordFaceVideo = async (options = {}) => {
|
|
|
2302
2319
|
},
|
|
2303
2320
|
audio: false
|
|
2304
2321
|
});
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
mimeType: selectedMimeType,
|
|
2315
|
-
videoBitsPerSecond: 1e6
|
|
2316
|
-
// 1 Mbps
|
|
2317
|
-
});
|
|
2318
|
-
mediaRecorder.ondataavailable = (event) => {
|
|
2322
|
+
const selectedMimeType = getSupportedMimeType();
|
|
2323
|
+
let recorder;
|
|
2324
|
+
try {
|
|
2325
|
+
recorder = selectedMimeType ? new MediaRecorder(cameraStream, { mimeType: selectedMimeType, videoBitsPerSecond: 1e6 }) : new MediaRecorder(cameraStream, { videoBitsPerSecond: 1e6 });
|
|
2326
|
+
} catch {
|
|
2327
|
+
recorder = new MediaRecorder(cameraStream);
|
|
2328
|
+
}
|
|
2329
|
+
const finalMimeType = recorder.mimeType || selectedMimeType;
|
|
2330
|
+
recorder.ondataavailable = (event) => {
|
|
2319
2331
|
if (event.data.size > 0) {
|
|
2320
2332
|
chunks.push(event.data);
|
|
2321
2333
|
}
|
|
2322
2334
|
};
|
|
2323
|
-
|
|
2324
|
-
if (
|
|
2325
|
-
|
|
2335
|
+
recorder.onstop = () => {
|
|
2336
|
+
if (cameraStream) {
|
|
2337
|
+
cameraStream.getTracks().forEach((track) => track.stop());
|
|
2326
2338
|
}
|
|
2327
|
-
const videoBlob = new Blob(chunks, { type:
|
|
2339
|
+
const videoBlob = new Blob(chunks, { type: finalMimeType });
|
|
2328
2340
|
resolve(videoBlob);
|
|
2329
2341
|
};
|
|
2330
|
-
|
|
2331
|
-
if (
|
|
2332
|
-
|
|
2342
|
+
recorder.onerror = (event) => {
|
|
2343
|
+
if (cameraStream) {
|
|
2344
|
+
cameraStream.getTracks().forEach((track) => track.stop());
|
|
2333
2345
|
}
|
|
2334
2346
|
reject(new NeoFaceError(`Recording error: ${event}`, ErrorType.CAPTURE_ERROR));
|
|
2335
2347
|
};
|
|
2336
|
-
|
|
2348
|
+
recorder.start(100);
|
|
2337
2349
|
if (onProgress) {
|
|
2338
2350
|
const progressInterval = 100;
|
|
2339
2351
|
let elapsed = 0;
|
|
@@ -2347,13 +2359,13 @@ const recordFaceVideo = async (options = {}) => {
|
|
|
2347
2359
|
}, progressInterval);
|
|
2348
2360
|
}
|
|
2349
2361
|
setTimeout(() => {
|
|
2350
|
-
if (
|
|
2351
|
-
|
|
2362
|
+
if (recorder && recorder.state === "recording") {
|
|
2363
|
+
recorder.stop();
|
|
2352
2364
|
}
|
|
2353
2365
|
}, durationMs);
|
|
2354
2366
|
} catch (error) {
|
|
2355
|
-
if (
|
|
2356
|
-
|
|
2367
|
+
if (cameraStream) {
|
|
2368
|
+
cameraStream.getTracks().forEach((track) => track.stop());
|
|
2357
2369
|
}
|
|
2358
2370
|
if (error instanceof Error) {
|
|
2359
2371
|
if (error.name === "NotAllowedError") {
|
|
@@ -2808,12 +2820,14 @@ const modalReducer = (state, action) => {
|
|
|
2808
2820
|
};
|
|
2809
2821
|
}
|
|
2810
2822
|
return state;
|
|
2823
|
+
case "CAMERA_LOADING":
|
|
2824
|
+
return { status: "camera-loading" };
|
|
2811
2825
|
case "START_PROCESSING":
|
|
2812
2826
|
return { status: "processing" };
|
|
2813
2827
|
case "SUCCESS":
|
|
2814
2828
|
return { status: "success" };
|
|
2815
2829
|
case "ERROR":
|
|
2816
|
-
return { status: "error", message: action.message };
|
|
2830
|
+
return { status: "error", message: action.message, retryable: action.retryable ?? true };
|
|
2817
2831
|
case "RESET":
|
|
2818
2832
|
return initialState;
|
|
2819
2833
|
default:
|
|
@@ -2859,9 +2873,7 @@ function BiometricRegistrationModal({
|
|
|
2859
2873
|
onClose,
|
|
2860
2874
|
onSuccess,
|
|
2861
2875
|
onPhotosCaptured,
|
|
2862
|
-
onError
|
|
2863
|
-
useRealApi = true
|
|
2864
|
-
// Sempre usa API real em produção
|
|
2876
|
+
onError
|
|
2865
2877
|
}) {
|
|
2866
2878
|
const videoRef = useRef(null);
|
|
2867
2879
|
const canvasRef = useRef(null);
|
|
@@ -2870,10 +2882,12 @@ function BiometricRegistrationModal({
|
|
|
2870
2882
|
const progressIntervalRef = useRef(null);
|
|
2871
2883
|
const isCapturingRef = useRef(false);
|
|
2872
2884
|
const currentStepRef = useRef("center");
|
|
2885
|
+
const watchdogTimerRef = useRef(null);
|
|
2886
|
+
const detectionStartedRef = useRef(false);
|
|
2873
2887
|
const captureStateRef = useRef(null);
|
|
2874
2888
|
const [state, dispatch] = useReducer(modalReducer, initialState);
|
|
2875
2889
|
const [faceDetected, setFaceDetected] = useState(false);
|
|
2876
|
-
const [
|
|
2890
|
+
const [_faceDistance, setFaceDistance] = useState("ok");
|
|
2877
2891
|
useEffect(() => {
|
|
2878
2892
|
if (state.status === "liveness") {
|
|
2879
2893
|
const newStep = state.step;
|
|
@@ -2989,20 +3003,66 @@ function BiometricRegistrationModal({
|
|
|
2989
3003
|
const loadModels = async () => {
|
|
2990
3004
|
try {
|
|
2991
3005
|
const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3006
|
+
const timeout = new Promise(
|
|
3007
|
+
(_, reject) => setTimeout(() => reject(new Error("Timeout ao carregar modelos")), 1e4)
|
|
3008
|
+
);
|
|
3009
|
+
await Promise.race([
|
|
3010
|
+
Promise.all([
|
|
3011
|
+
faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
|
|
3012
|
+
faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
|
|
3013
|
+
]),
|
|
3014
|
+
timeout
|
|
2995
3015
|
]);
|
|
2996
3016
|
} catch (error) {
|
|
2997
|
-
dispatch({
|
|
3017
|
+
dispatch({
|
|
3018
|
+
type: "ERROR",
|
|
3019
|
+
message: "Não foi possível carregar os modelos de IA. Verifique sua conexão e tente novamente.",
|
|
3020
|
+
retryable: true
|
|
3021
|
+
});
|
|
2998
3022
|
}
|
|
2999
3023
|
};
|
|
3000
3024
|
loadModels();
|
|
3001
3025
|
}, []);
|
|
3026
|
+
useEffect(() => {
|
|
3027
|
+
return () => {
|
|
3028
|
+
if (streamRef.current) {
|
|
3029
|
+
streamRef.current.getTracks().forEach((track) => {
|
|
3030
|
+
track.stop();
|
|
3031
|
+
});
|
|
3032
|
+
streamRef.current = null;
|
|
3033
|
+
}
|
|
3034
|
+
if (videoRef.current) {
|
|
3035
|
+
videoRef.current.srcObject = null;
|
|
3036
|
+
}
|
|
3037
|
+
if (watchdogTimerRef.current) {
|
|
3038
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3039
|
+
watchdogTimerRef.current = null;
|
|
3040
|
+
}
|
|
3041
|
+
if (progressIntervalRef.current) {
|
|
3042
|
+
clearInterval(progressIntervalRef.current);
|
|
3043
|
+
progressIntervalRef.current = null;
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
}, []);
|
|
3002
3047
|
useEffect(() => {
|
|
3003
3048
|
let mounted = true;
|
|
3004
3049
|
const initCamera = async () => {
|
|
3005
3050
|
if (state.status !== "liveness" || streamRef.current) return;
|
|
3051
|
+
detectionStartedRef.current = false;
|
|
3052
|
+
if (watchdogTimerRef.current) {
|
|
3053
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3054
|
+
watchdogTimerRef.current = null;
|
|
3055
|
+
}
|
|
3056
|
+
watchdogTimerRef.current = window.setTimeout(() => {
|
|
3057
|
+
if (!mounted) return;
|
|
3058
|
+
if (detectionStartedRef.current === false) {
|
|
3059
|
+
dispatch({
|
|
3060
|
+
type: "ERROR",
|
|
3061
|
+
message: "A câmera não respondeu a tempo. Verifique se ela está disponível e tente novamente.",
|
|
3062
|
+
retryable: true
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
}, 2e4);
|
|
3006
3066
|
try {
|
|
3007
3067
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
3008
3068
|
video: {
|
|
@@ -3032,13 +3092,30 @@ function BiometricRegistrationModal({
|
|
|
3032
3092
|
await video.play();
|
|
3033
3093
|
} catch (error) {
|
|
3034
3094
|
if (!mounted) return;
|
|
3035
|
-
|
|
3036
|
-
|
|
3095
|
+
if (watchdogTimerRef.current) {
|
|
3096
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3097
|
+
watchdogTimerRef.current = null;
|
|
3098
|
+
}
|
|
3099
|
+
let errorMessage = error instanceof Error ? error.message : "Erro ao acessar câmera";
|
|
3100
|
+
if (error instanceof DOMException) {
|
|
3101
|
+
if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
|
|
3102
|
+
errorMessage = "Permissão de câmera negada. Permita o acesso à câmera nas configurações do navegador e tente novamente.";
|
|
3103
|
+
} else if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
|
|
3104
|
+
errorMessage = "Nenhuma câmera encontrada. Conecte uma câmera e tente novamente.";
|
|
3105
|
+
} else if (error.name === "NotReadableError") {
|
|
3106
|
+
errorMessage = "A câmera está sendo usada por outro programa. Feche-o e tente novamente.";
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
dispatch({ type: "ERROR", message: errorMessage, retryable: true });
|
|
3037
3110
|
}
|
|
3038
3111
|
};
|
|
3039
3112
|
initCamera();
|
|
3040
3113
|
return () => {
|
|
3041
3114
|
mounted = false;
|
|
3115
|
+
if (watchdogTimerRef.current) {
|
|
3116
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3117
|
+
watchdogTimerRef.current = null;
|
|
3118
|
+
}
|
|
3042
3119
|
if (streamRef.current) {
|
|
3043
3120
|
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
3044
3121
|
streamRef.current = null;
|
|
@@ -3118,21 +3195,59 @@ function BiometricRegistrationModal({
|
|
|
3118
3195
|
requestAnimationFrame(detectFace2);
|
|
3119
3196
|
}
|
|
3120
3197
|
};
|
|
3121
|
-
const
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
clearTimeout(startTimer);
|
|
3130
|
-
if (progressIntervalRef.current) {
|
|
3131
|
-
clearInterval(progressIntervalRef.current);
|
|
3132
|
-
progressIntervalRef.current = null;
|
|
3133
|
-
}
|
|
3134
|
-
holdStartTimeRef.current = null;
|
|
3198
|
+
const startDetection = () => {
|
|
3199
|
+
if (!isRunning || !videoRef.current) return;
|
|
3200
|
+
detectionStartedRef.current = true;
|
|
3201
|
+
if (watchdogTimerRef.current) {
|
|
3202
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3203
|
+
watchdogTimerRef.current = null;
|
|
3204
|
+
}
|
|
3205
|
+
detectFace2();
|
|
3135
3206
|
};
|
|
3207
|
+
const video = videoRef.current;
|
|
3208
|
+
if (video && video.readyState >= 2) {
|
|
3209
|
+
const startTimer = setTimeout(startDetection, 200);
|
|
3210
|
+
return () => {
|
|
3211
|
+
isRunning = false;
|
|
3212
|
+
clearTimeout(startTimer);
|
|
3213
|
+
if (progressIntervalRef.current) {
|
|
3214
|
+
clearInterval(progressIntervalRef.current);
|
|
3215
|
+
progressIntervalRef.current = null;
|
|
3216
|
+
}
|
|
3217
|
+
holdStartTimeRef.current = null;
|
|
3218
|
+
};
|
|
3219
|
+
} else {
|
|
3220
|
+
const onCanPlay = () => {
|
|
3221
|
+
if (isRunning) startDetection();
|
|
3222
|
+
};
|
|
3223
|
+
let pollCount = 0;
|
|
3224
|
+
const MAX_POLLS = 50;
|
|
3225
|
+
const pollTimer = window.setInterval(() => {
|
|
3226
|
+
pollCount++;
|
|
3227
|
+
if (!isRunning) {
|
|
3228
|
+
clearInterval(pollTimer);
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
if (videoRef.current && videoRef.current.readyState >= 2) {
|
|
3232
|
+
clearInterval(pollTimer);
|
|
3233
|
+
videoRef.current.removeEventListener("canplay", onCanPlay);
|
|
3234
|
+
startDetection();
|
|
3235
|
+
} else if (pollCount >= MAX_POLLS) {
|
|
3236
|
+
clearInterval(pollTimer);
|
|
3237
|
+
}
|
|
3238
|
+
}, 300);
|
|
3239
|
+
video == null ? void 0 : video.addEventListener("canplay", onCanPlay, { once: true });
|
|
3240
|
+
return () => {
|
|
3241
|
+
isRunning = false;
|
|
3242
|
+
clearInterval(pollTimer);
|
|
3243
|
+
video == null ? void 0 : video.removeEventListener("canplay", onCanPlay);
|
|
3244
|
+
if (progressIntervalRef.current) {
|
|
3245
|
+
clearInterval(progressIntervalRef.current);
|
|
3246
|
+
progressIntervalRef.current = null;
|
|
3247
|
+
}
|
|
3248
|
+
holdStartTimeRef.current = null;
|
|
3249
|
+
};
|
|
3250
|
+
}
|
|
3136
3251
|
}, [state.status]);
|
|
3137
3252
|
const capturePhoto = async () => {
|
|
3138
3253
|
if (!videoRef.current || !canvasRef.current || isCapturingRef.current) {
|
|
@@ -3287,14 +3402,37 @@ function BiometricRegistrationModal({
|
|
|
3287
3402
|
}
|
|
3288
3403
|
)
|
|
3289
3404
|
] }) }),
|
|
3290
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Não foi possível
|
|
3405
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Não foi possível processar" }),
|
|
3291
3406
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: state.message }),
|
|
3292
3407
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "button-group", children: [
|
|
3293
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "secondary-button", onClick: () => dispatch({ type: "RESET" }), children: "Tentar Novamente" }),
|
|
3408
|
+
state.retryable && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "secondary-button", onClick: () => dispatch({ type: "RESET" }), children: "Tentar Novamente" }),
|
|
3294
3409
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ghost-button", onClick: onClose, children: "Cancelar" })
|
|
3295
3410
|
] })
|
|
3296
3411
|
] });
|
|
3297
3412
|
};
|
|
3413
|
+
const renderCameraLoading = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "processing-container", children: [
|
|
3414
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "spinner-wrapper", children: [
|
|
3415
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "spinner" }),
|
|
3416
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "spinner-ring" })
|
|
3417
|
+
] }),
|
|
3418
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Inicializando câmera" }),
|
|
3419
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Aguarde enquanto preparamos a câmera..." }),
|
|
3420
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3421
|
+
"div",
|
|
3422
|
+
{
|
|
3423
|
+
className: "branding",
|
|
3424
|
+
style: {
|
|
3425
|
+
marginTop: "40px",
|
|
3426
|
+
paddingTop: "24px",
|
|
3427
|
+
borderTop: "1px solid rgba(255, 255, 255, 0.1)"
|
|
3428
|
+
},
|
|
3429
|
+
children: [
|
|
3430
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "NeoFaceId by" }),
|
|
3431
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "brand-name", children: "OCTA" })
|
|
3432
|
+
]
|
|
3433
|
+
}
|
|
3434
|
+
)
|
|
3435
|
+
] });
|
|
3298
3436
|
const renderSuccess = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "result-container success", children: [
|
|
3299
3437
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "result-icon success-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "60", height: "60", viewBox: "0 0 60 60", fill: "none", children: [
|
|
3300
3438
|
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "30", cy: "30", r: "28", stroke: "currentColor", strokeWidth: "3" }),
|
|
@@ -3876,6 +4014,7 @@ function BiometricRegistrationModal({
|
|
|
3876
4014
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "modal-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "modal-content", children: [
|
|
3877
4015
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "close-button", onClick: onClose, children: "×" }),
|
|
3878
4016
|
state.status === "liveness" && renderLivenessDetection(),
|
|
4017
|
+
state.status === "camera-loading" && renderCameraLoading(),
|
|
3879
4018
|
state.status === "processing" && renderProcessing(),
|
|
3880
4019
|
state.status === "error" && renderError(),
|
|
3881
4020
|
state.status === "success" && renderSuccess()
|
|
@@ -5194,14 +5333,15 @@ async function captureFaceSilently() {
|
|
|
5194
5333
|
video.style.width = "1px";
|
|
5195
5334
|
video.style.height = "1px";
|
|
5196
5335
|
video.style.opacity = "0";
|
|
5336
|
+
video.muted = true;
|
|
5337
|
+
video.playsInline = true;
|
|
5197
5338
|
video.setAttribute("autoplay", "");
|
|
5198
|
-
video.setAttribute("muted", "");
|
|
5199
5339
|
video.setAttribute("playsinline", "");
|
|
5200
5340
|
const canvas = document.createElement("canvas");
|
|
5201
5341
|
canvas.style.display = "none";
|
|
5202
5342
|
let stream = null;
|
|
5203
5343
|
let faceDetectionAttempts = 0;
|
|
5204
|
-
const MAX_FACE_DETECTION_ATTEMPTS =
|
|
5344
|
+
const MAX_FACE_DETECTION_ATTEMPTS = 30;
|
|
5205
5345
|
const cleanup = () => {
|
|
5206
5346
|
if (stream) {
|
|
5207
5347
|
stream.getTracks().forEach((track) => track.stop());
|
|
@@ -5215,22 +5355,27 @@ async function captureFaceSilently() {
|
|
|
5215
5355
|
};
|
|
5216
5356
|
document.body.appendChild(video);
|
|
5217
5357
|
document.body.appendChild(canvas);
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5358
|
+
const tryGetUserMedia = (withConstraints) => {
|
|
5359
|
+
if (withConstraints) {
|
|
5360
|
+
return navigator.mediaDevices.getUserMedia({
|
|
5361
|
+
video: {
|
|
5362
|
+
width: { ideal: 640 },
|
|
5363
|
+
height: { ideal: 480 },
|
|
5364
|
+
facingMode: "user"
|
|
5365
|
+
},
|
|
5366
|
+
audio: false
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
5369
|
+
return navigator.mediaDevices.getUserMedia({ video: { facingMode: "user" }, audio: false });
|
|
5370
|
+
};
|
|
5371
|
+
tryGetUserMedia(true).catch(() => tryGetUserMedia(false)).then((mediaStream) => {
|
|
5226
5372
|
stream = mediaStream;
|
|
5227
5373
|
video.srcObject = stream;
|
|
5228
|
-
video.play();
|
|
5229
5374
|
const tryCapture = () => {
|
|
5230
5375
|
if (!video.videoWidth || !video.videoHeight) {
|
|
5231
5376
|
if (faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
|
|
5232
5377
|
faceDetectionAttempts++;
|
|
5233
|
-
setTimeout(tryCapture,
|
|
5378
|
+
setTimeout(tryCapture, 150);
|
|
5234
5379
|
return;
|
|
5235
5380
|
}
|
|
5236
5381
|
cleanup();
|
|
@@ -5260,7 +5405,7 @@ async function captureFaceSilently() {
|
|
|
5260
5405
|
detectFace2().then((hasFace) => {
|
|
5261
5406
|
if (!hasFace && faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
|
|
5262
5407
|
faceDetectionAttempts++;
|
|
5263
|
-
setTimeout(tryCapture,
|
|
5408
|
+
setTimeout(tryCapture, 150);
|
|
5264
5409
|
return;
|
|
5265
5410
|
}
|
|
5266
5411
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
@@ -5278,9 +5423,26 @@ async function captureFaceSilently() {
|
|
|
5278
5423
|
);
|
|
5279
5424
|
});
|
|
5280
5425
|
};
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5426
|
+
const playPromise = video.play();
|
|
5427
|
+
if (playPromise !== void 0) {
|
|
5428
|
+
playPromise.then(() => {
|
|
5429
|
+
if (video.videoWidth && video.videoHeight) {
|
|
5430
|
+
setTimeout(tryCapture, CAMERA_READY_DELAY);
|
|
5431
|
+
} else {
|
|
5432
|
+
video.onloadedmetadata = () => setTimeout(tryCapture, CAMERA_READY_DELAY);
|
|
5433
|
+
setTimeout(() => {
|
|
5434
|
+
if (video.videoWidth && video.videoHeight) {
|
|
5435
|
+
tryCapture();
|
|
5436
|
+
}
|
|
5437
|
+
}, 1500);
|
|
5438
|
+
}
|
|
5439
|
+
}).catch((err) => {
|
|
5440
|
+
cleanup();
|
|
5441
|
+
reject(new NeoFaceError("Erro ao reproduzir câmera: " + err.message, ErrorType.CAMERA_ERROR));
|
|
5442
|
+
});
|
|
5443
|
+
} else {
|
|
5444
|
+
video.onloadedmetadata = () => setTimeout(tryCapture, CAMERA_READY_DELAY);
|
|
5445
|
+
}
|
|
5284
5446
|
video.onerror = () => {
|
|
5285
5447
|
cleanup();
|
|
5286
5448
|
reject(new NeoFaceError("Erro ao acessar câmera", ErrorType.CAMERA_ERROR));
|
|
@@ -5354,27 +5516,55 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
5354
5516
|
video.style.width = "1px";
|
|
5355
5517
|
video.style.height = "1px";
|
|
5356
5518
|
video.style.opacity = "0";
|
|
5519
|
+
video.muted = true;
|
|
5520
|
+
video.playsInline = true;
|
|
5357
5521
|
video.setAttribute("autoplay", "");
|
|
5358
|
-
video.setAttribute("muted", "");
|
|
5359
5522
|
video.setAttribute("playsinline", "");
|
|
5360
5523
|
document.body.appendChild(video);
|
|
5361
5524
|
let stream = null;
|
|
5362
5525
|
try {
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5526
|
+
try {
|
|
5527
|
+
stream = await navigator.mediaDevices.getUserMedia({
|
|
5528
|
+
video: {
|
|
5529
|
+
width: { ideal: 640 },
|
|
5530
|
+
height: { ideal: 480 },
|
|
5531
|
+
facingMode: "user"
|
|
5532
|
+
},
|
|
5533
|
+
audio: false
|
|
5534
|
+
});
|
|
5535
|
+
} catch {
|
|
5536
|
+
stream = await navigator.mediaDevices.getUserMedia({
|
|
5537
|
+
video: { facingMode: "user" },
|
|
5538
|
+
audio: false
|
|
5539
|
+
});
|
|
5540
|
+
}
|
|
5371
5541
|
video.srcObject = stream;
|
|
5542
|
+
await new Promise((resolve) => {
|
|
5543
|
+
if (video.readyState >= 1) {
|
|
5544
|
+
resolve();
|
|
5545
|
+
} else {
|
|
5546
|
+
const handler = () => {
|
|
5547
|
+
video.removeEventListener("loadedmetadata", handler);
|
|
5548
|
+
resolve();
|
|
5549
|
+
};
|
|
5550
|
+
video.addEventListener("loadedmetadata", handler);
|
|
5551
|
+
setTimeout(resolve, 2e3);
|
|
5552
|
+
}
|
|
5553
|
+
});
|
|
5372
5554
|
await video.play();
|
|
5373
5555
|
await new Promise((resolve) => {
|
|
5374
|
-
if (video.
|
|
5375
|
-
resolve(
|
|
5556
|
+
if (video.videoWidth && video.videoHeight) {
|
|
5557
|
+
resolve();
|
|
5376
5558
|
} else {
|
|
5377
|
-
|
|
5559
|
+
const checkSize = () => {
|
|
5560
|
+
if (video.videoWidth && video.videoHeight) {
|
|
5561
|
+
resolve();
|
|
5562
|
+
} else {
|
|
5563
|
+
setTimeout(checkSize, 100);
|
|
5564
|
+
}
|
|
5565
|
+
};
|
|
5566
|
+
setTimeout(resolve, 3e3);
|
|
5567
|
+
checkSize();
|
|
5378
5568
|
}
|
|
5379
5569
|
});
|
|
5380
5570
|
overlay.updateStatus("detecting");
|
|
@@ -5844,8 +6034,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
|
|
|
5844
6034
|
initializeBiometricDetection,
|
|
5845
6035
|
isAdvancedDetectionAvailable
|
|
5846
6036
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
5847
|
-
const VERSION = "1.
|
|
5848
|
-
const RELEASE_DATE = "2026-02-
|
|
6037
|
+
const VERSION = "1.13.2";
|
|
6038
|
+
const RELEASE_DATE = "2026-02-21";
|
|
5849
6039
|
class OnboardingCaptureModal {
|
|
5850
6040
|
constructor(options) {
|
|
5851
6041
|
__publicField(this, "overlay", null);
|
|
@@ -5896,7 +6086,7 @@ class OnboardingCaptureModal {
|
|
|
5896
6086
|
<div class="neofaceid-modal-body">
|
|
5897
6087
|
<p class="neofaceid-subtitle">${this.subtitle ?? "Vamos capturar sua face e documento"}</p>
|
|
5898
6088
|
<div class="neofaceid-camera-container">
|
|
5899
|
-
<video class="neofaceid-video" autoplay playsinline></video>
|
|
6089
|
+
<video class="neofaceid-video" autoplay muted playsinline></video>
|
|
5900
6090
|
<canvas class="neofaceid-canvas"></canvas>
|
|
5901
6091
|
<div class="neofaceid-overlay">
|
|
5902
6092
|
<div class="neofaceid-frame"></div>
|
|
@@ -5947,12 +6137,20 @@ class OnboardingCaptureModal {
|
|
|
5947
6137
|
* Inicia a câmera do usuário com resolução adequada.
|
|
5948
6138
|
*/
|
|
5949
6139
|
async startCamera() {
|
|
5950
|
-
const constraints = {
|
|
5951
|
-
video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } },
|
|
5952
|
-
audio: false
|
|
5953
|
-
};
|
|
5954
|
-
this.stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
5955
6140
|
if (!this.video) throw new Error("Elemento de vídeo não encontrado");
|
|
6141
|
+
this.video.muted = true;
|
|
6142
|
+
this.video.playsInline = true;
|
|
6143
|
+
try {
|
|
6144
|
+
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
6145
|
+
video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } },
|
|
6146
|
+
audio: false
|
|
6147
|
+
});
|
|
6148
|
+
} catch {
|
|
6149
|
+
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
6150
|
+
video: { facingMode: "user" },
|
|
6151
|
+
audio: false
|
|
6152
|
+
});
|
|
6153
|
+
}
|
|
5956
6154
|
this.video.srcObject = this.stream;
|
|
5957
6155
|
await this.video.play();
|
|
5958
6156
|
}
|
|
@@ -6071,11 +6269,9 @@ class NeoFaceID {
|
|
|
6071
6269
|
*/
|
|
6072
6270
|
constructor(config) {
|
|
6073
6271
|
__publicField(this, "appToken");
|
|
6074
|
-
__publicField(this, "baseUrl");
|
|
6075
6272
|
__publicField(this, "signature");
|
|
6076
6273
|
__publicField(this, "sessionData");
|
|
6077
6274
|
this.appToken = config.appToken;
|
|
6078
|
-
this.baseUrl = config.baseUrl || getBaseUrl();
|
|
6079
6275
|
this.signature = config.signature;
|
|
6080
6276
|
this.sessionData = config.sessionData;
|
|
6081
6277
|
if (this.signature && !this.validateSignatureFormat(this.signature)) {
|
|
@@ -6164,7 +6360,7 @@ class NeoFaceID {
|
|
|
6164
6360
|
* ```
|
|
6165
6361
|
*/
|
|
6166
6362
|
async loginRecognition(options) {
|
|
6167
|
-
const { biometricData,
|
|
6363
|
+
const { biometricData, purpose, confidenceThreshold = 0.8 } = options;
|
|
6168
6364
|
const imageBlobs = Array.isArray(biometricData) ? biometricData : [biometricData];
|
|
6169
6365
|
if (imageBlobs.length === 0) {
|
|
6170
6366
|
throw new NeoFaceError(
|
|
@@ -6634,7 +6830,7 @@ class DocumentCaptureModal {
|
|
|
6634
6830
|
|
|
6635
6831
|
<div class="neoface-doc-body">
|
|
6636
6832
|
<div class="neoface-doc-camera-container">
|
|
6637
|
-
<video class="neoface-doc-video" autoplay playsinline></video>
|
|
6833
|
+
<video class="neoface-doc-video" autoplay muted playsinline></video>
|
|
6638
6834
|
<canvas class="neoface-doc-canvas"></canvas>
|
|
6639
6835
|
|
|
6640
6836
|
<div class="neoface-doc-guide">
|
|
@@ -6886,6 +7082,8 @@ class DocumentCaptureModal {
|
|
|
6886
7082
|
this.video = (_a = this.overlay) == null ? void 0 : _a.querySelector(".neoface-doc-video");
|
|
6887
7083
|
this.canvas = (_b = this.overlay) == null ? void 0 : _b.querySelector(".neoface-doc-canvas");
|
|
6888
7084
|
if (this.video) {
|
|
7085
|
+
this.video.muted = true;
|
|
7086
|
+
this.video.playsInline = true;
|
|
6889
7087
|
this.video.srcObject = this.stream;
|
|
6890
7088
|
await this.video.play();
|
|
6891
7089
|
}
|
|
@@ -7507,7 +7705,12 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
7507
7705
|
const container = document.createElement("div");
|
|
7508
7706
|
container.id = "neoface-biometric-registration-container";
|
|
7509
7707
|
document.body.appendChild(container);
|
|
7708
|
+
let rootRef = null;
|
|
7510
7709
|
const cleanup = () => {
|
|
7710
|
+
if (rootRef) {
|
|
7711
|
+
rootRef.unmount();
|
|
7712
|
+
rootRef = null;
|
|
7713
|
+
}
|
|
7511
7714
|
if (document.body.contains(container)) {
|
|
7512
7715
|
document.body.removeChild(container);
|
|
7513
7716
|
}
|
|
@@ -7517,7 +7720,6 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
7517
7720
|
personData,
|
|
7518
7721
|
applicationToken,
|
|
7519
7722
|
useRealApi: (options == null ? void 0 : options.useRealApi) ?? true,
|
|
7520
|
-
// Sempre usa API real em produção
|
|
7521
7723
|
onClose: cleanup,
|
|
7522
7724
|
onSuccess: (result) => {
|
|
7523
7725
|
cleanup();
|
|
@@ -7528,8 +7730,8 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
7528
7730
|
callbacks.onError("BIOMETRIC_REGISTRATION_ERROR", error);
|
|
7529
7731
|
}
|
|
7530
7732
|
});
|
|
7531
|
-
|
|
7532
|
-
|
|
7733
|
+
rootRef = clientExports.createRoot(container);
|
|
7734
|
+
rootRef.render(modalElement);
|
|
7533
7735
|
}).catch((error) => {
|
|
7534
7736
|
cleanup();
|
|
7535
7737
|
callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
|
|
@@ -7539,16 +7741,29 @@ function startLivenessCapture(applicationToken, callbacks) {
|
|
|
7539
7741
|
const container = document.createElement("div");
|
|
7540
7742
|
container.id = "neoface-liveness-capture-container";
|
|
7541
7743
|
document.body.appendChild(container);
|
|
7744
|
+
let completed = false;
|
|
7745
|
+
let rootRef = null;
|
|
7542
7746
|
const cleanup = () => {
|
|
7747
|
+
if (rootRef) {
|
|
7748
|
+
rootRef.unmount();
|
|
7749
|
+
rootRef = null;
|
|
7750
|
+
}
|
|
7543
7751
|
if (document.body.contains(container)) {
|
|
7544
7752
|
document.body.removeChild(container);
|
|
7545
7753
|
}
|
|
7546
7754
|
};
|
|
7755
|
+
const handleClose = () => {
|
|
7756
|
+
cleanup();
|
|
7757
|
+
if (!completed && callbacks.onCancel) {
|
|
7758
|
+
callbacks.onCancel();
|
|
7759
|
+
}
|
|
7760
|
+
};
|
|
7547
7761
|
validateToken(applicationToken).then(() => {
|
|
7548
7762
|
const modalElement = React.createElement(BiometricRegistrationModal, {
|
|
7549
7763
|
applicationToken,
|
|
7550
|
-
onClose:
|
|
7764
|
+
onClose: handleClose,
|
|
7551
7765
|
onPhotosCaptured: (photos) => {
|
|
7766
|
+
completed = true;
|
|
7552
7767
|
callbacks.onSuccess(photos);
|
|
7553
7768
|
},
|
|
7554
7769
|
onError: (error) => {
|
|
@@ -7556,8 +7771,8 @@ function startLivenessCapture(applicationToken, callbacks) {
|
|
|
7556
7771
|
callbacks.onError("LIVENESS_CAPTURE_ERROR", error);
|
|
7557
7772
|
}
|
|
7558
7773
|
});
|
|
7559
|
-
|
|
7560
|
-
|
|
7774
|
+
rootRef = clientExports.createRoot(container);
|
|
7775
|
+
rootRef.render(modalElement);
|
|
7561
7776
|
}).catch((error) => {
|
|
7562
7777
|
cleanup();
|
|
7563
7778
|
callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
|