@neofaceid/web-sdk 1.41.1 → 1.42.1

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.
@@ -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 { environment, baseUrl, applicationToken, appName, accent, radius, theme, locale, consent } = options;
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,8 +8867,8 @@ function BiometricRegistrationModal({
8732
8867
  /* @__PURE__ */ jsx("canvas", { ref: canvasRef })
8733
8868
  ] });
8734
8869
  }
8735
- const VERSION = "1.41.1";
8736
- const RELEASE_DATE = "2026-09-10";
8870
+ const VERSION = "1.42.1";
8871
+ const RELEASE_DATE = "2026-09-13";
8737
8872
  const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
8738
8873
  const CONSENT_TRAIL_MAX_LIMIT = 100;
8739
8874
  async function hashString(inputStr) {
@@ -8810,8 +8945,6 @@ const COPY = {
8810
8945
  bullet1Body: "Você vê a imagem o tempo todo e pode encerrar quando quiser.",
8811
8946
  bullet2Title: "Pediremos dois movimentos simples",
8812
8947
  bullet2Body: "Virar o rosto ou piscar — é assim que sabemos que há alguém ali.",
8813
- bullet3Title: "A imagem não fica guardada",
8814
- bullet3Body: "Ela é usada para a verificação e descartada em seguida.",
8815
8948
  primary: "Começar verificação"
8816
8949
  },
8817
8950
  registration: {
@@ -8821,11 +8954,26 @@ const COPY = {
8821
8954
  bullet1Body: "Você vê a imagem o tempo todo e pode encerrar quando quiser.",
8822
8955
  bullet2Title: "Pediremos dois movimentos simples",
8823
8956
  bullet2Body: "Virar o rosto ou piscar — é assim que sabemos que há alguém ali.",
8824
- bullet3Title: "Guardamos um vetor matemático do seu rosto",
8825
- bullet3Body: "Não a foto. A imagem é descartada.",
8826
8957
  primary: "Começar cadastro"
8827
8958
  }
8828
8959
  };
8960
+ const plural = (n) => n === 1 ? "dia" : "dias";
8961
+ function terceiroMarcador(flow, retentionDays) {
8962
+ const prazoDeclarado = Number.isFinite(retentionDays) && retentionDays > 0;
8963
+ if (flow === "registration") {
8964
+ return {
8965
+ title: "Guardamos um vetor matemático do seu rosto",
8966
+ body: prazoDeclarado ? `Não a foto. A imagem fica guardada por até ${retentionDays} ${plural(retentionDays)} e é apagada no fim desse prazo.` : "Não a foto. O prazo de guarda da imagem está na política de privacidade."
8967
+ };
8968
+ }
8969
+ return prazoDeclarado ? {
8970
+ title: `A imagem fica guardada por até ${retentionDays} ${plural(retentionDays)}`,
8971
+ body: "Ela é usada para a verificação e apagada no fim desse prazo."
8972
+ } : {
8973
+ title: "A imagem é tratada conforme a política de privacidade",
8974
+ body: "Ela é usada para a verificação; o prazo de guarda está descrito lá."
8975
+ };
8976
+ }
8829
8977
  const IconCamera = () => /* @__PURE__ */ jsxs(
8830
8978
  "svg",
8831
8979
  {
@@ -8890,6 +9038,8 @@ function ConsentModalComponent({
8890
9038
  onDecline
8891
9039
  }) {
8892
9040
  const copy = COPY[flow];
9041
+ const marcador3 = terceiroMarcador(flow, retentionDays);
9042
+ const prazoDeclarado = Number.isFinite(retentionDays) && retentionDays > 0;
8893
9043
  return /* @__PURE__ */ jsxs(Fragment, { children: [
8894
9044
  /* @__PURE__ */ jsx("style", { children: `
8895
9045
  @keyframes neofaceidConsentFadeIn { from { opacity: 0 } to { opacity: 1 } }
@@ -9066,23 +9216,19 @@ function ConsentModalComponent({
9066
9216
  /* @__PURE__ */ jsxs("div", { className: "neofaceid-consent-bullet", children: [
9067
9217
  /* @__PURE__ */ jsx("div", { className: "neofaceid-consent-bullet-icon", children: /* @__PURE__ */ jsx(IconShield, {}) }),
9068
9218
  /* @__PURE__ */ jsxs("div", { children: [
9069
- /* @__PURE__ */ jsx("p", { className: "neofaceid-consent-bullet-title", children: copy.bullet3Title }),
9070
- /* @__PURE__ */ jsx("p", { className: "neofaceid-consent-bullet-body", children: copy.bullet3Body })
9219
+ /* @__PURE__ */ jsx("p", { className: "neofaceid-consent-bullet-title", children: marcador3.title }),
9220
+ /* @__PURE__ */ jsx("p", { className: "neofaceid-consent-bullet-body", children: marcador3.body })
9071
9221
  ] })
9072
9222
  ] })
9073
9223
  ] }),
9074
- /* @__PURE__ */ jsxs(
9224
+ /* @__PURE__ */ jsx(
9075
9225
  "a",
9076
9226
  {
9077
9227
  href: privacyPolicyUrl,
9078
9228
  target: "_blank",
9079
9229
  rel: "noopener noreferrer",
9080
9230
  className: "neofaceid-consent-link",
9081
- children: [
9082
- "Ver política de privacidade (retenção de até ",
9083
- retentionDays,
9084
- " dias)"
9085
- ]
9231
+ children: prazoDeclarado ? `Ver política de privacidade (retenção de até ${retentionDays} ${plural(retentionDays)})` : "Ver política de privacidade"
9086
9232
  }
9087
9233
  ),
9088
9234
  /* @__PURE__ */ jsxs("div", { className: "neofaceid-consent-actions", children: [
@@ -9178,112 +9324,808 @@ const DEFAULT_CONSENT_INFO = {
9178
9324
  privacyPolicyUrl: DEFAULT_PRIVACY_URL,
9179
9325
  retentionDays: DEFAULT_RETENTION_DAYS
9180
9326
  };
9181
- function getHaloStyles(state) {
9182
- switch (state) {
9183
- case "centered":
9184
- return {
9185
- borderColor: "#0E9F6E",
9186
- boxShadow: "0 0 0 4px rgba(14, 159, 110, 0.8), 0 0 45px rgba(14, 159, 110, 0.45)",
9187
- svgStroke: "#0E9F6E",
9188
- badgeBg: "rgba(14, 159, 110, 0.95)",
9189
- badgeColor: "#FFFFFF"
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 = {}
9327
+ function Sheet({
9328
+ open,
9329
+ onDismiss,
9330
+ dismissOnOverlay = true,
9331
+ ariaLabel,
9332
+ ariaLabelledBy,
9333
+ children,
9334
+ className,
9335
+ style
9218
9336
  }) {
9219
- const videoRef = useRef(null);
9220
9337
  useEffect(() => {
9221
- const videoEl = videoRef.current;
9222
- if (!videoEl) return;
9223
- if (stream) {
9224
- videoEl.srcObject = stream;
9225
- videoEl.play().catch(() => {
9226
- });
9227
- } else {
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(
9338
+ injectThemeStyles();
9339
+ }, []);
9340
+ if (!open) return null;
9341
+ const handleOverlay = () => {
9342
+ if (dismissOnOverlay && onDismiss) onDismiss();
9343
+ };
9344
+ return /* @__PURE__ */ jsx(
9241
9345
  "div",
9242
9346
  {
9243
- className: `neofaceid-camera-oval-guide-container ${className}`,
9347
+ className: "neofaceid-root",
9348
+ role: "presentation",
9244
9349
  style: {
9350
+ position: "fixed",
9351
+ inset: 0,
9352
+ zIndex: 10001,
9245
9353
  display: "flex",
9246
- flexDirection: "column",
9247
9354
  alignItems: "center",
9248
9355
  justifyContent: "center",
9249
- position: "relative",
9250
- width: "100%",
9251
- maxHeight: "100%",
9252
- padding: "16px",
9253
- boxSizing: "border-box",
9254
- ...style
9356
+ padding: 20,
9357
+ background: `var(${CSS_VAR.overlay})`,
9358
+ backdropFilter: "blur(3px)",
9359
+ WebkitBackdropFilter: "blur(3px)"
9255
9360
  },
9256
- children: [
9257
- /* @__PURE__ */ jsx("style", { children: `
9258
- .neofaceid-oval-frame {
9259
- width: 320px;
9260
- height: 400px;
9261
- border-radius: 50%;
9262
- overflow: hidden;
9263
- position: relative;
9264
- background: #000000;
9265
- transition: box-shadow 0.3s ease, border-color 0.3s ease;
9361
+ onClick: handleOverlay,
9362
+ children: /* @__PURE__ */ jsx(
9363
+ "div",
9364
+ {
9365
+ role: "dialog",
9366
+ "aria-modal": "true",
9367
+ "aria-label": ariaLabel,
9368
+ "aria-labelledby": ariaLabelledBy,
9369
+ className,
9370
+ onClick: (e) => e.stopPropagation(),
9371
+ style: {
9372
+ background: `var(${CSS_VAR.surface})`,
9373
+ color: `var(${CSS_VAR.text})`,
9374
+ borderRadius: `var(${CSS_VAR.radius}, 16px)`,
9375
+ padding: 28,
9376
+ maxWidth: 440,
9377
+ width: "100%",
9378
+ boxShadow: "0 1px 3px rgba(10,19,32,.08)",
9379
+ boxSizing: "border-box",
9380
+ ...style
9381
+ },
9382
+ children
9266
9383
  }
9267
-
9268
- .neofaceid-oval-video {
9269
- width: 100%;
9270
- height: 100%;
9271
- object-fit: cover;
9272
- transform: scaleX(-1);
9273
- display: block;
9384
+ )
9385
+ }
9386
+ );
9387
+ }
9388
+ const NON_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["connected", "heartbeat", "processing"]);
9389
+ const DEFAULT_TIMEOUT_MS = 31e4;
9390
+ const DEFAULT_MAX_RECONNECTS = 5;
9391
+ const RECONNECT_BASE_DELAY_MS = 500;
9392
+ const RECONNECT_MAX_DELAY_MS = 8e3;
9393
+ function delay(ms, signal) {
9394
+ return new Promise((resolve) => {
9395
+ const id = setTimeout(resolve, ms);
9396
+ signal == null ? void 0 : signal.addEventListener(
9397
+ "abort",
9398
+ () => {
9399
+ clearTimeout(id);
9400
+ resolve();
9401
+ },
9402
+ { once: true }
9403
+ );
9404
+ });
9405
+ }
9406
+ async function consumeSseStream(options) {
9407
+ const {
9408
+ requestTicket,
9409
+ buildUrl,
9410
+ classify,
9411
+ timeoutMs = DEFAULT_TIMEOUT_MS,
9412
+ maxReconnects = DEFAULT_MAX_RECONNECTS,
9413
+ signal,
9414
+ eventSourceFactory,
9415
+ onProgress
9416
+ } = options;
9417
+ const makeEventSource = eventSourceFactory ?? ((url) => {
9418
+ const Ctor = globalThis.EventSource;
9419
+ if (!Ctor) {
9420
+ throw new NeoFaceError(
9421
+ "EventSource indisponível neste ambiente",
9422
+ ErrorType.INITIALIZATION_ERROR
9423
+ );
9424
+ }
9425
+ return new Ctor(url);
9426
+ });
9427
+ const startedAt = Date.now();
9428
+ let reconnects = 0;
9429
+ for (; ; ) {
9430
+ if (signal == null ? void 0 : signal.aborted) {
9431
+ throw new NeoFaceError("Fluxo cancelado", ErrorType.UNKNOWN);
9432
+ }
9433
+ const remaining = timeoutMs - (Date.now() - startedAt);
9434
+ if (remaining <= 0) {
9435
+ throw new NeoFaceError("Tempo esgotado aguardando resposta", ErrorType.NETWORK);
9436
+ }
9437
+ const ticket = await requestTicket();
9438
+ const outcome = await new Promise((resolve, reject) => {
9439
+ let source;
9440
+ try {
9441
+ source = makeEventSource(buildUrl(ticket));
9442
+ } catch (err) {
9443
+ reject(err);
9444
+ return;
9445
+ }
9446
+ let settled = false;
9447
+ let timerId;
9448
+ let onAbort;
9449
+ const cleanup = () => {
9450
+ if (timerId !== void 0) clearTimeout(timerId);
9451
+ if (onAbort) signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
9452
+ source.close();
9453
+ };
9454
+ timerId = setTimeout(() => {
9455
+ if (settled) return;
9456
+ settled = true;
9457
+ cleanup();
9458
+ reject(new NeoFaceError("Tempo esgotado aguardando resposta", ErrorType.NETWORK));
9459
+ }, remaining);
9460
+ onAbort = () => {
9461
+ if (settled) return;
9462
+ settled = true;
9463
+ cleanup();
9464
+ reject(new NeoFaceError("Fluxo cancelado", ErrorType.UNKNOWN));
9465
+ };
9466
+ signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
9467
+ source.onmessage = (event) => {
9468
+ if (settled) return;
9469
+ let frame;
9470
+ try {
9471
+ frame = JSON.parse(event.data);
9472
+ } catch {
9473
+ return;
9274
9474
  }
9275
-
9276
- .neofaceid-oval-svg-overlay {
9277
- position: absolute;
9278
- inset: 0;
9279
- width: 100%;
9280
- height: 100%;
9281
- pointer-events: none;
9282
- z-index: 2;
9475
+ if (frame.status && NON_TERMINAL_STATUSES.has(frame.status)) {
9476
+ if (onProgress) onProgress(frame);
9477
+ return;
9283
9478
  }
9284
-
9285
- .neofaceid-oval-instruction-badge {
9286
- margin-top: 20px;
9479
+ const verdict = classify(frame);
9480
+ if (verdict.kind === "pending") {
9481
+ if (onProgress) onProgress(frame);
9482
+ return;
9483
+ }
9484
+ settled = true;
9485
+ cleanup();
9486
+ resolve({ type: "settled", verdict });
9487
+ };
9488
+ source.onerror = () => {
9489
+ if (settled) return;
9490
+ settled = true;
9491
+ cleanup();
9492
+ resolve({ type: "transport" });
9493
+ };
9494
+ });
9495
+ if (outcome.type === "settled") {
9496
+ const { verdict } = outcome;
9497
+ if (verdict.kind === "resolve") return verdict.value;
9498
+ throw verdict.error;
9499
+ }
9500
+ reconnects += 1;
9501
+ if (reconnects > maxReconnects) {
9502
+ throw new NeoFaceError(
9503
+ `Conexão perdida após ${maxReconnects} tentativas de reconexão`,
9504
+ ErrorType.NETWORK
9505
+ );
9506
+ }
9507
+ const backoff = Math.min(
9508
+ RECONNECT_BASE_DELAY_MS * 2 ** (reconnects - 1),
9509
+ RECONNECT_MAX_DELAY_MS
9510
+ );
9511
+ await delay(backoff, signal);
9512
+ }
9513
+ }
9514
+ const BASE_STYLE = {
9515
+ minHeight: 44,
9516
+ minWidth: 44,
9517
+ padding: "12px 20px",
9518
+ borderRadius: 16,
9519
+ border: "none",
9520
+ fontSize: 15,
9521
+ fontWeight: 600,
9522
+ fontFamily: "inherit",
9523
+ cursor: "pointer",
9524
+ transition: "background 120ms ease-out",
9525
+ display: "inline-flex",
9526
+ alignItems: "center",
9527
+ justifyContent: "center",
9528
+ gap: 8
9529
+ };
9530
+ const Button = forwardRef(
9531
+ ({ variant = "primary", style, type = "button", ...rest }, ref) => {
9532
+ const variantStyle = variant === "primary" ? {
9533
+ background: `var(${CSS_VAR.action})`,
9534
+ color: `var(${CSS_VAR.actionText})`
9535
+ } : variant === "secondary" ? {
9536
+ background: `var(${CSS_VAR.surfaceMuted})`,
9537
+ color: `var(${CSS_VAR.text})`
9538
+ } : {
9539
+ background: "transparent",
9540
+ color: `var(${CSS_VAR.textMuted})`
9541
+ };
9542
+ return (
9543
+ // eslint-disable-next-line react/button-has-type
9544
+ /* @__PURE__ */ jsx(
9545
+ "button",
9546
+ {
9547
+ ref,
9548
+ type,
9549
+ ...rest,
9550
+ style: { ...BASE_STYLE, ...variantStyle, ...style }
9551
+ }
9552
+ )
9553
+ );
9554
+ }
9555
+ );
9556
+ Button.displayName = "Button";
9557
+ function StatusPill({ tone, children, icon, className }) {
9558
+ const palette = SEMANTIC[tone];
9559
+ return /* @__PURE__ */ jsxs(
9560
+ "span",
9561
+ {
9562
+ className,
9563
+ style: {
9564
+ display: "inline-flex",
9565
+ alignItems: "center",
9566
+ gap: 6,
9567
+ padding: "4px 10px",
9568
+ borderRadius: 9999,
9569
+ background: palette.bg,
9570
+ color: palette.fg,
9571
+ fontSize: 13,
9572
+ fontWeight: 600,
9573
+ fontFamily: "inherit",
9574
+ lineHeight: 1.3
9575
+ },
9576
+ children: [
9577
+ icon,
9578
+ children
9579
+ ]
9580
+ }
9581
+ );
9582
+ }
9583
+ const STYLE_ID$2 = "neofaceid-awaiting-push-styles";
9584
+ const CSS$1 = `
9585
+ .neofaceid-root .nfid-push {
9586
+ display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
9587
+ padding: 8px 8px 4px;
9588
+ }
9589
+ .neofaceid-root .nfid-push h2 {
9590
+ margin: 6px 0 0; font-size: 20px; font-weight: 700; color: var(--nfid-text); line-height: 1.25;
9591
+ letter-spacing: -0.015em;
9592
+ }
9593
+ .neofaceid-root .nfid-push p {
9594
+ margin: 0; font-size: 14px; color: var(--nfid-text-muted); line-height: 1.5;
9595
+ }
9596
+ .neofaceid-root .nfid-push-phone-icon {
9597
+ width: 56px; height: 56px; color: var(--nfid-action);
9598
+ }
9599
+ .neofaceid-root .nfid-push-code {
9600
+ display: flex; flex-direction: column; align-items: center; gap: 6px;
9601
+ margin-top: 14px; padding: 16px 28px;
9602
+ background: var(--nfid-surface-muted); border-radius: 16px; width: 100%;
9603
+ }
9604
+ .neofaceid-root .nfid-push-code-label {
9605
+ font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
9606
+ color: var(--nfid-text-subtle);
9607
+ }
9608
+ .neofaceid-root .nfid-push-code-value {
9609
+ font-size: 52px; font-weight: 800; line-height: 1; letter-spacing: 0.08em;
9610
+ color: var(--nfid-text); font-variant-numeric: tabular-nums;
9611
+ }
9612
+ .neofaceid-root .nfid-push-countdown {
9613
+ font-size: 13px; color: var(--nfid-text-muted); font-variant-numeric: tabular-nums;
9614
+ margin-top: 12px;
9615
+ }
9616
+ .neofaceid-root .nfid-push-actions {
9617
+ display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 16px;
9618
+ }
9619
+ .neofaceid-root .nfid-push-footer {
9620
+ display: flex; justify-content: center; margin-top: 16px; padding-top: 12px;
9621
+ border-top: 1px solid var(--nfid-line);
9622
+ width: 100%;
9623
+ }
9624
+ .neofaceid-root .nfid-push-pulse {
9625
+ animation: nfidPushPulse 1.8s ease-in-out infinite;
9626
+ }
9627
+ @keyframes nfidPushPulse {
9628
+ 0%, 100% { opacity: 1; }
9629
+ 50% { opacity: 0.45; }
9630
+ }
9631
+ @media (prefers-reduced-motion: reduce) {
9632
+ .neofaceid-root .nfid-push-pulse { animation: none; }
9633
+ }
9634
+ `;
9635
+ function formatRemaining(seconds) {
9636
+ const safe = Math.max(0, seconds);
9637
+ const mm = Math.floor(safe / 60);
9638
+ const ss = safe % 60;
9639
+ return `${mm}:${String(ss).padStart(2, "0")}`;
9640
+ }
9641
+ function AwaitingPushView({
9642
+ appName,
9643
+ numericCode,
9644
+ remainingSeconds,
9645
+ onCancel
9646
+ }) {
9647
+ useEffect(() => {
9648
+ injectThemeStyles();
9649
+ injectScopedStyles(STYLE_ID$2, CSS$1);
9650
+ }, []);
9651
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "status", "aria-live": "polite", children: [
9652
+ /* @__PURE__ */ jsx(StatusPill, { tone: "attention", children: "Aguardando confirmação" }),
9653
+ /* @__PURE__ */ jsxs(
9654
+ "svg",
9655
+ {
9656
+ className: "nfid-push-phone-icon nfid-push-pulse",
9657
+ viewBox: "0 0 24 24",
9658
+ fill: "none",
9659
+ stroke: "currentColor",
9660
+ strokeWidth: "1.75",
9661
+ strokeLinecap: "round",
9662
+ strokeLinejoin: "round",
9663
+ "aria-hidden": "true",
9664
+ children: [
9665
+ /* @__PURE__ */ jsx("rect", { x: "6", y: "2", width: "12", height: "20", rx: "3" }),
9666
+ /* @__PURE__ */ jsx("path", { d: "M11 18h2" })
9667
+ ]
9668
+ }
9669
+ ),
9670
+ /* @__PURE__ */ jsx("h2", { children: "Confirme no seu celular" }),
9671
+ /* @__PURE__ */ jsxs("p", { children: [
9672
+ "Enviamos um pedido de confirmação para o seu aparelho. Abra o NeoFaceID Push e confirme com o seu rosto para continuar no ",
9673
+ appName,
9674
+ "."
9675
+ ] }),
9676
+ numericCode && /* @__PURE__ */ jsxs("div", { className: "nfid-push-code", children: [
9677
+ /* @__PURE__ */ jsx("span", { className: "nfid-push-code-label", children: "Digite este número no celular" }),
9678
+ /* @__PURE__ */ jsx(
9679
+ "span",
9680
+ {
9681
+ className: "nfid-push-code-value",
9682
+ "aria-label": `Código de confirmação: ${numericCode.split("").join(" ")}`,
9683
+ children: numericCode
9684
+ }
9685
+ )
9686
+ ] }),
9687
+ /* @__PURE__ */ jsxs("p", { className: "nfid-push-countdown", children: [
9688
+ "Expira em ",
9689
+ formatRemaining(remainingSeconds)
9690
+ ] }),
9691
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Cancelar" }) }),
9692
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9693
+ ] });
9694
+ }
9695
+ function PushExpiredView({ onFallback, onCancel }) {
9696
+ useEffect(() => {
9697
+ injectThemeStyles();
9698
+ injectScopedStyles(STYLE_ID$2, CSS$1);
9699
+ }, []);
9700
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
9701
+ /* @__PURE__ */ jsxs(
9702
+ "svg",
9703
+ {
9704
+ className: "nfid-push-phone-icon",
9705
+ viewBox: "0 0 24 24",
9706
+ fill: "none",
9707
+ stroke: "currentColor",
9708
+ strokeWidth: "1.75",
9709
+ strokeLinecap: "round",
9710
+ strokeLinejoin: "round",
9711
+ "aria-hidden": "true",
9712
+ style: { color: "var(--nfid-text-muted)" },
9713
+ children: [
9714
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
9715
+ /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
9716
+ ]
9717
+ }
9718
+ ),
9719
+ /* @__PURE__ */ jsx("h2", { children: "O tempo esgotou" }),
9720
+ /* @__PURE__ */ jsx("p", { children: "Não recebemos a confirmação do seu celular a tempo." }),
9721
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: onFallback ? /* @__PURE__ */ jsxs(Fragment, { children: [
9722
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onFallback, children: "Entrar de outro jeito" }),
9723
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Agora não" })
9724
+ ] }) : /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
9725
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9726
+ ] });
9727
+ }
9728
+ function PushDeniedView({ appName, onCancel }) {
9729
+ useEffect(() => {
9730
+ injectThemeStyles();
9731
+ injectScopedStyles(STYLE_ID$2, CSS$1);
9732
+ }, []);
9733
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
9734
+ /* @__PURE__ */ jsxs(
9735
+ "svg",
9736
+ {
9737
+ className: "nfid-push-phone-icon",
9738
+ viewBox: "0 0 24 24",
9739
+ fill: "none",
9740
+ stroke: "currentColor",
9741
+ strokeWidth: "1.75",
9742
+ strokeLinecap: "round",
9743
+ strokeLinejoin: "round",
9744
+ "aria-hidden": "true",
9745
+ style: { color: "#D64545" },
9746
+ children: [
9747
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
9748
+ /* @__PURE__ */ jsx("path", { d: "M15 9l-6 6M9 9l6 6" })
9749
+ ]
9750
+ }
9751
+ ),
9752
+ /* @__PURE__ */ jsx("h2", { children: "Confirmação recusada" }),
9753
+ /* @__PURE__ */ jsxs("p", { children: [
9754
+ "O pedido foi recusado no celular. Se não foi você, procure o suporte do ",
9755
+ appName,
9756
+ "."
9757
+ ] }),
9758
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
9759
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9760
+ ] });
9761
+ }
9762
+ const CONTAINER_ID = "neofaceid-push-approval-container";
9763
+ function classifyAuthorizationFrame(frame) {
9764
+ var _a2, _b;
9765
+ switch (frame.status) {
9766
+ case "approved":
9767
+ case "success":
9768
+ return { kind: "resolve", value: { status: "approved", data: frame.data ?? {} } };
9769
+ case "denied":
9770
+ return {
9771
+ kind: "resolve",
9772
+ value: { status: "denied", reason: ((_a2 = frame.data) == null ? void 0 : _a2.reason) ?? frame.message }
9773
+ };
9774
+ case "expired":
9775
+ return { kind: "resolve", value: { status: "expired" } };
9776
+ case "failed":
9777
+ case "error":
9778
+ return {
9779
+ kind: "reject",
9780
+ error: new NeoFaceError(
9781
+ frame.message ?? ((_b = frame.data) == null ? void 0 : _b.message) ?? "Falha na confirmação",
9782
+ ErrorType.API_ERROR
9783
+ )
9784
+ };
9785
+ default:
9786
+ return { kind: "pending" };
9787
+ }
9788
+ }
9789
+ function PushApprovalModal({
9790
+ requestId,
9791
+ applicationToken,
9792
+ expiresIn,
9793
+ numericCode,
9794
+ appName,
9795
+ onFallback,
9796
+ onSettle
9797
+ }) {
9798
+ const headerAppName = appName ?? getAppName() ?? "sua aplicação";
9799
+ const [remaining, setRemaining] = useState(expiresIn);
9800
+ const [phase, setPhase] = useState({ kind: "waiting" });
9801
+ useEffect(() => {
9802
+ if (phase.kind !== "waiting") return void 0;
9803
+ const id = setInterval(() => {
9804
+ setRemaining((r) => r > 0 ? r - 1 : 0);
9805
+ }, 1e3);
9806
+ return () => clearInterval(id);
9807
+ }, [phase.kind]);
9808
+ useEffect(() => {
9809
+ const controller = new AbortController();
9810
+ let done = false;
9811
+ const base = getBaseUrl();
9812
+ const ticketUrl = `${base}/api/v1/authorization/events/${encodeURIComponent(requestId)}/ticket/`;
9813
+ consumeSseStream({
9814
+ signal: controller.signal,
9815
+ timeoutMs: (expiresIn + 30) * 1e3,
9816
+ requestTicket: async () => {
9817
+ var _a2;
9818
+ const res = await fetch(ticketUrl, {
9819
+ method: "POST",
9820
+ headers: { "Content-Type": "application/json", "X-App-Token": applicationToken }
9821
+ });
9822
+ if (!res.ok) {
9823
+ throw new NeoFaceError(
9824
+ `Falha ao abrir canal de confirmação: ${res.status}`,
9825
+ res.status === 401 || res.status === 403 ? ErrorType.INVALID_TOKEN : ErrorType.NETWORK
9826
+ );
9827
+ }
9828
+ const body = await res.json();
9829
+ const ticket = ((_a2 = body == null ? void 0 : body.data) == null ? void 0 : _a2.ticket) ?? (body == null ? void 0 : body.ticket);
9830
+ if (!ticket) {
9831
+ throw new NeoFaceError("Canal de confirmação sem ticket", ErrorType.API_ERROR);
9832
+ }
9833
+ return ticket;
9834
+ },
9835
+ buildUrl: (ticket) => `${base}/api/v1/authorization/events/${encodeURIComponent(requestId)}/?ticket=${encodeURIComponent(ticket)}`,
9836
+ classify: classifyAuthorizationFrame
9837
+ }).then((outcome) => {
9838
+ if (done) return;
9839
+ done = true;
9840
+ if (outcome.status === "denied") {
9841
+ setPhase({ kind: "denied" });
9842
+ return;
9843
+ }
9844
+ if (outcome.status === "expired") {
9845
+ setPhase({ kind: "expired" });
9846
+ return;
9847
+ }
9848
+ onSettle(outcome);
9849
+ }).catch(() => {
9850
+ if (done || controller.signal.aborted) return;
9851
+ done = true;
9852
+ setPhase({ kind: "expired" });
9853
+ });
9854
+ return () => {
9855
+ done = true;
9856
+ controller.abort();
9857
+ };
9858
+ }, [requestId]);
9859
+ if (phase.kind === "denied") {
9860
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Confirmação recusada", children: /* @__PURE__ */ jsx(PushDeniedView, { appName: headerAppName, onCancel: () => onSettle({ status: "denied" }) }) });
9861
+ }
9862
+ if (phase.kind === "expired") {
9863
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Tempo esgotado", children: /* @__PURE__ */ jsx(
9864
+ PushExpiredView,
9865
+ {
9866
+ onFallback: onFallback ? () => {
9867
+ onFallback();
9868
+ onSettle({ status: "expired" });
9869
+ } : void 0,
9870
+ onCancel: () => onSettle({ status: "expired" })
9871
+ }
9872
+ ) });
9873
+ }
9874
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Aguardando confirmação no celular", children: /* @__PURE__ */ jsx(
9875
+ AwaitingPushView,
9876
+ {
9877
+ appName: headerAppName,
9878
+ numericCode,
9879
+ remainingSeconds: remaining,
9880
+ onCancel: () => onSettle({ status: "cancelled" })
9881
+ }
9882
+ ) });
9883
+ }
9884
+ function awaitPushApproval(options) {
9885
+ return new Promise((resolve) => {
9886
+ const container = document.createElement("div");
9887
+ container.id = CONTAINER_ID;
9888
+ document.body.appendChild(container);
9889
+ const root = createRoot(container);
9890
+ const settle = (outcome) => {
9891
+ root.unmount();
9892
+ if (document.body.contains(container)) document.body.removeChild(container);
9893
+ resolve(outcome);
9894
+ };
9895
+ root.render(/* @__PURE__ */ jsx(PushApprovalModal, { ...options, onSettle: settle }));
9896
+ });
9897
+ }
9898
+ function evaluateFacePosition(box, videoWidth, videoHeight) {
9899
+ if (!box || !videoWidth || !videoHeight) {
9900
+ return {
9901
+ faceState: "searching",
9902
+ instruction: "Centralize o rosto"
9903
+ };
9904
+ }
9905
+ const faceCenterX = box.x + box.width / 2;
9906
+ const faceCenterY = box.y + box.height / 2;
9907
+ const frameCenterX = videoWidth / 2;
9908
+ const frameCenterY = videoHeight / 2;
9909
+ const offsetX = Math.abs(faceCenterX - frameCenterX) / videoWidth;
9910
+ const offsetY = Math.abs(faceCenterY - frameCenterY) / videoHeight;
9911
+ const faceArea = box.width * box.height;
9912
+ const frameArea = videoWidth * videoHeight;
9913
+ const faceAreaRatio = faceArea / frameArea;
9914
+ if (faceAreaRatio < 0.08) {
9915
+ return {
9916
+ faceState: "too-far",
9917
+ instruction: "Aproxime-se"
9918
+ };
9919
+ }
9920
+ if (faceAreaRatio > 0.45) {
9921
+ return {
9922
+ faceState: "too-close",
9923
+ instruction: "Afaste-se"
9924
+ };
9925
+ }
9926
+ if (offsetX > 0.18 || offsetY > 0.18) {
9927
+ return {
9928
+ faceState: "off-center",
9929
+ instruction: "Centralize o rosto"
9930
+ };
9931
+ }
9932
+ return {
9933
+ faceState: "centered",
9934
+ instruction: "Perfeito, mantenha"
9935
+ };
9936
+ }
9937
+ function showCameraOvalGuideModal(initialOptions = {}) {
9938
+ const overlayDiv = document.createElement("div");
9939
+ overlayDiv.className = "neofaceid-camera-oval-guide-overlay-root";
9940
+ Object.assign(overlayDiv.style, {
9941
+ position: "fixed",
9942
+ top: "0",
9943
+ left: "0",
9944
+ width: "100vw",
9945
+ height: "100vh",
9946
+ backgroundColor: "rgba(0, 0, 0, 0.85)",
9947
+ backdropFilter: "blur(8px)",
9948
+ zIndex: "99999",
9949
+ display: "flex",
9950
+ flexDirection: "column",
9951
+ alignItems: "center",
9952
+ justifyContent: "center"
9953
+ });
9954
+ document.body.appendChild(overlayDiv);
9955
+ let root = createRoot(overlayDiv);
9956
+ let currentStream = initialOptions.stream || null;
9957
+ let currentFaceState = initialOptions.faceState || "searching";
9958
+ let currentInstruction = initialOptions.instruction || "Centralize o rosto";
9959
+ const renderModal = (stream, faceState, instruction) => {
9960
+ if (!root) return;
9961
+ root.render(
9962
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }, children: [
9963
+ initialOptions.onCancel && /* @__PURE__ */ jsx(
9964
+ "button",
9965
+ {
9966
+ onClick: () => {
9967
+ var _a2;
9968
+ handle.close();
9969
+ (_a2 = initialOptions.onCancel) == null ? void 0 : _a2.call(initialOptions);
9970
+ },
9971
+ style: {
9972
+ position: "absolute",
9973
+ top: "20px",
9974
+ right: "20px",
9975
+ background: "rgba(255, 255, 255, 0.2)",
9976
+ border: "none",
9977
+ color: "#fff",
9978
+ fontSize: "24px",
9979
+ width: "40px",
9980
+ height: "40px",
9981
+ borderRadius: "50%",
9982
+ cursor: "pointer",
9983
+ zIndex: 10,
9984
+ display: "flex",
9985
+ alignItems: "center",
9986
+ justifyContent: "center"
9987
+ },
9988
+ "aria-label": "Cancelar",
9989
+ children: "✕"
9990
+ }
9991
+ ),
9992
+ /* @__PURE__ */ jsx(
9993
+ CameraOvalGuide,
9994
+ {
9995
+ stream,
9996
+ faceState,
9997
+ instruction
9998
+ }
9999
+ )
10000
+ ] })
10001
+ );
10002
+ };
10003
+ renderModal(currentStream, currentFaceState, currentInstruction);
10004
+ const handle = {
10005
+ update: (stream, faceState, instruction) => {
10006
+ currentStream = stream;
10007
+ currentFaceState = faceState;
10008
+ currentInstruction = instruction;
10009
+ renderModal(stream, faceState, instruction);
10010
+ },
10011
+ close: () => {
10012
+ if (root) {
10013
+ root.unmount();
10014
+ root = null;
10015
+ }
10016
+ if (overlayDiv.parentElement) {
10017
+ overlayDiv.parentElement.removeChild(overlayDiv);
10018
+ }
10019
+ }
10020
+ };
10021
+ return handle;
10022
+ }
10023
+ function getHaloStyles(state) {
10024
+ switch (state) {
10025
+ case "centered":
10026
+ return {
10027
+ borderColor: "#0E9F6E",
10028
+ boxShadow: "0 0 0 4px rgba(14, 159, 110, 0.8), 0 0 45px rgba(14, 159, 110, 0.45)",
10029
+ svgStroke: "#0E9F6E",
10030
+ badgeBg: "rgba(14, 159, 110, 0.95)",
10031
+ badgeColor: "#FFFFFF"
10032
+ };
10033
+ case "too-far":
10034
+ case "too-close":
10035
+ case "off-center":
10036
+ return {
10037
+ borderColor: "#F59E0B",
10038
+ boxShadow: "0 0 0 4px rgba(245, 158, 11, 0.7), 0 0 35px rgba(245, 158, 11, 0.35)",
10039
+ svgStroke: "#F59E0B",
10040
+ badgeBg: "rgba(245, 158, 11, 0.95)",
10041
+ badgeColor: "#1F2937"
10042
+ };
10043
+ case "searching":
10044
+ default:
10045
+ return {
10046
+ borderColor: "rgba(156, 163, 175, 0.5)",
10047
+ boxShadow: "0 0 0 4px rgba(156, 163, 175, 0.3), 0 0 20px rgba(156, 163, 175, 0.15)",
10048
+ svgStroke: "#9CA3AF",
10049
+ badgeBg: "rgba(55, 65, 81, 0.9)",
10050
+ badgeColor: "#FFFFFF"
10051
+ };
10052
+ }
10053
+ }
10054
+ function CameraOvalGuide({
10055
+ stream,
10056
+ faceState,
10057
+ instruction,
10058
+ className = "",
10059
+ style = {}
10060
+ }) {
10061
+ const videoRef = useRef(null);
10062
+ useEffect(() => {
10063
+ const videoEl = videoRef.current;
10064
+ if (!videoEl) return;
10065
+ if (stream) {
10066
+ videoEl.srcObject = stream;
10067
+ videoEl.play().catch(() => {
10068
+ });
10069
+ } else {
10070
+ videoEl.srcObject = null;
10071
+ }
10072
+ return () => {
10073
+ if (stream) {
10074
+ stream.getTracks().forEach((track) => track.stop());
10075
+ }
10076
+ if (videoEl) {
10077
+ videoEl.srcObject = null;
10078
+ }
10079
+ };
10080
+ }, [stream]);
10081
+ const halo = getHaloStyles(faceState);
10082
+ return /* @__PURE__ */ jsxs(
10083
+ "div",
10084
+ {
10085
+ className: `neofaceid-camera-oval-guide-container ${className}`,
10086
+ style: {
10087
+ display: "flex",
10088
+ flexDirection: "column",
10089
+ alignItems: "center",
10090
+ justifyContent: "center",
10091
+ position: "relative",
10092
+ width: "100%",
10093
+ maxHeight: "100%",
10094
+ padding: "16px",
10095
+ boxSizing: "border-box",
10096
+ ...style
10097
+ },
10098
+ children: [
10099
+ /* @__PURE__ */ jsx("style", { children: `
10100
+ .neofaceid-oval-frame {
10101
+ width: 320px;
10102
+ height: 400px;
10103
+ border-radius: 50%;
10104
+ overflow: hidden;
10105
+ position: relative;
10106
+ background: #000000;
10107
+ transition: box-shadow 0.3s ease, border-color 0.3s ease;
10108
+ }
10109
+
10110
+ .neofaceid-oval-video {
10111
+ width: 100%;
10112
+ height: 100%;
10113
+ object-fit: cover;
10114
+ transform: scaleX(-1);
10115
+ display: block;
10116
+ }
10117
+
10118
+ .neofaceid-oval-svg-overlay {
10119
+ position: absolute;
10120
+ inset: 0;
10121
+ width: 100%;
10122
+ height: 100%;
10123
+ pointer-events: none;
10124
+ z-index: 2;
10125
+ }
10126
+
10127
+ .neofaceid-oval-instruction-badge {
10128
+ margin-top: 20px;
9287
10129
  padding: 10px 20px;
9288
10130
  border-radius: 20px;
9289
10131
  font-size: 15px;
@@ -10789,7 +11631,6 @@ const CAMERA_READY_DELAY = 200;
10789
11631
  const CAMERA_STABILITY_DELAY = 100;
10790
11632
  const FACE_DETECTION_TIME = 5e3;
10791
11633
  const DETECTION_INTERVAL = 100;
10792
- const SUCCESS_DELAY = 200;
10793
11634
  let modelsLoaded = false;
10794
11635
  let modelsLoading = null;
10795
11636
  async function preloadFaceDetectionModels() {
@@ -10946,14 +11787,13 @@ async function captureFaceSilently() {
10946
11787
  });
10947
11788
  });
10948
11789
  }
10949
- async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL, overlay) {
11790
+ async function detectFaceWithPosition(video, stream, guideModal, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL) {
10950
11791
  return new Promise((resolve) => {
10951
11792
  let attempts = 0;
10952
11793
  const maxAttempts = Math.ceil(maxTime / interval);
10953
11794
  let timeoutId = null;
10954
11795
  let isResolved = false;
10955
- let overlayShowingWaiting = false;
10956
- const ATTEMPTS_BEFORE_WAITING = Math.ceil(3e3 / interval);
11796
+ let centeredDurationMs = 0;
10957
11797
  const finish = (success) => {
10958
11798
  if (isResolved) return;
10959
11799
  isResolved = true;
@@ -10964,11 +11804,7 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
10964
11804
  if (isResolved) return;
10965
11805
  attempts++;
10966
11806
  if (!modelsLoaded) {
10967
- if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
10968
- overlay.updateStatus("waiting-for-face");
10969
- overlayShowingWaiting = true;
10970
- console.log("⏳ Models not loaded - waiting for face...");
10971
- }
11807
+ guideModal.update(stream, "searching", "Carregando modelos de detecção...");
10972
11808
  if (attempts >= maxAttempts) {
10973
11809
  finish(false);
10974
11810
  return;
@@ -10981,33 +11817,29 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
10981
11817
  video,
10982
11818
  new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.3 })
10983
11819
  );
10984
- if (detection) {
10985
- if (overlay && overlayShowingWaiting) {
10986
- overlay.updateStatus("detecting");
10987
- overlayShowingWaiting = false;
10988
- console.log("👤 Face appeared");
11820
+ const { faceState, instruction } = evaluateFacePosition(
11821
+ detection ? detection.box : null,
11822
+ video.videoWidth,
11823
+ video.videoHeight
11824
+ );
11825
+ guideModal.update(stream, faceState, instruction);
11826
+ if (faceState === "centered") {
11827
+ centeredDurationMs += interval;
11828
+ if (centeredDurationMs >= 1e3) {
11829
+ console.log("✅ Rosto mantido centralizado por 1s consecutivo");
11830
+ finish(true);
11831
+ return;
10989
11832
  }
10990
- console.log("✅ Face detected!");
10991
- finish(true);
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...");
11833
+ } else {
11834
+ centeredDurationMs = 0;
10998
11835
  }
10999
11836
  if (attempts >= maxAttempts) {
11000
- console.warn("⏱️ Face detection timeout - no face found");
11001
11837
  finish(false);
11002
11838
  return;
11003
11839
  }
11004
11840
  timeoutId = window.setTimeout(checkFace, interval);
11005
11841
  } catch (error) {
11006
- console.error("❌ Face detection error:", error);
11007
- if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
11008
- overlay.updateStatus("waiting-for-face");
11009
- overlayShowingWaiting = true;
11010
- }
11842
+ console.error("❌ Erro na detecção facial:", error);
11011
11843
  if (attempts >= maxAttempts) {
11012
11844
  finish(false);
11013
11845
  return;
@@ -11017,19 +11849,19 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
11017
11849
  };
11018
11850
  window.setTimeout(() => {
11019
11851
  if (!isResolved) {
11020
- console.warn("⏱️ Face detection safety timeout reached");
11021
11852
  finish(false);
11022
11853
  }
11023
11854
  }, maxTime + 500);
11024
11855
  checkFace();
11025
11856
  });
11026
11857
  }
11027
- async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry = false) {
11028
- if (!isRetry) {
11029
- overlay.updateStatus("preparing");
11030
- } else {
11031
- overlay.updateStatus("detecting");
11032
- }
11858
+ async function attemptLogin(applicationToken, fastMode = false, isRetry = false, onCancel) {
11859
+ const guideModal = showCameraOvalGuideModal({
11860
+ stream: null,
11861
+ faceState: "searching",
11862
+ instruction: "Iniciando câmera...",
11863
+ onCancel
11864
+ });
11033
11865
  const video = document.createElement("video");
11034
11866
  video.style.position = "fixed";
11035
11867
  video.style.top = "-9999px";
@@ -11060,6 +11892,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
11060
11892
  });
11061
11893
  }
11062
11894
  video.srcObject = stream;
11895
+ guideModal.update(stream, "searching", "Centralize o rosto");
11063
11896
  await new Promise((resolve) => {
11064
11897
  if (video.readyState >= 1) {
11065
11898
  resolve();
@@ -11088,18 +11921,19 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
11088
11921
  checkSize();
11089
11922
  }
11090
11923
  });
11091
- overlay.updateStatus("detecting");
11092
11924
  await new Promise((resolve) => setTimeout(resolve, CAMERA_STABILITY_DELAY));
11093
11925
  let faceDetected = true;
11094
11926
  if (!fastMode) {
11095
- faceDetected = await detectFaceQuickly(
11927
+ faceDetected = await detectFaceWithPosition(
11096
11928
  video,
11929
+ stream,
11930
+ guideModal,
11097
11931
  FACE_DETECTION_TIME,
11098
- DETECTION_INTERVAL,
11099
- overlay
11932
+ DETECTION_INTERVAL
11100
11933
  );
11101
11934
  }
11102
11935
  if (!faceDetected) {
11936
+ guideModal.close();
11103
11937
  if (stream) {
11104
11938
  stream.getTracks().forEach((track) => track.stop());
11105
11939
  }
@@ -11107,16 +11941,17 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
11107
11941
  video.parentElement.removeChild(video);
11108
11942
  }
11109
11943
  throw new NeoFaceError(
11110
- "Nenhum rosto detectado. Por favor, posicione seu rosto na frente da câmera e tente novamente.",
11944
+ "Nenhum rosto centralizado detectado. Por favor, tente novamente.",
11111
11945
  ErrorType.VALIDATION_ERROR
11112
11946
  );
11113
11947
  }
11114
- overlay.updateStatus("capturing");
11948
+ guideModal.update(stream, "centered", "Capturando imagem...");
11115
11949
  const canvas = document.createElement("canvas");
11116
11950
  canvas.width = video.videoWidth;
11117
11951
  canvas.height = video.videoHeight;
11118
11952
  const ctx = canvas.getContext("2d");
11119
11953
  if (!ctx) {
11954
+ guideModal.close();
11120
11955
  if (stream) {
11121
11956
  stream.getTracks().forEach((track) => track.stop());
11122
11957
  }
@@ -11126,12 +11961,6 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
11126
11961
  throw new NeoFaceError("Erro ao capturar imagem", ErrorType.CAPTURE_ERROR);
11127
11962
  }
11128
11963
  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
11964
  const imageBlob = await new Promise((resolve, reject) => {
11136
11965
  canvas.toBlob(
11137
11966
  (blob) => {
@@ -11146,13 +11975,21 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
11146
11975
  0.85
11147
11976
  );
11148
11977
  });
11149
- overlay.updateStatus("verifying");
11978
+ guideModal.update(stream, "centered", "Verificando com o servidor...");
11150
11979
  const result = await loginWithBiometric(imageBlob, applicationToken);
11980
+ guideModal.close();
11981
+ if (stream) {
11982
+ stream.getTracks().forEach((track) => track.stop());
11983
+ }
11984
+ if (video.parentElement) {
11985
+ video.parentElement.removeChild(video);
11986
+ }
11151
11987
  if (!result.success) {
11152
11988
  throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
11153
11989
  }
11154
11990
  return result;
11155
11991
  } catch (error) {
11992
+ guideModal.close();
11156
11993
  if (stream) {
11157
11994
  stream.getTracks().forEach((track) => track.stop());
11158
11995
  }
@@ -11177,7 +12014,6 @@ async function executeBiometricLoginFlow(options) {
11177
12014
  onCancel,
11178
12015
  fastMode = false
11179
12016
  } = options;
11180
- const overlay = new BiometricStatusOverlay();
11181
12017
  const fallbackPrompt = new FallbackPrompt();
11182
12018
  let attempts = 0;
11183
12019
  let lastError;
@@ -11185,34 +12021,26 @@ async function executeBiometricLoginFlow(options) {
11185
12021
  preloadFaceDetectionModels().catch(() => {
11186
12022
  });
11187
12023
  }
11188
- overlay.show("preparing");
11189
12024
  const tryLogin = async () => {
11190
12025
  try {
11191
12026
  attempts++;
11192
- const result = await attemptLogin(applicationToken, overlay, fastMode, attempts > 1);
11193
- overlay.updateStatus("success");
11194
- setTimeout(() => {
11195
- overlay.close();
11196
- onSuccess(result);
11197
- }, SUCCESS_DELAY);
12027
+ const result = await attemptLogin(applicationToken, fastMode, attempts > 1, onCancel);
12028
+ onSuccess(result);
11198
12029
  } catch (error) {
11199
12030
  lastError = error instanceof NeoFaceError ? error : new NeoFaceError(
11200
12031
  error instanceof Error ? error.message : "Erro desconhecido",
11201
12032
  ErrorType.UNKNOWN
11202
12033
  );
11203
- overlay.updateStatus("error");
11204
12034
  if (attempts < MAX_ATTEMPTS$1) {
11205
12035
  setTimeout(() => {
11206
12036
  tryLogin();
11207
12037
  }, RETRY_DELAY);
11208
12038
  } else {
11209
12039
  setTimeout(() => {
11210
- overlay.close();
11211
12040
  fallbackPrompt.show(
11212
12041
  lastError,
11213
12042
  () => {
11214
12043
  attempts = 0;
11215
- overlay.show("preparing");
11216
12044
  setTimeout(() => tryLogin(), RETRY_DELAY);
11217
12045
  },
11218
12046
  () => {
@@ -11234,9 +12062,7 @@ async function executeBiometricLoginFlow(options) {
11234
12062
  }
11235
12063
  }
11236
12064
  };
11237
- {
11238
- tryLogin();
11239
- }
12065
+ tryLogin();
11240
12066
  }
11241
12067
  const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
11242
12068
  __proto__: null,
@@ -11245,6 +12071,25 @@ const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
11245
12071
  preloadFaceDetectionModels
11246
12072
  }, Symbol.toStringTag, { value: "Module" }));
11247
12073
  const CONSENT_DENIED_MESSAGE$1 = "Consentimento recusado pelo usuário.";
12074
+ function openPasswordFallback(options) {
12075
+ if (!getAutoFallback().toPassword) {
12076
+ options.onError(
12077
+ new NeoFaceError(
12078
+ "Biometria não reconhecida e a queda automática para senha está desligada.",
12079
+ ErrorType.LOGIN_FAILED
12080
+ )
12081
+ );
12082
+ return;
12083
+ }
12084
+ const fallbackOptions = {
12085
+ applicationToken: options.applicationToken,
12086
+ onSuccess: options.onSuccess,
12087
+ onError: options.onError,
12088
+ title: "Login Alternativo",
12089
+ subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
12090
+ };
12091
+ new EmailPasswordModal(fallbackOptions).open();
12092
+ }
11248
12093
  async function startFaceLogin(options) {
11249
12094
  const consentAccepted = await requestConsent({
11250
12095
  appName: options.appName,
@@ -11259,17 +12104,7 @@ async function startFaceLogin(options) {
11259
12104
  applicationToken: options.applicationToken,
11260
12105
  onSuccess: options.onSuccess,
11261
12106
  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
- }),
12107
+ onFallbackRequest: options.onFallbackRequest || (() => openPasswordFallback(options)),
11273
12108
  onCancel: options.onCancel
11274
12109
  });
11275
12110
  } catch (error) {
@@ -12438,15 +13273,23 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
12438
13273
  resolve();
12439
13274
  };
12440
13275
  });
13276
+ const guideModal = showCameraOvalGuideModal({
13277
+ stream,
13278
+ faceState: "centered",
13279
+ instruction: PHOTO_INSTRUCTIONS[0]
13280
+ });
12441
13281
  const canvas = document.createElement("canvas");
12442
13282
  canvas.width = video.videoWidth;
12443
13283
  canvas.height = video.videoHeight;
12444
13284
  const ctx = canvas.getContext("2d");
12445
13285
  if (!ctx) {
13286
+ guideModal.close();
13287
+ stream.getTracks().forEach((track) => track.stop());
12446
13288
  throw new NeoFaceError("Failed to get canvas context", ErrorType.CAPTURE_ERROR);
12447
13289
  }
12448
13290
  for (let i = 0; i < TOTAL_PHOTOS; i++) {
12449
13291
  const instruction = PHOTO_INSTRUCTIONS[i];
13292
+ guideModal.update(stream, "centered", instruction);
12450
13293
  if (options == null ? void 0 : options.onProgress) {
12451
13294
  options.onProgress(i + 1, TOTAL_PHOTOS, instruction);
12452
13295
  }
@@ -12472,6 +13315,7 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
12472
13315
  options.onPhotoTaken(i + 1, blob);
12473
13316
  }
12474
13317
  }
13318
+ guideModal.close();
12475
13319
  stream.getTracks().forEach((track) => track.stop());
12476
13320
  const lastPhoto = capturedPhotos[TOTAL_PHOTOS - 1];
12477
13321
  const sessionData = {
@@ -12519,110 +13363,6 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
12519
13363
  throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
12520
13364
  }
12521
13365
  };
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
13366
  function FocusFrame({
12627
13367
  size = 96,
12628
13368
  color,
@@ -12850,32 +13590,6 @@ function pointsFor(g) {
12850
13590
  return ["24 24", "24 24", "24 24"];
12851
13591
  }
12852
13592
  }
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
13593
  const DEFAULT = {
12880
13594
  title: "Não deu certo desta vez",
12881
13595
  hint: "Tente se posicionar em um ambiente mais estável.",
@@ -14646,15 +15360,18 @@ export {
14646
15360
  VERSION,
14647
15361
  authorize,
14648
15362
  authorizeOperation,
15363
+ awaitPushApproval,
14649
15364
  checkUserExistence,
14650
15365
  clearConsentTrail,
14651
15366
  completeOnboarding,
14652
15367
  completeOnboardingWithData,
14653
15368
  confirmPasswordReset,
15369
+ consumeSseStream,
14654
15370
  detectBiometricType,
14655
15371
  getAccent,
14656
15372
  getAppName,
14657
15373
  getApplicationToken,
15374
+ getAutoFallback,
14658
15375
  getBaseUrl,
14659
15376
  getCachedCaptureSession,
14660
15377
  getConfig,
@@ -14679,10 +15396,12 @@ export {
14679
15396
  recognizeBiometric,
14680
15397
  recognizeByPurpose,
14681
15398
  recordConsent,
15399
+ refreshSession,
14682
15400
  registerBiometric,
14683
15401
  registerPersonWithBiometric,
14684
15402
  registerPersonWithoutFace,
14685
15403
  requestConsent,
15404
+ requestDeviceEnrollmentToken,
14686
15405
  requestLivenessChallenge,
14687
15406
  requestLivenessChallengeWithSession,
14688
15407
  requestPasswordReset,