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:
@@ -84,7 +84,7 @@ export interface BufferedEvent extends Event {
|
||||
export class EventBus {
|
||||
// Индекс соединений по организации/пользователю — publishEvent не перебирает все соединения
|
||||
private index = new SseConnectionIndex<SSEConnection>();
|
||||
private heartbeatInterval = 30000; // 30 seconds
|
||||
private heartbeatInterval = 25000; // 25 seconds — должен быть заметно меньше client watchdog (45s)
|
||||
private heartbeatTimer: NodeJS.Timeout | null = null;
|
||||
private eventIdCounter = 0;
|
||||
// Буферы последних событий для восстановления после reconnect
|
||||
@@ -112,7 +112,10 @@ export class EventBus {
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
this.index.forEach((connection) => {
|
||||
try {
|
||||
connection.res.write(`: heartbeat\n\n`);
|
||||
// Именованное событие ping — в отличие от SSE-комментария (": heartbeat"),
|
||||
// оно доставляется клиенту как обычное событие и позволяет ему отличить
|
||||
// живое соединение от мёртвого (watchdog в sseManager).
|
||||
connection.res.write(`event: ping\ndata: {}\n\n`);
|
||||
const resAny = connection.res as any;
|
||||
if (typeof resAny.flush === 'function') {
|
||||
resAny.flush();
|
||||
|
||||
Reference in New Issue
Block a user