Перейти до змісту

IP-телефонія (SIP/WebRTC) — Документація

Що це

Інтеграція IP-телефонії в DOP-додаток через протокол SIP over WebSocket з використанням WebRTC для передачі голосу. Дозволяє здійснювати телефонні дзвінки прямо з браузера — натиснув на іконку телефону в картці клієнта і одразу розмовляєш.

Бібліотека: JsSIP 3.x — зрілий SIP-стек для браузера, підтримує WebRTC, працює з Asterisk, FreeSWITCH, Obbitel, Binotel та іншими SIP-серверами.


Навіщо

  1. Швидкість — дзвінок в один клік без перемикання між програмами
  2. Контекст — оператор бачить картку клієнта прямо під час дзвінка
  3. Єдина система — всі комунікації (дзвінки, чат, email) в одному інтерфейсі
  4. Без додаткового ПЗ — не потрібен софтфон, достатньо браузера + гарнітура

Архітектура

┌──────────────┐     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. Використання

  1. Відкрити картку контрагента
  2. Біля контактної особи з'явиться зелена іконка телефону (якщо SIP активний)
  3. Клік → починається дзвінок
  4. В правому нижньому куті з'явиться overlay з інформацією про дзвінок
  5. Натиснути червону кнопку для завершення

Вимоги до 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:

<X-PRE-PROCESS cmd="set" data="internal_ssl_enable=true"/>

WebSocket listener в internal.xml:

<param name="ws-binding" value=":5066"/>
<param name="wss-binding" value=":7443"/>


Стан дзвінка (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

  1. При зміні credentials/config → створюється новий JsSIP.UA
  2. UA підключається до WebSocket і реєструється на SIP-сервері
  3. При call() → створюється RTCSession з WebRTC audio
  4. Remote audio stream прив'язується до прихованого <audio> елемента
  5. При 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-контейнері:

docker-compose up -d

Це дасть: - 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-режим:

  1. Settings → Телефонія → увімкнути "Enable IP Telephony"
  2. Увімкнути "Demo Mode" (оранжевий перемикач)
  3. Натиснути "Зберегти"

В 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