@neofaceid/web-sdk 1.41.1 → 1.42.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 +252 -1
- package/dist/neoface-id-sdk.es.js +1015 -307
- package/dist/neoface-id-sdk.umd.js +1 -1
- package/package.json +1 -1
|
@@ -4311,9 +4311,16 @@ function refine(fn, _params = {}) {
|
|
|
4311
4311
|
function superRefine(fn) {
|
|
4312
4312
|
return /* @__PURE__ */ _superRefine(fn);
|
|
4313
4313
|
}
|
|
4314
|
+
const AuthMethodSchema = _enum(["face", "password"]);
|
|
4314
4315
|
object({
|
|
4315
4316
|
success: boolean(),
|
|
4316
4317
|
accessToken: string().optional(),
|
|
4318
|
+
/**
|
|
4319
|
+
* Só chega quando `application.can_issue_user_tokens` está ligado no core.
|
|
4320
|
+
* Sessões longas (o console) dependem dele; fluxos curtos podem ignorá-lo.
|
|
4321
|
+
*/
|
|
4322
|
+
refreshToken: string().optional(),
|
|
4323
|
+
method: AuthMethodSchema.optional(),
|
|
4317
4324
|
alias: string().optional(),
|
|
4318
4325
|
user: object({
|
|
4319
4326
|
id: string(),
|
|
@@ -4547,6 +4554,10 @@ const ENVIRONMENT_URLS = {
|
|
|
4547
4554
|
sandbox: "https://sandbox-core.neofaceid.com",
|
|
4548
4555
|
production: "https://core.neofaceid.com.br"
|
|
4549
4556
|
};
|
|
4557
|
+
const DEFAULT_AUTO_FALLBACK = {
|
|
4558
|
+
toPhone: false,
|
|
4559
|
+
toPassword: true
|
|
4560
|
+
};
|
|
4550
4561
|
const DEFAULT_LOCALE = "pt-BR";
|
|
4551
4562
|
const DEFAULT_RADIUS = 16;
|
|
4552
4563
|
const DEFAULT_THEME = "light";
|
|
@@ -4563,13 +4574,31 @@ function makeDefaultConfig() {
|
|
|
4563
4574
|
theme: DEFAULT_THEME,
|
|
4564
4575
|
locale: DEFAULT_LOCALE,
|
|
4565
4576
|
resolvedTheme: null,
|
|
4566
|
-
consent: { ...DEFAULT_CONSENT }
|
|
4577
|
+
consent: { ...DEFAULT_CONSENT },
|
|
4578
|
+
autoFallback: { ...DEFAULT_AUTO_FALLBACK }
|
|
4567
4579
|
};
|
|
4568
4580
|
}
|
|
4569
4581
|
let globalConfig = makeDefaultConfig();
|
|
4570
4582
|
let consentWarningShown = false;
|
|
4571
4583
|
function init(options = {}) {
|
|
4572
|
-
const {
|
|
4584
|
+
const {
|
|
4585
|
+
environment,
|
|
4586
|
+
baseUrl,
|
|
4587
|
+
applicationToken,
|
|
4588
|
+
appName,
|
|
4589
|
+
accent,
|
|
4590
|
+
radius,
|
|
4591
|
+
theme,
|
|
4592
|
+
locale,
|
|
4593
|
+
consent,
|
|
4594
|
+
autoFallback
|
|
4595
|
+
} = options;
|
|
4596
|
+
if (autoFallback) {
|
|
4597
|
+
globalConfig.autoFallback = {
|
|
4598
|
+
toPhone: autoFallback.toPhone ?? DEFAULT_AUTO_FALLBACK.toPhone,
|
|
4599
|
+
toPassword: autoFallback.toPassword ?? DEFAULT_AUTO_FALLBACK.toPassword
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4573
4602
|
if (consent) {
|
|
4574
4603
|
globalConfig.consent = consent;
|
|
4575
4604
|
} else {
|
|
@@ -4667,6 +4696,9 @@ function getAppName() {
|
|
|
4667
4696
|
function getAccent() {
|
|
4668
4697
|
return globalConfig.accent;
|
|
4669
4698
|
}
|
|
4699
|
+
function getAutoFallback() {
|
|
4700
|
+
return { ...globalConfig.autoFallback };
|
|
4701
|
+
}
|
|
4670
4702
|
function getRadius() {
|
|
4671
4703
|
return globalConfig.radius;
|
|
4672
4704
|
}
|
|
@@ -5132,6 +5164,9 @@ const loginWithBiometric = async (image, applicationToken) => {
|
|
|
5132
5164
|
return {
|
|
5133
5165
|
success: true,
|
|
5134
5166
|
accessToken: data.accessToken || data.access_token || "",
|
|
5167
|
+
// Só vem quando `can_issue_user_tokens` está ligado na aplicação.
|
|
5168
|
+
refreshToken: data.refreshToken || data.refresh_token || void 0,
|
|
5169
|
+
method: "face",
|
|
5135
5170
|
alias: data.alias,
|
|
5136
5171
|
user: {
|
|
5137
5172
|
id: ((_a2 = data.user) == null ? void 0 : _a2.id) || "",
|
|
@@ -5618,6 +5653,8 @@ const loginWithEmail = async (email2, password, applicationToken) => {
|
|
|
5618
5653
|
success: true,
|
|
5619
5654
|
// Compatível com diferentes formatos de resposta do backend
|
|
5620
5655
|
accessToken: data.access_token || data.access,
|
|
5656
|
+
refreshToken: data.refresh_token || data.refresh || void 0,
|
|
5657
|
+
method: "password",
|
|
5621
5658
|
alias: data.user && data.user.alias || data.alias,
|
|
5622
5659
|
user: {
|
|
5623
5660
|
id: data.user && data.user.id || "",
|
|
@@ -5640,6 +5677,98 @@ const loginWithEmail = async (email2, password, applicationToken) => {
|
|
|
5640
5677
|
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5641
5678
|
}
|
|
5642
5679
|
};
|
|
5680
|
+
const requestDeviceEnrollmentToken = async (userAccessToken, applicationToken) => {
|
|
5681
|
+
var _a2, _b, _c;
|
|
5682
|
+
if (!userAccessToken) {
|
|
5683
|
+
throw new NeoFaceError(
|
|
5684
|
+
"Inscrição de aparelho exige um usuário autenticado (access token ausente).",
|
|
5685
|
+
ErrorType.INVALID_TOKEN
|
|
5686
|
+
);
|
|
5687
|
+
}
|
|
5688
|
+
const controller = createTimeoutController();
|
|
5689
|
+
try {
|
|
5690
|
+
const response = await fetch(`${getApiBaseUrl()}/api/v1/devices/enrollment-token/`, {
|
|
5691
|
+
method: "POST",
|
|
5692
|
+
headers: {
|
|
5693
|
+
"Content-Type": "application/json",
|
|
5694
|
+
"X-App-Token": applicationToken,
|
|
5695
|
+
Authorization: `Bearer ${userAccessToken}`
|
|
5696
|
+
},
|
|
5697
|
+
signal: controller.signal
|
|
5698
|
+
});
|
|
5699
|
+
if (!response.ok) {
|
|
5700
|
+
if (response.status === 401 || response.status === 403) {
|
|
5701
|
+
throw new NeoFaceError(
|
|
5702
|
+
"Não foi possível inscrever o aparelho: sessão expirada ou conta sem permissão. Faça login novamente.",
|
|
5703
|
+
ErrorType.INVALID_TOKEN
|
|
5704
|
+
);
|
|
5705
|
+
}
|
|
5706
|
+
throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
|
|
5707
|
+
}
|
|
5708
|
+
const data = await response.json();
|
|
5709
|
+
const token = data.token ?? ((_a2 = data.data) == null ? void 0 : _a2.token);
|
|
5710
|
+
if (!token) {
|
|
5711
|
+
throw new NeoFaceError("Resposta sem token de inscrição", ErrorType.API_ERROR);
|
|
5712
|
+
}
|
|
5713
|
+
return {
|
|
5714
|
+
token,
|
|
5715
|
+
expiresIn: data.expires_in ?? ((_b = data.data) == null ? void 0 : _b.expires_in) ?? 300,
|
|
5716
|
+
purpose: data.purpose ?? ((_c = data.data) == null ? void 0 : _c.purpose) ?? "device-enrollment"
|
|
5717
|
+
};
|
|
5718
|
+
} catch (error) {
|
|
5719
|
+
if (error instanceof NeoFaceError) throw error;
|
|
5720
|
+
if (error instanceof Error) {
|
|
5721
|
+
if (error.name === "AbortError") {
|
|
5722
|
+
throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
|
|
5723
|
+
}
|
|
5724
|
+
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5725
|
+
}
|
|
5726
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5727
|
+
}
|
|
5728
|
+
};
|
|
5729
|
+
const refreshSession = async (refreshToken, applicationToken) => {
|
|
5730
|
+
const controller = createTimeoutController();
|
|
5731
|
+
try {
|
|
5732
|
+
const response = await fetch(`${getApiBaseUrl()}/api/v1/token/refresh/`, {
|
|
5733
|
+
method: "POST",
|
|
5734
|
+
headers: {
|
|
5735
|
+
"Content-Type": "application/json",
|
|
5736
|
+
"X-App-Token": applicationToken
|
|
5737
|
+
},
|
|
5738
|
+
body: JSON.stringify({ refresh: refreshToken }),
|
|
5739
|
+
signal: controller.signal
|
|
5740
|
+
});
|
|
5741
|
+
if (!response.ok) {
|
|
5742
|
+
if (response.status === 401 || response.status === 403) {
|
|
5743
|
+
throw new NeoFaceError(
|
|
5744
|
+
"Sessão expirada — faça login novamente",
|
|
5745
|
+
ErrorType.INVALID_TOKEN
|
|
5746
|
+
);
|
|
5747
|
+
}
|
|
5748
|
+
throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
|
|
5749
|
+
}
|
|
5750
|
+
const data = await response.json();
|
|
5751
|
+
const access = data.access ?? data.access_token;
|
|
5752
|
+
if (!access) {
|
|
5753
|
+
throw new NeoFaceError("Resposta de refresh sem access token", ErrorType.API_ERROR);
|
|
5754
|
+
}
|
|
5755
|
+
return {
|
|
5756
|
+
accessToken: access,
|
|
5757
|
+
// Com rotação ligada o core devolve refresh novo. Se algum ambiente
|
|
5758
|
+
// estiver sem rotação, o refresh atual segue valendo.
|
|
5759
|
+
refreshToken: data.refresh ?? data.refresh_token ?? refreshToken
|
|
5760
|
+
};
|
|
5761
|
+
} catch (error) {
|
|
5762
|
+
if (error instanceof NeoFaceError) throw error;
|
|
5763
|
+
if (error instanceof Error) {
|
|
5764
|
+
if (error.name === "AbortError") {
|
|
5765
|
+
throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
|
|
5766
|
+
}
|
|
5767
|
+
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5768
|
+
}
|
|
5769
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5770
|
+
}
|
|
5771
|
+
};
|
|
5643
5772
|
const identifyPerson = async (image, applicationToken) => {
|
|
5644
5773
|
var _a2, _b, _c, _d;
|
|
5645
5774
|
ensureSecureContext();
|
|
@@ -5952,7 +6081,7 @@ const recordFaceVideo = async (options = {}) => {
|
|
|
5952
6081
|
});
|
|
5953
6082
|
};
|
|
5954
6083
|
const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
|
|
5955
|
-
var _a2, _b, _c, _d, _e, _f;
|
|
6084
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
5956
6085
|
const { maxAttempts = 60, intervalMs = 1e3, onStatusChange } = options;
|
|
5957
6086
|
let attempts = 0;
|
|
5958
6087
|
while (attempts < maxAttempts) {
|
|
@@ -5984,6 +6113,10 @@ const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
|
|
|
5984
6113
|
if (status === "revoked") {
|
|
5985
6114
|
throw new NeoFaceError("Task was cancelled", ErrorType.RECOGNITION_FAILED);
|
|
5986
6115
|
}
|
|
6116
|
+
if (data.success === false) {
|
|
6117
|
+
const reason = ((_h = (_g = data.data) == null ? void 0 : _g.result) == null ? void 0 : _h.message) || ((_i = data.data) == null ? void 0 : _i.error) || ((_j = data.data) == null ? void 0 : _j.message) || data.message || "Task failed";
|
|
6118
|
+
throw new NeoFaceError(reason, ErrorType.RECOGNITION_FAILED);
|
|
6119
|
+
}
|
|
5987
6120
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
5988
6121
|
} catch (error) {
|
|
5989
6122
|
if (error instanceof NeoFaceError) {
|
|
@@ -6242,11 +6375,13 @@ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty(
|
|
|
6242
6375
|
recognizeBiometric,
|
|
6243
6376
|
recognizeByPurpose,
|
|
6244
6377
|
recordFaceVideo,
|
|
6378
|
+
refreshSession,
|
|
6245
6379
|
registerApplication,
|
|
6246
6380
|
registerBiometric,
|
|
6247
6381
|
registerDocumentByImage,
|
|
6248
6382
|
registerPersonWithBiometric,
|
|
6249
6383
|
registerPersonWithoutFace,
|
|
6384
|
+
requestDeviceEnrollmentToken,
|
|
6250
6385
|
requestPasswordReset,
|
|
6251
6386
|
simpleIdentification,
|
|
6252
6387
|
startProofOfLife,
|
|
@@ -8732,7 +8867,7 @@ function BiometricRegistrationModal({
|
|
|
8732
8867
|
/* @__PURE__ */ jsx("canvas", { ref: canvasRef })
|
|
8733
8868
|
] });
|
|
8734
8869
|
}
|
|
8735
|
-
const VERSION = "1.
|
|
8870
|
+
const VERSION = "1.42.0";
|
|
8736
8871
|
const RELEASE_DATE = "2026-09-10";
|
|
8737
8872
|
const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
|
|
8738
8873
|
const CONSENT_TRAIL_MAX_LIMIT = 100;
|
|
@@ -9178,118 +9313,814 @@ const DEFAULT_CONSENT_INFO = {
|
|
|
9178
9313
|
privacyPolicyUrl: DEFAULT_PRIVACY_URL,
|
|
9179
9314
|
retentionDays: DEFAULT_RETENTION_DAYS
|
|
9180
9315
|
};
|
|
9181
|
-
function
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
};
|
|
9191
|
-
case "too-far":
|
|
9192
|
-
case "too-close":
|
|
9193
|
-
case "off-center":
|
|
9194
|
-
return {
|
|
9195
|
-
borderColor: "#F59E0B",
|
|
9196
|
-
boxShadow: "0 0 0 4px rgba(245, 158, 11, 0.7), 0 0 35px rgba(245, 158, 11, 0.35)",
|
|
9197
|
-
svgStroke: "#F59E0B",
|
|
9198
|
-
badgeBg: "rgba(245, 158, 11, 0.95)",
|
|
9199
|
-
badgeColor: "#1F2937"
|
|
9200
|
-
};
|
|
9201
|
-
case "searching":
|
|
9202
|
-
default:
|
|
9203
|
-
return {
|
|
9204
|
-
borderColor: "rgba(156, 163, 175, 0.5)",
|
|
9205
|
-
boxShadow: "0 0 0 4px rgba(156, 163, 175, 0.3), 0 0 20px rgba(156, 163, 175, 0.15)",
|
|
9206
|
-
svgStroke: "#9CA3AF",
|
|
9207
|
-
badgeBg: "rgba(55, 65, 81, 0.9)",
|
|
9208
|
-
badgeColor: "#FFFFFF"
|
|
9209
|
-
};
|
|
9210
|
-
}
|
|
9211
|
-
}
|
|
9212
|
-
function CameraOvalGuide({
|
|
9213
|
-
stream,
|
|
9214
|
-
faceState,
|
|
9215
|
-
instruction,
|
|
9216
|
-
className = "",
|
|
9217
|
-
style = {}
|
|
9316
|
+
function Sheet({
|
|
9317
|
+
open,
|
|
9318
|
+
onDismiss,
|
|
9319
|
+
dismissOnOverlay = true,
|
|
9320
|
+
ariaLabel,
|
|
9321
|
+
ariaLabelledBy,
|
|
9322
|
+
children,
|
|
9323
|
+
className,
|
|
9324
|
+
style
|
|
9218
9325
|
}) {
|
|
9219
|
-
const videoRef = useRef(null);
|
|
9220
9326
|
useEffect(() => {
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
videoEl.srcObject = null;
|
|
9229
|
-
}
|
|
9230
|
-
return () => {
|
|
9231
|
-
if (stream) {
|
|
9232
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
9233
|
-
}
|
|
9234
|
-
if (videoEl) {
|
|
9235
|
-
videoEl.srcObject = null;
|
|
9236
|
-
}
|
|
9237
|
-
};
|
|
9238
|
-
}, [stream]);
|
|
9239
|
-
const halo = getHaloStyles(faceState);
|
|
9240
|
-
return /* @__PURE__ */ jsxs(
|
|
9327
|
+
injectThemeStyles();
|
|
9328
|
+
}, []);
|
|
9329
|
+
if (!open) return null;
|
|
9330
|
+
const handleOverlay = () => {
|
|
9331
|
+
if (dismissOnOverlay && onDismiss) onDismiss();
|
|
9332
|
+
};
|
|
9333
|
+
return /* @__PURE__ */ jsx(
|
|
9241
9334
|
"div",
|
|
9242
9335
|
{
|
|
9243
|
-
className:
|
|
9336
|
+
className: "neofaceid-root",
|
|
9337
|
+
role: "presentation",
|
|
9244
9338
|
style: {
|
|
9339
|
+
position: "fixed",
|
|
9340
|
+
inset: 0,
|
|
9341
|
+
zIndex: 10001,
|
|
9245
9342
|
display: "flex",
|
|
9246
|
-
flexDirection: "column",
|
|
9247
9343
|
alignItems: "center",
|
|
9248
9344
|
justifyContent: "center",
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
boxSizing: "border-box",
|
|
9254
|
-
...style
|
|
9345
|
+
padding: 20,
|
|
9346
|
+
background: `var(${CSS_VAR.overlay})`,
|
|
9347
|
+
backdropFilter: "blur(3px)",
|
|
9348
|
+
WebkitBackdropFilter: "blur(3px)"
|
|
9255
9349
|
},
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9350
|
+
onClick: handleOverlay,
|
|
9351
|
+
children: /* @__PURE__ */ jsx(
|
|
9352
|
+
"div",
|
|
9353
|
+
{
|
|
9354
|
+
role: "dialog",
|
|
9355
|
+
"aria-modal": "true",
|
|
9356
|
+
"aria-label": ariaLabel,
|
|
9357
|
+
"aria-labelledby": ariaLabelledBy,
|
|
9358
|
+
className,
|
|
9359
|
+
onClick: (e) => e.stopPropagation(),
|
|
9360
|
+
style: {
|
|
9361
|
+
background: `var(${CSS_VAR.surface})`,
|
|
9362
|
+
color: `var(${CSS_VAR.text})`,
|
|
9363
|
+
borderRadius: `var(${CSS_VAR.radius}, 16px)`,
|
|
9364
|
+
padding: 28,
|
|
9365
|
+
maxWidth: 440,
|
|
9366
|
+
width: "100%",
|
|
9367
|
+
boxShadow: "0 1px 3px rgba(10,19,32,.08)",
|
|
9368
|
+
boxSizing: "border-box",
|
|
9369
|
+
...style
|
|
9370
|
+
},
|
|
9371
|
+
children
|
|
9266
9372
|
}
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9373
|
+
)
|
|
9374
|
+
}
|
|
9375
|
+
);
|
|
9376
|
+
}
|
|
9377
|
+
const NON_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["connected", "heartbeat", "processing"]);
|
|
9378
|
+
const DEFAULT_TIMEOUT_MS = 31e4;
|
|
9379
|
+
const DEFAULT_MAX_RECONNECTS = 5;
|
|
9380
|
+
const RECONNECT_BASE_DELAY_MS = 500;
|
|
9381
|
+
const RECONNECT_MAX_DELAY_MS = 8e3;
|
|
9382
|
+
function delay(ms, signal) {
|
|
9383
|
+
return new Promise((resolve) => {
|
|
9384
|
+
const id = setTimeout(resolve, ms);
|
|
9385
|
+
signal == null ? void 0 : signal.addEventListener(
|
|
9386
|
+
"abort",
|
|
9387
|
+
() => {
|
|
9388
|
+
clearTimeout(id);
|
|
9389
|
+
resolve();
|
|
9390
|
+
},
|
|
9391
|
+
{ once: true }
|
|
9392
|
+
);
|
|
9393
|
+
});
|
|
9394
|
+
}
|
|
9395
|
+
async function consumeSseStream(options) {
|
|
9396
|
+
const {
|
|
9397
|
+
requestTicket,
|
|
9398
|
+
buildUrl,
|
|
9399
|
+
classify,
|
|
9400
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
9401
|
+
maxReconnects = DEFAULT_MAX_RECONNECTS,
|
|
9402
|
+
signal,
|
|
9403
|
+
eventSourceFactory,
|
|
9404
|
+
onProgress
|
|
9405
|
+
} = options;
|
|
9406
|
+
const makeEventSource = eventSourceFactory ?? ((url) => {
|
|
9407
|
+
const Ctor = globalThis.EventSource;
|
|
9408
|
+
if (!Ctor) {
|
|
9409
|
+
throw new NeoFaceError(
|
|
9410
|
+
"EventSource indisponível neste ambiente",
|
|
9411
|
+
ErrorType.INITIALIZATION_ERROR
|
|
9412
|
+
);
|
|
9413
|
+
}
|
|
9414
|
+
return new Ctor(url);
|
|
9415
|
+
});
|
|
9416
|
+
const startedAt = Date.now();
|
|
9417
|
+
let reconnects = 0;
|
|
9418
|
+
for (; ; ) {
|
|
9419
|
+
if (signal == null ? void 0 : signal.aborted) {
|
|
9420
|
+
throw new NeoFaceError("Fluxo cancelado", ErrorType.UNKNOWN);
|
|
9421
|
+
}
|
|
9422
|
+
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
9423
|
+
if (remaining <= 0) {
|
|
9424
|
+
throw new NeoFaceError("Tempo esgotado aguardando resposta", ErrorType.NETWORK);
|
|
9425
|
+
}
|
|
9426
|
+
const ticket = await requestTicket();
|
|
9427
|
+
const outcome = await new Promise((resolve, reject) => {
|
|
9428
|
+
let source;
|
|
9429
|
+
try {
|
|
9430
|
+
source = makeEventSource(buildUrl(ticket));
|
|
9431
|
+
} catch (err) {
|
|
9432
|
+
reject(err);
|
|
9433
|
+
return;
|
|
9434
|
+
}
|
|
9435
|
+
let settled = false;
|
|
9436
|
+
let timerId;
|
|
9437
|
+
let onAbort;
|
|
9438
|
+
const cleanup = () => {
|
|
9439
|
+
if (timerId !== void 0) clearTimeout(timerId);
|
|
9440
|
+
if (onAbort) signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
|
|
9441
|
+
source.close();
|
|
9442
|
+
};
|
|
9443
|
+
timerId = setTimeout(() => {
|
|
9444
|
+
if (settled) return;
|
|
9445
|
+
settled = true;
|
|
9446
|
+
cleanup();
|
|
9447
|
+
reject(new NeoFaceError("Tempo esgotado aguardando resposta", ErrorType.NETWORK));
|
|
9448
|
+
}, remaining);
|
|
9449
|
+
onAbort = () => {
|
|
9450
|
+
if (settled) return;
|
|
9451
|
+
settled = true;
|
|
9452
|
+
cleanup();
|
|
9453
|
+
reject(new NeoFaceError("Fluxo cancelado", ErrorType.UNKNOWN));
|
|
9454
|
+
};
|
|
9455
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
|
|
9456
|
+
source.onmessage = (event) => {
|
|
9457
|
+
if (settled) return;
|
|
9458
|
+
let frame;
|
|
9459
|
+
try {
|
|
9460
|
+
frame = JSON.parse(event.data);
|
|
9461
|
+
} catch {
|
|
9462
|
+
return;
|
|
9274
9463
|
}
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
inset: 0;
|
|
9279
|
-
width: 100%;
|
|
9280
|
-
height: 100%;
|
|
9281
|
-
pointer-events: none;
|
|
9282
|
-
z-index: 2;
|
|
9464
|
+
if (frame.status && NON_TERMINAL_STATUSES.has(frame.status)) {
|
|
9465
|
+
if (onProgress) onProgress(frame);
|
|
9466
|
+
return;
|
|
9283
9467
|
}
|
|
9284
|
-
|
|
9285
|
-
.
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9468
|
+
const verdict = classify(frame);
|
|
9469
|
+
if (verdict.kind === "pending") {
|
|
9470
|
+
if (onProgress) onProgress(frame);
|
|
9471
|
+
return;
|
|
9472
|
+
}
|
|
9473
|
+
settled = true;
|
|
9474
|
+
cleanup();
|
|
9475
|
+
resolve({ type: "settled", verdict });
|
|
9476
|
+
};
|
|
9477
|
+
source.onerror = () => {
|
|
9478
|
+
if (settled) return;
|
|
9479
|
+
settled = true;
|
|
9480
|
+
cleanup();
|
|
9481
|
+
resolve({ type: "transport" });
|
|
9482
|
+
};
|
|
9483
|
+
});
|
|
9484
|
+
if (outcome.type === "settled") {
|
|
9485
|
+
const { verdict } = outcome;
|
|
9486
|
+
if (verdict.kind === "resolve") return verdict.value;
|
|
9487
|
+
throw verdict.error;
|
|
9488
|
+
}
|
|
9489
|
+
reconnects += 1;
|
|
9490
|
+
if (reconnects > maxReconnects) {
|
|
9491
|
+
throw new NeoFaceError(
|
|
9492
|
+
`Conexão perdida após ${maxReconnects} tentativas de reconexão`,
|
|
9493
|
+
ErrorType.NETWORK
|
|
9494
|
+
);
|
|
9495
|
+
}
|
|
9496
|
+
const backoff = Math.min(
|
|
9497
|
+
RECONNECT_BASE_DELAY_MS * 2 ** (reconnects - 1),
|
|
9498
|
+
RECONNECT_MAX_DELAY_MS
|
|
9499
|
+
);
|
|
9500
|
+
await delay(backoff, signal);
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9503
|
+
const BASE_STYLE = {
|
|
9504
|
+
minHeight: 44,
|
|
9505
|
+
minWidth: 44,
|
|
9506
|
+
padding: "12px 20px",
|
|
9507
|
+
borderRadius: 16,
|
|
9508
|
+
border: "none",
|
|
9509
|
+
fontSize: 15,
|
|
9510
|
+
fontWeight: 600,
|
|
9511
|
+
fontFamily: "inherit",
|
|
9512
|
+
cursor: "pointer",
|
|
9513
|
+
transition: "background 120ms ease-out",
|
|
9514
|
+
display: "inline-flex",
|
|
9515
|
+
alignItems: "center",
|
|
9516
|
+
justifyContent: "center",
|
|
9517
|
+
gap: 8
|
|
9518
|
+
};
|
|
9519
|
+
const Button = forwardRef(
|
|
9520
|
+
({ variant = "primary", style, type = "button", ...rest }, ref) => {
|
|
9521
|
+
const variantStyle = variant === "primary" ? {
|
|
9522
|
+
background: `var(${CSS_VAR.action})`,
|
|
9523
|
+
color: `var(${CSS_VAR.actionText})`
|
|
9524
|
+
} : variant === "secondary" ? {
|
|
9525
|
+
background: `var(${CSS_VAR.surfaceMuted})`,
|
|
9526
|
+
color: `var(${CSS_VAR.text})`
|
|
9527
|
+
} : {
|
|
9528
|
+
background: "transparent",
|
|
9529
|
+
color: `var(${CSS_VAR.textMuted})`
|
|
9530
|
+
};
|
|
9531
|
+
return (
|
|
9532
|
+
// eslint-disable-next-line react/button-has-type
|
|
9533
|
+
/* @__PURE__ */ jsx(
|
|
9534
|
+
"button",
|
|
9535
|
+
{
|
|
9536
|
+
ref,
|
|
9537
|
+
type,
|
|
9538
|
+
...rest,
|
|
9539
|
+
style: { ...BASE_STYLE, ...variantStyle, ...style }
|
|
9540
|
+
}
|
|
9541
|
+
)
|
|
9542
|
+
);
|
|
9543
|
+
}
|
|
9544
|
+
);
|
|
9545
|
+
Button.displayName = "Button";
|
|
9546
|
+
function StatusPill({ tone, children, icon, className }) {
|
|
9547
|
+
const palette = SEMANTIC[tone];
|
|
9548
|
+
return /* @__PURE__ */ jsxs(
|
|
9549
|
+
"span",
|
|
9550
|
+
{
|
|
9551
|
+
className,
|
|
9552
|
+
style: {
|
|
9553
|
+
display: "inline-flex",
|
|
9554
|
+
alignItems: "center",
|
|
9555
|
+
gap: 6,
|
|
9556
|
+
padding: "4px 10px",
|
|
9557
|
+
borderRadius: 9999,
|
|
9558
|
+
background: palette.bg,
|
|
9559
|
+
color: palette.fg,
|
|
9560
|
+
fontSize: 13,
|
|
9561
|
+
fontWeight: 600,
|
|
9562
|
+
fontFamily: "inherit",
|
|
9563
|
+
lineHeight: 1.3
|
|
9564
|
+
},
|
|
9565
|
+
children: [
|
|
9566
|
+
icon,
|
|
9567
|
+
children
|
|
9568
|
+
]
|
|
9569
|
+
}
|
|
9570
|
+
);
|
|
9571
|
+
}
|
|
9572
|
+
const STYLE_ID$2 = "neofaceid-awaiting-push-styles";
|
|
9573
|
+
const CSS$1 = `
|
|
9574
|
+
.neofaceid-root .nfid-push {
|
|
9575
|
+
display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
|
|
9576
|
+
padding: 8px 8px 4px;
|
|
9577
|
+
}
|
|
9578
|
+
.neofaceid-root .nfid-push h2 {
|
|
9579
|
+
margin: 6px 0 0; font-size: 20px; font-weight: 700; color: var(--nfid-text); line-height: 1.25;
|
|
9580
|
+
letter-spacing: -0.015em;
|
|
9581
|
+
}
|
|
9582
|
+
.neofaceid-root .nfid-push p {
|
|
9583
|
+
margin: 0; font-size: 14px; color: var(--nfid-text-muted); line-height: 1.5;
|
|
9584
|
+
}
|
|
9585
|
+
.neofaceid-root .nfid-push-phone-icon {
|
|
9586
|
+
width: 56px; height: 56px; color: var(--nfid-action);
|
|
9587
|
+
}
|
|
9588
|
+
.neofaceid-root .nfid-push-code {
|
|
9589
|
+
display: flex; flex-direction: column; align-items: center; gap: 6px;
|
|
9590
|
+
margin-top: 14px; padding: 16px 28px;
|
|
9591
|
+
background: var(--nfid-surface-muted); border-radius: 16px; width: 100%;
|
|
9592
|
+
}
|
|
9593
|
+
.neofaceid-root .nfid-push-code-label {
|
|
9594
|
+
font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
|
|
9595
|
+
color: var(--nfid-text-subtle);
|
|
9596
|
+
}
|
|
9597
|
+
.neofaceid-root .nfid-push-code-value {
|
|
9598
|
+
font-size: 52px; font-weight: 800; line-height: 1; letter-spacing: 0.08em;
|
|
9599
|
+
color: var(--nfid-text); font-variant-numeric: tabular-nums;
|
|
9600
|
+
}
|
|
9601
|
+
.neofaceid-root .nfid-push-countdown {
|
|
9602
|
+
font-size: 13px; color: var(--nfid-text-muted); font-variant-numeric: tabular-nums;
|
|
9603
|
+
margin-top: 12px;
|
|
9604
|
+
}
|
|
9605
|
+
.neofaceid-root .nfid-push-actions {
|
|
9606
|
+
display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 16px;
|
|
9607
|
+
}
|
|
9608
|
+
.neofaceid-root .nfid-push-footer {
|
|
9609
|
+
display: flex; justify-content: center; margin-top: 16px; padding-top: 12px;
|
|
9610
|
+
border-top: 1px solid var(--nfid-line);
|
|
9611
|
+
width: 100%;
|
|
9612
|
+
}
|
|
9613
|
+
.neofaceid-root .nfid-push-pulse {
|
|
9614
|
+
animation: nfidPushPulse 1.8s ease-in-out infinite;
|
|
9615
|
+
}
|
|
9616
|
+
@keyframes nfidPushPulse {
|
|
9617
|
+
0%, 100% { opacity: 1; }
|
|
9618
|
+
50% { opacity: 0.45; }
|
|
9619
|
+
}
|
|
9620
|
+
@media (prefers-reduced-motion: reduce) {
|
|
9621
|
+
.neofaceid-root .nfid-push-pulse { animation: none; }
|
|
9622
|
+
}
|
|
9623
|
+
`;
|
|
9624
|
+
function formatRemaining(seconds) {
|
|
9625
|
+
const safe = Math.max(0, seconds);
|
|
9626
|
+
const mm = Math.floor(safe / 60);
|
|
9627
|
+
const ss = safe % 60;
|
|
9628
|
+
return `${mm}:${String(ss).padStart(2, "0")}`;
|
|
9629
|
+
}
|
|
9630
|
+
function AwaitingPushView({
|
|
9631
|
+
appName,
|
|
9632
|
+
numericCode,
|
|
9633
|
+
remainingSeconds,
|
|
9634
|
+
onCancel
|
|
9635
|
+
}) {
|
|
9636
|
+
useEffect(() => {
|
|
9637
|
+
injectThemeStyles();
|
|
9638
|
+
injectScopedStyles(STYLE_ID$2, CSS$1);
|
|
9639
|
+
}, []);
|
|
9640
|
+
return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "status", "aria-live": "polite", children: [
|
|
9641
|
+
/* @__PURE__ */ jsx(StatusPill, { tone: "attention", children: "Aguardando confirmação" }),
|
|
9642
|
+
/* @__PURE__ */ jsxs(
|
|
9643
|
+
"svg",
|
|
9644
|
+
{
|
|
9645
|
+
className: "nfid-push-phone-icon nfid-push-pulse",
|
|
9646
|
+
viewBox: "0 0 24 24",
|
|
9647
|
+
fill: "none",
|
|
9648
|
+
stroke: "currentColor",
|
|
9649
|
+
strokeWidth: "1.75",
|
|
9650
|
+
strokeLinecap: "round",
|
|
9651
|
+
strokeLinejoin: "round",
|
|
9652
|
+
"aria-hidden": "true",
|
|
9653
|
+
children: [
|
|
9654
|
+
/* @__PURE__ */ jsx("rect", { x: "6", y: "2", width: "12", height: "20", rx: "3" }),
|
|
9655
|
+
/* @__PURE__ */ jsx("path", { d: "M11 18h2" })
|
|
9656
|
+
]
|
|
9657
|
+
}
|
|
9658
|
+
),
|
|
9659
|
+
/* @__PURE__ */ jsx("h2", { children: "Confirme no seu celular" }),
|
|
9660
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
9661
|
+
"Enviamos um pedido de confirmação para o seu aparelho. Abra o NeoFaceID Push e confirme com o seu rosto para continuar no ",
|
|
9662
|
+
appName,
|
|
9663
|
+
"."
|
|
9664
|
+
] }),
|
|
9665
|
+
numericCode && /* @__PURE__ */ jsxs("div", { className: "nfid-push-code", children: [
|
|
9666
|
+
/* @__PURE__ */ jsx("span", { className: "nfid-push-code-label", children: "Digite este número no celular" }),
|
|
9667
|
+
/* @__PURE__ */ jsx(
|
|
9668
|
+
"span",
|
|
9669
|
+
{
|
|
9670
|
+
className: "nfid-push-code-value",
|
|
9671
|
+
"aria-label": `Código de confirmação: ${numericCode.split("").join(" ")}`,
|
|
9672
|
+
children: numericCode
|
|
9673
|
+
}
|
|
9674
|
+
)
|
|
9675
|
+
] }),
|
|
9676
|
+
/* @__PURE__ */ jsxs("p", { className: "nfid-push-countdown", children: [
|
|
9677
|
+
"Expira em ",
|
|
9678
|
+
formatRemaining(remainingSeconds)
|
|
9679
|
+
] }),
|
|
9680
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Cancelar" }) }),
|
|
9681
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
|
|
9682
|
+
] });
|
|
9683
|
+
}
|
|
9684
|
+
function PushExpiredView({ onFallback, onCancel }) {
|
|
9685
|
+
useEffect(() => {
|
|
9686
|
+
injectThemeStyles();
|
|
9687
|
+
injectScopedStyles(STYLE_ID$2, CSS$1);
|
|
9688
|
+
}, []);
|
|
9689
|
+
return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
|
|
9690
|
+
/* @__PURE__ */ jsxs(
|
|
9691
|
+
"svg",
|
|
9692
|
+
{
|
|
9693
|
+
className: "nfid-push-phone-icon",
|
|
9694
|
+
viewBox: "0 0 24 24",
|
|
9695
|
+
fill: "none",
|
|
9696
|
+
stroke: "currentColor",
|
|
9697
|
+
strokeWidth: "1.75",
|
|
9698
|
+
strokeLinecap: "round",
|
|
9699
|
+
strokeLinejoin: "round",
|
|
9700
|
+
"aria-hidden": "true",
|
|
9701
|
+
style: { color: "var(--nfid-text-muted)" },
|
|
9702
|
+
children: [
|
|
9703
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
9704
|
+
/* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
|
|
9705
|
+
]
|
|
9706
|
+
}
|
|
9707
|
+
),
|
|
9708
|
+
/* @__PURE__ */ jsx("h2", { children: "O tempo esgotou" }),
|
|
9709
|
+
/* @__PURE__ */ jsx("p", { children: "Não recebemos a confirmação do seu celular a tempo." }),
|
|
9710
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: onFallback ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9711
|
+
/* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onFallback, children: "Entrar de outro jeito" }),
|
|
9712
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Agora não" })
|
|
9713
|
+
] }) : /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
|
|
9714
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
|
|
9715
|
+
] });
|
|
9716
|
+
}
|
|
9717
|
+
function PushDeniedView({ appName, onCancel }) {
|
|
9718
|
+
useEffect(() => {
|
|
9719
|
+
injectThemeStyles();
|
|
9720
|
+
injectScopedStyles(STYLE_ID$2, CSS$1);
|
|
9721
|
+
}, []);
|
|
9722
|
+
return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
|
|
9723
|
+
/* @__PURE__ */ jsxs(
|
|
9724
|
+
"svg",
|
|
9725
|
+
{
|
|
9726
|
+
className: "nfid-push-phone-icon",
|
|
9727
|
+
viewBox: "0 0 24 24",
|
|
9728
|
+
fill: "none",
|
|
9729
|
+
stroke: "currentColor",
|
|
9730
|
+
strokeWidth: "1.75",
|
|
9731
|
+
strokeLinecap: "round",
|
|
9732
|
+
strokeLinejoin: "round",
|
|
9733
|
+
"aria-hidden": "true",
|
|
9734
|
+
style: { color: "#D64545" },
|
|
9735
|
+
children: [
|
|
9736
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
9737
|
+
/* @__PURE__ */ jsx("path", { d: "M15 9l-6 6M9 9l6 6" })
|
|
9738
|
+
]
|
|
9739
|
+
}
|
|
9740
|
+
),
|
|
9741
|
+
/* @__PURE__ */ jsx("h2", { children: "Confirmação recusada" }),
|
|
9742
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
9743
|
+
"O pedido foi recusado no celular. Se não foi você, procure o suporte do ",
|
|
9744
|
+
appName,
|
|
9745
|
+
"."
|
|
9746
|
+
] }),
|
|
9747
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
|
|
9748
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
|
|
9749
|
+
] });
|
|
9750
|
+
}
|
|
9751
|
+
const CONTAINER_ID = "neofaceid-push-approval-container";
|
|
9752
|
+
function classifyAuthorizationFrame(frame) {
|
|
9753
|
+
var _a2, _b;
|
|
9754
|
+
switch (frame.status) {
|
|
9755
|
+
case "approved":
|
|
9756
|
+
case "success":
|
|
9757
|
+
return { kind: "resolve", value: { status: "approved", data: frame.data ?? {} } };
|
|
9758
|
+
case "denied":
|
|
9759
|
+
return {
|
|
9760
|
+
kind: "resolve",
|
|
9761
|
+
value: { status: "denied", reason: ((_a2 = frame.data) == null ? void 0 : _a2.reason) ?? frame.message }
|
|
9762
|
+
};
|
|
9763
|
+
case "expired":
|
|
9764
|
+
return { kind: "resolve", value: { status: "expired" } };
|
|
9765
|
+
case "failed":
|
|
9766
|
+
case "error":
|
|
9767
|
+
return {
|
|
9768
|
+
kind: "reject",
|
|
9769
|
+
error: new NeoFaceError(
|
|
9770
|
+
frame.message ?? ((_b = frame.data) == null ? void 0 : _b.message) ?? "Falha na confirmação",
|
|
9771
|
+
ErrorType.API_ERROR
|
|
9772
|
+
)
|
|
9773
|
+
};
|
|
9774
|
+
default:
|
|
9775
|
+
return { kind: "pending" };
|
|
9776
|
+
}
|
|
9777
|
+
}
|
|
9778
|
+
function PushApprovalModal({
|
|
9779
|
+
requestId,
|
|
9780
|
+
applicationToken,
|
|
9781
|
+
expiresIn,
|
|
9782
|
+
numericCode,
|
|
9783
|
+
appName,
|
|
9784
|
+
onFallback,
|
|
9785
|
+
onSettle
|
|
9786
|
+
}) {
|
|
9787
|
+
const headerAppName = appName ?? getAppName() ?? "sua aplicação";
|
|
9788
|
+
const [remaining, setRemaining] = useState(expiresIn);
|
|
9789
|
+
const [phase, setPhase] = useState({ kind: "waiting" });
|
|
9790
|
+
useEffect(() => {
|
|
9791
|
+
if (phase.kind !== "waiting") return void 0;
|
|
9792
|
+
const id = setInterval(() => {
|
|
9793
|
+
setRemaining((r) => r > 0 ? r - 1 : 0);
|
|
9794
|
+
}, 1e3);
|
|
9795
|
+
return () => clearInterval(id);
|
|
9796
|
+
}, [phase.kind]);
|
|
9797
|
+
useEffect(() => {
|
|
9798
|
+
const controller = new AbortController();
|
|
9799
|
+
let done = false;
|
|
9800
|
+
const base = getBaseUrl();
|
|
9801
|
+
const ticketUrl = `${base}/api/v1/authorization/events/${encodeURIComponent(requestId)}/ticket/`;
|
|
9802
|
+
consumeSseStream({
|
|
9803
|
+
signal: controller.signal,
|
|
9804
|
+
timeoutMs: (expiresIn + 30) * 1e3,
|
|
9805
|
+
requestTicket: async () => {
|
|
9806
|
+
var _a2;
|
|
9807
|
+
const res = await fetch(ticketUrl, {
|
|
9808
|
+
method: "POST",
|
|
9809
|
+
headers: { "Content-Type": "application/json", "X-App-Token": applicationToken }
|
|
9810
|
+
});
|
|
9811
|
+
if (!res.ok) {
|
|
9812
|
+
throw new NeoFaceError(
|
|
9813
|
+
`Falha ao abrir canal de confirmação: ${res.status}`,
|
|
9814
|
+
res.status === 401 || res.status === 403 ? ErrorType.INVALID_TOKEN : ErrorType.NETWORK
|
|
9815
|
+
);
|
|
9816
|
+
}
|
|
9817
|
+
const body = await res.json();
|
|
9818
|
+
const ticket = ((_a2 = body == null ? void 0 : body.data) == null ? void 0 : _a2.ticket) ?? (body == null ? void 0 : body.ticket);
|
|
9819
|
+
if (!ticket) {
|
|
9820
|
+
throw new NeoFaceError("Canal de confirmação sem ticket", ErrorType.API_ERROR);
|
|
9821
|
+
}
|
|
9822
|
+
return ticket;
|
|
9823
|
+
},
|
|
9824
|
+
buildUrl: (ticket) => `${base}/api/v1/authorization/events/${encodeURIComponent(requestId)}/?ticket=${encodeURIComponent(ticket)}`,
|
|
9825
|
+
classify: classifyAuthorizationFrame
|
|
9826
|
+
}).then((outcome) => {
|
|
9827
|
+
if (done) return;
|
|
9828
|
+
done = true;
|
|
9829
|
+
if (outcome.status === "denied") {
|
|
9830
|
+
setPhase({ kind: "denied" });
|
|
9831
|
+
return;
|
|
9832
|
+
}
|
|
9833
|
+
if (outcome.status === "expired") {
|
|
9834
|
+
setPhase({ kind: "expired" });
|
|
9835
|
+
return;
|
|
9836
|
+
}
|
|
9837
|
+
onSettle(outcome);
|
|
9838
|
+
}).catch(() => {
|
|
9839
|
+
if (done || controller.signal.aborted) return;
|
|
9840
|
+
done = true;
|
|
9841
|
+
setPhase({ kind: "expired" });
|
|
9842
|
+
});
|
|
9843
|
+
return () => {
|
|
9844
|
+
done = true;
|
|
9845
|
+
controller.abort();
|
|
9846
|
+
};
|
|
9847
|
+
}, [requestId]);
|
|
9848
|
+
if (phase.kind === "denied") {
|
|
9849
|
+
return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Confirmação recusada", children: /* @__PURE__ */ jsx(PushDeniedView, { appName: headerAppName, onCancel: () => onSettle({ status: "denied" }) }) });
|
|
9850
|
+
}
|
|
9851
|
+
if (phase.kind === "expired") {
|
|
9852
|
+
return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Tempo esgotado", children: /* @__PURE__ */ jsx(
|
|
9853
|
+
PushExpiredView,
|
|
9854
|
+
{
|
|
9855
|
+
onFallback: onFallback ? () => {
|
|
9856
|
+
onFallback();
|
|
9857
|
+
onSettle({ status: "expired" });
|
|
9858
|
+
} : void 0,
|
|
9859
|
+
onCancel: () => onSettle({ status: "expired" })
|
|
9860
|
+
}
|
|
9861
|
+
) });
|
|
9862
|
+
}
|
|
9863
|
+
return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Aguardando confirmação no celular", children: /* @__PURE__ */ jsx(
|
|
9864
|
+
AwaitingPushView,
|
|
9865
|
+
{
|
|
9866
|
+
appName: headerAppName,
|
|
9867
|
+
numericCode,
|
|
9868
|
+
remainingSeconds: remaining,
|
|
9869
|
+
onCancel: () => onSettle({ status: "cancelled" })
|
|
9870
|
+
}
|
|
9871
|
+
) });
|
|
9872
|
+
}
|
|
9873
|
+
function awaitPushApproval(options) {
|
|
9874
|
+
return new Promise((resolve) => {
|
|
9875
|
+
const container = document.createElement("div");
|
|
9876
|
+
container.id = CONTAINER_ID;
|
|
9877
|
+
document.body.appendChild(container);
|
|
9878
|
+
const root = createRoot(container);
|
|
9879
|
+
const settle = (outcome) => {
|
|
9880
|
+
root.unmount();
|
|
9881
|
+
if (document.body.contains(container)) document.body.removeChild(container);
|
|
9882
|
+
resolve(outcome);
|
|
9883
|
+
};
|
|
9884
|
+
root.render(/* @__PURE__ */ jsx(PushApprovalModal, { ...options, onSettle: settle }));
|
|
9885
|
+
});
|
|
9886
|
+
}
|
|
9887
|
+
function evaluateFacePosition(box, videoWidth, videoHeight) {
|
|
9888
|
+
if (!box || !videoWidth || !videoHeight) {
|
|
9889
|
+
return {
|
|
9890
|
+
faceState: "searching",
|
|
9891
|
+
instruction: "Centralize o rosto"
|
|
9892
|
+
};
|
|
9893
|
+
}
|
|
9894
|
+
const faceCenterX = box.x + box.width / 2;
|
|
9895
|
+
const faceCenterY = box.y + box.height / 2;
|
|
9896
|
+
const frameCenterX = videoWidth / 2;
|
|
9897
|
+
const frameCenterY = videoHeight / 2;
|
|
9898
|
+
const offsetX = Math.abs(faceCenterX - frameCenterX) / videoWidth;
|
|
9899
|
+
const offsetY = Math.abs(faceCenterY - frameCenterY) / videoHeight;
|
|
9900
|
+
const faceArea = box.width * box.height;
|
|
9901
|
+
const frameArea = videoWidth * videoHeight;
|
|
9902
|
+
const faceAreaRatio = faceArea / frameArea;
|
|
9903
|
+
if (faceAreaRatio < 0.08) {
|
|
9904
|
+
return {
|
|
9905
|
+
faceState: "too-far",
|
|
9906
|
+
instruction: "Aproxime-se"
|
|
9907
|
+
};
|
|
9908
|
+
}
|
|
9909
|
+
if (faceAreaRatio > 0.45) {
|
|
9910
|
+
return {
|
|
9911
|
+
faceState: "too-close",
|
|
9912
|
+
instruction: "Afaste-se"
|
|
9913
|
+
};
|
|
9914
|
+
}
|
|
9915
|
+
if (offsetX > 0.18 || offsetY > 0.18) {
|
|
9916
|
+
return {
|
|
9917
|
+
faceState: "off-center",
|
|
9918
|
+
instruction: "Centralize o rosto"
|
|
9919
|
+
};
|
|
9920
|
+
}
|
|
9921
|
+
return {
|
|
9922
|
+
faceState: "centered",
|
|
9923
|
+
instruction: "Perfeito, mantenha"
|
|
9924
|
+
};
|
|
9925
|
+
}
|
|
9926
|
+
function showCameraOvalGuideModal(initialOptions = {}) {
|
|
9927
|
+
const overlayDiv = document.createElement("div");
|
|
9928
|
+
overlayDiv.className = "neofaceid-camera-oval-guide-overlay-root";
|
|
9929
|
+
Object.assign(overlayDiv.style, {
|
|
9930
|
+
position: "fixed",
|
|
9931
|
+
top: "0",
|
|
9932
|
+
left: "0",
|
|
9933
|
+
width: "100vw",
|
|
9934
|
+
height: "100vh",
|
|
9935
|
+
backgroundColor: "rgba(0, 0, 0, 0.85)",
|
|
9936
|
+
backdropFilter: "blur(8px)",
|
|
9937
|
+
zIndex: "99999",
|
|
9938
|
+
display: "flex",
|
|
9939
|
+
flexDirection: "column",
|
|
9940
|
+
alignItems: "center",
|
|
9941
|
+
justifyContent: "center"
|
|
9942
|
+
});
|
|
9943
|
+
document.body.appendChild(overlayDiv);
|
|
9944
|
+
let root = createRoot(overlayDiv);
|
|
9945
|
+
let currentStream = initialOptions.stream || null;
|
|
9946
|
+
let currentFaceState = initialOptions.faceState || "searching";
|
|
9947
|
+
let currentInstruction = initialOptions.instruction || "Centralize o rosto";
|
|
9948
|
+
const renderModal = (stream, faceState, instruction) => {
|
|
9949
|
+
if (!root) return;
|
|
9950
|
+
root.render(
|
|
9951
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }, children: [
|
|
9952
|
+
initialOptions.onCancel && /* @__PURE__ */ jsx(
|
|
9953
|
+
"button",
|
|
9954
|
+
{
|
|
9955
|
+
onClick: () => {
|
|
9956
|
+
var _a2;
|
|
9957
|
+
handle.close();
|
|
9958
|
+
(_a2 = initialOptions.onCancel) == null ? void 0 : _a2.call(initialOptions);
|
|
9959
|
+
},
|
|
9960
|
+
style: {
|
|
9961
|
+
position: "absolute",
|
|
9962
|
+
top: "20px",
|
|
9963
|
+
right: "20px",
|
|
9964
|
+
background: "rgba(255, 255, 255, 0.2)",
|
|
9965
|
+
border: "none",
|
|
9966
|
+
color: "#fff",
|
|
9967
|
+
fontSize: "24px",
|
|
9968
|
+
width: "40px",
|
|
9969
|
+
height: "40px",
|
|
9970
|
+
borderRadius: "50%",
|
|
9971
|
+
cursor: "pointer",
|
|
9972
|
+
zIndex: 10,
|
|
9973
|
+
display: "flex",
|
|
9974
|
+
alignItems: "center",
|
|
9975
|
+
justifyContent: "center"
|
|
9976
|
+
},
|
|
9977
|
+
"aria-label": "Cancelar",
|
|
9978
|
+
children: "✕"
|
|
9979
|
+
}
|
|
9980
|
+
),
|
|
9981
|
+
/* @__PURE__ */ jsx(
|
|
9982
|
+
CameraOvalGuide,
|
|
9983
|
+
{
|
|
9984
|
+
stream,
|
|
9985
|
+
faceState,
|
|
9986
|
+
instruction
|
|
9987
|
+
}
|
|
9988
|
+
)
|
|
9989
|
+
] })
|
|
9990
|
+
);
|
|
9991
|
+
};
|
|
9992
|
+
renderModal(currentStream, currentFaceState, currentInstruction);
|
|
9993
|
+
const handle = {
|
|
9994
|
+
update: (stream, faceState, instruction) => {
|
|
9995
|
+
currentStream = stream;
|
|
9996
|
+
currentFaceState = faceState;
|
|
9997
|
+
currentInstruction = instruction;
|
|
9998
|
+
renderModal(stream, faceState, instruction);
|
|
9999
|
+
},
|
|
10000
|
+
close: () => {
|
|
10001
|
+
if (root) {
|
|
10002
|
+
root.unmount();
|
|
10003
|
+
root = null;
|
|
10004
|
+
}
|
|
10005
|
+
if (overlayDiv.parentElement) {
|
|
10006
|
+
overlayDiv.parentElement.removeChild(overlayDiv);
|
|
10007
|
+
}
|
|
10008
|
+
}
|
|
10009
|
+
};
|
|
10010
|
+
return handle;
|
|
10011
|
+
}
|
|
10012
|
+
function getHaloStyles(state) {
|
|
10013
|
+
switch (state) {
|
|
10014
|
+
case "centered":
|
|
10015
|
+
return {
|
|
10016
|
+
borderColor: "#0E9F6E",
|
|
10017
|
+
boxShadow: "0 0 0 4px rgba(14, 159, 110, 0.8), 0 0 45px rgba(14, 159, 110, 0.45)",
|
|
10018
|
+
svgStroke: "#0E9F6E",
|
|
10019
|
+
badgeBg: "rgba(14, 159, 110, 0.95)",
|
|
10020
|
+
badgeColor: "#FFFFFF"
|
|
10021
|
+
};
|
|
10022
|
+
case "too-far":
|
|
10023
|
+
case "too-close":
|
|
10024
|
+
case "off-center":
|
|
10025
|
+
return {
|
|
10026
|
+
borderColor: "#F59E0B",
|
|
10027
|
+
boxShadow: "0 0 0 4px rgba(245, 158, 11, 0.7), 0 0 35px rgba(245, 158, 11, 0.35)",
|
|
10028
|
+
svgStroke: "#F59E0B",
|
|
10029
|
+
badgeBg: "rgba(245, 158, 11, 0.95)",
|
|
10030
|
+
badgeColor: "#1F2937"
|
|
10031
|
+
};
|
|
10032
|
+
case "searching":
|
|
10033
|
+
default:
|
|
10034
|
+
return {
|
|
10035
|
+
borderColor: "rgba(156, 163, 175, 0.5)",
|
|
10036
|
+
boxShadow: "0 0 0 4px rgba(156, 163, 175, 0.3), 0 0 20px rgba(156, 163, 175, 0.15)",
|
|
10037
|
+
svgStroke: "#9CA3AF",
|
|
10038
|
+
badgeBg: "rgba(55, 65, 81, 0.9)",
|
|
10039
|
+
badgeColor: "#FFFFFF"
|
|
10040
|
+
};
|
|
10041
|
+
}
|
|
10042
|
+
}
|
|
10043
|
+
function CameraOvalGuide({
|
|
10044
|
+
stream,
|
|
10045
|
+
faceState,
|
|
10046
|
+
instruction,
|
|
10047
|
+
className = "",
|
|
10048
|
+
style = {}
|
|
10049
|
+
}) {
|
|
10050
|
+
const videoRef = useRef(null);
|
|
10051
|
+
useEffect(() => {
|
|
10052
|
+
const videoEl = videoRef.current;
|
|
10053
|
+
if (!videoEl) return;
|
|
10054
|
+
if (stream) {
|
|
10055
|
+
videoEl.srcObject = stream;
|
|
10056
|
+
videoEl.play().catch(() => {
|
|
10057
|
+
});
|
|
10058
|
+
} else {
|
|
10059
|
+
videoEl.srcObject = null;
|
|
10060
|
+
}
|
|
10061
|
+
return () => {
|
|
10062
|
+
if (stream) {
|
|
10063
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
10064
|
+
}
|
|
10065
|
+
if (videoEl) {
|
|
10066
|
+
videoEl.srcObject = null;
|
|
10067
|
+
}
|
|
10068
|
+
};
|
|
10069
|
+
}, [stream]);
|
|
10070
|
+
const halo = getHaloStyles(faceState);
|
|
10071
|
+
return /* @__PURE__ */ jsxs(
|
|
10072
|
+
"div",
|
|
10073
|
+
{
|
|
10074
|
+
className: `neofaceid-camera-oval-guide-container ${className}`,
|
|
10075
|
+
style: {
|
|
10076
|
+
display: "flex",
|
|
10077
|
+
flexDirection: "column",
|
|
10078
|
+
alignItems: "center",
|
|
10079
|
+
justifyContent: "center",
|
|
10080
|
+
position: "relative",
|
|
10081
|
+
width: "100%",
|
|
10082
|
+
maxHeight: "100%",
|
|
10083
|
+
padding: "16px",
|
|
10084
|
+
boxSizing: "border-box",
|
|
10085
|
+
...style
|
|
10086
|
+
},
|
|
10087
|
+
children: [
|
|
10088
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
10089
|
+
.neofaceid-oval-frame {
|
|
10090
|
+
width: 320px;
|
|
10091
|
+
height: 400px;
|
|
10092
|
+
border-radius: 50%;
|
|
10093
|
+
overflow: hidden;
|
|
10094
|
+
position: relative;
|
|
10095
|
+
background: #000000;
|
|
10096
|
+
transition: box-shadow 0.3s ease, border-color 0.3s ease;
|
|
10097
|
+
}
|
|
10098
|
+
|
|
10099
|
+
.neofaceid-oval-video {
|
|
10100
|
+
width: 100%;
|
|
10101
|
+
height: 100%;
|
|
10102
|
+
object-fit: cover;
|
|
10103
|
+
transform: scaleX(-1);
|
|
10104
|
+
display: block;
|
|
10105
|
+
}
|
|
10106
|
+
|
|
10107
|
+
.neofaceid-oval-svg-overlay {
|
|
10108
|
+
position: absolute;
|
|
10109
|
+
inset: 0;
|
|
10110
|
+
width: 100%;
|
|
10111
|
+
height: 100%;
|
|
10112
|
+
pointer-events: none;
|
|
10113
|
+
z-index: 2;
|
|
10114
|
+
}
|
|
10115
|
+
|
|
10116
|
+
.neofaceid-oval-instruction-badge {
|
|
10117
|
+
margin-top: 20px;
|
|
10118
|
+
padding: 10px 20px;
|
|
10119
|
+
border-radius: 20px;
|
|
10120
|
+
font-size: 15px;
|
|
10121
|
+
font-weight: 600;
|
|
10122
|
+
text-align: center;
|
|
10123
|
+
max-width: 320px;
|
|
9293
10124
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
|
|
9294
10125
|
backdrop-filter: blur(8px);
|
|
9295
10126
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
@@ -10789,7 +11620,6 @@ const CAMERA_READY_DELAY = 200;
|
|
|
10789
11620
|
const CAMERA_STABILITY_DELAY = 100;
|
|
10790
11621
|
const FACE_DETECTION_TIME = 5e3;
|
|
10791
11622
|
const DETECTION_INTERVAL = 100;
|
|
10792
|
-
const SUCCESS_DELAY = 200;
|
|
10793
11623
|
let modelsLoaded = false;
|
|
10794
11624
|
let modelsLoading = null;
|
|
10795
11625
|
async function preloadFaceDetectionModels() {
|
|
@@ -10946,14 +11776,13 @@ async function captureFaceSilently() {
|
|
|
10946
11776
|
});
|
|
10947
11777
|
});
|
|
10948
11778
|
}
|
|
10949
|
-
async function
|
|
11779
|
+
async function detectFaceWithPosition(video, stream, guideModal, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL) {
|
|
10950
11780
|
return new Promise((resolve) => {
|
|
10951
11781
|
let attempts = 0;
|
|
10952
11782
|
const maxAttempts = Math.ceil(maxTime / interval);
|
|
10953
11783
|
let timeoutId = null;
|
|
10954
11784
|
let isResolved = false;
|
|
10955
|
-
let
|
|
10956
|
-
const ATTEMPTS_BEFORE_WAITING = Math.ceil(3e3 / interval);
|
|
11785
|
+
let centeredDurationMs = 0;
|
|
10957
11786
|
const finish = (success) => {
|
|
10958
11787
|
if (isResolved) return;
|
|
10959
11788
|
isResolved = true;
|
|
@@ -10964,11 +11793,7 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
|
|
|
10964
11793
|
if (isResolved) return;
|
|
10965
11794
|
attempts++;
|
|
10966
11795
|
if (!modelsLoaded) {
|
|
10967
|
-
|
|
10968
|
-
overlay.updateStatus("waiting-for-face");
|
|
10969
|
-
overlayShowingWaiting = true;
|
|
10970
|
-
console.log("⏳ Models not loaded - waiting for face...");
|
|
10971
|
-
}
|
|
11796
|
+
guideModal.update(stream, "searching", "Carregando modelos de detecção...");
|
|
10972
11797
|
if (attempts >= maxAttempts) {
|
|
10973
11798
|
finish(false);
|
|
10974
11799
|
return;
|
|
@@ -10981,33 +11806,29 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
|
|
|
10981
11806
|
video,
|
|
10982
11807
|
new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.3 })
|
|
10983
11808
|
);
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
11809
|
+
const { faceState, instruction } = evaluateFacePosition(
|
|
11810
|
+
detection ? detection.box : null,
|
|
11811
|
+
video.videoWidth,
|
|
11812
|
+
video.videoHeight
|
|
11813
|
+
);
|
|
11814
|
+
guideModal.update(stream, faceState, instruction);
|
|
11815
|
+
if (faceState === "centered") {
|
|
11816
|
+
centeredDurationMs += interval;
|
|
11817
|
+
if (centeredDurationMs >= 1e3) {
|
|
11818
|
+
console.log("✅ Rosto mantido centralizado por 1s consecutivo");
|
|
11819
|
+
finish(true);
|
|
11820
|
+
return;
|
|
10989
11821
|
}
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
return;
|
|
10993
|
-
}
|
|
10994
|
-
if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
|
|
10995
|
-
overlay.updateStatus("waiting-for-face");
|
|
10996
|
-
overlayShowingWaiting = true;
|
|
10997
|
-
console.log("⏳ Waiting for face...");
|
|
11822
|
+
} else {
|
|
11823
|
+
centeredDurationMs = 0;
|
|
10998
11824
|
}
|
|
10999
11825
|
if (attempts >= maxAttempts) {
|
|
11000
|
-
console.warn("⏱️ Face detection timeout - no face found");
|
|
11001
11826
|
finish(false);
|
|
11002
11827
|
return;
|
|
11003
11828
|
}
|
|
11004
11829
|
timeoutId = window.setTimeout(checkFace, interval);
|
|
11005
11830
|
} catch (error) {
|
|
11006
|
-
console.error("❌
|
|
11007
|
-
if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
|
|
11008
|
-
overlay.updateStatus("waiting-for-face");
|
|
11009
|
-
overlayShowingWaiting = true;
|
|
11010
|
-
}
|
|
11831
|
+
console.error("❌ Erro na detecção facial:", error);
|
|
11011
11832
|
if (attempts >= maxAttempts) {
|
|
11012
11833
|
finish(false);
|
|
11013
11834
|
return;
|
|
@@ -11017,19 +11838,19 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
|
|
|
11017
11838
|
};
|
|
11018
11839
|
window.setTimeout(() => {
|
|
11019
11840
|
if (!isResolved) {
|
|
11020
|
-
console.warn("⏱️ Face detection safety timeout reached");
|
|
11021
11841
|
finish(false);
|
|
11022
11842
|
}
|
|
11023
11843
|
}, maxTime + 500);
|
|
11024
11844
|
checkFace();
|
|
11025
11845
|
});
|
|
11026
11846
|
}
|
|
11027
|
-
async function attemptLogin(applicationToken,
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11847
|
+
async function attemptLogin(applicationToken, fastMode = false, isRetry = false, onCancel) {
|
|
11848
|
+
const guideModal = showCameraOvalGuideModal({
|
|
11849
|
+
stream: null,
|
|
11850
|
+
faceState: "searching",
|
|
11851
|
+
instruction: "Iniciando câmera...",
|
|
11852
|
+
onCancel
|
|
11853
|
+
});
|
|
11033
11854
|
const video = document.createElement("video");
|
|
11034
11855
|
video.style.position = "fixed";
|
|
11035
11856
|
video.style.top = "-9999px";
|
|
@@ -11060,6 +11881,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
11060
11881
|
});
|
|
11061
11882
|
}
|
|
11062
11883
|
video.srcObject = stream;
|
|
11884
|
+
guideModal.update(stream, "searching", "Centralize o rosto");
|
|
11063
11885
|
await new Promise((resolve) => {
|
|
11064
11886
|
if (video.readyState >= 1) {
|
|
11065
11887
|
resolve();
|
|
@@ -11088,18 +11910,19 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
11088
11910
|
checkSize();
|
|
11089
11911
|
}
|
|
11090
11912
|
});
|
|
11091
|
-
overlay.updateStatus("detecting");
|
|
11092
11913
|
await new Promise((resolve) => setTimeout(resolve, CAMERA_STABILITY_DELAY));
|
|
11093
11914
|
let faceDetected = true;
|
|
11094
11915
|
if (!fastMode) {
|
|
11095
|
-
faceDetected = await
|
|
11916
|
+
faceDetected = await detectFaceWithPosition(
|
|
11096
11917
|
video,
|
|
11918
|
+
stream,
|
|
11919
|
+
guideModal,
|
|
11097
11920
|
FACE_DETECTION_TIME,
|
|
11098
|
-
DETECTION_INTERVAL
|
|
11099
|
-
overlay
|
|
11921
|
+
DETECTION_INTERVAL
|
|
11100
11922
|
);
|
|
11101
11923
|
}
|
|
11102
11924
|
if (!faceDetected) {
|
|
11925
|
+
guideModal.close();
|
|
11103
11926
|
if (stream) {
|
|
11104
11927
|
stream.getTracks().forEach((track) => track.stop());
|
|
11105
11928
|
}
|
|
@@ -11107,16 +11930,17 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
11107
11930
|
video.parentElement.removeChild(video);
|
|
11108
11931
|
}
|
|
11109
11932
|
throw new NeoFaceError(
|
|
11110
|
-
"Nenhum rosto detectado. Por favor,
|
|
11933
|
+
"Nenhum rosto centralizado detectado. Por favor, tente novamente.",
|
|
11111
11934
|
ErrorType.VALIDATION_ERROR
|
|
11112
11935
|
);
|
|
11113
11936
|
}
|
|
11114
|
-
|
|
11937
|
+
guideModal.update(stream, "centered", "Capturando imagem...");
|
|
11115
11938
|
const canvas = document.createElement("canvas");
|
|
11116
11939
|
canvas.width = video.videoWidth;
|
|
11117
11940
|
canvas.height = video.videoHeight;
|
|
11118
11941
|
const ctx = canvas.getContext("2d");
|
|
11119
11942
|
if (!ctx) {
|
|
11943
|
+
guideModal.close();
|
|
11120
11944
|
if (stream) {
|
|
11121
11945
|
stream.getTracks().forEach((track) => track.stop());
|
|
11122
11946
|
}
|
|
@@ -11126,12 +11950,6 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
11126
11950
|
throw new NeoFaceError("Erro ao capturar imagem", ErrorType.CAPTURE_ERROR);
|
|
11127
11951
|
}
|
|
11128
11952
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
11129
|
-
if (stream) {
|
|
11130
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
11131
|
-
}
|
|
11132
|
-
if (video.parentElement) {
|
|
11133
|
-
video.parentElement.removeChild(video);
|
|
11134
|
-
}
|
|
11135
11953
|
const imageBlob = await new Promise((resolve, reject) => {
|
|
11136
11954
|
canvas.toBlob(
|
|
11137
11955
|
(blob) => {
|
|
@@ -11146,13 +11964,21 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
11146
11964
|
0.85
|
|
11147
11965
|
);
|
|
11148
11966
|
});
|
|
11149
|
-
|
|
11967
|
+
guideModal.update(stream, "centered", "Verificando com o servidor...");
|
|
11150
11968
|
const result = await loginWithBiometric(imageBlob, applicationToken);
|
|
11969
|
+
guideModal.close();
|
|
11970
|
+
if (stream) {
|
|
11971
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
11972
|
+
}
|
|
11973
|
+
if (video.parentElement) {
|
|
11974
|
+
video.parentElement.removeChild(video);
|
|
11975
|
+
}
|
|
11151
11976
|
if (!result.success) {
|
|
11152
11977
|
throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
|
|
11153
11978
|
}
|
|
11154
11979
|
return result;
|
|
11155
11980
|
} catch (error) {
|
|
11981
|
+
guideModal.close();
|
|
11156
11982
|
if (stream) {
|
|
11157
11983
|
stream.getTracks().forEach((track) => track.stop());
|
|
11158
11984
|
}
|
|
@@ -11177,7 +12003,6 @@ async function executeBiometricLoginFlow(options) {
|
|
|
11177
12003
|
onCancel,
|
|
11178
12004
|
fastMode = false
|
|
11179
12005
|
} = options;
|
|
11180
|
-
const overlay = new BiometricStatusOverlay();
|
|
11181
12006
|
const fallbackPrompt = new FallbackPrompt();
|
|
11182
12007
|
let attempts = 0;
|
|
11183
12008
|
let lastError;
|
|
@@ -11185,34 +12010,26 @@ async function executeBiometricLoginFlow(options) {
|
|
|
11185
12010
|
preloadFaceDetectionModels().catch(() => {
|
|
11186
12011
|
});
|
|
11187
12012
|
}
|
|
11188
|
-
overlay.show("preparing");
|
|
11189
12013
|
const tryLogin = async () => {
|
|
11190
12014
|
try {
|
|
11191
12015
|
attempts++;
|
|
11192
|
-
const result = await attemptLogin(applicationToken,
|
|
11193
|
-
|
|
11194
|
-
setTimeout(() => {
|
|
11195
|
-
overlay.close();
|
|
11196
|
-
onSuccess(result);
|
|
11197
|
-
}, SUCCESS_DELAY);
|
|
12016
|
+
const result = await attemptLogin(applicationToken, fastMode, attempts > 1, onCancel);
|
|
12017
|
+
onSuccess(result);
|
|
11198
12018
|
} catch (error) {
|
|
11199
12019
|
lastError = error instanceof NeoFaceError ? error : new NeoFaceError(
|
|
11200
12020
|
error instanceof Error ? error.message : "Erro desconhecido",
|
|
11201
12021
|
ErrorType.UNKNOWN
|
|
11202
12022
|
);
|
|
11203
|
-
overlay.updateStatus("error");
|
|
11204
12023
|
if (attempts < MAX_ATTEMPTS$1) {
|
|
11205
12024
|
setTimeout(() => {
|
|
11206
12025
|
tryLogin();
|
|
11207
12026
|
}, RETRY_DELAY);
|
|
11208
12027
|
} else {
|
|
11209
12028
|
setTimeout(() => {
|
|
11210
|
-
overlay.close();
|
|
11211
12029
|
fallbackPrompt.show(
|
|
11212
12030
|
lastError,
|
|
11213
12031
|
() => {
|
|
11214
12032
|
attempts = 0;
|
|
11215
|
-
overlay.show("preparing");
|
|
11216
12033
|
setTimeout(() => tryLogin(), RETRY_DELAY);
|
|
11217
12034
|
},
|
|
11218
12035
|
() => {
|
|
@@ -11234,9 +12051,7 @@ async function executeBiometricLoginFlow(options) {
|
|
|
11234
12051
|
}
|
|
11235
12052
|
}
|
|
11236
12053
|
};
|
|
11237
|
-
|
|
11238
|
-
tryLogin();
|
|
11239
|
-
}
|
|
12054
|
+
tryLogin();
|
|
11240
12055
|
}
|
|
11241
12056
|
const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
11242
12057
|
__proto__: null,
|
|
@@ -11245,6 +12060,25 @@ const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
|
|
|
11245
12060
|
preloadFaceDetectionModels
|
|
11246
12061
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
11247
12062
|
const CONSENT_DENIED_MESSAGE$1 = "Consentimento recusado pelo usuário.";
|
|
12063
|
+
function openPasswordFallback(options) {
|
|
12064
|
+
if (!getAutoFallback().toPassword) {
|
|
12065
|
+
options.onError(
|
|
12066
|
+
new NeoFaceError(
|
|
12067
|
+
"Biometria não reconhecida e a queda automática para senha está desligada.",
|
|
12068
|
+
ErrorType.LOGIN_FAILED
|
|
12069
|
+
)
|
|
12070
|
+
);
|
|
12071
|
+
return;
|
|
12072
|
+
}
|
|
12073
|
+
const fallbackOptions = {
|
|
12074
|
+
applicationToken: options.applicationToken,
|
|
12075
|
+
onSuccess: options.onSuccess,
|
|
12076
|
+
onError: options.onError,
|
|
12077
|
+
title: "Login Alternativo",
|
|
12078
|
+
subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
|
|
12079
|
+
};
|
|
12080
|
+
new EmailPasswordModal(fallbackOptions).open();
|
|
12081
|
+
}
|
|
11248
12082
|
async function startFaceLogin(options) {
|
|
11249
12083
|
const consentAccepted = await requestConsent({
|
|
11250
12084
|
appName: options.appName,
|
|
@@ -11259,17 +12093,7 @@ async function startFaceLogin(options) {
|
|
|
11259
12093
|
applicationToken: options.applicationToken,
|
|
11260
12094
|
onSuccess: options.onSuccess,
|
|
11261
12095
|
onError: options.onError,
|
|
11262
|
-
onFallbackRequest: options.onFallbackRequest || (() =>
|
|
11263
|
-
const fallbackOptions = {
|
|
11264
|
-
applicationToken: options.applicationToken,
|
|
11265
|
-
onSuccess: options.onSuccess,
|
|
11266
|
-
onError: options.onError,
|
|
11267
|
-
title: "Login Alternativo",
|
|
11268
|
-
subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
|
|
11269
|
-
};
|
|
11270
|
-
const modal = new EmailPasswordModal(fallbackOptions);
|
|
11271
|
-
modal.open();
|
|
11272
|
-
}),
|
|
12096
|
+
onFallbackRequest: options.onFallbackRequest || (() => openPasswordFallback(options)),
|
|
11273
12097
|
onCancel: options.onCancel
|
|
11274
12098
|
});
|
|
11275
12099
|
} catch (error) {
|
|
@@ -12438,15 +13262,23 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
|
|
|
12438
13262
|
resolve();
|
|
12439
13263
|
};
|
|
12440
13264
|
});
|
|
13265
|
+
const guideModal = showCameraOvalGuideModal({
|
|
13266
|
+
stream,
|
|
13267
|
+
faceState: "centered",
|
|
13268
|
+
instruction: PHOTO_INSTRUCTIONS[0]
|
|
13269
|
+
});
|
|
12441
13270
|
const canvas = document.createElement("canvas");
|
|
12442
13271
|
canvas.width = video.videoWidth;
|
|
12443
13272
|
canvas.height = video.videoHeight;
|
|
12444
13273
|
const ctx = canvas.getContext("2d");
|
|
12445
13274
|
if (!ctx) {
|
|
13275
|
+
guideModal.close();
|
|
13276
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
12446
13277
|
throw new NeoFaceError("Failed to get canvas context", ErrorType.CAPTURE_ERROR);
|
|
12447
13278
|
}
|
|
12448
13279
|
for (let i = 0; i < TOTAL_PHOTOS; i++) {
|
|
12449
13280
|
const instruction = PHOTO_INSTRUCTIONS[i];
|
|
13281
|
+
guideModal.update(stream, "centered", instruction);
|
|
12450
13282
|
if (options == null ? void 0 : options.onProgress) {
|
|
12451
13283
|
options.onProgress(i + 1, TOTAL_PHOTOS, instruction);
|
|
12452
13284
|
}
|
|
@@ -12472,6 +13304,7 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
|
|
|
12472
13304
|
options.onPhotoTaken(i + 1, blob);
|
|
12473
13305
|
}
|
|
12474
13306
|
}
|
|
13307
|
+
guideModal.close();
|
|
12475
13308
|
stream.getTracks().forEach((track) => track.stop());
|
|
12476
13309
|
const lastPhoto = capturedPhotos[TOTAL_PHOTOS - 1];
|
|
12477
13310
|
const sessionData = {
|
|
@@ -12519,110 +13352,6 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
|
|
|
12519
13352
|
throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
|
|
12520
13353
|
}
|
|
12521
13354
|
};
|
|
12522
|
-
function Sheet({
|
|
12523
|
-
open,
|
|
12524
|
-
onDismiss,
|
|
12525
|
-
dismissOnOverlay = true,
|
|
12526
|
-
ariaLabel,
|
|
12527
|
-
ariaLabelledBy,
|
|
12528
|
-
children,
|
|
12529
|
-
className,
|
|
12530
|
-
style
|
|
12531
|
-
}) {
|
|
12532
|
-
useEffect(() => {
|
|
12533
|
-
injectThemeStyles();
|
|
12534
|
-
}, []);
|
|
12535
|
-
if (!open) return null;
|
|
12536
|
-
const handleOverlay = () => {
|
|
12537
|
-
if (dismissOnOverlay && onDismiss) onDismiss();
|
|
12538
|
-
};
|
|
12539
|
-
return /* @__PURE__ */ jsx(
|
|
12540
|
-
"div",
|
|
12541
|
-
{
|
|
12542
|
-
className: "neofaceid-root",
|
|
12543
|
-
role: "presentation",
|
|
12544
|
-
style: {
|
|
12545
|
-
position: "fixed",
|
|
12546
|
-
inset: 0,
|
|
12547
|
-
zIndex: 10001,
|
|
12548
|
-
display: "flex",
|
|
12549
|
-
alignItems: "center",
|
|
12550
|
-
justifyContent: "center",
|
|
12551
|
-
padding: 20,
|
|
12552
|
-
background: `var(${CSS_VAR.overlay})`,
|
|
12553
|
-
backdropFilter: "blur(3px)",
|
|
12554
|
-
WebkitBackdropFilter: "blur(3px)"
|
|
12555
|
-
},
|
|
12556
|
-
onClick: handleOverlay,
|
|
12557
|
-
children: /* @__PURE__ */ jsx(
|
|
12558
|
-
"div",
|
|
12559
|
-
{
|
|
12560
|
-
role: "dialog",
|
|
12561
|
-
"aria-modal": "true",
|
|
12562
|
-
"aria-label": ariaLabel,
|
|
12563
|
-
"aria-labelledby": ariaLabelledBy,
|
|
12564
|
-
className,
|
|
12565
|
-
onClick: (e) => e.stopPropagation(),
|
|
12566
|
-
style: {
|
|
12567
|
-
background: `var(${CSS_VAR.surface})`,
|
|
12568
|
-
color: `var(${CSS_VAR.text})`,
|
|
12569
|
-
borderRadius: `var(${CSS_VAR.radius}, 16px)`,
|
|
12570
|
-
padding: 28,
|
|
12571
|
-
maxWidth: 440,
|
|
12572
|
-
width: "100%",
|
|
12573
|
-
boxShadow: "0 1px 3px rgba(10,19,32,.08)",
|
|
12574
|
-
boxSizing: "border-box",
|
|
12575
|
-
...style
|
|
12576
|
-
},
|
|
12577
|
-
children
|
|
12578
|
-
}
|
|
12579
|
-
)
|
|
12580
|
-
}
|
|
12581
|
-
);
|
|
12582
|
-
}
|
|
12583
|
-
const BASE_STYLE = {
|
|
12584
|
-
minHeight: 44,
|
|
12585
|
-
minWidth: 44,
|
|
12586
|
-
padding: "12px 20px",
|
|
12587
|
-
borderRadius: 16,
|
|
12588
|
-
border: "none",
|
|
12589
|
-
fontSize: 15,
|
|
12590
|
-
fontWeight: 600,
|
|
12591
|
-
fontFamily: "inherit",
|
|
12592
|
-
cursor: "pointer",
|
|
12593
|
-
transition: "background 120ms ease-out",
|
|
12594
|
-
display: "inline-flex",
|
|
12595
|
-
alignItems: "center",
|
|
12596
|
-
justifyContent: "center",
|
|
12597
|
-
gap: 8
|
|
12598
|
-
};
|
|
12599
|
-
const Button = forwardRef(
|
|
12600
|
-
({ variant = "primary", style, type = "button", ...rest }, ref) => {
|
|
12601
|
-
const variantStyle = variant === "primary" ? {
|
|
12602
|
-
background: `var(${CSS_VAR.action})`,
|
|
12603
|
-
color: `var(${CSS_VAR.actionText})`
|
|
12604
|
-
} : variant === "secondary" ? {
|
|
12605
|
-
background: `var(${CSS_VAR.surfaceMuted})`,
|
|
12606
|
-
color: `var(${CSS_VAR.text})`
|
|
12607
|
-
} : {
|
|
12608
|
-
background: "transparent",
|
|
12609
|
-
color: `var(${CSS_VAR.textMuted})`
|
|
12610
|
-
};
|
|
12611
|
-
return (
|
|
12612
|
-
// eslint-disable-next-line react/button-has-type
|
|
12613
|
-
/* @__PURE__ */ jsx(
|
|
12614
|
-
"button",
|
|
12615
|
-
{
|
|
12616
|
-
ref,
|
|
12617
|
-
type,
|
|
12618
|
-
...rest,
|
|
12619
|
-
style: { ...BASE_STYLE, ...variantStyle, ...style }
|
|
12620
|
-
}
|
|
12621
|
-
)
|
|
12622
|
-
);
|
|
12623
|
-
}
|
|
12624
|
-
);
|
|
12625
|
-
Button.displayName = "Button";
|
|
12626
13355
|
function FocusFrame({
|
|
12627
13356
|
size = 96,
|
|
12628
13357
|
color,
|
|
@@ -12850,32 +13579,6 @@ function pointsFor(g) {
|
|
|
12850
13579
|
return ["24 24", "24 24", "24 24"];
|
|
12851
13580
|
}
|
|
12852
13581
|
}
|
|
12853
|
-
function StatusPill({ tone, children, icon, className }) {
|
|
12854
|
-
const palette = SEMANTIC[tone];
|
|
12855
|
-
return /* @__PURE__ */ jsxs(
|
|
12856
|
-
"span",
|
|
12857
|
-
{
|
|
12858
|
-
className,
|
|
12859
|
-
style: {
|
|
12860
|
-
display: "inline-flex",
|
|
12861
|
-
alignItems: "center",
|
|
12862
|
-
gap: 6,
|
|
12863
|
-
padding: "4px 10px",
|
|
12864
|
-
borderRadius: 9999,
|
|
12865
|
-
background: palette.bg,
|
|
12866
|
-
color: palette.fg,
|
|
12867
|
-
fontSize: 13,
|
|
12868
|
-
fontWeight: 600,
|
|
12869
|
-
fontFamily: "inherit",
|
|
12870
|
-
lineHeight: 1.3
|
|
12871
|
-
},
|
|
12872
|
-
children: [
|
|
12873
|
-
icon,
|
|
12874
|
-
children
|
|
12875
|
-
]
|
|
12876
|
-
}
|
|
12877
|
-
);
|
|
12878
|
-
}
|
|
12879
13582
|
const DEFAULT = {
|
|
12880
13583
|
title: "Não deu certo desta vez",
|
|
12881
13584
|
hint: "Tente se posicionar em um ambiente mais estável.",
|
|
@@ -14646,15 +15349,18 @@ export {
|
|
|
14646
15349
|
VERSION,
|
|
14647
15350
|
authorize,
|
|
14648
15351
|
authorizeOperation,
|
|
15352
|
+
awaitPushApproval,
|
|
14649
15353
|
checkUserExistence,
|
|
14650
15354
|
clearConsentTrail,
|
|
14651
15355
|
completeOnboarding,
|
|
14652
15356
|
completeOnboardingWithData,
|
|
14653
15357
|
confirmPasswordReset,
|
|
15358
|
+
consumeSseStream,
|
|
14654
15359
|
detectBiometricType,
|
|
14655
15360
|
getAccent,
|
|
14656
15361
|
getAppName,
|
|
14657
15362
|
getApplicationToken,
|
|
15363
|
+
getAutoFallback,
|
|
14658
15364
|
getBaseUrl,
|
|
14659
15365
|
getCachedCaptureSession,
|
|
14660
15366
|
getConfig,
|
|
@@ -14679,10 +15385,12 @@ export {
|
|
|
14679
15385
|
recognizeBiometric,
|
|
14680
15386
|
recognizeByPurpose,
|
|
14681
15387
|
recordConsent,
|
|
15388
|
+
refreshSession,
|
|
14682
15389
|
registerBiometric,
|
|
14683
15390
|
registerPersonWithBiometric,
|
|
14684
15391
|
registerPersonWithoutFace,
|
|
14685
15392
|
requestConsent,
|
|
15393
|
+
requestDeviceEnrollmentToken,
|
|
14686
15394
|
requestLivenessChallenge,
|
|
14687
15395
|
requestLivenessChallengeWithSession,
|
|
14688
15396
|
requestPasswordReset,
|