# SDK

URL: /desenvolvedores/sdk

Referência do bridge: init, captura de erros, contexto, React, TanStack Query e source maps.

O SDK tem dois pacotes. O `@mira/bridge` fica na app e cuida do widget, da captura de erros e do contexto. O `@mira/sourcemaps` roda no build e envia os source maps. Para instalar e configurar cada um, siga [Integrar uma app](/desenvolvedores/integrar).

```bash
npm install https://app.miraqa.org/downloads/sdk/mira-bridge-0.15.0.tgz
npm install --save-dev https://app.miraqa.org/downloads/sdk/mira-sourcemaps-0.1.1.tgz
```

O bridge exporta:

| Import | Conteúdo |
| --- | --- |
| `@mira/bridge` | `mira`, com `init`, `open`, `setContext`, `register`, `setUser`, `captureException`, `captureMessage`, `lastEventId`, `addErrorExtractor` e `sessionEventIds` |
| `@mira/bridge/widget` | `widgetIntegration`, para ativar o widget junto com a captura de erros |
| `@mira/bridge/react` | `ErrorBoundary` |
| `@mira/bridge/tanstack-query` | `tanstackQueryIntegration` |

## Iniciar [#iniciar]

```ts
import { mira } from "@mira/bridge";

mira.init({
  dsn: import.meta.env.VITE_MIRA_DSN,
  release: `${version}+${import.meta.env.VITE_BUILD_SHA}`,
  buildTime: import.meta.env.VITE_BUILD_TIME,
  sampleRate: 1,
});
```

| Opção | Uso |
| --- | --- |
| `dsn` | Ativa a captura de erros. Vazio ou ausente, o SDK de erros fica desativado. Malformado, o SDK avisa no console com o prefixo `[Mira]` e fica desativado, sem lançar erro |
| `release` | Versão e SHA curto, como `1.4.0+abc1234`. Entra em filtros e na detecção de regressão |
| `buildTime` | Horário do build (ISO, número ou `Date`). Sem ele, o Mira não reabre um erro resolvido |
| `sampleRate` | Fração das ocorrências enviadas, de 0 a 1. Padrão 1 |
| `beforeSend(event, { originalException })` | Altera a ocorrência ou descarta com `null`. A redação roda depois |
| `integrations` | Lista de integrações, como a do TanStack Query e a do widget |
| `redaction.extraKeyPatterns` | Nomes de chave extras para redigir, somados aos padrões |
| `dedupeWindowMs` | Janela do dedupe. Padrão 5 s |
| `maxEventsPerPage` | Envios por carregamento de página. Padrão 100 |
| `environment` | Informativo: o ambiente vem da chave do DSN. Se divergir, o SDK avisa uma vez no console |

`mira.init({ integrations: [widgetIntegration({ instanceUrl })] })` ativa só o widget. Passe também `dsn` para ativar a captura de erros. O atalho `mira.init({ instanceUrl })` foi removido: sem a integração, o SDK avisa no console e o widget não abre.

### Opções do widget [#opções-do-widget]

| Opção | Uso |
| --- | --- |
| `instanceUrl` | URL do Mira, como `https://app.miraqa.org`. Obrigatória |
| `trigger` | `"button"` (padrão), `"shortcut"` ou `"manual"` |
| `shortcut` | Combinação do atalho, como `"Ctrl+Shift+M"`. O padrão é `Alt+Shift+M` |
| `capture` | `false` impede o widget de iniciar a captura de console, erros e rede. Screenshots, arquivos e texto continuam disponíveis |

Com `trigger: "manual"`, nenhum botão aparece. Chame `mira.open()` a partir de um menu da app.

## O que vira ocorrência [#o-que-vira-ocorrência]

| Fonte | `level` | Tag `mechanism` |
| --- | --- | --- |
| `window.onerror` | `error` | `onerror` |
| `unhandledrejection` | `error` | `unhandledrejection` |
| `console.error` | `error` | `console` |
| `console.warn` | `warning` | `console` |
| `ErrorBoundary` | `error` | `react` |
| Query ou mutation do TanStack Query que falhou depois do `retry` | `error` | `tanstack-query` ou `tanstack-mutation` |

Um `console.error` ou `console.warn` com um `Error` nos argumentos vira exceção. Sem `Error`, vira mensagem com os argumentos em texto. O mesmo objeto de erro conta uma vez só, mesmo que chegue por mais de um caminho.

## Capturar à mão [#capturar-à-mão]

```ts
const eventId = mira.captureException(error, {
  request: { url, method, headers, body },
  response: { status, body },
  tags: { feature: "checkout" },
});
mira.captureMessage("Pagamento recusado sem motivo", "warning");
mira.lastEventId();
```

`request` e `response` aceitam `Request` e `Response` nativos ou objetos simples. De um `Response` nativo o SDK lê só status e URL, porque o corpo já foi consumido. Para enviar o corpo, passe o objeto simples. No nível de dados mínimo do ambiente, os corpos não saem do navegador.

`captureException` devolve o id da ocorrência, ou `undefined` quando a amostragem, o `beforeSend` ou o limite `maxEventsPerPage` a descarta. Use `mira.lastEventId()` na página de erro da app para mostrar o id à pessoa, que pode colar o id na busca de **Erros**.

### Validação de resposta com Zod [#validação-de-resposta-com-zod]

Quando o parse com Zod roda depois do client HTTP, capture o `ZodError` junto com o request:

```ts
const response = await apiClient.get(url);
const parsed = schema.safeParse(response.data);
if (!parsed.success) {
  mira.captureException(parsed.error, {
    request: { url, method: "GET" },
    response: { status: response.status, body: response.data },
  });
}
```

O extrator embutido envia cada issue com caminho, código, esperado, recebido e mensagem. A mensagem sai das issues sem o valor recebido, então o agrupamento não muda com o valor e uma senha não vaza pela mensagem. No nível de dados completo, o detalhe mostra o valor recebido, lido do corpo da resposta pelo `path` de cada issue.

### Extratores da app [#extratores-da-app]

```ts
mira.addErrorExtractor((error) =>
  error instanceof ApiError
    ? {
        tags: { api: error.service },
        fingerprint: [error.code],
        details: [{ kind: "custom", name: "api", data: { code: error.code, traceId: error.traceId } }],
      }
    : undefined,
);
```

O extrator devolve `type`, `value`, `fingerprint`, `tags` e `details`, ou `undefined` para erros que não conhece. Os extratores rodam na ordem de registro, depois do de Zod: `type`, `value` e `fingerprint` do último valem, as `tags` se juntam e os `details` se somam. Coloque em `details` o que varia a cada ocorrência, porque `details` não entra no agrupamento. Um detalhe `custom` aceita até 50 chaves com valores primitivos. Um extrator que lança erro é ignorado.

### Cadeia de `cause` [#cadeia-de-cause]

Cada nível de `error.cause` entra na exceção, do mais próximo ao mais distante, com tipo, mensagem e stack, até cinco níveis.

## Usuário e contexto [#usuário-e-contexto]

```ts
mira.setUser({ id: user.id, email: user.email });
mira.setUser(null); // no logout
```

Sem `setUser`, o SDK usa um id anônimo aleatório, salvo no `localStorage`, para contar usuários afetados. No nível de dados mínimo, o Mira descarta o email.

`mira.setContext({ version, env, appUser })` define o contexto que vai nos reports, e `mira.register(nome, provider)` registra um snapshot de estado. Os dois estão em [Integrar uma app](/desenvolvedores/integrar#c-expor-usuário-e-estado-da-app).

## React e TanStack Query [#react-e-tanstack-query]

O núcleo não importa React. O `ErrorBoundary` e a integração com o TanStack Query ficam em subpaths:

```tsx
import { ErrorBoundary } from "@mira/bridge/react";
import { tanstackQueryIntegration } from "@mira/bridge/tanstack-query";

mira.init({ dsn, integrations: [tanstackQueryIntegration(queryClient)] });

<ErrorBoundary
  fallback={({ eventId, resetError }) => <ErrorPage eventId={eventId} onRetry={resetError} />}
>
  <App />
</ErrorBoundary>;
```

A integração escuta o `QueryCache` e o `MutationCache` do `QueryClient` e envia as tags `query.key` ou `mutation.key`, só com o primeiro segmento da key. O resto da key costuma trazer filtros e ids digitados e fica fora do evento.

## Widget como integração [#widget-como-integração]

Em desenvolvimento, QA e staging, o widget entra como integração da captura de erros:

```ts
import { widgetIntegration } from "@mira/bridge/widget";

mira.init({ dsn, integrations: [widgetIntegration({ instanceUrl: "https://app.miraqa.org", trigger: "button" })] });
```

Sem o widget, o build de produção não baixa a interface dele: o núcleo só carrega o widget por `import()`.

## Breadcrumbs [#breadcrumbs]

Cada ocorrência leva até 100 breadcrumbs, do mais antigo ao mais recente:

- `console`: as chamadas de console, com o texto dos argumentos.
- `fetch` e `xhr`: método, URL e status, sem headers nem corpos.
- `navigation`: trocas de URL por `pushState`, `replaceState`, `popstate` e `hashchange`.

As requisições do próprio SDK ao Mira ficam fora.

## Redação [#redação]

A redação roda na página antes do envio, depois do `beforeSend`, e o Mira a aplica de novo antes de gravar. Ela cobre os headers `Authorization`, `Cookie` e `Set-Cookie`, chaves sensíveis (como `token`, `password` e `secret`), JWTs, tokens Bearer e pares `chave=valor`. Remove senhas embutidas em URLs. Nas URLs, também esconde os parâmetros `code`, `state`, `id_token`, `access_token` e `refresh_token`, como os de um callback OAuth.

A redação não reconhece dados pessoais soltos, como um nome ou email em `?q=`. Evite dados pessoais na query string ou cadastre o nome da chave em `redaction.extraKeyPatterns`.

## Dedupe e limites [#dedupe-e-limites]

A mesma ocorrência sai na primeira vez. As repetições dentro de `dedupeWindowMs` saem juntas no fim da janela, como uma ocorrência com contador. Depois de `maxEventsPerPage` envios, o SDK descarta o resto até a página recarregar, para conter loops de erro.

Um evento cabe em 200 KiB. Acima disso, o SDK corta, nesta ordem: corpos do request, breadcrumbs mais antigos, causas, detalhes, estado e, por fim, a mensagem. Quando a ingestão responde `429`, o SDK pausa os envios pelo tempo do cabeçalho `Retry-After`.

## Source maps [#source-maps]

### Plugin Vite [#plugin-vite]

```ts
import { miraSourceMaps } from "@mira/sourcemaps/vite";

miraSourceMaps({ release, buildTime });
```

| Opção | Padrão |
| --- | --- |
| `release` | Obrigatória. A mesma do `mira.init` |
| `buildTime` | Início do build. Use o mesmo valor do `mira.init` |
| `url` | Variável `MIRA_URL` |
| `projectId` | Variável `MIRA_PROJECT_ID` |
| `token` | Variável `MIRA_SOURCEMAPS_TOKEN` |

O plugin só roda em `vite build`. Sem token, os maps são removidos sem envio e o log avisa. Com token e sem URL ou ID do projeto, o build falha.

### Linha de comando [#linha-de-comando]

```bash
mira-sourcemaps upload dist --release 1.4.0+abc1234 [--build-time <iso>] [--url <url>] [--project <id>]
mira-sourcemaps check dist
```

`upload` injeta os Debug IDs, envia os maps e apaga os `.map` do diretório. `check` sai com erro se sobrar um `.map`. Os dois leem a credencial de `MIRA_SOURCEMAPS_TOKEN`.

## Próximos passos [#próximos-passos]

<Cards>
<Card href="/desenvolvedores/integrar" title="Integrar uma app" description="A trilha completa, do ambiente ao primeiro report." />
<Card href="/desenvolvedores/webhooks" title="Payloads de webhook" description="Receba eventos de erro, como error.created e error.spike." />
<Card href="/desenvolvedores/tokens" title="Tokens" description="A credencial de serviço do upload de source maps." />
</Cards>
