13 March 2017

Web PUSH Notifications быстро и просто

Website developmentPHPJavaScriptProgramming
Tutorial

Добрый день. В этой небольшой заметке я хочу рассказать как быстро и просто настроить push-уведомления на вашем сайте. Эта статья ни в коем случае не претендует на звание исчерпывающего руководства, но, я надеюсь, что она даст точку старта для дальнейшего изучения.


Информации по этой теме в интернете полно, но она фрагментирована, разбросана по разным ресурсам и перемешена с уведомлениями для мобильных устройств с примерами на Java, C++ и Python. Нас же, как веб-разработчиков, интересует JavaScript. В этой статье я постараюсь саккумулировать всю необходимую и полезную информацию.


Web PUSH Notifications


Я думаю, вы уже знаете что такое push-уведомления, но я всё же напишу коротко о главном.


Пользователь, заходя на сайт, вытягивает (pull) с него данные. Это удобно и безопасно, но с развитием интернет ресурсов, появилась необходимость оперативно доставлять информацию пользователям не дожидаясь пока те сами сделают запрос. Так и появилась технология принудительной доставки (push) данных с сервера клиенту.


Важно

Push-уведомления работают только если у вас на сайте есть HTTPS.
Без валидного SSL сертификата запустить не получится. Так что если у вас еще нет поддержки HTTPS, то пришло время её сделать. Рекомендую воспользоваться Let's Encrypt.
Для запуска на localhost нужно прибегать к хитростям. Я же тестировал скрипты на Github Pages.

Оглавление



Хорошие уведомления


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


Хороший пример:


  • Отправка уведомления об изменении статуса обращения пользователя в службу техподдержки;
  • Отправка уведомления об изменении статуса заказа;
  • Появление на складе товара, который ждал пользователь;
  • Ответили на комментарий пользователя к статье;
  • Новая задача в багтрекере со статусом Bug или Critical.

Плохой пример:


  • Новые поступления на склад;
  • Скидки и акции на товары;
  • Новая статья на сайте;
  • Ответили на комментарий пользователя к статье, который он написал год назад.

Плохие примеры тоже требуют уведомления, но на них не нужно реагировать оперативно. Эти уведомления можно отправить на почту. Вообще, все важные уведомления рекомендуется дублировать на почту, так-как push-уведомления могут не дойти до пользователя по разным, не зависящих от вас, причинам. Также важным фактором является актуальность события. Об этом я еще поговорю чуть позже. Рекомендую к прочтению:



Вернемся к нашим баранам. Так как же всё это работает? Для начала немного теории.


Теория


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


Для начала небольшая схема того как все это работает (анимированная схема):


Схема взаимодействия в PUSH Notifications


  1. Сервер отдает страницу пользователю;
  2. Клиент подключается к серверу сообщений, регистрируется и получает ID;
  3. Клиент отправляет полученный ID на сервер и сервер привязывает конкретного пользователя к конкретному устройству используя ID устройства;
  4. Сервер отправляет сообщение клиенту через сервер сообщений используя полученный ранее ID.

К сожалению, мне не удалось выяснить кто и как создает ID устройства и как сервер сообщений привязывается к конкретному устройству. Я использовал сервер сообщений Firebase Cloud Messaging от Google и его библиотеку. К сожалению, я не смог выяснить можно ли его заменить на свой сервер и как это сделать.


Забавный факт

Изначально для отправки сообщений использовали:
Cloud to Device Messaging

Потом его заменили на:
Google Cloud Messaging

А потом еще раз поменяли на:
Firebase Cloud Messaging

Интересно, что дальше.

Что же происходит на стороне клиента?


  • JavaScript запрашивает у пользователя разрешение на показ уведомлений;
  • Если пользователь одобрил, то подключаемся к серверу сообщений и получаем ID устройства;
  • Отправляем идентификатор на наш сервер, чтобы мы идентифицировали пользователя;
  • Инициализируем JavaScript воркер который будет работать в фоне, получать сообщения от сервера сообщений и показывать их пользователю;
  • Подключаемся к серверу сообщений и ждем новых поступлений.

Запрос прав на показ уведомлений


Заметка

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

Это все выглядит очень сложно, но на сервере все не проще.


Сложности на серверной стороне


  • Понятно, что идентификатор устройства, присылаемый пользователем, мы сохраняем в базу данных;
  • Идентификатор устройства хорошо бы привязывать к пользователю, чтобы отправлять персонализированные сообщения;
  • Стоит помнить, что пользователь у нас один, а устройств у него может быть несколько, также одним устройством могут пользоваться несколько пользователей;
  • Отправка уведомлений пользователям не самая дешевая операция и поэтому событие, инициирующее отправку уведомления, нужно ставить в очередь на отправку;
  • Только маленькие проекты с малым числом получателей могут позволить себе отправлять уведомления по событию, в течении того-же HTTP запроса;
  • Так у нас появляется система очередей на RabbitMQ, Redis и т.д.;
  • Появляются демоны/воркеры которые разбирают очередь и другие инструменты поддержки очередей;
  • Для увеличения скорости отправки можно распараллелить процесс и разнести его на несколько нод.

Практика


Наконец-то, мы перешли к самому главному. Как я уже говорил ранее, в качестве сервера сообщений мы будем использовать Firebase Cloud Messaging, поэтому мы начинаем с регистрации и создания проекта на Firebase.


Тут все просто:


  • Заходим на сайт;
  • Регистрируемся;
  • Жмём кнопку Create new project или Import Google project, если у вас уже есть проект;
  • При создании указываем название проекта и страну;
  • После создания проекта попадаем на его dashboard;
  • В меню наводим на колесико рядом с Overview и выбираем Project settings;
  • На открывшейся странице переходим во вкладку Cloud Messaging;
  • Нас интересует Server key, который будет использоваться для отправки сообщений с сервера и Sender ID который будет использоваться для получения сообщений на стороне клиента.

Можно еще покопаться в настройках и поиграться с разделением прав доступа, но, в общем-то, работа с сайтом Firebase закончена.


Приступаем к написанию клиента


Начнем с того что создадим Service Worker для получения push-уведомлений.
Создаем файл firebase-messaging-sw.js с следующим содержимым.


// firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/3.6.8/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.6.8/firebase-messaging.js');

firebase.initializeApp({
    messagingSenderId: '<SENDER_ID>'
});

const messaging = firebase.messaging();

где,


  • <SENDER_ID> — это Sender ID который мы получили после регистрации в Firebase.

Важное замечание

Файл Service Worker-а должен называться именно firebase-messaging-sw.js и обязательно должен находиться в корне проекта, то есть доступен по адресу https://example.com/firebase-messaging-sw.js. Путь к этому файлу жестко прописан в библиотеке Firebase.

Написанного кода достаточно для того чтобы показывать уведомления. О дополнительных возможностях поговорим чуть позже. Теперь добавим библиотеку Firebase и скрипт подписки в наш шаблон страницы.


<script type="text/javascript" src="//www.gstatic.com/firebasejs/3.6.8/firebase.js"></script>
<script type="text/javascript" src="/firebase_subscribe.js"></script>

Добавляем на страницу кнопку для подписки на уведомления


<button type="button" id="subscribe">Следить за изменениями</button>

Подписка на уведомления


// firebase_subscribe.js
firebase.initializeApp({
    messagingSenderId: '<SENDER_ID>'
});

// браузер поддерживает уведомления
// вообще, эту проверку должна делать библиотека Firebase, но она этого не делает
if ('Notification' in window) {
    var messaging = firebase.messaging();

    // пользователь уже разрешил получение уведомлений
    // подписываем на уведомления если ещё не подписали
    if (Notification.permission === 'granted') {
        subscribe();
    }

    // по клику, запрашиваем у пользователя разрешение на уведомления
    // и подписываем его
    $('#subscribe').on('click', function () {
        subscribe();
    });
}

function subscribe() {
    // запрашиваем разрешение на получение уведомлений
    messaging.requestPermission()
        .then(function () {
            // получаем ID устройства
            messaging.getToken()
                .then(function (currentToken) {
                    console.log(currentToken);

                    if (currentToken) {
                        sendTokenToServer(currentToken);
                    } else {
                        console.warn('Не удалось получить токен.');
                        setTokenSentToServer(false);
                    }
                })
                .catch(function (err) {
                    console.warn('При получении токена произошла ошибка.', err);
                    setTokenSentToServer(false);
                });
    })
    .catch(function (err) {
        console.warn('Не удалось получить разрешение на показ уведомлений.', err);
    });
}

// отправка ID на сервер
function sendTokenToServer(currentToken) {
    if (!isTokenSentToServer(currentToken)) {
        console.log('Отправка токена на сервер...');

        var url = ''; // адрес скрипта на сервере который сохраняет ID устройства
        $.post(url, {
            token: currentToken
        });

        setTokenSentToServer(currentToken);
    } else {
        console.log('Токен уже отправлен на сервер.');
    }
}

// используем localStorage для отметки того,
// что пользователь уже подписался на уведомления
function isTokenSentToServer(currentToken) {
    return window.localStorage.getItem('sentFirebaseMessagingToken') == currentToken;
}

function setTokenSentToServer(currentToken) {
    window.localStorage.setItem(
        'sentFirebaseMessagingToken',
        currentToken ? currentToken : ''
    );
}

Вот и все. Это весь код который требуется для получения push-уведомлений.


Отправка уведомлений с сервера


В общем виде отправка уведомления выглядит так:


POST /fcm/send HTTP/1.1
Host: fcm.googleapis.com
Authorization: key=YOUR-SERVER-KEY
Content-Type: application/json

{
  "notification": {
    "title": "Ералаш",
    "body": "Начало в 21:00",
    "icon": "https://eralash.ru.rsz.io/sites/all/themes/eralash_v5/logo.png?width=40&height=40",
    "click_action": "http://eralash.ru/"
  },
  "to": "YOUR-TOKEN-ID"
}

где,


  • YOUR-SERVER-KEY — это Server key который мы получили при регистрации в Firebase;
  • YOUR-TOKEN-ID — это ID устройства конкретного пользователя.

Все поля по порядку:


  • notification — параметры уведомления;
  • title — заголовок уведомления. Лимит 30 символов;
  • body — текст уведомление. Лимит 120 символов;
  • icon — иконка уведомления. Есть некоторые стандарты размеров иконок, но я использую 192x192. Иконки меньшего размера плохо смотрятся на мобильных устройствах;
  • click_action — URL адрес страницы на которую перейдет пользователь кликнув по уведомлению;
  • to — ID устройства получателя уведомления;
  • Полный список параметров здесь.

Уведомление

Это пример отправки одного уведомления одному получателю. Можно отправить одно уведомление сразу нескольким получателям. Вплоть до 1000 получателей за раз.


{
  "notification": {
    "title": "Ералаш",
    "body": "Начало в 21:00",
    "icon": "https://eralash.ru.rsz.io/sites/all/themes/eralash_v5/logo.png?width=192&height=192",
    "click_action": "http://eralash.ru/"
  },
  "registration_ids": [
    "YOUR-TOKEN-ID-1",
    "YOUR-TOKEN-ID-2"
    "YOUR-TOKEN-ID-3"
  ]
}

Пример ответов от сервера сообщений:


Отправка уведомления в Chrome
{
    "multicast_id": 6407277574671070000,
    "success": 1,
    "failure": 0,
    "canonical_ids": 0,
    "results": [
        {
            "message_id": "0:1489072146895227%e609af1cf9fd7ecd"
        }
    ]
}

Отправка уведомления в FireFox
{
    "multicast_id": 7867877497742898000,
    "success": 1,
    "failure": 0,
    "canonical_ids": 0,
    "results": [
        {
            "message_id": "https://updates.push.services.mozilla.com/m/gAAAAABYwWmlTCKje5OLwedhNUQr9LbOCmZ0evAF9HJBnR-v7DF2KEkZY3zsT8AbrqB6JfJO6Z6vsotLJMmiIvJs9Pt1Q9oc980BRX2IU1-jlzRLIhSVVBLo2i80kBvTMYadVAMIlSIyFkWm-qg_DfLbenlO9z1S4TGMJl0XbN5gKMUlfaIjnX2FBG4XsQjDKasiw8-1L38v"
        }
    ]
}

Ошибка отправки уведомления
{
    "multicast_id": 8165639692561075000,
    "success": 0,
    "failure": 1,
    "canonical_ids": 0,
    "results": [
        {
            "error": "InvalidRegistration"
        }
    ]
}

Полный список кодов ошибок.


Мы не привязаны к какому-то конкретному языку программирования и для простоты примера будем использовать PHP с расширением cURL. Скрипт отправки уведомления нужно запускать из консоли.


#!/usr/bin/env php
<?php

$url = 'https://fcm.googleapis.com/fcm/send';
$YOUR_API_KEY = ''; // Server key
$YOUR_TOKEN_ID = ''; // Client token id

$request_body = [
    'to' => $YOUR_TOKEN_ID,
    'notification' => [
        'title' => 'Ералаш',
        'body' => sprintf('Начало в %s.', date('H:i')),
        'icon' => 'https://eralash.ru.rsz.io/sites/all/themes/eralash_v5/logo.png?width=192&height=192',
        'click_action' => 'http://eralash.ru/',
    ],
];
$fields = json_encode($request_body);

$request_headers = [
    'Content-Type: application/json',
    'Authorization: key=' . $YOUR_API_KEY,
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);

echo $response;

messaging.onMessage


Обработчик messaging.onMessage стоит отдельного упоминания, так как он относится как раз к категории подводных камней. В примерах от Firebase я не видел примера использование этого обработчика. О нем мне рассказал FluorescentHallucinogen, за что ему отдельное спасибо, но он не упомянул о некоторых особенностях его использования.


Что же это за обработчик и как он работает. Из документации мы знаем, что этот обработчик вызывается если мы получаем push-уведомление и находимся в этот момент на странице сайта с которого отправлено уведомление (желающие использовать нативное решение могут посмотреть пример реализации). Эта функциональность очень полезна тем, что мы можем отобразить уведомление на странице сделав красивую модалку или еще что-то. У меня такой необходимости нет, потому я просто отображу стандартное уведомление.


if ('Notification' in window) {
    var messaging = firebase.messaging();

    messaging.onMessage(function(payload) {
        console.log('Message received. ', payload);
        new Notification(payload.notification.title, payload.notification);
    });

    // ...
}
// ...

Вроде все просто, но есть подводный камень. Дело все в том что на мобильных устройствах запрещено использовать конструктор Notification. И для решения этой проблемы нужно использовать ServiceWorkerRegistration.showNotification() и обработчик в этом случае будет иметь виде:


// ...
messaging.onMessage(function(payload) {
    console.log('Message received. ', payload);

    // регистрируем пустой ServiceWorker каждый раз
    navigator.serviceWorker.register('messaging-sw.js');

    // запрашиваем права на показ уведомлений если еще не получили их
    Notification.requestPermission(function(result) {
        if (result === 'granted') {
            navigator.serviceWorker.ready.then(function(registration) {
                // теперь мы можем показать уведомление
                return registration.showNotification(payload.notification.title, payload.notification);
            }).catch(function(error) {
                console.log('ServiceWorker registration failed', error);
            });
        }
    });
});
// ...

Теперь уведомления работают и на мобильных устройствах. Казалось бы уже все, но нет. Не смотря на заверения некоторых, ServiceWorker не должен быть пустым. Мы же хотим, что бы по клику пользователь переходил на нужную нам страницу. Для этого нам нужно добавить обработчик клика по уведомлению в ServiceWorker.


Сохраняем параметры уведомления для доступа свойству click_action в ServiceWorker-е.


// ...
navigator.serviceWorker.ready.then(function(registration) {
    payload.notification.data = payload.notification; // параметры уведомления
    registration.showNotification(payload.notification.title, payload.notification);
}).catch(function(error) {
    console.log('ServiceWorker registration failed', error);
});
// ...

Обрабатываем клик по уведомлению в ServiceWorker-е.


// messaging-sw.js
self.addEventListener('notificationclick', function(event) {
    const target = event.notification.data.click_action || '/';
    event.notification.close();

    // этот код должен проверять список открытых вкладок и переключатся на открытую
    // вкладку с ссылкой если такая есть, иначе открывает новую вкладку
    event.waitUntil(clients.matchAll({
        type: 'window',
        includeUncontrolled: true
    }).then(function(clientList) {
        // clientList почему-то всегда пуст!?
        for (var i = 0; i < clientList.length; i++) {
            var client = clientList[i];
            if (client.url == target && 'focus' in client) {
                return client.focus();
            }
        }

        // Открываем новое окно
        return clients.openWindow(target);
    }));
});

TTL и дополнительный контроль над уведомлением


Важным свойством для уведомления может является время его актуальности. Это зависит от ваших бизнес процессов. По умолчанию время жизни уведомлений 4 недели. Это очень много для уведомлений такого характера. Например, уведомление "Ваша любимая передача начинается через 15 минут" актуально в течении 15 минут. После этого сообщение уже не актуально и показываться не должно. За контроль над временем жизни отвечает свойство time_to_live со значением от 0 до 2419200 секунд. Подробней читать в документации. Сообщение с указанным TTL будет иметь вид:


{
  "notification": {
    "title": "Ералаш",
    "body": "Начало через 15 минут",
    "icon": "https://eralash.ru.rsz.io/sites/all/themes/eralash_v5/logo.png?width=192&height=192",
    "click_action": "http://eralash.ru/"
  },
  "time_to_live": 900,
  "to": "YOUR-TOKEN-ID"
}

Сообщение вида "Ваша любимая передача начинается через 15 минут" актуально в течении 15 минут, но уже через минуту после отправки оно станет не корректным. Потому что передача начнется не через 15 минут, а уже через 14. Контролировать такие ситуации нужно на стороне клиента.


Для этого мы поменяем отправляемое с сервера сообщение:


{
  "data": {
    "title": "Ералаш",
    "time": 1489006800,
    "icon": "https://eralash.ru.rsz.io/sites/all/themes/eralash_v5/logo.png?width=192&height=192",
    "click_action": "http://eralash.ru/"
  },
  "time_to_live": 900,
  "to": "YOUR-TOKEN-ID"
}

Обратите внимание что поле notification поменялось на data. Теперь не будет вызываться обработчик по умолчанию Firebase и нам нужно самостоятельно сделать это. Добавим в конце файла firebase-messaging-sw.js следующие строки:


// регистрируем свой обработчик уведомлений
messaging.setBackgroundMessageHandler(function(payload) {
    if (typeof payload.data.time != 'undefined') {
        var time = new Date(payload.data.time * 1000);
        var now = new Date();

        if (time < now) { // истек срок годности уведомления
            return null;
        }

        var diff = Math.round((time.getTime() - now.getTime()) / 1000);

        // показываем реальное время в уведомлении
        // будет сгенерировано сообщение вида: "Начало через 14 минут, в 21:00"
        payload.data.body = 'Начало через ' +
            Math.round(diff / 60) + ' минут, в ' + time.getHours() + ':' +
            (time.getMinutes() > 9 ? time.getMinutes() : '0' + time.getMinutes())
        ;
    }

    // Сохраяем data для получения пареметров в обработчике клика
    payload.data.data = payload.data;

    // Показываем уведомление
    return self.registration.showNotification(payload.data.title, payload.data);
});

// свой обработчик клика по уведомлению
self.addEventListener('notificationclick', function(event) {
    // извлекаем адрес перехода из параметров уведомления 
    const target = event.notification.data.click_action || '/';
    event.notification.close();

    // этот код должен проверять список открытых вкладок и переключатся на открытую
    // вкладку с ссылкой если такая есть, иначе открывает новую вкладку
    event.waitUntil(clients.matchAll({
        type: 'window',
        includeUncontrolled: true
    }).then(function(clientList) {
        // clientList почему-то всегда пуст!?
        for (var i = 0; i < clientList.length; i++) {
            var client = clientList[i];
            if (client.url == target && 'focus' in client) {
                return client.focus();
            }
        }

        // Открываем новое окно
        return clients.openWindow(target);
    }));
});

Вот таким незамысловатым образом мы получили полный контроль над уведомлением. Что самое интересное, пользователю мы показываем время уведомления в его часовом поясе. Это актуально для сервисов который работают по всему миру или регионах с широким разбросом часовых поясов как у матушки-России.


Заключение


А теперь поговорим о грустном. Не смотря на все прелести технологии, у неё есть ряд недостатков:


  1. Самая главная проблема это, как всегда, поддержка в браузерах. Полноценная поддержка есть в Chrome, Firefox и Opera последних версий. IE, Safari, Opera Mini, UC Browser, Dolphin и прочая братия остаются за бортом. Но зато работает в мобильных версиях браузеров Chrome, Firefox и Opera.
  2. Открытый сайт и работающий Service Worker не гарантируют доставку сообщения. Хотя уведомления могут дойти и при закрытом браузере.

Библиотека Firebase скрывает в себе много тайн и её исследование могло бы дать ответы на некоторые вопросы, но это уже выходит за рамки этой статьи.


Поиграться


Проект на GitHub Pages


Так как для запуска Service Worker-а нужен HTTPS, то самым простым решением было разместить проект на GitHub Pages, что я и сделал.


Проект доступен по адресу: https://peter-gribanov.github.io/serviceworker/
Исходники проекта: https://github.com/peter-gribanov/serviceworker



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


  • Зайти на страницу;
  • Нажать кнопку Register;
  • Браузер запросит разрешение на показ уведомлений;
  • Подтверждаем разрешение;
  • На странице и в консоли браузера будет напечатан ID вашего устройства;
  • Появится кнопка Delete Token для удаления существующего токена и повторной регистрации;
  • Появится форма с параметрами уведомления которое можно отправить нажав на кнопку Send;
  • Меняем параметры по усмотрению и получаем разные уведомления.

Можно отправить уведомление через любой инструмент для отправки HTTP запросов. Можно использовать сURL, я предпочитаю приложение Postman для Chrome.


Запрос такой же как и описанный ранее:


POST /fcm/send HTTP/1.1
Host: fcm.googleapis.com
Authorization: key=AAAAaGQ_q2M:APA91bGCEOduj8HM6gP24w2LEnesqM2zkL_qx2PJUSBjjeGSdJhCrDoJf_WbT7wpQZrynHlESAoZ1VHX9Nro6W_tqpJ3Aw-A292SVe_4Ho7tJQCQxSezDCoJsnqXjoaouMYIwr34vZTs
Content-Type: application/json

{
  "data": {
    "title": "Ералаш",
    "body": "Начало в 21:00",
    "icon": "https://eralash.ru.rsz.io/sites/all/themes/eralash_v5/logo.png?width=192&height=192",
    "click_action": "http://eralash.ru/"
  },
  "to": "YOUR-TOKEN-ID"
}

где,


  • YOUR-TOKEN-ID — это ID устройства который вы получили на странице приложения.

Вот и все. Получаем уведомление и радуемся жизни.


Ссылки



Updated at 2018-06-09


Обнаружились некоторые "особенности" в работе уведомлений.


Дубликаты уведомлений


Ко мне несколько раз обращались с вопросом: "Как исправить дублирующиеся уведомления?"


Проявляется эта проблема если открыть сайт отправляющий уведомления одновременно в нескольких вкладках. В этом случае Service Worker отправляет уведомление в обе вкладки и в обоих вкладках срабатывает метод messaging.onMessage. Наблюдать эту проблему можно на моем Demo проекте.


Что бы решить эту проблему, нужно в методе messaging.onMessage знать, что уведомление уже показывалось в другой вкладке. В качестве единого хранилища можно использовать localStorage, а идентифицировать уведомления по хеш сумме уведомления или присваивать уникальный id. Только стоит помнить, что localStorage не резиновый и id уже показанных уведомлений нужно подчищать через некоторое время.


Могу порекомендовать для этих целей библиотеку pamelafox/lscache.
Если у вас есть другой метод решения проблемы, напишите в комментариях.


Картинки в уведомлениях


Сегодня ко мне обратился пользователь CTterorist, заметивший, что не отображаются картинки (image) в уведомлениях.



Немножко потестировав, мне удалось разобраться. Не смотря на то, что поле image отправляется в Firebase, вместе с другими параметрами уведомления, но обратно от Firebase поле image не приходит. Решается проблема очень просто. Можно отправлять картунку в поле data, а в обработчике показа уведомления вытягивать картинку из data и вставлять ее на место в уведомление.


То есть, если вы отправите сообщение в таком виде, то Firebase потеряет картинку.


{
  "notification": {
    "title": "Bubble Nebula",
    "body": "It's found today at 21:00",
    "icon": "https://peter-gribanov.github.io/serviceworker/Bubble-Nebula.jpg",
    "image": "https://peter-gribanov.github.io/serviceworker/Bubble-Nebula_big.jpg",
    "click_action": "https://www.nasa.gov/feature/goddard/2016/hubble-sees-a-star-inflating-a-giant-bubble"
  },
  "to": "YOUR-TOKEN-ID"
}

Но если передать картинку в data, то она дойдет.


{
  "data": {
    "title": "Bubble Nebula",
    "body": "It's found today at 21:00",
    "icon": "https://peter-gribanov.github.io/serviceworker/Bubble-Nebula.jpg",
    "image": "https://peter-gribanov.github.io/serviceworker/Bubble-Nebula_big.jpg",
    "click_action": "https://www.nasa.gov/feature/goddard/2016/hubble-sees-a-star-inflating-a-giant-bubble"
  },
  "to": "YOUR-TOKEN-ID"
}

Обработчики показа уведомления такие же как в примерах выше.


messaging.onMessage(function(payload) {
    console.log('Message received. ', payload);

    // регистрируем пустой ServiceWorker каждый раз
    navigator.serviceWorker.register('firebase-messaging-sw.js');

    // запрашиваем права на показ уведомлений если еще не получили их
    Notification.requestPermission(function(result) {
        if (result === 'granted') {
            navigator.serviceWorker.ready.then(function(registration) {
                // своя логика как в примере с TTL и т.д.

                // копируем объект data
                payload.data.data = JSON.parse(JSON.stringify(payload.data));

                registration.showNotification(payload.data.title, payload.data);
            }).catch(function(error) {
                console.log('ServiceWorker registration failed', error);
            });
        }
    });
});

Аналогично в Service Worker (firebase-messaging-sw.js)


messaging.setBackgroundMessageHandler(function(payload) {
    console.log('Handling background message', payload);

    // своя логика как в примере с TTL и т.д.

    // копируем объект data
    payload.data.data = JSON.parse(JSON.stringify(payload.data));

    registration.showNotification(payload.data.title, payload.data);
});
Tags:phpjavascriptpushpush notificationsfirebasefirebase cloud messaginggcm
Hubs: Website development PHP JavaScript Programming
+34
222.1k 512
Comments 64