Push уведомления

Push-уведомления позволяют веб-приложениям уведомлять пользователя о событиях в реальном времени, даже если вкладка с приложением не активна. В контексте JavaScript и фреймворка Mithril процесс их интеграции опирается на стандарты Service Worker и Push API, а также на эффективное управление состоянием в компонентах Mithril.


Подключение Service Worker

Service Worker — это фоновый скрипт, который работает независимо от страницы. Он отвечает за получение push-сообщений и их отображение. Для регистрации Service Worker используется следующий код:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(registration => {
            console.log('Service Worker зарегистрирован', registration);
        })
        .catch(error => {
            console.error('Ошибка регистрации Service Worker', error);
        });
}

Ключевой момент: Service Worker должен находиться в корневой директории или иметь корректный scope, чтобы управлять необходимой областью сайта.


Подписка на Push

Push-подписка создается с использованием PushManager. Основные шаги:

  1. Проверка поддержки Push API.
  2. Запрос разрешения у пользователя.
  3. Создание подписки с использованием публичного ключа VAPID.

Пример реализации:

async function subscribeUser() {
    if (!('PushManager' in window)) return;

    const registration = await navigator.serviceWorker.ready;
    const subscription = await registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('<VAPID_PUBLIC_KEY>')
    });

    console.log('Подписка создана:', subscription);
    return subscription;
}

function urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
    const rawData = window.atob(base64);
    return new Uint8Array([...rawData].map(char => char.charCodeAt(0)));
}

Важно: userVisibleOnly: true гарантирует, что каждое push-сообщение будет отображаться пользователю в виде уведомления.


Обработка уведомлений в Service Worker

Service Worker получает push-сообщения и отображает их с помощью Notification API. Пример sw.js:

self.addEventListener('push', event => {
    const data = event.data.json();
    const options = {
        body: data.body,
        icon: '/icon.png',
        badge: '/badge.png',
        data: { url: data.url }
    };
    event.waitUntil(
        self.registration.showNotification(data.title, options)
    );
});

self.addEventListener('notificationclick', event => {
    event.notification.close();
    event.waitUntil(
        clients.matchAll({ type: 'window' }).then(clientList => {
            for (const client of clientList) {
                if (client.url === event.notification.data.url && 'focus' in client) {
                    return client.focus();
                }
            }
            if (clients.openWindow) {
                return clients.openWindow(event.notification.data.url);
            }
        })
    );
});

Особенности:

  • event.waitUntil гарантирует завершение асинхронных операций до того, как Service Worker завершит обработку события.
  • clients.matchAll и clients.openWindow управляют открытием или фокусировкой вкладок при клике на уведомление.

Интеграция с Mithril

Mithril предоставляет легковесный подход к построению интерфейса, где состояние можно связать с push-уведомлениями через компоненты и сервисы.

Пример сервиса для работы с push-подпиской:

const PushService = {
    subscription: null,

    async init() {
        if (!('serviceWorker' in navigator)) return;
        const registration = await navigator.serviceWorker.ready;
        this.subscription = await registration.pushManager.getSubscription();
    },

    async subscribe() {
        if (!this.subscription) {
            this.subscription = await subscribeUser();
        }
    },

    async unsubscribe() {
        if (this.subscription) {
            await this.subscription.unsubscribe();
            this.subscription = null;
        }
    }
};

Компонент Mithril для управления подпиской:

const PushComponent = {
    view() {
        return m('div', [
            m('button', { onclick: () => PushService.subscribe() }, 'Подписаться на уведомления'),
            m('button', { onclick: () => PushService.unsubscribe() }, 'Отписаться')
        ]);
    }
};

Преимущество: все асинхронные операции скрыты внутри сервиса, а компонент сосредоточен исключительно на интерфейсе.


Безопасность и лучшие практики

  • Использовать HTTPS, иначе Push API работать не будет.
  • Генерировать уникальные VAPID ключи для сервера.
  • Ограничивать размер payload push-сообщений (обычно до 4 КБ).
  • Обрабатывать ошибки подписки и отказа пользователя.
  • Минимизировать нагрузку на Service Worker, избегая сложной логики в обработчиках push-событий.

Управление состоянием уведомлений

Mithril позволяет хранить состояние подписки в глобальном объекте сервиса и обновлять интерфейс через m.redraw(). Пример:

const PushStatus = {
    subscribed: false
};

async function toggleSubscription() {
    if (PushStatus.subscribed) {
        await PushService.unsubscribe();
        PushStatus.subscribed = false;
    } else {
        await PushService.subscribe();
        PushStatus.subscribed = true;
    }
    m.redraw();
}

Использование глобального состояния облегчает интеграцию push-уведомлений с другими частями приложения и упрощает тестирование.


Поддержка разных браузеров

  • Chrome, Firefox и Edge полностью поддерживают Push API.
  • Safari использует отдельный механизм через safari.pushNotification.
  • Рекомендуется проверять наличие API и использовать полифиллы или fallback уведомления через WebSocket для старых браузеров.

Заключение по архитектуре

Push-уведомления в Mithril строятся на четком разделении задач: Service Worker обрабатывает фоновые события, сервис управляет подпиской, компоненты отображают состояние. Такая структура обеспечивает масштабируемость и простоту сопровождения приложений с реальными уведомлениями.