Push-уведомления
В этом модуле добавим Web Push уведомления — они будут приходить, когда пользователь офлайн.
Перед началом: Модуль 5. Инструменты: Web Push API, VAPID. Время: ~4 ч.
🔥 Инцидент. Пользователь закрыл вкладку. Приходит новое сообщение — он не знает. Push-уведомление должно прийти на телефон/десктоп. Но: Safari требует разных ключей, Firefox — других, Chrome — third-party cookies... Push API — стандартизирован, но полон подводных камней.
📖 Теория
Фокус: Web Push через VAPID. Сервер шлёт зашифрованный payload push-сервису браузера, Service Worker показывает уведомление даже при закрытой вкладке.
Web Push API и VAPID
Web Push позволяет отправлять уведомления пользователю даже когда браузер закрыт.
VAPID (Voluntary Application Server Identification) — это механизм аутентификации, который позволяет браузеру доверять вашему серверу.
Генерация VAPID ключей
bashnpm install -g web-push web-push generate-vapid-keys
Сохраните public key на фронтенде, private key — на бэкенде.
Запрос разрешения и подписка (Frontend)
typescript// utils/pushNotifications.ts export async function subscribeUserToPush(vapidPublicKey: string) { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.ready; // Запрос разрешения const permission = await Notification.requestPermission(); if (permission !== 'granted') return; // Подписка на пуши const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }); // Отправляем подписку на бэкенд await fetch('/api/subscribe', { method: 'POST', body: JSON.stringify(subscription), headers: { 'Content-Type': 'application/json' } }); } } function urlBase64ToUint8Array(base64String: string) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }
Service Worker (обработка пуша)
typescript// sw.ts self.addEventListener('push', (event) => { const data = event.data?.json() || { title: 'Новое сообщение', body: 'У вас новое сообщение в чате' }; event.waitUntil( self.registration.showNotification(data.title, { body: data.body, icon: '/icon-192.png', badge: '/badge-72.png', data: { url: '/' } // URL для перехода при клике }) ); }); // Обработка клика на уведомление self.addEventListener('notificationclick', (event) => { event.notification.close(); event.waitUntil( clients.openWindow(event.notification.data.url || '/') ); });
Отправка пуша (Backend, Go)
Используем библиотеку github.com/SherClockHolmes/webpush-go:
gofunc SendPush(subscription webpush.Subscription, message string) error { resp, err := webpush.SendNotification([]byte(message), &subscription, &webpush.Options{ Subscriber: "admin@example.com", VAPIDPublicKey: "YOUR_PUBLIC_KEY", VAPIDPrivateKey: "YOUR_PRIVATE_KEY", TTL: 30, }) if err != nil { return err } defer resp.Body.Close() return nil }
Lambda Function для отправки пушей
go// cmd/sendpush/main.go func handler(ctx context.Context, event Event) (*Response, error) { // 1. Получаем подписки из YDB для получателя subscriptions, _ := getSubscriptions(event.UserID) // 2. Отправляем пуш каждому for _, sub := range subscriptions { SendPush(sub, event.Message) } return &Response{StatusCode: 200}, nil }
В следующем модуле задеплоим наше приложение.
💱 Компромиссы (Trade-offs)
| Решение | Плюсы | Минусы |
|---|---|---|
| VAPID (public/private keys) | Стандарт аутентификации сервера у push-сервиса | Ключи надо хранить, ротация = переподписка |
| Push-subscription в БД | Сервер знает, куда слать пуш | Истекает при смене браузера/устройства |
aes128gcm payload | Сквозное шифрование, приватность | Жёсткий лимит размера (≤ 4 КБ) |
| Уведомление через SW | Работает при закрытой вкладке | Safari/Firefox/Chrome — разные quirks |
| TTL на пуш | Сообщение дойдёт после оффлайна (в срок TTL) | Старые пуши могут прийти «пачкой» |
🧩 Проверь себя
🔗 Мостик к Модулю 7
Push работает. Покроем всё тестами.