feat(chat): онлайн-доставка как в Telegram — watchdog SSE, тихий скролл, персистентный кэш сообщений, невидимое обновление SW

- SSE: серверный heartbeat теперь именованное событие ping (25с) — клиент может отличить живое соединение от мёртвого; клиентский watchdog (45с без активности → принудительный reconnect), reconnect 1с вместо 3с, polling-фолбэк 5с вместо 10с
- Скролл: на скрытой вкладке скролл не трогаем (раньше автоскролл «выбрасывал» пользователя вниз при возврате); возврат на вкладку — pin к низу если был у низа; при чтении истории плавающий индикатор «↓ N новых» (оба чата)
- Персистентный кэш последних 100 сообщений чата в localStorage → initialData: мгновенный рендер после перезагрузки страницы без «Загрузка сообщений...», свежие данные в фоне; gcTime: Infinity
- SW: reload на controllerchange только на скрытой вкладке (обновление незаметно), предыдущая версия кэша сохраняется для ленивых чанков старой страницы; кэш v15
This commit is contained in:
2026-09-21 20:29:13 +03:00
parent f8d78afd2e
commit 34838a302d
9 changed files with 336 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
const CACHE_NAME = 'iistwin-pwa-v14';
const CACHE_NAME = 'iistwin-pwa-v15';
const STATIC_ASSETS = [
'/',
'/index.html',
@@ -328,9 +328,16 @@ self.addEventListener('activate', (event) => {
console.log('[SW] Activating service worker...');
event.waitUntil(
caches.keys().then((cacheNames) => {
// Старая страница может ещё работать под новым SW (reload отложен до ухода
// вкладки в фон). Если удалить её кэш сразу, ленивые чанки старой сборки
// дадут 404 — поэтому предыдущую версию кэша сохраняем.
const versionMatch = CACHE_NAME.match(/^(.*-v)(\d+)$/);
const prevCacheName = versionMatch
? `${versionMatch[1]}${Number(versionMatch[2]) - 1}`
: null;
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.filter((name) => name !== CACHE_NAME && name !== prevCacheName)
.map((name) => {
console.log('[SW] Deleting old cache:', name);
return caches.delete(name);