Search code examples
javascripteventscachingservice-workerworker

How to update cached resource by service worker


I have an offline app based on service worker which allows users to download pdf files offline.

There is a text file which has download path of the pdf files and titles of files (along with css, js, and images path) in a specific format, Application read this text file via ajax and draw the list of all available pdf files (by showing title and download link)

Worker read the same file and put all pdf files in the cache along with text file itself during the install event so that these files can be available offline.

The problem occurs when I update the text file with some new pdf files, the worker is able to update the updated text file in cache because the application give an ajax request to the file and worker able to update it in the cache via fetch event, but new added PDF files not added in the Cache as fetch not triggered for these.

// function to register worker add files in the cache 
this.addEventListener('install', event => {
    const client = clients.get(event.clientId);
    fetch('masterdata.txt')
        .then(
            function(response) {
                response.text().then(function(text) {
                    var documentFileArray = parseString(text);
                    var cacheDocs = [];
                     clients.claim();
                    var  count = 0;
                     caches.open(currentCache.offline).then(function(cache) {
                         var total_files = documentFileArray.length;
                         for(var i =0;i<total_files;i++){                            
                             cache.add(documentFileArray[i]).then(function(){   
                                 count++
                                 var data = {'count':count,'total':total_files}
                                 passMesagetoClient(clients,data)                            
                             })
                         }
                    })
                })
            }
        )
        .catch(function(err) {
            //console.log('Fetch Error :-S', err);
            clients.claim();
            passMesagetoClient(clients,'failed');
        });
});


//comman function to let client know service worker done caching files 
function passMesagetoClient(client,data){   
    client.matchAll().then(all => {
        all.map(client => {         
                client.postMessage({"status":"success","data":data})
            })
    }); 
}

//this will check and provide the files from cache if network not avalible 
this.addEventListener("fetch", function(event) {
    //var url = event.request.url;
       clients.claim();
       passMesagetoClient(clients,'success');
                        
        event.respondWith(
            fetch(event.request).then(function(response) {
                return caches.open(currentCache.offline).then(function(cache) {
                    return cache.put(event.request, response.clone()).then(function() {
                        return response
                    })
                })
            }).catch(function(e) {
                //console.log(e);
                return caches.match(event.request)
            })
        )
   
})

Is there any way around to update newly added pdf files in the cache if there is any update in the text file ?


Solution

  • Here is a simple solution. In the fetch event I check if the request is for the masterdata.txt and if so before I overwrite the response in the cache I get the old masterdata.txt from the cache and if it exists I compare both to check if they are different which means there are new files added then I fetch those files and add them to the cache.

    // add the files in the array to the cache
    async function cacheFiles(filesArray) {
      const count = 0;
      const cache = await caches.open(currentCache.offline);
      const totalFiles = filesArray.length;
      for (let i = 0; i < totalFiles; i++) {
        await cache.add(filesArray[i]);
        count++;
        const data = { count: count, total: totalFiles };
        passMesagetoClient(clients, data);
      }
    }
    
    // this will check and provide the files from cache if network not avalible
    this.addEventListener("fetch", function(event) {
      clients.claim();
      passMesagetoClient(clients, "success");
    
      event.respondWith(
        fetch(event.request)
          .then(async function(newResponse) {
            // If this is the text file
            if (event.request.url.includes("masterdata.txt")) {
              // try to get the text file from the cache
              const cachedResponse = await caches.match(event.request);
    
              // If it exists
              if (cachedResponse) {
                // convert both responses to text
                const cachedText = await cachedResponse.text();
                const text = await newResponse.text();
                // if they are different
                if (cachedText !== text) {
                  // If you can compare the old and new text to get the new urls
                  // If you can't just compare the two arrays as following
                  const oldFileArray = parseString(cachedText);
                  const fileArray = parseString(text);
                  // get the new urls
                  const newFileArray = fileArray.filter(
                    f => !oldFileArray.includes(f)
                  );
                  // fetch and cache them like you did in the install event
                  await cacheFiles(newFileArray);
                }
              }
            }
            // add the new response to the cache
            const cache = await caches.open(currentCache.offline);
            cache.put(event.request, newResponse.clone());
            return newResponse;
          })
          .catch(function(e) {
            // console.log(e);
            return caches.match(event.request);
          })
      );
    });
    

    I have another solution in mind which depends on how you update the masterdata.txt. If you do add the new files URLs to masterdata.txt from the client-side you can simply fetch and cache at the same time.