сервис-воркера

Максим Сальников

@webmaxru

  Укрощение

Как создавать быстрые, автономные, удобные веб-приложения

С удовольствием

Максим Сальников

@webmaxru

  • Google Developer Expert в Angular

  • Организатор Mobile Oslo / Angular Oslo / PWA Oslo митапов

  • Организатор конференций Mobile Era и ngVikings

Full-stack разработчик "приложений из будущего" в ForgeRock

Сервис-воркер

Что это за зверь?

Прогрессивные веб-приложения

... используют последние веб-технологии

... сочетают функции большинства современных браузеров с преимуществами мобильных платформ

Десять характеристик

  • Работает во всех браузерах

  • Легко находится

  • Можно дать на него ссылку

  • Похоже на приложение

  • Подстраивается под устройство

  • Может работать автономно

  • Может напоминать о себе

  • Устанавливается

  • Всегда последней версии

  • Безопасное

Service Worker API

Знакомимся с инструментарием

  • Service Worker API

  • Cache API

  • IndexedDB

  • Fetch

  • Clients API

  • Broadcast Channel API

  • Push API

  • Notifications API

  • Local Storage

  • Session Storage

  • XMLHttpRequest

  • DOM

Сервис-воркер

  • Дает возможность использовать некоторые функции приложения без доступа в сеть

  • При доступе в сеть увеличивает быстродействие за счет экономии на некоторых запросах

Не только сеть

  • Получение push-сообщений и показ уведомлений

  • Организация каналов связи между "клиентами"

  • Выполнение задач по расписанию

  • ...

Логически

Физически

JS

-файл

Приложение

Сервис-воркер

Непростой жизненный цикл

'install'

Загрузка

Установка

Активация

Удален

'activate'

Ожидание

Активен

В разработке

За флагом

Можно ли использовать?

Прогрессивные веб-приложения

стремящийся к прогрессу, проникнутый передовыми идеями

постепенно возрастающий, увеличивающийся

Прогрессивное улучшение

Определяйте наличие функциональности

#1

Регистрация

if ('serviceWorker' in navigator) {

    // Регистрируем сервис-воркер

}

Фоновая синхронизация

if ('SyncManager' in window) {

    // Реализуем функциональность для оффлайн-режима

}

Подписка на push

if (!('PushManager' in window)) {

    // Прячем интерфейс подписки на push-уведомления

}

Действия в уведомлениях

if ('actions' in Notification.prototype) {

  // Можем использовать кнопки с разными действиями

}

?

Правильный момент для регистрации

Чем позже, тем лучше

#2

if ('serviceWorker' in navigator) {

        navigator.serviceWorker.register('/sw-workbox.js')
            .then(...);

}
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw-workbox.js')
            .then(...);
    )};
}
platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .then(() => {

    // Регистрация сервис-воркера

  });

main.ts

Если что-то пошло не так

Реализуем kill switch

#3

Ни одно животное

не пострадало

  • Разместить исправленный сервис-воркер (или пустышку)

  • Удостовериться, что сервис-воркер не берется из кеша HTTP

  • ... или разрегистрировать наш сервис-воркер

План спасения

No-op

self.addEventListener('install', () => {
  self.skipWaiting();
});
self.addEventListener('activate', () => {
  self.clients.matchAll({type: 'window'}).then(tabs => {
    tabs.forEach(tab => {
      tab.navigate(tab.url);
    });
  });
});

Крайняя мера для UX

Обновление и обход кеша HTTP

Cache-Control: max-age=0

На стороне сервера

Сервис-воркер

Спецификация обновится

  • Побайтное сравнение, нужно использовать версионность

  • Не работает для скриптов, импортированных с помощью importScripts()

Отмена регистрации

navigator.serviceWorker.getRegistrations()
    .then((registrations) => {
        for(let registration of registrations) {
            registration.unregister()
        }
    })

index.html

Предварительное кеширование

Знайте ваши ресурсы и не оставляйте мусор

#4

const appShellFilesToCache = [
  ...
  './non-existing.html'
]

sw-handmade.js

self.addEventListener('install', (event) => {

  event.waitUntil(
    caches.open(cacheName).then((cache) => {
      return cache.addAll(appShellFilesToCache)
    })
  )

})
  • Ошибки HTTP

  • Время работы сервис-воркера

  • Ошибки хранилищ

Chrome <6% своб. пространства
Firefox <10% своб. пространства
Safari <50MB
IE10 <250MB
Edge Зависит от размера диска

Объем хранилищ ограничен

  • Quota Management API

  • Storage Quota Estimate API

 

Кеширование ресурсов с других адресов

Приготовьтесь

к непрозрачности

#5

3 варианта

  • Добавить заголовки CORS на удаленной стороне

  • Обрабатывать opaque ответы

  • Использовать foreign fetch

Ограничения opaque ответов

  • Работают для ограниченного набора элементов: <script>, <link rel="stylesheet">, <img>, <video>, <audio>, <object>, <embed>, <iframe>

  • Свойство status всегда равно нулю и не зависит от того, успешен запрос или нет

  • Методы Cache API add()/addAll() сработают аварийно, если статус хотя бы одного из ответов не находится в диапазоне 2XX

const appShellFilesToCache = [
  ...
  'https://workboxjs.org/offline-ga.min.svg'
]

sw-handmade.js

self.addEventListener('install', (event) => {

  event.waitUntil(
    caches.open(cacheName).then((cache) => {
      return cache.addAll(appShellFilesToCache)
    })
  )

})

Решение для no-cors

const noCorsRequest =
    new Request('https://workboxjs.org/offline-ga.svg', {
        mode: 'no-cors'
    });

fetch(noCorsRequest)
    .then(response => cache.put(noCorsRequest, response));

Перенаправления

Следовать или нет?

#6

self.addEventListener('install', (event) => {

  event.waitUntil(
    caches.open(cacheName).then((cache) => {
      return cache.addAll(appShellFilesToCache)
    })
  )

})
const appShellFilesToCache = [
    ...
    './assets/redirect/redirectfrom.html'
]

sw-handmade.js

app.get('/assets/redirect/redirectfrom.html', (req, res) => {
    res.redirect(301, '/assets/redirect/redirectto.html')
})

server/index.js

Возвратить ошибку сети, если хотя бы одно из следующих утверждений верно:

  • ...

  • Redirect mode запроса не "follow", и список адресов ответа содержит больше, чем один элемент.

Redirect mode навигационного запроса - “manual”

 

  • Добавить ограничение безопасности, запрещающее сервис-воркерам отвечать на запросы с redirect mode иным, чем "follow" (чтобы избежать открытых перенаправлений)

  • Добавить в класс Response Fetch API атрибут .redirected. Разработчики могут его проверять, чтобы избежать небезопасных ответов. 

  • Не кешировать перенаправленные ответы

  • "Очищать" ответ перед отправкой

Решения для 3xx

Не кешировать 3XX совсем

/dashboard

/dashboard

/login

// If "cleanRedirects" and this is a redirected response,
// then get a "clean" copy to add to the cache.

const newResponse = cleanRedirects && response.redirected ?
    await cleanResponseCopy({response}) :
    response.clone();

workbox/.../request-wrapper.js#L420

"Очищать" ответ

Правильные инструменты

Доверяй И проверяй

#7

Инструменты помогают

  • Реализовывать сложные алгоритмы

  • Следить за обновлениями спецификаций

  • Обрабатывать пограничные случаи

  • Перенимать лучшие практики

  • Фокусироваться на ВАШЕЙ задаче

Фреймворки

  • sw-precache / sw-toolbox

  • Workbox

  • offline-plugin для Webpack

  • create-react-app

  • preact-cli

  • polymer-cli

  • vue-cli

  • angular-cli

Генераторы

App shell

Динамическое кеширование

Оффлайн GA

Повторение неуспешных запросов

Уведомления об обновлениях

Build-интеграции

Возможность расширять функциональность собственного сервис-воркера

Тестирование

Service Worker Mock

const env = {
  // Environment polyfills
  skipWaiting: Function,
  caches: CacheStorage,
  clients: Clients,
  registration: ServiceWorkerRegistration,
  addEventListener: Function,
  Request: constructor Function,
  Response: constructor Function,
  URL: constructor Function
};

Push-уведомления

Реинкарнация всплывающих окон?!

#8

  • Только после явного запроса от пользователя

  • Очевидная и простая отписка 

Подписка

Уведомления

  • Рассмотреть все альтернативные варианты. Уведомление – крайний случай.

  • Не для массовых, но для индивидуальных сообщений

MyAirline

Открыта регистрация на ваш рейс

MyAirline

myairline.com

Ваш рейс

MY 1228

Отправление

21.09 13:45

SVO -> GOJ

Нажмите здесь, чтобы зарегистрироваться

myairline.com

Содержимое

  • Не повторяйтесь. Места и так мало.

  • Отправляйте непосредственно информацию, а не факт ее поступления

  • Продумайте призыв к действию

Новые функции

Еще в разработке

#9

Периодическая синхронизация

  • Ограничение временным интервалом, состоянием батареи, режимом подключения к сети

  • Требует явного разрешения со стороны пользователя

  • Не требуется поддержки со стороны сервера

  • Финальное решение по запуску – за браузером

navigator.serviceWorker.ready.then((registration) => {
  registration.periodicSync.register({
    tag: 'get-latest-news',         // default: ''
    minPeriod: 12 * 60 * 60 * 1000, // default: 0
    powerState: 'avoid-draining',   // default: 'auto'
    networkState: 'avoid-cellular'  // default: 'online'
  }).then((periodicSyncReg) => {

    // Задача успешно зарегистрирована

  })
});

index.html

self.addEventListener('periodicsync', function(event) {
  if (event.registration.tag == 'get-latest-news') {
    event.waitUntil(fetchAndCacheLatestNews());
  }
  else {
    // Неизвестная задача, лучше отменить регистрацию
    event.registration.unregister();
  }
});

sw-handmade.js

  • 900+ разработчиков

  • Представители основных браузеров, библиотек, фреймворков

#10

Спасибо!

@webmaxru

Максим Сальников

Есть вопрос?

Tame your Service Worker before your Progressive Web App go into the wild

By Maxim Salnikov

Tame your Service Worker before your Progressive Web App go into the wild

The collection of modern web browsers APIs and set of best practices on creating the applications turned into a new software creation methodology called Progressive Web Apps (PWA). The Service Worker API is a key API of the whole concept. Let me unleash its power for you! But with great power comes great responsibility - trivially, but true: I'll show the examples of how easy the "Progressive" part of the PWA term could become "Regressive", how to fix this, and how to test our Service Worker before deploying your app. First, we'll go through the well-known PWA functionality (App Shell, Offline Cache, Push) with focusing on the pitfalls we could easily get into, and how to avoid these. Next, I'll expand your horizons with the new PWA features, like Foreign fetch, Periodic sync, Navigation Preloads. And again - "handle with care". I'll share the points we have to pay attention to, as well as best practices. As a practical result, you will get a full overview of basic and advanced Service Worker features, as well as knowledge on how to solve a real life project issues in the best possible way. BONUS: I'll share the latest additions to Service Worker and satellite APIs, so you will be ready to build the applications for the future!

  • 1,796