Skip to content

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

#EventPayloadKhi nào emit
1registered{}SIP register thành công lần đầu hoặc sau reconnect
2unregistered{ reason }SIP unregister (manual / remote / transport_disconnect)
3connected{}WebSocket WSS lên — chưa register
4disconnected{ reason? }WebSocket drop, trước khi auto-reconnect kick in
5incoming_call{ call, contact }INVITE tới
6call_ringing{ call }Outbound nhận 180/183, hoặc inbound đang đổ chuông local
7call_answered{ call }Cuộc gọi vào state answered
8call_hangup{ call, reason, billsec }Bất kỳ bên nào hangup, hoặc call fail
9dtmf{ call, digit }Nhận DTMF từ remote (RFC 2833)
10errorZorioWebphoneErrorMọi lỗi runtime — bắt buộc handle
11auto_reconnect{ attempt, delay_ms }Mỗi lần supervisor thử reconnect lại
12audio_autoplay_blocked{ call }Browser block autoplay khi cuộc gọi tới

1. registered

ts
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

ts
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
manualUser gọi phone.unregister() chủ động
remoteZorio PBX gửi 401/403, có thể do credential thay đổi
transport_disconnectWebSocket WSS drop

3. connected

ts
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

ts
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

ts
phone.on('incoming_call', ({ call, contact }) => {
  myUI.showIncomingPopup({
    name: contact?.full_name || call.number,
    onAccept: () => call.answer(),
    onReject: () => call.hangup(),
  })
})

Payload đầy đủ:

ts
{
  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

ts
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

ts
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

ts
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ặpNguồn Zorio PBX HangupCause
normal_clearingNORMAL_CLEARING (billsec > 0)
no_answerNORMAL_CLEARING + billsec = 0 hoặc NO_ANSWER
busyUSER_BUSY
unallocated_numberUNALLOCATED_NUMBER
originator_cancelORIGINATOR_CANCEL
call_rejectedCALL_REJECTED

Chi tiết mapping xem Hangup cause codes.

9. dtmf

ts
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

ts
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:

ts
class ZorioWebphoneError extends Error {
  code:     string                            // machine-readable
  details?: Record<string, unknown>
}

code đầy đủ xem API methods → Error handling.

11. auto_reconnect

ts
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

ts
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

ts
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

ts
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:

Cấp phép theo điều khoản sử dụng của Zorio.