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.type = 'button';
|
||||
acceptBtn.className = 'primary-btn';
|
||||
acceptBtn.className = 'call-accept-btn';
|
||||
acceptBtn.textContent = 'Ответить';
|
||||
acceptBtn.addEventListener('click', async () => {
|
||||
await acceptIncomingCall();
|
||||
|
||||
@ -1,4 +1,49 @@
|
||||
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) {
|
||||
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
||||
@ -36,13 +81,16 @@ export async function initPwaPush({ authService, onLog = null }) {
|
||||
}
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register('./firebase-messaging-sw.js');
|
||||
const registration = await navigator.serviceWorker.register('./firebase-messaging-sw.js', {
|
||||
updateViaCache: 'none',
|
||||
});
|
||||
log({
|
||||
level: 'info',
|
||||
source: 'web-push',
|
||||
message: 'Service Worker зарегистрирован',
|
||||
details: { scope: registration.scope },
|
||||
});
|
||||
setupServiceWorkerAutoUpdate(registration, log);
|
||||
|
||||
const permission = await Notification.requestPermission();
|
||||
if (permission !== 'granted') {
|
||||
|
||||
@ -868,6 +868,33 @@
|
||||
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 {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user