Über unsMediaKontaktImpressum
Katharina Siegrist 29. Juni 2021

Service Worker in Web-Anwendungen

Ich bin dann mal offline

Komplexe Web-Anwendungen sind längst ein Standard für Unternehmen, die Daten und Services für ihre Kunden anbieten. Dabei ist es gelebte Praxis, dass Web-Apps auf verschiedensten Endgeräten immer und überall abrufbar sind: Während der Bahnfahrt mal schnell die Nachrichten in der News-App lesen? Beim Picknick im Wald das TV-Programm vom Abend ansehen? Doch was tun, wenn die Internetverbindung streikt? Eine einfache Anzeige "Keine Internet-Verbindung" reicht hier oftmals nicht aus. Nutzer erwarten eine dauerhafte App-Verfügbarkeit. Und genau hier kommen die Service Worker ins Spiel!

Service Worker bilden die technische Basis von Progressive Web Apps. Sie erweitern Web-Anwendungen um wichtige Features von Native-Apps wie Offline-Fähigkeit, Push-Notifications, Background-Sync oder Performance-Beschleunigung und helfen so, das App-Erlebnis zu verbessern: Weg von einer einfachen Webseite hin zu einer echten App. Und das in nahezu jedem modernen Browser auf verschiedensten Endgeräten.

In diesem Artikel zeige ich, was Service Worker sind und erkläre, wie man sie verwenden kann. Abschließend stelle ich noch einen echten Anwendungsfall in der Organspende mit einigen Besonderheiten vor.

Was sind Service Worker?

Bei Service Workern handelt es sich um reinen Javascript-Code, der in einem eigenen Thread neben der eigentlichen Web-App läuft. Die Service Worker können aus ihrem Thread heraus weder auf die eigentliche Web-Anwendung noch auf deren DOM-Elemente zugreifen. Allerdings können sie mit Hilfe von Events HTTPS-Requests der Web-Anwendung abfangen, manipulieren und speziell behandeln. Die (möglicherweise manipulierte) Response schicken sie wieder an die Anwendung zurück, dabei haben sie Zugriff auf einen speziellen, asynchronen Cache, in dem sie Daten (Request-Response-Paare) ablegen können.

Der bekannteste Einsatzzweck von Service Workern und auch der Fokus in diesem Artikel ist es, bestimmte Seiten oder Inhalte auch offline verfügbar zu machen. Dabei gibt es verschiedene Best Practices oder auch Strategien, die zum Einsatz kommen können:

Eine Standard-Strategie beim Einsatz von Service Workern ist die "Network First"-Strategie: dabei werden Inhalte zunächst immer zuerst via Netzwerk abgerufen und nur im Fehlerfall wird auf gecachte Inhalte zurückgegriffen. Ein weiterer beliebter Ansatz ist die "Stale While Revalidate"-Strategie: um kürzere Ladezeiten zu ermöglichen, werden Inhalte immer aus dem Cache geladen, der Cache wird allerdings direkt danach mit aktuellen Inhalten aktualisiert.

Darüber hinaus gibt es noch viele weitere Strategien wie z. B. "Network Only", "Cache Only" oder auch "Cache First". Die passende Strategie sollte immer je nach Anwendungsfall ausgewählt werden – sie kann für jeden Request gesondert festgelegt werden.

Wie benutze ich Service Worker im Standardfall?

Nachfolgend schauen wir uns an, wie Service Worker verwendet werden können. Dazu implementieren wir den Standardfall mit der "Network First"-Strategie. Die Service Worker folgen einem bestimmten Lifecycle (s. Abb. 3).

Nachfolgend wird der Ablauf für die Verwendung eines Service Workers Schritt für Schritt anhand dieses Lifecycles mit Code-Beispielen dargestellt.

Zunächst muss der Service Worker für eine Web-Anwendung registriert werden:

Listing 1: Registrierung eines Service Workers

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw-test/sw.js', { scope: '/‘ })
    .then(function(reg) {
      // Registrierung erfolgreich
      console.log('Registr. erfolgreich. Scope ist ' + reg.scope);
    })
};

Zu beachten ist, dass der Service Worker nur Seiten kontrollieren kann, die sich in dessen Scope befinden. Standardmäßig muss der Service Worker im Root-Ordner der Seite abgelegt werden, die er kontrollieren soll. Ist das nicht möglich, dann können auch größere Scopes genutzt werden, indem man im HTTP-Header (Response der Anwendung auf einen Request) das Attribut Service-Worker-Allowed setzt. Es können auch mehrere Service Worker pro Anwendung verwendet werden.

Nach der Registrierung wird der Service Worker installiert. An dieser Stelle kann er bereits erste Inhalte (Request-Response-Paare) in den Cache schreiben: Er verwendet dazu CacheStorage, einen speziellen domain-spezifischen Cache, der asynchron arbeitet.

Listing 2: Precaching von Inhalten

this.addEventListener('install', function(event) {
  event.waitUntil(
    // Hinzufügen der Requests zum Cache
    caches.open(CACHE).then(cache => {
        return cache.addAll([/mypage1.html, '/mywebapirequest1']);
      })
  );
}); 

Nach der Installation geht der Service Worker in den Zustand Activated über. Hier wird der Service-Worker-Code byte für byte auf Updates überprüft, eventuelle neue Service-Worker-Versionen werden geladen (s. u.). Alte Cache-Einträge können hier entfernt werden.

Der Service Worker geht abschließend in den Zustand Idle über – er ist jetzt bereit und wartet auf Requests, die er manipulieren kann. Dazu benutzt er das fetch-Event: Er wartet auf HTTPS-Requests, fängt diese ab und entscheidet, wie er eine Antwort senden möchte – entweder indem er Inhalte vom Server lädt, indem er Inhalte aus dem Cache lädt oder indem er eine eigene Antwort zusammenbaut:

Listing 3: fetch-Event

this.addEventListener('fetch', function(event) {
  event.respondWith(
    // Response aus dem Cache
    caches.match(event.request);
    // oder Response via Network
    fetch(event.request);
    // oder eigene Response
    new Response('Hello from service worker');
  );
}); 

Bei der Verwendung der "Network First"-Strategie würde die Antwort wie folgt aussehen:

Listing 4: "Network First"-Strategie

this.addEventListener('fetch', function(event) {
  event.respondWith(
    // Response via Network („Network First“)
    fetch(event.request).catch(function() {
        // Response via Cache (gecachter Offline Inhalt)
        return caches.match(event.request).catch(function() {
          // Response via Cache (Fallback)
          return caches.match('/myFallbackPage.html');
        });
    });
  );
});

Nachdem wir jetzt die Service Worker kennengelernt haben und die "Standard-Verwendung" anhand von kleinen Code-Beispielen gezeigt wurde, werde ich in den weiteren Kapiteln einen echten Anwendungsfall vorstellen: Wir haben die Service Worker in einer Anwendung für die Organspende eingesetzt.

Einsatz von Service Workern bei der Organspende

Bei einer Organspende werden menschliche Organe für eine Transplantation vom Organspender zur Verfügung gestellt [3]. Die Organspende ist für todkranke Menschen oft die letzte Möglichkeit, weiterleben zu können. Sie ist ein wichtiger Bestandteil der Medizin und wird auch von der Bundesregierung in verschiedenen Kampagnen gefördert.

Bereits seit 2016 implementieren wir für die Deutsche Stiftung Organtransplantation (DSO) das System DSO.isys web – die zentrale Anwendung zur Abwicklung von Organspenden, Kontaktverwaltung und Dokumentation des gesamten Organspendeprozesses in Deutschland. Ein Teil dieses Systems ist die OP-Entnahme: Bei einer Organspende werden im Operationssaal die freigegebenen Organe entnommen. Auch die Dokumentation dieser Entnahme inkl. beteiligter Personen, Medikation, Zustand der Organe, Entnahmezeiten usw. wird noch im Operationssaal ausgefüllt, ausgedruckt und gemeinsam mit dem Organ zum Transplantationszentrum des vorgesehenen Organempfängers transportiert. Da es in vielen OP-Sälen keine Internet-Verbindung gibt, muss diese OP-Entnahme auch offline funktionieren – und genau hier haben wir die Service Worker eingesetzt!

In Abb. 5 kann man erkennen, dass die OP-Entnahme ein Erfassungspopup ist, über das verschiedene Daten zu einer Operation eingegeben werden können. Im Offline-Fall klickt der Anwender zunächst auf den Button "Offline gehen". Anschließend wird ein Informations-Dialog angezeigt, der den Status des Offline-Modus' anzeigt. Im Hintergrund wird der Service Worker installiert, benötigte Inhalte und Daten im Cache abgelegt und der Offline-Modus gestartet. Abschließend erhält der Benutzer die Information, dass der Offline-Modus erfolgreich gestartet wurde und die Internetverbindung getrennt werden kann.

Wenn man sich den Use Case für die Offline-OP-Entnahme im System DSO.isys web ansieht, dann erkennt man einige Besonderheiten:

Offline auf Knopfdruck (unabhängig vom Status der Internetverbindung)

Im ersten Teil dieses Artikels haben wir die Standard-Strategie der Service Worker kennengelernt: Je nach Internetverbindung bezieht der Service Worker die Daten entweder vom Server (Online-Fall) oder aus dem Cache (Offline-Fall). In unserem Projekt DSO.isys web müssen wir unabhängig von der Internetverbindung arbeiten – nach Klick auf den "Offline Modus"-Button soll sich die Anwendung verhalten, als wäre sie offline, also auch dann, wenn sie eigentlich eine Internetverbindung besitzt. Wir mussten also eine Funktion "Offline auf Knopfdruck" realisieren.

Dazu haben wir die Messages zum Nachrichtenaustausch verwendet. Über diese Messages können Nachrichten zwischen der Anwendung und dem Service Worker verschickt werden. Dies ist in beide Richtungen möglich, auch Daten können übergeben werden. Nach dem Klick auf den "Offline Modus"-Button schicken wir eine startOfflineMode-Nachricht an den Service Worker, auf welche dieser geeignet reagieren kann:

Hat der Service Worker alles für den Offline-Fall vorbereitet, antwortet er mit einer offlineModeStarted-Nachricht:

Um sich den eigenen Zustand zu merken, haben wir ein eigenes State-Handling eingeführt. Sowohl der Service Worker besitzt eigene States wie OnlineMode, OfflineMode, Precaching oder WaitForAuthorization, um je nach Status und Request unterschiedlich reagieren zu können. So werden beispielsweise im Status OfflineMode alle Requests immer an den Cache weitergeleitet und die Daten von dort bezogen.

Aber auch die Anwendung selbst besitzt ein eigenes State-Handling, das ähnlich funktioniert. Hier wird z. B. im Status OfflineMode das Erfassungspopup anders dargestellt, um bereits im Layout für den Nutzer hervorzuheben, dass die Anwendung aktuell offline arbeitet. Außerdem sind einige Eingabemöglichkeiten dann nicht verfügbar, man kann z. B. keine beteiligten Personen aus dem angeschlossenen Kontaktmanagement von DSO.isys web laden, sondern Personendaten nur manuell erfassen.

Offline-Fähigkeit nur für einen kleinen Teil der Anwendung

Im Normalfall wird der Service Worker beim Starten der Anwendung installiert und arbeitet dann während der gesamten Lebensdauer der Anwendung im Hintergrund mit. Er steuert alle Requests der Anwendung je nach Internetverbindung. In der Anwendung DSO.isys web ist der Service Worker jedoch nur für einen kleinen Teil der Anwendung (die OP-Entnahme) zuständig. In den meisten Fällen wird er nicht benötigt, da die OP-Entnahme entweder gar nicht aufgerufen oder nur vorbereitet wird. Daher wird in unserer Anwendung der Service Worker erst gezielt nach Klick auf den Button "Offline-Modus" installiert.

Nach der Installation erfolgt das Precaching der Offline-Daten, um die benötigten Offline-Daten für die Offline-OP-Entnahme in den Cache zu laden. Es werden alle Requests abgeschickt, die für die Offline-OP-Entnahme benötigt werden (bei uns sind das ca. 200 Requests). Dazu ist natürlich eine stabile Internetverbindung notwendig.

Die Request-Response-Paare werden anschließend im Cache abgelegt.

Listing 5: Zugriff auf den Service Worker Cache

// Hinzufügen eines Request-Response-Paares zum Cache
const cacheResponse = (request, response) => { 
  return caches.open(CACHE).then(cache => {
    cache.put(request.url, response);
  })
} 

// Abfragen eines Requests aus dem Cache
const offlineModeReaction = event => {
  return caches.open(CACHE).then(cache => {
    return cache.match(event.request);
  })
}

Ist man in der DSO.isys-web-Anwendung im Offline-Modus, so kann man den Rest der Anwendung nicht verwenden. So ist eine Navigation über das Menu gezielt deaktiviert und auch eine Änderung der URL führt immer zur Offline-OP-Entnahme zurück. Dieses Feature erreichen wir durch eine eigens zusammengebaute Redirect-Response im Service Worker:

Listing 6: Redirect auf OP-Entnahme im Offline-Modus

const redirectReaction = event => {
    return localforage.getItem(SPM_ID).then(id => {
      const responsePath = `${uiBasePath}/#/spv/${id}/OPEntnahme`;
      const redirectRequest = createRequest(responsePath, 'GET');
      return Response.redirect(redirectRequest.url);
    })
}

Auf diese Weise muss der Anwender die Offline-OP-Entnahme erst fertigstellen, bevor er auch zu anderen Teilen der Anwendung navigieren kann. Auch ein Schließen des Browsers oder Neustart des Rechners ändert daran nichts: Beim erneuten Aufruf der DSO.isys web-Anwendung gelangt der Anwender im Offline-Modus immer wieder zur Offline-OP-Entnahme.

Schlechte Internetverbindung im Krankenhaus

Für das Starten des Offline-Modus' ist eine stabile Internetverbindung notwendig, da hier viele Requests für das Precaching abgeschickt werden. Da es im Krankenhaus gelegentlich eine schlechte Internetverbindung gibt und der Start des Offline-Modus wegen einer kurzen Unterbrechung der Internetverbindung nicht gleich fehlschlagen soll, haben wir uns einen Retry-Mechanismus überlegt:

Gibt es beim Precaching einen Fehler, dann wird per Retry ein erneuter Versuch gestartet. Die Anzahl der Versuche sowie die Wartezeit dazwischen sind konfigurierbar. Kurze Unterbrechungen der Internetverbindung verhindern demnach das Precaching nicht.

Daten können offline verändert werden und online hochgeladen werden

Der Offline-Modus im DSO.isys web dient nicht nur der Anzeige von OP-Entnahme-Daten, es werden offline auch Daten angepasst oder erzeugt. So muss der Anwender während einer Operation beispielsweise Medikationen erfassen oder auch den Zustand der entnommenen Organe beschreiben. Dabei stehen ihm nicht alle Funktionalitäten der OP-Entnahme zur Verfügung, beispielsweise ist der Zugriff auf das Kontaktmanagement von DSO.isys web im Offline-Modus gesperrt. Veränderte Daten werden im Offline-Modus bei Klick auf "Zwischenspeichern" zunächst in den Cache geschrieben. Dabei werden ggfs. bereits vorhandene Daten im Cache überschrieben.

Klickt der Anwender nach der OP auf den Button "Online Modus", dann wird zunächst in einen Zwischen-Zustand gewechselt. Im Status PrepareOnlineMode ist der Anwender wieder online, hat aber die Offline-Daten noch nicht in der Datenbank gespeichert. Der Anwender hat hier die Möglichkeit, Daten noch einmal anzupassen (denn jetzt stehen ihm alle Funktionen der Anwendung zur Verfügung) bevor er sie anschließend auf dem Server speichert. Beim endgültigen Speichern über den Anwendungs-Server wird das Netzwerk verwendet und alle Daten aus dem Cache gelöscht.

Online auf Knopfdruck (mit Prüfung der Internetverbindung)

Im Projekt DSO.isys web arbeiten die Service Worker nicht mit dem echten Zustand der Internetverbindung, es gibt wie schon beschrieben eigene States, die dem Service Worker den jeweiligen Zustand mitteilen und die gewünschte Reaktion hervorrufen. Beispielsweise arbeitet der Service Worker im Zustand OfflineMode so, als wäre er offline – auch wenn es trotzdem eine Internetverbindung gibt. Möchte der Anwender nach einer OP-Entnahme wieder in den Online-Zustand wechseln, ist es jedoch wichtig, dass nicht nur der Service Worker in den State OnlineMode wechselt, sondern dass auch wirklich eine Internetverbindung vorhanden ist (denn es müssen ja geänderte Daten via Netzwerk in die Datenbank geschrieben werden).

Die Prüfung der Internetverbindung haben wir mit einem eigenen Request umgesetzt:

Listing 7: Prüfung der Internetverbindung beim Wechsel in den Online-Modus
(Teil 1)

// Prüfen, ob der Anwender wirklich eine Internetverbindung besitzt:
this.opEntnahmeService.checkOnlineStatus().then(
response => {
      	if (response.status == 200 && response.data == true) {
            	// Internetverbindung vorhanden, Online Modus vorbereiten
                  this.updateUiState(UIState.PREPARE_ONLINE);
                  this.setupOnlineMode();
            } else {
                  // Internetverbindung nicht vorhanden, Meldung anzeigen
                  this.noInternetConnectionDialog.open();
            }
      }
);

Der Service Worker benutzt für den CheckOnlineStatus-Request natürlich nicht den Cache, sondern das Netzwerk:

Listing 8: Prüfung der Internetverbindung beim Wechsel in den Online-Modus
(Teil 2)

const offlineModeReaction = event => {
  if (isCheckOnlineStatusRequest(event.request)) {
    // verwende nicht den Cache (Online-Status soll vor dem
    // Wieder-Online-Gehen überprüft werden)
    return fetch(event.request);
  }  
}

Die Aktualisierung des Service Workers muss gemeinsam mit der Aktualisierung des Codes von DSO.isys web erfolgen

Während der Weiterentwicklung einer Anwendung, die vom Service Worker kontrolliert wird, kommt es durch neue Features oder auch Fehlerbehebung irgendwann vor, dass auch am Code des Service Workers etwas geändert werden muss. Es ist also eine "alte" Version der Service-Worker-Datei gerade bei einigen Anwendern live im Einsatz und wird während der Offline-OP-Entnahme verwendet. Und eine "neue" Version der Service-Worker-Datei ist bereits auf dem Anwendungs-Server nach dem Bereitstellen eines neuen Releases verfügbar. Es muss jetzt also der Service-Worker-Code ausgetauscht bzw. aktualisiert werden, ohne Fehler bei den Anwendern zu produzieren, die genau jetzt mit dem "alten" Service Worker eine Offline-OP-Entnahme durchführen.

Normalerweise wird der Service-Worker-Code automatisch durch den Browser aktualisiert, wenn sich an dessen JS-Datei Änderungen ergeben. Wir als Entwickler müssen uns darum also in der Regel nicht kümmern:

  • Beim Laden der App wird die JS-Datei des Service Workers byte für byte auf Änderungen überprüft.
  • Hat sich etwas am Code geändert, dann wird die neue Version des Service Workers automatisch in den Browser geladen und installiert.
  • Sie wird aber noch nicht aktiviert, sondern befindet sich im Zustand waiting.
  • Wie in Abb. 10 zu sehen, sind zu dem Zeitpunkt also zwei Service-Worker-Versionen im Browser zu finden.
  • Erst wenn keine Seite der App des alten Service Workers mehr durch den Anwender im Browser geladen wird, wird die neue Version aktiviert und die alte Version gelöscht.
  • Die Aktualisierung geschieht automatisch durch den Browser, dafür muss der Entwickler nichts tun. Für die komplette Aktualisierung muss der Browser i.d.R. zweimal neu gestartet werden.

Im Projekt DSO.isys web muss die Aktualisierung des Service Workers allerdings gemeinsam mit dem Code der Anwendung erfolgen, da es durch die besonderen Umstände (Offline auf Knopfdruck) Abhängigkeiten gibt. Daher haben wir uns für eine Deregistrierung des Service Workers nach dem Beenden des Offline-Modus' entschieden:

Listing 9: Deregistrierung des Service Workers

// In der DSO.isys web Anwendung
private deactivateServiceWorker() {
  this.notifyServiceWorker({
    msgName: 'deactivateServiceWorker',
    payload: null
  });
}

// Im Service Worker
DEACTIVATE: () => {
  // gehe in den "Idle"-Zustand (Online) über, lösche vorher alle Daten
    clearAllData().then(() => {
      return updateState(States.ONLINE_MODE);
    }).then(() => {
      self.registration.unregister()
        .then(() => {
          notifyClients({
            msgName: 'serviceWorkerDeactivated',
            payload: null
          })
        })
    });
}

Der Service Worker wird somit bei jedem Neustarten des Offline-Modus' komplett neu geladen und in dieser neuen Version installiert.

Fazit

Mit dem Projekt DSO.isys web haben wir eine spannende und sehr komplexe Web-Anwendung entwickelt, die durch Features wie z. B. die Offline-OP-Entnahme auch technisch sehr interessant ist. Mit unserem Vorwissen im Bereich Javascript, Promises und Frontend konnten wir mit den Service Workern relativ einfach starten und die gewünschte Offline-Funktionalität umsetzen – trotz aller Besonderheiten. Zwar hätten wir uns eine bessere Typescript-Unterstützung gewünscht und würden uns beim nächsten Mal die vorhandenen APIs (z. B. Workbox von Google) genauer anschauen und diese evtl. auch einsetzen. Insgesamt können wir aber ein durchaus positives Fazit ziehen: Es macht Spaß mit Service Workern zu arbeiten und ein beeindruckendes Offline-Nutzer-Erlebnis zu schaffen. Und auch unser Kunde, die Deutsche Stiftung für Organtransplantation, ist mit dem Ergebnis sehr zufrieden! Und ganz nebenbei unterstützen wir mit diesem Projekt eine sehr wichtige, medizinische Therapie-Möglichkeit: die Organspende!

Quellen
  1. Workbox Strategies
  2. Service Workers: An Introduction
  3. Wikipedia: Organspende

Autorin

Katharina Siegrist

Katharina Siegrist ist Managing Consultant bei der Accso - Accelerated Solutions GmbH und beschäftigt sich schon seit vielen Jahren mit Frontend-Lösungen im Web- und Mobil-Umfeld.
>> Weiterlesen
Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben