Events
Webphone SDK emit 12 events theo lifecycle SIP registration + cuộc gọi + lỗi hệ thống. Anh chị subscribe qua phone.on(eventName, handler) và unsubscribe qua phone.off(eventName, handler?).
Phạm vi handler
Handler được gọi đồng bộ khi event xảy ra. Nếu trong handler cần async work nặng (gọi API, ghi DB), nên await riêng để không block event loop của SDK.
Bảng tổng hợp
| # | Event | Payload | Khi nào emit |
|---|---|---|---|
| 1 | registered | {} | SIP register thành công lần đầu hoặc sau reconnect |
| 2 | unregistered | { reason } | SIP unregister (manual / remote / transport_disconnect) |
| 3 | connected | {} | WebSocket WSS lên — chưa register |
| 4 | disconnected | { reason? } | WebSocket drop, trước khi auto-reconnect kick in |
| 5 | incoming_call | { call, contact } | INVITE tới |
| 6 | call_ringing | { call } | Outbound nhận 180/183, hoặc inbound đang đổ chuông local |
| 7 | call_answered | { call } | Cuộc gọi vào state answered |
| 8 | call_hangup | { call, reason, billsec } | Bất kỳ bên nào hangup, hoặc call fail |
| 9 | dtmf | { call, digit } | Nhận DTMF từ remote (RFC 2833) |
| 10 | error | ZorioWebphoneError | Mọi lỗi runtime — bắt buộc handle |
| 11 | auto_reconnect | { attempt, delay_ms } | Mỗi lần supervisor thử reconnect lại |
| 12 | audio_autoplay_blocked | { call } | Browser block autoplay khi cuộc gọi tới |
1. registered
phone.on('registered', () => {
console.log('SIP sẵn sàng, extension:', phone.extension)
showBanner('Đã online', 'success')
})Emit cả lần register đầu (sau connect()) và mọi lần re-register thành công sau reconnect. Dùng để hiển thị trạng thái online lên UI.
2. unregistered
phone.on('unregistered', ({ reason }) => {
// reason: 'manual' | 'remote' | 'transport_disconnect'
if (reason === 'transport_disconnect') {
showBanner('Mất kết nối, đang khôi phục…', 'warn')
}
})reason | Ý nghĩa |
|---|---|
manual | User gọi phone.unregister() chủ động |
remote | Zorio PBX gửi 401/403, có thể do credential thay đổi |
transport_disconnect | WebSocket WSS drop |
3. connected
phone.on('connected', () => {
console.log('WSS đã connect — chuẩn bị register')
})WebSocket layer lên nhưng SIP REGISTER chưa hoàn tất. Hữu ích để hiển thị progress 2-step "Connecting…" → "Registering…" → "Ready".
4. disconnected
phone.on('disconnected', ({ reason }) => {
console.warn('WSS drop:', reason)
})WebSocket bị đóng. SDK supervisor sẽ tự retry — emit kèm auto_reconnect sau đó.
5. incoming_call
phone.on('incoming_call', ({ call, contact }) => {
myUI.showIncomingPopup({
name: contact?.full_name || call.number,
onAccept: () => call.answer(),
onReject: () => call.hangup(),
})
})Payload đầy đủ:
{
call: ZorioCall, // direction='incoming', state='ringing'
contact: {
id: number
full_name: string
phones?: string[]
customer_id?: number
lead_id?: number
} | null
}contact lookup từ backend Zorio (/api/contacts/lookup?number=...). Nếu chưa có khách trong CSKH → contact = null, hiển thị raw call.number.
6. call_ringing
phone.on('call_ringing', ({ call }) => {
if (call.direction === 'outgoing') {
showStatus(`Đang gọi ${call.number}…`)
}
})Early media v7
Với outbound, event này emit khi SDK nhận SIP 180 Ringing hoặc 183 Session Progress. Nhờ earlyMedia: true, bạn nghe ringback / IVR carrier announcement ngay từ lúc này — không cần đợi answer.
7. call_answered
phone.on('call_answered', ({ call }) => {
console.log('Kết nối thành công, UUID:', call.uuid)
startTimer(call.uuid)
})Cuộc gọi đã được phía remote bốc máy (200 OK + ACK). Bắt đầu billsec tính từ thời điểm này.
8. call_hangup
phone.on('call_hangup', ({ call, reason, billsec }) => {
stopTimer(call.uuid)
if (reason === 'no_answer') {
showToast('Không bắt máy')
} else if (reason === 'busy') {
showToast('Máy bận')
}
saveBillsec(call.uuid, billsec)
})reason thường gặp | Nguồn Zorio PBX HangupCause |
|---|---|
normal_clearing | NORMAL_CLEARING (billsec > 0) |
no_answer | NORMAL_CLEARING + billsec = 0 hoặc NO_ANSWER |
busy | USER_BUSY |
unallocated_number | UNALLOCATED_NUMBER |
originator_cancel | ORIGINATOR_CANCEL |
call_rejected | CALL_REJECTED |
Chi tiết mapping xem Hangup cause codes.
9. dtmf
phone.on('dtmf', ({ call, digit }) => {
console.log('Remote bấm:', digit)
})Nhận DTMF gửi đến từ remote (vd khách hàng bấm phím trong IVR đối ứng). digit là single character 0-9 * # A-D.
10. error
phone.on('error', (err) => {
switch (err.code) {
case 'bootstrap_failed':
alert('Không kết nối được tới Zorio — kiểm tra token / network')
break
case 'audio_autoplay_blocked':
showBanner('Vui lòng click vào trang để bật âm thanh')
break
case 'reconnect_failed':
showBanner('Mất kết nối quá lâu — vui lòng reload trang')
break
default:
console.error('[zorio-webphone]', err.code, err.message, err.details)
}
})Catch-all cho mọi runtime error. Class ZorioWebphoneError:
class ZorioWebphoneError extends Error {
code: string // machine-readable
details?: Record<string, unknown>
}Mã code đầy đủ xem API methods → Error handling.
11. auto_reconnect
phone.on('auto_reconnect', ({ attempt, delay_ms }) => {
console.log(`Reconnect lần ${attempt}, đợi ${delay_ms}ms`)
if (attempt > 5) showBanner('Đường truyền không ổn định', 'warn')
})Emit mỗi lần supervisor v7 lên lịch reconnect. Exponential backoff: 1s → 2s → 4s → 8s → 16s → 30s (cap) + jitter 0-500ms. Reset khi register lại OK.
UX khuyến nghị
Hiển thị banner discreet "Đang khôi phục kết nối…" khi attempt >= 2. Đừng làm hoảng user ngay attempt đầu vì network blip 1-2s là bình thường.
12. audio_autoplay_blocked
phone.on('audio_autoplay_blocked', ({ call }) => {
// Safari/iOS chặn autoplay khi tab chưa có user gesture
myUI.showUnlockBanner({
message: 'Bấm vào đây để bật âm thanh cuộc gọi',
onClick: async () => {
await phone.unlockAudio() // tạo gesture context cho audio element
},
})
})Safari / iOS quirk
Browser block audio.play() nếu trang chưa có user interaction (click/tap). SDK detect → emit event này → host app PHẢI hiển thị nút yêu cầu user bấm. Nếu bỏ qua, cuộc gọi vẫn kết nối nhưng user không nghe được audio.
Pattern thường dùng
Wire 3 events tối thiểu cho MVP
const phone = await ZorioWebphone.connect({ apiBase, token, mount: '#zorio-phone' })
phone.on('incoming_call', ({ call, contact }) => myUI.showIncoming(call, contact))
phone.on('call_answered', ({ call }) => myUI.openInCall(call))
phone.on('call_hangup', ({ call, billsec }) => myUI.closeInCall(call, billsec))
phone.on('error', (err) => console.error(err))Theo dõi state online cho dashboard
function updateStatusBadge() {
badge.textContent = phone.status === 'registered' ? '🟢 Online' : '🔴 Offline'
}
phone.on('registered', updateStatusBadge)
phone.on('unregistered', updateStatusBadge)
phone.on('connected', updateStatusBadge)
phone.on('disconnected', updateStatusBadge)
phone.on('auto_reconnect', updateStatusBadge)Tham khảo thêm:
