Лекция
Жизненный цикл сервис-воркера — его самая сложная часть. Если вы не знаете, что он пытается сделать и каковы преимущества, может возникнуть ощущение, что он борется с вами. Но как только вы поймете, как это работает, вы сможете предоставлять пользователям плавные и ненавязчивые обновления, сочетая лучшее из веб- и собственных шаблонов.
Это глубокое погружение, но пункты в начале каждого раздела охватывают большую часть того, что вам нужно знать.
Целью жизненного цикла является:
Последнее очень важно. Без сервис-воркеров пользователи могут загрузить одну вкладку на ваш сайт, а затем открыть другую. Это может привести к одновременной работе двух версий вашего сайта. Иногда это нормально, но если вы имеете дело с хранилищем, вы можете легко получить две вкладки с совершенно разными мнениями о том, как следует управлять общим хранилищем. Это может привести к ошибкам или, что еще хуже, к потере данных.
Внимание: пользователи активно не любят потерю данных. Это причиняет им большую печаль.
Вкратце:
install
— это первое событие, которое получает сервисный работник, и оно происходит только один раз.installEvent.waitUntil()
сигнализирует о продолжительности и успехе или неудаче вашей установки.fetch
и push
, пока он не завершит установку и не станет «активным».clients.claim()
может отменить это значение по умолчанию и получить контроль над неконтролируемыми страницами.Возьмите этот HTML:
An image will appear here in 3 seconds:
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW registered!', reg))
.catch(err => console.log('Boo!', err));
setTimeout(() => {
const img = new Image();
img.src = '/dog.svg';
document.body.appendChild(img);
}, 3000);
Он регистрирует сервис-воркера и через 3 секунды добавляет изображение собаки.
Вот его сервисный работник sw.js
:
self.addEventListener('install', event => {
console.log('V1 installing…');
// cache a cat SVG
event.waitUntil(
caches.open('static-v1').then(cache => cache.add('/cat.svg'))
);
});
self.addEventListener('activate', event => {
console.log('V1 now ready to handle fetches!');
});
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// serve the cat SVG from the cache if the request is
// same-origin and the path is '/dog.svg'
if (url.origin == location.origin && url.pathname == '/dog.svg') {
event.respondWith(caches.match('/cat.svg'));
}
});
Он кэширует изображение кошки и обслуживает его при каждом запросе /dog.svg
. Однако, если вы запустите приведенный выше пример , вы увидите собаку при первой загрузке страницы. Нажмите «Обновить», и вы увидите кота.
Примечание: кошки лучше собак. Они просто есть .
Область регистрации сервисного работника по умолчанию ./
определяется URL-адресом сценария. Это означает, что если вы зарегистрируете сервисного работника, у //example.com/foo/bar.js
него будет область действия по умолчанию //example.com/foo/
.
Мы называем страницы, рабочие и общие рабочие clients
. Ваш сервисный работник может управлять только клиентами, которые находятся в области действия. Как только клиент становится «контролируемым», его выборка проходит через сервисного работника, находящегося в области действия. Вы можете определить, контролируется ли клиент, navigator.serviceWorker.controller
который будет иметь значение null или экземпляр сервисного работника.
Ваш самый первый сервис-воркер загружает файлы, когда вы звоните .register()
. Если ваш скрипт не может загрузить, проанализировать или выдает ошибку при первоначальном выполнении, обещание регистрации отклоняется, и сервис-воркер удаляется.
Инструменты разработчика Chrome отображают ошибку в консоли и в разделе Service Worker на вкладке приложения:
Первое событие, которое получает сервисный работник, — это install
. Он запускается сразу после выполнения работника и вызывается только один раз для каждого сервис-воркера. Если вы измените сценарий своего сервис-воркера, браузер посчитает его другим сервис-воркером и получит собственное install
событие. Подробно об обновлениях я расскажу позже .
Это install
событие — ваш шанс кэшировать все, что вам нужно, прежде чем вы сможете управлять клиентами. Обещание, которое вы передаете, event.waitUntil()
сообщает браузеру, когда установка завершится и прошла ли она успешно.
Если ваше обещание отклоняется, это означает, что установка не удалась, и браузер удаляет сервис-воркера. Он никогда не будет контролировать клиентов. Это означает, что мы не можем полагаться на cat.svg
присутствие в кеше наших fetch
событий. Это зависимость.
Как только ваш сервис-воркер будет готов управлять клиентами и обрабатывать функциональные события, такие как push
и sync
, вы получите activate
событие. Но это не означает, что вызывающая страница .register()
будет контролироваться.
При первой загрузке демо-версии , хотя dog.svg
запрос запрашивается спустя долгое время после активации сервис-воркера, он не обрабатывает запрос, и вы все равно видите изображение собаки. По умолчанию используется согласованность . Если ваша страница загружается без сервисного работника, ее подресурсы тоже не будут загружаться. Если вы загрузите демо-версию второй раз (другими словами, обновите страницу), она будет под контролем. И страница, и изображение пройдут через fetch
события, и вместо этого вы увидите кошку.
Вы можете взять под контроль неконтролируемых клиентов, позвонив clients.claim()
своему сервисному работнику после его активации.
Вот вариант приведенной выше демонстрации , которая вызывает clients.claim()
свое activate
событие. Вы должны увидеть кошку в первый раз. Я говорю «должен», потому что это зависит от времени. Вы увидите кота только в том случае, если сервисный работник активируется и clients.claim()
вступит в силу до того, как изображение попытается загрузиться.
Если вы используете своего сервис-воркера для загрузки страниц иначе, чем они загружаются через сеть, clients.claim()
это может быть проблематично, поскольку ваш сервис-воркер в конечном итоге контролирует некоторых клиентов, которые загружались без него.
Примечание. Примечание. Я воспринимаю многих людей clients.claim()
как шаблонные, но сам редко делаю это. Это действительно имеет значение только при самой первой загрузке, и благодаря постепенному улучшению страница в любом случае обычно работает успешно без сервис-воркера.
Вкратце:
push
и sync
, если в течение предыдущих 24 часов не проводилась проверка обновлений..register()
только в том случае, если URL-адрес сервис-воркера изменился. Однако вам следует избегать изменения рабочего URL-адреса .importScripts()
. Вы можете переопределить это поведение по умолчанию, установив этот updateViaCache
параметр при регистрации вашего сервис-воркера.install
событие.wait
до тех пор, пока существующий рабочий процесс не будет контролировать ноль клиентов. (Обратите внимание, что клиенты перекрываются во время обновления.)self.skipWaiting()
предотвращает ожидание, то есть сервис-воркер активируется сразу после завершения установки.Допустим, мы изменили сценарий нашего сервис-воркера, чтобы он отвечал изображением лошади, а не кошки:
const expectedCaches = ['static-v2'];
self.addEventListener('install', event => {
console.log('V2 installing…');
// cache a horse SVG into a new cache, static-v2
event.waitUntil(
caches.open('static-v2').then(cache => cache.add('/horse.svg'))
);
});
self.addEventListener('activate', event => {
// delete any caches that aren't in expectedCaches
// which will get rid of static-v1
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => {
if (!expectedCaches.includes(key)) {
return caches.delete(key);
}
})
)).then(() => {
console.log('V2 now ready to handle fetches!');
})
);
});
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// serve the horse SVG from the cache if the request is
// same-origin and the path is '/dog.svg'
if (url.origin == location.origin && url.pathname == '/dog.svg') {
event.respondWith(caches.match('/horse.svg'));
}
});
Примечание: Примечание: у меня нет твердого мнения о лошадях.
Посмотрите демо-версию вышеизложенного . Вы все равно должны увидеть изображение кошки. Вот почему…
Обратите внимание, что я изменил имя кэша с static-v1
на static-v2
. Это означает, что я могу настроить новый кеш, не перезаписывая содержимое текущего, которое все еще использует старый сервис-воркер.
Эти шаблоны создают кэши для конкретной версии, похожие на ресурсы, которые родное приложение связывает со своим исполняемым файлом. У вас также могут быть кэши, не зависящие от версии, например avatars
.
После успешной установки обновленный Service Worker откладывает активацию до тех пор, пока существующий Service Worker больше не перестанет контролировать клиентов. Это состояние называется «ожиданием», и именно так браузер гарантирует, что одновременно работает только одна версия вашего сервис-воркера.
Если вы запустили обновленную демо-версию , вы все равно должны увидеть изображение кота, поскольку рабочий V2 еще не активирован. Вы можете увидеть нового сервисного работника, ожидающего на вкладке «Приложение» DevTools:
Даже если у вас открыта только одна вкладка для демо-версии, обновления страницы недостаточно, чтобы новая версия вступила во владение. Это связано с тем, как работает навигация в браузере. При навигации текущая страница не исчезает до тех пор, пока не будут получены заголовки ответа, и даже тогда текущая страница может остаться, если ответ имеет заголовок Content-Disposition
. Из-за этого перекрытия текущий сервисный работник всегда управляет клиентом во время обновления.
Чтобы получить обновление, закройте все вкладки или закройте их с помощью текущего сервис-воркера. Затем, когда вы снова перейдете к демо-версии , вы должны увидеть лошадь.
Этот шаблон похож на то, как обновляется Chrome. Обновления Chrome загружаются в фоновом режиме, но не применяются до перезапуска Chrome. В то же время вы можете продолжать использовать текущую версию без каких-либо сбоев. Тем не менее, это проблема во время разработки, но в DevTools есть способы облегчить эту задачу, о которых я расскажу позже в этой статье .
Это срабатывает, когда старый сервис-воркер уходит, и ваш новый сервис-воркер может управлять клиентами. Это идеальное время, чтобы сделать то, что вы не могли сделать, пока старый рабочий процесс все еще использовался, например миграцию баз данных и очистку кешей.
В приведенной выше демонстрации я веду список кешей, которые, как я ожидаю, там будут, и в случае, если activate
я избавлюсь от любых других, это приведет к удалению старого static-v1
кеша.
Внимание: возможно, вы не обновляетесь с предыдущей версии. Это может быть сервисный работник, у которого много старых версий.
Если вы передадите ему обещание, event.waitUntil()
он будет буферизовать функциональные события ( fetch
, и т. д.) до тех пор push
, sync
пока обещание не будет выполнено. Поэтому, когда ваше fetch
событие сработает, активация полностью завершена.
Внимание: API хранилища кэша является «исходным хранилищем» (например, localStorage и IndexedDB). Если вы запускаете несколько сайтов в одном и том же источнике (например, yourname.github.io/myapp
), будьте осторожны и не удаляйте кэши других сайтов. Чтобы избежать этого, дайте именам кешей уникальный префикс для текущего сайта, например myapp-static-v1
, и не трогайте кеши, если они не начинаются с myapp-
.
Фаза ожидания означает, что вы одновременно используете только одну версию своего сайта, но если вам не нужна эта функция, вы можете активировать новый сервис-воркер раньше, позвонив по телефону self.skipWaiting()
.
Это приведет к тому, что ваш сервис-воркер выкинет текущего активного работника и активирует себя, как только он войдет в фазу ожидания (или немедленно, если он уже находится в фазе ожидания). Это не заставляет вашего работника пропускать установку, а просто ждать.
Не имеет значения, когда вы позвоните skipWaiting()
, главное, во время ожидания или перед ним. Довольно часто его называют в install
событии:
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
// caching etc
);
});
Но вы можете вызвать его как результат postMessage()
работника службы. Например, вы хотите skipWaiting()
следить за взаимодействием с пользователем.
Вот демо, в котором используетсяskipWaiting()
. Вы должны увидеть изображение коровы, не отходя от нее. Как будто clients.claim()
это гонка, поэтому вы увидите корову только в том случае, если новый сервисный работник загрузит, установит и активирует до того, как страница попытается загрузить изображение.
Внимание: skipWaiting()
означает, что ваш новый сервис-воркер, скорее всего, контролирует страницы, загруженные в более старой версии. Это означает, что некоторые загрузки вашей страницы будут обрабатываться вашим старым сервис-воркером, но ваш новый сервис-воркер будет обрабатывать последующие выборки. Если это может что-то сломать, не используйте skipWaiting()
.
Как я упоминал ранее, браузер автоматически проверяет наличие обновлений после навигации и функциональных событий, но вы также можете запускать их вручную:
navigator.serviceWorker.register('/sw.js').then(reg => {
// sometime later…
reg.update();
});
Если вы ожидаете, что пользователь будет использовать ваш сайт в течение длительного времени без перезагрузки, вы можете звонить update()
через определенный интервал (например, каждый час).
Если вы прочитали мой пост о лучших методах кэширования , вы можете рассмотреть возможность предоставления каждой версии вашего сервис-воркера уникальный URL-адрес. Не делай этого! Обычно это плохая практика для сервисных работников: просто обновите скрипт в его текущем местоположении.
Это может привести к такой проблеме:
index.html
регистрируется sw-v1.js
как сервисный работник.sw-v1.js
кэширует и обслуживает, index.html
поэтому сначала работает в автономном режиме.index.html
, чтобы он зарегистрировал ваш новый и блестящий файл sw-v2.js
.Если вы сделаете вышеописанное, пользователь никогда не получит sw-v2.js
, поскольку sw-v1.js
обслуживает старую версию index.html
из своего кэша. Вы оказались в ситуации, когда вам необходимо обновить своего сервис-воркера, чтобы обновить своего сервис-воркера. Фу.
Однако для приведенной выше демонстрации я изменил URL-адрес сервис-воркера. Это так, ради демки можно переключаться между версиями. Я бы не стал этим заниматься в производстве.
Жизненный цикл сервис-воркера построен с учетом потребностей пользователя, но во время разработки это вызывает некоторые затруднения. К счастью, есть несколько инструментов, которые могут помочь:
Это мой любимый.
Это меняет жизненный цикл и делает его более удобным для разработчиков. Каждая навигация будет:
install
событие запускается и ваши кеши обновляются.Это означает, что вы будете получать обновления при каждой навигации (включая обновление) без необходимости дважды перезагружать или закрывать вкладку.
Если у вас есть ожидающий рабочий процесс, вы можете нажать «пропустить ожидание» в DevTools, чтобы немедленно повысить его статус «активный».
Если вы принудительно перезагрузите страницу (shift-reload), она полностью обойдет сервис-воркера. Это будет бесконтрольно. Эта функция включена в спецификацию, поэтому она работает в других браузерах, поддерживающих сервис-воркеров.
Service Worker был разработан как часть расширяемой сети . Идея состоит в том, что мы, как разработчики браузеров, признаем, что мы не лучше веб-разработчиков, чем веб-разработчики. И поэтому мы не должны предоставлять узкие высокоуровневые API, которые решают конкретную проблему с использованием шаблонов, которые нам нравятся, а вместо этого должны предоставлять вам доступ к внутренностям браузера и позволять вам делать это так, как вы хотите, и таким способом, который работает лучше всего. для ваших пользователей.
Итак, чтобы включить как можно больше шаблонов, можно наблюдать весь цикл обновления:
navigator.serviceWorker.register('/sw.js').then(reg => { reg.installing; // the installing worker, or undefined reg.waiting; // the waiting worker, or undefined reg.active; // the active worker, or undefined reg.addEventListener('updatefound', () => { // A wild service worker has appeared in reg.installing! const newWorker = reg.installing; newWorker.state;
// «installing» — событие установки произошло, но еще не завершено // "installed" - установка завершена // "activating" - событие активации сработало, но еще не завершено // "activated" - полностью активен // "redundant" - отброшен. Либо не удалось установить, либо версия была заменена более новой.newWorker.addEventListener('statechange', () => { // newWorker.state has changed }); }); }); navigator.serviceWorker.addEventListener('controllerchange', () => { // This fires when the service worker controlling this page // changes, eg a new worker has skipped waiting and become // the new active worker. });
Как видите, полезно понимать жизненный цикл сервис-воркера — и благодаря этому пониманию поведение сервис-воркера должно казаться более логичным и менее загадочным. Эти знания придадут вам больше уверенности при развертывании и обновлении сервис-воркеров.
Push Notification
На сегодняшнем занятии мы с вами узнаем, как использовать малоизвестный тандем Web Push + Service Workers (SW). Я расскажу о способе удерживать аудиториюблагодаря технологии Web Push и о том, чем это может быть полезно для редакций сайтов и прочих интернет-сервисов.
Вы принимаете оповещения на свою электронную почту: заходите в почтовый клиент и смотрите входящие письма. В данном случае это технология pull, то есть вы заходите на сайт и «тянете» с него данные, когда они вам нужны.
В случае же с push-уведомлениями ресурс проталкивает новые данные вам сам. При этом вы сразу получаете самые свежие данные, ведь в этой технологии нет определенного периода проверки данных, они приходят в режиме онлайн. Использование пушей не ограничивается получением уведомлений. Так, через push-технологию можно синхронизировать данные при обновлении.
Push-уведомления — это небольшие всплывающие окна. Они могут появляться на экране, где есть область оповещений или есть возможность вывода на экран принятых данных. Push-уведомления работают даже тогда, когда пользователь ушел с сайта. Это означает, что вы можете доставлять сообщения о новых материалах и новых событиях, чтобы привлечь внимание пользователя в ваше приложение.
Не забывайте о поддержке в браузерах.
Для написания своего собственного SW для работы с Web-push нам как всегда понадобятся:
Все, что нужно сделать, это в index.html подключить index.js, в котором будет происходить регистрация файла sw.js.
В файле server.js я укажу лишь эндпоинт (точка входа в API серверного приложения) для регистрации push-уведомлений.
// Мы используем библиотеку web-push, чтобы скрыть детали реализации связи
// между сервером приложений и службой push.
// Для получения дополнительной информации
// см. https://tools.ietf.org/html/draft-ietf-webpush-protocol
// и https://tools.ietf.org/html/draft-ietf-webpush-encryption.
var webPush = require('web-push');
// Про GCM_API_KEY вы можете подробнее узнать из
// https://developers.google.com/cloud-messaging/
webPush.setGCMAPIKey(process.env.GCM_API_KEY || null);
// В данном примере мы будем рассматривать только route'ы в express.js
module.exports = function(app, route) {
app.post(route + 'register', function(req, res) {
res.sendStatus(201);
});
app.post(route + 'sendNotification', function(req, res) {
setTimeout(function() {
// Для отправки сообщения с payload, подписка должна иметь ключи 'auth' и 'p256dh'.
webPush.sendNotification({
endpoint: req.body.endpoint,
TTL: req.body.ttl,
keys: {
p256dh: req.body.key,
auth: req.body.authSecret
}
}, req.body.payload)
.then(function() {
res.sendStatus(201);
})
.catch(function(error) {
res.sendStatus(500);
console.log(error);
});
}, req.query.delay * 1000);
});
};
В статье мы рассмотрим варианты отправки push-уведомлений и способы их применения. Давайте знакомиться с магией вне Хогвартса вместе.
Этот простейшее примеры показывают, как отправлять и получать строки, но данные могут быть извлечены из push-сообщения в различных форматах: строки, буфер ArrayBuffer, BLOB-объект в JSON.
Способ применения
Если вам просто нужны push-уведомления — этот пример для вас. Сообщение может доставлять не только текст, но и payload — обогащенные данные для приложения. Код ниже демонстрирует, как вы можете доставлять payload для вашего приложения.
Для демонстрации мы используем данные из текстового поля, которые будут отправлены на сервер и затем отображены в виде push-уведомления через SW.
var endpoint;
var key;
var authSecret;
navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
// Используем PushManager, чтобы получить подписку пользователя из пуш-сервиса.
return registration.pushManager.getSubscription()
.then(function(subscription) {
// Если подписка уже существует возвращаем ее.
if (subscription) {
return subscription;
}
// В противном случае, подписываем пользователя.
// userVisibleOnly - это флаг указывающий, что возвращенная push-подписка
// будет использоваться только для сообщений,
// эффект которых будет виден для пользователя.
return registration.pushManager.subscribe({ userVisibleOnly: true });
});
}).then(function(subscription) {
// Получаем public key для пользователя.
var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
key = rawKey
? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey)))
: '';
var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
authSecret = rawAuthSecret
? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret)))
: '';
endpoint = subscription.endpoint;
// Отправляем детали о подписке на сервер используя Fetch API
fetch('./register', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
endpoint: subscription.endpoint,
key,
authSecret,
}),
});
});
// Для демонстрации функционала.
// Данный код на "Боевых" приложениях не нужен, т.к. генерация уведомлений всегда происходит на сервере.
document.getElementById('doIt').onclick = function() {
var payload = document.getElementById('notification-payload').value;
var delay = document.getElementById('notification-delay').value;
var ttl = document.getElementById('notification-ttl').value;
fetch('./sendNotification', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
endpoint: endpoint,
payload: payload,
delay: delay,
ttl: ttl,
key: key,
authSecret: authSecret
}),
});
};
продолжение следует...
Часть 1 Жизненный цикл service worker для использования кеша и получения пуш уведомлений
Часть 2 пример Embedded fallback - Жизненный цикл service worker для использования
Комментарии
Оставить комментарий
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)