# NeoFace ID SDK Web

SDK JavaScript/TypeScript (React) para autenticação, cadastro e verificação
facial em aplicações web, publicado no npm como
[`@neofaceid/web-sdk`](https://www.npmjs.com/package/@neofaceid/web-sdk).

Veja o [`CHANGELOG.md`](CHANGELOG.md) para o histórico de versões.

> 🚀 Integrando o SDK pela primeira vez? Veja o
> [guia de integração passo a passo](docs/INTEGRATION.md) — instalação,
> requisitos, `init()`, exemplos React/Next.js, CSP e troubleshooting.

## Índice

- [Instalação](#instalação)
- [Requisitos do consumer](#requisitos-do-consumer)
- [Início rápido](#início-rápido)
- [LGPD e consentimento](#lgpd-e-consentimento)
- [Fluxos principais](#fluxos-principais)
  - [Login facial (`startFaceLogin`)](#login-facial-startfacelogin)
  - [Login por mão / automático](#login-por-mão--automático-starthandlogin-startautologin)
  - [Cadastro biométrico (`startBiometricRegistration`)](#cadastro-biométrico-startbiometricregistration)
  - [Captura de liveness (`startLivenessCapture`)](#captura-de-liveness-startlivenesscapture)
  - [Onboarding completo (`startOnboarding`)](#onboarding-completo-startonboarding)
  - [Captura de documento (`startDocumentCapture`)](#captura-de-documento-startdocumentcapture)
  - [Autorização por biometria (`authorize` / `authorizeOperation`)](#autorização-por-biometria-authorize--authorizeoperation)
  - [Prova de vida, captura de frames e operações avulsas](#prova-de-vida-captura-de-frames-e-operações-avulsas)
- [Tratamento de erros](#tratamento-de-erros)
- [Componentes React exportados](#componentes-react-exportados)
- [Sessão de captura e desafio de liveness (baixo nível)](#sessão-de-captura-e-desafio-de-liveness-baixo-nível)
- [Trilha de consentimento (auditoria LGPD)](#trilha-de-consentimento-auditoria-lgpd)
- [API de baixo nível (`api.ts`)](#api-de-baixo-nível-apits)
- [Versão do SDK](#versão-do-sdk)
- [Compatibilidade](#compatibilidade)
- [Licença](#licença)

## Instalação

```bash
npm install @neofaceid/web-sdk
```

`react` e `react-dom` são `peerDependencies` (`^18.0.0 || ^19.0.0`) — o
projeto consumidor precisa já ter as duas instaladas.

## Requisitos do consumer

Antes de usar qualquer fluxo do SDK, confirme os itens abaixo no projeto que
vai consumi-lo:

1. **HTTPS.** O SDK acessa a câmera via `getUserMedia`, que exige contexto
   seguro. `localhost` funciona em desenvolvimento; produção precisa de
   HTTPS (recomendado configurar HSTS no servidor).
2. **O detector facial (`@vladmandic/face-api`, fork mantido do
   `face-api.js`) já vem embutido** no `@neofaceid/web-sdk`, rodando sobre o
   TF.js 4.x que o SDK instala como dependência (não precisa instalar nada) — mas os **pesos do
   modelo (weights) não são publicados no bundle** e precisam ser
   auto-hospedados pelo consumer. O SDK carrega os modelos em runtime via
   `faceapi.nets.<rede>.loadFromUri('/models')`, ou seja, relativos à própria
   origem do seu app. Baixe os arquivos abaixo do repositório oficial de
   pesos do face-api.js (o fork usa os mesmos arquivos) e sirva-os em
   `public/models/` (ou equivalente do seu bundler) para que fiquem acessíveis em `/models/*`:
   - `tiny_face_detector_model-weights_manifest.json` +
     `tiny_face_detector_model-shard1`
   - `face_landmark_68_model-weights_manifest.json` +
     `face_landmark_68_model-shard1`

   Sem esses arquivos em `/models`, qualquer fluxo que dependa de detecção
   facial local (blink detection do liveness, `biometricDetection`) falha ao
   carregar o modelo. Se o manifest não for encontrado (404), o console
   mostra um `console.error` acionável, com os 4 arquivos e o destino:

   ```
   [NeoFace ID SDK] Modelos de detecção facial não encontrados em /models/.
   Copie os 4 arquivos abaixo para public/models/ (ou equivalente do seu bundler):
     - tiny_face_detector_model-weights_manifest.json
     - tiny_face_detector_model-shard1
     - face_landmark_68_model-weights_manifest.json
     - face_landmark_68_model-shard1
   Fonte: https://github.com/vladmandic/face-api/tree/master/model
   Sem esses arquivos em /models, o fluxo de detecção facial falhará silenciosamente.
   ```
3. **Ícone/logo do SDK não precisa de asset externo.** Desde a 1.29.0 a marca
   é renderizada via SVG inline (`src/assets/brand.ts`) — não há mais PNG
   para hospedar.
4. **`applicationToken`** válido, obtido no painel administrativo do
   NeoFace ID.
5. **Ambiente correto configurado via `init`** — veja
   [Início rápido](#início-rápido). O default (sem `init`) é `sandbox`.

## Início rápido

```ts
import { init } from '@neofaceid/web-sdk';

init({
  environment: 'sandbox', // 'development' | 'sandbox' | 'production'
  applicationToken: 'seu-token-de-aplicacao',
  appName: 'Minha Aplicação', // exibido no topo dos modais
  consent: {
    purpose: 'authentication',
    legalBasis: 'fraud_prevention', // 'consent' | 'legal_obligation' | 'fraud_prevention'
    privacyPolicyUrl: 'https://minha-app.com/privacidade',
    retentionDays: 0,
  },
});
```

Mapeamento de `environment` → URL base ([`src/config.ts`](src/config.ts)):

| `environment`  | URL base                             |
| -------------- | ------------------------------------ |
| `development`  | `http://localhost:8000`              |
| `sandbox`      | `https://sandbox-core.neofaceid.com` |
| `production`   | `https://core.neofaceid.com.br`      |

Use `baseUrl` em vez de `environment` para apontar para uma URL customizada
(tem precedência sobre `environment`). Outras opções de `init` — tema visual
do integrador:

```ts
init({
  accent: '#0059C4', // cor do botão primário; contraste < 4.5 vs branco = fallback + warn
  radius: 16, // 8 | 16 | 24, fallback 16 se fora da lista
  theme: 'auto', // 'light' | 'dark' | 'auto' (respeita prefers-color-scheme)
  locale: 'pt-BR',
});
```

Getters correspondentes: `getBaseUrl()`, `getEnvironment()`,
`getApplicationToken()`, `getConsent()`, `isInitialized()`, `getConfig()`,
`getAppName()`, `getAccent()`, `getRadius()`, `getTheme()`, `getLocale()`,
`getResolvedThemeMode()`, e o mapa `ENVIRONMENT_URLS`.

## LGPD e consentimento

Todo fluxo que abre a câmera (`start`, `startBiometricRegistration`,
`startLivenessCapture`, `startFaceLogin`, `startHandLogin`, `startAutoLogin`,
`startOnboarding`, `startDocumentCapture`, `authorizeOperation`, `authorize`)
chama internamente `requestConsent({ appName, flow })` **antes** de qualquer
`getUserMedia()`. Se o titular recusar, o callback de erro recebe
`NeoFaceError` com `ErrorType.CONSENT_DENIED` e a câmera nunca é aberta.

Configure a base legal uma vez em `init({ consent })` (ver
[Início rápido](#início-rápido)) — omitir gera `console.warn` e usa defaults
conservadores (`fraud_prevention`, sem URL de política, retenção
indefinida). Também é possível chamar `requestConsent` diretamente para UI
customizada:

```ts
import { requestConsent, DEFAULT_CONSENT_INFO } from '@neofaceid/web-sdk';

const accepted = await requestConsent({
  ...DEFAULT_CONSENT_INFO,
  appName: 'Minha Aplicação',
  flow: 'registration', // 'verification' (imagem descartada) | 'registration' (guarda vetor matemático)
});
```

Toda decisão de consentimento pode ser auditada — veja
[Trilha de consentimento](#trilha-de-consentimento-auditoria-lgpd).

## Fluxos principais

### Login facial (`startFaceLogin`)

Overlay minimalista estilo Face ID (não mostra a câmera na tela).

```ts
import { startFaceLogin } from '@neofaceid/web-sdk';

await startFaceLogin({
  applicationToken: 'seu-token-de-aplicacao',
  appName: 'Minha Aplicação',
  onSuccess: result => console.log('Login ok:', result),
  onError: err => console.error(err.type, err.getFriendlyMessage()),
  onAttemptError: (err, attempt) => analytics.track('biometric_attempt_failed', { attempt, type: err.type }),
  onCancel: () => console.log('Usuário cancelou'),
  onFallbackRequest: () => {
    /* opcional — default já abre EmailPasswordModal */
  },
});
```

> `onError` × `onAttemptError` (`BiometricLoginOptions`, NEO-563): `onError` é
> chamado quando o fluxo guiado **termina** — usuário cancelou, escolheu
> email/senha, ou erro fora do loop de tentativas (ex.: `CONSENT_DENIED`
> antes da câmera abrir). Já `onAttemptError` (opcional) é chamado a cada
> tentativa individual que falha **dentro** do loop (401, 429, rosto não
> detectado, etc.), antes de o `FallbackPrompt` interno aparecer — útil para
> registrar cada falha no seu próprio sistema de monitoramento sem esperar o
> fluxo encerrar.

### Login por mão / automático (`startHandLogin`, `startAutoLogin`)

Mesma assinatura de `startFaceLogin` (`BiometricLoginOptions`); `startAutoLogin`
detecta rosto ou mão automaticamente.

```ts
import { startHandLogin, startAutoLogin } from '@neofaceid/web-sdk';

await startHandLogin({ applicationToken, onSuccess, onError });
await startAutoLogin({ applicationToken, onSuccess, onError });
```

### Cadastro biométrico (`startBiometricRegistration`)

```ts
import { startBiometricRegistration } from '@neofaceid/web-sdk';

startBiometricRegistration(
  {
    name: 'Maria Silva',
    birth_date: '1990-05-20',
    cpf: '12345678900',
    email: 'maria@exemplo.com',
    password: 'senha-forte',
  },
  'seu-token-de-aplicacao',
  {
    onSuccess: result => console.log('Cadastro concluído:', result.person_id),
    onError: err => console.error(err.type, err.message),
  },
  { appName: 'Minha Aplicação' }
);
```

### Captura de liveness (`startLivenessCapture`)

Só a captura de fotos com prova de vida, sem cadastro — útil quando o
consumer já tem seu próprio pipeline de reconhecimento.

```ts
import { startLivenessCapture } from '@neofaceid/web-sdk';

startLivenessCapture(
  'seu-token-de-aplicacao',
  {
    onSuccess: (photos: Blob[]) => console.log(`${photos.length} fotos capturadas`),
    onError: err => console.error(err.type, err.message),
    onCancel: () => console.log('Usuário fechou o modal'),
  },
  { appName: 'Minha Aplicação' }
);
```

### Onboarding completo (`startOnboarding`)

Orquestra: validação do link → consentimento → captura de rosto e
documento → conclusão no backend.

```ts
import { startOnboarding } from '@neofaceid/web-sdk';

await startOnboarding({
  applicationToken: 'seu-token-de-aplicacao',
  onboardingToken: 'token-do-link-de-onboarding',
  appName: 'Minha Aplicação',
  onSuccess: result => console.log('Onboarding concluído:', result.person_id),
  onError: err => console.error(err.type, err.message),
  onCancel: () => console.log('Usuário cancelou'),
});
```

### Captura de documento (`startDocumentCapture`)

```ts
import { startDocumentCapture } from '@neofaceid/web-sdk';

const result = await startDocumentCapture({
  appName: 'Minha Aplicação',
  preSelectedDocument: 'CNH', // 'RG' | 'CNH' | 'CPF' — pula a tela de seleção
  useBackCamera: true,
});

if (result.success) {
  console.log('Frente:', result.frontImage);
  console.log('Verso:', result.backImage); // null se o documento não tem verso
}
```

### Autorização por biometria (`authorize` / `authorizeOperation`)

`authorize` é a API atual (protocolo Focus Frame, orquestra
`runLivenessChallenge` — o servidor decide a sequência de gestos e a
aprovação final):

```ts
import { authorize } from '@neofaceid/web-sdk';

await authorize({
  applicationToken: 'seu-token-de-aplicacao',
  subject: '12345678900', // CPF, e-mail ou id opaco do titular
  appName: 'Minha Aplicação',
  amount: 1250, // exibido como "R$ 1.250,00"
  challenges: 2, // 0-3, teto sugerido; servidor decide a sequência real
  onChallengeStart: (gesture, index, total) => console.log(gesture, index, total),
  onSuccess: collectResult => {
    // envie collectResult.capturedGestures ao seu próprio endpoint de recognition
  },
  onError: err => console.error(err.type, err.message),
  onCancel: () => console.log('Usuário cancelou'),
});
```

`authorizeOperation` é a variante legada, mais baixo nível (captura 4 fotos
com gestos fixos e chama `recognizeByPurpose('AUTHORIZATION')` diretamente):

```ts
import { authorizeOperation } from '@neofaceid/web-sdk';

const result = await authorizeOperation('seu-token-de-aplicacao', '12345678900', {
  appName: 'Minha Aplicação',
  onProgress: (step, total, instruction) => console.log(instruction, step, total),
  onPhotoTaken: (n, blob) => console.log('Foto', n, blob),
});
```

### Prova de vida, captura de frames e operações avulsas

Operações que não abrem um fluxo `start*` completo. Até a 1.x viviam na
classe `NeoFaceID`; desde a 2.0.0 são funções, como o resto do SDK — ver a
tabela de migração no [CHANGELOG](CHANGELOG.md#200---2026-09-19--neo-244-neo-245).

```ts
import {
  proofOfLife,
  captureFaceFrames,
  recognizeByPurpose,
  registerDocumentByImage,
} from '@neofaceid/web-sdk';

// Prova de vida: grava vídeo curto, envia ao core e acompanha até o resultado.
// `applicationToken` pode ser omitido se já foi passado a init().
const proof = await proofOfLife({
  applicationToken: 'seu-token-de-aplicacao',
  videoDurationMs: 3000,
  onRecordingProgress: progress => console.log(`Gravando: ${progress}%`),
  onTaskStatusChange: (status, progress) => console.log(status, progress),
});
// Reprovar na prova é desfecho, não exceção: confira `success` e `isLive`.
if (proof.success && proof.isLive) {
  console.log('Liveness score:', proof.livenessScore);
  console.log('Dados da pessoa:', proof.personalData);
}

// Login com assinatura (integrações externas que já geram HMAC)
const [frame] = await captureFaceFrames({ numFrames: 1, livenessCheck: false });
const login = await recognizeByPurpose(
  frame,
  'seu-token-de-aplicacao',
  'LOGIN',
  0.8, // limiar de confiança
  signatureFromBackend, // HMAC-SHA256 hex, mínimo 64 caracteres
  { email: 'user@exemplo.com', cpf: '12345678900', sessionId: 'session-uuid' }
);

// Registro de documento para pessoa já cadastrada
const docResult = await registerDocumentByImage(personId, userJwtToken);
console.log('Task ID:', docResult.taskId);
```

## Tratamento de erros

Toda falha do SDK (nos fluxos novos) é uma instância de `NeoFaceError`, com
`error.type` (`ErrorType`) e `error.getFriendlyMessage()` para exibir ao
usuário final sem termos técnicos:

```ts
import { NeoFaceError, ErrorType } from '@neofaceid/web-sdk';

function onError(err: NeoFaceError) {
  if (err.type === ErrorType.CONSENT_DENIED) {
    // usuário recusou o consentimento — não abriu câmera
  }
  alert(err.getFriendlyMessage());
}
```

> Desde a 1.41.0, `start`, `startBiometricRegistration` e
> `startLivenessCapture` também usam `onError(err: NeoFaceError)` — antes
> usavam `onError(code: string, message: string)`. Veja o `CHANGELOG.md`
> (breaking change) se seu código ainda depende da assinatura antiga.

| `ErrorType`             | Quando ocorre                                                  |
| ----------------------- | --------------------------------------------------------------- |
| `NETWORK`               | Erro de conexão com o servidor                                  |
| `INVALID_TOKEN`         | `applicationToken` inválido ou expirado                         |
| `RECOGNITION_FAILED`    | Falha no reconhecimento facial                                  |
| `LOGIN_FAILED`          | Falha no login biométrico                                       |
| `VALIDATION_ERROR`      | Dados inválidos ou rosto não detectado corretamente              |
| `API_ERROR`             | Erro genérico de API (400/500)                                  |
| `PERSON_NOT_FOUND`      | Pessoa não encontrada na base                                   |
| `INITIALIZATION_ERROR`  | Falha ao inicializar um fluxo                                   |
| `CAMERA_ERROR`          | Erro genérico de câmera                                          |
| `NO_CAMERA`             | Câmera não disponível ou permissão negada                        |
| `CAPTURE_ERROR`         | Falha durante a captura de imagem/vídeo                         |
| `NOT_FOUND`             | Recurso não encontrado                                           |
| `UNKNOWN`               | Erro não categorizado                                            |
| `CONSENT_DENIED`        | Titular recusou o consentimento LGPD                             |
| `LIVENESS_BLINK_MISSING`| Piscada real (EAR) não detectada na janela de liveness (3s)      |

## Componentes React exportados

Para quem prefere montar a própria árvore React em vez de usar as funções
`start*` (que já cuidam de criar/desmontar o container):

```tsx
import {
  FaceCaptureModal,
  BiometricRegistrationModal,
  ConsentModal,
  EmailPasswordModal,
  ForgotPasswordModal,
  BiometricStatusOverlay,
  FallbackPrompt,
  DocumentCaptureModal,
} from '@neofaceid/web-sdk';
```

Todos recebem `accessToken`/`applicationToken`, `onSuccess`/`onError` e
`onClose` conforme o fluxo — consulte a assinatura de cada `start*`
correspondente acima para as props equivalentes.

## Sessão de captura e desafio de liveness (baixo nível)

Usado internamente por `authorize`/`authorizeOperation`, mas exportado para
quem monta o próprio orquestrador de liveness:

```ts
import {
  openCaptureSession,
  requestLivenessChallenge,
  runLivenessChallenge,
} from '@neofaceid/web-sdk';

const session = await openCaptureSession({
  applicationToken: 'seu-token-de-aplicacao',
  purpose: 'authorization', // 'login' | 'onboarding' | 'authorization' | 'identification' | 'liveness'
});

const challenge = await requestLivenessChallenge({
  applicationToken: 'seu-token-de-aplicacao',
  sessionId: session.session_id,
});

// Ou o fluxo completo (session → challenge → coleta via callback → retorno):
const collected = await runLivenessChallenge({
  applicationToken: 'seu-token-de-aplicacao',
  purpose: 'login',
  collectFramesForGesture: async (gesture, index, total) => {
    // capture o(s) frame(s) correspondente(s) ao gesto e retorne os Blobs
    return [] as Blob[];
  },
});
```

## Trilha de consentimento (auditoria LGPD)

```ts
import { recordConsent, getConsentTrail, clearConsentTrail } from '@neofaceid/web-sdk';

// Grava no localStorage apenas hash(userAgent+purpose+timestamp), timestamp,
// finalidade, base legal e versão do SDK — zero PII. Usa por padrão o
// `consent` configurado em `init()`; pode ser sobrescrito pontualmente:
await recordConsent({ purpose: 'authentication', legalBasis: 'fraud_prevention' });

const trail = getConsentTrail(); // ConsentRecord[] — histórico local (máx. 100, FIFO)
clearConsentTrail(); // limpa o histórico (ex.: logout)
```

## API de baixo nível (`api.ts`)

Funções que os fluxos `start*` já usam por baixo dos panos, expostas para
integrações que precisam de controle fino sobre a chamada HTTP:

```ts
import {
  validateToken,
  validateOnboardingToken,
  recognize,
  recognizeBiometric,
  simpleIdentification,
  recognizeByPurpose,
  loginWithBiometric,
  loginWithEmail,
  registerPersonWithoutFace,
  registerPersonWithBiometric,
  registerBiometric,
  identifyPerson,
  identifyPersonAsync,
  checkUserExistence,
  requestPasswordReset,
  confirmPasswordReset,
  completeOnboarding,
  completeOnboardingWithData,
} from '@neofaceid/web-sdk';

await validateToken('seu-token-de-aplicacao'); // boolean
await validateOnboardingToken('seu-token-de-aplicacao', 'token-do-onboarding'); // boolean

await recognize(faceBlob, 'seu-token-de-aplicacao');
await recognizeBiometric(faceBlob, 'seu-token-de-aplicacao', /* livenessCheck */ true, 0.8);
await simpleIdentification('CPF', '12345678900', 'seu-token-de-aplicacao');
await recognizeByPurpose(faceBlob, 'seu-token-de-aplicacao', 'LOGIN', 0.8);

await loginWithBiometric(faceBlob, 'seu-token-de-aplicacao');
await loginWithEmail('user@exemplo.com', 'senha', 'seu-token-de-aplicacao');

await registerPersonWithoutFace(
  { name: 'Maria Silva', birth_date: '1990-05-20', cpf: '12345678900', email: 'maria@exemplo.com', password: 'senha-forte' },
  'seu-token-de-aplicacao'
);
await registerPersonWithBiometric(
  { name: 'Maria Silva', birth_date: '1990-05-20', cpf: '12345678900', email: 'maria@exemplo.com', password: 'senha-forte' },
  [faceBlob1, faceBlob2],
  'seu-token-de-aplicacao'
);
await registerBiometric(personId, faceBlob, 'seu-token-de-aplicacao');

await identifyPerson(faceBlob, 'seu-token-de-aplicacao');
await identifyPersonAsync(faceBlob, 'seu-token-de-aplicacao', { maxRetries: 5, interval: 1000 });
await checkUserExistence({ email: 'user@exemplo.com' }); // ou { cpf }

await requestPasswordReset('user@exemplo.com', 'seu-token-de-aplicacao');
await confirmPasswordReset('token-do-email', 'nova-senha');

await completeOnboarding('seu-token-de-aplicacao', 'token-do-onboarding', faceBlob, documentBlob);
await completeOnboardingWithData(
  'seu-token-de-aplicacao',
  'token-do-onboarding',
  { name: 'Maria Silva', cpf: '12345678900' },
  [faceBlob1, faceBlob2],
  documentBlob
);
```

Utilitário de performance para pré-carregar os modelos de detecção facial
antes do primeiro uso (evita o delay do primeiro `loadFromUri` acontecer
durante a interação do usuário):

```ts
import { preloadFaceDetectionModels } from '@neofaceid/web-sdk';

preloadFaceDetectionModels(); // dispara o carregamento de /models em background
```

## Versão do SDK

```ts
import { VERSION, RELEASE_DATE } from '@neofaceid/web-sdk';

console.log(`NeoFace ID SDK ${VERSION} (${RELEASE_DATE})`);
```

Histórico completo em [`CHANGELOG.md`](CHANGELOG.md).

## Compatibilidade

### Navegadores suportados

- Chrome 60+
- Firefox 55+
- Safari 11+
- Edge 79+

### Responsividade

O SDK é totalmente responsivo. Em dispositivos móveis, os modais ocupam a
tela inteira.

## Licença

MIT
