IP-телефонія (SIP/WebRTC) — Документація¶
Що це¶
Інтеграція IP-телефонії в DOP-додаток через протокол SIP over WebSocket з використанням WebRTC для передачі голосу. Дозволяє здійснювати телефонні дзвінки прямо з браузера — натиснув на іконку телефону в картці клієнта і одразу розмовляєш.
Бібліотека: JsSIP 3.x — зрілий SIP-стек для браузера, підтримує WebRTC, працює з Asterisk, FreeSWITCH, Obbitel, Binotel та іншими SIP-серверами.
Навіщо¶
- Швидкість — дзвінок в один клік без перемикання між програмами
- Контекст — оператор бачить картку клієнта прямо під час дзвінка
- Єдина система — всі комунікації (дзвінки, чат, email) в одному інтерфейсі
- Без додаткового ПЗ — не потрібен софтфон, достатньо браузера + гарнітура
Архітектура¶
┌──────────────┐ WebSocket (WSS) ┌──────────────┐
│ Браузер │ ◄──────────────────────► │ SIP-сервер │
│ (JsSIP + │ SIP signaling │ (Asterisk/ │
│ WebRTC) │ │ FreeSWITCH) │
│ │ WebRTC (SRTP) │ │
│ 🎤 ──────► │ ◄──────────────────────► │ ──► PSTN ☎️ │
│ │ голосовий потік │ │
└──────────────┘ └──────────────┘
│ │
│ STUN/TURN │
└──────────► NAT Traversal ◄──────────────┘
Потік дзвінка:
1. Браузер підключається до SIP-сервера через WebSocket (WSS)
2. JsSIP реєструє SIP-extension (наприклад, sip:101@pbx.example.com)
3. При натисканні на телефон — відправляється SIP INVITE
4. SIP-сервер маршрутизує дзвінок (внутрішній або через PSTN-шлюз)
5. WebRTC встановлює голосовий канал (SRTP) між браузером і сервером
6. STUN/TURN допомагають обійти NAT
Що було реалізовано¶
Backend (Django)¶
| Файл | Опис |
|---|---|
backend/essentials/models/telephony_settings.py |
Модель TelephonySettings — налаштування SIP-сервера per tenant |
backend/essentials/serializers/master_data.py |
TelephonySettingsSerializer |
backend/essentials/views/master_data.py |
TelephonySettingsViewSet |
backend/essentials/urls.py |
URL: /api/v1/essentials/telephony-settings/ |
backend/essentials/migrations/0042_telephony_settings.py |
Міграція БД |
Модель TelephonySettings (singleton per tenant):
- is_enabled — увімкнення/вимкнення телефонії
- provider — тип АТС (Asterisk, FreeSWITCH, Binotel, Other)
- sip_server — адреса SIP-сервера (наприклад pbx.example.com)
- ws_url — WebSocket URL (наприклад wss://pbx.example.com:8089/ws)
- sip_realm — SIP домен для автентифікації
- outbound_prefix — префікс для зовнішніх номерів (наприклад 9)
- stun_server, turn_server, turn_username, turn_password — NAT traversal
Frontend (React)¶
| Файл | Опис |
|---|---|
frontend/erp/src/api/telephony.ts |
API-клієнт для CRUD налаштувань |
frontend/erp/src/store/telephonyStore.ts |
Zustand store — серверні налаштування + локальні SIP credentials + стан дзвінка |
frontend/erp/src/hooks/useSipPhone.ts |
Головний хук — JsSIP UserAgent, call(), hangup(), WebRTC audio |
frontend/erp/src/components/Telephony/SipCallOverlay.tsx |
Floating overlay — статус дзвінка, таймер, кнопка завершення |
frontend/erp/src/pages/SettingsPage/TelephonySettings.tsx |
Сторінка налаштувань телефонії |
Інтеграція в існуючі компоненти:
- ClientFields.tsx — кнопки телефону біля контактних осіб (контактна особа, директор, бухгалтер, касир) тепер підтримують SIP-дзвінки
- App.tsx — глобально підключений SipCallOverlay
- SettingsPage/index.tsx — додано секцію "Телефонія" в налаштування
Як працює кнопка телефону¶
Клік на іконку 📞 біля контактної особи
│
├── SIP налаштований? (useSipPhone.isReady === true)
│ ├── Так → sip.call(phone, name) → SIP INVITE → дзвінок через АТС
│ └── Ні → fallback на <a href="tel:..."> (стандартний tel: протокол)
│
└── Під час дзвінка → SipCallOverlay показує:
- Ім'я / номер
- Статус (З'єднання → Виклик → Розмова → Завершено)
- Таймер тривалості
- Кнопка завершення (червона)
Як налаштувати¶
1. Налаштування SIP-сервера (адмін тенанту)¶
Перейти в Settings → Телефонія і заповнити:
| Поле | Приклад | Опис |
|---|---|---|
| Provider | Asterisk | Тип вашої АТС |
| SIP Server | pbx.company.com |
Адреса SIP-сервера |
| WebSocket URL | wss://pbx.company.com:8089/ws |
WebSocket endpoint (обов'язково WSS!) |
| SIP Realm | pbx.company.com |
Домен автентифікації (зазвичай = SIP Server) |
| Outbound Prefix | 9 |
Префікс для зовнішніх ліній (залежить від АТС) |
| STUN Server | stun:stun.l.google.com:19302 |
За замовчуванням Google STUN |
| TURN Server | turn:turn.company.com:3478 |
Потрібен якщо є проблеми з NAT |
Натиснути "Зберегти" — налаштування збережуться на сервері для всього тенанту.
2. Налаштування SIP-акаунту (кожен користувач)¶
На тій же сторінці, секція "Ваш SIP-акаунт":
| Поле | Приклад | Опис |
|---|---|---|
| SIP User | 101 |
Ваш внутрішній номер (extension) на АТС |
| SIP Password | **** |
Пароль до SIP-акаунту |
Важливо: SIP credentials зберігаються тільки в браузері (localStorage) і ніколи не відправляються на сервер ESWF.
3. Використання¶
- Відкрити картку контрагента
- Біля контактної особи з'явиться зелена іконка телефону (якщо SIP активний)
- Клік → починається дзвінок
- В правому нижньому куті з'явиться overlay з інформацією про дзвінок
- Натиснути червону кнопку для завершення
Вимоги до SIP-сервера¶
Asterisk¶
В http.conf увімкнути WebSocket:
[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088
tlsenable=yes
tlsbindaddr=0.0.0.0:8089
tlscertfile=/etc/asterisk/keys/asterisk.pem
В pjsip.conf додати WebSocket transport:
[transport-wss]
type=transport
protocol=wss
bind=0.0.0.0:8089
cert_file=/etc/asterisk/keys/asterisk.pem
Extension для WebRTC:
[101]
type=endpoint
transport=transport-wss
context=default
disallow=all
allow=opus
allow=ulaw
webrtc=yes
auth=101
aors=101
FreeSWITCH¶
В vars.xml:
WebSocket listener в internal.xml:
Стан дзвінка (CallStatus)¶
| Статус | Опис | Колір |
|---|---|---|
idle |
Немає дзвінка | — |
connecting |
Відправлено SIP INVITE | жовтий |
ringing |
Абонент отримав виклик | синій |
active |
Розмова | зелений |
ended |
Дзвінок завершено | сірий |
error |
Помилка (мережа, авторизація тощо) | червоний |
Технічні деталі¶
Zustand Store (telephonyStore)¶
Зберігає в localStorage під ключем eswf-telephony:
- Серверні налаштування (кеш з backend) — wsUrl, sipServer, stunServer тощо
- Локальні SIP credentials — sipUser, sipPassword
- НЕ зберігає стан дзвінка (транзієнтний)
JsSIP UserAgent lifecycle¶
- При зміні credentials/config → створюється новий
JsSIP.UA - UA підключається до WebSocket і реєструється на SIP-сервері
- При
call()→ створюєтьсяRTCSessionз WebRTC audio - Remote audio stream прив'язується до прихованого
<audio>елемента - При unmount або зміні config → UA зупиняється
Fallback поведінка¶
Якщо телефонія не налаштована (useSipPhone.isReady === false):
- Кнопка телефону працює як звичайний <a href="tel:..."> лінк
- На мобільних — відкриє дайлер
- На десктопі — відкриє прив'язану програму (Skype, Teams тощо)
Своя безкоштовна АТС в компанії — що для цього потрібно¶
Загальна ідея¶
Asterisk (або FreePBX) — це безкоштовна open-source АТС. Встановлюється на будь-який сервер або ПК, перетворюючи його в повноцінну телефонну станцію. Всі дзвінки між співробітниками йдуть через інтернет — безкоштовно і безлімітно. Для дзвінків на мобільні/стаціонарні — підключається SIP-провайдер (копійки за хвилину).
Що потрібно¶
| Компонент | Ціна | Що це |
|---|---|---|
| Asterisk / FreePBX | 0 грн | Сервер АТС — маршрутизація дзвінків, голосова пошта, IVR, запис |
| Сервер | VPS від $5/міс або будь-який старий ПК в офісі | Де крутиться Asterisk |
| Інтернет | Вже є | Передача голосу (VoIP), ~100 Kbps на дзвінок |
| Гарнітури / навушники | від 200 грн | Або просто навушники від телефону |
| Софтфони | 0 грн | Браузер (як в ESWF), або Obbitel, MicroSIP, Obbitel (безкоштовні) |
| Docker (для тесту) | 0 грн | Запуск Asterisk в контейнері за 2 хвилини |
Що отримуєш безкоштовно (внутрішні дзвінки)¶
- Дзвінки між співробітниками — безлімітно, 0 грн, з будь-якої точки світу
- Конференц-дзвінки — скільки завгодно учасників одночасно
- Голосова пошта — якщо не відповіли, запис залишається
- IVR — голосове меню ("Натисніть 1 для відділу продаж, 2 для бухгалтерії...")
- Черги дзвінків — call center функціонал (оператори по черзі)
- Запис розмов — всі дзвінки записуються автоматично
- Дзвінки між офісами (Київ ↔ Львів ↔ Одеса) — 0 грн через інтернет
- Віддалені працівники — працюють з дому як ніби сидять в офісі
Дзвінки на мобільні / стаціонарні (SIP-провайдери)¶
Для виходу на реальну телефонну мережу (PSTN) підключається SIP-trunk — це як міст між інтернетом і телефонними лініями:
| Провайдер (Україна) | Вартість | Що дає |
|---|---|---|
| Binotel | від 300 грн/міс | Український номер + хвилини + свій CRM |
| Zadarma | від 0 (pay-as-you-go) | ~0.30 грн/хв на мобільний UA, безкоштовні внутрішні |
| IPtel | від 150 грн/міс | Українські міські номери (044, 032, 048...) |
| VoIPstudio | від $5/юзер/міс | Міжнародні дзвінки, номери 100+ країн |
| Ukrtelecom SIP | за тарифом | SIP-trunk від Укртелекому |
Підсумок: між собою — безкоштовно, назовні — копійки за хвилину.
Типова схема для компанії¶
Інтернет
│
┌───────┴───────┐
│ Asterisk │ ← VPS $5/міс або офісний ПК
│ (FreePBX) │
└───┬───┬───┬───┘
│ │ │
┌────────────┘ │ └────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌──────┴──────┐
│ Офіс Київ │ │ Віддалені │ │ SIP-trunk │
│ 20 ext. │ │ працівники│ │ (Zadarma) │
│ браузер / │ │ з дому / │ │ ↓ │
│ софтфон │ │ в дорозі │ │ 044-XXX-XX │
└───────────┘ └───────────┘ │ міські/моб. │
└─────────────┘
Порівняння з класичною АТС¶
| Класична офісна АТС | VoIP (Asterisk) | |
|---|---|---|
| Обладнання | $2000–10000 (залізо) | $0 (програма) |
| Внутрішні дзвінки | По мідному кабелю | По інтернету |
| Віддалені працівники | Неможливо | Працює звідки завгодно |
| Масштабування | Купувати нові плати розширення | Просто додати extension в конфіг |
| Між офісами | Платні міжміські дзвінки | Безкоштовно |
| Запис розмов | Додаткове обладнання | Вбудовано |
| IVR / голосове меню | Дорога опція | Вбудовано |
| Інтеграція з DOP/CRM | Складно або неможливо | Нативно через SIP + WebSocket |
Локальний тест через Docker¶
Для швидкого тестування можна підняти Asterisk в Docker-контейнері:
Це дасть: - Extension 101 — твій браузер реєструється як SIP-телефон - Extension 102 — другий тестовий акаунт (другий браузер або MicroSIP) - *43 — echo test: дзвониш і чуєш свій голос із затримкою (перевірка аудіо)
Налаштування в ESWF для локального тесту:
| Поле | Значення |
|---|---|
| SIP Server | localhost |
| WebSocket URL | wss://localhost:8089/ws |
| SIP User | 101 |
| SIP Password | test123 |
Demo-режим (без сервера взагалі)¶
Якщо немає SIP-сервера і Docker — в ESWF є вбудований demo-режим:
- Settings → Телефонія → увімкнути "Enable IP Telephony"
- Увімкнути "Demo Mode" (оранжевий перемикач)
- Натиснути "Зберегти"
В demo-режимі кнопки телефону стають зеленими і при натисканні симулюють повний цикл дзвінка (З'єднання → Виклик → Розмова) без реального підключення. Overlay показує бейдж "DEMO".
Можливі розширення (TODO)¶
- [ ] Вхідні дзвінки (incoming calls) з popup-нотифікацією
- [ ] Журнал дзвінків (CallLog) з прив'язкою до клієнта
- [ ] Запис розмов (recording) через SIP-сервер
- [ ] Click-to-call з будь-якого місця в DOP (не тільки картка клієнта)
- [ ] DTMF (тонові сигнали) для IVR-меню
- [ ] Перевод дзвінка (transfer) на іншого оператора
- [ ] Конференц-дзвінки
- [ ] Інтеграція з Binotel API (callback, history)
- [ ] Статус SIP-реєстрації в хедері (зелена/червона іконка)
- [ ] Per-user налаштування ringtone