# Aula 4.9 - Interceptors, erros e acessibilidade

## Identificação

- **Duração:** 2 horas
- **Tipo:** teoria aplicada e laboratório
- **Entrega:** fluxo HTTP resiliente e retorno acessível
- **Laboratório:** [`../exemplos/aula-4.9/index.html`](../exemplos/aula-4.9/index.html)

## Introdução

### O que é um interceptor?

Interceptor é uma função posicionada no caminho das requisições feitas pelo
`HttpClient`. Ela pode observar ou transformar uma requisição antes de chegar à API e
observar a resposta quando ela retorna.

```text
componente → HttpClient → interceptor → API
componente ← Observable ← interceptor ← API
```

Ele funciona como um middleware do **cliente Angular**. Não é a API NestJS, não roda
no MySQL e não intercepta todas as requisições do navegador: apenas as que passam
pelo `HttpClient` configurado naquela aplicação.

### Para que serve?

Interceptors removem preocupações repetidas de cada serviço:

- adicionar um identificador de correlação;
- anexar credenciais permitidas ao destino correto;
- medir duração;
- registrar falhas técnicas;
- aplicar timeout ou retry controlado;
- coordenar um indicador global de atividade.

O `TaskApi` continua responsável pelos endpoints de tarefas. O interceptor cuida de
uma política transversal que vale para várias chamadas.

### Interceptor protege a API?

Não. Qualquer código Angular é entregue ao navegador e pode ser inspecionado ou
alterado pelo usuário. A API NestJS precisa autenticar, autorizar e validar cada
operação.

```text
Angular adiciona credencial → NestJS verifica credencial → regra autoriza ação
```

Nunca coloque senha do MySQL, chave privada ou segredo de servidor em um interceptor.

### O que é uma cadeia de interceptors?

Podemos registrar várias funções. Na ida, elas executam na ordem configurada. A
resposta retorna pelas mesmas funções no sentido inverso.

```text
requisição → correlação → autenticação → telemetria → API
resposta   ← correlação ← autenticação ← telemetria ← API
```

Cada interceptor decide se chama `next(request)`. Sem `next`, a cadeia não continua,
a menos que ele produza uma resposta sintética conscientemente, como em um cache.

### O que é erro HTTP?

É uma falha observada no fluxo de uma requisição. O Angular a representa com
`HttpErrorResponse`, incluindo falhas de rede, timeout, parsing e respostas como 401,
404 ou 500.

Um status descreve a categoria técnica, mas a interface precisa comunicar uma ação
útil: tentar novamente, revisar dados, entrar novamente ou procurar suporte.

### Erro HTTP é igual a exceção global?

Não. Um 404 esperado pertence ao fluxo da chamada e deve ser tratado perto da
operação. `ErrorHandler` é uma última fronteira para erros inesperados que escaparam,
não um substituto para `catchError`.

### O que acessibilidade tem a ver com erros?

Uma mensagem apenas vermelha pode não ser percebida por quem não enxerga cores ou
usa leitor de tela. Retorno acessível combina texto, semântica, foco e uma próxima
ação clara.

```html
<p role="status">Tarefa salva com sucesso.</p>
<p role="alert">Não foi possível salvar. Revise a conexão.</p>
```

`role="status"` anuncia atualizações comuns de forma educada. `role="alert"` é
assertivo e deve ser usado com moderação para mensagens importantes.

### Como isso entra na Knowledge AI?

O painel enviará requisições por uma cadeia previsível. O interceptor adicionará
correlação e medirá a operação; o serviço preservará o erro; a página apresentará
loading, sucesso ou falha com semântica acessível.

## Objetivos

Ao final da aula, o aluno deverá conseguir:

1. explicar interceptor e middleware;
2. diferenciar interceptor Angular e middleware NestJS;
3. criar um interceptor funcional;
4. configurar `withInterceptors`;
5. explicar a ordem de requisição e resposta;
6. clonar requisições imutáveis;
7. utilizar `HttpContextToken` para políticas por requisição;
8. preservar erros com `throwError`;
9. classificar rede, timeout, autenticação, cliente e servidor;
10. aplicar retry apenas quando seguro;
11. separar mensagem ao usuário de telemetria técnica;
12. implementar loading sem contagem incorreta;
13. usar status, alert e foco conscientemente;
14. distinguir erro esperado de `ErrorHandler` global.

## Pré-requisitos

- Aulas 4.1 a 4.8 concluídas;
- `HttpClient`, Observable e operadores RxJS;
- serviços e injeção de dependência;
- estados loading, success, empty e error;
- HTML semântico e navegação por teclado.

## Pergunta orientadora

> Como aplicar políticas comuns a todas as requisições sem esconder erros nem deixar usuários sem retorno?

## Roteiro sugerido

| Etapa | Duração |
|---|---:|
| Interceptor e cadeia HTTP | 25 min |
| Imutabilidade, headers e contexto | 25 min |
| Erros, timeout e retry | 30 min |
| Retorno acessível | 20 min |
| Laboratório | 15 min |
| Revisão | 5 min |

## 1. Forma mínima de um interceptor funcional

```ts
import { HttpInterceptorFn } from "@angular/common/http";

export const loggingInterceptor: HttpInterceptorFn = (request, next) => {
  console.log(request.method, request.url);
  return next(request);
};
```

`request` é a mensagem atual. `next` entrega essa mensagem ao próximo elemento da
cadeia e retorna um Observable de eventos HTTP.

## 2. Configuração explícita

```ts
import { provideHttpClient, withInterceptors } from "@angular/common/http";

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([
        correlationInterceptor,
        authInterceptor,
        telemetryInterceptor,
      ]),
    ),
  ],
};
```

Interceptors funcionais são preferidos pela documentação atual por apresentarem ordem
mais previsível em configurações complexas.

## 3. Ordem da cadeia

Com `[correlation, auth, telemetry]`:

```text
ida:   correlation → auth → telemetry → backend
volta: correlation ← auth ← telemetry ← backend
```

Na volta, cada função observa o Observable devolvido por `next`.

## 4. Requisições são imutáveis

A maior parte de `HttpRequest` e `HttpResponse` não deve ser modificada diretamente.
Use `clone`:

```ts
const correlatedRequest = request.clone({
  setHeaders: { "X-Correlation-ID": crypto.randomUUID() },
});

return next(correlatedRequest);
```

Isso torna o interceptor mais seguro quando a mesma requisição atravessa a cadeia
novamente em um retry. O corpo não recebe proteção contra mutação profunda; evite
alterá-lo no lugar.

## 5. Correlação não é autenticação

Um correlation ID liga registros do navegador, gateway e servidor para investigar uma
operação. Ele não comprova identidade nem concede permissão.

```text
X-Correlation-ID: 58fa...
Authorization: Bearer credencial-do-usuário
```

Cada header tem finalidade diferente.

## 6. Não enviar credencial para qualquer destino

```ts
const isOurApi = request.url.startsWith("/api/");
if (!isOurApi) return next(request);
```

Antes de anexar uma credencial, confirme destino e política. Uma URL externa para
imagens ou analytics não deve receber automaticamente o token da API.

## 7. Exemplo de autenticação

```ts
export const authInterceptor: HttpInterceptorFn = (request, next) => {
  const session = inject(SessionService);
  const token = session.accessToken();

  if (!token || !request.url.startsWith("/api/")) {
    return next(request);
  }

  return next(request.clone({
    setHeaders: { Authorization: `Bearer ${token}` },
  }));
};
```

O servidor ainda deve validar token, expiração, audiência e autorização.

## 8. Metadados com `HttpContext`

Algumas políticas pertencem à aplicação, mas não devem viajar como headers.

```ts
export const SKIP_GLOBAL_ERROR = new HttpContextToken<boolean>(() => false);

http.get("/api/health", {
  context: new HttpContext().set(SKIP_GLOBAL_ERROR, true),
});
```

O interceptor lê `request.context`. Esse contexto não é enviado à API.

## 9. Observando a resposta

```ts
return next(request).pipe(
  tap((event) => {
    if (event.type === HttpEventType.Response) {
      console.log(event.status, request.url);
    }
  }),
);
```

O fluxo contém diferentes `HttpEvent`. Verifique o tipo antes de tratar o evento como
resposta final.

## 10. Telemetria com `finalize`

```ts
export const telemetryInterceptor: HttpInterceptorFn = (request, next) => {
  const startedAt = performance.now();

  return next(request).pipe(
    finalize(() => {
      const duration = performance.now() - startedAt;
      console.info(request.method, request.url, duration);
    }),
  );
};
```

`finalize` executa após sucesso, erro ou cancelamento. Não registre corpo, tokens ou
dados pessoais indiscriminadamente.

## 11. Classificação de falhas

| Situação | Indício | Ação comum |
|---|---|---|
| rede/CORS | status 0 | verificar conexão e configuração |
| timeout | erro com causa de timeout | permitir nova tentativa consciente |
| não autenticado | 401 | renovar sessão ou entrar novamente |
| sem permissão | 403 | explicar limite, não insistir |
| não encontrado | 404 | contextualizar o recurso |
| conflito | 409 | atualizar dados ou resolver versão |
| validação | 400/422 | associar problemas aos campos |
| servidor | 5xx | mensagem segura e correlação |

Não trate todos os 4xx ou 5xx com o mesmo texto.

## 12. Mapeamento para erro de aplicação

```ts
export interface AppHttpError {
  readonly kind: "network" | "auth" | "forbidden" | "not-found" | "server" | "unknown";
  readonly message: string;
  readonly correlationId: string | null;
  readonly retryable: boolean;
}
```

O modelo da interface não precisa expor toda a estrutura técnica de
`HttpErrorResponse`.

## 13. Preserve o canal de erro

```ts
return next(request).pipe(
  catchError((error: HttpErrorResponse) => {
    telemetry.capture(sanitize(error));
    return throwError(() => error);
  }),
);
```

O interceptor registrou e devolveu o erro. O serviço ou componente ainda decide a
mensagem e o estado da operação.

## 14. Quando recuperar no interceptor

Recuperar globalmente só é adequado quando existe uma resposta correta para todas as
chamadas afetadas, como usar um cache válido. Transformar qualquer falha em `[]` faz
a interface confundir erro com vazio.

## 15. Retry não é “tentar até funcionar”

Retry repete a operação. Ele pode ajudar em falhas transitórias, mas aumenta carga e
pode duplicar mutações.

```ts
const canRetry = request.method === "GET";

return next(request).pipe(
  retry({ count: canRetry ? 2 : 0, delay: 500 }),
);
```

Na prática, examine o tipo de erro, aplique atraso progressivo, limite tentativas e
considere jitter. POST só deve ser repetido automaticamente com uma estratégia de
idempotência acordada com o servidor.

## 16. Timeout

```ts
http.get("/api/tasks", { timeout: 5_000 });
```

Timeout limita espera do cliente, mas não prova que o servidor desfez uma mutação. A
operação pode ter chegado ao back-end antes de a resposta expirar.

## 17. Renovação de sessão

Várias requisições podem receber 401 simultaneamente. Um fluxo de renovação precisa:

1. permitir uma renovação por vez;
2. enfileirar ou rejeitar requisições concorrentes;
3. limitar tentativas;
4. evitar interceptar a própria chamada de renovação em ciclo;
5. encerrar sessão quando a renovação falhar.

Não implemente recursão ilimitada dentro do interceptor.

## 18. Indicador global com contador

Um boolean falha quando duas requisições se sobrepõem:

```text
requisição A inicia → loading=true
requisição B inicia → loading=true
requisição A termina → loading=false  ← B ainda está ativa
```

Use um contador:

```ts
loading.start();
return next(request).pipe(finalize(() => loading.finish()));
```

O serviço expõe `activeRequests > 0`. Garanta que cancelamento e erro também reduzam
o contador.

## 19. Quem apresenta a mensagem?

```text
interceptor → política técnica e telemetria
serviço     → traduz contrato para erro de aplicação
componente  → contexto, texto, foco e ação
```

Se todas as camadas abrirem um toast, o usuário receberá mensagens duplicadas.

## 20. Estados acessíveis

```html
@switch (state().status) {
  @case ("loading") {
    <p role="status">Carregando tarefas...</p>
  }
  @case ("error") {
    <section aria-labelledby="error-title">
      <h2 id="error-title">Não foi possível carregar</h2>
      <p role="alert">{{ state().message }}</p>
      <button type="button" (click)="reload()">Tentar novamente</button>
    </section>
  }
}
```

O botão continua acessível por teclado e a mensagem não depende somente de cor.

## 21. `status` ou `alert`?

| Semântica | Uso |
|---|---|
| `role="status"` | carregamento concluído, item salvo, atualização comum |
| `role="alert"` | falha importante que exige atenção imediata |
| foco programático | levar a uma região que precisa ser lida e operada |

Alertas assertivos podem interromper o leitor de tela. Não use para cada pequena
mudança.

## 22. Região viva existente

Para compatibilidade consistente, mantenha a região viva no DOM e altere seu texto:

```html
<p role="status" aria-atomic="true">{{ announcement() }}</p>
```

`aria-atomic="true"` solicita o anúncio do conteúdo completo após a atualização.

## 23. Gerenciamento de foco

Depois de uma falha de envio com vários problemas, mover o foco para um resumo pode
ser útil:

```ts
readonly errorSummary = viewChild<ElementRef<HTMLElement>>("errorSummary");

focusErrorSummary(): void {
  this.errorSummary()?.nativeElement.focus();
}
```

```html
<section #errorSummary tabindex="-1" aria-labelledby="error-title">
```

Não mova foco a cada atualização automática. Preserve o contexto do usuário.

## 24. Mensagem deve orientar

Evite “Erro 500” como único texto. Prefira:

> Não foi possível carregar as tarefas. Tente novamente. Se o problema continuar,
> informe o código 58FA ao suporte.

O status e o stack trace podem ir para telemetria sanitizada.

## 25. `ErrorHandler` global

`ErrorHandler` captura erros inesperados entregues ao mecanismo global do Angular.
Use-o como última fronteira de observabilidade e recuperação segura.

```ts
@Service()
export class GlobalErrorHandler implements ErrorHandler {
  handleError(error: unknown): void {
    this.telemetry.captureUnknown(error);
  }
}
```

Não encaminhe todo 404 esperado ao `ErrorHandler`; trate-o no fluxo HTTP.

## 26. Privacidade e registros

Nunca registre indiscriminadamente:

- header `Authorization`;
- cookies ou tokens;
- prompts e documentos privados;
- dados pessoais;
- corpo completo de formulários;
- SQL ou stack trace na interface.

Prefira allowlist de campos, correlação e redaction.

## 27. Testando interceptors

Teste comportamento observável:

- header foi adicionado apenas à API correta;
- requisição original não foi mutada;
- ordem configurada foi respeitada;
- erro continuou chegando ao consumidor;
- contador voltou a zero em sucesso, falha e cancelamento;
- GET transitório respeitou o limite de retry;
- POST não foi repetido sem idempotência.

Use as ferramentas de teste do `HttpClient` para controlar requisições e respostas.

## 28. Laboratório guiado

Abra o [fluxo de interceptors](../exemplos/aula-4.9/index.html).

### Etapa 1 — Envie uma requisição com sucesso

Observe a ordem de ida e a ordem inversa da resposta.

### Etapa 2 — Inspecione os headers

Veja correlação e autorização simulada. Desative autenticação e compare.

### Etapa 3 — Simule rede, 401, 403, 404 e 500

Compare classificação técnica, mensagem ao usuário e ação sugerida.

### Etapa 4 — Ative retry em GET

Veja uma falha transitória ser repetida até o limite. Troque para POST e observe o
bloqueio do retry automático.

### Etapa 5 — Compare status e alert

O laboratório identifica qual região viva anunciaria cada estado.

### Etapa 6 — Examine as fontes

Compare configuração, interceptors, mapeamento e template acessível.

## 29. Sobre o laboratório estático

O laboratório simula a cadeia sem enviar requisições externas. A pasta `src/app`
contém a configuração e os interceptors Angular equivalentes. Dados inseridos são
renderizados com `textContent`, não `innerHTML`.

## 30. Erros comuns

### Colocar toda regra no interceptor

Ele se torna global, acoplado e difícil de entender.

### Engolir o erro

Devolver lista vazia impede a tela de diferenciar falha de ausência de dados.

### Anexar token em URL externa

Credenciais podem vazar para destinos que não deveriam recebê-las.

### Mutar requisição diretamente

Interceptors e retries dependem de operações idempotentes e clones previsíveis.

### Retry em toda operação

POST pode ser executado duas vezes.

### Um boolean para requisições concorrentes

O primeiro término esconde o loading enquanto outra chamada continua.

### Alertar tudo

Muitas regiões assertivas interrompem e confundem tecnologias assistivas.

### Usar somente cor

Estado precisa de texto, semântica e ação compreensível.

### Mostrar detalhes técnicos

Stack trace e infraestrutura não ajudam o usuário e podem expor informação sensível.

## 31. Exercício de fixação

Implemente:

1. correlation interceptor;
2. telemetry interceptor com duração;
3. token de contexto para pular o loading global;
4. mapeador de 401, 403, 404 e 5xx;
5. região de status para sucesso;
6. resumo de erro focável com botão de nova tentativa.

Desenhe a ordem completa de requisição e resposta.

## 32. Desafio individual

Adicione uma política de retry que:

- aceite apenas GET e HEAD;
- repita apenas status 0, 502, 503 ou 504;
- limite tentativas;
- use atraso progressivo com jitter;
- permita opt-out com `HttpContextToken`;
- registre apenas dados sanitizados;
- comunique a tentativa ao usuário sem alertas excessivos.

## 33. Lista de verificação de conclusão

- [ ] Sei explicar interceptor sem confundi-lo com API.
- [ ] Conheço a ordem da cadeia.
- [ ] Clono requisições antes de alterar headers.
- [ ] Restrinjo credenciais ao destino correto.
- [ ] Preservo erros que não resolvi.
- [ ] Diferencio 401 de 403.
- [ ] Não aplico retry cego a POST.
- [ ] Uso contador para loading concorrente.
- [ ] Distingo `status`, `alert` e foco.
- [ ] Não exponho detalhes sensíveis.

## 34. Critérios de avaliação

| Critério | Pontos |
|---|---:|
| Cadeia e configuração corretas | 20 |
| Imutabilidade e headers seguros | 15 |
| Classificação e preservação de erros | 20 |
| Retry e concorrência conscientes | 15 |
| Retorno acessível | 20 |
| Privacidade, testes e explicação | 10 |
| **Total** | **100** |

## 35. Resumo

- interceptor é middleware do `HttpClient`;
- funções registradas formam uma cadeia ordenada;
- requisição e resposta devem ser clonadas para alteração;
- políticas globais não substituem regras de domínio;
- erros não resolvidos continuam no canal de erro;
- retry depende de método, falha e idempotência;
- mensagens acessíveis combinam texto, semântica, foco e ação;
- `ErrorHandler` trata o inesperado, não substitui tratamento HTTP.

## 36. Fontes oficiais

- [Angular: interceptors](https://angular.dev/guide/http/interceptors)
- [Angular: fazendo requisições e tratando erros](https://angular.dev/guide/http/making-requests)
- [Angular: HttpErrorResponse](https://angular.dev/api/common/http/HttpErrorResponse)
- [Angular: acessibilidade](https://angular.dev/best-practices/a11y)
- [W3C: erros com alert e regiões vivas](https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA19)

## Próxima aula

Na Aula 4.10, concluiremos o módulo com testes de componentes e serviços, compilação de
produção, documentação e apresentação do painel Angular.
