Webphone SDK
O softphone BCVOZ como uma janela flutuante em qualquer página web.
Uma linha de <script>, sem dependências, 30 kB.
versão 0.7.1 · apenas navegador
Instalação
Cole este trecho inline na página. Ele resolve a versão publicada e carrega o bundle dela:
<script>
fetch('https://data.jsdelivr.com/v1/packages/npm/@bcvoz/webphone/resolved')
.then((r) => r.json())
.then(({ version }) => {
const s = document.createElement('script')
s.src = `https://cdn.jsdelivr.net/npm/@bcvoz/webphone@${version}/dist/bcvoz.umd.js`
s.onload = () => BCVoz.init({ token: TOKEN_DO_USUARIO, mode: 'srcdoc' })
s.onerror = () => console.error('BCVoz: falha ao carregar do CDN')
document.head.appendChild(s)
})
</script>
Não use a URL sem versão.
/npm/@bcvoz/webphone parece mais simples, mas o CDN a
entrega com max-age=604800 — sete dias de cache no
navegador do usuário. Uma correção publicada não alcança quem já
carregou, e purgar o CDN não ajuda: o cache está na máquina dele.
Resolver a versão primeiro custa uma requisição de ~1 kB (com 5 min de
cache) e faz o bundle vir de uma URL immutable — atualização
rápida e cache perfeito ao mesmo tempo.
Para travar numa versão — integração de terceiros, política de build ou SRI:
<script src="https://cdn.jsdelivr.net/npm/@bcvoz/webphone@0.7.1/dist/bcvoz.umd.js"></script>
Por npm:
npm install @bcvoz/webphone
import BCVoz from '@bcvoz/webphone' // default export
O build UMD expõe window.BCVoz. Não use BCVoz.default.
init(options)
Chame uma vez, com o <body> já existindo. É idempotente:
chamar de novo devolve a mesma instância e ignora as novas opções.
Para trocar de configuração, use destroy() antes.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
token | string | — | Sessão do usuário, emitida pelo seu backend |
mode | 'hosted' | 'srcdoc' | 'hosted' | Como o webphone é carregado |
hostBase | string | CDN da versão | Só em srcdoc. Deve terminar em / |
hostUrl | string | domínio oficial | Só em hosted |
frame | 'bar' | 'none' | 'bar' | 'none' preserva 100% da UI do webphone |
position | 'bottom-right' | … | 'bottom-right' | Canto em que a janela nasce |
open | boolean | false | false começa recolhido na aba |
launcher | boolean | true | Exibir a aba lateral |
launcherSide | 'right' | 'left' | 'right' | Borda em que a aba cola |
launcherIcon | string | 'phone-waves' | waveform, headset, chat-phone |
dockTop | 'max' | 'top-half' | 'max' | O que arrastar até o topo faz |
title | string | 'BCVOZ' | Só com frame: 'bar' |
O token nunca é literal no HTML.
Ele identifica um usuário. Gere no seu backend e injete no template, como faria com qualquer credencial de sessão.
Modos de carga
O webphone roda dentro de um iframe. O que muda entre os modos é de onde esse iframe vem — e a consequência é bem concreta.
srcdoc | hosted | |
|---|---|---|
| Origem do iframe | a do seu site | domínio do BCVoz |
| De onde vêm os arquivos | CDN, travados na versão | do host |
| Iframe de terceiro | não | sim — sujeito a bloqueadores |
| Login entre domínios | storage próprio | particionado por site |
| CORS dos backends | sua origem precisa estar liberada | origem fixa, nada a fazer |
Como escolher.
Use 'srcdoc' se a origem do seu site já estiver na allowlist de
CORS do BCVoz. Se não estiver, o webphone aparece na tela e não
registra — o navegador descarta as respostas. Nesse caso use
'hosted' até a liberação.
Telefonia
Todos devolvem Promise. async
| Método | Retorno | Notas |
|---|---|---|
call(number, meta?) | {ok, phone} | Número em formato livre |
answer() | {ok} | Atende a chamada entrante |
hangup() | {ok} | Encerra a chamada atual |
mute() | {ok} | Alterna — não aceita argumento |
hold() | {ok} | Alterna — não aceita argumento |
sendDTMF(tone) | {ok} | Um caractere: 0-9, *, # |
transfer(to) | {ok} | Ramal ou número de destino |
getStatus() | PhoneStatus | Estado atual da linha |
setAuth(token) | {ok} | Troca a sessão sem recarregar |
logout() | {ok} | Encerra a sessão |
call(number, meta?)
O número aceita formato livre — 11987654321,
(11) 98765-4321, com ou sem DDI. A normalização é a mesma da
extensão de navegador.
await BCVoz.call('11987654321', {
name: 'Ana Ribeiro', // aparece no card da chamada
crm: 'Acme Ltda', // rótulo da origem
photo: 'https://…/foto.jpg', // avatar
gateway: 'meu-sistema', // identifica a origem nos relatórios
dealId: '4821',
})
Trate a rejeição.
call() rejeita com número inválido ou webphone ainda
carregando. Sem .catch(), o clique do usuário falha em
silêncio e ninguém descobre por quê.
PhoneStatus devolve { ready, inCall, phase, number, incoming, muted, held }.
Janela
| Método | Descrição |
|---|---|
show() / hide() / toggle() | Exibe, recolhe, alterna |
reveal() | Abre; se já aberta, traz para a vista e destaca |
minimize(force?) | Colapsa para a barra de título |
move(x, y) | Posiciona em coordenadas da viewport |
resize(w, h) | Redimensiona, respeitando os limites |
dock(zone) | Encaixa numa região — ver abaixo |
destroy() | Remove o widget e desfaz os listeners |
Zonas de dock(): 'left', 'right',
'left-half', 'right-half', 'top-half',
'bottom-half', 'top', 'bottom',
'max', 'float'.
O usuário também encaixa arrastando: as laterais dão altura cheia, a borda de baixo dá a metade inferior, o topo maximiza e os cantos dão meia tela. Uma prévia mostra o resultado antes de soltar. Redimensionando, chegar perto de uma borda completa até ela.
Aba lateral
Com a janela recolhida, o webphone fica acessível por uma aba colada na lateral — arrastável na vertical, com a posição preservada entre sessões. O ícone reflete o estado da linha e pulsa em vermelho numa chamada entrante.
| Método | Descrição |
|---|---|
setLauncherSide('right' | 'left') | Troca a borda em runtime |
setLauncherIcon(nome) | phone-waves, waveform, headset, chat-phone |
Eventos
const off = BCVoz.on('call:incoming', ({ number }) => {
// abrir a ficha do cliente, por exemplo
})
off() // remove o listener — útil ao desmontar um componente
| Evento | Payload | Quando |
|---|---|---|
ready | { version } | A ponte conectou; aceita comandos |
state | { state } | connecting, ready, error |
call:dialing | CallInfo | Chamada de saída iniciada |
call:incoming | CallInfo | Entrante — a janela se abre sozinha |
call:answered | CallInfo | Atendida |
call:ended | CallInfo | Encerrada |
resize | { width, height, dock } | Redimensionada ou encaixada |
open / close | — | Janela exibida ou recolhida |
reveal | — | reveal() foi chamado |
error | { message } | Falha na ponte ou no carregamento |
CallInfo: { id, number, direction }, com direction em 'inbound' ou 'outbound'.
Não existe call:failed.
Uma chamada que não completa chega como call:ended — o estado
interno do webphone não distingue desligar de falhar.
Para depurar, on('*', ({ event, payload }) => …) recebe tudo. Evite deixar em produção.
Propriedades
| Propriedade | Tipo | Descrição |
|---|---|---|
version | string | Versão do SDK carregado |
isOpen | boolean | A janela está visível |
geometry | object | null | {x, y, width, height, dock}; null antes do init |
Receitas
Click-to-call numa lista
document.querySelectorAll('[data-fone]').forEach((el) => {
el.addEventListener('click', () => {
BCVoz.call(el.dataset.fone, {
name: el.dataset.nome,
crm: el.dataset.empresa,
gateway: 'meu-sistema',
}).catch((e) => alert('Não foi possível ligar: ' + e.message))
})
})
Registrar o atendimento ao fim da chamada
BCVoz.on('call:ended', ({ number, direction }) => {
fetch('/api/atendimentos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ number, direction }),
})
})
Carregar por JavaScript
Quando não há como escrever a <script> no HTML — SPA, Tag Manager:
function carregar() {
if (window.BCVoz) return Promise.resolve(window.BCVoz)
if (window.__bpCarregando) return window.__bpCarregando
window.__bpCarregando = new Promise((ok, erro) => {
const s = document.createElement('script')
s.src = 'https://cdn.jsdelivr.net/npm/@bcvoz/webphone@0.7'
s.async = true
s.onload = () => window.BCVoz ? ok(window.BCVoz)
: erro(new Error('carregou mas a API não apareceu'))
s.onerror = () => erro(new Error('falhou (rede, bloqueador ou CSP)'))
document.head.appendChild(s)
})
return window.__bpCarregando
}
Requisitos da sua página
- HTTPS ou
localhost.getUserMedianão existe fora de contexto seguro — sem isso o webphone carrega e nunca captura áudio. - Se houver CSP, liberar
cdn.jsdelivr.netemscript-src,style-srcefont-src. A maioria dos sites não precisa mexer. - Navegadores com WebRTC e Shadow DOM: Chrome/Edge 88+, Firefox 90+, Safari 14+.
O microfone é concedido à sua origem.
No modo srcdoc o usuário autoriza o microfone para o
seu domínio, não para o BCVoz — uma vez por site. A favor:
a permissão fica no domínio que ele já conhece.
Diagnóstico
Quando algo não funciona, siga nesta ordem — cada passo elimina uma camada:
| Verificação | Se falhar |
|---|---|
window.BCVoz existe? | O script não carregou: rede, CSP ou bloqueador |
O evento ready disparou? | A ponte não conectou com o webphone |
state veio 'error'? | Registro SIP: token inválido, ou origem fora do CORS |
| A UI aparece sem imagens? | Os assets não resolveram — confira o hostBase |
Os erros do webphone ficam noutro console.
Ele roda dentro de um iframe. Nas devtools, troque o contexto do console para o do frame — o console da página principal não mostra nada do que acontece lá dentro.