Events
Webphone SDK phát ra 12 sự kiện 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 tác vụ bất đồng bộ nặng (gọi API, ghi DB), nên await riêng để không chặn vòng lặp xử lý sự kiện 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 cơ chế tự động kết nối lại bắt đầu hoạt động |
| 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, sự kiện này được phát ra 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)
}
})Bắt tất cả lỗi phát sinh trong quá trình chạy. 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.
Khuyến nghị về trải nghiệm người dùng (UX)
Hiển thị banner discreet "Đang khôi phục kết nối…" khi attempt >= 2. Không nên làm người dùng lo lắng 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 phát hiện tình huống này, phát sự kiện tương ứng và ứng dụng tích hợp 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:
