Service Workers

Семичев Олег

Native vs Web

C;    Internet    :C
Service workers essentially act as proxy servers that sit between web applications, and the browser and network (when available).

They are intended to enable the creation of effective offline experiences, intercepting network requests. They will also allow access to push notifications and background sync APIs.

Вот зэй кэн

Кэшировать ресурсы (читай, позволяют сайту "работать" "оффлайн")
Перехватывать запросы из браузера
Давать доступ к push-уведомлениям
Производить фоновые синхронизации *
Жить своей скрытой активной
жизнью и делать то, что они хотят (в рамках закона) *

DISCLAIMER, AGAIN

Вообще, Service Workers – сырая технология. Работать может не везде, не всегда и необязательно так, как хотелось бы и как заявлено.
Поэтому – бикэрфул и вотчаут.
DEMO

Что вам нужно знать?

Вы не сможете трогать DOM
Вы должны использовать https (ну, или localhost)
Вы обязательно наступите в промисы
Вы обязательно расстроитесь, увидев поддержку
...ну и когда Олег покажет детальную информацию
о поддержке тоже не особо обрадуетесь

Feature detection


if (navigator.serviceWorker) {}
            

Немного матчасти

Каждый Service Worker должен сделать в своей жизни три вещи:
  1. Download
  2. Install
  3. Activate

Состояния

    
    navigator.serviceWorker.register('service-worker.js')
                    
  1. [Download]: с этого момента воркер уже может слушать события
  2. Installing: воркер делает все грязные дела, например, кэширует ресурсы
  3. Installed
  4. [Install]: этап завершен
  5. Activating: назначение этого этапа – очистка ресурсов предыдущих версий воркера
  6. Activated
  7. [Activate]: теперь страница в распоряжении воркера
  8. Reduntant: воркер замещен воркером новой версии

Registration


navigator.serviceWorker
    .register('service-worker.js', { scope: './' })
    .then (function() { console.log('oh, yeah!) })
    .catch(function() { console.log('oh, nooo!) });
            

Путь указывается относительно origin


scope по умолчанию './'

service-worker.js

Представляет собой обычный js-файл, который имеет собственный контекст – ServiceWorkerGlobalScope, в котором лежат все необходимые нам для работы свойства и методы. Поэтому, чтобы сделать "грязные делишки" на этапе установки воркера, напишем:

this.addEventListener('install', function(event) {
    // here we'll cache stuff
});
            

Caching


this.addEventListener('install', function(event) {
    event.waitUntil(
        // we'll cache here for sure
    );
});
            
У event так же существует метод .skipWaiting(), который просто немедленно делает воркер активным

this.addEventListener('install', function(event) {
    event.waitUntil(
        caches
            .open('v1')
            .then(function(cache) {
                return cache.addAll([
                    '/index.html',
                    '/style.css',
                    ...
                ]);
            });
    );
})
            
Тут можно почитать о caches (CacheStorage) и cache

Request Hijacking

Чтобы перехватить запрос используется событие fetch

this.addEventListener('fetch', function(event) {
    event.respondWith(
        // magic goes here
    );
});
            

Fire


this.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request);
    );
});
            

Fira


...
event.respondWith(
    caches
        .match(event.request)
        .catch(function() {
            return fetch(event.request);
        });
);
...
            
Тут можно почитать о Fetch API и fetch

Firaga


...
return fetch(event.request)
    .then(function(res) {
        return caches
            .open('v1')
            .then(function(cache) {
                cache.put(event.request, res.clone());
                return res;
            });
    });
...
            

Firaja


...
return fetch(event.request)
    .then(function(res) {
        return caches
            .open('v1')
            .then(function(cache) {
                cache.put(event.request, res.clone());
                return res;
            })
    })
    .catch(function() {
        return caches.match('placeholder.jpg');
    });
...
                

Summary

don't write code in such way, I beg you

this.addEventListener('fetch', function(event) {
  event.respondWith(
    caches
      .match(event.request)
      .catch(function() {
        return fetch(event.request)
          .then(function(res) {
            return caches
              .open('v1')
              .then(function(cache) {
                cache.put(event.request, res.clone());
                return res;
              })
          })
          .catch(function() {
                return caches.match('placeholder.jpg');
          });
      })
  );
});
            

Update Service Worker


this.addEventListener('install', function(event) {
    event.waitUntil(
        caches
            .open('v2') // <========
            .then(function(cache) {
                return cache.addAll([ ... ]);
            });
    );
});
            
Не забывайте поменять версию кэша на актуальную
в функцию при обработке fetch!

If you love...


this.addEventListener('activate', function(event) {
    var safeList = ['v2'];

    event.waitUntil(
        // delete old caches
    );
});
            

...let go


caches
    .keys()
    .then(function(cacheKeys) {
        return Promise.all(
            cacheKeys
                .filter(function (key) {
                    return safeList.indexOf(key) < 0;
                })
                .map(function (key) {
                    return caches.delete(key);
                });
        )
    });
            
Тут можно почитать о caches.keys()

Как вообще с этим работать?

в Chrome
в Firefox

Useful Links

Specification
Is Service Workers Ready?
Cheatsheet
Cookbook
Godlike Article About Offline
Что такое service-worker? Пётр Лаптев

That's all Folks!