{"_id":"@callsync/webrtc-sip-sdk-react","_rev":"4-155f62a78aedb5369ae6d1ef740fb836","name":"@callsync/webrtc-sip-sdk-react","dist-tags":{"latest":"1.1.0"},"versions":{"0.3.0":{"name":"@callsync/webrtc-sip-sdk-react","version":"0.3.0","keywords":["webrtc","sip","react","nextjs","voip","typescript"],"license":"MIT","_id":"@callsync/webrtc-sip-sdk-react@0.3.0","maintainers":[{"name":"marcelocmenezes","email":"marcelomenezes.0907@gmail.com"}],"dist":{"shasum":"eb1dd87fead97582a5b23bbd7721d2b88801beb4","tarball":"https://registry.npmjs.org/@callsync/webrtc-sip-sdk-react/-/webrtc-sip-sdk-react-0.3.0.tgz","fileCount":14,"integrity":"sha512-p4EkXqXPQmMY4UrQR7hU2lViiPMJjh5ZV31o0D/s03ZQ8NfYDGS9VI5hLPzvPnTvvw0DbA36hoLiDC/+Pu23Kw==","signatures":[{"sig":"MEQCIH4lzL26KYLU0Hj5fB3Yqz8ZgzjhoeqSZHFuxbhcPlROAiBIs1Y+9NU3tN3fDRaXpf0Xk3+gILonLkjvMlmy29TcFw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":281706},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.mjs","require":"./dist/react.js"}},"gitHead":"6bd710a38092c2b72457115339be8d879c29e45f","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","release":"changeset publish","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","test:coverage:changed":"npm run test:coverage && node scripts/check-changed-coverage.mjs --base \"$COVERAGE_BASE\" --head \"$COVERAGE_HEAD\""},"_npmUser":{"name":"marcelocmenezes","email":"marcelomenezes.0907@gmail.com"},"_npmVersion":"11.8.0","description":"TypeScript SDK for React/Next.js WebRTC/SIP Gateway integration","directories":{},"_nodeVersion":"24.13.1","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^24.1.0","react":"^18.3.0","vitest":"^1.6.0","react-dom":"^18.3.0","typescript":"^5.4.0","@types/react":"^18.3.0","@changesets/cli":"^2.27.0","@types/react-dom":"^18.3.0","@vitest/coverage-v8":"^1.6.1","@testing-library/react":"^14.3.0","@testing-library/jest-dom":"^6.4.0","@changesets/changelog-github":"^0.5.0"},"peerDependencies":{"react":">=18","react-dom":">=18"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/webrtc-sip-sdk-react_0.3.0_1786728472289_0.8728804308210192","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@callsync/webrtc-sip-sdk-react","version":"0.3.1","keywords":["webrtc","sip","react","nextjs","voip","typescript"],"license":"MIT","_id":"@callsync/webrtc-sip-sdk-react@0.3.1","maintainers":[{"name":"marcelocmenezes","email":"marcelomenezes.0907@gmail.com"}],"dist":{"shasum":"3ce04a047bd0318867b0dfcb68c47eb963086670","tarball":"https://registry.npmjs.org/@callsync/webrtc-sip-sdk-react/-/webrtc-sip-sdk-react-0.3.1.tgz","fileCount":14,"integrity":"sha512-3kTve3dfnYzrhfyF52PSQJG1KgAVM3f9hSnzHQaZ7x6JvZPI1qYUJxIz0POzNdRTwDdWCU775mFk2F7KKH6h8Q==","signatures":[{"sig":"MEQCIGalrbdKVI1xtNETCX7YmjlBP+dVMc68C9sGBdApr1ruAiBNdN2F4gf2C5HftPlW5ja6JGeS2Fgf3MaeHtEkWYk9lw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":281738},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.mjs","require":"./dist/react.js"}},"gitHead":"6bd710a38092c2b72457115339be8d879c29e45f","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","prepack":"npm run build","release":"changeset publish","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","test:coverage:changed":"npm run test:coverage && node scripts/check-changed-coverage.mjs --base \"$COVERAGE_BASE\" --head \"$COVERAGE_HEAD\""},"_npmUser":{"name":"marcelocmenezes","email":"marcelomenezes.0907@gmail.com"},"_npmVersion":"11.8.0","description":"TypeScript SDK for React/Next.js WebRTC/SIP Gateway integration","directories":{},"_nodeVersion":"24.13.1","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^24.1.0","react":"^18.3.0","vitest":"^1.6.0","react-dom":"^18.3.0","typescript":"^5.4.0","@types/react":"^18.3.0","@changesets/cli":"^2.27.0","@types/react-dom":"^18.3.0","@vitest/coverage-v8":"^1.6.1","@testing-library/react":"^14.3.0","@testing-library/jest-dom":"^6.4.0","@changesets/changelog-github":"^0.5.0"},"peerDependencies":{"react":">=18","react-dom":">=18"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/webrtc-sip-sdk-react_0.3.1_1786728823259_0.0009011096090856263","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@callsync/webrtc-sip-sdk-react","version":"1.0.0","keywords":["webrtc","sip","react","nextjs","voip","typescript"],"license":"MIT","_id":"@callsync/webrtc-sip-sdk-react@1.0.0","maintainers":[{"name":"marcelocmenezes","email":"marcelomenezes.0907@gmail.com"}],"dist":{"shasum":"21655d23b223eb988c8cb87c6dac9bd36ee8acfb","tarball":"https://registry.npmjs.org/@callsync/webrtc-sip-sdk-react/-/webrtc-sip-sdk-react-1.0.0.tgz","fileCount":14,"integrity":"sha512-4HuIHYpOtsk/NybXv7lUu4I23fJCtYYsA8+5lMGUI+GpiP3tVNZ9hZFX2De0EVGGqdgsU7H9bXv0BflQ0eg3kA==","signatures":[{"sig":"MEQCIBib0v1YNvRFKK5BUVj0qnIs5MimrDSnimkXdDsCh+CZAiBFn44L5W7DFO1qiXakrV4MqeqP9nR57EP+orSHEu80pQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":299676},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.mjs","require":"./dist/react.js"}},"gitHead":"f5fc072b1b82b3c19481e37e1fda3f8cdfa27f69","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","prepack":"npm run build","release":"changeset publish","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","test:coverage:changed":"npm run test:coverage && node scripts/check-changed-coverage.mjs --base \"$COVERAGE_BASE\" --head \"$COVERAGE_HEAD\""},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:be6fbf1a-fafb-4a70-a22c-f983b18861d5"}},"_npmVersion":"12.0.2","description":"TypeScript SDK for React/Next.js WebRTC/SIP Gateway integration","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["dist/react.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^24.1.0","react":"^18.3.0","vitest":"^1.6.0","react-dom":"^18.3.0","typescript":"^5.4.0","@types/react":"^18.3.0","@changesets/cli":"^2.27.0","@types/react-dom":"^18.3.0","@vitest/coverage-v8":"^1.6.1","@testing-library/react":"^14.3.0","@testing-library/jest-dom":"^6.4.0","@changesets/changelog-github":"^0.5.0"},"peerDependencies":{"react":">=18","react-dom":">=18"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/webrtc-sip-sdk-react_1.0.0_1787772933169_0.37619752450930966","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@callsync/webrtc-sip-sdk-react","version":"1.1.0","description":"TypeScript SDK for React/Next.js WebRTC/SIP Gateway integration","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","typesVersions":{"*":{"react":["dist/react.d.ts"]}},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.mjs","require":"./dist/react.js"}},"scripts":{"build":"tsup","prepack":"npm run build","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","test:coverage:changed":"npm run test:coverage && node scripts/check-changed-coverage.mjs --base \"$COVERAGE_BASE\" --head \"$COVERAGE_HEAD\"","typecheck":"tsc --noEmit","release":"changeset publish"},"peerDependencies":{"react":">=18","react-dom":">=18"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"devDependencies":{"@changesets/changelog-github":"^0.5.0","@changesets/cli":"^2.27.0","@testing-library/jest-dom":"^6.4.0","@testing-library/react":"^14.3.0","@types/react":"^18.3.0","@types/react-dom":"^18.3.0","@vitest/coverage-v8":"^1.6.1","jsdom":"^24.1.0","react":"^18.3.0","react-dom":"^18.3.0","tsup":"^8.0.0","typescript":"^5.4.0","vitest":"^1.6.0"},"publishConfig":{"registry":"https://registry.npmjs.org/","access":"public"},"keywords":["webrtc","sip","react","nextjs","voip","typescript"],"license":"MIT","gitHead":"9e87a18bfddb739b7aaa70893851f034211eced0","_id":"@callsync/webrtc-sip-sdk-react@1.1.0","_nodeVersion":"22.23.2","_npmVersion":"12.0.2","dist":{"integrity":"sha512-EPDjKSUq4ZlGkRReHtP90hpFA5KqU0esAuHrFWNnHk0R+THOaSQ6kRPOLAE7qUZBPx+AvIGfgcvX5EBZ5B3lfw==","shasum":"21dbad4e8b8111a9b34a66edb97102c23f4848ea","tarball":"https://registry.npmjs.org/@callsync/webrtc-sip-sdk-react/-/webrtc-sip-sdk-react-1.1.0.tgz","fileCount":14,"unpackedSize":324628,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEQYR6KArctokdGPpb/JA+NJC9ew7Vtvz4lBVYzoWl1FAiEAirbEUa4nhiAIyJrj+bu6U2l/QiQGPEATkLbyxOJbpkg="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:be6fbf1a-fafb-4a70-a22c-f983b18861d5"}},"directories":{},"maintainers":[{"name":"marcelocmenezes","email":"marcelomenezes.0907@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/webrtc-sip-sdk-react_1.1.0_1787863373634_0.5191130306889031"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-14T17:27:52.149Z","modified":"2026-08-27T20:42:54.023Z","0.3.0":"2026-08-14T17:27:52.447Z","0.3.1":"2026-08-14T17:33:43.411Z","1.0.0":"2026-08-26T19:35:33.319Z","1.1.0":"2026-08-27T20:42:53.778Z"},"license":"MIT","keywords":["webrtc","sip","react","nextjs","voip","typescript"],"description":"TypeScript SDK for React/Next.js WebRTC/SIP Gateway integration","maintainers":[{"name":"marcelocmenezes","email":"marcelomenezes.0907@gmail.com"}],"readme":"# @callsync/webrtc-sip-sdk-react\n\nSDK TypeScript para integrar aplicações React / Next.js com o **WebRTC/SIP Gateway** — um servidor Go que faz a ponte entre clientes WebRTC no browser e uma central SIP (Asterisk).\n\n## Funcionalidades\n\n- Zero dependências de runtime (usa `WebSocket`, `RTCPeerConnection` e `fetch` nativos)\n- TypeScript estrito em todo o código\n- Reconexão automática com backoff exponencial e keep-alive via PING/PONG\n- Suporte a ICE trickle e priorização de Opus, com PCMU/PCMA como fallback no SDP\n- Hooks React 18 com `useSyncExternalStore` — sem Context, sem Provider, sem re-renders desnecessários\n- Seguro para SSR (Next.js App Router e Pages Router)\n\n---\n\n## Instalação\n\nEste pacote é publicado no **GitHub Packages**. Adicione o registry do escopo ao `.npmrc` do seu projeto (ou globalmente):\n\n```ini\n# .npmrc\n@callsync:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}\n```\n\nEm seguida, instale:\n\n```bash\nnpm install @callsync/webrtc-sip-sdk-react\n# ou\npnpm add @callsync/webrtc-sip-sdk-react\n```\n\nAs peer dependencies do React são **opcionais** — necessárias apenas se você usar o sub-caminho `/react`.\n\n```bash\nnpm install react react-dom   # React 18+\n```\n\n---\n\n## Início Rápido\n\n### React / Next.js (App Router)\n\n```tsx\n// lib/gateway.ts — crie UMA vez fora da árvore React\nimport { GatewayClient } from \"@callsync/webrtc-sip-sdk-react\";\n\nexport const gatewayClient = new GatewayClient({\n  serverUrl:\n    process.env.NEXT_PUBLIC_GATEWAY_URL ?? \"wss://gateway.example.com/ws\",\n  // Call-center: trata todo OFFER inbound automaticamente.\n  // Remova ou use false para exigir uma decisão manual por chamada.\n  autoAnswer: true,\n});\n```\n\n```tsx\n// app/page.tsx\n\"use client\";\nimport { useEffect, useCallback } from \"react\";\nimport { useGateway, useCallEvents } from \"@callsync/webrtc-sip-sdk-react/react\";\nimport { gatewayClient } from \"@/lib/gateway\";\n\nexport default function Page() {\n  const { connectionState, currentSessionId, login, call, hangup } =\n    useGateway(gatewayClient);\n\n  useEffect(() => {\n    gatewayClient.connect();\n    return () => {\n      gatewayClient.disconnect();\n    };\n  }, []);\n\n  useCallEvents(\n    gatewayClient,\n    useCallback((e) => {\n      if (e.type === \"call:incoming\") {\n        // Com autoAnswer: true, este evento é apenas uma notificação.\n        console.log(\"Chamada recebida:\", e.sessionId);\n      }\n      if (e.type === \"call:connected\") {\n        console.log(\"Mídia conectada:\", e.sessionId);\n      }\n      if (e.type === \"call:ended\") {\n        console.log(\"Chamada encerrada:\", e.reason);\n      }\n    }, [])\n  );\n\n  return (\n    <div>\n      <p>Status: {connectionState}</p>\n      <button onClick={() => login(\"1001\", \"senha123\")}>Login</button>\n      <button onClick={() => call(\"9999\")}>Ligar para 9999</button>\n      {currentSessionId && (\n        <button onClick={() => hangup(currentSessionId)}>Desligar</button>\n      )}\n    </div>\n  );\n}\n```\n\n## Ciclo de vida e atendimento automático\n\nConfigure `autoAnswer: true` por instância para que o SDK responda chamadas inbound depois de preparar o SDP local. O padrão é `false`, preservando `answer(sessionId)` e `reject(sessionId)` no modo manual. Em autoanswer, a aplicação recebe `call:incoming` como notificação, mas não deve chamar `answer()` ou `reject()` para arbitrar a chamada; políticas condicionais por fila ou CRM ficam na aplicação antes de criar a instância.\n\nUse eventos para efeitos pontuais e `callState` de `useGateway()` para renderização. Os marcos não são equivalentes: `call:created` significa que o Gateway criou a sessão outbound; `call:ringing` é SIP 180; `call:answered` significa que o SDP do SIP 200 chegou; `call:answer-sent` confirma a resposta inbound enviada; `call:connected` ocorre somente quando a `RTCPeerConnection` está conectada e é o marco operacional recomendado para CRM, timers e controles. `call:ended` é terminal e inclui rejeição local como `reason: 'rejected'`.\n\n```ts\nconst client = new GatewayClient({\n  serverUrl: \"wss://gateway.example/ws\",\n  autoAnswer: true,\n});\n\nuseCallEvents(client, (event) => {\n  if (event.type === \"call:connected\") startOperationalTimer(event.sessionId);\n  if (event.type === \"call:ended\") stopOperationalTimer(event.sessionId);\n});\n```\n\n> **Atenção — React StrictMode**: sempre crie o `GatewayClient` fora da árvore React — no nível de módulo ou via `useRef`. Nunca dentro de `useState` ou diretamente no corpo do componente. O StrictMode executa efeitos duas vezes e destruiria a conexão WebSocket na montagem.\n\n---\n\n### Vanilla JS (sem framework) — legado\n\n> **Legado/descontinuado:** `examples/vanilla` não é mais o exemplo de referência e não receberá novos comportamentos. Para integrações React, use `examples/nextjs`, o exemplo oficial desta package. O diretório Vanilla só será removido após a migração dos consumidores estar documentada.\n\n```js\nimport { GatewayClient } from \"@callsync/webrtc-sip-sdk-react\";\n\nconst client = new GatewayClient({ serverUrl: \"wss://gateway.example.com/ws\" });\n\nclient.on(\"event\", (e) => {\n  if (e.type === \"call:incoming\") {\n    console.log(\"Chamada recebida:\", e.sessionId);\n    client.answer(e.sessionId);\n  }\n});\n\nawait client.connect();\nawait client.login(\"1001\", \"senha123\");\nconst sessionId = await client.call(\"9999\");\n// ... mais tarde\nawait client.hangup(sessionId);\n```\n\n---\n\n## Referência da API\n\n### `new GatewayClient(config)`\n\n| Opção                  | Tipo               | Padrão         | Descrição                                                                           |\n| ---------------------- | ------------------ | -------------- | ----------------------------------------------------------------------------------- |\n| `serverUrl`            | `string`           | —              | URL do WebSocket, ex.: `wss://host/ws`                                              |\n| `pingInterval`         | `number`           | `20000`        | Intervalo do keep-alive em ms                                                       |\n| `reconnectDelay`       | `number`           | `3000`         | Atraso base de reconexão em ms (dobra a cada tentativa, máximo 30 s)                |\n| `maxReconnectAttempts` | `number`           | `5`            | Número máximo de tentativas de reconexão                                            |\n| `requestTimeout`       | `number`           | `15000`        | Prazo de `LOGIN`, `OFFER`, `BYE` e `LOGOUT`; expiração rejeita a operação           |\n| `iceGatheringTimeout`  | `number`           | `2000`         | Prazo do ICE gathering em offer/answer; preserva o SDP local disponível             |\n| `autoAnswer`           | `boolean`          | `false`        | Responde todo OFFER inbound automaticamente; `call:incoming` continua sendo emitido |\n| `rtcConfiguration`     | `RTCConfiguration` | STUN do Google | ICE local; não cria relay/rota até o Gateway                                        |\n\n#### Métodos\n\n| Método                         | Retorno                        | Descrição                                                                                |\n| ------------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------- |\n| `connect()`                    | `Promise<void>`                | Abre a conexão WebSocket                                                                 |\n| `disconnect()`                 | `Promise<void>`                | Fecha o WebSocket e libera todos os recursos                                             |\n| `login(userID, password)`      | `Promise<{ expires: number }>` | Autenticação SIP; `userID` exige ASCII imprimível sem acentos/espaços                    |\n| `logout()`                     | `Promise<void>`                | Desregistro SIP; pode rejeitar por timeout/interrupção                                   |\n| `call(destination)`            | `Promise<string>`              | Exige `LOGIN_OK`; resolve no `SESSION_CREATED`, antes de ringing/answer                  |\n| `answer(sessionId)`            | `Promise<void>`                | Atende uma chamada recebida; disponível somente com `autoAnswer: false`                  |\n| `reject(sessionId)`            | `Promise<void>`                | Rejeita uma chamada recebida com `REJECT {}`; disponível somente com `autoAnswer: false` |\n| `hangup(sessionId)`            | `Promise<void>`                | Encerra uma chamada ativa; resolve em `BYE_OK` ou rejeita por timeout/interrupção        |\n| `setMuted(muted)`              | `void`                         | Ativa/desativa o mudo do microfone local                                                 |\n| `getAudioElement()`            | `HTMLAudioElement \\| null`     | Elemento de áudio interno para o áudio remoto                                            |\n| `on('event', handler)`         | `void`                         | Assina o stream de `CallEvent`                                                           |\n| `off('event', handler)`        | `void`                         | Remove a assinatura do stream de `CallEvent`                                             |\n| `subscribeConnectionState(cb)` | `() => void`                   | Assinatura para `useSyncExternalStore`                                                   |\n| `getConnectionState()`         | `ConnectionState`              | Snapshot síncrono do estado de conexão                                                   |\n| `subscribeSession(cb)`         | `() => void`                   | Assinatura para `useSyncExternalStore`                                                   |\n| `getSessionId()`               | `string \\| null`               | Snapshot síncrono do ID de sessão                                                        |\n\n#### Propriedades (somente leitura)\n\n| Propriedade        | Tipo                  | Descrição                         |\n| ------------------ | --------------------- | --------------------------------- |\n| `connectionState`  | `ConnectionState`     | Estado atual da conexão WebSocket |\n| `currentSessionId` | `string \\| null`      | ID da sessão ativa, ou `null`     |\n| `localStream`      | `MediaStream \\| null` | Stream do microfone local         |\n| `remoteStream`     | `MediaStream \\| null` | Stream de áudio remoto            |\n\n---\n\n### `useGateway(client)`\n\nHook React que assina os dois slices de estado via `useSyncExternalStore`.\n\n```ts\nconst {\n  connectionState, // 'connecting' | 'connected' | 'disconnected' | 'reconnecting'\n  currentSessionId, // string | null\n  login,\n  logout,\n  call,\n  answer,\n  reject,\n  hangup,\n  muted,      // boolean — microfone mutado\n  setMuted,   // (muted: boolean) => void\n  toggleMute, // () => void\n} = useGateway(gatewayClient);\n```\n\nTodos os callbacks de ação são estáveis (`useCallback` com dep `[client]`) — seguros para passar como props sem causar re-renders extras.\n\n#### Mute do microfone\n\n```tsx\n<button onClick={toggleMute} disabled={!currentSessionId}>\n  {muted ? 'Desmutar' : 'Mutar'}\n</button>\n```\n\nO mute desabilita a track (`enabled = false`) em vez de pará-la, então o\nnavegador continua enviando RTP — silêncio, mas um fluxo contínuo. Isso não é\ndetalhe de implementação: o Gateway alimenta a perna SIP com o áudio do\nnavegador depois que a chamada sobe e **não** volta a injetar silêncio próprio\nse essa fonte secar. Parar a track derruba a ligação em poucos segundos, com um\n`BYE` da operadora e nenhum erro no log.\n\nPor isso, se você manipular a track diretamente, **nunca** use `track.stop()`,\n`replaceTrack(null)` ou `removeTrack()` para mutar.\n\nO estado **reinicia em `false` a cada nova chamada**, de propósito: um mute\nherdado da chamada anterior faz o atendente falar com um cliente que não ouve\nnada, sem sinal para nenhum dos dois.\n\n`toggleMute` lê o estado atual do cliente em vez de fechar sobre `muted`, então\numa referência guardada de um render antigo continua invertendo corretamente.\n\n---\n\n### `useCallEvents(client, handler)`\n\nAssina o stream de eventos durante o ciclo de vida do componente. Remove a assinatura automaticamente no desmonte.\n\n```ts\nuseCallEvents(\n  client,\n  useCallback((e) => {\n    // trata CallEvent\n  }, [])\n);\n```\n\nO `handler` deve ser estabilizado com `useCallback` para evitar reinscrições desnecessárias a cada render.\n\n---\n\n## Referência de Eventos\n\n| Tipo do evento      | Campos extras                                             | Descrição                                                                                      |\n| ------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `connection:change` | `state: ConnectionState`                                  | Estado da conexão WebSocket alterado.                                                          |\n| `login:success`     | `expires: number`                                         | Login SIP bem-sucedido.                                                                        |\n| `login:failed`      | `code: ErrorCode; message: string`                        | Falha no login SIP.                                                                            |\n| `call:created`      | `sessionId; direction: 'outbound'; destination`           | Gateway aceitou a oferta outbound e criou a sessão; ainda não é ringing, atendimento ou mídia. |\n| `call:incoming`     | `sessionId; sdp; from?; direction: 'inbound'; autoAnswer` | Oferta inbound pronta localmente. Em autoanswer é uma notificação, não uma decisão manual.     |\n| `call:ringing`      | `sessionId`                                               | SIP 180 para a chamada outbound.                                                               |\n| `call:answered`     | `sessionId; direction: 'outbound'`                        | SDP do SIP 200 chegou; a mídia ainda pode não estar conectada.                                 |\n| `call:answer-sent`  | `sessionId; direction: 'inbound'; autoAnswer`             | SDK enviou a resposta SDP inbound ao Gateway.                                                  |\n| `call:connected`    | `sessionId; direction`                                    | `RTCPeerConnection` conectada; marco operacional recomendado para áudio, CRM e timer.          |\n| `call:ended`        | `sessionId; reason; sipCode?; detail?`                    | Término único da chamada; falha SIP inclui código/detalhe quando disponíveis.                  |\n| `call:error`        | `sessionId; code; message`                                | Erro local ou de sinalização da chamada.                                                       |\n\n### `CallState`\n\n`getCallState()` e `subscribeCallState()` expõem o snapshot estável `{ phase, sessionId, direction, destination?, from?, autoAnswer }`. Em React, `useGateway(client).callState` é a fonte de render; eventos continuam próprios para efeitos pontuais.\n\n### `ConnectionState`\n\n```ts\ntype ConnectionState =\n  | \"connecting\"\n  | \"connected\"\n  | \"disconnected\"\n  | \"reconnecting\";\n```\n\n### `CallEndReason`\n\n```ts\ntype CallEndReason =\n  | \"normal\"\n  | \"cancelled\"\n  | \"rejected\"\n  | \"failed\"\n  | \"connection-lost\"\n  | \"media-failed\";\n```\n\n`reason` é obrigatório em `call:ended`. Quando a origem é uma falha SIP, `reason` é `\"failed\"` e o evento inclui `sipCode` e `detail` quando disponibilizados pelo Gateway. Quando ICE ou a conexão WebRTC falha, `reason` é `\"media-failed\"`; o SDK encerra localmente e tenta `BYE {}` apenas se o WebSocket estiver conectado.\n\n### `ErrorCode`\n\n```ts\ntype ErrorCode =\n  | \"AUTH_FAILED\" // Falha de autenticação ou throttle\n  | \"UNAUTHORIZED\" // OFFER sem LOGIN\n  | \"INTERNAL_ERROR\"; // Erro de protocolo/ANSWER\n// Ocupado e recusado chegam como BYE com sipCode.\n```\n\n---\n\n## Limites do browser e ICE\n\nO browser envia `Origin` no upgrade WebSocket. Se o Gateway configurar `server.http.allowed_origins`, inclua a origem da aplicação; caso contrário o upgrade recebe HTTP 403.\n\nConfigurar STUN/TURN apenas no cliente não cria relay no Gateway. Sem relay/rota direta, browser e Gateway precisam de conectividade IP direta (LAN, VPN ou IP público configurado).\n\n`call()` resolve quando o Gateway envia `SESSION_CREATED`: o `sessionId` já existe, mas a chamada ainda pode não estar tocando ou atendida. Acompanhe `call:ringing`, `call:answered` e `call:ended`. Queda do WebSocket e falha de mídia são terminais: o SDK não recupera a sessão anterior nem chama `restartIce()`, porque o Gateway não expõe renegociação SDP.\n\nAs operações remotas rejeitam por timeout (`requestTimeout`) ou interrupção de conexão; respostas normativas tardias não reabrem a operação. `logout()` antes de um LOGIN confirmado é local e não envia `LOGOUT`, pois o Gateway o trata como no-op silencioso.\n\nO `userID` de `login()` deve usar somente ASCII imprimível, sem espaços ou acentos (por exemplo, `usuario.teste`, não `usuário.teste`). Isso evita codificação percentual no URI SIP observada no SBC.\n\n---\n\n## Build\n\n```bash\nnpm install\nnpm run build      # gera dist/index.{js,mjs,d.ts} e dist/react.{js,mjs,d.ts}\nnpm test           # testes unitários com Vitest\nnpm run typecheck              # tsc --noEmit\nnpm run test:coverage:changed  # ≥90% de linhas e branches em src/ modificado desde HEAD\n```\n\n---\n\n## Estrutura do Projeto\n\n```\nsrc/\n├── index.ts                   # Barrel — exports principais\n├── protocol/\n│   ├── types.ts               # Enum MessageType, ErrorCode, SignalMessage\n│   └── payloads.ts            # Interfaces de payload por tipo de mensagem\n├── core/\n│   ├── GatewayClient.ts       # Classe principal (sem dependência de framework)\n│   ├── WebSocketTransport.ts  # Reconexão e keep-alive\n│   └── WebRTCSession.ts       # Ciclo de vida do RTCPeerConnection + preferPCMU\n├── react/\n│   ├── useGateway.ts          # Hook useSyncExternalStore\n│   ├── useCallEvents.ts       # Hook de assinatura de eventos\n│   └── index.ts               # Exports do sub-caminho /react\n└── types/\n    └── events.ts              # Union type CallEvent, ConnectionState\n\nexamples/\n├── nextjs/                    # Exemplo oficial: integração Next.js 14 App Router\n└── vanilla/                   # Legado/descontinuado; remover após migração documentada dos consumidores\n\ntest/\n├── protocol.test.ts           # Serialização de mensagens\n├── sdp.test.ts                # Manipulação de SDP com preferPCMU\n├── gateway-client.test.ts     # Máquina de estados do GatewayClient (WebSocket mockado)\n└── hooks.test.tsx             # useGateway + useCallEvents (cliente mockado)\n```\n\n---\n\n## Licença\n\nMIT\n","readmeFilename":"README.md"}