@bravophone/webphone 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -14
- package/dist/bravophone.mjs +334 -295
- package/dist/bravophone.umd.js +38 -19
- package/host/js/bravophone-route-selector.js +1 -1
- package/host/popup.js +3 -3
- package/host/shim/guest-bridge.js +23 -7
- package/package.json +5 -3
- package/types/index.d.ts +23 -2
package/README.md
CHANGED
|
@@ -1,21 +1,48 @@
|
|
|
1
1
|
# @bravophone/webphone
|
|
2
2
|
|
|
3
|
-
Webphone BRAVOPHONE embutível em qualquer página web
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Webphone BRAVOPHONE embutível em qualquer página web: o softphone aparece como
|
|
4
|
+
uma janela flutuante arrastável, com as mesmas funcionalidades da extensão de
|
|
5
|
+
navegador.
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/@bravophone/webphone@0.1"></script>
|
|
9
8
|
<script>
|
|
10
|
-
|
|
9
|
+
// Resolve a versão publicada e carrega o bundle dela.
|
|
10
|
+
// Duas etapas de propósito: a consulta tem 5 min de cache, o bundle é
|
|
11
|
+
// immutable. Assim as correções chegam em minutos, sem revalidar 30 kB a cada
|
|
12
|
+
// visita — e sem os 7 dias de cache que a URL sem versão carrega.
|
|
13
|
+
// cache:'no-store' na CONSULTA (~1 kB): sem ele, a resposta fica até 5 min
|
|
14
|
+
// no navegador e uma publicação recém-saída não aparece — foi o que exigiu
|
|
15
|
+
// Ctrl+Shift+R nos testes. O bundle continua vindo de cache immutable, então
|
|
16
|
+
// o custo é uma requisição pequena por carregamento, não 30 kB.
|
|
17
|
+
fetch('https://data.jsdelivr.com/v1/packages/npm/@bravophone/webphone/resolved',
|
|
18
|
+
{ cache: 'no-store' })
|
|
19
|
+
.then((r) => r.json())
|
|
20
|
+
.then(({ version }) => {
|
|
21
|
+
const s = document.createElement('script')
|
|
22
|
+
s.src = `https://cdn.jsdelivr.net/npm/@bravophone/webphone@${version}/dist/bravophone.umd.js`
|
|
23
|
+
s.onload = () => Bravophone.init({ session: SESSAO_DO_LOGIN })
|
|
24
|
+
s.onerror = () => console.error('Bravophone: falha ao carregar do CDN')
|
|
25
|
+
document.head.appendChild(s)
|
|
26
|
+
})
|
|
11
27
|
</script>
|
|
12
28
|
```
|
|
13
29
|
|
|
30
|
+
Cole **inline**, não como arquivo externo — um arquivo externo teria o mesmo
|
|
31
|
+
problema de cache que este trecho existe para evitar.
|
|
32
|
+
Detalhes em [Manter o cliente sempre atualizado](#manter-o-cliente-sempre-atualizado).
|
|
33
|
+
|
|
34
|
+
Para travar numa versão (integração de terceiros, ou build com SRI):
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script src="https://cdn.jsdelivr.net/npm/@bravophone/webphone@0.3.0/dist/bravophone.umd.js"></script>
|
|
38
|
+
<script>Bravophone.init({ token: TOKEN_DO_USUARIO })</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
14
41
|
```js
|
|
15
42
|
// ou via npm
|
|
16
43
|
import Bravophone from '@bravophone/webphone'
|
|
17
44
|
|
|
18
|
-
Bravophone.init({ token
|
|
45
|
+
Bravophone.init({ token })
|
|
19
46
|
Bravophone.on('call:incoming', ({ number }) => console.log('ligação de', number))
|
|
20
47
|
await Bravophone.call('11987654321')
|
|
21
48
|
```
|
|
@@ -55,9 +82,9 @@ todas — é o [`host/shim/chrome-shim.js`](host/shim/chrome-shim.js).
|
|
|
55
82
|
│ │ Shadow DOM · janela arrastável │ │
|
|
56
83
|
│ │ API pública · ponte postMessage │ │
|
|
57
84
|
│ │ │ │
|
|
58
|
-
│ │ ┌─ <iframe>
|
|
59
|
-
│ │ │ origem
|
|
60
|
-
│ │ │
|
|
85
|
+
│ │ ┌─ <iframe srcdoc> ───────────┐ │ │
|
|
86
|
+
│ │ │ origem: a do próprio site │ │ │
|
|
87
|
+
│ │ │ arquivos: CDN (jsDelivr) │ │ │
|
|
61
88
|
│ │ │ │ │ │
|
|
62
89
|
│ │ │ chrome-shim.js │ │ │
|
|
63
90
|
│ │ │ libwebphone.js (604 KB) │ │ │
|
|
@@ -78,7 +105,7 @@ Testei mentalmente as duas rotas; o iframe ganha em quatro frentes de uma vez:
|
|
|
78
105
|
1. **CSS.** O bundle traz Tailwind + `dark-theme.css` globais. Injetado na página do
|
|
79
106
|
cliente, ele quebraria o site do cliente — e o CSS do cliente quebraria o webphone.
|
|
80
107
|
2. **CORS, e este é o argumento decisivo.** Dentro do iframe, todo request para
|
|
81
|
-
`
|
|
108
|
+
`pabx.teambravotech.com` e `devices.wavoip.com` sai com
|
|
82
109
|
`Origin: https://webphone.bravophone.com` — **uma origem só, fixa**. Sem iframe,
|
|
83
110
|
cada cliente novo exigiria liberar mais uma origem no CORS de três backends. Com
|
|
84
111
|
iframe, a lista de origens do backend nunca cresce.
|
|
@@ -255,6 +282,7 @@ carrega (o mock em `?host=mock` continua funcionando).
|
|
|
255
282
|
| `npm start` | Sobe as duas origens locais (5173 site, 5174 host) |
|
|
256
283
|
| `npm test` | 100 asserções: shim, geometria da janela e aba de abertura |
|
|
257
284
|
| `npm run audit:theme` | Procura texto invisível no tema escuro |
|
|
285
|
+
| `npm run purge` | Limpa o cache do CDN nas URLs sem versão fixa (roda sozinho após o publish) |
|
|
258
286
|
|
|
259
287
|
Abra **http://localhost:5173/**.
|
|
260
288
|
|
|
@@ -308,7 +336,7 @@ sync, ou versione por query string); `index.html` sempre com `no-cache`.
|
|
|
308
336
|
|
|
309
337
|
### 2. Backends — CORS
|
|
310
338
|
|
|
311
|
-
Liberar **uma única origem** em `api.bravophone.com`, `
|
|
339
|
+
Liberar **uma única origem** em `api.bravophone.com`, `pabx.teambravotech.com` e
|
|
312
340
|
`devices.wavoip.com`:
|
|
313
341
|
|
|
314
342
|
```
|
|
@@ -330,6 +358,89 @@ um major não quebre a página deles.
|
|
|
330
358
|
|
|
331
359
|
---
|
|
332
360
|
|
|
361
|
+
## Manter o cliente sempre atualizado
|
|
362
|
+
|
|
363
|
+
A intuição diz para usar a URL sem versão. **É a pior escolha para isso**, e os
|
|
364
|
+
headers do CDN mostram por quê:
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
sem versão / @0.2 → max-age=604800 (7 dias no navegador do usuário)
|
|
368
|
+
@0.2.1 exata → immutable (eterno, mas fixo)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
A URL sem versão é entregue com sete dias de cache **na máquina de quem
|
|
372
|
+
acessa**. Publicar uma correção não alcança essa pessoa: `npm run purge` limpa
|
|
373
|
+
as bordas do CDN, não o cache que já está no navegador dela.
|
|
374
|
+
|
|
375
|
+
O caminho que resolve é [`examples/loader-latest.js`](examples/loader-latest.js),
|
|
376
|
+
que separa as duas coisas:
|
|
377
|
+
|
|
378
|
+
1. pergunta ao CDN qual é a versão atual — resposta com **5 min** de cache;
|
|
379
|
+
2. carrega o bundle daquela versão **exata** — URL imutável, cache eterno.
|
|
380
|
+
|
|
381
|
+
Uma publicação chega em até cinco minutos, e o arquivo pesado vem de um cache
|
|
382
|
+
que nunca precisa ser revalidado. O custo é uma requisição de ~1 kB antes do
|
|
383
|
+
bundle, quase sempre servida do cache.
|
|
384
|
+
|
|
385
|
+
```js
|
|
386
|
+
const { version } = await (await fetch(
|
|
387
|
+
'https://data.jsdelivr.com/v1/packages/npm/@bravophone/webphone/resolved'
|
|
388
|
+
)).json()
|
|
389
|
+
|
|
390
|
+
const s = document.createElement('script')
|
|
391
|
+
s.src = `https://cdn.jsdelivr.net/npm/@bravophone/webphone@${version}/dist/bravophone.umd.js`
|
|
392
|
+
document.head.appendChild(s)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Cole isso **inline** na página, não como arquivo externo — um arquivo externo
|
|
396
|
+
teria o mesmo problema de cache que estamos evitando.
|
|
397
|
+
|
|
398
|
+
### Se uma publicação não aparecer
|
|
399
|
+
|
|
400
|
+
Três caches diferentes, do mais provável ao menos:
|
|
401
|
+
|
|
402
|
+
| O que está velho | Como saber | Solução |
|
|
403
|
+
|---|---|---|
|
|
404
|
+
| **A consulta de versão** | `Bravophone.version` mostra a anterior | Já resolvido: o snippet usa `cache: 'no-store'` |
|
|
405
|
+
| **A página do integrador** | o próprio snippet mudou e não teve efeito | Não sirva o HTML com `max-age` longo |
|
|
406
|
+
| **O bundle** | — | Não acontece: a URL é versionada e `immutable` |
|
|
407
|
+
|
|
408
|
+
Durante o desenvolvimento, `Ctrl+Shift+R` limpa os três de uma vez — foi o que
|
|
409
|
+
funcionou nos primeiros testes. Em produção não há como pedir isso ao usuário,
|
|
410
|
+
e é por isso que o `no-store` está na consulta: sem ele, quem carregou a página
|
|
411
|
+
nos últimos cinco minutos continua na versão anterior.
|
|
412
|
+
|
|
413
|
+
Se ainda assim algo ficar para trás, `npm run purge` limpa as bordas do CDN —
|
|
414
|
+
mas lembre que ele não alcança o navegador de ninguém.
|
|
415
|
+
|
|
416
|
+
Os assets do webphone acompanham automaticamente: o `public_path` é gravado com
|
|
417
|
+
a versão do pacote, então carregar o SDK 0.2.1 carrega o host 0.2.1.
|
|
418
|
+
|
|
419
|
+
## A sessão que o webphone espera
|
|
420
|
+
|
|
421
|
+
`init()` recebe a resposta do `/api/voxfree/login` inteira:
|
|
422
|
+
|
|
423
|
+
```js
|
|
424
|
+
Bravophone.init({
|
|
425
|
+
session: {
|
|
426
|
+
vxToken: '…', // obrigatório
|
|
427
|
+
expiresIn: 3600, // segundos
|
|
428
|
+
sip: '…', // sem isto o webphone não registra
|
|
429
|
+
ramal: '…', // idem
|
|
430
|
+
tenant: '…',
|
|
431
|
+
clienteId: '…',
|
|
432
|
+
ramaisUrl: '…',
|
|
433
|
+
},
|
|
434
|
+
})
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
O SDK grava essas chaves onde o bundle as procura, **antes** dele avaliar — a
|
|
438
|
+
sessão já sobe autenticada, sem piscar a tela de login.
|
|
439
|
+
|
|
440
|
+
`token: '…'` continua aceito como atalho para `{ vxToken }`, mas **sozinho não
|
|
441
|
+
basta**: o webphone carrega, não registra, e o RouteSelector avisa "faça login
|
|
442
|
+
pelo webphone" — justamente o que a auto-autenticação existe para evitar.
|
|
443
|
+
|
|
333
444
|
## Documentação
|
|
334
445
|
|
|
335
446
|
| Documento | Para quem |
|
|
@@ -346,7 +457,7 @@ um major não quebre a página deles.
|
|
|
346
457
|
| Opção | Tipo | Padrão | Descrição |
|
|
347
458
|
|---|---|---|---|
|
|
348
459
|
| `token` | `string` | — | Token de sessão emitido pelo seu backend |
|
|
349
|
-
| `mode` | `'
|
|
460
|
+
| `mode` | `'srcdoc' \| 'hosted'` | `'srcdoc'` | Como o webphone é carregado — ver abaixo |
|
|
350
461
|
| `hostUrl` | `string` | `https://webphone.bravophone.com/` | Origem do webphone (só no modo `hosted`) |
|
|
351
462
|
| `position` | `string` | `'bottom-right'` | Canto inicial |
|
|
352
463
|
| `open` | `boolean` | `false` | Abrir já visível |
|
|
@@ -385,7 +496,7 @@ Bravophone.init({ token }) // hospedado (padrão)
|
|
|
385
496
|
Bravophone.init({ token, mode: 'srcdoc' }) // na origem do próprio site
|
|
386
497
|
```
|
|
387
498
|
|
|
388
|
-
| | `
|
|
499
|
+
| | `srcdoc` (padrão) | `hosted` |
|
|
389
500
|
|---|---|---|
|
|
390
501
|
| Onde o iframe roda | `webphone.bravophone.com` | origem do próprio site |
|
|
391
502
|
| De onde vêm os arquivos | do host | do CDN, travados nesta versão |
|
|
@@ -395,7 +506,7 @@ Bravophone.init({ token, mode: 'srcdoc' }) // na origem do próprio site
|
|
|
395
506
|
| Você precisa manter | o domínio do host | nada |
|
|
396
507
|
|
|
397
508
|
**Antes de oferecer o `srcdoc` a um cliente, a origem dele precisa estar na
|
|
398
|
-
allowlist de CORS de `api.bravophone.com`, `
|
|
509
|
+
allowlist de CORS de `api.bravophone.com`, `pabx.teambravotech.com` e
|
|
399
510
|
`devices.wavoip.com`.** Sem isso o webphone carrega, aparece na tela e não
|
|
400
511
|
registra — o navegador descarta as respostas. Isso é trabalho no **nosso**
|
|
401
512
|
backend: o integrador não tem como liberar CORS de um servidor que não é dele.
|