Лекция
Жизненный цикл сервис-воркера — его самая сложная часть. Если вы не знаете, что он пытается сделать и каковы преимущества, может возникнуть ощущение, что он борется с вами. Но как только вы поймете, как это работает, вы сможете предоставлять пользователям плавные и ненавязчивые обновления, сочетая лучшее из веб- и собственных шаблонов.
Это глубокое погружение, но пункты в начале каждого раздела охватывают большую часть того, что вам нужно знать.
Целью жизненного цикла является:
Последнее очень важно. Без сервис-воркеров пользователи могут загрузить одну вкладку на ваш сайт, а затем открыть другую. Это может привести к одновременной работе двух версий вашего сайта. Иногда это нормально, но если вы имеете дело с хранилищем, вы можете легко получить две вкладки с совершенно разными мнениями о том, как следует управлять общим хранилищем. Это может привести к ошибкам или, что еще хуже, к потере данных.
Внимание: пользователи активно не любят потерю данных. Это причиняет им большую печаль.
Вкратце:
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)