Integrar o retorno estruturado#
Leia primeiro os requisitos de instalação, permissões e build Release no README. 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<VerificationResult> |
@workspace/face-local-sdk/expo | releaseFaceLocalVerification(verificationId): Promise<void> |
@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.
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<void>,
) {
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:
{result.photos.liveness && (
<Image source={{ uri: result.photos.liveness.uri }} style={{ width: 160, height: 200 }} />
)}
{result.photos.compared && (
<Image source={{ uri: result.photos.compared.uri }} style={{ width: 160, height: 200 }} />
)}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#
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:
- Referência/embedding: somente memória nativa, até 5 minutos, liberada ao encerrar a varredura, inclusive com erro. Nunca é retornada.
- 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#
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
clearReferenceremovendo template/câmera, mas preservando apenas as cópias explicitamente solicitadas para entrega; - implementar
getEvidenceapó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
releaseEvidenceeclearcom 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,approvedereasontratados 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.