Лекция
Это окончание невероятной информации про service worker.
...
border-box;">
// Регистрируем функцию на событие 'push'
self.addEventListener('push', function(event) {
var payload = event.data ? event.data.text() : 'Alohomora';
event.waitUntil(
// Показываем уведомление с заголовком и телом сообщения.
self.registration.showNotification('My first spell', {
body: payload,
})
);
});
Усложним предыдущий вариант и добавим спецэффектов, здесь все зависит от ваших желаний и фантазии. Нам поможет полное Notification API. API предоставляет интерфейс для использования «живых» push-уведомлений пользователю c указанием локали, шаблоном вибрации, изображения.
Способ применения
Пример схож с тем, что описан выше, но позволяет использовать более расширенное Notification API, чтобы выбирать изображение, выставлять локаль и шаблон уведомления — то есть делать уведомление уникальным.
// Ключевое отличие по сравнению с Push Payload именно в использовании
// Notitfication API в SW
self.addEventListener('push', function(event) {
var payload = event.data
// У нас все-таки волшебный мир с фантастическими тварями, поэтому
// try.. catch мы не ставим ¯\_(ツ)_/¯
? JSON.parse(event.data)
: {
name: 'Expecto patronum!',
icon: 'buck.jpg',
locale: 'en'
};
event.waitUntil(
// Показываем уведомление с заголовком и телом сообщения.
// Устанавливаем иные параметры:
// * язык
// * шаблон вибрации
// * изображение
// имеется очень много параметров, о которых вы можете узнать тут
// https://notifications.spec.whatwg.org/
self.registration.showNotification('Summoning spell', {
lang: payload.locale,
body: payload.name,
icon: payload.icon,
vibrate: [500, 100, 500],
})
);
});
Следующее команды будет демонстрировать аккумулирование и замену предыдущего примера более сильным. Используем тег для уведомления, чтобы заменить старые уведомления новыми. Позволяет показывать пользователи только актуальную информацию или сворачивать несколько уведомлений в одно.
Способ применения
Вариант подойдет для тех приложений, где имеются чаты или уведомления о новом контенте. Ниже код демонстрирует, как управлять очередью уведомлений, чтобы предыдущие уведомления можно было отбросить или объединить в одно уведомление. Это полезно, чтобы иметь fallback на случай, если мы написали чат, где можно редактировать сообщения. Клиент увидит не тонну уведомлений с исправлениями, а всего лишь одно.
var num = 1;
self.addEventListener('push', function(event) {
event.waitUntil(
// Показываем уведомление с заголовком и телом сообщения.
// Число, которое увеличивается для каждого полученного уведомления.
// Поле тега позволяет заменить старое уведомление на новое
// (уведомление с тем же тегом другого заменит его)
self.registration.showNotification('Attacking Spell', {
body: ++num > 1 ? 'Bombarda Maxima' : 'Bombarda',
tag: 'spell',
})
);
});
Когда пользователь нажмет на уведомление, сгенерированное из push-события, оно сфокусирует его на вкладке приложения или даже повторно откроет его, если оно было закрыто.
Способ применения
Ниже код для трех случаев использования доставки уведомлений в зависимости от состояния приложения.
Он может распознавать, когда вы находитесь на странице или нужно переключиться на открытую вкладку или повторно открыть вкладку.
Способ поможет вам увеличить конверсию приложения, если вы хотите вернуть пользователя . Только не перегибайте палку, пользователям может не понравиться излишнее внимание приложения.
Самые классические примеры использования:
Во всех этих случаях при клике в push клиенту откроется наше приложение или он будет сфокусирован уже на открытой вкладке.
self.addEventListener('install', function(event) {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim());
});
self.addEventListener('push', function(event) {
event.waitUntil(
// Получить список клиентов для SW
self.clients.matchAll().then(function(clientList) {
// Проверяем, есть ли хотя бы один сфокусированный клиент.
var focused = clientList.some(function(client) {
return client.focused;
});
var notificationMessage;
if (focused) {
notificationMessage = 'Imperio! You\'re still here, thanks!';
} else if (clientList.length > 0) {
notificationMessage = 'Imperio! You haven\'t closed the page, ' +
'click here to focus it!';
} else {
notificationMessage = 'Imperio! You have closed the page, ' +
'click here to re-open it!';
}
// Показывать уведомление с заголовком «Unforgiveable Curses»
// и телом в зависимости от состоянию клиентов SW
// (три разных тела:
// * 1, страница сфокусирована;
// * 2, страница по-прежнему открыта, но не сфокусирована;
// * 3, страница закрыта).
return self.registration.showNotification('Unforgiveable Curses', {
body: notificationMessage,
});
})
);
});
// Регистрируем обработчик события 'notificationclick'.
self.addEventListener('notificationclick', function(event) {
event.waitUntil(
// Получаем список клиентов SW.
self.clients.matchAll().then(function(clientList) {
// Если есть хотя бы один клиент, фокусируем его.
if (clientList.length > 0) {
return clientList[0].focus();
}
// В противном случае открываем новую страницу.
return self.clients.openWindow('our/url/page');
})
);
});
Пришло время завладеть разумом наших пользователей . пользователи называют это «телепатией» или чтением мыслей, но будем делать иначе. Давайте научимся помещать нашу информацию и заставлять привязываться к нашему приложению. Этот пример показывает, как использовать push-уведомления с управлением подпиской, позволяя пользователям подписаться на приложение и поддерживать связь с ним.
Способ применения
После того, как SW зарегистрирован, клиент проверяет, подписан ли он на сервис уведомлений. В зависимости от этого устанавливается текст кнопки.
После успешной подписки (index.js::pushManager.subscribe) клиент отправляет post-запрос на сервер приложений для регистрации подписки.
Сервер периодически отправляет уведомление с помощью библиотеки web-push на все зарегистрированные эндпоинты. Если эндпоинт больше не зарегистрирован (подписка истекла или отменена), текущая подписка удаляется из списка подписок.
После успешной отписки (index.js::pushSubscription.unsubscribe) клиент отправляет post-запрос на сервер приложений, чтобы отменить регистрацию подписки. Сервер больше не отправляет уведомления. SW также следит за событиями pushsubscriptionchange и resubscribes.
Подписка может быть отменена пользователем вне страницы в настройках браузера или UI-уведомлений. В этом случае бекенд перестанет отправлять уведомления, но фронтенд об этом не узнает. Чтобы магия работала, важно периодически проверять, активна ли регистрация в службе уведомлений.
// Для простоты будем использовать кнопку.
// На боевой версии лучше использовать события.
var subscriptionButton = document.getElementById('subscriptionButton');
// Поскольку объект подписки требуется в нескольких местах, давайте создадим метод,
// который возвращает Promise.
function getSubscription() {
return navigator.serviceWorker.ready
.then(function(registration) {
return registration.pushManager.getSubscription();
});
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(function() {
console.log('SW registered');
subscriptionButton.removeAttribute('disabled');
});
getSubscription()
.then(function(subscription) {
if (subscription) {
console.log('Already invaded', subscription.endpoint);
setUnsubscribeButton();
} else {
setSubscribeButton();
}
});
}
// Получить «registration» от SW и создать новую
// подписку с помощью `registration.pushManager.subscribe`.
// Затем зарегистрировать новую подписку, отправив POST-запрос.
function subscribe() {
navigator.serviceWorker.ready.then(function(registration) {
return registration.pushManager.subscribe({ userVisibleOnly: true });
}).then(function(subscription) {
console.log('Legilimens!', subscription.endpoint);
return fetch('register', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
endpoint: subscription.endpoint
})
});
}).then(setUnsubscribeButton);
}
// Получить существующую подписку от SW,
// отменить подписку (`subscription.unsubscribe ()`) и
// отменить регистрацию на сервере с помощью POST-запроса
// для прекращения отправки push-сообщений.
function unsubscribe() {
getSubscription().then(function(subscription) {
return subscription.unsubscribe()
.then(function() {
console.log('Unsubscribed', subscription.endpoint);
return fetch('unregister', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
endpoint: subscription.endpoint
})
});
});
}).then(setSubscribeButton);
}
// Для демонстрации (или тренировок). Изменяем текст кнопки.
function setSubscribeButton() {
subscriptionButton.onclick = subscribe;
subscriptionButton.textContent = 'Open mind!';
}
function setUnsubscribeButton() {
subscriptionButton.onclick = unsubscribe;
subscriptionButton.textContent = 'Protego!';
}
// Слушаем событие 'push'.
self.addEventListener('push', function(event) {
event.waitUntil(self.registration.showNotification('Your mind', {
body: 'Wizard invaded to your mind!'
}));
});
// Слушаем событие 'pushsubscriptionchange', которое запускается,
// когда истекает срок подписки.
// Подписываемся снова и регистрируем новую подписку на сервере,
// отправив POST-запрос.
// На боевом приложении скорее всего будет использоваться ID или token
// для идентификации пользователя.
self.addEventListener('pushsubscriptionchange', function(event) {
console.log('Spell expired');
event.waitUntil(
self.registration.pushManager.subscribe({ userVisibleOnly: true })
.then(function(subscription) {
console.log('Another invade! Legilimens!', subscription.endpoint);
return fetch('register', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
endpoint: subscription.endpoint
})
});
})
);
});
var webPush = require('web-push');
var subscriptions = [];
var pushInterval = 10;
webPush.setGCMAPIKey(process.env.GCM_API_KEY || null);
// Отправляем уведомление push-сервису.
// Удаляем подписку из общего массива `subscriptions`,
// если push-сервис отвечает на ошибку или подписка отменена или истекла.
function sendNotification(endpoint) {
webPush.sendNotification({
endpoint: endpoint
}).then(function() {
}).catch(function() {
subscriptions.splice(subscriptions.indexOf(endpoint), 1);
});
}
// В реальных условиях приложение отправляет уведовление только в случае
// возникновения события.
// Чтобы имитировать его, сервер отправляет уведомление каждые `pushInterval` секунд
// каждому подписчику
setInterval(function() {
subscriptions.forEach(sendNotification);
}, pushInterval * 1000);
function isSubscribed(endpoint) {
return (subscriptions.indexOf(endpoint) >= 0);
}
module.exports = function(app, route) {
app.post(route + 'register', function(req, res) {
var endpoint = req.body.endpoint;
if (!isSubscribed(endpoint)) {
console.log('We invaded into mind ' + endpoint);
subscriptions.push(endpoint);
}
res.type('js').send('{"success":true}');
});
// Unregister a subscription by removing it from the `subscriptions` array
app.post(route + 'unregister', function(req, res) {
var endpoint = req.body.endpoint;
if (isSubscribed(endpoint)) {
console.log('It was counterspell from ' + endpoint);
subscriptions.splice(subscriptions.indexOf(endpoint), 1);
}
res.type('js').send('{"success":true}');
});
};
Существует проблема, когда браузер по умолчанию выдает вам сообщение о том, что вы офлайн. Я называю это проблемой, так как:
![]() |
![]() |
![]() |
---|
Лучшим решением в данной ситуации было бы показать пользователю пользовательский фрагмент автономного кэша. С помощью SW мы можем подготовить заранее заготовленный ответ, говорящий о том, что приложение вне сети и его функционал на определенное время ограничен.
Решение
Нужно отдать fallback-данные, если нет доступа к ресурсам (сеть и кэш).
Данные подготавливаются заранее и кладутся как статичные ресурсы, доступные SW.
const CACHE = 'offline-fallback-v1';
// При установке воркера мы должны закешировать часть данных (статику).
self.addEventListener('install', (event) => {
event.waitUntil(
caches
.open(CACHE)
.then((cache) => cache.addAll(['/img/background']))
// `skipWaiting()` необходим, потому что мы хотим активировать SW
// и контролировать его сразу, а не после перезагрузки.
.then(() => self.skipWaiting())
);
});
self.addEventListener('activate', (event) => {
// `self.clients.claim()` позволяет SW начать перехватывать запросы с самого начала,
// это работает вместе с `skipWaiting()`, позволяя использовать `fallback` с самых первых запросов.
event.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', function(event) {
// Можете использовать любую стратегию описанную выше.
// Если она не отработает корректно, то используейте `Embedded fallback`.
event.respondWith(networkOrCache(event.request)
.catch(() => useFallback()));
});
function networkOrCache(request) {
return fetch(request)
.then((response) => response.ok ? response : fromCache(request))
.catch(() => fromCache(request));
}
// Наш Fallback вместе с нашим собсвенным Динозавриком.
const FALLBACK =
'\n' +
' App Title\n' +
' you are offline\n' +
'
\n' +
'';
// Он никогда не упадет, т.к мы всегда отдаем заранее подготовленные данные.
function useFallback() {
return Promise.resolve(new Response(FALLBACK, { headers: {
'Content-Type': 'text/html; charset=utf-8'
}}));
}
function fromCache(request) {
return caches.open(CACHE).then((cache) =>
cache.match(request).then((matching) =>
matching || Promise.reject('no-match')
));
}
Выше мы рассмотрели магические способы использования SW и Web Push для приложений.
Данный тандем таит в себе множество интересных применений.
Если вам нужно лишь иногда зазывать пользователя к себе в приложение или сообщать ему о об исправлениях или изменении статуса его заказа, то используйте Push Payload. Можно добавить немного фантазии и заиспользовать Notification API — тогда цвета и иконка вашего приложения будет видны пользователю в Rich Push.
Если же вы желаете завладеть всем вниманием пользователи установить с ним контакт — примеры Push Client и Push Subscription для вас
Жду ваших комментариев и пожеланий на следующую тему. От себя добавлю, что хотелось бы поговорить и обсудить тему работы SW + React/Redux-приложений и способы ускорения. Будет полезно?
Часть 1 Жизненный цикл service worker для использования кеша и получения пуш уведомлений
Часть 2 пример Embedded fallback - Жизненный цикл service worker для использования
Комментарии
Оставить комментарий
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)