# Kasper® — Real-time через Redis (SSE)

## Что добавлено

| Файл | Тип | Назначение |
|---|---|---|
| `realtime.php`   | новый  | Bus событий: `rt_emit($chat_id, $event)` |
| `events.php`     | новый  | SSE-эндпоинт, держит долгий коннект |
| `realtime.js`    | новый  | Frontend SSE-клиент |
| `api.php`        | патч   | ping/get_online через Redis + `rt_emit` после операций записи |
| `redis_config.php` | без изменений | используется как есть |

## Архитектура

```
   Клиент A                  api.php                    Клиент B
   ───────                   ───────                    ───────
   send_message ────POST───►  INSERT msg
                              rt_emit(chat_id) ────────► PUBLISH
                                                         kasper:user:B
                                                         │
                                                         ▼
                                                    events.php (SSE)
                              ◄────────────────────  получает событие
                                                    шлёт data: {...}
                                                         │
                                                         ▼
                                                    realtime.js
                                                    обновляет _mStore
                                                    рисует сообщение
```

**Каналы Redis Pub/Sub:** `kasper:user:<uid>` — персональный для каждого пользователя.

**Online-статус:** `kasper:online_set` (sorted set, score=ts, member=uid) + `kasper:online:<uid>` (TTL 35с) — уже описаны в `redis_config.php`.

## Установка

```bash
# 1. Redis
sudo apt install redis-server php-redis
sudo systemctl enable --now redis-server

# 2. Файлы (положить рядом с api.php)
realtime.php
events.php
realtime.js

# 3. api.php заменить на патченный (или применить diff)

# 4. В Kasper.html перед закрывающим </body> добавить:
<script src="realtime.js"></script>
```

## Конфиг сервера

SSE — это долгий HTTP-коннект (~10 минут на жизнь, потом клиент переподключается). Дефолтные таймауты PHP-FPM/Nginx/Apache его убьют.

### PHP-FPM (`/etc/php/8.x/fpm/pool.d/www.conf`)
```
request_terminate_timeout = 0
```
Иначе FPM убьёт SSE через 30с.

### Nginx
```nginx
location ~ \.php$ {
    fastcgi_pass            unix:/run/php/php-fpm.sock;
    fastcgi_read_timeout    700;     # > max_lifetime_s в events.php (600с)
    fastcgi_buffering       off;     # не буферизовать SSE
    proxy_buffering         off;
    proxy_cache             off;
}
```

### Apache (mod_php)
```
Timeout 700
```

## Проверка

### 1. Redis работает
```bash
redis-cli ping       # → PONG
redis-cli psubscribe 'kasper:user:*'   # подписка-наблюдатель
```

### 2. SSE отдаёт события
```bash
curl -N "http://yoursite/events.php?user_id=123"
# Должно прийти:
# event: hello
# data: {"user_id":123,"ts":...}
#
# event: ping
# data: {"ts":...}    (каждые 20с)
```

### 3. Отправка сообщения генерит push
В одной вкладке откройте `redis-cli psubscribe 'kasper:user:*'`,
в другой — Kasper, отправьте сообщение → в первой увидите событие `msg_new`.

### 4. В DevTools браузера
```
[KasperRT] connected {user_id:..., ts:...}
```
Если видите — всё работает. Если `[KasperRT] SSE недоступен, fallback на polling` — Redis не запущен или PHP-FPM режет коннект.

## Что меняется для пользователя

| Метрика | Было | Стало |
|---|---|---|
| Задержка доставки сообщения | до 800мс (активный чат) / до 4с (фон) | ~50мс |
| Запросов к API в минуту (idle) | ~75 (4 пользователя × ~75 polling-калов) | ~2 (heartbeat) |
| Нагрузка на MySQL от ping | каждые 15с UPDATE | то же + Redis SETEX (быстро) |
| Нагрузка от get_online | SELECT каждые 4с | ZRANGEBYSCORE из Redis |

## Безопасность

`events.php?user_id=X` сейчас **не аутентифицирует** — кто угодно может подписаться на чужой канал. В вашем `api.php` точно такая же модель (id передаётся в запросе и не верифицируется). Это OK для прототипа, но перед продом стоит добавить токен в users (`session_token`), и проверять его в обоих местах. Если хотите — могу добавить отдельным шагом.

## Fallback-поведение

Если Redis выключить (`systemctl stop redis-server`):
- `events.php` отдаст 503 → клиент переходит в polling-режим (4с/800мс) — как было до патча
- `api.php` продолжает работать: `rt_emit` тихо вернёт 0 без ошибок
- `get_online` использует MySQL-запрос как фолбэк
- `ping` пишет и в Redis, и в MySQL — то есть совместимо туда-обратно

То есть real-time — это **апгрейд**, а не **зависимость**: можно включать/выключать без правки кода.

## Известные ограничения

1. **Pub/Sub теряет события при отвале клиента.** Polling-safety net решает: при переоткрытии SSE клиент догонит пропущенное через `bgPollAllChats` (теперь 30с) и `pollMessages` активного чата (5с).
2. **Длинные коннекты × FPM workers.** Каждый онлайн-пользователь занимает 1 PHP-процесс. На стандартном `pm.max_children = 5` это ≤ 5 одновременных юзеров. Для прода поднимайте `pm.max_children` или переходите на ReactPHP/Swoole/Workerman.
3. **HTTP/1.1 limit на 6 коннектов к домену в браузере.** SSE занимает один. Если параллельно много fetch — могут вставать в очередь. На HTTPS/HTTP-2 проблема снимается (мультиплексирование).
