Skip to content

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 ​

#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 cơ chế tự động kết nối lại bắt đầu hoạt động
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, 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 ​

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)
  }
})

Bắt tất cả lỗi phát sinh trong quá trình chạy. Class ZorioWebphoneError:

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

Mã 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.

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 ​

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 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 ​

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.