[DE] Überblick über Technologien zur Entwicklung von Progressive Web Apps #2 | Service Worker API, CacheStorage & SyncManager

in #programming7 years ago (edited)

banner

Dies ist Teil 2 der Serie "Überblick über Technologien zur Entwicklung von Progressive Web Apps". Du hast Teil 1 noch nicht gelesen? Dann klicke hier.


Vor dem Erscheinen des Service Workers war der Lebenszyklus einer Webseite sehr kurzlebig. Aufgrund des Aufbaus des HTTP-Protokolls, auf dem Webseiten üblicherweise basieren, wird die Verbindung zum Server nach der Auslieferung der Webseite direkt geschlossen. Somit gab es keine Kontrolle über die Inhalte für den Client und keine Möglichkeit für den Server den Client abermals zu erreichen, wenn dieser beispielsweise den Tab geschlossen hat. Dieses Defizit wurde mittels der Service Worker API geschlossen.

Service Worker

Der Service Worker stellt eine Art Proxy zwischen Netzwerk und Browser dar. Er wird in JavaScript (ES6) geschrieben und hat Zugriff auf jegliche Netzwerkanfragen der Applikation. Diese Netzwerkanfragen kann er unterbrechen, ändern oder verwerfen. Direkten Zugriff auf das DOM (Document Object Model) hat der Service Worker keinen. Er kann ausschließlich über postmessage mit den einzelnen Clients (Tabs oä.) kommunizieren. Außerdem ist die Service Worker API nur über HTTPS nutzbar, um Man-In-The-Browser-Attacken entgegen zu wirken.

Installation eines Service-Workers

Installiert werden kann ein Service-Worker aus einem JavaScript heraus. Hierbei ist zu beachten, dass der Service Worker nur auf einen bestimmen Scope registriert werden kann. Registriert man ihn beispielsweise auf /test hat er ausschließlich Zugriff auf Anfragen, welche auf der Unterseite /test stattfinden.

async function registerServiceWorker () {
  if ('serviceWorker' in navigator) {
    try {
      const registration = await navigator.serviceWorker.register('/serviceworker.js', { scope: '/test' });
      console.log('Service Worker Registration war erfolgreich', registration);
    } catch (error) {
      console.error('Service Worker Registration schlug fehl', error);
    }
  }
}


Ab diesem Punkt beginnt der Lebenszyklus des Service Workers.
service-worker-lifecycle
Während der Service Worker diesen nun durchläuft löst er diverse Events aus. Auf diese kann sich mit self.addEventListener registriert werden.

self.addEventListener('install', (event) => {
  console.log('installing service worker');
});

self.addEventListener('activate', (event) => {
  console.log('activating service worker');
});

self.addEventListener('fetch', (event) => {
  console.log(`fetching: ${event.request}`);
});

self.addEventListener('sync', (event) => {
  console.log(`sync-request: ${event}`);
});

self.addEventListener('push', (event) => {
  console.log(`push-request: ${event}`);
});


Es ist zu bemerken, dass nach der Installation des Service Worker die ersten beiden Strings und bei erneutem Laden der Seite erst der dritte String in der Konsole ausgegeben wird.

Unterstützung

Die Service Worker API wird zum heutigen Zeitpunkt im neusten Edge, Firefox, Chrome und in den neusten Versionen des Safaris unterstützt. Eine detaillierte Auflistung findest du hier.

CacheStorage

Durch den Service Worker nutzbar wird der CacheStorage. Er ist ein Service Worker Interface und eine Art Caching-Schicht. Er erlaubt uns komplexe Webanwendungen offline verfügbar zu machen.

Den Versuch Ressourcen offline zur Verfügung zu stellen, gab es bereits in Form des Application Caches, welcher mit HTML5 eingeführt wurde. Dieser hatte diverse Probleme. So mussten alle zu cachenden Ressourcen in einem Manifest eingetragen werden und waren somit nicht dynamisch. Einmal gecached, kamen Ressourcen ausschließlich aus dem Cache (selbst bei intakter Netzwerkverbindung). Ressourcen, welche dagegen nicht gecached wurden, waren gar nicht mehr verfügbar (selbst bei intakter Netzwerkverbindung). Mit diesem Verhalten war der Applikation Cache also nicht praktikabel.
Der CacheStorage arbeitet hingegen aufgrund des Service Workers komplett dynamisch. So können wir auf jede Anfrage dynamisch mit einer Antwort aus dem Cache reagieren, oder nicht.

Ein neuer Cache

Um einen Cache zu initialisieren, ändern wir zuerst die Funktion unseres install-Events.

const CACHE_NAME = 'cache-v1';
const CACHED_URLS = [
  '/index.html',
  '/index-offline.html',
  '/img/offline.png',
  '/css/offline.css',
  '/css/appshell.css',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(CACHED_URLS))
  );
});

Am Beispiel-Code oben, sehen wir eine der wichtigsten Funktionen, wenn man mit dem Service Worker arbeitet: event.waitUntil. Diese sorgt dafür, dass der Service Worker wartet, bis das als Parameter mitgegebene Promise beendet ist.
Das nächste Code-Beispiel zeigt, wie man mit Hilfe des fetch-Events bei fehlender Netzwerkverbindung Ressourcen aus dem Cache lädt und als Antwort bereitstellt.

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .catch(() => caches.match(event.request))
  );
});


Unterstützung

Da der CacheStorage ein Interface der Service Worker API ist, ist die Unterstützung gleich der des Service Workers.

SyncManager

Der SyncManager ist das zweite Service Worker API Interface. Er erlaubt es Events zu registrieren, um Aktionen von der Webseite in den Hintergrund auszulagern. Dies bietet den Vorteil, dass Aktionen nicht mehr abhängig vom Status der Webseite sind. Nehmen wir das Beispiel "Messenger App". Der Nutzer möchte sich keine Sorgen darüber machen, ob er online oder offline ist und ob seine Nachrichten somit ankommen, oder nicht. Der Workflow wäre also:

  • Nutzer öffnet Kontakt
  • Nutzer schreibt Nachricht
  • Nutzer schickt Nachricht
  • Nutzer schließt die App / die Webseite

Läge die Aktion die Nachricht an den Server auszuliefern nun also im Vordergrund würde sie nicht ausgeliefert werden, da der Nutzer die App geschlossen hat.
Statt also eine Aktion in der App selbst auszuführen, registriert man nun ein Event im SyncManager.

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.ready
    .then((registration) => {
      registration.sync.register('send-messages');
    });
}


Dieser Code kann an jeder Stelle im JavaScript der App platziert werden und wird nur ausgeführt, wenn der Service Worker bereit ist. Um nun auf dieses Event zu reagieren müssen wir auf das sync-Event reagieren.

self.addEventListener('sync', (event) => {
  switch(event.tag) {
    case 'send-messages':
      event.waitUntil(sendMessages());
      break;
    default:
      break;
  }
});


Zu beachten ist, dass die Funktion sendMessages ein Promise zurückgeben muss. Ist dieses Promise nicht erfolgreich, wird das Event in eine Warteschlange eingereiht und zu einem späteren Zeitpunkt automatisch neu ausgeführt.

Unterstützung

Auch bei dem SyncManager ist die Unterstützung gleich der des Service Workers.

Die folgenden Posts

In den folgenden Posts der Serie "Überblick über Technologien zur Entwicklung von Progressive Web Apps" werde ich auf folgende Technologien eingehen:

  1. Service Worker API inkl. CacheStorage & SyncManager ✔
  2. IndexedDB
  3. Notification & Push API
  4. Payment Request API
  5. Credential Management API
  6. Web Share API & Web Share Target API
  7. Media Session API

Dabei werde ich mich maßgeblich an dem Buch Building Progressive Web Apps von Tal Ater orientieren.

Quellen und weiterführende Links

http://alistapart.com/article/application-cache-is-a-douchebag (besucht am 28.01.2018 15:26 Uhr)

https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker (besucht am 28.01.2018 16:17 Uhr)


Du hast Änderungsvorschläge oder Wünsche? Lass es mich in den Kommentaren wissen ;)
In dem Sinne, frohes Coden.

(() => {
  const colors = [
    '001f3f', '0074d9', '7fdbff', '39cccc',
    '3d9970', '2ecc40', '01ff70', 'ffdc00',
    'ff851b', 'ff4136', '85144b', 'f012be',
  ];
  const contents = ['%cI', '%c❤', '%cweb', '%cdev'];
  const options = Array.from(
    new Array(contents.length),
    () => `
      color:#${colors[Math.floor(Math.random() * colors.length)]};
      font-size:64px;
    `
  );
  console.log.apply(console, [contents.join('')].concat(options));
})();
Sort:  

This post has received a 4.15% UpGoat from @shares. Send at least 0.1 SBD to @shares with a post link in the memo field.

To support our daily curation initiative, please donate 1 SBD or delegate Steem Power (SP) to @shares by clicking one fo the following links: 10 SP, 50 SP, 100 SP, 500 SP, 1000 SP, 5000 SP.

Support my owner. Please vote @Yehey as Witness - simply click and vote.