Offline-Daten

Damit Sie auch im Offlinemodus präsent sind, benötigt Ihre PWA eine Speicherverwaltung. Im Kapitel zum Caching haben Sie gelernt, dass der Cache eine Option zum Speichern von Daten auf einem Gerät ist. In diesem Kapitel erfahren Sie, wie Sie Offlinedaten verwalten, einschließlich Datenpersistenz, Limits und die verfügbaren Tools.

Speicher

Der Speicher umfasst nicht nur Dateien und Assets, sondern kann auch andere Datentypen enthalten. In allen Browsern, die PWAs unterstützen, sind die folgenden APIs für den On-Device-Speicher verfügbar:

  • IndexedDB: Eine NoSQL-Objektspeicheroption für strukturierte Daten und Blobs (Binärdaten).
  • WebStorage: Eine Möglichkeit, Schlüssel/Wert-Zeichenfolgenpaare unter Verwendung des lokalen Speichers oder Sitzungsspeichers zu speichern. Sie ist innerhalb eines Service Worker-Kontexts nicht verfügbar. Da diese API synchron ist, wird sie für komplexe Datenspeicher nicht empfohlen.
  • Cache-Speicher: Wie im Caching-Modul beschrieben.

Sie können den gesamten Gerätespeicher mit der Storage Manager API auf unterstützten Plattformen verwalten. Die Cache Storage API und IndexedDB bieten für PWAs asynchronen Zugriff auf nichtflüchtigen Speicher und können über den Hauptthread, Web Worker und Service Worker aufgerufen werden. Beide tragen entscheidend dazu bei, dass PWAs auch dann zuverlässig funktionieren, wenn das Netzwerk instabil oder nicht vorhanden ist. Aber wann sollten sie verwendet werden?

Verwenden Sie die Cache Storage API für Netzwerkressourcen, d. h. Inhalte, auf die Sie über eine URL zugreifen können, z. B. HTML, CSS, JavaScript, Bilder, Videos und Audio.

Verwenden Sie IndexedDB zum Speichern strukturierter Daten. Dazu gehören Daten, die in NoSQL-ähnlicher Weise durchsuchbar oder kombinierbar sein müssen, sowie andere Daten wie nutzerspezifische Daten, die nicht unbedingt mit einer URL-Anfrage übereinstimmen. IndexedDB ist nicht für die Volltextsuche vorgesehen.

IndexedDB

Öffnen Sie zuerst eine Datenbank, um IndexedDB zu verwenden. Dadurch wird eine neue Datenbank erstellt, sofern noch keine vorhanden ist. IndexedDB ist ein asynchrones API, nimmt jedoch einen Callback an, statt ein Promise zurückzugeben. Im folgenden Beispiel wird die idb-Bibliothek von Jake Archibald verwendet, ein winziger Promise-Wrapper für IndexedDB. Hilfsbibliotheken sind zur Verwendung von IndexedDB nicht erforderlich. Wenn Sie jedoch die Promise-Syntax verwenden möchten, ist die Bibliothek idb eine Option.

Im folgenden Beispiel wird eine Datenbank für Kochrezepte erstellt.

Datenbank erstellen und öffnen

So öffnen Sie eine Datenbank:

  1. Erstellen Sie mit der Funktion openDB eine neue IndexedDB-Datenbank mit dem Namen cookbook. Da IndexedDB-Datenbanken versioniert sind, müssen Sie die Versionsnummer jedes Mal erhöhen, wenn Sie Änderungen an der Datenbankstruktur vornehmen. Der zweite Parameter ist die Datenbankversion. Im Beispiel ist der Wert auf 1 festgelegt.
  2. Ein Initialisierungsobjekt, das einen upgrade()-Callback enthält, wird an openDB() übergeben. Die Callback-Funktion wird aufgerufen, wenn die Datenbank zum ersten Mal installiert oder auf eine neue Version aktualisiert wird. Dies ist der einzige Ort, an dem Aktionen ausgeführt werden können. Aktionen können das Erstellen neuer Objektspeicher (die Strukturen, die IndexedDB zum Organisieren von Daten verwendet) oder Indexe (die Sie durchsuchen möchten) umfassen. An dieser Stelle sollte auch die Datenmigration stattfinden. In der Regel enthält die Funktion upgrade() eine switch-Anweisung ohne break-Anweisungen, damit die einzelnen Schritte entsprechend der alten Version der Datenbank in der richtigen Reihenfolge ausgeführt werden können.
import { openDB } from 'idb';

async function createDB() {
  // Using https://github.com/jakearchibald/idb
  const db = await openDB('cookbook', 1, {
    upgrade(db, oldVersion, newVersion, transaction) {
      // Switch over the oldVersion, *without breaks*, to allow the database to be incrementally upgraded.
    switch(oldVersion) {
     case 0:
       // Placeholder to execute when database is created (oldVersion is 0)
     case 1:
       // Create a store of objects
       const store = db.createObjectStore('recipes', {
         // The `id` property of the object will be the key, and be incremented automatically
           autoIncrement: true,
           keyPath: 'id'
       });
       // Create an index called `name` based on the `type` property of objects in the store
       store.createIndex('type', 'type');
     }
   }
  });
}

Im Beispiel wird ein Objektspeicher mit dem Namen recipes in der cookbook-Datenbank erstellt, wobei das Attribut id als Indexschlüssel des Speichers festgelegt ist. Außerdem wird ein weiterer Index mit dem Namen type erstellt, der auf dem Attribut type basiert.

Sehen wir uns den soeben erstellten Objektspeicher an. Nachdem Sie Schemas zum Objektspeicher hinzugefügt und die Entwicklertools in Chromium-basierten Browsern oder den Web Inspector in Safari geöffnet haben, sollte Folgendes angezeigt werden:

In Safari und Chrome werden IndexedDB-Inhalte angezeigt.

Daten hinzufügen

IndexedDB verwendet Transaktionen. In Transaktionen werden Aktionen gruppiert, sodass sie eine Einheit bilden. Sie tragen dazu bei, dass die Datenbank immer in einem konsistenten Zustand ist. Sie sind auch wichtig, wenn mehrere Kopien Ihrer App ausgeführt werden, um gleichzeitiges Schreiben in dieselben Daten zu verhindern. So fügen Sie Daten hinzu:

  1. Starten Sie eine Transaktion und setzen Sie mode auf readwrite.
  2. Rufen Sie den Objektspeicher ab, in dem Sie Daten hinzufügen können.
  3. Rufen Sie add() mit den Daten auf, die Sie speichern. Die Methode empfängt Daten in Wörterbuchform (als Schlüssel/Wert-Paare) und fügt sie dem Objektspeicher hinzu. Das Wörterbuch muss mithilfe von strukturiertem Klonen geklont werden können. Wenn Sie ein vorhandenes Objekt aktualisieren möchten, rufen Sie stattdessen die Methode put() auf.

Transaktionen haben ein done-Promise, das aufgelöst wird, wenn die Transaktion erfolgreich abgeschlossen wurde, oder mit einem Transaktionsfehler abgelehnt wird.

Wie in der Dokumentation zur IDB-Bibliothek erläutert, gibt tx.done beim Schreiben in die Datenbank an, dass der Commit für alle Elemente erfolgreich abgeschlossen wurde. Es ist jedoch sinnvoll, auf einzelne Vorgänge zu warten, damit Sie Fehler sehen können, die zum Scheitern der Transaktion führen.

// Using https://github.com/jakearchibald/idb
async function addData() {
  const cookies = {
      name: "Chocolate chips cookies",
      type: "dessert",
        cook_time_minutes: 25
  };
  const tx = await db.transaction('recipes', 'readwrite');
  const store = tx.objectStore('recipes');
  store.add(cookies);
  await tx.done;
}

Nachdem Sie die Cookies hinzugefügt haben, befindet sich das Rezept zusammen mit anderen Rezepten in der Datenbank. Die ID wird von indexiertDB automatisch festgelegt und erhöht. Wenn Sie diesen Code zweimal ausführen, erhalten Sie zwei identische Cookie-Einträge.

Daten werden abgerufen…

So rufen Sie Daten von IndexedDB ab:

  1. Starten Sie eine Transaktion und geben Sie den oder die Objektspeicher sowie optional den Transaktionstyp an.
  2. Rufen Sie aus dieser Transaktion objectStore() auf. Geben Sie unbedingt den Namen des Objektspeichers an.
  3. Rufen Sie get() mit dem gewünschten Schlüssel auf. Standardmäßig verwendet der Speicher seinen Schlüssel als Index.
// Using https://github.com/jakearchibald/idb
async function getData() {
  const tx = await db.transaction('recipes', 'readonly')
  const store = tx.objectStore('recipes');
// Because in our case the `id` is the key, we would
// have to know in advance the value of the id to
// retrieve the record
  const value = await store.get([id]);
}

Speichermanager

Damit Netzwerkantworten richtig gespeichert und gestreamt werden können, müssen Sie wissen, wie Sie den Speicher Ihrer PWA verwalten.

Die Speicherkapazität wird von allen Speicheroptionen gemeinsam genutzt, einschließlich Cache Storage, IndexedDB, Web Storage und sogar der Service Worker-Datei und ihren Abhängigkeiten. Der verfügbare Speicherplatz variiert jedoch von Browser zu Browser. Sie werden wahrscheinlich nicht aufgebraucht werden. können Websites in einigen Browsern Megabyte und sogar Gigabyte an Daten speichern. Chrome erlaubt dem Browser beispielsweise, bis zu 80% des gesamten Speicherplatzes zu nutzen, und ein einzelner Ursprung kann bis zu 60% des gesamten Speicherplatzes belegen. Bei Browsern, die die Storage API unterstützen, können Sie sehen, wie viel Speicherplatz noch für Ihre Anwendung verfügbar ist, und wie viel Speicherplatz noch verfügbar ist. Im folgenden Beispiel werden mithilfe der Storage API das geschätzte Kontingent und die Nutzung ermittelt. Anschließend werden die genutzten und die verbleibenden Byte in Prozent berechnet. Beachten Sie, dass navigator.storage eine Instanz von StorageManager zurückgibt. Es gibt eine separate Storage-Oberfläche, die leicht verwechselt werden kann.

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

In den Chromium-Entwicklertools können Sie das Kontingent Ihrer Website und den verwendeten Speicherplatz aufgeschlüsselt nach Auslastung sehen. Öffnen Sie dazu auf dem Tab Anwendung den Bereich Speicher.

Chrome-Entwicklertools in der Anwendung, Bereich „Speicherinhalt löschen“

In Firefox und Safari wird keine Zusammenfassung des gesamten Speicherkontingents und der gesamten Speichernutzung für den aktuellen Ursprung angezeigt.

Datenpersistenz

Sie können vom Browser auf kompatiblen Plattformen dauerhaften Speicher anfordern, um eine automatische Datenbereinigung nach Inaktivität oder bei Speicherauslastung zu vermeiden. Wenn diese Option gewährt wird, entfernt der Browser niemals Daten aus dem Speicher. Dieser Schutz umfasst die Service Worker-Registrierung, IndexedDB-Datenbanken und Dateien im Cache-Speicher. Beachten Sie, dass die Nutzer immer die Verantwortung dafür übernehmen und sie den Speicher jederzeit löschen können, auch wenn der Browser Ihnen nichtflüchtigen Speicher gewährt hat.

Rufen Sie StorageManager.persist() auf, um nichtflüchtigen Speicher anzufordern. Wie zuvor erfolgt der Zugriff auf die StorageManager-Schnittstelle über das Attribut navigator.storage.

async function persistData() {
  if (navigator.storage && navigator.storage.persist) {
    const result = await navigator.storage.persist();
    console.log(`Data persisted: ${result}`);
}

Sie können auch prüfen, ob im aktuellen Ursprung bereits nichtflüchtiger Speicher gewährt wurde, indem Sie StorageManager.persisted() aufrufen. Firefox fordert vom Nutzer eine Berechtigung zur Verwendung des nichtflüchtigen Speichers an. Chromium-basierte Browser entscheiden anhand einer Heuristik, wie wichtig Inhalte für Nutzer sind, oder lehnen die Persistenz ab. Ein Kriterium für Google Chrome ist beispielsweise die PWA-Installation. Wenn der Nutzer im Betriebssystem ein Symbol für die PWA installiert hat, gewährt der Browser möglicherweise nichtflüchtigen Speicher.

Mozilla Firefox bittet den Nutzer um die Berechtigung zur Speicherpersistenz.

API-Browser-Unterstützung

Webspeicher

Unterstützte Browser

  • Chrome: 4. <ph type="x-smartling-placeholder">
  • Rand: 12. <ph type="x-smartling-placeholder">
  • Firefox: 3.5 <ph type="x-smartling-placeholder">
  • Safari: 4. <ph type="x-smartling-placeholder">

Quelle

Dateisystemzugriff

Unterstützte Browser

  • Chrome: 86 <ph type="x-smartling-placeholder">
  • Edge: 86. <ph type="x-smartling-placeholder">
  • Firefox: 111 <ph type="x-smartling-placeholder">
  • Safari: 15.2 <ph type="x-smartling-placeholder">

Quelle

Speichermanager

Unterstützte Browser

  • Chrome: 55 <ph type="x-smartling-placeholder">
  • Edge: 79. <ph type="x-smartling-placeholder">
  • Firefox: 57. <ph type="x-smartling-placeholder">
  • Safari: 15.2 <ph type="x-smartling-placeholder">

Quelle

Ressourcen