Files
glchat/web/src/main.tsx
T
grendervill f45cc21a9c feat(web): push-уведомления в браузере и раздел в настройках (7.16)
Клиент подписывает устройство на Web Push, а показывает уведомление service
worker — приложение при этом может быть и закрыто.

- `web/public/sw.js`: обработчики `push` (показывает уведомление, иначе браузер
  может отозвать подписку) и `notificationclick` (фокусирует окно и переводит
  его в комнату, иначе открывает новое). Путь из payload берётся только
  внутренний: `//host` и абсолютные адреса отбрасываются;
- `web/src/lib/webPush.ts`: поддержка API, запрос разрешения, подписка и
  отписка, преобразование ключей в base64url, слушатель перехода по клику
  (`push-navigate` → `history.pushState` без перезагрузки);
- `web/src/api/push.ts`: `GET /push/config`, `GET|POST|DELETE /push/subscriptions`;
- раздел «Push-уведомления» в настройках: тумблер «Включить на этом
  устройстве», состояние «включено», объяснение отказа браузера в разрешении и
  честная деградация — «браузер не поддерживает» или «на инстансе не настроено»;
- владелец инстанса управляет ключами, клиент только спрашивает публичный.

Тесты: `web/tests/webPush.test.ts` (поддержка API, ключи, подписка, отписка,
отказ в разрешении, переход по клику) и `web/tests/pushSettings.test.tsx`
(раздел настроек: деградация, включение, отключение, ошибка загрузки).
2026-09-26 16:15:24 +03:00

30 lines
939 B
TypeScript

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClientProvider } from '@tanstack/react-query';
import { App } from '@/App';
import { getQueryClient } from '@/lib/queryClient';
import { registerServiceWorker } from '@/lib/serviceWorker';
import { listenForPushNavigation } from '@/lib/webPush';
import { applyTheme, useUiStore } from '@/stores/ui';
import '@/i18n';
import '@/index.css';
applyTheme(useUiStore.getState().theme);
registerServiceWorker();
// Клик по push-уведомлению переводит уже открытое окно в комнату (Фаза 7).
listenForPushNavigation();
const container = document.getElementById('root');
if (!container) {
throw new Error('root container is missing');
}
createRoot(container).render(
<StrictMode>
<QueryClientProvider client={getQueryClient()}>
<App />
</QueryClientProvider>
</StrictMode>,
);