3

I am using service-worker to cache a few static files but I am also trying to cache my json data in the indexedDB. So that whenever my app accesses the url "www.someurl.com/api/my-items" it gets intercepted by the service worker and instead, a custom response is returned with my indexedDB data.

I am using the promise based idb from here https://github.com/jakearchibald/idb

So far I came up with the following code. As I understand, I need to intercept the fetch event and return a custom response.

importScripts('idb.js');
var pageCache = 'v1';
var idbName = 'data';
var idbTableName = 'idbtable';

var cacheFiles = [
'../js/',
'../css/file1.css',
'../css/fle2.css'
];

//Install and Activate events
 //...

//Fetch Event
self.addEventListener('fetch', (event) => {
var requestUrl = new URL(event.request.url);

    if (requestUrl.origin !== location.origin) {
        //...

        if(event.request.url.endsWith('/api/my-items')){
          event.respondWith(

              idb.open(idbName, 1).then((db) => {
                  var tx = db.transaction([idbTableName], 'readonly');
                  var store = tx.objectStore(idbTableName);
                  return store.getAll();
              }).then(function(items) {
                  return new Response(JSON.stringify(items),  { "status" : 200 , "statusText" : "MyCustomResponse!" })
              })

          )

        } 

        //...
    }
 })

I am trying to understand if there is a cleaner way of writing this code, without specifically creating a response with "new Response()". I am sure there is a promise based concept I do not fully understand.

Anand
  • 9,672
  • 4
  • 55
  • 75
Roman Gherta
  • 821
  • 2
  • 15
  • 27
  • hi Roman, did you find success in intercepting and getting the data from indexed-db and returning as response. – Aji Sep 10 '18 at 19:38
  • 1
    @Aji Roman wasn't looking for a successful means of getting the data from IndexedDb and returning as a Response - his code already does that successfully. He was asking if there was a cleaner way of doing it, without specifically creating a Response. IMO, there's not - a Response has to be explicitly created. But I'm open to correction! – Velojet Feb 04 '20 at 03:19

3 Answers3

2

i came across the same situation where its not custom response i need to intercept the Http call and look the same URL in Indexed db where i already pushed the URL and response and read from there and give as a response

In the Service worker fetch event, i implemented the network first approach that means it will look for the service first if any error happened then read from the indexed db and return the response.

fetch(event.request).catch(function (result) { });

  self.addEventListener('fetch', function (event) {
     if (event.request.url.includes('[yourservicecallName]')) {
                event.respondWith(
                    fetch(event.request).catch(function (result) {
                       return readtheDatafromIndexedDb('firstDB', 'firstStore', event.request.url).then(function(response)
                        {
                            return response;
                        });
                    })
                );
            }
  });

method to read from the Indexed db and return the response

function readtheDatafromIndexedDb(dbName, storeName, key) {
   return new Promise((resolve, reject) => {
    var openRequest = indexedDB.open(dbName, 2);
    openRequest.onupgradeneeded = function (e) {
        let db = request.result;
        if (!db.objectStore.contains(storeName)) {
            db.createObjectStore(storeName, { autoIncrement: true });
        }
    }
    openRequest.onsuccess = function (e) {
        console.log("Success!");
        db = e.target.result;
        var transaction = db.transaction([storeName], "readwrite");
        var store = transaction.objectStore(storeName);
        var request = store.get(key);
        request.onerror = function () {
            console.log("Error");
            reject("unexpected error happened");
        }
        request.onsuccess = function (e) {
            console.log("return the respose from db");
            //JSON.parse(request.result)
            resolve(new Response( request.result, { headers: { 'content-type':'text/plain' } } ));
        }
    }
    openRequest.onerror = function (e) {
        console.log("Error");
        console.dir(e);
    }
   });

}

hope this will help you.

Aji
  • 423
  • 8
  • 23
  • 1
    This doesn't answer the OP's question, which asked for "a cleaner way of writing this code, without specifically creating a response with `new Response()`." This answer *also* uses `new Response()`. – Velojet Jan 31 '20 at 21:10
1

I would recommend Cache Storage API using helper library like Workbox to make things easy. This SO answer discusses using IndexDB -idb helper classes vs cache API - work-box.

Workbox is form Chrome team who are leading the PWA implementation. Also WorkBox is their new re-written lib(from sw-precache) with years of learning. Worth considering.

Anand
  • 9,672
  • 4
  • 55
  • 75
  • This doesn't address the OP's question. As the [SO answer](https://stackoverflow.com/questions/45696113/workbox-is-caching-only-time-stamps-to-indexdb-how-to-intercept-with-json-data/45698366#45698366) states, Workbox simply "uses IndexedDB for some out-of-band housekeeping info, like timestamps", not for caching JSON data in the indexedDB, which is the OP's requirement. – Velojet Feb 22 '20 at 07:03
1

The OP asked for "a cleaner way of writing this code, without specifically creating a response with new Response()". In an earlier comment, I said "IMO, there's not - a Response has to be explicitly created". Having thought more about the issue and after implementing the following approach myself in an app, I believe there is "a cleaner way of writing this code".

Google's 'Live Data in the Service Worker' states "Once an IndexedDB database is created, data can then be read locally from IndexedDB rather than making network requests to a backend database" (my emphasis) - or, by extension, rather than making requests to a service worker, where, as stated in Google's 'High-performance service worker loading', "you'll end up introducing a small latency hit whenever a network request is made. There's overhead involved in starting up a service worker if it's not already running, and there's also overhead in passing the response from the service worker to the client that made the request.".

So, once the JSON data is stored in the browser's IndexedDB, your app can access it directly from there, without the need for your app to access the URL "www.someurl.com/api/my-items", thereby incurring the overhead of the service worker's mediation. (We assume there has been an initial network fetch from that address to load the JSON data into the IndexedDB.)

So if we extract the IndexedDB processing code from your respondWith() in your fetch event handler, we can use it to replace your fetch call in your app itself:

idb.open(idbName, 1).then((db) => {
    var tx = db.transaction([idbTableName], 'readonly');
    var store = tx.objectStore(idbTableName);
    return store.getAll();
})

or, to use Jake Archibald's latest idb implementation together with async/await:

const db = await idb.openDB(idbName);
return await db.getAll(idbTableName);
Velojet
  • 878
  • 11
  • 18