Aplikacja progresywna (progreessive application) to nowy typ aplikacji mocno wspierany przez Google. W założeniu serviceworker.js, skypt oparty na java scriptcie tworzy specjalny gateway, "program" który ma się stać mostem pomiędzy aplikacją w trybie online i offline.
Do tej pory istniały głównie dwa podejścia do budowy aplikacji webowych i mobilnych. Wersja online czyli typowe aplikacja internetowa (netflix, facebook) oraz aplikacje offline (którą trzeba wcześniej ściągnąć).
Tutaj wchodzi service worker i daje 2 dodatkowe możliwości rozwoju aplikacji; Online first oraz offline first, oraz duże możliwości zarządzaniem cache naszej strony / aplikacji.
Możemy przechwytywać żądania użytkownika, zmusić przeglądarkę aby ściągnęła i zachowała treści i zdjęcia. Dodatkowo ustalać zasady dotyczące priorytetu treści online / offline. Service worker odpowiednio skonfigurowany pomoże także w sytuacji "lie-fi" (dobry zasięg a jednak sie nie otwiera ... )
Warto zaznaczyć że jak narazie service worker jest obsługiwany tylko przeglądarkę Chrome (Web / Android), dodatkowym wymogiem jest protokół HTTPS.
Jak to działa ?
W praktyce nasza strona internetowa może zostać pełnoprawną ikonką w momencie otworzenia przez urządzenia mobilne.
Po wejściu w naszą aplikację progresywną, pojawia się wcześniej ustalony splashscreen. Stronę / aplikację możemy pokazywać w wybranej formie: standalone (full screen), Web browser. Ustalić orientację ekranu itp ...
Co najważniejsze i najfajniejsze w tym wszytskim ?
Nasza strona działa w trybie offline na komórki i na komputerze.
Service Worker
Rejestracja serviceworker.js na index.html / index.js
Typowa rejestracja serviceworker następuje przez umieszczenie następującego js scriptu;
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
Przyklad prostego service workera do pracy w trybie offline;
"use strict";
console.log('WORKER: executing.');
/* Numer wersji może się okazać pomocny podczas updatu naszego service workera.
*/
var version = 'v1::';
/* Poniższe zasoby będą ściągane podczas instalacji naszej progresywnej appki na komputer czy komórkę. Jeżeli coś się nie powiedzie, service worker nie zostanie zainstalowany.
*/
var offlineFundamentals = [
'',
'/assets/css/style.css',
'/assets/js/global.js'
];
/* Instalacja zostanie rozpoczęta, gdy serviceworker.js zostanie najpierw zainstalowany. Możesz użyć "event listener", aby przygotować sw.js do obsługi
plików, gdy odwiedzający są w trybie offline.
*/
self.addEventListener("install", function(event) {
console.log('WORKER: install event in progress.');
/* Użycie event.waitUntil(p) blokuje instalacje na poniższym "promise" czyli
asychonicznym wezwaniu. Jeżeli "promise" zostanie odrzucony, service worker nie będzie zainstalowany.
*/
event.waitUntil(
/* Wbudowany API "Cache" oparty na "promise" pomaga w cachowaniu odpowiedzi, znajdowaniu i usuwaniu plików.
*/
caches
/* To pozwala aby otwierac cache po nazwie, ta metoda także zwraca "promise". Możemy używać "versioned cache name", przypisanej nazwy cache aby poźniej pozbywac się starszych wpisów / "entries" z cache'u, kiedy będziemy wprowadzać nowego service workera.
*/
.open(version + 'fundamentals')
.then(function(cache) {
/* Po otworzeniu cache, możemy go uzupełnić z zapisanymi wcześniej "offline fundamentals".
*/
return cache.addAll(offlineFundamentals);
})
.then(function() {
console.log('WORKER: install completed');
})
);
});
/* Zdarzenie pobierania jest wywoływane za każdym razem, gdy strona kontrolowana przez tego serviceworker.js żada zasoby (request) zasób. Nie jest to tylko ograniczone do żądania `fetch` lub nawet XMLHttpRequest, ale nawet do request'u strony HTML przy pierwszym załadowaniu, plików JS, CSS, fontów czy obrazów itp.
*/
self.addEventListener("fetch", function(event) {
console.log('WORKER: fetch event in progress.');
/* Powinnismy cacho'wac tylko "GET request", resztą niech zajmie się przeglądarka poprzez zarządzanie (handling) nieudanymi requestów "POST,PUT,PATCH, itp".
*/
if (event.request.method !== 'GET') {
/*
Jeśli nie zablokujemy zdarzenia, jak pokazano poniżej, żądanie zostanie wysłane przez sieć jak zwykle.
*/
console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);
return;
}
/* Podobnie jak "event.waitUntil", blokujemy zdarzenie pobierania na "promise"
Wynik realizacji zostanie użyty jako odpowiedź, a odrzucenie zakończy się na
Odpowiedzi (http) informująca o awarii.
*/
event.respondWith(
caches
/*
Ta metoda zwraca "promise", która rozwiązuje problem z dopasowaniem wpisu pamięci podręcznej (cache) do naszego request'u. Gdy "promise" zostanie spełniony, będziemy mogli udzielić odpowiedzi na żądanie pobrania.
*/
.match(event.request)
.then(function(cached) {
/* Nawet jeśli odpowiedź znajduje się w naszej pamięci podręcznej, robimy wezwanie także do do sieci. Ten wzorzec jest znany z wywoływania "świeżych" reakcji,gdzie natychmiast zwracamy buforowane odpowiedzi, a tymczasem ciągniemy odpowiedź sieciowa i zapisz ją w pamięci podręcznej.
*/
var networked = fetch(event.request)
// We handle the network request with success and failure scenarios.
.then(fetchedFromNetwork, unableToResolve)
// We should catch errors on the fetchedFromNetwork handler as well.
.catch(unableToResolve);
/* We return the cached response immediately if there is one, and fall
back to waiting on the network as usual.
*/
console.log('WORKER: fetch event', cached ? '(cached)' : '(network)', event.request.url);
return cached || networked;
function fetchedFromNetwork(response) {
/* Klonujemy "response". To jest odpowiedź, która będzie przechowywana w pamięci podręcznej ServiceWorker. */
var cacheCopy = response.clone();
console.log('WORKER: fetch response from network.', event.request.url);
caches
//Otwieramy pamięć podręczną, aby zapisać odpowiedź na to żądanie.
.open(version + 'pages')
.then(function add(cache) {
/* Przechowujemy odpowiedź na to żądanie. Później ta odpowiedz będzie dostępna dla wywołań caches.match (event.request) podczas wyszukiwania zbuforowanych odpowiedzi.
*/
return cache.put(event.request, cacheCopy);
})
.then(function() {
console.log('WORKER: fetch response stored in cache.', event.request.url);
});
// Zwróć odpowiedź, aby obietnica została spełniona.
return response;
}
/* Gdy wywoływana jest ta metoda, oznacza to, że nie jesteśmy w stanie wytworzyć odpowiedzi z pamięci podręcznej lub sieci. Dobra okazja do wyświetlenia jakies wiadomosci jeżeli wszystko inne zawiedzie. Typu "Usługa niedostępna" lub ogólna wiadomość błędu.
*/
function unableToResolve () {
console.log('WORKER: fetch request failed in both cache and network.');
/* Tutaj programujemy odpowiedź. Pierwszym parametrem jest
treść odpowiedzi, a druga określa opcje odpowiedzi.
*/
return new Response('<h1>Service Unavailable</h1>', {
status: 503,
statusText: 'Service Unavailable',
headers: new Headers({
'Content-Type': 'text/html'
})
});
}
})
);
});
/* Zdarzenie activate uruchamia się po pomyślnym zainstalowaniu modułu serwisowego.
Jest to najbardziej przydatne przy wycofywaniu starszej wersji, jeżeli serviceworker.js został zainstalowany poprawnie. W tym przykładzie usuwamy stare pamięci podręczne, które nie pasują do wersji w właśnie zakończonym procesie instalacyjnym.
*/
self.addEventListener("activate", function(event) {
/* Podobnie jak w przypadku instalacji, event.waitUntil aktywuje się na "promise". Aktywacja zakończy się niepowodzeniem, dopóki "promise" nie zostanie spełniony.
*/
console.log('WORKER: activate event in progress.');
event.waitUntil(
caches
/*
Ta metoda zwraca obietnicę, która rozwiąże "array"" dostępnych
kluczy pamięci podręcznej
*/
.keys()
.then(function (keys) {
// Zwracamy "promise", która "settle" się po usunięciu wszystkich nieaktualnych pamięci podręcznych.
return Promise.all(
keys
.filter(function (key) {
//Filtruj według kluczy, które nie rozpoczynają się od prefiksu najnowszej wersji.
return !key.startsWith(version);
})
.map(function (key) {
/* Zwróc "promise" która zostala spelniona po tym jak przestarzaly cache zostal zwrocony.
*/
return caches.delete(key);
})
);
})
.then(function() {
console.log('WORKER: activate completed.');
})
);
});
Więcej informacji na temat serviceworker.js:
https://developers.google.com/web/fundamentals/codelabs/
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API