Деякі тонкощі використання Service Workers



Передмова

Сервіс-воркеры (Service Workers, хай вибачать мене читачі) сьогодні є корисним доповненням до основної функціональності сайту: тут і робота в оффлайні, і фонова синхронізація даних, і модні пуш-повідомлення.
Однак велика кількість статей про сервіс-воркеры виглядають досить стисло і описують прості приклади. Я спробую звернути увагу на деякі особливості роботи сервіс-воркеров, так що потрібні якісь базові знання. Відправною точкою може бути ця стаття перевод) або трохи більш детальна стаття.

Кілька сервіс-воркеров на одному домені

У реєстрації (registration) конкретного сервіс-воркера є таке поняття, як scope. Воно визначає, які сторінки на певному домені будуть підпадати під її контроль. При цьому можна реєструвати декілька сервіс-воркеров на одному домені, але з різними scope. Якщо спробувати зареєструвати їх з різними іменами, але одним scope, то встановлений пізніше воркер «заміщати» свого більш раннього брата.

До речі, для того, щоб файл з вказаним шляхом можна було встановити в якості сервіс-воркера по шляху вище (така поведінка заборонено за замовчуванням, збільшувати шлях можна, зменшувати — ні), то для цього можна використовувати http-заголовок Service-Worker-Allowed.

Метод getRegistration() без параметрів повертає відповідну для поточної сторінки реєстрацію сервіс-воркера, можливо, неактивну. Це також означає те, що по вкладеному шляху ми будемо отримувати ту ж саму реєстрацію, якщо немає більш підходящої. Це може призводити до несподіванок, якщо очікується робота декількох сервіс-воркеров на одному домені.

Розглянемо приклад: у нас є встановлений сервіс-воркер з scope /. Нехай це буде новинний сайт і ми надаємо оффлайн версії текстів. Також є панель управління по шляху /admin/ зі своїм власним сервіс-воркером. Якщо другий сервіс-воркер ще не спробували встановити, то getRegistaration() повертатиме реєстрацію першого сервіс-воркера і це може призводити до помилок (наприклад, ми будемо слати нотифікації з панелі адміністратора в сервіс-воркер, не готовий до них зовсім).

getRegistration має опціональний параметр — scope. Якщо його вказати, то метод поверне реєстрацію, найбільш підходящу (не обов'язково однакову) переданим scope. Тим самим ми можемо відписуватися від сервіс-воркеров на вкладених сторінках або отримувати взагалі будь реєстрації з поточного домену, потрібно лише знати відповідні scope.

А якщо ми не знаємо все scope, тобто метод getRegistrations(), який повертає все реєстрації з поточного домену у вигляді масиву. Потрібно Firefox або Chrome 45+.

Зв'язок між сторінкою і сервіс-воркером

Можливість обміну даними між сервіс-воркером і підпорядкованої сторінкою може призвести до досить оригінальним схемами роботи. Наприклад, можна відразу надсилати дані з кеша, паралельно запитуючи нові; як тільки будуть нові дані — покласти їх в кеш і надіслати на сторінку.

Приклад на serviceworke.rs показує простий спосіб спілкування з сервіс-воркером:
navigator.serviceWorker.controller.postMessage(message.value);

Тут controller — сервіс-воркер, контролюючий сторінку. У свіжих браузерах (всі версії Firefox і Chrome 51+) можна досить просто відповісти на такий запит:
self.addEventListener. ('message', function (event) {
event.source.postMessage('response');
});

У більш старих версіях доводилося обходити всі вкладки і знаходити потрібну, а то й створювати руками MessageChannel. Також тепер у нас є можливість відправляти повідомлення вкладці події fetch. Все це описано в статті, хіба що сучасне апі у нас вже є.

Інший момент — збереження даних в сервіс-воркере. Люди, які вже випробували сервіс-воркеры, могли помітити, що LocalStorage там немає. Все тому, що в сервіс-воркерах був узятий курс на повністю асинхронне апі (за винятком, мабуть, importScripts). Але всередині все ще залишаються:
  • caches
  • indexedDB
  • просто змінні, оголошені в контексті воркера (але вони недовговічні і будуть забуті при зупинці сервіс-воркера)
І caches, і indexedDB доступні звичайним чином на сторінках, повністю поділяючи з воркером дані. Якщо звернутися до попереднього параграфу, можна також прийти до висновку, що і кілька сервіс-воркеров на одному origin будуть розділяти дані! У такому випадку потрібно не терти кеші іншого сервіс-воркера, наприклад, перевіряючи їх з префіксом:
var CACHE_PREFIX = 'my-page-';
var CACHE_VERSION = 1;
var CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;

self.addEventListener. ('activate', function(event) {
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (cacheName.indexOf(CACHE_PREFIX) === 0) {
return caches.delete(cacheName);
}
})
);
})
);
});

Або как-так, але тоді потрібно буде мати в одному місці повний список можливих кешей.

Але при всьому цьому варто пам'ятати, що ніхто не гарантує 100% збереження даних у сховищах. Браузер може автоматично чистити CacheStorage і indexedDB при нестачі місця на диску, так і користувач може зробити це сам.

Кроссдоменные запити та інше взаємодія з іншими доменами

З введенням fetch ситуація могла здатися трохи заплутаною (там є різні режими запиту/відповіді), а з сервіс-воркерами все стає в два рази складніше: один fetch на стороні клієнта, другий — на стороні сервіс-воркера.

Найпростіше розуміння, до якого можна прийти: «обдурити» CORS і отримати доступ до контенту з іншого домену без заголовків не вийде. Важливо розділяти два види використання: з доступом з боку javascript і без нього. Наприклад, підмінити одну картинку іншого можна без проблем: достатньо вказати в fetch сервіс-воркера mode: 'no-cors' і не важливо, які там заголовки. Якщо не використовувати 'no-cors', fetch буде очікувати CORS заголовки й у випадку їхньої відсутності все скінчиться помилкою.

Якщо говорити строго, то будь-який запит (Request) зі сторінки має mode. Наприклад, запит картинки — 'no-cors', а запит картинки з атрибутом crossOrigin (anonymous або use-credentials) — вже 'cors'. Запити через XMLHttpRequest завжди в режимі 'cors'. А fetch дозволяє задавати режим безпосередньо.

Відповідь (Response) має властивість type. Запити на поточний домен — 'basic'. Інакше, якщо режим запит — 'cors', type відповіді теж буде 'cors', при наявності необхідних заголовків. Режим відповіді 'opaque' можна отримати на запит у режимі 'no-cors', в ньому можна отримати доступ до будь-якого даними відповіді.

Тут описані не всі можливі види режимів запитів, але цього повинно бути достатньо для загального розуміння. Більше інформації можна почерпнути з статті з описом fetch.

Тепер спробуємо все скомбінувати. Зі сторінки йде запит, його перехоплює сервіс-воркер і робить свій fetch, отримує відповідь. До поточного моменту ситуація розібрана, але тепер буде нюанс: при передачі відповіді з типом 'opaque' у відповідь на запит сторінки. який був зроблений не з режимом 'no-cors', ми отримаємо помилку.

Крім просто запитів, ми можемо встановити сервіс-воркер на інший домен. Ні, ми не отримаємо контроль за одною сторінкою через наш сервіс-воркер — умови на сервіс-воркер залишаються тими ж (сам скрипт повинен бути на тому ж домені, на який реєструється воркер). Для цього можна використовувати iframe з потрібного домену — дозволів від користувача не потрібно і iframe можна зробити просто невидимою.

Інша цікава можливість, яка зараз перебуває у своїй ранньої версії — Foreign Fetch. Якщо звичайний сервіс-воркер контролює запити зі сторінки в своєму scope (сторінка в scope, а не запити), то foreign fetch дозволяє контролювати запити на свій домен. Припустимо, звичайна подія fetch буде спрацьовувати при запиті за бібліотекою на CDN, а foreignFetch буде спрацьовувати при всіх запитах за цією бібліотекою на будь-яких сайтах! Це цікава можливість може бути використана, наприклад, службами аналітики.

Тестування

З написанням тестів на сервіс-воркеры є певні складності. Складання тесту не так просто: якщо ми хочемо перевірити офлайновий режим, то потрібно якось эмулиовать помилки мережі, якщо хочемо перевірити оновлення — потрібно підміняти файл новим і тому подібне.

Додаткові проблеми полягають у тому, що в поточний момент «безголові» браузери не підтримують сервіс-воркеры, а значить, потрібні справжні.

що стоїть стаття на тему тестування сервіс-воркеров. В ній є посилання і на пару інструментів: sw-unit test sample і platinum-sw (елемент для Polymer, у ньому є також пара тестів). У статті також описаний цікавий прийом: створення іфрейма для того, щоб він контролювався тестованим сервіс-воркером. Взагалі кажучи, у елементів iframe і object є інша особливість: запити за ними та їх вмістом йдуть в обхід поточного сервіс-воркера сторінки, використовуючи власні сервіс-воркеры.

Те, що caches доступно на самій сторінці, що може бути корисно при тестуванні для очищення і перевірки вмісту кеша.

Важливий нюанс при роботі автотестів — визначення моменту, коли сервіс-воркер контролює сторінку і може перехоплювати запити. Простий navigator.serviceWorker.ready не завжди є вірним рішенням — ready спрацьовує в момент активації сервіс воркера, але до того, як закінчиться виконання clients.claim(). Більш докладно описано тут, як одне з рішень — слухати подія controllerchange.

Оновлення сервіс-воркера

Є кілька нюансів при оновленні сервіс-воркеров, на які варто звернути увагу.

Незважаючи на підтримку кешуючих заголовків при запиті до скрипта сервіс-воркера, браузери зменшують час життя кешу до 24 годин. Зроблено це для того, щоб випадково не залишити сайт в убитому стані на великий проміжок часу. Ось хороший відповідь на StackOverflow про кешування.

Інший нюанс: оновлення спрацьовує, тільки якщо сам скрипт сервіс-воркера оновився, і визначення цього відбувається побайтово. З цього випливає, що оновлення файлів, які підключені через importScripts, не призведе до оновлення самого сервіс-воркера.

При оновленні часто додаються в кеш з мережі якісь файли. Але при цьому працює браузерний кеш! Як і при викликах fetch всередині сервіс-воркера. Потрібно або бути впевненим, що файли не змінилися (наприклад, включати версію/хеш заголовок файлу), або завантажувати ресурси в обхід кєша. Щоб завантажувати ресурси в обхід кєша, можна руками або кликати fetch і потім додавати відповідь в кеш (не забуваючи перевіряти response.ok, наприклад), або використовувати опцію cache: 'no-cache' Request'а (поки працює тільки в Firefox Nightly). І то і то описано в статті Jake Archibald.

Також варто згадати, що запит за скриптом сервіс-воркера при оновленні йде в обхід обробника події fetch поточного сервіс-воркера.

Різне

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.