f45cc21a9c
Клиент подписывает устройство на 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` (раздел настроек: деградация, включение, отключение, ошибка загрузки).
30 lines
939 B
TypeScript
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>,
|
|
);
|