Приветствую всех на новом этапе выпуска статей по веб разработке. Сегодня мы рассмотрим, как наконец внедрить пуш уведомления на ваш веб ресурс. Думаю многие из вас представляют что это за технология такая но тем не менее поясню. Простым языком, пуш уведомления - это небольшие сообщения которые рассылаются специализированными сервисами прямо на ваши устройства(ПК, телефоны и т.п.). Тут сразу стоит добавить, чтобы пользователь мог получать эти сообщения он должен быть на них подписан и дать разрешение на уведомления! Про преимущества и недостатки расписывать не буду, так как информации по этой теме навалом! Итак, переходим непосредственно к настройке и внедрению.
Для разработки нужно будет развернуть локальный сервер на localhost. Стек можете использовать любой, я выберу php. На будущее так же стоит понимать что технология WebPush требует обязательного использования ssl (https) протокола, это важно запомнить перед тем как будете внедрять пуши в свой проект на удаленном сервере!
Первое, что нам следует сделать - сгенерировать Vapid ключи! Сделать это можно многими способами, я выберу вариант для ленивых. Просто вбиваю рандомный email и нажимаю сгенерировать, получаю что-то подобное:
{
"subject": "mailto: <andrey@mail.ru>",
"publicKey": "BMQM0HM7r6ROQ92q5XGaXG9V-5L3KTRL2MZu900qsuv7dNzBLLTBmt2-yF1GhYpqiReRmb6tO7ha_Jy_mfq6ato",
"privateKey": "bIKijIkyR3kmoXFJ_ktaXPt8o53PL8xf0NxOarzYH8g"
}
Это нам нужно обязательно сохранить для дальнейшей работы! Сами данные будут участвовать в процессе шифрования при отправке данных на сервис WebPush.
Далее все стандартно, создаем папку с проектом в которой будет поднимать локальный сервер. В самой папке: index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Push уведомления на сайте</title>
</head>
<body>
<button class="enableNotification">Subscribe</button>
<script src="app.js"></script>
</body>
</html>
Здесь довольно все минималистично. Одинокая кнопка при клике на которую мы будем подписываться на пуш уведомления, ниже подключаем app.js файл.
(async function(){
if('serviceWorker' in navigator && 'Notification' in window){
let enableNotificationsButton = document.querySelector('.enableNotification');
enableNotificationsButton.addEventListener('click', askPermissionNotification);
function askPermissionNotification(){
Notification.requestPermission(function(result){
if(result !== 'granted'){
console.log('No notification permission granted');
}else{
configurePushSub();
}
})
}
navigator.serviceWorker.register('/sw-push.js').then(function(){
console.log('Service worker registered!');
}).catch(err => {
console.log(`Error: ${err.message}`)
});
async function configurePushSub(){
try{
let sw = await navigator.serviceWorker.ready;
let subsribeInfo = await sw.pushManager.getSubscription();
if(subsribeInfo === null){
let vapidPublicKey = 'BMQM0HM7r6ROQ92q5XGaXG9V-5L3KTRL2MZu900qsuv7dNzBLLTBmt2-yF1GhYpqiReRmb6tO7ha_Jy_mfq6ato';
let convertedVapidPublicKey = urlBase64ToUint8Array(vapidPublicKey);
let newSubscribe = await sw.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidPublicKey
});
if(newSubscribe){
let response = await fetch('/server/setSubscribe.php', {
method: 'POST',
body: JSON.stringify(newSubscribe),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
response = await response.json();
if(response.ok){
displayConfirmNotification();
}
}
}
}catch(err){
console.log(err);
}
}
function urlBase64ToUint8Array(base64String) {
let padding = '='.repeat((4 - base64String.length % 4) % 4);
let base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
let rawData = window.atob(base64);
let outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function displayConfirmNotification(){
let options = {
body: "Вы подписаны на уведомления на данном ресурсе!",
// icon: '/icon/icons-192.png',
// image: '/image/image.jpg',
dir: 'ltr',
lang: 'ru-RU',
vibrate: [100, 50, 200],
// badge: '/icon/badge.png',
tag: 'confirm-notification',
renotify: true,
actions: [
{action: 'confitm', title: 'Okay', icon: '/icon/badge.png'},
{action: 'cancel', title: 'Cancel', icon: '/icon/badge.png'}
]
};
navigator.serviceWorker.ready.then(function(sw){
sw.showNotification('Вы успешно подписались!', options)
})
}
}
})()
А вот здесь уже жарче) Давайте разбирать код!
Первое, что мы делаем - это проверяем поддержку уведомлений и serviceWorker в браузере!
if('serviceWorker' in navigator && 'Notification' in window){
Важно чтобы браузер поддерживал данные технологии!
Регистрируем файл sw-push.js это будет наш serviceWorker:navigator.serviceWorker.register('/sw-push.js').then(function(){
console.log('Service worker registered!');
}).catch(err => {
console.log(`Error: ${err.message}`)
});
Сама технология serviceWorker является ключевой в нашей задаче. Именно благодаря ей мы сможем принимать и отображать push уведомления на наших устройствах! Поэтому важно чтобы регистрация успешно произошла, заходим в консоль браузера. Если видим "Service worker registered", то все отлично. По содержимому sw-push.js поговорим чуть позже.
Далее вешаем на кнопку слушатель события клика:
let enableNotificationsButton = document.querySelector('.enableNotification');
enableNotificationsButton.addEventListener('click', askPermissionNotification);
function askPermissionNotification(){
Notification.requestPermission(function(result){
if(result !== 'granted'){
console.log('No notification permission granted');
}else{
configurePushSub();
}
})
}
Здесь при клике вызываем функцию askPermissionNotification() в которой идет запрос Notification.requestPermission() на разрешение получения уведомлений от данного веб ресурса. Нам просто принципиально важно получить разрешение для дальнейшей работы.
При нажатии на кнопку у нас может быть три сценария:
1. Уведомления разрешены - в этом случае мы вызовем функцию configurePushSub() и перейдем к процессу подписки
2. Уведомления заблокированы - в этом случае внешне ничего не будет происходить, но если мы зайдем в консоль разработчика, например google ctrl + shift +j, то увидим сообщение "No notification permission granted". Данное сообщение будет появляется каждый раз как мы будем нажимать на кнопку. Чтобы разблокировать уведомления нам нужно будет кликнуть на значок слева рядом с доменом сайта.
3. Спрашивать по умолчанию - в этом случае браузер сам запросит ваше разрешение.
После того как мы разрешили получать уведомления мы попадаем в функцию configurePushSub(), здесь:
let sw = await navigator.serviceWorker.ready;
Получаем наш зарегистрированный serviceWorker.
let subsribeInfo = await sw.pushManager.getSubscription();
if(subsribeInfo === null){
Проверяем есть ли у пользователя активные подписки на нашем сервисе. Если нет(subsribeInfo === null), то:
let vapidPublicKey = 'BMQM0HM7r6ROQ92q5XGaXG9V-5L3KTRL2MZu900qsuv7dNzBLLTBmt2-yF1GhYpqiReRmb6tO7ha_Jy_mfq6ato';
let convertedVapidPublicKey = urlBase64ToUint8Array(vapidPublicKey);
let newSubscribe = await sw.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidPublicKey
});
Генерируем новую подписку (newSubscribe) для пользователя. Здесь остановимся подробнее:
vapidPublicKey - Это наш публичный VAPID ключ publicKey, сам ключ мы конвертируем из строки base64 в Uint8Array с помощью функции urlBase64ToUint8Array().
function urlBase64ToUint8Array(base64String) {
let padding = '='.repeat((4 - base64String.length % 4) % 4);
let base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
let rawData = window.atob(base64);
let outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
Данный код ниже:
let newSubscribe = await sw.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidPublicKey
});
if(newSubscribe){
Создает подписку. Если подписка сгенерировалась успешно, то нам необходимо ее сохранить на сервере, для этого:
let response = await fetch('/server/setSubscribe.php', {
method: 'POST',
body: JSON.stringify(newSubscribe),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
response = await response.json();
if(response.ok){
displayConfirmNotification();
}
Используем ajax запрос. В моем случае, я методом POST передаю подписку в json формате на обработчик php по адресу /server/setSubscribe.php. Сам обработчик возвращает ответ в json формате. Если ответ соответствует response.ok, то мы выполняем функцию displayConfirmNotification().
По содержанию файла setSubscribe.php пройдемся чуть позже.
Функция displayConfirmNotification() не является обязательной из всего что описано в файле app.js. Я ее добавил для примера вызова первого пуша сразу после подписки.
function displayConfirmNotification(){
let options = {
body: "Вы подписаны на уведомления на данном ресурсе!",
// icon: '/icon/icons-192.png',
// image: '/image/image.jpg',
dir: 'ltr',
lang: 'ru-RU',
vibrate: [100, 50, 200],
// badge: '/icon/badge.png',
tag: 'confirm-notification',
renotify: true,
actions: [
{action: 'confitm', title: 'Okay', icon: '/icon/badge.png'},
{action: 'cancel', title: 'Cancel', icon: '/icon/badge.png'}
]
};
navigator.serviceWorker.ready.then(function(sw){
sw.showNotification('Вы успешно подписались!', options)
})
}
Следует отметить что сам пуш мы вызываем из файла serviceWorker в нашем случае sw-push.js
navigator.serviceWorker.ready.then(function(sw){
sw.showNotification('Вы успешно подписались!', options)
})
Ниже я хочу показать какие мы можем передавать параметры для генерации пуш уведомления!
Первым и обязательным является заголовок пуша.
sw.showNotification('Вы успешно подписались!', options)
В нашем случае это текст 'Вы успешно подписались!'. Все параметры которые идут в объекте options не являются обязательными.
let options = {
body: "Вы подписаны на уведомления на данном ресурсе!",
// icon: '/icon/icons-192.png',
// image: '/image/image.jpg',
dir: 'ltr',
lang: 'ru-RU',
vibrate: [100, 50, 200],
// badge: '/icon/badge.png',
tag: 'confirm-notification',
renotify: true,
actions: [
{action: 'confitm', title: 'Okay', icon: '/icon/badge.png'},
{action: 'cancel', title: 'Cancel', icon: '/icon/badge.png'}
]
};
body - это сообщение пуш уведомления.
icon(закомментировано) - Иконка пуш уведомления, формат предпочтительно выбирать png с прозрачностью, размер небольшой(значок). Учтите что все медиа файлы которые указаны в параметрах должны быть уже загружены на сервер!
image(закомментировано) - Картинка, которая будет отображаться в теле сообщения пуша, то есть рядом с текстом.
dir - Направление текста в уведомлении.
lang - Язык Уведомления.
vibrate - довольно специфический параметр. По идее задает алгоритм вибрации уведомления когда оно приходит на телефон. Но работает далеко не везде!
badge(закомментировано) - Иконка, которая отображается в статус баре устройства. На примере телефона это значок который висит вверху пока вы его не просмотрите. Здесь также рекомендую использовать png с прозрачностью.
tag - уникальный идентификатор уведомления. Задавать можно произвольно. На самом деле очень полезный параметр. Если мы к примеру отправим два уведомления с одинаковым тегом, то второе не придет если пользователь не прочитает или не удалит первое!
renotify - данный параметр необходимо использовать совместно с tag. Сам параметр указывает, следует ли подавлять вибрацию и звуковые оповещения при повторном использовании значения тега. Принимает два значения true и false.
actions - Кнопки в пуш уведомлении. В опции входят:
action - Названия действия при нажатии на кнопку. Название произвольное.
title - Название кнопки.
icon - Иконка у кнопки (работает далеко не на всех устройствах).
На этом разбор кода файла app.js закончен. Идем дальше!
Переходим к файлу sw-push.js.
self.addEventListener('push', function(event){
console.log('Push Notification received!', event);
if(event.data) {
let data = JSON.parse(event.data.text())
if(data.title && data.options){
event.waitUntil(
self.registration.showNotification(data.title, data.options)
);
}
}
});
self.addEventListener('notificationclick', function(event){
let notification = event.notification;
let action = event.action;
if(action === 'view'){
if(notification.data && notification.data.url){
actionUser('Пользователь перешел по ссылке уведомления!');
event.waitUntil(
clients.matchAll()
.then(function(cl){
let client = cl.find(function(c){
return c.visibilityState === 'visible';
})
if(client !== undefined){
client.navigate(notification.data.url);
client.focus();
}else{
clients.openWindow(notification.data.url);
}
notification.close();
})
);
}
}else if(action === 'unsubscribe'){
actionUser('Пользователь хочет отписаться!');
notification.close();
}else{
actionUser('Пользователь просто кликнул на уведомление!');
}
})
self.addEventListener('notificationclose', function(event){
actionUser('Пользователь закрыл уведомление!');
});
function actionUser(mes){
fetch('/server/info_user.php', {
method: 'POST',
body: JSON.stringify({message: mes}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}});
}
Данный код отвечает за вывод пуш уведомлений и связанных с ними событий. Итак, разбираем!
self.addEventListener('push', function(event){
console.log('Push Notification received!', event);
if(event.data) {
let data = JSON.parse(event.data.text())
if(data.title && data.options){
event.waitUntil(
self.registration.showNotification(data.title, data.options)
);
}
}
});
Это самый важный участок, здесь мы по событию push получаем данные в формате json от WebPush сервиса. Проверяем чтобы обязательно были поля title(Заголовок пуша) и options(остальные опции).
И если все хорошо, то с помощью такой конструкции:
event.waitUntil(
self.registration.showNotification(data.title, data.options)
);
Отображаем пуш уведомление на устройстве. Затем у нас идет такая конструкция:
self.addEventListener('notificationclick', function(event){
let notification = event.notification;
let action = event.action;
if(action === 'view'){
if(notification.data && notification.data.url){
actionUser('Пользователь перешел по ссылке уведомления!');
event.waitUntil(
clients.matchAll()
.then(function(cl){
let client = cl.find(function(c){
return c.visibilityState === 'visible';
})
if(client !== undefined){
client.navigate(notification.data.url);
client.focus();
}else{
clients.openWindow(notification.data.url);
}
notification.close();
})
);
}
}else if(action === 'unsubscribe'){
actionUser('Пользователь хочет отписаться!');
notification.close();
}else{
actionUser('Пользователь просто кликнул на уведомление!');
}
})
Она отвечает за событие клика notificationclick. Данное событие срабатывает всякий раз как мы производим клик по уведомлению. Если у пуша есть еще кнопки мы можем вычислить по ним клик используя данный тригер.
let notification = event.notification;
Здесь мы получаем notification - уведомление, с ним мы проделываем следующие манипуляции:
notification.close() - закрываем при клике;
notification.data - получаем мета данные, в нашем случае notification.data.url: ссылку которую хотим открыть при клике;
let action = event.action;
Значение параметра action у кнопки. Если нажатие произошло не по кнопке то возвращается пустая строка.
Функция actionUser() принимает в качестве аргумента сообщение в формате строки и передает на наш сервер.
function actionUser(mes){
fetch('/server/info_user.php', {
method: 'POST',
body: JSON.stringify({message: mes}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}});
}
Это функция чисто вспомогательная чтобы логировать действия пользователя(при нажатие определенных кнопок, клику по уведомлению, удалению уведомления). Тем самым я хочу вам показать что мы можем делать при нажатии на пуш.
На самом деле здесь вариантов не так много. Я вижу следующие возможности:
- - Отправлять данные на наш сервер с помощью ajax запроса(наша функция actionUser как раз это делает). Здесь сразу добавлю, что сервер может быть и не нашим если поддерживает кросс-доменные запросы, то есть имеет открытый api. Но следует также учитывать что это работает только при наличии интернет соединения, если пользователь будет проводить манипуляции с пушем в офлайн режиме то данные отправлены не будут!
- - Сохранять данные с помощью механизма Cache или базы IndexedDB для дальнейших манипуляций. Это может быть полезно когда пользователь тригерит уведомления в режиме офлайн.
- - Открывать ссылки, рассматриваем ниже:
event.waitUntil(
clients.matchAll()
.then(function(cl){
let client = cl.find(function(c){
return c.visibilityState === 'visible';
})
if(client !== undefined){
client.navigate(notification.data.url);
client.focus();
}else{
clients.openWindow(notification.data.url);
}
notification.close();
})
);
Эта часть отвечает за открытие ссылки(notification.data.url) которую мы передадим в мета параметре пуша.
Ну и самое последнее что остается рассмотреть в sw-push.js:
self.addEventListener('notificationclose', function(event){
actionUser('Пользователь закрыл уведомление!');
});
notificationclose - событие которое срабытывает как только пуш закрывается(удаляется).
На этом описание клиентской части закончено. Переходим к серверу!
Для работы с серверной частью нам понадобится специальная библиотека web-push. Найти ее можно по следующей ссылке. Есть версии для php, javascript, java, C#, python. Думаю можно найти и для других языков программирования.
Сама библиотека избавляет нас от необходимости разбираться в криптографии. Так как запрос передается по WebPush протоколу, он требует обязательного шифрования.
На сервере я использую php, поэтому:
composer require minishlink/web-push
Устанавливаю библиотеку через composer.
Далее необходимо написать обработчик, который сохраняет подписки на сервере.
setSubscribe.php
<?php
$newSub = file_get_contents('php://input');
$subscribes = array();
if(file_exists(__DIR__.'/subscribes.log')){
$res = file_get_contents(__DIR__.'/subscribes.log');
$subscribes = json_decode($res, true);
}
$subscribes[] = json_decode($newSub, true);
$res = file_put_contents(__DIR__.'/subscribes.log', json_encode($subscribes, true));
if($res){
echo json_encode(array('ok' => true), true);
die();
}
Здесь я не стал придумывать ничего сложного! Просто сохраняю подписки в файле 'subscribes.log' в виде массива $subscribes. По-хорошему это лучше делать конечно в базе, но для простоты восприятия думаю сойдет).
Напоминаю что запрос на сохранение подписки у нас делается в файле app.js:
let response = await fetch('/server/setSubscribe.php', {
method: 'POST',
body: JSON.stringify(newSubscribe),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
response = await response.json();
if(response.ok){
displayConfirmNotification();
}
Теперь переходим к отправке пуш уведомления на специализированный сервис! Файл send_message_push.php:
<?php
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
use Minishlink\WebPush\VAPID;
require_once(__DIR__.'/vendor/autoload.php');
$auth = [
'VAPID' => [
'subject' => 'mailto:andrey@mail.ru',
'publicKey' => 'BMQM0HM7r6ROQ92q5XGaXG9V-5L3KTRL2MZu900qsuv7dNzBLLTBmt2-yF1GhYpqiReRmb6tO7ha_Jy_mfq6ato',
'privateKey' => 'bIKijIkyR3kmoXFJ_ktaXPt8o53PL8xf0NxOarzYH8g',
],
];
$webPush = new WebPush($auth);
$payload = array(
"title" => "Заголовок пуша",
"options" => array(
"body" => "Новый текст",
"icon" => "/icon/icons-192.png",
"image" => '/image/image.jpg',
"badge" => '/icon/badge.png',
"tag" => 'confirm-notification',
"renotify" => false,
"actions" => array(
array("action" => "view", "title" => 'Посмотреть', 'icon' => '/icon/badge.png'),
array("action" => "unsubscribe", "title" => 'Отписаться', 'icon' => '/icon/badge.png'),
),
"data" => array(
"url" => "https://webfanat.com"
)
)
);
if(!file_exists(__DIR__.'/subscribes.log')){
die();
}
$answer = file_get_contents(__DIR__.'/subscribes.log');
if($answer){
$json = json_decode($answer, true);
if($json && count($json) > 0){
foreach($json as $item){
if(isset($item['endpoint']) && isset($item['keys']['auth']) && isset($item['keys']['p256dh'])){
$notification = [
'subscription' => Subscription::create([
'endpoint' => $item['endpoint'],
'publicKey' => $item['keys']['p256dh'],
'authToken' => $item['keys']['auth'],
]),
'payload' => json_encode($payload, true),
];
try{
$res = $webPush->sendOneNotification(
$notification['subscription'],
$notification['payload']
);
$convert_json_str = json_encode($res, true);
$json_result = json_decode($convert_json_str, true);
if(isset($json_result['success']) && !$json_result['success']){
// Подписка деактивирована!
}
}catch(Exception $e){
var_dump($e);
}
}
}
}
}
В целом здесь ничего сложного нет. Разбираем:
<?php
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
use Minishlink\WebPush\VAPID;
require_once(__DIR__.'/vendor/autoload.php');
$auth = [
'VAPID' => [
'subject' => 'mailto:andrey@mail.ru',
'publicKey' => 'BMQM0HM7r6ROQ92q5XGaXG9V-5L3KTRL2MZu900qsuv7dNzBLLTBmt2-yF1GhYpqiReRmb6tO7ha_Jy_mfq6ato',
'privateKey' => 'bIKijIkyR3kmoXFJ_ktaXPt8o53PL8xf0NxOarzYH8g',
],
];
$webPush = new WebPush($auth);
Подключаем библиотеку и передаем данные VAPID которые мы сгенерировали. Эти данные будут участвовать в шифровании.
Далее:
$payload = array(
"title" => "Заголовок пуша",
"options" => array(
"body" => "Новый текст",
"icon" => "/icon/icons-192.png",
"image" => '/image/image.jpg',
"badge" => '/icon/badge.png',
"tag" => 'confirm-notification',
"renotify" => false,
"actions" => array(
array("action" => "view", "title" => 'Посмотреть', 'icon' => '/icon/badge.png'),
array("action" => "unsubscribe", "title" => 'Отписаться', 'icon' => '/icon/badge.png'),
),
"data" => array(
"url" => "https://webfanat.com"
)
)
);
В $payload хранится конфигурация пуш уведомления. А именно:
- title - Заголовок пуша;
- options - Другие опции уведомления. Мы их уже разбирали в файле app.js. Обработка кнопок Посмотреть(view) и Отписаться(unsubscribe) уже описана в файле sw-push.js, думаю разберетесь. Отдельно стоит рассмотреть дополнительный параметр data;
В параметре data хранятся метаданные, то есть произвольные данные которые мы можем отправлять вместе с пушем. В нашем случае мы отправляем ключ "url" со значением ссылки "https://webfanat.com". Эту ссылку то мы и получаем в serviceWorker (sw-push.js) по ключу url (notification.data.url) чтобы ее открыть.
После у нас идет проверка наличия подписок и их получение:
if(!file_exists(__DIR__.'/subscribes.log')){
die();
}
$answer = file_get_contents(__DIR__.'/subscribes.log');
if($answer){
$json = json_decode($answer, true);
if($json && count($json) > 0){
Напоминаю что подписки мы берем из файла subscribes.log который генерируем и обновляем при получении новых подписок (setSubscribe.php).
Как только убедились что подписки есть и их получили:
foreach($json as $item){
if(isset($item['endpoint']) && isset($item['keys']['auth']) && isset($item['keys']['p256dh'])){
$notification = [
'subscription' => Subscription::create([
'endpoint' => $item['endpoint'],
'publicKey' => $item['keys']['p256dh'],
'authToken' => $item['keys']['auth'],
]),
'payload' => json_encode($payload, true),
];
try{
$res = $webPush->sendOneNotification(
$notification['subscription'],
$notification['payload']
);
$convert_json_str = json_encode($res, true);
$json_result = json_decode($convert_json_str, true);
if(isset($json_result['success']) && !$json_result['success']){
// Подписка деактивирована!
}
}catch(Exception $e){
var_dump($e);
}
}
}
С помощью цикла foreach пробегаемся по ним. При этом проверяя у подписки наличие ключей 'endpoint', 'keys/auth', 'keys/p256dh'. Важно чтобы все эти данные были!
endpoint - это адрес сервиса WebPush. Именно он будет рассылать пуш уведомление по подпискам.
publicKey и authToken - ключи авторизации, также участвуют в шифровании.
WebPush сервис также следить за тем чтобы push уведомление было доставлено подписчику. К примеру в офлайн режиме уведомление не дойдет. В этом случае сервис переотправить пуш когда пользователь установить соединение с интернетом.
В поле 'payload' мы передаем параметры уведомления в json формате. Финальный аккорд:
try{
$res = $webPush->sendOneNotification(
$notification['subscription'],
$notification['payload']
);
$convert_json_str = json_encode($res, true);
$json_result = json_decode($convert_json_str, true);
if(isset($json_result['success']) && !$json_result['success']){
// Подписка деактивирована!
}
}catch(Exception $e){
var_dump($e);
}
Попытка отправки уведомления. Если все пройдет хорошо, то подписчик получит наше уведомление. В случае ошибок мы перехватим исключение и выведем дополнительную информацию.
Также есть еще вариант когда пользователь может случайно или вручную затереть свою подписку. В этом случае подписка станет неактивной, то есть мы не сможем отправлять на нее уведомления.
Чтобы не хранить деактивированную подписку у нас на сервере мы можем ее удалить, для этого:
$convert_json_str = json_encode($res, true);
$json_result = json_decode($convert_json_str, true);
if(isset($json_result['success']) && !$json_result['success']){
// Подписка деактивирована!
}
Проверяем ответ $res который возвращает метод sendOneNotification(). Конвертируем его сначала в json строку, а затем в json объект в виде ассоциативного массива. Это нужно для удобства работы с данными ответа! И если в ответе есть ключ 'success' и он равен 'false' то это значит подписка деактивирована. Следовательно, ее лучше удалить так как отправка на нее уведомления по сути бессмысленна! Процесс реализации удаления оставляю за вами).
На этом статью можно было бы закончить, но у нас еще остается файл info_user.php который мы не разобрали.
<?php
$data = file_get_contents('php://input');
$data = json_decode($data, true);
if(isset($data['message']) && trim($data['message']) !== ''){
file_put_contents(__DIR__.'/actionUser.log', trim($data['message'])."\r\n\r\n", FILE_APPEND | LOCK_EX);
}
Здесь мы просто получаем некое сообщение(лог) и записываем в файл actionUser.log.
Напоминаю что само сообщение передается ajax запросом из нашего serviceWorker:
function actionUser(mes){
fetch('/server/info_user.php', {
method: 'POST',
body: JSON.stringify({message: mes}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}});
}
Теперь мы сможем узнать какое событие(нажатие определенной кнопки, клик по уведомлению или закрытие) произошло у подписчика.
На этом статья подошла к концу. Надеюсь я смог донести до вас как внедрить и начать использовать пуш уведомления на вашем сайте. Уверен что это далеко не все что можно сделать используя данную технологию. Но основы работы постарался расписать как мог).
Кстати вы можете подписаться на . Обещаю не спамить!) Сейчас после долгого перерыва буду публиковать новые посты. Материала за все время накопилось много. Так что !
Ну а я же с вами прощаюсь. Всем удачи и здоровья в этом году! Пока, пока.