# Integrar o retorno estruturado Leia primeiro os requisitos de instalação, permissões e build Release no [README](../README.md). O SDK não funciona no Expo Go e não aprova no navegador. ## APIs | Importação | API | |---|---| | `@workspace/face-local-sdk/expo` | `runFaceLocalVerification(options): Promise` | | `@workspace/face-local-sdk/expo` | `releaseFaceLocalVerification(verificationId): Promise` | | `@workspace/face-local-sdk/expo` | `createExpoEngine(): EvidenceEngine` | | `@workspace/face-local-sdk` | `runVerification(options)` para um adaptador próprio | | `@workspace/face-local-sdk` | Tipos `VerificationResult`, `VerificationOptions`, `EvidencePhoto`, `EvidenceEngine` | `runFaceLocalVerification` cuida da autorização de fotos, descoberta e Face Match. O app fornece o aceite, a solicitação da câmera e callbacks de interface opcionais. Não há botão intermediário para buscar ou comparar, nem seletor de referência. ## Exemplo de integração As funções de interface abaixo são do **app integrador**, não do SDK: `showResult` apresenta o resultado e só resolve quando o usuário encerra a tela. Esse cuidado evita remover os arquivos enquanto a tela ainda precisa deles. ```ts import { FaceLocalError, type Consent, type VerificationResult, } from "@workspace/face-local-sdk"; import { runFaceLocalVerification, releaseFaceLocalVerification, } from "@workspace/face-local-sdk/expo"; import { requestCameraPermissionsAsync } from "expo-image-picker"; async function verify( consentFromYourUI: Consent, signal: AbortSignal, showResult: (result: VerificationResult) => Promise, ) { let result: VerificationResult | undefined; try { result = await runFaceLocalVerification({ // O aceite deve ter sido obtido na sua interface, não presumido pelo SDK. consent: { ...consentFromYourUI, photoLibrary: false }, signal, policy: { matchThreshold: 0.83, maxPhotos: 100, captureTimeoutSeconds: 15, requireCaptureMetadata: true, }, evidence: { format: "uri", // Padrão. Nenhum Base64 no objeto. ttlSeconds: 300, // 30–300 segundos; padrão 300. }, requestCameraAccess: async () => { // Solicita a permissão; NÃO abre câmera estática nem seletor de imagens. const permission = await requestCameraPermissionsAsync(); if (!permission.granted) { throw new FaceLocalError("CAMERA_PERMISSION", "Câmera não autorizada."); } }, }); await showResult(result); } finally { if (result?.verificationId) { // Aguarde e trate uma eventual falha; não anuncie limpeza que não ocorreu. await releaseFaceLocalVerification(result.verificationId); } } } ``` Se o seu app não precisa manter a tela com fotos, libere-as assim que terminar de consumir o resultado. Para exibir em React Native, use uma URI local: ```tsx {result.photos.liveness && ( )} {result.photos.compared && ( )} ``` `Image` é de `react-native`. O retorno não depende dessa interface. Ao expirar ou liberar os arquivos, retire também as imagens da tela e limpe referências/caches de visualização que o seu app controla. ## Status: não misture recusa com impossibilidade de decidir ```ts switch (result.status) { case "approved": // Prosseguir no onboarding; approved === true. break; case "not_approved": // A tentativa não atingiu os critérios; approved === false. break; case "inconclusive": // Não há elementos suficientes; approved === null. break; case "cancelled": // Encerrar sem transformar cancelamento em reprovação. break; case "error": // Tratar errorCode: permissão, ambiente, integridade, expiração ou limpeza. break; } ``` O retorno usa objetos simples serializáveis com `JSON.stringify(result)`. **Serializar não significa gravar, registrar no console ou enviar para um servidor.** O JSON contém biometria, caminhos privados e metadados sensíveis. As falhas do fluxo da API de alto nível são retornadas como estados estruturados, incluindo opções inválidas e callbacks que lançam erros. As operações de limpeza explícita retornam uma Promise que pode rejeitar; trate esse erro no app. Nas APIs anteriores, erros continuam sendo lançados. ## Opções e callbacks | Opção | Regra | |---|---| | `consent` | `camera` e `biometricProcessing` devem ser verdadeiros após aceite; fotos começam desautorizadas | | `requestCameraAccess` | Deve pedir a permissão real e lançar erro se recusada | | `signal` | `AbortSignal` opcional para cancelar a tentativa em andamento | | `policy.matchThreshold` | Número finito de `0.5` a `0.99`; padrão `0.83` | | `policy.maxPhotos` | Inteiro de `1` a `1000`; padrão `100` | | `policy.captureTimeoutSeconds` | Inteiro de `6` a `22`; padrão `15` | | `policy.requireCaptureMetadata` | Deve ser `true`; não existe modo sem filtro EXIF | | `evidence.format` | `uri` por padrão, ou `base64` explicitamente | | `evidence.ttlSeconds` | Inteiro de `30` a `300`, a contar da primeira exportação nativa | | `onPhase` | Etapa do onboarding, para atualizar a interface | | `onCapture` | Recibo sem imagem/template; não altere a sessão com base nesse callback | | `onSearchProgress` | Progresso da descoberta da biblioteca | | `onProgress` | Progresso das comparações | O sistema operacional é soberano sobre permissões. Não é possível forçar “acesso completo” ou esconder a opção de acesso limitado. A API exige o escopo completo real; com acesso limitado ou recusado, encerra sem varrer fotos não autorizadas. Não há upload/download de originais da nuvem nem permissão de GPS para o filtro. ## Base64 opcional Use `evidence: { format: "base64" }` somente quando seu consumidor local realmente precisar dos bytes no JavaScript. As URIs também continuam presentes. O campo `photo.base64` contém só o conteúdo, sem `data:image/jpeg;base64,`. Base64 aumenta uso de memória e o tamanho do JSON. Não registre esse objeto, não o coloque em AsyncStorage, banco, clipboard, analytics ou crash reports. Se você copiar a string, o SDK **não consegue revogá-la nem zerar com garantia uma string no heap JavaScript**. A expiração remove os arquivos privados, não cópias que o app integrador já fez. Limpe suas referências e não persista. ## Retenção, expiração e descarte Há dois ciclos de vida distintos: 1. **Referência/embedding:** somente memória nativa, até 5 minutos, liberada ao encerrar a varredura, inclusive com erro. Nunca é retornada. 2. **Fotos de evidência:** criadas somente ao chamar a nova API de verificação com exportação. Até duas cópias JPEG privadas, sem metadados originais. Antes da exportação, as cópias têm prazo máximo de 5 minutos a partir da abertura da captura. Após a primeira exportação, vigora o TTL solicitado de 30–300 segundos. Ler novamente não renova o prazo. `evidence.expiresAt` informa o horário ISO correspondente; internamente a validade usa relógio monotônico. As cópias são removidas ao: - chamar `releaseFaceLocalVerification(verificationId)`; - cancelar ou falhar a tentativa antes da entrega; - iniciar outra verificação; - chamar `await createExpoEngine().clear()`; - atingir a expiração enquanto o processo pode executar a limpeza; - reinicializar o módulo após morte do processo, antes de uma nova sessão. O iOS também limpa ao entrar em background. No Android, o app integrador precisa encerrar/limpar ao sair do seu fluxo; a câmera é outra Activity, portanto não confunda a transição legítima para captura com saída definitiva do app. **Limite importante:** se o sistema suspender ou matar o processo, nenhum timer JavaScript ou nativo pode garantir execução naquele segundo. A validade é conferida nos acessos e a limpeza é retomada na inicialização. Uma URI `file://` já entregue é um caminho de arquivo, não um token criptográfico que expira sozinho. O integrador deve respeitar `expiresAt` e fazer descarte explícito. O TTL é um prazo máximo, não uma garantia de disponibilidade: o sistema pode limpar cache antes disso, e uma nova sessão ou saída do fluxo também revoga as cópias. Não se alega apagamento forense de flash ou de cópias/cache feitos pelo SO/app. `releaseFaceLocalVerification` é idempotente e só opera sobre a sessão correspondente. Não aceita um caminho arbitrário para apagar. Um ID antigo não remove as fotos de uma sessão nova. **Originais da galeria nunca são excluídos.** Uma falha de remoção mantém o vínculo interno para que uma nova tentativa de liberação não alegue sucesso simplesmente porque a sessão já foi invalidada. No Android, cache privado e backup desabilitado pelo plugin; no iOS, cache privado com proteção de arquivo completa e exclusão de backup. Isso não protege contra um processo/app/OS comprometido. ## Cancelar e impedir resultados atrasados ```ts const controller = new AbortController(); // Passe controller.signal para runFaceLocalVerification. // Ao cancelar a tentativa em andamento: controller.abort(); // Aguarde a Promise da verificação; o SDK descarta callbacks atrasados. ``` Após a Promise entregar o resultado, use `releaseFaceLocalVerification` para remover as cópias; abortar um sinal já concluído não é a API de descarte. Não mantenha verificações paralelas: o SDK tem uma sessão nativa ativa por processo. Uma segunda chamada de alto nível retorna `BUSY` sem apagar a primeira. Não misture chamadas manuais de captura/comparação com um onboarding em andamento. ## Migração a partir do relatório antigo | API antiga | Nova API | |---|---| | `runFaceLocalOnboarding` | `runFaceLocalVerification` | | `outcome: "positive"` | `status: "approved"`, `approved: true` | | `outcome: "negative"` | `status: "not_approved"`, `approved: false` | | `outcome: "inconclusive"` | `status: "inconclusive"`, `approved: null` | | Erros lançados | Estado `error` ou `cancelled`, com motivo/código | | Relatório com candidatas da biblioteca | JSON filtrado com até duas cópias privadas | | Sem exportação da captura | Exportação explícita com TTL e descarte obrigatório | O SDK mantém as APIs anteriores para compatibilidade; elas não passam a gravar cópias de imagem automaticamente. O app de demonstração ainda pode usar esse fluxo anterior até ser migrado. Isso não limita o uso da nova API por outro app. Instale o pacote 0.5+, recompile o binário e reinstale-o. Trocar só o JS no Metro ou instalar um `.tgz` não atualiza um APK/iOS já instalado. Sem `capabilities().evidenceVersion === 1`, a nova API retorna `EVIDENCE_UNAVAILABLE`. Não há fallback que invente fotos, EXIF, score ou aprovação. ## Adaptador próprio `runVerification` recebe um `EvidenceEngine`, `requestPhotoAccess` e `readPhotos`, além das opções compartilhadas. Os detalhes dos tipos são exportados pelo pacote. A concessão deve vir do sistema, com `accessPrivileges: "all"`. O adaptador deve: - preservar todas as proteções de `BiometricEngine`; - implementar `captureReferenceWithEvidence`, sem aceitar imagem/URI/frames externos; - retornar EXIF nativo em `NativeComparison.captureMetadata`, dos mesmos bytes analisados; - manter a melhor comparação até o primeiro match, sem associar imagens de outra sessão; - implementar `clearReference` removendo template/câmera, mas preservando apenas as cópias explicitamente solicitadas para entrega; - implementar `getEvidence` após limpar a referência, com sessão, score, URI original comparada, EXIF, cópias normalizadas, hash e prazo real; esse objeto interno não deve ser apresentado diretamente como JSON de integração; - implementar `releaseEvidence` e `clear` com remoção real, restrita aos arquivos próprios; falhas devem ser explícitas; - não renovar o TTL em releituras nem exportar resultado de uma sessão antiga. Não anuncie suporte a essa interface apenas para passar nas verificações do TypeScript. Os testes com dublês validam a orquestração, não uma implementação biométrica alternativa. ## Checklist antes de integrar em outro app - [ ] Build Release e aparelho físico; não Expo Go/web. - [ ] Permissões e aceite real; automático após a prova de vida. - [ ] Nenhum seletor/importador de referência. - [ ] `status`, `approved` e `reason` tratados separadamente. - [ ] Resultado/fotos em memória da interface, sem logs, persistência ou upload. - [ ] Similaridade não apresentada como acurácia/probabilidade. - [ ] EXIF e perfil do modelo não anunciados como prova de propriedade. - [ ] Descarte explícito ao concluir/fechar; expiração e Base64 compreendidos. - [ ] Galeria original preservada após liberar, cancelar e expirar. - [ ] Novo binário recompilado, incluindo o código nativo de exportação. - [ ] Testes reais Android/iPhone, inclusive background, retorno tardio e modo avião. - [ ] Política do plugin revisada: ele remove INTERNET do release Android inteiro. - [ ] Condições de uso do Face Local respeitadas: uso com marca própria permitido, revenda do SDK proibida.