feat(ui): зелёная кнопка ответа и автообновление PWA
This commit is contained in:
parent
e0333a9c32
commit
1a8d1c70fd
75
doc/instructions/coturn-install.md
Normal file
75
doc/instructions/coturn-install.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Установка TURN сервера (coturn)
|
||||||
|
|
||||||
|
## 1. Что нужно
|
||||||
|
- Сервер с публичным IP (в примере: `37.214.58.208`).
|
||||||
|
- Доступ `root` по SSH.
|
||||||
|
- Открытые порты в firewall:
|
||||||
|
- `3478/tcp`
|
||||||
|
- `3478/udp`
|
||||||
|
- диапазон relay-портов, например `49160-49200/udp`
|
||||||
|
|
||||||
|
## 2. Быстрая установка (рекомендуется)
|
||||||
|
В проекте уже есть готовый скрипт:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash scripts/setup_turn_coturn.sh \
|
||||||
|
--secret "CHANGE_ME_LONG_RANDOM_SECRET" \
|
||||||
|
--realm "shineup.me"
|
||||||
|
```
|
||||||
|
|
||||||
|
Что делает скрипт:
|
||||||
|
- ставит `coturn`;
|
||||||
|
- включает режим `use-auth-secret` (временные логин/пароль);
|
||||||
|
- пишет `/etc/turnserver.conf`;
|
||||||
|
- включает и перезапускает сервис `coturn`.
|
||||||
|
|
||||||
|
## 3. Проверка
|
||||||
|
Проверить статус:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status coturn --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что порт слушается:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ss -lntup | grep 3478
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Ручная установка (если без скрипта)
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y coturn
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример `/etc/turnserver.conf`:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
listening-port=3478
|
||||||
|
fingerprint
|
||||||
|
lt-cred-mech
|
||||||
|
use-auth-secret
|
||||||
|
static-auth-secret=CHANGE_ME_LONG_RANDOM_SECRET
|
||||||
|
realm=shineup.me
|
||||||
|
external-ip=37.214.58.208
|
||||||
|
listening-ip=0.0.0.0
|
||||||
|
relay-ip=37.214.58.208
|
||||||
|
min-port=49160
|
||||||
|
max-port=49200
|
||||||
|
no-cli
|
||||||
|
simple-log
|
||||||
|
```
|
||||||
|
|
||||||
|
В `/etc/default/coturn`:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
TURNSERVER_ENABLED=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable coturn
|
||||||
|
sudo systemctl restart coturn
|
||||||
|
```
|
||||||
|
|
||||||
48
doc/instructions/turn-connect-to-shine-server.md
Normal file
48
doc/instructions/turn-connect-to-shine-server.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Подключение TURN к SHiNE-серверу
|
||||||
|
|
||||||
|
Начиная с текущей версии, клиент звонков запрашивает ICE-конфиг у backend через WS-операцию `GetCallIceConfig` и использует её для `RTCPeerConnection`.
|
||||||
|
|
||||||
|
## 1. Настройки backend
|
||||||
|
Файл: `src/main/resources/application.properties`
|
||||||
|
|
||||||
|
Ключи:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
call.ice.stun.urls=stun:stun.l.google.com:19302
|
||||||
|
call.ice.turn.urls=turn:37.214.58.208:3478?transport=udp,turn:37.214.58.208:3478?transport=tcp
|
||||||
|
call.ice.turn.ttlSec=600
|
||||||
|
call.ice.turn.userPrefix=shine
|
||||||
|
call.ice.turn.sharedSecret=CHANGE_ME_LONG_RANDOM_SECRET
|
||||||
|
|
||||||
|
# fallback (если не используете shared-secret)
|
||||||
|
call.ice.turn.username=
|
||||||
|
call.ice.turn.password=
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Рекомендуемый режим (временные credentials)
|
||||||
|
- На coturn и на SHiNE-сервере должен быть **одинаковый** secret:
|
||||||
|
- coturn: `static-auth-secret=...`
|
||||||
|
- SHiNE: `call.ice.turn.sharedSecret=...`
|
||||||
|
- Тогда SHiNE выдаёт короткоживущие `turnUsername/turnPassword` (TTL).
|
||||||
|
|
||||||
|
## 3. Fallback режим (статический логин/пароль)
|
||||||
|
Если временные credentials не используются:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
call.ice.turn.sharedSecret=
|
||||||
|
call.ice.turn.username=turn_user
|
||||||
|
call.ice.turn.password=turn_password
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Деплой после изменения
|
||||||
|
```bash
|
||||||
|
./gradlew deployServerNoCleanNoTests
|
||||||
|
./gradlew deployWEB
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Проверка
|
||||||
|
1. Авторизоваться двумя клиентами.
|
||||||
|
2. Запустить звонок.
|
||||||
|
3. Проверить, что звонок устанавливается даже в сети, где прямой P2P затруднён.
|
||||||
|
4. Если TURN недоступен, клиент автоматически откатится к STUN-конфигу по умолчанию.
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ function ensureUi() {
|
|||||||
|
|
||||||
acceptBtn = document.createElement('button');
|
acceptBtn = document.createElement('button');
|
||||||
acceptBtn.type = 'button';
|
acceptBtn.type = 'button';
|
||||||
acceptBtn.className = 'primary-btn';
|
acceptBtn.className = 'call-accept-btn';
|
||||||
acceptBtn.textContent = 'Ответить';
|
acceptBtn.textContent = 'Ответить';
|
||||||
acceptBtn.addEventListener('click', async () => {
|
acceptBtn.addEventListener('click', async () => {
|
||||||
await acceptIncomingCall();
|
await acceptIncomingCall();
|
||||||
|
|||||||
@ -1,4 +1,49 @@
|
|||||||
const LS_KEY = 'shine-ui-webpush-subscription-v1';
|
const LS_KEY = 'shine-ui-webpush-subscription-v1';
|
||||||
|
const SW_UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000;
|
||||||
|
let swUpdateIntervalId = null;
|
||||||
|
let controllerChangeHandled = false;
|
||||||
|
|
||||||
|
function setupServiceWorkerAutoUpdate(registration, log) {
|
||||||
|
if (!registration) return;
|
||||||
|
|
||||||
|
if (!controllerChangeHandled && 'serviceWorker' in navigator) {
|
||||||
|
controllerChangeHandled = true;
|
||||||
|
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||||
|
log({
|
||||||
|
level: 'info',
|
||||||
|
source: 'web-push',
|
||||||
|
message: 'Service Worker обновился, перезагружаем UI',
|
||||||
|
});
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registration.addEventListener('updatefound', () => {
|
||||||
|
const installing = registration.installing;
|
||||||
|
if (!installing) return;
|
||||||
|
log({
|
||||||
|
level: 'info',
|
||||||
|
source: 'web-push',
|
||||||
|
message: 'Найдено обновление Service Worker',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (swUpdateIntervalId) {
|
||||||
|
window.clearInterval(swUpdateIntervalId);
|
||||||
|
swUpdateIntervalId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkNow = () => {
|
||||||
|
registration.update().catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
checkNow();
|
||||||
|
swUpdateIntervalId = window.setInterval(checkNow, SW_UPDATE_CHECK_INTERVAL_MS);
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState !== 'visible') return;
|
||||||
|
checkNow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function urlBase64ToUint8Array(base64String) {
|
function urlBase64ToUint8Array(base64String) {
|
||||||
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
||||||
@ -36,13 +81,16 @@ export async function initPwaPush({ authService, onLog = null }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registration = await navigator.serviceWorker.register('./firebase-messaging-sw.js');
|
const registration = await navigator.serviceWorker.register('./firebase-messaging-sw.js', {
|
||||||
|
updateViaCache: 'none',
|
||||||
|
});
|
||||||
log({
|
log({
|
||||||
level: 'info',
|
level: 'info',
|
||||||
source: 'web-push',
|
source: 'web-push',
|
||||||
message: 'Service Worker зарегистрирован',
|
message: 'Service Worker зарегистрирован',
|
||||||
details: { scope: registration.scope },
|
details: { scope: registration.scope },
|
||||||
});
|
});
|
||||||
|
setupServiceWorkerAutoUpdate(registration, log);
|
||||||
|
|
||||||
const permission = await Notification.requestPermission();
|
const permission = await Notification.requestPermission();
|
||||||
if (permission !== 'granted') {
|
if (permission !== 'granted') {
|
||||||
|
|||||||
@ -868,6 +868,33 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.call-accept-btn {
|
||||||
|
border: 1px solid rgba(122, 230, 166, 0.64);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: linear-gradient(120deg, rgba(98, 210, 146, 0.95), rgba(61, 164, 108, 0.94));
|
||||||
|
color: #052113;
|
||||||
|
padding: 9px 12px;
|
||||||
|
min-height: 38px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-accept-btn:hover {
|
||||||
|
border-color: rgba(156, 246, 194, 0.88);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-accept-btn:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
color: #93a6cc;
|
||||||
|
background: linear-gradient(180deg, rgba(18, 30, 54, 0.8), rgba(11, 20, 39, 0.86));
|
||||||
|
border-color: rgba(124, 145, 189, 0.28);
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.pwa-diag-list {
|
.pwa-diag-list {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user