Skip to content

Nhúng qua iframe (SSO)

Phương án nhúng đơn giản nhất cho CRM legacy không sửa được source frontend hoặc khi đối tác chỉ muốn "treo" webphone Zorio mà không cần controlling nó. Iframe đã được build sẵn UI đầy đủ (dialer, popup incoming, in-call panel), chỉ cần <iframe src=...> là chạy.

1. Cú pháp tối giản

html
<iframe
  src="https://app.zorio.vn/webphone-embed?token=<jwt>"
  allow="microphone"
  style="border:0; width:380px; height:560px"
></iframe>

Tên miền tuỳ chỉnh

Thay app.zorio.vn bằng tên miền tuỳ chỉnh riêng nếu đối tác đã được cấp custom domain (vd pbx.crm-cua-ban.vn). Iframe luôn host trên đúng tên miền cấp cho khách hàng để bearer token / SIP credentials không bị cross-account.

2. SSO exchange — lấy <jwt>

JWT truyền qua query string là short-lived embed token (TTL mặc định 5 phút), được backend của đối tác đổi từ session user của họ qua API:

POST https://acme.zorio.example/api/webphone/embed-token
Authorization: Bearer <bearer-token-từ-/api/auth/login>
Content-Type: application/json

{
  "ttl_seconds": 300
}

Response:

json
{
  "embed_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_at": "2026-06-30T10:35:00+07:00"
}

Không expose bearer token chính

Tuyệt đối không truyền bearer token gốc vào src của iframe. Bearer dài hạn lộ qua URL sẽ ghi vào Referer header / log proxy. Luôn đổi sang embed token TTL ngắn rồi inject vào iframe.

Flow điển hình

text
[CRM của đối tác]               [Backend CRM]                [Zorio API]
       │                              │                            │
       │  cần render webphone         │                            │
       ├─────────────────────────────►│                            │
       │                              │  POST /webphone/embed-token│
       │                              ├───────────────────────────►│
       │                              │                            │
       │                              │◄───────────────────────────┤
       │                              │  embed_token (TTL 5 phút)  │
       │◄─────────────────────────────┤                            │
       │  embed_token                 │                            │
       │                              │                            │
       │  <iframe src="?token=...">                                 │
       │  → mount + SIP register      │                            │

Khi token gần hết hạn, frame parent có thể fetch token mới rồi gửi qua postMessage (xem postMessage API).

3. Cấu hình query string đầy đủ

html
<iframe
  src="https://app.zorio.vn/webphone-embed
        ?token=<jwt>
        &layout=docked
        &theme=auto
        &locale=vi
        &auto_register=1"
  allow="microphone"
  style="border:0; width:380px; height:560px"
></iframe>
Tham sốGiá trịMô tả
tokenJWT embedBắt buộc — short-lived embed token
layoutdocked | overlay | fullscreenBố cục UI. Default: docked
themelight | dark | autoDefault: auto
localevi | enDefault: vi
auto_register1 | 0Tự register SIP sau load. Default: 1

4. Quyền microphone trên iframe

Bắt buộc thêm allow="microphone" — nếu thiếu, browser sẽ block getUserMedia ngay cả khi user đã cấp quyền cho parent page:

html
<iframe allow="microphone" ... ></iframe>

<!-- Nếu cần thêm camera (video Phase 2): -->
<iframe allow="microphone; camera" ... ></iframe>

Cross-origin

Iframe app.zorio.vn luôn cross-origin với CRM của đối tác → mọi giao tiếp giữa parent và iframe phải qua postMessage (xem postMessage API). Không cố truy cập iframe.contentWindow.phone trực tiếp — sẽ bị browser block.

5. postMessage API

Iframe webphone giao tiếp với parent qua window.postMessage. Mọi message đều có format:

ts
interface ZorioWebphoneMessage {
  source: 'zorio-webphone'   // marker, lọc message khác
  type:   string             // tên event hoặc command
  payload?: any              // dữ liệu kèm theo
}

5.1 Lắng nghe event từ iframe (iframe → parent)

js
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://app.zorio.vn') return       // verify origin
  if (e.data?.source !== 'zorio-webphone') return       // verify marker

  switch (e.data.type) {
    case 'registered':
      console.log('Webphone đã sẵn sàng')
      break
    case 'incoming_call':
      console.log('Cuộc gọi đến:', e.data.payload.call.number)
      myUI.showIncomingBanner(e.data.payload)
      break
    case 'call_answered':
      myUI.openCallControls(e.data.payload.call.uuid)
      break
    case 'call_hangup':
      myUI.closeCallControls()
      console.log('Billsec:', e.data.payload.billsec)
      break
    case 'error':
      console.error('Webphone error:', e.data.payload)
      break
  }
})

Iframe forward đầy đủ 12 events giống SDK npm/UMD — xem Events.

5.2 Điều khiển webphone (parent → iframe)

js
const iframe = document.querySelector('iframe')

function sendCommand(type, payload) {
  iframe.contentWindow.postMessage(
    { source: 'zorio-webphone', type, payload },
    'https://app.zorio.vn'  // target origin — KHÔNG dùng '*'
  )
}

// Click-to-call từ button trong CRM
sendCommand('call', {
  number: '0912345678',
  options: { campaignId: 36, leadId: 78912 },
})

// Hangup cuộc đang nói
sendCommand('hangup', {})

// Gửi DTMF
sendCommand('send_dtmf', { digits: '1' })

// Mute / unmute
sendCommand('mute', {})
sendCommand('unmute', {})

// Refresh token sắp hết hạn
sendCommand('refresh_token', { token: newEmbedToken })

Danh sách type command tương ứng 1-1 với 12 method API.

5.3 Ví dụ tích hợp đầy đủ

html
<div id="phone-container">
  <iframe
    id="zorio-frame"
    src="https://app.zorio.vn/webphone-embed?token=PLACEHOLDER"
    allow="microphone"
    style="border:0; width:380px; height:560px"
  ></iframe>
</div>

<button id="btn-call">Gọi 0912345678</button>

<script>
const ZORIO_ORIGIN = 'https://app.zorio.vn'
const iframe = document.getElementById('zorio-frame')

// 1. Lấy embed token rồi inject
fetch('/api/internal/zorio-embed-token').then(r => r.json()).then(({ token }) => {
  iframe.src = `${ZORIO_ORIGIN}/webphone-embed?token=${token}&layout=docked`
})

// 2. Lắng nghe events
window.addEventListener('message', (e) => {
  if (e.origin !== ZORIO_ORIGIN) return
  if (e.data?.source !== 'zorio-webphone') return
  console.log('[zorio]', e.data.type, e.data.payload)
})

// 3. Điều khiển
document.getElementById('btn-call').onclick = () => {
  iframe.contentWindow.postMessage(
    { source: 'zorio-webphone', type: 'call', payload: { number: '0912345678' } },
    ZORIO_ORIGIN
  )
}
</script>

6. Tính năng v7 vẫn hoạt động trong iframe

Iframe build từ cùng bundle SDK, nên đầy đủ:

  • Auto-reconnect supervisor — không cần xử lý reconnect ở parent.
  • Pagehide cleanup — khi user đóng tab CRM, iframe unregister sạch.
  • Early media — outbound nghe ringback ngay từ 183 Session Progress.

7. Khi nào nên iframe vs SDK npm/UMD?

Tiêu chíIframeSDK npm/UMD
Tốc độ tích hợp✅ Nhanh nhất (1 thẻ <iframe>)Trung bình
Custom UI❌ UI mặc định của Zorio✅ Toàn quyền
Cross-origin✅ Cách ly hoàn toànCần CSP setup
Tùy biến events nâng caopostMessage✅ Native callback
Air-gapCần host iframe nội bộ✅ Self-host UMD

Khuyến nghị

Nếu đối tác chỉ cần "có cái webphone" trên CRM trong một ngày → dùng iframe. Nếu cần custom UX hoặc tích hợp sâu vào workflow CRM (CTI, screen-pop) → dùng npm hoặc UMD.

Tham khảo thêm:

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