Первый шаг — получить разрешение пользователя на отправку ему 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-уведомлениям
- Распространенные проблемы и сообщения об ошибках