Dynamic Data

Storing dynamic data (other than images, scripts, html) in the cache isn't the most effective way to cache stuff. You really should consider IndexedDB for this.

Caching Dynamic Content with IndexedDB

Firefox and Edge do not support IndexedDB in private browsing mode.
https://caniuse.com/#feat=indexeddb

IndexedDB

IndexedDB is a asynchronous transactional key-value database in the browser for data that changes frequently and is typically in JSON format. You can save the complete data, or parts of it, or even transform it.

Transactional means that if one of the actions within an operation fails, none of those actions are applied to keep database integrity.

Typically you have one Database per app with one or more Object Stores (comparable with Tables). Inside the Object Store you store an Object, the data you want to save.

IndexedDB itself is not that nice to use out-of-the-box with a lot of events and callbacks.
Because of that it is recommended to use a tiny wrapper called IDB by Jake Archibald. This package mostly mirrors the IndexedDB API, but with small improvements and promises that make a big difference to usability.

To use it in your Service Worker you will need to import the idb.js file and add it to your static files cache for when the user is offline.

// sw.js

importScripts("/idb.js");

const STATIC_FILES = ["/", "/index.html", "/offline.html", "/idb.js", "/app.js", "/app.css", "/image.png"];

We now want to replace the dynamic cache with IndexedDB for the dynamic (JSON) content.





 
 





 
 
 
 
 
 
 
 




 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 





























// sw.js

importScripts("/idb.js");

const DYNAMIC_DB = "dynamic";
const DYNAMIC_DB_VERSION = "v1";
const DYNAMIC_CACHE = "dynamic-v2";
const STATIC_CACHE = "static-v3";
const STATIC_FILES = ["/", "/index.html", "/offline.html", "/idb.js", "/app.js", "/app.css", "/image.png"];
const url = "https://whatever.com/json";

// open database and get a callback after it is opened
const DB = idb.open(DYNAMIC_DB, DYNAMIC_DB_VERSION, function(db) {
  // create a store if it doesn't exist already
  if (!db.objectStoreNames.contains("articles")) {
    // name the store and set the primary key
    db.createObjectStore("articles", { keyPath: "id" });
  }
});

self.addEventListener("fetch", event => {
  if (event.request.url.indexOf(url) > -1) {
    event.respondWith(
      fetch(event.request).then(res => {
        // store data in IndexedDB
        const cloneRes = res.clone();
        cloneRes.json().then(data => {
          // CHECK DATA FORMAT for example !!!
          for (let key in data) {
            dbPromise.then(db => {
              const tx = db.transaction("articles", "readwrite");
              const store = tx.objectStore("articles");
              store.put(data[key]);
              return tx.complete;
            });
          }
        });
        // return original response
        return res;
      })
    );
  } else if (STATIC_FILES.includes(event.request.url)) {
    event.respondWith(caches.match(event.request));
  } else {
    event.respondWith(
      caches.match(event.request).then(response => {
        if (response) {
          return response;
        } else {
          return fetch(event.request)
            .then(res => {
              return caches.open(DYNAMIC_CACHE).then(cache => {
                cache.put(event.request.url, res.clone());
                return res;
              });
            })
            .catch(err => {
              if (event.request.headers.get("accept").includes("text/html")) {
                return caches.open(STATIC_CACHE).then(cache => {
                  return cache.match("/offline.html");
                });
              }
            });
        }
      })
    );
  }
});
  • Seperate stuff?
  • Also include app.js for dynamic content
  • Look at more examples from course
  • Create a utility file!
  • Attention to deleted items on the source!