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

# Session Replay — Network & Console

> Captura metric-only de fetch/XHR + console.* via rrweb custom events. scrubURL automático em query params sensíveis.

<Warning>
  **Política metric-only por default.** O SDK NÃO captura headers nem
  request/response body. O operador pode opt-in via `recordHeaders: true` ou
  `recordBody: true`, mas mesmo nesse modo as blocklists hardcoded
  (`Authorization`, `Cookie`, etc) permanecem ativas.
</Warning>

## O que é capturado por default

O SDK monkey-patches `window.fetch` + `XMLHttpRequest.prototype.{open,send}`
e empurra cada request como rrweb custom event (`tag: 'atlas-network'`). Cada
evento contém:

```typescript theme={null}
{
  source: 'fetch' | 'xhr',
  method: 'GET' | 'POST' | ...,
  url: 'https://api.brand.com/x?token=%5Bredacted%5D&foo=bar',  // scrubbed
  status: 200,
  statusText: 'OK',
  startTime: 12345.6,        // performance.now()
  endTime: 12567.8,
  duration: 222.2,
  requestSize: 128,          // bytes (calculado a partir do body)
  responseSize: 1024,        // bytes (Content-Length OR responseText.length)
  // Headers e body NÃO inclusos por default
}
```

Console também é capturado (custom event `tag: 'atlas-console'`):

```typescript theme={null}
{
  level: 'warn' | 'error' | ...,
  args: ['msg 1', '{"x":1}'],  // cada arg stringificado + truncado em maxLength
  stack: '...',                 // apenas para nível 'error'
  timestamp: 1716123456789
}
```

## URL scrubbing automático

Sempre que uma URL é capturada (fetch, XHR, navigation), o SDK roda
`scrubURL(rawUrl)`. Query params na seguinte lista são **substituídos por
`[redacted]`** (URL-encoded como `%5Bredacted%5D`):

```typescript theme={null}
// src/replay/constants.ts
export const REPLAY_SENSITIVE_QUERY_PARAMS = [
  'password', 'pwd',
  'token', 'access_token', 'refresh_token',
  'api_key', 'apikey',
  'secret', 'authorization', 'auth',
  'cpf', 'document',
] as const;
```

Exemplo:

```javascript theme={null}
// Original (no app):
fetch('https://api.brand.com/v1/auth?token=eyJhbGciOiJIUzI1...&user=42');

// Capturado no replay:
{
  url: 'https://api.brand.com/v1/auth?token=%5Bredacted%5D&user=42',
  //                                  ^^^^^^^^^^^^^^^^^^^
  //                            token redacted, user preservado
}
```

Matching é **case-insensitive** no nome do param. O valor do param é sempre
substituído integralmente — não há heurística parcial (todo o token vira
`[redacted]`, não apenas um pedaço).

<Tip>
  Se o operador usa nomes custom para tokens (`auth_token_v2`, `bearer`,
  etc), adicione-os na lista via fork local do SDK. Phase 2.5 vai expor
  isto como config (`network.scrubAdditionalParams`).
</Tip>

## Headers hardcoded como blocklist

Mesmo com `recordHeaders: true`, estes headers **nunca** aparecem no replay:

```typescript theme={null}
// src/replay/constants.ts
export const REPLAY_NETWORK_HEADERS_BLOCKLIST = [
  'authorization',
  'cookie',
  'set-cookie',
  'x-api-key',
  'proxy-authorization',
] as const;
```

Matching é case-insensitive. Headers extras podem ser adicionados via config
do operador:

```javascript theme={null}
sessionReplay: {
  network: {
    enabled: true,
    recordHeaders: true,                          // opt-in (default false)
    requestHeadersToIgnore: ['x-custom-secret']   // extras (case-insensitive)
  }
}
```

## Configurações disponíveis

```javascript theme={null}
window.iGamingSDK.init({
  // ... core ...
  sessionReplay: {
    enabled: true,

    network: {
      enabled: true,                  // default true se sessionReplay.enabled
      recordHeaders: false,           // OPT-IN — exposição PII
      recordBody:    false,           // OPT-IN — alta exposição PII
      capturePerformance: true,       // timing data (default true)
      payloadSizeLimit: 1_048_576,    // 1MB cap para responseSize calc
      requestHeadersToIgnore: []      // extras além da blocklist hardcoded
    },

    console: {
      enabled: true,                                   // default true
      levels:   ['warn', 'error'],                     // default; ampliar p/ log/info/debug se necessário
      maxLength: 2000                                  // truncate cada arg em chars
    }
  }
});
```

## Truncamento + stringify dos args do console

`console.warn(obj)` no app vira string no replay via:

1. `null` / `undefined` → "null" / "undefined"
2. `Error` instances → `"<name>: <message>"`
3. Objetos → `JSON.stringify(obj)` (catch para circular refs → `Object.prototype.toString.call(...)`)
4. Funções → `"function <name>"` ou `"function"`
5. Resto → `String(value)`

Cada arg é truncado em `maxLength` chars (default 2000). Truncated value
termina em `'…'` para sinalizar corte.

```javascript theme={null}
// Original:
console.warn('player', { id: 42, name: 'long name that goes on...' });

// Capturado:
{
  level: 'warn',
  args: [
    'player',
    '{"id":42,"name":"long name that goes on..."}'   // até 2000 chars
  ],
  timestamp: 1716123456789
  // stack omitido (level != 'error')
}
```

## Erros no app NÃO param o console nativo

```javascript theme={null}
// Operador pode confiar:
console.error(new Error('boom'));
// 1. Replay registra { level: 'error', args: ['Error: boom'], stack: ... }
// 2. console.error NATIVO continua funcionando — devtools mostra normal

// Mesmo se a captura do replay lançar erro internamente (improvável):
// 3. Try/catch envolvendo o SDK garante que console.error NUNCA é
//    quebrado pelo Atlas — operador nunca vê regressão no log nativo.
```

## Auditando o que está sendo capturado

```sql theme={null}
-- ClickHouse: ver os 10 últimos eventos network capturados
SELECT
  organization_id,
  brand_id,
  replay_id,
  JSONExtract(payload, 'data.payload.method', 'String') AS method,
  JSONExtract(payload, 'data.payload.url',    'String') AS url,
  JSONExtract(payload, 'data.payload.status', 'Int32')  AS status
FROM atlas.tbt_sdk_replay_events
ARRAY JOIN events AS payload
WHERE JSONExtract(payload, 'data.tag', 'String') = 'atlas-network'
  AND event_date = today()
ORDER BY first_event_ts DESC
LIMIT 10;
```

## Recomendação de produção

<CardGroup cols={2}>
  <Card title="Manter metric-only" icon="shield-check">
    Default config (`recordHeaders: false`, `recordBody: false`) é safe.
    Atende 90% dos casos de debug — duração, status, ordem das requests.
  </Card>

  <Card title="Opt-in com Compliance review" icon="user-check">
    Habilitar `recordHeaders: true` só após review do time de Compliance e
    audit log ativo. Body capture (`recordBody: true`) requer DPA explícito
    com o jogador.
  </Card>
</CardGroup>

## Limitações

* Streams (`fetch().then(r => r.body)`) reportam `responseSize=0` quando o
  client lê via `.body.getReader()` em vez de `.text()`/`.json()`.
* WebSocket NÃO é capturado nesta versão (briefing v4 §5.14 — Phase 2.5).
* Server-Sent Events (`EventSource`) NÃO é capturado — Phase 2.5.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Masking" icon="mask" href="/sdk/web-analytics/session-replay-masking">
    Garantir que PII visual também não vaza.
  </Card>

  <Card title="Triggers" icon="bolt" href="/sdk/web-analytics/session-replay-triggers">
    Reduzir volume capturando só sessões críticas.
  </Card>
</CardGroup>
