Реализация уведомлений в реальном времени в веб-приложениях Javascript

Уведомления в реальном времени стали краеугольным камнем современного дизайна веб-приложений. Пользователи ожидают мгновенной обратной связи, когда происходят события: приходит новое сообщение в чате, коллега одобряет документ или запускает предупреждение сервера. Создание этой функциональности в веб-приложении JavaScript требует тщательного планирования, правильных протоколов связи и продуманного дизайна пользовательского интерфейса. Это руководство проведет вас через реализацию уведомлений в реальном времени с использованием WebSockets и Server-Sent Events (SSE), интеграцию их с серверными системами, такими как Directus, и обеспечение отполированного, готового к производству пользовательского опыта.

Почему уведомления в реальном времени имеют значение

Уведомления в режиме реального времени устраняют необходимость обновления или опроса страниц вручную, что истощает пропускную способность и ухудшает пользовательский опыт. Когда пользователь получает мгновенное оповещение, он остается вовлеченным и может быстро реагировать. Для платформ SaaS, инструментов совместной работы или панелей электронной коммерции эта непосредственность напрямую влияет на производительность и удовлетворенность. Исследования показывают, что сокращение задержки уведомлений от секунд до миллисекунд может повысить удержание пользователей более чем на 20% (]Nielsen Norman Group.

Помимо пользовательского опыта, уведомления в режиме реального времени также позволяют создавать новые шаблоны взаимодействия: потоки комментариев в реальном времени, курсоры совместного редактирования и каналы событий на стороне сервера.

WebSocket: Полнодуплексная связь

WebSockets обеспечивают постоянный двунаправленный канал между клиентом и сервером. В отличие от HTTP, соединение остается открытым после первоначального рукопожатия, позволяя любой стороне нажимать данные в любое время. Это делает WebSockets идеальным для приложений чата, живых финансовых каналов и многопользовательских игр — любой сценарий, где низкая задержка, двусторонний обмен сообщениями имеет решающее значение.

Создание WebSocket соединения в JavaScript

Нативный API браузера делает настройку соединения простой. Вы инстанцируете новый объект с URL-адресом сервера (используя схему для безопасных соединений. Затем прикрепите слушателей событий для , , и .

const socket = new WebSocket('wss://api.directus.app/websocket');

socket.onopen = () => {
 console.log('WebSocket connection established.');
 // Optionally send an authentication token
 socket.send(JSON.stringify({ type: 'auth', token: 'your-jwt' }));
};

socket.onmessage = (event) => {
 const data = JSON.parse(event.data);
 if (data.type === 'notification') {
 showToast(data.payload.message);
 }
};

socket.onerror = (err) => {
 console.error('WebSocket error:', err);
};

socket.onclose = (event) => {
 console.warn('WebSocket closed:', event.code, event.reason);
 // optional reconnect logic
};

После установления соединения вы можете отправлять сообщения формата JSON и обрабатывать ответы в обработчике . Сервер должен поддерживать протокол WebSocket; многие фреймворки Node.js (например, , ) и бэкэнды CMS, такие как Directus, предлагают встроенную или плагин-ориентированную поддержку WebSocket.

Обработка Воссоединения и Сердцебиения

Надежная система реального времени должна изящно справляться с перебоями в сети. Реализовать стратегию переподключения, которая отступает экспоненциально:

function connectWebSocket() {
 const socket = new WebSocket('wss://api.directus.app/websocket');
 let retryDelay = 1000;

 socket.onclose = () => {
 setTimeout(() => {
 console.log('Reconnecting...');
 connectWebSocket();
 }, retryDelay);
 retryDelay = Math.min(retryDelay * 2, 30000);
 };

 // ...other handlers
}

connectWebSocket();

Кроме того, отправляйте периодические пинг-тесты сердцебиения (каждые 30–60 секунд) для обнаружения устаревших соединений. Многие библиотеки WebSocket обрабатывают это автоматически, но если вы используете необработанный API, установите интервал для отправки сообщения и ожидайте ответа .

Сервер-отправленные события: более простая однонаправленная потоковая передача

События сервера-отправителя (SSE) являются легкой альтернативой, когда вам нужен только сервер для передачи данных клиентам, не требуя сообщений от клиента к серверу. SSE использует стандартный HTTP; сервер отвечает заголовком и сохраняет соединение открытым. Клиент читает поток через API .

SSE проще в реализации, чем WebSockets, работает по HTTP/2 и автоматически подключается при падении соединения.Однако он не поддерживает двунаправленную связь, поэтому лучше всего подходит для новостных лент, тикеров акций или системных оповещений.

Использование EventSource в браузере

Интерфейс браузера минимален:

const eventSource = new EventSource('/api/events?user_id=42');

eventSource.onopen = () => {
 console.log('SSE connection opened.');
};

eventSource.addEventListener('notification', (event) => {
 const data = JSON.parse(event.data);
 displayNotification(data);
});

eventSource.onerror = (err) => {
 console.error('EventSource error:', err);
 // The browser will automatically attempt to reconnect
};

function displayNotification(data) {
 // Update UI
}

На стороне сервера вы форматируете каждое событие в виде текстовых строк:

event: notification
data: {"message":"Your report is ready","severity":"info"}

Обратите внимание, что API поддерживает только запросы GET и не может отправлять пользовательские заголовки.Если вам нужно пройти токены аутентификации, добавьте их в параметры запроса (используйте HTTPS, чтобы избежать раскрытия токена).

WebSocket vs. SSE: правильный выбор

Обе технологии способны обрабатывать уведомления в режиме реального времени, но они обслуживают различные варианты использования:

Feature WebSocket SSE
Direction Bidirectional Server → Client only
Auto‑reconnect Must implement manually Built‑in
Binary data Yes (ArrayBuffer, Blob) Text only (UTF‑8)
Browser support Excellent (IE10+) Good (no IE/Edge Legacy)
Complexity Higher Lower

Если клиенту необходимо отправить команды или данные обратно на сервер (например, пометить уведомление как прочитанное), WebSocket является естественным выбором. Для простых каналов уведомлений SSE снижает накладные расходы на разработку и легче отлаживать, поскольку он использует стандартные заголовки HTTP.

Интеграция уведомлений в реальном времени с Directus

Directus — безголовая CMS, которая предоставляет REST и GraphQL API. Для добавления возможностей в реальном времени можно воспользоваться поддержкой WebSocket (введена в Directus 10.x) или настроить конечную точку SSE через специальное расширение. API WebSocket позволяет подписываться на изменения в конкретных коллекциях, что позволяет легко нажимать уведомления при создании или обновлении новой записи.

Подписка на WebSocket в Directus

Конечная точка WebSocket Directus (] принимает сообщения JSON для подписки, отписки или аутентификации. Например, для прослушивания новых сообщений в коллекции :

const ws = new WebSocket('wss://cms.example.com/websocket');

ws.onopen = () => {
 // Subscribe to changes on the notifications collection
 ws.send(JSON.stringify({
 type: 'subscribe',
 collection: 'notifications',
 query: { filter: { user_id: { _eq: currentUserId } } }
 }));
};

ws.onmessage = (event) => {
 const msg = JSON.parse(event.data);
 if (msg.type === 'subscription' && msg.event === 'create') {
 showNotification(msg.data);
 }
};

Такой подход перегружает сложность синхронизации в реальном времени на CMS, в то время как вашему приложению JavaScript нужно только обрабатывать входящие данные. Для аутентификации отправьте токен в первом сообщении WebSocket (как показано ранее).

Отображение уведомлений в UI

После того, как у вас есть данные, пользовательский опыт зависит от того, как вы их представляете. Стильные уведомления о тосте являются наиболее распространенным шаблоном: небольшое, ненавязчивое всплывающее окно, которое появляется в углу экрана и автоматически отклоняется через несколько секунд. Библиотеки, такие как Toastr , Notyf или Sonner , предоставляют готовые к использованию компоненты с доступной разметкой и анимацией.

В качестве альтернативы, создайте пользовательский компонент уведомлений. Вот минимальный пример с использованием ванильного JavaScript и CSS:

function showToast(message, type = 'info') {
 const toast = document.createElement('div');
 toast.className = `toast toast-${type}`;
 toast.textContent = message;
 toast.setAttribute('role', 'alert');
 document.getElementById('toast-container').appendChild(toast);

 setTimeout(() => toast.remove(), 3000);
}

// CSS (simplified):
.toast {
 padding: 12px 20px;
 margin-bottom: 8px;
 border-radius: 4px;
 color: #fff;
 opacity: 0.9;
 transition: opacity 0.3s;
}
.toast-info { background: #007bff; }
.toast-error { background: #dc3545; }
.toast-success { background: #28a745; }

Убедитесь, что контейнер закреплен в правом верхнем углу и что тосты стекаются вертикально. Используйте на контейнере, чтобы обеспечить считыватели экрана объявляют новые уведомления.

Предпочтения пользователей и управление уведомлениями

Не все уведомления одинаково важны. Позволяют пользователям настраивать, какие события они хотят получать и по каким каналам (в приложении, электронной почте, push). Сохранить предпочтения в бэкэнде и фильтровать события на сервере, прежде чем подталкивать их к клиенту. Например, пользователь может отключить оповещения о «новых комментариях», но сохранить оповещения о «присвоении задачи».

В вашем приложении JavaScript периодически извлекайте профиль предпочтений пользователя и соответствующим образом настраивайте фильтры подписки.При использовании Directus WebSockets вы можете при изменении предпочтений отправлять обновленный запрос подписки.

Fallback для неподдерживаемых браузеров

В то время как современные браузеры широко поддерживают WebSockets и SSE, более старые среды (например, Internet Explorer 11 для WebSockets, IE / Edge Legacy для SSE) могут потребовать полизаполнения или стратегии резервного копирования.

При опросе установите разумный интервал (например, 5-10 секунд) и верните пустой ответ, если события не ожидаются. Для уменьшения нагрузки используйте заголовки или запросы на основе метки времени.

Рассмотрение вопросов безопасности

Связи в реальном времени вводят несколько векторов безопасности, которые необходимо учитывать:

Производительность и масштабируемость

По мере роста количества одновременных соединений ваш сервер должен эффективно управлять ими.

Соединяя все вместе: полный рабочий процесс

Чтобы проиллюстрировать реальный пример, давайте объединим бэкэнд Directus с фронтендом JavaScript, который использует SSE для уведомлений.

  1. Backend (Расширение Directus): Создайте пользовательскую конечную точку на , которая проверяет токен JWT пользователя, а затем передает новые уведомления из очереди (например, Redis pub/sub или Directus hook, который пишет в коллекцию ).
  2. Frontend: При загрузке страницы аутентифицируйтесь с Directus и откройте , указывающий на .
  3. Передача: Каждое полученное уведомление выдается в качестве тоста с использованием пользовательского компонента с опциями для удаления или открытия соответствующего ресурса.
  4. Предпочтения пользователя: Когда пользователь обновляет свои настройки уведомлений через форму, отправьте POST в конечную точку Directus . Бэкэнд обновляет фильтр потока событий для этой сессии.

Эта архитектура удерживает клиента наклонным и толкает тяжелую подъемную силу к Directus и его системе крючков.

Тестирование уведомлений в реальном времени

Перед развертыванием тщательно проверьте свою реализацию:

Заключение

Уведомления в реальном времени больше не являются роскошью; они являются базовым ожиданием в современных веб-приложениях. Используя WebSockets или SSE в сочетании с бэкэндом, таким как Directus, который обеспечивает крючки в реальном времени, вы можете предоставлять мгновенные обновления без перегружающей вашей инфраструктуры. Сосредоточьтесь на настройке пользователя, изящных запасных частей и надежной безопасности для создания системы уведомлений, которая чувствует себя бесшовной и надежной.

Начните с малого: выполните простой тост за один тип события, затем постепенно расширяйтесь до нескольких каналов и предпочтений пользователей. Ключ заключается в том, чтобы повторять пользовательский опыт, сохраняя при этом базовую транспортировку эффективной и поддерживающей. С описанными здесь подходами вы будете на пути к готовой к производству функции в реальном времени.