@neofaceid/web-sdk 1.29.0 → 1.31.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.
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import React, { useRef, useState, useCallback, useReducer, useEffect } from "react";
4
+ import React, { useRef, useState, useCallback, useReducer, useEffect, forwardRef } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
7
7
  import * as faceapi from "face-api.js";
@@ -4342,21 +4342,218 @@ object({
4342
4342
  task_id: string().optional(),
4343
4343
  status: string().optional()
4344
4344
  });
4345
+ const BLUE = {
4346
+ 400: "#00B0F8",
4347
+ 500: "#0091F6",
4348
+ 700: "#0059C4"
4349
+ };
4350
+ const GOLD = {
4351
+ 500: "#F1BE00"
4352
+ };
4353
+ const INK = {
4354
+ 25: "#F7F9FC",
4355
+ 50: "#F1F5FA",
4356
+ 100: "#E6ECF4",
4357
+ 300: "#AFBDCE",
4358
+ 400: "#8496AD",
4359
+ 500: "#617489",
4360
+ 600: "#48586B",
4361
+ 800: "#1E2B3C",
4362
+ 900: "#111C2B",
4363
+ 950: "#0A1320"
4364
+ };
4365
+ const LIGHT_TOKENS = {
4366
+ ground: INK[25],
4367
+ surface: "#FFFFFF",
4368
+ surfaceMuted: INK[50],
4369
+ line: INK[100],
4370
+ text: INK[950],
4371
+ textMuted: INK[600],
4372
+ textSubtle: INK[500],
4373
+ action: BLUE[700],
4374
+ actionText: "#FFFFFF",
4375
+ accent: GOLD[500],
4376
+ overlay: "rgba(10,19,32,.55)",
4377
+ focusRing: BLUE[700]
4378
+ };
4379
+ const DARK_TOKENS = {
4380
+ ground: INK[950],
4381
+ surface: INK[900],
4382
+ surfaceMuted: INK[800],
4383
+ line: "#22334D",
4384
+ text: "#EEF3F9",
4385
+ textMuted: INK[300],
4386
+ textSubtle: INK[400],
4387
+ action: BLUE[500],
4388
+ actionText: "#FFFFFF",
4389
+ accent: GOLD[500],
4390
+ overlay: "rgba(10,19,32,.72)",
4391
+ focusRing: BLUE[400]
4392
+ };
4393
+ function contrastRatio(hexA, hexB) {
4394
+ const lumA = luminance(hexA);
4395
+ const lumB = luminance(hexB);
4396
+ const [hi, lo] = lumA > lumB ? [lumA, lumB] : [lumB, lumA];
4397
+ return (hi + 0.05) / (lo + 0.05);
4398
+ }
4399
+ function luminance(hex) {
4400
+ const [r, g, b] = parseHex(hex).map((v) => {
4401
+ const s = v / 255;
4402
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
4403
+ });
4404
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
4405
+ }
4406
+ function parseHex(hex) {
4407
+ let h = hex.trim().replace("#", "");
4408
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
4409
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) return [0, 0, 0];
4410
+ return [
4411
+ parseInt(h.slice(0, 2), 16),
4412
+ parseInt(h.slice(2, 4), 16),
4413
+ parseInt(h.slice(4, 6), 16)
4414
+ ];
4415
+ }
4416
+ const ALLOWED_RADIUS = [8, 16, 24];
4417
+ function resolveTheme(options = {}) {
4418
+ const mode = resolveMode(options.theme);
4419
+ const tokens = mode === "dark" ? { ...DARK_TOKENS } : { ...LIGHT_TOKENS };
4420
+ let radius = 16;
4421
+ if (options.radius !== void 0) {
4422
+ if (ALLOWED_RADIUS.includes(options.radius)) {
4423
+ radius = options.radius;
4424
+ } else {
4425
+ console.warn(
4426
+ `[NeoFaceID SDK] radius ${options.radius} inválido — permitidos: 8, 16, 24. Usando 16.`
4427
+ );
4428
+ }
4429
+ }
4430
+ let accent = tokens.action;
4431
+ let accentFellBack = false;
4432
+ if (options.accent) {
4433
+ const ratio = contrastRatio(options.accent, "#FFFFFF");
4434
+ if (ratio >= 4.5) {
4435
+ accent = options.accent;
4436
+ } else {
4437
+ accentFellBack = true;
4438
+ console.warn(
4439
+ `[NeoFaceID SDK] accent ${options.accent} reprova AA vs branco (${ratio.toFixed(2)}:1). Usando ${BLUE[700]}.`
4440
+ );
4441
+ accent = BLUE[700];
4442
+ }
4443
+ }
4444
+ return { mode, tokens: { ...tokens, action: accent }, radius, accent, accentFellBack };
4445
+ }
4446
+ function resolveMode(theme) {
4447
+ if (theme === "dark") return "dark";
4448
+ if (theme === "light") return "light";
4449
+ if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
4450
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) return "dark";
4451
+ }
4452
+ return "light";
4453
+ }
4454
+ const CSS_VAR = {
4455
+ ground: "--nfid-ground",
4456
+ surface: "--nfid-surface",
4457
+ surfaceMuted: "--nfid-surface-muted",
4458
+ line: "--nfid-line",
4459
+ text: "--nfid-text",
4460
+ textMuted: "--nfid-text-muted",
4461
+ textSubtle: "--nfid-text-subtle",
4462
+ action: "--nfid-action",
4463
+ actionText: "--nfid-action-text",
4464
+ accent: "--nfid-accent",
4465
+ overlay: "--nfid-overlay",
4466
+ focusRing: "--nfid-focus-ring",
4467
+ radius: "--nfid-radius"
4468
+ };
4469
+ const INJECTED_STYLE_ID = "neofaceid-focus-frame-tokens";
4470
+ function injectThemeStyles$1(options = {}) {
4471
+ const resolved = resolveTheme(options);
4472
+ if (typeof document === "undefined") return resolved;
4473
+ const css = buildRootCss(resolved);
4474
+ const existing = document.getElementById(INJECTED_STYLE_ID);
4475
+ if (existing) {
4476
+ if (existing.textContent !== css) existing.textContent = css;
4477
+ return resolved;
4478
+ }
4479
+ const style = document.createElement("style");
4480
+ style.id = INJECTED_STYLE_ID;
4481
+ style.textContent = css;
4482
+ document.head.appendChild(style);
4483
+ return resolved;
4484
+ }
4485
+ function buildRootCss(theme) {
4486
+ const t = theme.tokens;
4487
+ return `.neofaceid-root {
4488
+ ${CSS_VAR.ground}: ${t.ground};
4489
+ ${CSS_VAR.surface}: ${t.surface};
4490
+ ${CSS_VAR.surfaceMuted}: ${t.surfaceMuted};
4491
+ ${CSS_VAR.line}: ${t.line};
4492
+ ${CSS_VAR.text}: ${t.text};
4493
+ ${CSS_VAR.textMuted}: ${t.textMuted};
4494
+ ${CSS_VAR.textSubtle}: ${t.textSubtle};
4495
+ ${CSS_VAR.action}: ${t.action};
4496
+ ${CSS_VAR.actionText}: ${t.actionText};
4497
+ ${CSS_VAR.accent}: ${t.accent};
4498
+ ${CSS_VAR.overlay}: ${t.overlay};
4499
+ ${CSS_VAR.focusRing}: ${t.focusRing};
4500
+ ${CSS_VAR.radius}: ${theme.radius}px;
4501
+ color: var(${CSS_VAR.text});
4502
+ font-family: inherit;
4503
+ }
4504
+ .neofaceid-root :focus-visible {
4505
+ outline: 2px solid var(${CSS_VAR.focusRing});
4506
+ outline-offset: 2px;
4507
+ }
4508
+ @media (prefers-reduced-motion: reduce) {
4509
+ .neofaceid-root,
4510
+ .neofaceid-root * {
4511
+ animation-duration: 0.001ms !important;
4512
+ animation-iteration-count: 1 !important;
4513
+ transition-duration: 0.001ms !important;
4514
+ scroll-behavior: auto !important;
4515
+ }
4516
+ }`;
4517
+ }
4518
+ function injectScopedStyles$1(id, css) {
4519
+ if (typeof document === "undefined") return;
4520
+ const existing = document.getElementById(id);
4521
+ if (existing) {
4522
+ if (existing.textContent !== css) existing.textContent = css;
4523
+ return;
4524
+ }
4525
+ const style = document.createElement("style");
4526
+ style.id = id;
4527
+ style.textContent = css;
4528
+ document.head.appendChild(style);
4529
+ }
4345
4530
  const __vite_import_meta_env__ = {};
4346
4531
  const ENVIRONMENT_URLS = {
4347
4532
  development: "http://localhost:8000",
4348
4533
  sandbox: "https://sandbox-core.neofaceid.com",
4349
4534
  production: "https://core.neofaceid.com.br"
4350
4535
  };
4351
- let globalConfig = {
4352
- baseUrl: ENVIRONMENT_URLS.sandbox,
4353
- // Default para sandbox
4354
- applicationToken: null,
4355
- environment: "sandbox",
4356
- initialized: false
4357
- };
4536
+ const DEFAULT_LOCALE = "pt-BR";
4537
+ const DEFAULT_RADIUS = 16;
4538
+ const DEFAULT_THEME = "light";
4539
+ const ALLOWED_RADII = [8, 16, 24];
4540
+ function makeDefaultConfig() {
4541
+ return {
4542
+ baseUrl: ENVIRONMENT_URLS.sandbox,
4543
+ applicationToken: null,
4544
+ environment: "sandbox",
4545
+ initialized: false,
4546
+ appName: null,
4547
+ accent: null,
4548
+ radius: DEFAULT_RADIUS,
4549
+ theme: DEFAULT_THEME,
4550
+ locale: DEFAULT_LOCALE,
4551
+ resolvedTheme: null
4552
+ };
4553
+ }
4554
+ let globalConfig = makeDefaultConfig();
4358
4555
  function init(options = {}) {
4359
- const { environment, baseUrl, applicationToken } = options;
4556
+ const { environment, baseUrl, applicationToken, appName, accent, radius, theme, locale } = options;
4360
4557
  if (baseUrl) {
4361
4558
  globalConfig.baseUrl = baseUrl;
4362
4559
  globalConfig.environment = "custom";
@@ -4383,9 +4580,40 @@ function init(options = {}) {
4383
4580
  "[NeoFaceID SDK] applicationToken vazio em init() — 401 esperado em chamadas autenticadas."
4384
4581
  );
4385
4582
  }
4583
+ if (appName && appName.trim()) {
4584
+ globalConfig.appName = appName.trim();
4585
+ } else if (appName !== void 0) {
4586
+ console.warn(
4587
+ '[NeoFaceID SDK] init({ appName }) vazio — modais cairão para o fallback "sua aplicação".'
4588
+ );
4589
+ }
4590
+ if (radius !== void 0) {
4591
+ if (ALLOWED_RADII.includes(radius)) {
4592
+ globalConfig.radius = radius;
4593
+ } else {
4594
+ console.warn(
4595
+ `[NeoFaceID SDK] radius ${radius} inválido em init() — permitidos: 8, 16, 24. Usando 16.`
4596
+ );
4597
+ globalConfig.radius = DEFAULT_RADIUS;
4598
+ }
4599
+ }
4600
+ if (theme) globalConfig.theme = theme;
4601
+ if (locale) globalConfig.locale = locale;
4602
+ if (accent !== void 0) globalConfig.accent = accent;
4603
+ const resolved = resolveTheme({
4604
+ accent: globalConfig.accent ?? void 0,
4605
+ radius: globalConfig.radius,
4606
+ theme: globalConfig.theme
4607
+ });
4608
+ globalConfig.resolvedTheme = resolved;
4609
+ injectThemeStyles$1({
4610
+ accent: globalConfig.accent ?? void 0,
4611
+ radius: globalConfig.radius,
4612
+ theme: globalConfig.theme
4613
+ });
4386
4614
  globalConfig.initialized = true;
4387
4615
  console.log(
4388
- `[NeoFaceID SDK] Inicializado - Ambiente: ${globalConfig.environment}, URL: ${globalConfig.baseUrl}`
4616
+ `[NeoFaceID SDK] Inicializado — Ambiente: ${globalConfig.environment}, URL: ${globalConfig.baseUrl}, appName: ${globalConfig.appName ?? "(vazio)"}, theme: ${resolved.mode}, radius: ${globalConfig.radius}`
4389
4617
  );
4390
4618
  }
4391
4619
  function getBaseUrl() {
@@ -4406,6 +4634,25 @@ function isInitialized() {
4406
4634
  function getConfig() {
4407
4635
  return { ...globalConfig };
4408
4636
  }
4637
+ function getAppName() {
4638
+ return globalConfig.appName;
4639
+ }
4640
+ function getAccent() {
4641
+ return globalConfig.accent;
4642
+ }
4643
+ function getRadius() {
4644
+ return globalConfig.radius;
4645
+ }
4646
+ function getTheme() {
4647
+ return globalConfig.theme;
4648
+ }
4649
+ function getResolvedThemeMode() {
4650
+ var _a2;
4651
+ return ((_a2 = globalConfig.resolvedTheme) == null ? void 0 : _a2.mode) ?? (globalConfig.theme === "dark" ? "dark" : "light");
4652
+ }
4653
+ function getLocale() {
4654
+ return globalConfig.locale;
4655
+ }
4409
4656
  const getApiBaseUrl = () => getBaseUrl();
4410
4657
  const REQUEST_TIMEOUT = 1e4;
4411
4658
  const ensureSecureContext = () => {
@@ -5897,251 +6144,75 @@ const identifyPersonAsync = async (image, applicationToken, options = {}) => {
5897
6144
  return { personFound: false };
5898
6145
  }
5899
6146
  await new Promise((resolve) => setTimeout(resolve, interval));
5900
- }
5901
- throw new NeoFaceError("Timed out waiting for identification", ErrorType.NETWORK_ERROR);
5902
- };
5903
- async function requestPasswordReset(email2, applicationToken) {
5904
- const baseUrl = getBaseUrl();
5905
- const response = await fetch(`${baseUrl}/api/v1/user/password/reset/request/`, {
5906
- method: "POST",
5907
- headers: {
5908
- "Content-Type": "application/json",
5909
- "X-App-Token": applicationToken
5910
- },
5911
- body: JSON.stringify({ email: email2 })
5912
- });
5913
- if (!response.ok) {
5914
- const errorData = await response.json();
5915
- throw new NeoFaceError(
5916
- errorData.message || "Erro ao solicitar recuperação de senha",
5917
- ErrorType.API_ERROR
5918
- );
5919
- }
5920
- return response.json();
5921
- }
5922
- async function confirmPasswordReset(token, new_password) {
5923
- const baseUrl = getBaseUrl();
5924
- const response = await fetch(`${baseUrl}/api/v1/user/password/reset/confirm/`, {
5925
- method: "POST",
5926
- headers: {
5927
- "Content-Type": "application/json"
5928
- },
5929
- body: JSON.stringify({ token, new_password })
5930
- });
5931
- if (!response.ok) {
5932
- const errorData = await response.json();
5933
- throw new NeoFaceError(
5934
- errorData.error || errorData.message || "Erro ao redefinir senha",
5935
- ErrorType.API_ERROR
5936
- );
5937
- }
5938
- return response.json();
5939
- }
5940
- const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
5941
- __proto__: null,
5942
- checkUserExistence,
5943
- completeOnboarding,
5944
- completeOnboardingWithData,
5945
- confirmPasswordReset,
5946
- getOnboardingDetails,
5947
- getTaskStatus,
5948
- identifyPerson,
5949
- identifyPersonAsync,
5950
- loginWithBiometric,
5951
- loginWithEmail,
5952
- pollTaskStatus,
5953
- recognize,
5954
- recognizeBiometric,
5955
- recognizeByPurpose,
5956
- recordFaceVideo,
5957
- registerApplication,
5958
- registerBiometric,
5959
- registerDocumentByImage,
5960
- registerPersonWithBiometric,
5961
- registerPersonWithoutFace,
5962
- requestPasswordReset,
5963
- simpleIdentification,
5964
- startProofOfLife,
5965
- validateOnboardingToken,
5966
- validateToken,
5967
- videoToBase64
5968
- }, Symbol.toStringTag, { value: "Module" }));
5969
- const BLUE = {
5970
- 400: "#00B0F8",
5971
- 500: "#0091F6",
5972
- 700: "#0059C4"
5973
- };
5974
- const GOLD = {
5975
- 500: "#F1BE00"
5976
- };
5977
- const INK = {
5978
- 25: "#F7F9FC",
5979
- 50: "#F1F5FA",
5980
- 100: "#E6ECF4",
5981
- 300: "#AFBDCE",
5982
- 400: "#8496AD",
5983
- 500: "#617489",
5984
- 600: "#48586B",
5985
- 800: "#1E2B3C",
5986
- 900: "#111C2B",
5987
- 950: "#0A1320"
5988
- };
5989
- const LIGHT_TOKENS = {
5990
- ground: INK[25],
5991
- surface: "#FFFFFF",
5992
- surfaceMuted: INK[50],
5993
- line: INK[100],
5994
- text: INK[950],
5995
- textMuted: INK[600],
5996
- textSubtle: INK[500],
5997
- action: BLUE[700],
5998
- actionText: "#FFFFFF",
5999
- accent: GOLD[500],
6000
- overlay: "rgba(10,19,32,.55)",
6001
- focusRing: BLUE[700]
6002
- };
6003
- const DARK_TOKENS = {
6004
- ground: INK[950],
6005
- surface: INK[900],
6006
- surfaceMuted: INK[800],
6007
- line: "#22334D",
6008
- text: "#EEF3F9",
6009
- textMuted: INK[300],
6010
- textSubtle: INK[400],
6011
- action: BLUE[500],
6012
- actionText: "#FFFFFF",
6013
- accent: GOLD[500],
6014
- overlay: "rgba(10,19,32,.72)",
6015
- focusRing: BLUE[400]
6016
- };
6017
- function contrastRatio(hexA, hexB) {
6018
- const lumA = luminance(hexA);
6019
- const lumB = luminance(hexB);
6020
- const [hi, lo] = lumA > lumB ? [lumA, lumB] : [lumB, lumA];
6021
- return (hi + 0.05) / (lo + 0.05);
6022
- }
6023
- function luminance(hex) {
6024
- const [r, g, b] = parseHex(hex).map((v) => {
6025
- const s = v / 255;
6026
- return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
6027
- });
6028
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
6029
- }
6030
- function parseHex(hex) {
6031
- let h = hex.trim().replace("#", "");
6032
- if (h.length === 3) h = h.split("").map((c) => c + c).join("");
6033
- if (!/^[0-9a-fA-F]{6}$/.test(h)) return [0, 0, 0];
6034
- return [
6035
- parseInt(h.slice(0, 2), 16),
6036
- parseInt(h.slice(2, 4), 16),
6037
- parseInt(h.slice(4, 6), 16)
6038
- ];
6039
- }
6040
- const ALLOWED_RADIUS = [8, 16, 24];
6041
- function resolveTheme(options = {}) {
6042
- const mode = resolveMode(options.theme);
6043
- const tokens = mode === "dark" ? { ...DARK_TOKENS } : { ...LIGHT_TOKENS };
6044
- let radius = 16;
6045
- if (options.radius !== void 0) {
6046
- if (ALLOWED_RADIUS.includes(options.radius)) {
6047
- radius = options.radius;
6048
- } else {
6049
- console.warn(
6050
- `[NeoFaceID SDK] radius ${options.radius} inválido — permitidos: 8, 16, 24. Usando 16.`
6051
- );
6052
- }
6053
- }
6054
- let accent = tokens.action;
6055
- let accentFellBack = false;
6056
- if (options.accent) {
6057
- const ratio = contrastRatio(options.accent, "#FFFFFF");
6058
- if (ratio >= 4.5) {
6059
- accent = options.accent;
6060
- } else {
6061
- accentFellBack = true;
6062
- console.warn(
6063
- `[NeoFaceID SDK] accent ${options.accent} reprova AA vs branco (${ratio.toFixed(2)}:1). Usando ${BLUE[700]}.`
6064
- );
6065
- accent = BLUE[700];
6066
- }
6067
- }
6068
- return { mode, tokens: { ...tokens, action: accent }, radius, accent, accentFellBack };
6069
- }
6070
- function resolveMode(theme) {
6071
- if (theme === "dark") return "dark";
6072
- if (theme === "light") return "light";
6073
- if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
6074
- if (window.matchMedia("(prefers-color-scheme: dark)").matches) return "dark";
6075
- }
6076
- return "light";
6077
- }
6078
- const CSS_VAR = {
6079
- ground: "--nfid-ground",
6080
- surface: "--nfid-surface",
6081
- surfaceMuted: "--nfid-surface-muted",
6082
- line: "--nfid-line",
6083
- text: "--nfid-text",
6084
- textMuted: "--nfid-text-muted",
6085
- textSubtle: "--nfid-text-subtle",
6086
- action: "--nfid-action",
6087
- actionText: "--nfid-action-text",
6088
- accent: "--nfid-accent",
6089
- overlay: "--nfid-overlay",
6090
- focusRing: "--nfid-focus-ring",
6091
- radius: "--nfid-radius"
6147
+ }
6148
+ throw new NeoFaceError("Timed out waiting for identification", ErrorType.NETWORK_ERROR);
6092
6149
  };
6093
- const INJECTED_STYLE_ID = "neofaceid-focus-frame-tokens";
6094
- function injectThemeStyles$1(options = {}) {
6095
- const resolved = resolveTheme(options);
6096
- if (typeof document === "undefined") return resolved;
6097
- const css = buildRootCss(resolved);
6098
- const existing = document.getElementById(INJECTED_STYLE_ID);
6099
- if (existing) {
6100
- if (existing.textContent !== css) existing.textContent = css;
6101
- return resolved;
6150
+ async function requestPasswordReset(email2, applicationToken) {
6151
+ const baseUrl = getBaseUrl();
6152
+ const response = await fetch(`${baseUrl}/api/v1/user/password/reset/request/`, {
6153
+ method: "POST",
6154
+ headers: {
6155
+ "Content-Type": "application/json",
6156
+ "X-App-Token": applicationToken
6157
+ },
6158
+ body: JSON.stringify({ email: email2 })
6159
+ });
6160
+ if (!response.ok) {
6161
+ const errorData = await response.json();
6162
+ throw new NeoFaceError(
6163
+ errorData.message || "Erro ao solicitar recuperação de senha",
6164
+ ErrorType.API_ERROR
6165
+ );
6102
6166
  }
6103
- const style = document.createElement("style");
6104
- style.id = INJECTED_STYLE_ID;
6105
- style.textContent = css;
6106
- document.head.appendChild(style);
6107
- return resolved;
6108
- }
6109
- function buildRootCss(theme) {
6110
- const t = theme.tokens;
6111
- return `.neofaceid-root {
6112
- ${CSS_VAR.ground}: ${t.ground};
6113
- ${CSS_VAR.surface}: ${t.surface};
6114
- ${CSS_VAR.surfaceMuted}: ${t.surfaceMuted};
6115
- ${CSS_VAR.line}: ${t.line};
6116
- ${CSS_VAR.text}: ${t.text};
6117
- ${CSS_VAR.textMuted}: ${t.textMuted};
6118
- ${CSS_VAR.textSubtle}: ${t.textSubtle};
6119
- ${CSS_VAR.action}: ${t.action};
6120
- ${CSS_VAR.actionText}: ${t.actionText};
6121
- ${CSS_VAR.accent}: ${t.accent};
6122
- ${CSS_VAR.overlay}: ${t.overlay};
6123
- ${CSS_VAR.focusRing}: ${t.focusRing};
6124
- ${CSS_VAR.radius}: ${theme.radius}px;
6125
- color: var(${CSS_VAR.text});
6126
- font-family: inherit;
6127
- }
6128
- .neofaceid-root :focus-visible {
6129
- outline: 2px solid var(${CSS_VAR.focusRing});
6130
- outline-offset: 2px;
6131
- }`;
6167
+ return response.json();
6132
6168
  }
6133
- function injectScopedStyles$1(id, css) {
6134
- if (typeof document === "undefined") return;
6135
- const existing = document.getElementById(id);
6136
- if (existing) {
6137
- if (existing.textContent !== css) existing.textContent = css;
6138
- return;
6169
+ async function confirmPasswordReset(token, new_password) {
6170
+ const baseUrl = getBaseUrl();
6171
+ const response = await fetch(`${baseUrl}/api/v1/user/password/reset/confirm/`, {
6172
+ method: "POST",
6173
+ headers: {
6174
+ "Content-Type": "application/json"
6175
+ },
6176
+ body: JSON.stringify({ token, new_password })
6177
+ });
6178
+ if (!response.ok) {
6179
+ const errorData = await response.json();
6180
+ throw new NeoFaceError(
6181
+ errorData.error || errorData.message || "Erro ao redefinir senha",
6182
+ ErrorType.API_ERROR
6183
+ );
6139
6184
  }
6140
- const style = document.createElement("style");
6141
- style.id = id;
6142
- style.textContent = css;
6143
- document.head.appendChild(style);
6185
+ return response.json();
6144
6186
  }
6187
+ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
6188
+ __proto__: null,
6189
+ checkUserExistence,
6190
+ completeOnboarding,
6191
+ completeOnboardingWithData,
6192
+ confirmPasswordReset,
6193
+ getOnboardingDetails,
6194
+ getTaskStatus,
6195
+ identifyPerson,
6196
+ identifyPersonAsync,
6197
+ loginWithBiometric,
6198
+ loginWithEmail,
6199
+ pollTaskStatus,
6200
+ recognize,
6201
+ recognizeBiometric,
6202
+ recognizeByPurpose,
6203
+ recordFaceVideo,
6204
+ registerApplication,
6205
+ registerBiometric,
6206
+ registerDocumentByImage,
6207
+ registerPersonWithBiometric,
6208
+ registerPersonWithoutFace,
6209
+ requestPasswordReset,
6210
+ simpleIdentification,
6211
+ startProofOfLife,
6212
+ validateOnboardingToken,
6213
+ validateToken,
6214
+ videoToBase64
6215
+ }, Symbol.toStringTag, { value: "Module" }));
6145
6216
  const modalReducer$1 = (state, action) => {
6146
6217
  switch (action.type) {
6147
6218
  case "PERMISSION_GRANTED":
@@ -8668,10 +8739,11 @@ class ConsentModal {
8668
8739
  }
8669
8740
  const DEFAULT_APP_NAME = "sua aplicação";
8670
8741
  function requestConsent(info = {}) {
8671
- const appName = info.appName ?? DEFAULT_APP_NAME;
8672
- if (!info.appName) {
8742
+ const globalAppName = getAppName();
8743
+ const appName = info.appName ?? globalAppName ?? DEFAULT_APP_NAME;
8744
+ if (!info.appName && !globalAppName) {
8673
8745
  console.warn(
8674
- '[NeoFaceID SDK] requestConsent chamado sem appName — usando fallback "sua aplicação". Passe o nome do integrador via NeoFaceSDK.init({ appName }).'
8746
+ '[NeoFaceID SDK] requestConsent sem appName — usando fallback "sua aplicação". Passe via NeoFaceSDK.init({ appName }) ou opts do caller.'
8675
8747
  );
8676
8748
  }
8677
8749
  const flow = info.flow ?? "verification";
@@ -8710,7 +8782,7 @@ class BiometricCaptureModal {
8710
8782
  try {
8711
8783
  await this.createModal();
8712
8784
  await this.initializeCamera();
8713
- this.startCountdown();
8785
+ this.enableCaptureButton();
8714
8786
  } catch (error) {
8715
8787
  this.options.onError(
8716
8788
  new NeoFaceError(
@@ -8743,7 +8815,7 @@ class BiometricCaptureModal {
8743
8815
  <div class="neofaceid-modal-content">
8744
8816
  <div class="neofaceid-modal-header">
8745
8817
  <h2>${title}</h2>
8746
- <button class="neofaceid-close-btn" type="button">&times;</button>
8818
+ <button class="neofaceid-close-btn" type="button" aria-label="Fechar">&times;</button>
8747
8819
  </div>
8748
8820
  <div class="neofaceid-modal-body">
8749
8821
  <p class="neofaceid-subtitle">${subtitle}</p>
@@ -8752,9 +8824,6 @@ class BiometricCaptureModal {
8752
8824
  <canvas class="neofaceid-canvas" style="display: none;"></canvas>
8753
8825
  <div class="neofaceid-overlay">
8754
8826
  <div class="neofaceid-frame"></div>
8755
- <div class="neofaceid-countdown">
8756
- <span class="neofaceid-countdown-number">${this.options.countdown || 3}</span>
8757
- </div>
8758
8827
  </div>
8759
8828
  </div>
8760
8829
  <div class="neofaceid-status">
@@ -8765,6 +8834,9 @@ class BiometricCaptureModal {
8765
8834
  <button class="neofaceid-cancel-btn" type="button">Cancelar</button>
8766
8835
  <button class="neofaceid-capture-btn" type="button" disabled>Capturar</button>
8767
8836
  </div>
8837
+ <div class="neofaceid-seal-row">
8838
+ <span class="neofaceid-seal">Protegido por NeoFaceID</span>
8839
+ </div>
8768
8840
  </div>
8769
8841
  </div>
8770
8842
  `;
@@ -8801,42 +8873,24 @@ class BiometricCaptureModal {
8801
8873
  }
8802
8874
  }
8803
8875
  /**
8804
- * Inicia a contagem regressiva
8876
+ * NEO-413 · US14.6 (item 18) — a contagem "3, 2, 1" foi removida.
8877
+ * Agora habilita direto o botão "Capturar" assim que a câmera fica pronta.
8878
+ * Vivacidade fica com o desafio `runLivenessChallenge` (NEO-408), servidor decide.
8805
8879
  */
8806
- startCountdown() {
8880
+ enableCaptureButton() {
8807
8881
  if (!this.modal) return;
8808
- const countdownElement = this.modal.querySelector(".neofaceid-countdown-number");
8809
8882
  const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
8810
8883
  const statusText = this.modal.querySelector(".neofaceid-status-text");
8811
- let count = this.options.countdown || 3;
8812
- if (countdownElement) {
8813
- countdownElement.textContent = count.toString();
8884
+ if (captureBtn) {
8885
+ captureBtn.disabled = false;
8886
+ captureBtn.textContent = "Capturar";
8887
+ }
8888
+ if (statusText) {
8889
+ statusText.textContent = "Pronto para capturar";
8890
+ }
8891
+ if (this.options.mode === "auto") {
8892
+ setTimeout(() => this.captureImage(), 300);
8814
8893
  }
8815
- this.countdownInterval = window.setInterval(() => {
8816
- count--;
8817
- if (countdownElement) {
8818
- countdownElement.textContent = count.toString();
8819
- }
8820
- if (count <= 0) {
8821
- if (this.countdownInterval) {
8822
- clearInterval(this.countdownInterval);
8823
- this.countdownInterval = null;
8824
- }
8825
- if (countdownElement) {
8826
- countdownElement.style.display = "none";
8827
- }
8828
- if (captureBtn) {
8829
- captureBtn.disabled = false;
8830
- captureBtn.textContent = "Capturar Agora";
8831
- }
8832
- if (statusText) {
8833
- statusText.textContent = "Pronto para capturar!";
8834
- }
8835
- if (this.options.mode === "auto") {
8836
- setTimeout(() => this.captureImage(), 500);
8837
- }
8838
- }
8839
- }, 1e3);
8840
8894
  }
8841
8895
  /**
8842
8896
  * Captura a imagem da câmera
@@ -9018,18 +9072,22 @@ class BiometricCaptureModal {
9018
9072
  .neofaceid-close-btn {
9019
9073
  background: none;
9020
9074
  border: none;
9021
- font-size: 28px;
9075
+ font-size: 22px;
9022
9076
  cursor: pointer;
9023
9077
  color: #666;
9024
9078
  padding: 0;
9025
- width: 32px;
9026
- height: 32px;
9079
+ min-width: 44px;
9080
+ min-height: 44px;
9027
9081
  display: flex;
9028
9082
  align-items: center;
9029
9083
  justify-content: center;
9030
- border-radius: 50%;
9084
+ border-radius: 22px;
9031
9085
  transition: background-color 0.2s;
9032
9086
  }
9087
+ .neofaceid-close-btn:focus-visible {
9088
+ outline: 2px solid var(--nfid-focus-ring);
9089
+ outline-offset: 2px;
9090
+ }
9033
9091
 
9034
9092
  .neofaceid-close-btn:hover {
9035
9093
  background-color: #f0f0f0;
@@ -9087,24 +9145,18 @@ class BiometricCaptureModal {
9087
9145
  animation: pulse 2s infinite;
9088
9146
  }
9089
9147
 
9090
- .neofaceid-countdown {
9091
- position: absolute;
9092
- top: 20px;
9093
- right: 20px;
9094
- background: rgba(0, 0, 0, 0.7);
9095
- color: white;
9096
- padding: 10px;
9097
- border-radius: 50%;
9098
- width: 50px;
9099
- height: 50px;
9148
+ .neofaceid-seal-row {
9100
9149
  display: flex;
9101
- align-items: center;
9102
9150
  justify-content: center;
9151
+ padding: 12px 20px 16px;
9152
+ border-top: 1px solid var(--nfid-line, #E6ECF4);
9103
9153
  }
9104
-
9105
- .neofaceid-countdown-number {
9106
- font-size: 24px;
9107
- font-weight: bold;
9154
+ .neofaceid-seal {
9155
+ font-size: 11px;
9156
+ font-weight: 700;
9157
+ letter-spacing: 0.09em;
9158
+ text-transform: uppercase;
9159
+ color: var(--nfid-text-subtle, #617489);
9108
9160
  }
9109
9161
 
9110
9162
  .neofaceid-status {
@@ -10808,7 +10860,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
10808
10860
  initializeBiometricDetection,
10809
10861
  isAdvancedDetectionAvailable
10810
10862
  }, Symbol.toStringTag, { value: "Module" }));
10811
- const VERSION = "1.29.0";
10863
+ const VERSION = "1.31.0";
10812
10864
  const RELEASE_DATE = "2026-09-02";
10813
10865
  let cachedSession = null;
10814
10866
  function getCachedCaptureSession() {
@@ -11122,6 +11174,9 @@ class OnboardingCaptureModal {
11122
11174
  <button class="neofaceid-cancel-btn">Cancelar</button>
11123
11175
  <button class="neofaceid-capture-btn" disabled>Capturar</button>
11124
11176
  </div>
11177
+ <div class="neofaceid-seal-row">
11178
+ <span class="neofaceid-seal">Protegido por NeoFaceID</span>
11179
+ </div>
11125
11180
  </div>
11126
11181
  `;
11127
11182
  injectThemeStyles();
@@ -11145,6 +11200,8 @@ class OnboardingCaptureModal {
11145
11200
  .neofaceid-root .neofaceid-cancel-btn { background: var(--nfid-surface-muted); color: var(--nfid-text-muted); border:none; border-radius:8px; padding:10px 18px; cursor:pointer; min-height: 44px; }
11146
11201
  .neofaceid-root .neofaceid-capture-btn { background: var(--nfid-action); color: var(--nfid-action-text); border:none; border-radius:8px; padding:10px 18px; cursor:pointer; min-height: 44px; }
11147
11202
  .neofaceid-root .neofaceid-capture-btn:disabled { background: var(--nfid-line); cursor:not-allowed; }
11203
+ .neofaceid-root .neofaceid-seal-row { display:flex; justify-content:center; padding:12px 20px 14px; border-top: 1px solid var(--nfid-line); }
11204
+ .neofaceid-root .neofaceid-seal { font-size:11px; font-weight:700; letter-spacing:0.09em; text-transform:uppercase; color: var(--nfid-text-subtle); }
11148
11205
  `
11149
11206
  );
11150
11207
  overlay.classList.add("neofaceid-root");
@@ -11765,6 +11822,552 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
11765
11822
  throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
11766
11823
  }
11767
11824
  };
11825
+ function Sheet({
11826
+ open,
11827
+ onDismiss,
11828
+ dismissOnOverlay = true,
11829
+ ariaLabel,
11830
+ ariaLabelledBy,
11831
+ children,
11832
+ className,
11833
+ style
11834
+ }) {
11835
+ useEffect(() => {
11836
+ injectThemeStyles$1();
11837
+ }, []);
11838
+ if (!open) return null;
11839
+ const handleOverlay = () => {
11840
+ if (dismissOnOverlay && onDismiss) onDismiss();
11841
+ };
11842
+ return /* @__PURE__ */ jsx(
11843
+ "div",
11844
+ {
11845
+ className: "neofaceid-root",
11846
+ role: "presentation",
11847
+ style: {
11848
+ position: "fixed",
11849
+ inset: 0,
11850
+ zIndex: 10001,
11851
+ display: "flex",
11852
+ alignItems: "center",
11853
+ justifyContent: "center",
11854
+ padding: 20,
11855
+ background: `var(${CSS_VAR.overlay})`,
11856
+ backdropFilter: "blur(3px)",
11857
+ WebkitBackdropFilter: "blur(3px)"
11858
+ },
11859
+ onClick: handleOverlay,
11860
+ children: /* @__PURE__ */ jsx(
11861
+ "div",
11862
+ {
11863
+ role: "dialog",
11864
+ "aria-modal": "true",
11865
+ "aria-label": ariaLabel,
11866
+ "aria-labelledby": ariaLabelledBy,
11867
+ className,
11868
+ onClick: (e) => e.stopPropagation(),
11869
+ style: {
11870
+ background: `var(${CSS_VAR.surface})`,
11871
+ color: `var(${CSS_VAR.text})`,
11872
+ borderRadius: `var(${CSS_VAR.radius}, 16px)`,
11873
+ padding: 28,
11874
+ maxWidth: 440,
11875
+ width: "100%",
11876
+ boxShadow: "0 1px 3px rgba(10,19,32,.08)",
11877
+ boxSizing: "border-box",
11878
+ ...style
11879
+ },
11880
+ children
11881
+ }
11882
+ )
11883
+ }
11884
+ );
11885
+ }
11886
+ const BASE_STYLE = {
11887
+ minHeight: 44,
11888
+ minWidth: 44,
11889
+ padding: "12px 20px",
11890
+ borderRadius: 16,
11891
+ border: "none",
11892
+ fontSize: 15,
11893
+ fontWeight: 600,
11894
+ fontFamily: "inherit",
11895
+ cursor: "pointer",
11896
+ transition: "background 120ms ease-out",
11897
+ display: "inline-flex",
11898
+ alignItems: "center",
11899
+ justifyContent: "center",
11900
+ gap: 8
11901
+ };
11902
+ const Button = forwardRef(
11903
+ ({ variant = "primary", style, type = "button", ...rest }, ref) => {
11904
+ const variantStyle = variant === "primary" ? {
11905
+ background: `var(${CSS_VAR.action})`,
11906
+ color: `var(${CSS_VAR.actionText})`
11907
+ } : variant === "secondary" ? {
11908
+ background: `var(${CSS_VAR.surfaceMuted})`,
11909
+ color: `var(${CSS_VAR.text})`
11910
+ } : {
11911
+ background: "transparent",
11912
+ color: `var(${CSS_VAR.textMuted})`
11913
+ };
11914
+ return (
11915
+ // eslint-disable-next-line react/button-has-type
11916
+ /* @__PURE__ */ jsx(
11917
+ "button",
11918
+ {
11919
+ ref,
11920
+ type,
11921
+ ...rest,
11922
+ style: { ...BASE_STYLE, ...variantStyle, ...style }
11923
+ }
11924
+ )
11925
+ );
11926
+ }
11927
+ );
11928
+ Button.displayName = "Button";
11929
+ function FocusFrame({
11930
+ size = 96,
11931
+ color,
11932
+ strokeWidth,
11933
+ glow = false,
11934
+ className,
11935
+ style
11936
+ }) {
11937
+ const stroke = strokeWidth ?? autoStroke(size);
11938
+ const arm = size * 0.28;
11939
+ const offset = stroke / 2;
11940
+ const stops = [
11941
+ { x: offset, y: offset, dx: 1, dy: 1 },
11942
+ { x: size - offset, y: offset, dx: -1, dy: 1 },
11943
+ { x: offset, y: size - offset, dx: 1, dy: -1 },
11944
+ { x: size - offset, y: size - offset, dx: -1, dy: -1 }
11945
+ ];
11946
+ return /* @__PURE__ */ jsx(
11947
+ "svg",
11948
+ {
11949
+ width: size,
11950
+ height: size,
11951
+ viewBox: `0 0 ${size} ${size}`,
11952
+ xmlns: "http://www.w3.org/2000/svg",
11953
+ className,
11954
+ style: {
11955
+ color: color ?? `var(${CSS_VAR.action})`,
11956
+ filter: glow ? "drop-shadow(0 0 6px rgba(0,176,248,.5))" : void 0,
11957
+ ...style
11958
+ },
11959
+ "aria-hidden": "true",
11960
+ children: stops.map((c, i) => /* @__PURE__ */ jsx(
11961
+ "path",
11962
+ {
11963
+ d: `M ${c.x + c.dx * arm} ${c.y} L ${c.x} ${c.y} L ${c.x} ${c.y + c.dy * arm}`,
11964
+ fill: "none",
11965
+ stroke: "currentColor",
11966
+ strokeWidth: stroke,
11967
+ strokeLinecap: "round"
11968
+ },
11969
+ i
11970
+ ))
11971
+ }
11972
+ );
11973
+ }
11974
+ function autoStroke(size) {
11975
+ if (size <= 24) return 2;
11976
+ if (size <= 48) return 2.5;
11977
+ return 3.4;
11978
+ }
11979
+ function ChallengeRing({
11980
+ progress,
11981
+ size = 268,
11982
+ children,
11983
+ className,
11984
+ style
11985
+ }) {
11986
+ const clamped = Math.max(0, Math.min(1, Number.isFinite(progress) ? progress : 0));
11987
+ const stroke = 4.2;
11988
+ const radius = size / 2 - stroke;
11989
+ const circumference = 2 * Math.PI * radius;
11990
+ const dashOffset = circumference * (1 - clamped);
11991
+ const frameSize = size + 24;
11992
+ return /* @__PURE__ */ jsxs(
11993
+ "div",
11994
+ {
11995
+ className,
11996
+ style: {
11997
+ position: "relative",
11998
+ width: frameSize,
11999
+ height: frameSize,
12000
+ display: "inline-flex",
12001
+ alignItems: "center",
12002
+ justifyContent: "center",
12003
+ ...style
12004
+ },
12005
+ children: [
12006
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(FocusFrame, { size: frameSize, glow: true }) }),
12007
+ /* @__PURE__ */ jsx(
12008
+ "div",
12009
+ {
12010
+ style: {
12011
+ position: "relative",
12012
+ width: size,
12013
+ height: size,
12014
+ borderRadius: "50%",
12015
+ overflow: "hidden",
12016
+ background: "linear-gradient(160deg, #274B6E, #0F2438, #05080F)"
12017
+ },
12018
+ children
12019
+ }
12020
+ ),
12021
+ /* @__PURE__ */ jsxs(
12022
+ "svg",
12023
+ {
12024
+ width: size,
12025
+ height: size,
12026
+ viewBox: `0 0 ${size} ${size}`,
12027
+ style: {
12028
+ position: "absolute",
12029
+ top: (frameSize - size) / 2,
12030
+ left: (frameSize - size) / 2,
12031
+ transform: "rotate(-90deg)",
12032
+ pointerEvents: "none"
12033
+ },
12034
+ "aria-hidden": "true",
12035
+ children: [
12036
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: "nfid-challenge-arc", x1: "0%", y1: "0%", x2: "100%", y2: "100%", children: [
12037
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#00B0F8" }),
12038
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#0073F4" })
12039
+ ] }) }),
12040
+ /* @__PURE__ */ jsx(
12041
+ "circle",
12042
+ {
12043
+ cx: size / 2,
12044
+ cy: size / 2,
12045
+ r: radius,
12046
+ stroke: "rgba(255,255,255,0.18)",
12047
+ strokeWidth: stroke,
12048
+ fill: "none"
12049
+ }
12050
+ ),
12051
+ /* @__PURE__ */ jsx(
12052
+ "circle",
12053
+ {
12054
+ cx: size / 2,
12055
+ cy: size / 2,
12056
+ r: radius,
12057
+ stroke: `var(${CSS_VAR.action}, url(#nfid-challenge-arc))`,
12058
+ strokeWidth: stroke,
12059
+ strokeLinecap: "round",
12060
+ strokeDasharray: circumference,
12061
+ strokeDashoffset: dashOffset,
12062
+ fill: "none",
12063
+ style: { transition: "stroke-dashoffset 200ms ease-out" }
12064
+ }
12065
+ )
12066
+ ]
12067
+ }
12068
+ )
12069
+ ]
12070
+ }
12071
+ );
12072
+ }
12073
+ function ChallengeGlyph({
12074
+ gesture,
12075
+ size = 48,
12076
+ color,
12077
+ className,
12078
+ style
12079
+ }) {
12080
+ const stroke = 3.4;
12081
+ return /* @__PURE__ */ jsxs(
12082
+ "svg",
12083
+ {
12084
+ width: size,
12085
+ height: size,
12086
+ viewBox: "0 0 48 48",
12087
+ xmlns: "http://www.w3.org/2000/svg",
12088
+ className,
12089
+ style: { color: color ?? `var(${CSS_VAR.action})`, ...style },
12090
+ role: "img",
12091
+ "aria-label": LABELS[gesture],
12092
+ children: [
12093
+ gesture === "blink" ? /* @__PURE__ */ jsx(BlinkGlyph, { stroke }) : /* @__PURE__ */ jsx(FaceGlyph, { stroke }),
12094
+ gesture !== "blink" && gesture !== "center" && /* @__PURE__ */ jsx(Chevron, { gesture, stroke })
12095
+ ]
12096
+ }
12097
+ );
12098
+ }
12099
+ const LABELS = {
12100
+ center: "Centralize o rosto",
12101
+ turn_right: "Vire o rosto para a direita",
12102
+ turn_left: "Vire o rosto para a esquerda",
12103
+ look_up: "Olhe para cima",
12104
+ look_down: "Olhe para baixo",
12105
+ blink: "Pisque os olhos",
12106
+ move_closer: "Aproxime-se da câmera"
12107
+ };
12108
+ function FaceGlyph({ stroke }) {
12109
+ return /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", children: [
12110
+ /* @__PURE__ */ jsx("path", { d: "M14 20c0-5.5 4.5-10 10-10s10 4.5 10 10v4c0 5.5-4.5 10-10 10s-10-4.5-10-10v-4z" }),
12111
+ /* @__PURE__ */ jsx("circle", { cx: "20", cy: "22", r: "1.2", fill: "currentColor", stroke: "none" }),
12112
+ /* @__PURE__ */ jsx("circle", { cx: "28", cy: "22", r: "1.2", fill: "currentColor", stroke: "none" }),
12113
+ /* @__PURE__ */ jsx("path", { d: "M21 28c1 1 2 1.5 3 1.5s2-.5 3-1.5" })
12114
+ ] });
12115
+ }
12116
+ function BlinkGlyph({ stroke }) {
12117
+ return /* @__PURE__ */ jsxs("g", { fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", children: [
12118
+ /* @__PURE__ */ jsx("path", { d: "M8 24c4-4 8-6 16-6s12 2 16 6" }),
12119
+ /* @__PURE__ */ jsx("path", { d: "M12 26v3" }),
12120
+ /* @__PURE__ */ jsx("path", { d: "M18 27v3.5" }),
12121
+ /* @__PURE__ */ jsx("path", { d: "M24 27.5v4" }),
12122
+ /* @__PURE__ */ jsx("path", { d: "M30 27v3.5" }),
12123
+ /* @__PURE__ */ jsx("path", { d: "M36 26v3" })
12124
+ ] });
12125
+ }
12126
+ function Chevron({ gesture, stroke }) {
12127
+ const [start2, mid, end] = pointsFor(gesture);
12128
+ return /* @__PURE__ */ jsx(
12129
+ "path",
12130
+ {
12131
+ d: `M ${start2} L ${mid} L ${end}`,
12132
+ fill: "none",
12133
+ stroke: "currentColor",
12134
+ strokeWidth: stroke,
12135
+ strokeLinecap: "round",
12136
+ strokeLinejoin: "round"
12137
+ }
12138
+ );
12139
+ }
12140
+ function pointsFor(g) {
12141
+ switch (g) {
12142
+ case "turn_right":
12143
+ return ["40 18", "44 24", "40 30"];
12144
+ case "turn_left":
12145
+ return ["8 18", "4 24", "8 30"];
12146
+ case "look_up":
12147
+ return ["18 8", "24 4", "30 8"];
12148
+ case "look_down":
12149
+ return ["18 40", "24 44", "30 40"];
12150
+ case "move_closer":
12151
+ return ["18 40", "24 44", "30 40"];
12152
+ default:
12153
+ return ["24 24", "24 24", "24 24"];
12154
+ }
12155
+ }
12156
+ const STYLE_ID = "neofaceid-authorize-styles";
12157
+ const GESTURE_TO_GLYPH = {
12158
+ center: "center",
12159
+ turn_right: "turn_right",
12160
+ turn_left: "turn_left",
12161
+ look_up: "look_up",
12162
+ look_down: "look_down",
12163
+ blink: "blink",
12164
+ move_closer: "move_closer",
12165
+ smile: "blink",
12166
+ open_mouth: "blink"
12167
+ };
12168
+ const GESTURE_TO_INSTRUCTION = {
12169
+ center: "Centralize seu rosto no oval",
12170
+ turn_right: "Vire lentamente o rosto para a direita",
12171
+ turn_left: "Vire lentamente o rosto para a esquerda",
12172
+ look_up: "Olhe para cima",
12173
+ look_down: "Olhe para baixo",
12174
+ blink: "Pisque os olhos",
12175
+ move_closer: "Aproxime-se da câmera",
12176
+ smile: "Sorria para a câmera",
12177
+ open_mouth: "Abra a boca"
12178
+ };
12179
+ function formatCurrency(amount, locale) {
12180
+ try {
12181
+ return new Intl.NumberFormat(locale, {
12182
+ style: "currency",
12183
+ currency: "BRL"
12184
+ }).format(amount);
12185
+ } catch {
12186
+ return `R$ ${amount.toFixed(2)}`;
12187
+ }
12188
+ }
12189
+ function AuthorizeModalComponent({
12190
+ applicationToken,
12191
+ appName,
12192
+ amount,
12193
+ onChallengeStart,
12194
+ onChallengeComplete,
12195
+ onSuccess,
12196
+ onError,
12197
+ onCancel,
12198
+ onClose
12199
+ }) {
12200
+ const videoRef = useRef(null);
12201
+ const [progress, setProgress] = useState(0);
12202
+ const [currentGesture, setCurrentGesture] = useState(null);
12203
+ const [phase, setPhase] = useState("preparing");
12204
+ const cancelRef = useRef(false);
12205
+ const locale = getLocale();
12206
+ const headerAppName = appName ?? getAppName() ?? "sua aplicação";
12207
+ const headerAmount = typeof amount === "number" && Number.isFinite(amount) ? formatCurrency(amount, locale) : null;
12208
+ useEffect(() => {
12209
+ injectThemeStyles$1();
12210
+ injectScopedStyles$1(
12211
+ STYLE_ID,
12212
+ `
12213
+ .neofaceid-root .nfid-authorize-header { display: flex; flex-direction: column; gap: 4px; margin-bottom: 16px; }
12214
+ .neofaceid-root .nfid-authorize-appname { font-size: 15px; font-weight: 700; color: var(--nfid-text); line-height: 1.2; }
12215
+ .neofaceid-root .nfid-authorize-amount { font-size: 13px; color: var(--nfid-text-muted); }
12216
+ .neofaceid-root .nfid-authorize-brand { font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var(--nfid-text-subtle); }
12217
+ .neofaceid-root .nfid-authorize-video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
12218
+ .neofaceid-root .nfid-authorize-instruction { margin: 20px 0 0; font-size: 17px; font-weight: 600; color: var(--nfid-text); text-align: center; line-height: 1.3; }
12219
+ .neofaceid-root .nfid-authorize-support { margin: 6px 0 0; font-size: 13px; color: var(--nfid-text-muted); text-align: center; }
12220
+ .neofaceid-root .nfid-authorize-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; align-items: center; }
12221
+ .neofaceid-root .nfid-authorize-footer { display: flex; justify-content: center; margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--nfid-line); }
12222
+ .neofaceid-root .nfid-authorize-seal { font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var(--nfid-text-subtle); }
12223
+ .neofaceid-root .nfid-authorize-close {
12224
+ position: absolute; top: 8px; right: 8px;
12225
+ min-width: 44px; min-height: 44px;
12226
+ background: transparent; border: none;
12227
+ display: inline-flex; align-items: center; justify-content: center;
12228
+ color: var(--nfid-text-muted); cursor: pointer; border-radius: 22px;
12229
+ }
12230
+ .neofaceid-root .nfid-authorize-close:hover { background: var(--nfid-surface-muted); }
12231
+ .neofaceid-root .nfid-authorize-close:focus-visible { outline: 2px solid var(--nfid-focus-ring); outline-offset: 2px; }
12232
+ @media (prefers-reduced-motion: reduce) {
12233
+ .neofaceid-root svg circle { transition: none !important; }
12234
+ }
12235
+ `
12236
+ );
12237
+ }, []);
12238
+ useEffect(() => {
12239
+ let stream = null;
12240
+ async function run() {
12241
+ try {
12242
+ stream = await navigator.mediaDevices.getUserMedia({
12243
+ video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } }
12244
+ });
12245
+ if (videoRef.current) {
12246
+ videoRef.current.srcObject = stream;
12247
+ videoRef.current.play().catch(() => {
12248
+ });
12249
+ }
12250
+ setPhase("challenging");
12251
+ const result = await runLivenessChallenge({
12252
+ applicationToken,
12253
+ purpose: "authorization",
12254
+ collectFramesForGesture: async (gesture, index, total) => {
12255
+ if (cancelRef.current) throw new NeoFaceError("Cancelado", ErrorType.UNKNOWN);
12256
+ setCurrentGesture(gesture);
12257
+ setProgress(0);
12258
+ if (onChallengeStart) onChallengeStart(gesture, index, total);
12259
+ await progressiveHold(setProgress);
12260
+ if (onChallengeComplete) onChallengeComplete(gesture, index, total);
12261
+ const frame = await captureFrame(videoRef.current);
12262
+ return frame ? [frame] : [];
12263
+ }
12264
+ });
12265
+ setPhase("done");
12266
+ if (onSuccess) onSuccess(result);
12267
+ onClose();
12268
+ } catch (err) {
12269
+ const nfe = err instanceof NeoFaceError ? err : new NeoFaceError(err instanceof Error ? err.message : "Falha", ErrorType.UNKNOWN);
12270
+ if (onError) onError(nfe);
12271
+ onClose();
12272
+ } finally {
12273
+ if (stream) stream.getTracks().forEach((t) => t.stop());
12274
+ }
12275
+ }
12276
+ run();
12277
+ return () => {
12278
+ cancelRef.current = true;
12279
+ if (stream) stream.getTracks().forEach((t) => t.stop());
12280
+ };
12281
+ }, []);
12282
+ const handleClose = () => {
12283
+ cancelRef.current = true;
12284
+ if (onCancel) onCancel();
12285
+ onClose();
12286
+ };
12287
+ const gestureKey = currentGesture ? GESTURE_TO_GLYPH[currentGesture.gesture_key] ?? "center" : "center";
12288
+ const instruction = currentGesture ? GESTURE_TO_INSTRUCTION[currentGesture.gesture_key] ?? "Siga a instrução" : "Preparando a câmera…";
12289
+ return /* @__PURE__ */ jsx(Sheet, { open: true, onDismiss: void 0, dismissOnOverlay: false, ariaLabel: "Autorização por biometria", children: /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
12290
+ /* @__PURE__ */ jsx(
12291
+ "button",
12292
+ {
12293
+ type: "button",
12294
+ className: "nfid-authorize-close",
12295
+ onClick: handleClose,
12296
+ "aria-label": "Cancelar autorização",
12297
+ children: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" }) })
12298
+ }
12299
+ ),
12300
+ /* @__PURE__ */ jsxs("div", { className: "nfid-authorize-header", children: [
12301
+ /* @__PURE__ */ jsx("span", { className: "nfid-authorize-appname", children: headerAppName }),
12302
+ headerAmount && /* @__PURE__ */ jsxs("span", { className: "nfid-authorize-amount", children: [
12303
+ "Autorizando ",
12304
+ headerAmount
12305
+ ] }),
12306
+ /* @__PURE__ */ jsx("span", { className: "nfid-authorize-brand", children: "NeoFaceID" })
12307
+ ] }),
12308
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx(ChallengeRing, { progress, size: 220, children: /* @__PURE__ */ jsx("video", { ref: videoRef, className: "nfid-authorize-video", autoPlay: true, muted: true, playsInline: true }) }) }),
12309
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 12, marginTop: 16 }, children: [
12310
+ /* @__PURE__ */ jsx(ChallengeGlyph, { gesture: gestureKey, size: 40 }),
12311
+ /* @__PURE__ */ jsx(
12312
+ "p",
12313
+ {
12314
+ className: "nfid-authorize-instruction",
12315
+ "aria-live": "assertive",
12316
+ style: { margin: 0 },
12317
+ children: instruction
12318
+ }
12319
+ )
12320
+ ] }),
12321
+ phase === "preparing" && /* @__PURE__ */ jsx("p", { className: "nfid-authorize-support", children: "Ligando sua câmera…" }),
12322
+ /* @__PURE__ */ jsx("div", { className: "nfid-authorize-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleClose, children: "Não consigo fazer esse movimento" }) }),
12323
+ /* @__PURE__ */ jsx("div", { className: "nfid-authorize-footer", children: /* @__PURE__ */ jsx("span", { className: "nfid-authorize-seal", children: "Protegido por NeoFaceID" }) })
12324
+ ] }) });
12325
+ }
12326
+ async function progressiveHold(setter) {
12327
+ const total = 1500;
12328
+ const start2 = Date.now();
12329
+ return new Promise((resolve) => {
12330
+ const tick = () => {
12331
+ const elapsed = Date.now() - start2;
12332
+ const p = Math.min(1, elapsed / total);
12333
+ setter(p);
12334
+ if (elapsed >= total) resolve();
12335
+ else setTimeout(tick, 50);
12336
+ };
12337
+ tick();
12338
+ });
12339
+ }
12340
+ async function captureFrame(video) {
12341
+ if (!video || video.videoWidth === 0) return null;
12342
+ const canvas = document.createElement("canvas");
12343
+ canvas.width = video.videoWidth;
12344
+ canvas.height = video.videoHeight;
12345
+ const ctx = canvas.getContext("2d");
12346
+ if (!ctx) return null;
12347
+ ctx.drawImage(video, 0, 0);
12348
+ return canvas.toDataURL("image/jpeg", 0.85);
12349
+ }
12350
+ async function authorize(options) {
12351
+ const consentAccepted = await requestConsent({
12352
+ appName: options.appName,
12353
+ flow: "verification"
12354
+ });
12355
+ if (!consentAccepted) {
12356
+ if (options.onError) {
12357
+ options.onError(new NeoFaceError("Consentimento recusado", ErrorType.CONSENT_DENIED));
12358
+ }
12359
+ return;
12360
+ }
12361
+ const container = document.createElement("div");
12362
+ container.id = "neofaceid-authorize-container";
12363
+ document.body.appendChild(container);
12364
+ const root = createRoot(container);
12365
+ const cleanup = () => {
12366
+ root.unmount();
12367
+ if (document.body.contains(container)) document.body.removeChild(container);
12368
+ };
12369
+ root.render(/* @__PURE__ */ jsx(AuthorizeModalComponent, { ...options, onClose: cleanup }));
12370
+ }
11768
12371
  const DOCUMENT_INFO = {
11769
12372
  RG: {
11770
12373
  name: "Documento de Identidade (RG / CIN / CPF)",
@@ -12923,6 +13526,7 @@ export {
12923
13526
  NeoFaceID,
12924
13527
  RELEASE_DATE,
12925
13528
  VERSION,
13529
+ authorize,
12926
13530
  authorizeOperation,
12927
13531
  biometricLogin,
12928
13532
  biometricLoginWithFallback,
@@ -12931,11 +13535,17 @@ export {
12931
13535
  completeOnboardingWithData,
12932
13536
  confirmPasswordReset,
12933
13537
  detectBiometricType,
13538
+ getAccent,
13539
+ getAppName,
12934
13540
  getApplicationToken,
12935
13541
  getBaseUrl,
12936
13542
  getCachedCaptureSession,
12937
13543
  getConfig,
12938
13544
  getEnvironment,
13545
+ getLocale,
13546
+ getRadius,
13547
+ getResolvedThemeMode,
13548
+ getTheme,
12939
13549
  identifyPerson,
12940
13550
  identifyPersonAsync,
12941
13551
  init,