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

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

@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

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

Есть вопрос?

Made with Slides.com