@beltec/face-gate 0.0.0-stage → 0.1.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.
package/README.md CHANGED
@@ -1,3 +1,218 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @beltec/face-gate
2
+
3
+ **Face Gate, uma solução BelTec** — validação de fotos de rosto para o cadastro de visitantes e moradores no
4
+ controle de acesso. Este pacote traz:
5
+
6
+ - **`<FaceGate>`** (React): webcam com validação em **tempo real** — mira que segue o rosto, orientação do que
7
+ corrigir, captura automática só quando a pessoa fica parada com tudo aprovado, scanner sobre a foto e a foto
8
+ final (com o fundo removido, se contratado).
9
+ - **`@beltec/face-gate/server`** (Node/Next): guarda a sua chave, emite o ticket do navegador e valida arquivos.
10
+
11
+ O `client_secret` **nunca** vai ao navegador: o seu servidor pede um ticket de uso único (60 s) e só ele chega à
12
+ página.
13
+
14
+ ```
15
+ navegador ──POST /api/face-gate/ticket──▶ seu servidor ──chave──▶ Face Gate API
16
+ navegador ◀──────── ticket (60 s) ────────┘
17
+ navegador ══ WebSocket (ticket) ══▶ Face Gate API (quadros ao vivo → captura → resultado)
18
+ ```
19
+
20
+ ## Instalação
21
+
22
+ ```bash
23
+ npm install @beltec/face-gate
24
+ ```
25
+
26
+ React 18+ no navegador; Node 18.17+ no servidor. Crie a chave no portal do Face Gate
27
+ (**Chaves de acesso**): você recebe o `client_id` e o `client_secret` (este aparece uma vez só).
28
+
29
+ ```bash
30
+ # .env do seu servidor
31
+ FACE_GATE_CLIENT_ID=...
32
+ FACE_GATE_CLIENT_SECRET=...
33
+ ```
34
+
35
+ ## Next.js (App Router)
36
+
37
+ **1. Rota que emite o ticket** — `app/api/face-gate/ticket/route.ts`
38
+
39
+ ```ts
40
+ import { createFaceGate } from "@beltec/face-gate/server";
41
+
42
+ const faceGate = createFaceGate({
43
+ clientId: process.env.FACE_GATE_CLIENT_ID!,
44
+ clientSecret: process.env.FACE_GATE_CLIENT_SECRET!,
45
+ });
46
+
47
+ // Só usuários logados no SEU sistema abrem a câmera (cada captura consome tokens do seu plano).
48
+ export const POST = faceGate.ticketHandler({
49
+ authorize: async (req) => Boolean(await getSessionFromYourApp(req)),
50
+ });
51
+ ```
52
+
53
+ **2. A tela** — um Client Component
54
+
55
+ ```tsx
56
+ "use client";
57
+ import { FaceGate } from "@beltec/face-gate";
58
+ import "@beltec/face-gate/styles.css";
59
+
60
+ export function CadastroFoto() {
61
+ return (
62
+ <FaceGate
63
+ clean // devolve a foto sem fundo (serviço "validar e remover fundo" contratado)
64
+ getTicket={async () => {
65
+ const r = await fetch("/api/face-gate/ticket", { method: "POST" });
66
+ if (!r.ok) throw new Error("Não foi possível abrir a câmera");
67
+ return r.json();
68
+ }}
69
+ onCapture={({ image, result }) => {
70
+ if (result.accepted) salvarFoto(image); // data URL (PNG sem fundo ou JPEG do quadro)
71
+ }}
72
+ onError={(e) => console.warn(e.code, e.message)}
73
+ />
74
+ );
75
+ }
76
+ ```
77
+
78
+ ## Validar um arquivo (sem webcam)
79
+
80
+ ```ts
81
+ import { createFaceGate } from "@beltec/face-gate/server";
82
+
83
+ const faceGate = createFaceGate({ clientId, clientSecret });
84
+ const result = await faceGate.validate(fileBuffer, { clean: true, model: "birefnet-portrait" });
85
+
86
+ result.accepted; // true | false
87
+ result.hard_fails; // ex.: ["eyes_closed", "glasses_sunglasses"]
88
+ result.cleaned_image; // PNG sem fundo (data URL), quando clean
89
+ result.usage?.tokens; // tokens gastos nesta análise
90
+ ```
91
+
92
+ Com melhoria de imagem: `faceGate.validate(fileBuffer, { enhance: "classic" })` (veja abaixo).
93
+
94
+ Express/Fastify funcionam igual: use `createStreamTicket()` na sua rota e devolva `{ ticket, url }` ao navegador.
95
+
96
+ ## `<FaceGate>` — propriedades
97
+
98
+ | Prop | Tipo | Padrão | |
99
+ |---|---|---|---|
100
+ | `getTicket` | `() => Promise<{ ticket, url }>` | — | chama a sua rota de ticket |
101
+ | `onCapture` | `({ image, frame, result }) => void` | — | foto capturada e validada |
102
+ | `onLive` | `(result) => void` | — | cada quadro analisado ao vivo |
103
+ | `onError` | `(error: FaceGateError) => void` | — | câmera negada, limite de tokens, rede… |
104
+ | `clean` | `boolean` | `false` | remover o fundo da foto capturada |
105
+ | `model` | `"u2net" \| "u2netp" \| "birefnet-portrait"` | servidor | modelo da remoção de fundo |
106
+ | `device` | `"cpu" \| "gpu"` | servidor | onde rodar a remoção de fundo |
107
+ | `enhance` | `"classic" \| "gfpgan" \| null` | `null` | melhorar a foto capturada antes de validar (serviço "validar com melhoria") |
108
+ | `thresholds` | `Record<string, number>` | servidor | limiares (nomes de `GET /v1/thresholds`) |
109
+ | `autoFrame` | `boolean` | `true` | enquadramento automático: a imagem aproxima e acompanha o rosto, mantendo-o no centro; a foto capturada é esse recorte e só sai com o rosto centralizado (tolerância de 8%, ou a sua em `thresholds` se for mais rígida). Ignora as tarjas pretas que algumas câmeras acrescentam |
110
+ | `mirror` | `boolean` | `true` | prévia espelhada, como um espelho (a foto enviada e a final não são espelhadas) |
111
+ | `autoStart` | `boolean` | `false` | ligar a câmera ao montar |
112
+ | `intro` | `boolean` | `true` | abertura animada do escudo |
113
+ | `watermark` | `boolean` | `true` | escudo no canto do vídeo |
114
+ | `labels` | `Partial<FaceGateLabels>` | pt-BR | troque qualquer texto |
115
+
116
+ ### Aparência
117
+
118
+ Os estilos usam o prefixo `.fg-` e variáveis CSS:
119
+
120
+ ```css
121
+ .minha-tela .fg-root {
122
+ --fg-accent: #c9a43e; /* mira, botão, barra de "fique parado" */
123
+ --fg-accent-text: #1d2125; /* texto sobre o botão */
124
+ --fg-surface: #1d2125; /* fundo do vídeo */
125
+ --fg-radius: 12px;
126
+ }
127
+ ```
128
+
129
+ Respeita `prefers-reduced-motion` (sem animações).
130
+
131
+ ## Melhoria de imagem
132
+
133
+ Serviço contratável **"Validar com melhoria"** (scope `validate_enhance`). A API melhora a foto, valida a
134
+ versão melhorada e devolve `enhancement` com o resultado **antes** (original) e **depois** (melhorada) e a
135
+ imagem melhorada (`enhancement.enhanced_image`, JPEG). No `<FaceGate enhance="classic">`, só a foto
136
+ capturada passa pela melhoria (os quadros ao vivo não), o `onCapture` entrega a melhorada em `image` e a
137
+ tela mostra o selo "Imagem melhorada".
138
+
139
+ | Método | O que faz | Custo |
140
+ |---|---|---|
141
+ | `classic` | balanço de branco, exposição, contraste local, redução de ruído e nitidez leve. Determinística, não altera o rosto | baixo |
142
+ | `gfpgan` | a clássica + restauração do rosto com IA (GFPGAN). Recria detalhes do rosto: a resposta traz o aviso `face_restored_ai` e o componente mostra o alerta | alto |
143
+
144
+ **Regra:** a melhoria só corrige luz (`poor_lighting`, `overexposed`) e rosto não encontrado por falta de
145
+ luz (`no_face`). As demais falhas da foto original — desfoque, olhos fechados, óculos escuros, pose… —
146
+ continuam reprovando (`enhancement.carried_over`). Com `clean`, o fundo é removido da foto melhorada (exige
147
+ também o serviço "validar e remover fundo").
148
+
149
+ **Tokens:** a validação roda duas vezes (antes e depois) e soma a etapa `usage.breakdown.enhance`
150
+ (a clássica é barata; o GFPGAN custa bem mais).
151
+
152
+ ## Avatar
153
+
154
+ Serviço **Avatar** contratado (`POST /v1/photos/avatar`): valida a foto e, se aprovada, devolve o avatar só com a
155
+ pessoa do rosto em foco — o fundo e outros elementos ou pessoas são removidos —, recortado centrado no rosto.
156
+
157
+ ```ts
158
+ import { createFaceGate } from "@beltec/face-gate/server";
159
+
160
+ const faceGate = createFaceGate({ clientId, clientSecret });
161
+ const r = await faceGate.avatar(fileBuffer, {
162
+ shape: "round", // "round" (padrão; fora do círculo transparente) | "square"
163
+ size: 512, // 64 a 1024
164
+ backgroundColor: "#1d252a", // ou background: <imagem> (tem prioridade); sem nenhum = PNG transparente
165
+ margin: 25, // só com fundo: % do quadro deixado para o fundo (0 a 60)
166
+ layout: "bottom", // só com fundo: "center" | "bottom" | "left" | "right"
167
+ });
168
+
169
+ if (r.accepted) r.avatar_image; // data:image/png;base64,...
170
+ else r.hard_fails; // sem avatar: os mesmos motivos da validação
171
+ ```
172
+
173
+ | Opção | Tipo | Padrão | |
174
+ |---|---|---|---|
175
+ | `shape` | `"round" \| "square"` | `"round"` | formato do recorte |
176
+ | `size` | `number` | `512` | lado em pixels (64 a 1024) |
177
+ | `backgroundColor` | `string` | — | cor sólida `#RRGGBB` |
178
+ | `background` | `Blob \| ArrayBuffer \| Uint8Array` | — | imagem de fundo (preenche o quadro) |
179
+ | `template` | `number` | — | id de um **template de avatar** salvo no portal (Avatares): formato, tamanho, fundo e enquadramento prontos, sem reenviar a imagem; as outras opções têm prioridade |
180
+ | `backgroundPosition` | `{ x?, y?, zoom? }` | centro, `zoom` 100 | **só com `background`**: qual parte da imagem aparece — `x`/`y` de 0 a 100 (50 = centro, como o `object-position` do CSS) e `zoom` de 100 a 300 % |
181
+ | `margin` | `number` | `0` | **só com fundo**: % do quadro deixado para o fundo em volta da pessoa (0 a 60) |
182
+ | `layout` | `"center" \| "bottom" \| "left" \| "right"` | `"center"` | **só com fundo**: onde a pessoa fica (centro, apoiada na base, base à esquerda/direita) |
183
+ | `model` / `device` | | servidor (BiRefNet) | modelo de remoção de fundo que separa a pessoa |
184
+ | `thresholds` | `Record<string, number>` | servidor | limiares da validação |
185
+
186
+ O resultado traz também `avatar` (`shape`, `size`, `model`, `background`, `margin`, `layout`). **Tokens:** soma a etapa `avatar`
187
+ em `usage.breakdown` (a separação da pessoa roda na imagem analisada inteira).
188
+
189
+ ## Tokens e erros
190
+
191
+ Cada foto capturada (e cada `validate`) consome **tokens** do seu plano — megapixels processados × peso de cada
192
+ etapa; foto maior ou remoção de fundo com BiRefNet gastam mais. Os quadros ao vivo da webcam não consomem.
193
+ O plano limita os tokens por **janela** (ex.: 5 h a partir do 1º uso) e por **mês** (renova no aniversário do
194
+ contrato).
195
+
196
+ `FaceGateError.code`:
197
+
198
+ | code | quando |
199
+ |---|---|
200
+ | `token_window_limit` | janela de tokens esgotada (`resetsAt` = quando renova) |
201
+ | `token_month_limit` | teto mensal atingido (`resetsAt` = data da renovação) |
202
+ | `forbidden` | serviço não contratado, chave bloqueada/expirada, fora do contrato ou IP não liberado |
203
+ | `unauthorized` | chave/ticket inválido ou expirado |
204
+ | `camera_denied` / `camera_unavailable` | permissão negada, sem câmera ou página sem HTTPS |
205
+ | `network` / `server` / `bad_request` | falhas de conexão ou da API |
206
+
207
+ ## Segurança e LGPD
208
+
209
+ - A whitelist de IPs da chave vale para o **seu servidor** (onde o ticket é emitido); o navegador usa só o
210
+ ticket de uso único.
211
+ - A API não guarda as imagens: só o resultado e metadados da validação.
212
+ - A câmera exige HTTPS (ou `localhost`).
213
+ - As chamadas do servidor saem com o User-Agent `face-gate-sdk/<versão> (node <versão>; <sistema>)`
214
+ (`SDK_USER_AGENT`). Assim, uma tentativa de um IP não liberado aparece no portal como "SDK Face Gate <versão>".
215
+
216
+ ---
217
+
218
+ © BelTec, do grupo Belfort. Uso sujeito ao contrato do Face Gate.