> ## Documentation Index
> Fetch the complete documentation index at: https://docs.consentfly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Script do banner

> Referência completa do script de consentimento: instalação, atributos, comportamento e troubleshooting

O script do banner é a integração principal do ConsentFly: uma linha de HTML que exibe o banner de cookies, coleta a decisão do visitante e a envia para a API. Esta página é a referência completa — instalação, configuração, comportamento e resolução de problemas.

## Instalação

Adicione o snippet antes de `</body>`, substituindo `SITE_ID` pelo `site_id` do seu site (painel → **Sites**):

```html theme={null}
<script src="https://www.consentfly.com.br/api/v1/script.js"
        data-site-id="SITE_ID"></script>
```

`data-site-id` é o único atributo obrigatório. Sem ele, o script não faz nada — nenhum banner, nenhuma chamada de rede.

## Como funciona

Em cada carregamento de página, o script:

1. **Deriva a base da API da própria URL do script** (`document.currentScript.src`, com fallback para uma busca por `script[src*="/api/v1/script.js"]`). Se o script foi carregado de `https://www.consentfly.com.br/...`, todas as chamadas vão para `https://www.consentfly.com.br/api/v1`.
2. Reenvia silenciosamente qualquer consentimento pendente de uma visita anterior em que a rede falhou (ver [Fila offline](#fila-offline)).
3. Busca a configuração do site em `GET /api/v1/public/site/{siteId}` — textos, link da política, `policy_version`, `geo_enabled` e o `script_token`.
4. Decide se exibe o banner (ver [Quando o banner não aparece](#quando-o-banner-nao-aparece)).
5. Ao clicar em **Aceitar**, **Recusar** ou salvar preferências no modal, grava a decisão em `localStorage` **primeiro**, fecha o banner imediatamente e envia `POST /api/v1/public/consent` em segundo plano com o header `X-Site-Token`.

<Warning>
  **Armadilha do script "proxiado".** Como a base da API vem do `src` do script, um cliente que serve o script pelo próprio domínio (proxy/CDN próprio) recebe um banner que chama `https://seudominio.com/api/v1/...` — e falha, a menos que o proxy repasse também as rotas `/api/v1/public/*`. Este é o erro de integração mais comum. Se não for repassar a API inteira, sirva o script diretamente de `www.consentfly.com.br`.
</Warning>

Timeouts de rede são de **6 segundos** por requisição. Nenhuma falha de rede bloqueia a UI: o banner nunca prende o visitante esperando o backend.

## Atributos de configuração

Todos opcionais, exceto `data-site-id`. Atributos de texto têm precedência sobre a configuração cadastrada no painel; os de política só são usados como fallback quando a busca de configuração falha.

| Atributo              | Tipo        | Efeito                                                                                                                                                                                                                               |
| --------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data-site-id`        | string      | **Obrigatório.** Identifica o site.                                                                                                                                                                                                  |
| `data-banner-label`   | string      | Rótulo curto acima do texto. Padrão: "Este site utiliza cookies."                                                                                                                                                                    |
| `data-banner-text`    | string      | Texto principal do banner. Tem precedência sobre o `banner_text` cadastrado no painel.                                                                                                                                               |
| `data-accept-text`    | string      | Texto do botão de aceite. Padrão: "Aceitar".                                                                                                                                                                                         |
| `data-reject-text`    | string      | Texto do botão de recusa. Padrão: "Recusar".                                                                                                                                                                                         |
| `data-policy-version` | inteiro > 0 | Fixa a versão da política registrada no consentimento. Tem precedência sobre a versão vinda do servidor.                                                                                                                             |
| `data-policy-url`     | URL         | Link da política exibido no banner — **usado apenas como fallback** quando a configuração do site não pôde ser carregada. No fluxo normal, o link vem do painel. URLs `javascript:`, `data:`, `vbscript:` e `file:` são descartadas. |
| `data-policy-id`      | string      | Alternativa a `data-policy-url` no fallback: gera o link `/policy/{id}` na origem do script.                                                                                                                                         |

## Categorias de consentimento

O modal "Configurar cookies" expõe três categorias:

| Categoria   | Chave em `preferences` | Comportamento                                   |
| ----------- | ---------------------- | ----------------------------------------------- |
| Necessários | —                      | Sempre ativos; o toggle é exibido desabilitado. |
| Análise     | `analytics`            | Booleano, padrão `false`.                       |
| Marketing   | `marketing`            | Booleano, padrão `false`.                       |

* **Aceitar** envia `accepted: true` com `analytics: true, marketing: true`.
* **Recusar** envia `accepted: false` com ambas `false`.
* **Salvar preferências** (modal) envia `accepted: true` com os toggles escolhidos.

## Armazenamento no navegador

O script usa `localStorage` — **nenhum cookie é criado** — com fallback em memória quando `localStorage` está indisponível (modo privado restrito, etc.). Também não faz nenhuma requisição a terceiros: fala apenas com a origem de onde foi servido.

| Chave                       | Conteúdo                                                                               |
| --------------------------- | -------------------------------------------------------------------------------------- |
| `haldenhub-consent-id`      | Identificador da decisão deste visitante, gerado no primeiro carregamento.             |
| `haldenhub-consent`         | A decisão em si: `site_id`, `accepted`, `preferences`, `policy_version`, `created_at`. |
| `haldenhub-consent-pending` | Payload enfileirado quando o envio à API falhou.                                       |

<Note>
  O prefixo `haldenhub-` é **permanente** — chaves de armazenamento, ids de DOM (`haldenhub-banner`, `haldenhub-modal`) e classes CSS (`haldenhub-button`, ...) mantêm esse nome para que decisões já gravadas nos navegadores dos visitantes continuem válidas. Não trate como erro nem espere renomeação.
</Note>

## Quando o banner não aparece

O script encerra sem montar nada quando qualquer condição abaixo vale:

1. **Já existe consentimento válido**: `haldenhub-consent` guarda uma decisão para o mesmo `site_id` **e** a mesma `policy_version`. Publicar uma nova versão da política reexibe o banner para todos.
2. **`show_banner: false`** na configuração do site (hoje o servidor sempre envia `true`; o campo existe para supressão futura sem nova versão do script).
3. **Geofencing ativo e visitante fora da região** (abaixo).
4. **`data-site-id` ausente** ou o script já foi inicializado na página (guarda `window.haldenhubBannerInitialized` contra inclusão dupla).

## Geofencing

Chave opcional por site (padrão **desligado**) que restringe a exibição do banner a visitantes da **UE/EEE + Reino Unido + Suíça**. Pensada para clientes que operam fora do Brasil e só querem o banner onde ele é expectativa legal.

A decisão roda **no navegador, não no servidor**: o script compara `Intl.DateTimeFormat().resolvedOptions().timeZone` com uma lista de zonas IANA dessas jurisdições. Fora da lista, o script encerra — sem DOM, sem chamadas.

**Falha aberta, de propósito**: zona desconhecida, navegador sem `Intl` ou qualquer exceção resultam em banner exibido. Mostrar o banner a quem não precisava é um incômodo; escondê-lo de quem precisava é falha de compliance — só um dos dois é aceitável.

<Note>
  Geofencing é uma **decisão de exibição, não um controle de acesso**. Um visitante pode mentir sobre o próprio fuso horário — a única coisa que ele muda com isso é ver ou não o banner. Territórios ultramarinos com fuso próprio (Guiana Francesa, Reunião) ficam fora da lista e caem no lado seguro: banner exibido. Para sites com público brasileiro, mantenha a chave desligada — a LGPD vale para todo visitante, independentemente da origem.
</Note>

## Personalização visual

O script injeta um CSS próprio (cores fixas, botão de aceite em azul escuro) e **não há atributo de tema ou cor de destaque**. A personalização é feita por CSS do seu site, sobrescrevendo as classes `haldenhub-*` — que são estáveis (ver nota sobre o prefixo acima):

```css theme={null}
/* Depois do CSS do banner (o style tem id haldenhub-banner-style) */
#haldenhub-banner .haldenhub-button--accept {
  background: #0d9488;
  border-color: #0d9488;
}
#haldenhub-banner .haldenhub-banner__container {
  border-radius: 8px;
}
```

O banner usa `z-index: 2147483647` e o modal fica logo abaixo, para nunca serem cobertos pelo layout do site.

## Token do site e rotação

Cada site tem um `script_token` que autentica o `POST /public/consent`. O token viaja no header `X-Site-Token` (o fallback via `navigator.sendBeacon` usa o parâmetro `?script_token=`). Ele é entregue ao navegador dentro do payload de `GET /public/site/{siteId}` — ou seja, **não é um segredo**: é uma chave por site que permite revogar um embed vazado sem invalidar os demais sites.

* A verificação **falha fechada**: token inválido ou ausente retorna `401` com código `unauthorized` ("Invalid or missing script token"). Nenhum consentimento é gravado.
* Rotacione com `POST /api/v1/sites/{siteId}/rotate-script-token` (autenticado) ou pelo painel. A rotação invalida o cache da configuração pública; visitantes recebem o token novo no próximo carregamento de página.

## Fila offline

A decisão do visitante é gravada localmente **antes** de qualquer chamada de rede, então o clique sempre é honrado mesmo se o backend, a rede ou uma CSP bloquearem a requisição:

1. O payload é salvo em `haldenhub-consent-pending` e o banner fecha imediatamente.
2. O envio acontece em segundo plano; se o `fetch` falhar, o script tenta `navigator.sendBeacon` como último recurso.
3. Sucesso limpa a fila; falha mantém o payload, que é reenviado silenciosamente no próximo carregamento de página.

**Consequência para auditoria**: uma decisão pode chegar ao banco minutos ou dias depois de tomada — o `created_at` do registro é o momento de recepção no servidor, não o do clique.

## Limites e caching

* `POST /public/consent` tem corpo limitado a **64 KiB** (`413` acima disso) e rate limit de **30 req/min por cliente** e **600 req/min por site** (`429` com `too_many_requests`).
* O `script.js` é servido com `Cache-Control: public, max-age=300` e a configuração do site é cacheada no servidor. Mudanças de texto/política feitas no painel podem levar **até \~5 minutos** para aparecer nos visitantes.
* O IP bruto do visitante **nunca é persistido** — o servidor resolve país/região e descarta o endereço antes da gravação.

## Troubleshooting

### O banner não aparece

* **Consentimento já registrado neste navegador.** Limpe `haldenhub-consent` (e opcionalmente `haldenhub-consent-id`) no `localStorage` e recarregue. É o motivo mais comum durante testes.
* **`data-site-id` ausente ou vazio** na tag do script.
* **`site_id` inexistente**: `GET /public/site/{siteId}` retorna `404` e o script cai no modo fallback — nesse caso o banner ainda aparece com os textos padrão, mas os envios não serão aceitos. Confira o Network do DevTools.
* **Geofencing ligado** e o fuso horário do navegador está fora da UE/EEE/UK/CH. Desligue a chave no painel ou teste com o fuso de um país coberto.
* **Script incluído duas vezes**: só a primeira inicialização roda (`window.haldenhubBannerInitialized`).
* **Bloqueio por CSP ou adblock**: verifique se `script-src` permite `www.consentfly.com.br` e se não há extensão bloqueando a requisição.

### `401 unauthorized` no `POST /public/consent`

* **Token rotacionado com página antiga aberta**: abas carregadas antes da rotação seguram o token velho. O payload fica na fila offline e é reenviado — mas continuará falhando até o visitante recarregar a página e receber o token novo.
* **Requisição manual sem `X-Site-Token`**: o endpoint exige o token em toda chamada (header, `?script_token=` ou campo `script_token` no corpo).
* **Fallback ativo**: se a busca de configuração falhou, o script monta o banner sem token e os envios retornam `401` até a configuração voltar a carregar.

### Erros de CORS

* Os endpoints públicos aceitam qualquer origem, **sem credenciais** — o script chama com `credentials: "omit"`. Se você vê erro de CORS, quase sempre a causa é a **armadilha do proxy**: o script foi servido de outro domínio e está chamando `/api/v1` nesse domínio, que não existe ou responde sem os headers de CORS.
* Confira também `connect-src` na sua CSP: ele precisa permitir `https://www.consentfly.com.br`.

<p align="center">
  Dúvidas? <a href="mailto:suporte@consentfly.com.br">[suporte@consentfly.com.br](mailto:suporte@consentfly.com.br)</a>
</p>
