---
title: "WebRTC-дзвінки та STUNner TURN"
description: "Аудіо- й відеодзвінки 1:1 у месенджері через сигналізацію Tunnel і Kubernetes STUNner ICE — контрольний список для операторів та шляхи інтеграції"
locale: "uk"
---
# WebRTC-дзвінки та STUNner TURN

> **Info**
> Використовуйте вкладки **Founder** / **Developer** на бічній панелі документації, щоб фільтрувати цю сторінку. Видимість у бічній панелі налаштовується в `lib/docs/audience-curated-docs.ts`.

Месенджер Ring може здійснювати **аудіо- й відеодзвінки 1:1** усередині **прямої** розмови. Сигналізація проходить наявним каналом `Tunnel` `conversation:{id}`. Для медіа використовується браузерний `RTCPeerConnection`. Обхід NAT забезпечує **STUNner** (STUN/TURN) у кластері, а ICE-сервери надходять через **автентифікований** API — облікові дані ніколи не потрапляють у `NEXT_PUBLIC_*`.

| Рівень | Роль |
|-------|------|
| UI | Phone / Video у заголовку розмови → повноекранний оверлей дзвінка |
| Сигналізація | Події Tunnel `call:invite` … `call:hangup` у `conversation:{id}` |
| ICE | `GET /api/webrtc/ice-servers` (потрібна сесія) |
| TURN | Шлюз STUNner `TURN-UDP:3478` (наприклад, `turn.ring-platform.org`) |

Пов’язані матеріали: [Месенджер](/docs/features/messaging.md) · [Ігри з друзями](/docs/features/peer-games.md) · [Протокол Tunnel](/docs/features/tunnel-protocol.md) · [Архітектура реального часу](/docs/architecture/real-time.md)

> **Tip**
> Ігри з друзями використовують спільний **м’ютекс дзвінка ↔ гри** (`features/peer-games/lib/peer-game-mutex.ts`). `MessagesShell` публікує `setPeerCallBusy` із фази WebRTC, тому банер `/games` і віджети `game_request` відмовляються приймати запрошення, поки триває дзвінок. Синхронізація між вкладками проходить через **BroadcastChannel** (необов’язковий `navigator.locks`). Ігри з друзями також повторно використовують `GET /api/webrtc/ice-servers` для оптимістичних підказок ходів через **DataChannel** — Tunnel + DB залишаються SSOT дошки.

### For founders

## Чому це важливо для вашого клону

Учасники швидше укладають угоди, коли можуть **розмовляти**, не залишаючи ваш Ring. Дзвінки залишаються в тій самій розмові, яка вже містить контекст можливості, сутності або магазину.

  
- **[Месенджер](/docs/features/messaging.md)** — Розмови, групи та живий чат Tunnel.

  
- **[Протокол Tunnel](/docs/features/tunnel-protocol.md)** — Канал реального часу, який дзвінки використовують для запрошення, прийняття та завершення.

  
- **[Самостійне розгортання](/docs/deployment/self-hosted.md)** — Тут описано розміщення STUNner і налаштування env для клонів k8s.

### Контрольний список оператора (k3s / Ringdom cloud)

1. **Площину керування STUNner** встановлено (`stunner-system`), а Gateway налаштовано в просторі імен `stunner`.
2. Публічний UDP **3478** доступний (локальний / хмарний firewall).
3. Налаштовано DNS для хоста TURN (приклад production: `turn.ring-platform.org` A/AAAA → IP вузла LB).
4. Підключено App ConfigMap / Secret: `WEBRTC_STUN_URL`, `WEBRTC_TURN_URL`, `WEBRTC_TURN_USERNAME`, `WEBRTC_TURN_CREDENTIAL`.
5. Учасники використовують **прямі** чати з підключеним Tunnel — Phone / Video з’являються в заголовку.

### Типові сценарії

- Двоє підібраних фахівців переходять від тексту до короткого голосового дзвінка, не перемикаючи застосунки.
- Постачальник уточнює замовлення наживо через відео, поки гілка залишається SSOT.
- Навіть за суворого NAT з’єднання працює, оскільки TURN ретранслює через STUNner (коли облікові дані налаштовано).

> **Tip**
> Для дзвінків потрібна активна сесія Tunnel. Якщо зв’язок реального часу втрачено, UI відмовляється починати дзвінок — спочатку виправте Tunnel ([Протокол Tunnel](/docs/features/tunnel-protocol.md)).

### For developers

## Архітектура

```mermaid
sequenceDiagram
  participant A as Браузер абонента
  participant API as GET /api/webrtc/ice-servers
  participant T as Tunnel conversation:id
  participant B as Браузер адресата
  participant TURN as STUNner TURN-UDP

  A->>API: cookie сесії
  API-->>A: iceServers (+ TURN, якщо налаштовано)
  A->>T: call:invite
  T->>B: call:invite
  B->>API: cookie сесії
  B->>T: call:accept
  A->>T: call:offer
  B->>T: call:answer
  A->>T: call:ice
  B->>T: call:ice
  A->>TURN: виділення / ретрансляція
  B->>TURN: виділення / ретрансляція
```

### Перевірені модулі

| Шлях | Відповідальність |
|------|------------------|
| `app/api/webrtc/ice-servers/route.ts` | Автентифікована конфігурація ICE зі змінних середовища `WEBRTC_*` |
| `features/chat/lib/fetch-ice-servers.ts` | Клієнтський fetch через `apiClient` |
| `features/chat/lib/call-types.ts` | Типи подій сигналізації / payload |
| `hooks/use-webrtc-call.ts` | `RTCPeerConnection` + публікація/підписка Tunnel |
| `features/chat/components/call-overlay.tsx` | Повноекранні елементи керування (вимкнення звуку / камера / завершення) |
| `features/chat/components/conversation-header.tsx` | Дії Phone / Video (лише для прямих розмов) |
| `features/messages/components/messages-shell.tsx` | З’єднує hook + overlay |
| `infrastructure/k3s-or/stunner/` | GatewayClass, GatewayConfig, маніфести Gateway |
| `k8s/ENV-PROD-WIRING.md` | Таблиця production-змінних для `WEBRTC_*` |

### ICE API

`GET /api/webrtc/ice-servers` — потрібна сесія Auth.js (`401`, якщо користувач анонімний).

Форма відповіді (перевірено):

```json
{
  "success": true,
  "data": {
    "iceServers": [
      { "urls": "stun:…" },
      { "urls": "turn:…", "username": "…", "credential": "…" }
    ],
    "turnConfigured": true
  }
}
```

STUN використовує `stun:stun.l.google.com:19302` як резерв, якщо `WEBRTC_STUN_URL` не задано. TURN додається лише тоді, коли задано **всі** `WEBRTC_TURN_URL`, `WEBRTC_TURN_USERNAME` і `WEBRTC_TURN_CREDENTIAL`.

### Змінні середовища (перевірено)

| Змінна | Де | Примітки |
|----------|--------|------|
| `WEBRTC_STUN_URL` | ConfigMap | Необов’язкова; у route є стандартний Google STUN |
| `WEBRTC_TURN_URL` | ConfigMap | Наприклад, `turn:turn.ring-platform.org:3478?transport=udp` |
| `WEBRTC_TURN_USERNAME` | Secret `ring-platform-org-secrets` | Відповідає Secret автентифікації STUNner |
| `WEBRTC_TURN_CREDENTIAL` | Secret | **Ніколи** не використовуйте `NEXT_PUBLIC_*` |

Приклади: `k8s/secrets.example.yaml`, нотатки з підключення: `k8s/ENV-PROD-WIRING.md`.  
`env.local.template` поки **не** містить цих ключів — для відповідності local/prod використовуйте приклади k8s.

### Події сигналізації Tunnel

Публікуються в каналі `conversation:{conversationId}` (тому самому каналі, що й typing):

| Подія | Призначення |
|-------|---------|
| `call:invite` | Вихідний дзвінок |
| `call:accept` / `call:reject` | Рішення адресата |
| `call:offer` / `call:answer` | SDP |
| `call:ice` | Кандидати ICE |
| `call:hangup` | Завершення |

Підписуйтеся через **`useTunnelChannel`** — не підписуйтеся напряму в effects ([Протокол Tunnel](/docs/features/tunnel-protocol.md)).

### Порядок встановлення STUNner (k3s-or)

Встановіть оператор (він підтягує Gateway API + CRD STUNner):

{`export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
helm repo add stunner https://l7mp.io/stunner && helm repo update
helm upgrade --install stunner stunner/stunner \\
  --create-namespace --namespace=stunner-system --wait`}

Створіть Secret автентифікації (`type=static`, `username`, `password`) і застосуйте `infrastructure/k3s-or/stunner/gateway.yaml` (GatewayClass + GatewayConfig + Gateway з протоколом **`TURN-UDP`**).

Підключіть app Secret/ConfigMap і переконайтеся, що в Deployment є env-посилання на `WEBRTC_TURN_USERNAME` / `WEBRTC_TURN_CREDENTIAL` (`k8s/deployment.yaml`). Перезапустіть Deployment застосунку.

Smoke-тест: зовнішня STUN Binding на `:3478`; автентифікований `GET /api/webrtc/ice-servers`; два браузери в одній прямій розмові.

> **Warning**
> Протокол listener має бути **TURN-UDP**, а не простий UDP. Realm у GatewayConfig містить літери, цифри та дефіс (наприклад, `ring-platform-org`). Порожні `backendRefs` UDPRoute недійсні — peer TURN не потребує UDPRoute, доки не з’явиться SFU (LiveKit).

### Обсяг поставленого MVP

- **Входить:** пряме аудіо/відео 1:1; елементи керування overlay; ICE через server route; основа STUNner TURN.
- **Не входить (див. backlog):** вхідний дзвінок, коли співрозмовник не переглядає цю гілку; групові/багатосторонні дзвінки; LiveKit SFU; системні повідомлення про дзвінки; посилення шляху IPv6 TURN.

Стек Connect Platform RTVS / FastTransponder **не портовано** — використано лише шаблони UX керування (вимкнення звуку / камера / завершення).

## Backlog

## Пов’язана документація

  
- [features/messaging](/docs/features/messaging.md) — Залежить від: дзвінки починаються із заголовків прямих розмов у месенджері.

  
- [features/peer-games](/docs/features/peer-games.md) — Той самий процес: спільний м’ютекс дзвінка/гри (BroadcastChannel); ICE route повторно використовується для оптимістичних підказок ходів через DataChannel.

  
- [features/tunnel-protocol](/docs/features/tunnel-protocol.md) — Залежить від: call:invite … call:hangup проходять через conversation:{id}.

  
- [features/push-notifications-fcm](/docs/features/push-notifications-fcm.md) — Той самий процес: офлайновий банер ОС CALL_INVITE (FCM лише з data, TTL 90 с, RFC no-op у Chrome).

  
- [api/messaging](/docs/api/messaging.md) — Додатково: HTTP-контракти розмов і typing.

  
- [architecture/real-time](/docs/architecture/real-time.md) — Поглиблено: володіння TunnelProvider і споживачі.
