Первый шаг — получить разрешение пользователя на отправку ему push-сообщений, а затем мы сможем получить PushSubscription
.
API JavaScript для этого достаточно прост, поэтому давайте пройдемся по логической схеме.
Обнаружение функций
Сначала нам нужно проверить, действительно ли текущий браузер поддерживает push-сообщения. Мы можем проверить, поддерживается ли push, с помощью двух простых проверок.
- Проверьте наличие serviceWorker в навигаторе .
- Проверьте наличие PushManager в окне .
if (!('serviceWorker' in navigator)) {
// Service Worker isn't supported on this browser, disable or hide UI.
return;
}
if (!('PushManager' in window)) {
// Push isn't supported on this browser, disable or hide UI.
return;
}
Несмотря на то, что поддержка браузеров как для Service Worker, так и для push-сообщений быстро растет, всегда полезно предусмотреть функцию обнаружения для обоих и постепенно улучшать .
Зарегистрируйте сервисного работника
Благодаря функции обнаружения мы знаем, что поддерживаются как сервисные работники, так и Push. Следующим шагом будет «регистрация» нашего сервис-воркера.
Когда мы регистрируем сервис-воркера, мы сообщаем браузеру, где находится наш файл сервис-воркера. Файл по-прежнему представляет собой просто JavaScript, но браузер «предоставит ему доступ» к API-интерфейсам сервис-воркера, включая push. Точнее, браузер запускает файл в рабочей среде службы.
Чтобы зарегистрировать сервис-воркера, вызовите navigator.serviceWorker.register()
, передав путь к нашему файлу. Вот так:
function registerServiceWorker() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
console.log('Service worker successfully registered.');
return registration;
})
.catch(function (err) {
console.error('Unable to register service worker.', err);
});
}
Эта функция сообщает браузеру, что у нас есть рабочий файл службы и где он находится. В данном случае файл сервисного работника находится по адресу /service-worker.js
. За кулисами браузер после вызова register()
выполнит следующие шаги:
Загрузите файл сервисного работника.
Запустите JavaScript.
Если все работает правильно и ошибок нет, обещание, возвращаемое
register()
будет разрешено. Если есть какие-либо ошибки, обещание будет отклонено.
Если
register()
отклоняет запрос, дважды проверьте свой JavaScript на наличие опечаток/ошибок в Chrome DevTools.
Когда register()
разрешается, он возвращает ServiceWorkerRegistration
. Мы будем использовать эту регистрацию для доступа к API PushManager .
Совместимость браузера с API PushManager
Запрос разрешения
Мы зарегистрировали нашего сервис-воркера и готовы подписаться на пользователя. Следующий шаг — получить от пользователя разрешение на отправку ему push-сообщений.
API для получения разрешения относительно прост, недостатком является то, что API недавно изменился с обратного вызова на возврат Promise . Проблема в том, что мы не можем сказать, какая версия API реализована в текущем браузере, поэтому вам придется реализовать обе версии и обрабатывать обе.
function askPermission() {
return new Promise(function (resolve, reject) {
const permissionResult = Notification.requestPermission(function (result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
}).then(function (permissionResult) {
if (permissionResult !== 'granted') {
throw new Error("We weren't granted permission.");
}
});
}
В приведенном выше коде важным фрагментом кода является вызов Notification.requestPermission()
. Этот метод отобразит пользователю подсказку:
Как только пользователь взаимодействовал с запросом разрешения, нажав «Разрешить», «Блокировать» или просто закрыв его, мы получим результат в виде строки: 'granted'
, 'default'
или 'denied'
.
В приведенном выше примере кода обещание, возвращаемое функцией askPermission()
разрешается, если разрешение предоставлено, в противном случае мы выдаем ошибку, из-за которой обещание отклоняется.
Один крайний случай, который вам нужно обработать, — это если пользователь нажимает кнопку «Заблокировать». Если это произойдет, ваше веб-приложение не сможет снова запросить у пользователя разрешение. Им придется вручную «разблокировать» ваше приложение, изменив его статус разрешений, который скрыт на панели настроек. Тщательно подумайте, как и когда вы запрашиваете у пользователя разрешение, потому что, если он нажмет «Заблокировать», отменить это решение будет непросто.
Хорошей новостью является то, что большинство пользователей рады дать разрешение, если они знают , почему его запрашивают.
Позже мы рассмотрим, как некоторые популярные сайты запрашивают разрешение.
Подписать пользователя с помощью PushManager
Как только наш сервис-воркер зарегистрирован и мы получили разрешение, мы можем подписаться на пользователя, вызвав registration.pushManager.subscribe()
.
function subscribeUserToPush() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
),
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function (pushSubscription) {
console.log(
'Received PushSubscription: ',
JSON.stringify(pushSubscription),
);
return pushSubscription;
});
}
При вызове метода subscribe()
мы передаем объект параметров , который состоит как из обязательных, так и из необязательных параметров.
Давайте посмотрим на все варианты, которые мы можем передать.
Параметры userVisibleOnly
Когда push-сообщение впервые было добавлено в браузеры, существовала неуверенность в том, смогут ли разработчики отправлять push-сообщения и не показывать уведомление. Обычно это называется «тихим нажатием», поскольку пользователь не знает, что что-то произошло в фоновом режиме.
Опасение заключалось в том, что разработчики могли делать неприятные вещи, например, постоянно отслеживать местоположение пользователя без ведома пользователя.
Чтобы избежать этого сценария и дать авторам спецификаций время подумать о том, как лучше всего поддерживать эту функцию, был добавлен параметр userVisibleOnly
, и передача значения true
является символическим соглашением с браузером о том, что веб-приложение будет отображать уведомление при каждом нажатии. получено (т.е. нет тихого нажатия).
На данный момент вы должны передать значение true
. Если вы не укажете ключ userVisibleOnly
или не укажете false
вы получите следующую ошибку:
В настоящее время Chrome поддерживает Push API только для подписок, результатом которых будут сообщения, видимые пользователю. Вы можете указать это, вызвав вместо этого pushManager.subscribe({userVisibleOnly: true})
. См. https://goo.gl/yqv4Q4 для получения более подробной информации.
В настоящее время похоже, что сплошной тихий толчок никогда не будет реализован в Chrome. Вместо этого авторы спецификаций изучают идею бюджетного API, который позволит веб-приложениям отправлять определенное количество автоматических push-сообщений в зависимости от использования веб-приложения.
опция applicationServerKey
В предыдущем разделе мы кратко упомянули «ключи сервера приложений». «Ключи сервера приложений» используются службой push-уведомлений для идентификации приложения, подписывающего пользователя, и обеспечения того, чтобы то же приложение отправляло сообщения этому пользователю.
Ключи сервера приложений — это пара открытого и закрытого ключей, уникальная для вашего приложения. Закрытый ключ вашего приложения должен храниться в секрете, а открытый ключ можно свободно передавать.
Опция applicationServerKey
, передаваемая в вызов subscribe()
является открытым ключом приложения. Браузер передает это в службу push-уведомлений при подписке пользователя. Это означает, что служба push-уведомлений может связать открытый ключ вашего приложения с PushSubscription
пользователя.
Диаграмма ниже иллюстрирует эти шаги.
- Ваше веб-приложение загружается в браузер, и вы вызываете
subscribe()
, передавая общедоступный ключ сервера приложений. - Затем браузер отправляет сетевой запрос службе push-уведомлений, которая сгенерирует конечную точку, свяжет эту конечную точку с открытым ключом приложения и вернет конечную точку браузеру.
- Браузер добавит эту конечную точку в
PushSubscription
, который возвращается через обещаниеsubscribe()
.
Если позже вы захотите отправить push-сообщение, вам потребуется создать заголовок авторизации , который будет содержать информацию, подписанную закрытым ключом вашего сервера приложений. Когда служба push-уведомлений получает запрос на отправку push-сообщения, она может проверить этот подписанный заголовок авторизации , найдя открытый ключ, связанный с конечной точкой, получающей запрос. Если подпись действительна, служба push-уведомлений знает, что она должна быть получена с сервера приложений с соответствующим секретным ключом . По сути, это мера безопасности, которая не позволяет кому-либо отправлять сообщения пользователям приложения.
Технически, applicationServerKey
не является обязательным. Однако это требуется для самой простой реализации в Chrome, и в будущем это может потребоваться другим браузерам. В Firefox это необязательно.
Спецификация, определяющая , каким должен быть ключ сервера приложений, — это спецификация VAPID . Всякий раз, когда вы читаете что-то о «ключах сервера приложений» или «ключах VAPID» , просто помните, что это одно и то же.
Как создать ключи сервера приложений
Вы можете создать общедоступный и частный набор ключей сервера приложений, посетив web-push-codelab.glitch.me , или использовать командную строку web-push для генерации ключей, выполнив следующие действия:
$ npm install -g web-push
$ web-push generate-vapid-keys
Вам нужно создать эти ключи для вашего приложения только один раз, просто убедитесь, что закрытый ключ остается конфиденциальным. (Да, я только что это сказал.)
Разрешения и подписка()
У вызова subscribe()
есть один побочный эффект. Если у вашего веб-приложения нет разрешений на показ уведомлений во время вызова subscribe()
, браузер запросит у вас разрешения. Это полезно, если ваш пользовательский интерфейс работает с этим потоком, но если вы хотите большего контроля (а я думаю, что большинство разработчиков так и сделают), придерживайтесь API Notification.requestPermission()
, который мы использовали ранее.
Что такое PushSubscription?
Мы вызываем subscribe()
, передаем некоторые параметры и взамен получаем обещание, которое преобразуется в PushSubscription
, в результате чего получается такой код:
function subscribeUserToPush() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
),
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function (pushSubscription) {
console.log(
'Received PushSubscription: ',
JSON.stringify(pushSubscription),
);
return pushSubscription;
});
}
Объект PushSubscription
содержит всю необходимую информацию, необходимую для отправки push-сообщений этому пользователю. Если вы распечатаете содержимое с помощью JSON.stringify()
, вы увидите следующее:
{
"endpoint": "https://some.pushservice.com/something-unique",
"keys": {
"p256dh":
"BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
"auth":"FPssNDTKnInHVndSTdbKFw=="
}
}
endpoint
— это URL-адрес службы push-уведомлений. Чтобы вызвать push-сообщение, отправьте POST-запрос на этот URL-адрес.
Объект keys
содержит значения, используемые для шифрования данных сообщения, отправляемых с помощью push-сообщения (о чем мы поговорим позже в этом разделе).
Регулярная повторная подписка для предотвращения истечения срока действия
При подписке на push-уведомления вы часто получаете PushSubscription.expirationTime
со значением null
. Теоретически это означает, что срок действия подписки никогда не истекает (в отличие от случая, когда вы получаете DOMHighResTimeStamp
, который сообщает вам точный момент времени истечения срока действия подписки). На практике, однако, браузеры часто допускают истечение срока действия подписки, например, если push-уведомления не были получены в течение длительного времени или если браузер обнаруживает, что пользователь не использует приложение, у которого есть разрешение на push-уведомления. Одним из способов предотвращения этого является повторная подписка пользователя при каждом полученном уведомлении, как показано в следующем фрагменте кода. Это требует, чтобы вы отправляли уведомления достаточно часто, чтобы браузер не автоматически истекал срок действия подписки, и вам следует очень тщательно взвесить преимущества и недостатки законных потребностей в уведомлениях против непреднамеренной рассылки спама пользователю только для того, чтобы срок действия подписки не истек. В конце концов, не стоит пытаться бороться с браузером в его попытках защитить пользователя от давно забытых подписок на уведомления.
/* In the Service Worker. */
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
// Display notification or handle data
// Example: show a notification
const title = 'New Notification';
const body = 'You have new updates!';
const icon = '/images/icon.png';
const tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
// Attempt to resubscribe after receiving a notification
event.waitUntil(resubscribeToPush());
});
function resubscribeToPush() {
return self.registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.then(function() {
return self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
});
})
.then(function(subscription) {
console.log('Resubscribed to push notifications:', subscription);
// Optionally, send new subscription details to your server
})
.catch(function(error) {
console.error('Failed to resubscribe:', error);
});
}
Отправить подписку на Ваш сервер
Если у вас есть принудительная подписка, вы захотите отправить ее на свой сервер. Как это сделать, решать вам, но небольшой совет: используйте JSON.stringify()
чтобы получить все необходимые данные из объекта подписки. В качестве альтернативы вы можете собрать тот же результат вручную, например:
const subscriptionObject = {
endpoint: pushSubscription.endpoint,
keys: {
p256dh: pushSubscription.getKeys('p256dh'),
auth: pushSubscription.getKeys('auth'),
},
};
// The above is the same output as:
const subscriptionObjectToo = JSON.stringify(pushSubscription);
Отправка подписки осуществляется на веб-странице следующим образом:
function sendSubscriptionToBackEnd(subscription) {
return fetch('/api/save-subscription/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription),
})
.then(function (response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function (responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
Сервер узла получает этот запрос и сохраняет данные в базе данных для дальнейшего использования.
app.post('/api/save-subscription/', function (req, res) {
if (!isValidSaveRequest(req, res)) {
return;
}
return saveSubscriptionToDatabase(req.body)
.then(function (subscriptionId) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({data: {success: true}}));
})
.catch(function (err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(
JSON.stringify({
error: {
id: 'unable-to-save-subscription',
message:
'The subscription was received but we were unable to save it to our database.',
},
}),
);
});
});
Благодаря данным PushSubscription
на нашем сервере мы можем отправлять пользователю сообщение в любое время.
Регулярная повторная подписка для предотвращения истечения срока действия
При подписке на push-уведомления вы часто получаете PushSubscription.expirationTime
со значением null
. Теоретически это означает, что срок действия подписки никогда не истекает (в отличие от случая, когда вы получаете DOMHighResTimeStamp
, который сообщает вам точный момент времени истечения срока действия подписки). На практике, однако, браузеры часто допускают истечение срока действия подписки, например, если push-уведомления не поступали в течение длительного времени или если браузер обнаруживает, что пользователь не использует приложение, у которого есть разрешение на push-уведомления. Одним из способов предотвращения этого является повторная подписка пользователя при каждом полученном уведомлении, как показано в следующем фрагменте кода. Это требует, чтобы вы отправляли уведомления достаточно часто, чтобы браузер не автоматически истекал срок действия подписки, и вам следует очень тщательно взвесить преимущества и недостатки законных потребностей в уведомлениях против рассылки спама пользователю только для того, чтобы срок действия подписки не истек. В конце концов, не стоит пытаться бороться с браузером в его попытках защитить пользователя от давно забытых подписок на уведомления.
/* In the Service Worker. */
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
// Display notification or handle data
// Example: show a notification
const title = 'New Notification';
const body = 'You have new updates!';
const icon = '/images/icon.png';
const tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
// Attempt to resubscribe after receiving a notification
event.waitUntil(resubscribeToPush());
});
function resubscribeToPush() {
return self.registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.then(function() {
return self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
});
})
.then(function(subscription) {
console.log('Resubscribed to push notifications:', subscription);
// Optionally, send new subscription details to your server
})
.catch(function(error) {
console.error('Failed to resubscribe:', error);
});
}
Часто задаваемые вопросы
Несколько распространенных вопросов, которые люди задают на этом этапе:
Могу ли я изменить службу push-уведомлений, которую использует браузер?
Нет. Служба push выбирается браузером, и, как мы видели на примере вызова subscribe()
, браузер будет отправлять сетевые запросы к службе push, чтобы получить детали, составляющие PushSubscription .
Каждый браузер использует разные службы Push, разве у них нет разных API?
Все push-сервисы будут ожидать одного и того же API.
Этот общий API называется протоколом Web Push и описывает сетевой запрос, который ваше приложение должно будет выполнить для запуска push-сообщения.
Если я подпишу пользователя на рабочем столе, подпишутся ли они также на своем телефоне?
К сожалению, нет. Пользователь должен зарегистрироваться для push-уведомлений в каждом браузере, в котором он хочет получать сообщения. Также стоит отметить, что для этого потребуется предоставить пользователю разрешение на каждом устройстве.
Куда идти дальше
- Обзор веб-push-уведомлений
- Как работает Push
- Подписка пользователя
- Разрешение UX
- Отправка сообщений с помощью библиотек Web Push
- Веб-пуш-протокол
- Обработка push-событий
- Отображение уведомления
- Поведение уведомлений
- Общие шаблоны уведомлений
- Часто задаваемые вопросы по push-уведомлениям
- Распространенные проблемы и сообщения об ошибках