Максим Сальников
@webmaxru
Как правильно использовать то, что уже умеет сервис-воркер
Full-stack разработчик "приложений из будущего" в ForgeRock
Service Worker API
install, activate, fetch, backgroundfetchsuccess, backgroundfetchfail, backgroundfetchclick
sync
push, notificationclick
paymentrequest
Вебсайт
Сервис-воркер
Браузер/ОС
Event-driven воркер
'install'
Загрузка
Установка
Активация
Удален
'activate'
[Ожидание]
Активен
Эти приложения запускаются везде и обладают рядом характеристик, обеспечивающих пользователей преимуществами, аналогичными тем, что доступны в нативных решениях.
За флагом
OS
My App
Определиться с минимально необходимым набором ресурсов
Доступна новая версия.
Обновить?
Сервис-воркер
install: поместить ресурсы в Cache Storage
activate: очистить Cache от ресурсов предыдущей версии приложения
fetch: если ресурс в Cache Storage, выдать его оттуда, иначе — загрузить из сети (и закешировать)
Build time
Зарегистрировать сервис-воркер и проверять, не обновился ли он
Вебсайт
PWA Feature Detector
Web API Confluence
if ('serviceWorker' in navigator) {
// Регистрируем сервис-воркер
}
if ('SyncManager' in window) {
// Реализуем функциональность для оффлайн-режима
}
if (!('PushManager' in window)) {
// Прячем интерфейс подписки на push-уведомления
}
if ('actions' in Notification.prototype) {
// Можем использовать кнопки с разными действиями
}
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(() => {
// Регистрация сервис-воркера
});
const appShellFilesToCache = [
...
'./non-existing.html'
]
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('appshell').then((cache) => {
return cache.addAll(appShellFilesToCache)
})
)
})
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(({usage, quota}) => {
console.log(`Using ${usage} out of ${quota} bytes.`);
});
}
Chrome | <6% своб. пространства |
Firefox | <10% своб. пространства |
Safari | <50MB |
IE10 | <250MB |
Edge | Зависит от размера диска |
const appShellFilesToCache = [
'./styles.css',
...
'./styles.css'
]
event.waitUntil(
caches
.open('appshell').then(cache => {
return cache.addAll(['./bad-res.html'])
.catch(err => {
console.log(err)
})
})
);
event.waitUntil(
caches
.open('appshell').then(cache => {
return cache.addAll(['./bad-res.html'])
.catch(err => {
console.log(err)
throw err
})
})
);
const appShellFilesToCache = [
...
'https://workboxjs.org/offline-ga.min.svg'
]
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('appshell').then((cache) => {
return cache.addAll(appShellFilesToCache)
})
)
})
fetch(event.request).then( response => {
if (response.ok) {
let copy = response.clone();
caches.open('runtime').then( cache => {
cache.put(request, copy);
});
return response;
}
})
fetch(event.request).then( response => {
if (response.ok || response.status === 0) {
let copy = response.clone();
caches.open('runtime').then( cache => {
cache.put(request, copy);
});
return response;
}
})
navigator.serviceWorker.register('sw-handmade.js')
.then(registration => {
if (registration.waiting) {
// Показываем приглашение обновить страницу
}
})
Я прогрессивное веб-приложение, и я always fresh. Только это уже устаревшая версия. Кликни здесь чтобы обновить
SW Boot
Запрос навигации
SW Boot
Запрос навигации
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Включаем!
await self.registration.navigationPreload.enable();
}
}());
});
addEventListener('fetch', event => {
event.respondWith(async function() {
// Лучший вариант: отвечаем из кеша
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// OK вариант: отвечаем результатом предзагрузки
const response = await event.preloadResponse;
if (response) return response;
// Худший вариант: идем в сеть :(
return fetch(event.request);
}());
});
npm install -g sonarwhal
sonarwhal --init
sonarwhal https://airhorner.com
npm install -g lighthouse
lighthouse https://airhorner.com
workbox.core.setCacheNameDetails({precache: 'new-name'});
self.addEventListener('install', () => {
self.skipWaiting();
});
self.addEventListener('activate', () => {
self.clients.matchAll({type: 'window'}).then(tabs => {
tabs.forEach(tab => {
tab.navigate(tab.url);
});
});
});
Cache-Control: no-cache
importScripts(`/sw-lib.js?v=${VERSION}`);
navigator.serviceWorker.register('/sw.js', {
updateViaCache: 'none'
})
event.respondWith(async function() {
const response = await fetch(event.request);
const buffer = await response.arrayBuffer();
const WebPDecoder = await fetchWebPDecoder();
const decoder = new WebPDecoder(buffer);
const blob = await decoder.decodeToBMP();
return new Response(blob, { headers: { "content-type": "image/bmp",
"status": 200 } });
}());
const swReg = await navigator.serviceWorker.register("/sw.js");
await swReg.paymentManager.instruments.set(
"My Pay's Method ID",
{
name: "My Pay's Method",
method: "https://my.pay/my-method",
}
);
self.addEventListener("paymentrequest", event => {
// Открыть окно с интерфейсом данного платежного метода
event.openWindow('https://my.pay/checkout')
});
Метод (url) задан как supportedMethods в PaymentRequest сайта продавца и в момент оплаты этот метод выбран
На HEAD-запрос по этому адресу сайт платежной системы возвращает 200 + Link:<адрес Payment Method Manifest>, где в default_applications должен присутствовать Web App Manifest метода
В Web App Manifest метода присутствует секция serviceworker с src и scope
const registration = await navigator.serviceWorker.ready;
await registration.backgroundFetch.fetch(
'my-series',
['s01e01.mpg', 's01e02.mpg'],
{
title: 'Downloading My Series',
downloadTotal: 1000000000
}
);
const bgFetches =
await registration.backgroundFetch.getIds();
console.log(bgFetches);
addEventListener('backgroundfetchsuccess', event => {
event.waitUntil(
(async function() {
try {
// Копируем результаты в Cache Storage
...
await event.updateUI({ title: `Downloaded!` });
} catch (err) {
await event.updateUI({ title: `Fail: ${err}` });
}
})()
);
});
addEventListener('backgroundfetchfail', event => {
...
});
addEventListener('backgroundfetchclick', event => {
...
});
periodicsync