SDK React
Pacote NPM @paulorbj/wpp-gateway-react (privado/interno) com Provider, hooks e componentes prontos para usar no frontend.
Importante: o SDK React não fala direto com o wpp-gateway. A API key fica no servidor do cliente — o SDK chama um proxy do seu backend que reenvia para
wpp.ogmma.com.br. Isso evita exporak_live_...no browser.
Arquitetura
┌──────────────┐ HTTP ┌──────────────────┐ HTTP ┌─────────────┐
│ React App │─────────►│ Seu Backend │───────►│ wpp-gateway │
│ (SDK React) │ /api/wpp │ (proxy + auth) │ X-Key │ wpp.ogmma │
└──────────────┘ └──────────────────┘ └─────────────┘
Você implementa um proxy em /api/wpp/* no seu backend (Next API route, Express handler, etc) que:
- Autentica seu usuário (sessão, cookie, etc).
- Mapeia para o
accountIdcorreto. - Adiciona
X-API-Key: ak_live_...(vindo do seu env). - Faz fetch ao
https://wpp.ogmma.com.br/v1/<path>e devolve a resposta.
Instalação
npm install @paulorbj/wpp-gateway-react
Pacote zero-dep além de React (≥18). Componentes usam CSS inline mínimo.
Provider
Wrap a árvore com <WppGatewayProvider> no topo.
import { WppGatewayProvider } from '@paulorbj/wpp-gateway-react';
export default function RootLayout({ children }) {
return (
<WppGatewayProvider baseUrl="/api/wpp">
{children}
</WppGatewayProvider>
);
}
Props
| Prop | Tipo | Obrigatório | Descrição |
|---|---|---|---|
baseUrl | string | sim | Prefixo das URLs (relativo ou absoluto). Ex.: /api/wpp |
headers | object | não | Headers extras (Authorization, X-Clinic-Id, etc) |
fetchFn | function | não | Override de fetch (edge runtime, mock, etc) |
endpoints | object | não | Override de rotas individuais |
Default endpoints
{
listPhones: 'GET /phones',
createPhone: 'POST /phones',
getPhone: 'GET /phones/:id',
deletePhone: 'DELETE /phones/:id',
qrcode: 'GET /phones/:id/qrcode',
requestPairingCode: 'POST /phones/:id/pairing-code',
pairingCode: 'GET /phones/:id/pairing-code',
reconnect: 'POST /phones/:id/reconnect',
embeddedSignupConfig: 'GET /waba/embedded-signup/config',
finishEmbeddedSignup: 'POST /waba/embedded-signup',
}
Para sobrescrever (ex.: seu proxy usa /qr-code ao invés de /qrcode):
<WppGatewayProvider
baseUrl="/api/wpp"
endpoints={{ qrcode: 'GET /phones/:id/qr-code' }}
>
...
</WppGatewayProvider>
Hooks
useGatewayClient()
Retorna o GatewayClient para chamadas imperativas.
const client = useGatewayClient();
await client.deletePhone('5f7b2e1c-...');
usePhones()
Lista phones com refresh manual.
import { usePhones } from '@paulorbj/wpp-gateway-react';
function PhonesPage() {
const { phones, loading, error, refresh } = usePhones();
if (loading) return <Spinner />;
if (error) return <Alert>{error}</Alert>;
return (
<>
{phones.map(p => <div key={p.id}>{p.label} — {p.status}</div>)}
<button onClick={refresh}>Atualizar</button>
</>
);
}
Opções: { autoload?: boolean } (default true).
useQrCode(phoneId, opts?)
Polling do QR code Baileys. Para automaticamente quando stop=true (geralmente após CONNECTED).
import { useQrCode, useChannelStatus } from '@paulorbj/wpp-gateway-react';
function QrScreen({ phoneId }: { phoneId: string }) {
const { phone } = useChannelStatus(phoneId);
const connected = phone?.status === 'CONNECTED';
const { dataUrl, error } = useQrCode(phoneId, { stop: connected, intervalMs: 2000 });
if (connected) return <p>Conectado!</p>;
if (error) return <p>Erro: {error}</p>;
if (!dataUrl) return <Spinner />;
return <img src={dataUrl} alt="QR Code" width={300} />;
}
Opções: { intervalMs?: number; stop?: boolean }. Default intervalMs=2000. HTTP 425 (QR_NOT_READY) é tratado como "ainda esperando" — não vira erro.
useChannelStatus(phoneId, opts?)
Polling do status do phone. Útil para detectar transições (PENDING_QR → CONNECTED).
const { phone, error } = useChannelStatus(phoneId, { intervalMs: 3000 });
Componentes
<ConnectChannelWizard>
Wizard completo de conexão, com seletor de modo (Baileys QR, Baileys pairing, WABA embedded signup, WABA manual).
import { ConnectChannelWizard } from '@paulorbj/wpp-gateway-react';
<ConnectChannelWizard
modes={['baileys-qr', 'baileys-pairing', 'waba-embedded']}
onConnected={(phone) => router.push(`/phones/${phone.id}`)}
onCancel={() => router.back()}
/>
Props
| Prop | Tipo | Descrição |
|---|---|---|
modes | ConnectMode[] | Modos habilitados. Default: todos |
defaultMode | ConnectMode | Modo inicial |
onConnected | (phone: Phone) => void | Callback após CONNECTED |
onCancel | () => void | Botão "Voltar" |
ConnectMode = 'baileys-qr' | 'baileys-pairing' | 'waba-embedded' | 'waba-manual'
<BaileysQRConnect>
Wizard isolado de QR.
<BaileysQRConnect
label="Atendimento Vendas"
onConnected={(phone) => console.log('conectou:', phone)}
/>
Props
| Prop | Tipo | Default |
|---|---|---|
label | string | "WhatsApp" |
webhook | WebhookConfig | undefined (sem webhook) |
intervalMs | number | 2000 |
onConnected | (phone: Phone) => void | — |
onCancel | () => void | — |
Internamente faz: POST /phones (type=BAILEYS) → useQrCode polling → detecta CONNECTED via useChannelStatus.
<BaileysPairingConnect>
Fluxo alternativo via pairing code.
<BaileysPairingConnect
label="Atendimento Vendas"
onConnected={(phone) => router.push(`/phones/${phone.id}`)}
/>
UI:
- Input para o usuário digitar o número (E.164).
POST /phones+POST /phones/:id/pairing-code.- Polling em
GET /pairing-codeaté receber. - Exibe code formatado (ex:
ABCD-1234). - Polling do status até
CONNECTED.
<WabaEmbeddedSignup>
Botão "Conectar WhatsApp Business" que abre o SDK JS da Meta.
<WabaEmbeddedSignup
label="Comercial Acme"
onConnected={(phone) => console.log('Phone WABA criado:', phone)}
onError={(err) => alert(err.message)}
/>
UI:
- Botão "Conectar WhatsApp Business".
- Carrega config via
GET /waba/embedded-signup/config. - Inicializa Facebook SDK (
window.FB.init). - Abre popup
FB.logincomconfig_idcorreto. - Recebe
code+phone_number_id+waba_idno callback. POST /waba/embedded-signuppara finalizar.
<WabaManualConnect>
Form manual para inserir credenciais Meta (alternativa ao embedded para clientes que já têm WABA).
<WabaManualConnect onConnected={(phone) => ...} />
Campos: label, phoneNumberId, businessAccountId, accessToken, verifyToken.
<PhoneList>
Lista com status badges + ações (ver QR, reconectar, deletar).
<PhoneList
onSelect={(phone) => router.push(`/phones/${phone.id}`)}
onDelete={(phone) => alert(`Deletado: ${phone.label}`)}
/>
Primitives
Componentes UI base sem styling pesado (use seu design system se quiser substituir):
import {
Button, Input, Select, Card, Stack, Alert, Spinner, StatusBadge
} from '@paulorbj/wpp-gateway-react';
<StatusBadge status="CONNECTED">— badge colorido por phone status<Alert variant="error" | "warning" | "info">— caixa de aviso<Spinner>— loader CSS-only<Card>,<Stack>— containers<Button>,<Input>,<Select>— form básicos
Tipos exportados
import type {
Phone, PhoneStatus, ChannelType,
CreatePhoneInput, CreateBaileysPhoneInput, CreateWabaManualPhoneInput,
WebhookConfig, WabaEmbeddedSignupConfig, QrCode,
GatewayClientError, // class de erro normalizada
} from '@paulorbj/wpp-gateway-react';
GatewayClientError traz .code (mapeado do AppError.code do servidor), .status (HTTP), .details.
Server proxy de referência (Next.js)
// app/api/wpp/[...path]/route.ts
const GATEWAY_BASE = 'https://wpp.ogmma.com.br';
const API_KEY = process.env.WPP_API_KEY!;
async function handler(req: Request, ctx: { params: { path: string[] } }) {
const path = ctx.params.path.join('/');
const url = `${GATEWAY_BASE}/v1/${path}${new URL(req.url).search}`;
// ... aqui você AUTENTICA o usuário e mapeia para a accountId correta.
// Em um SaaS multi-tenant, cada tenant tem sua própria API key.
const upstream = await fetch(url, {
method: req.method,
headers: {
'X-API-Key': API_KEY,
'Content-Type': req.headers.get('content-type') ?? 'application/json',
},
body: ['GET', 'HEAD'].includes(req.method) ? undefined : await req.text(),
});
return new Response(await upstream.text(), {
status: upstream.status,
headers: { 'Content-Type': upstream.headers.get('content-type') ?? 'application/json' },
});
}
export { handler as GET, handler as POST, handler as PATCH, handler as DELETE };
SSE no cliente (sem SDK)
Para receber eventos em tempo real no browser, use diretamente o EventSource no proxy:
// app/api/wpp/events/route.ts
export async function GET() {
const upstream = await fetch(`${GATEWAY_BASE}/v1/events/stream?api_key=${API_KEY}`);
return new Response(upstream.body, {
headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
});
}
No componente React:
useEffect(() => {
const es = new EventSource('/api/wpp/events');
es.addEventListener('message:received', (e) => {
const evt = JSON.parse(e.data);
// ... atualizar UI
});
return () => es.close();
}, []);