60

I have just attempted to implement service workers to cache some JSON files and other assets on a static site (running on localhost chrome Version 47.0.2526.73 (64-bit)). Using cache.addAll() I have added my files to the cache, and when I open the resources tab in chrome, and click on cache storage, all the files are listed.

screenshot json files shown in cache

The issue I am having is that my service worker is listed as "activated" and "running" in chrome://service-worker-internals however, I cannot determine if the worker is actually intercepting the requests and serving up the cached files. I have added the event listener and even when I console log the event in the service workers dev tools instance, it never hits the break point:

this.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      console.log(cache);
      return cache.addAll([
        '/json/0.json',
        '/json/1.json',
        '/json/3.json',
        '/json/4.json',
        '/json/5.json',
      ]);
    })
  );
});

this.addEventListener('fetch', function(event) {
  console.log(event);
  var response;
  event.respondWith(caches.match(event.request).catch(function() {
    return fetch(event.request);
  }).then(function(r) {
    response = r;
    caches.open('v1').then(function(cache) {
      cache.put(event.request, response);
    });
    return response.clone();
  }).catch(function() {
  }));
});

Basically I am running through things exactly as described in HTML5 rocks service workers intro, but I am pretty sure that my assets aren't being served from the cache. I've noted that assets served up from a service worker are noted as such in the network tab of devtools in the size column by indicating 'from service workers'.

It just seems as if my code is no different than the examples but it's not ever hitting the fetch event for some reason. Gist of my code: https://gist.github.com/srhise/c2099b347f68b958884d

Kinlan
  • 16,315
  • 5
  • 56
  • 88
srhise
  • 713
  • 1
  • 5
  • 7

5 Answers5

93

After looking at your gist and your question, I think your issue is with scoping.

From what I've determined with service workers(at least with static files), the service worker only has a maximum scope of the directory it is in. That is to say, it can't load files/requests/responses that are pulled from a location at or above its structure, only below.

For example, /js/service-worker.js will only be able to load files in /js/{dirName}/.

Therefore, if you change the location of your service worker to the root of your web project, the fetch event should fire and your assets should load from cache.

So something like /service-worker.js, which should be able to access the /json directory, since it is deeper than the service-worker.js file.

This is further explained here, in the "Register A service worker" section. https://developers.google.com/web/fundamentals/getting-started/primers/service-workers

Stephen Archer
  • 984
  • 7
  • 6
  • Stephen's answer should be marked as correct. I just ran into the same issue as I stored my web-worker.js in a subfolder /assets/js/ . While files were stored and the web worker was registered, it could not fetch the get requests. After I moved it to the root directory for this domain it worked fine. – user1532132 Mar 16 '17 at 12:25
  • Is the service-worker able to access files in it's own directory? i.e `app/service-worker.js`, can it read `app/offline-page.html`? – tdc Apr 30 '17 at 19:31
  • @Prefix Yes, it should be able to. – Stephen Archer May 02 '17 at 19:55
  • I got the same problem if serviceworker not in root directory, does serviceworker really can locate in sub folder? – YDSS May 25 '17 at 12:33
  • Thanks. Worked for me too! – Robin Wieruch Jul 15 '17 at 16:41
  • I just set up a interval function, right into sw.js file, and fetched some files from same directory, it still did not work for me. – Teoman shipahi Mar 09 '18 at 16:51
  • navigator.serviceWorker.register('scripts/sw.js', { scope: '/' }) – VyvIT May 11 '18 at 14:35
  • Doesn't work for me. I did NEVER get any FETCH-event when the service worker was in a sub-folder. I tried any possible URL underneath the service-worker or on the same level. Any hint appreciated. – Chuck Sep 18 '18 at 09:33
  • Hey Chuck, are you sure that the service worker is: 1) Active, and 2) has "claimed" the web page? Both of these conditions should be shown on the Application tab in chrome dev tools – Stephen Archer Sep 18 '18 at 19:55
  • Well, after 4 years I’m finally going to mark this as the correct answer ‍♂️ – srhise Jul 24 '19 at 23:02
  • successfully wasted 1 hour on debugging the issue. finally found a solution – Mr. Alien Apr 29 '20 at 19:44
  • Same problem of Chuck here. Any solution? – Guilherme do Valle Mar 10 '22 at 18:28
37

I struggled with this for a long time, and I think the documentation related to the matter is seriously lacking. In my experience, there is a very important distinction:

The service worker can only intercept fetch events if it is in or above the scope of the URL it is accessed from.

For example, my sw.js file was located at /static/sw.js. When accessing my site's root at / and attempting to intercept fetch events to js files in /static/js/common.js, the fetch events were not intercepted, even though the scope of my service worker was /static/ and the js file was in /static/js/.

Once I moved my sw.js file to the top-level scope /sw.js, the fetch events were all intercepted. This is because the scope of the page I was accessing with my browser / was the same as the scope of my sw.js file /.

Please let me know if this clears things up for people, or if I am incorrect!

garviand
  • 397
  • 3
  • 7
5

The exact code in the HTML5Rocks article is

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }

        // IMPORTANT: Clone the request. A request is a stream and
        // can only be consumed once. Since we are consuming this
        // once by cache and once by the browser for fetch, we need
        // to clone the response
        var fetchRequest = event.request.clone();

        return fetch(fetchRequest).then(
          function(response) {
            // Check if we received a valid response
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            // IMPORTANT: Clone the response. A response is a stream
            // and because we want the browser to consume the response
            // as well as the cache consuming the response, we need
            // to clone it so we have 2 stream.
            var responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});

The biggest thing that I can see is that you are not cloning the request from the fetch, you need to clone it because it is read twice, once when being used to access the network (in the fetch) and once when being used as the key to the cache.

Kinlan
  • 16,315
  • 5
  • 56
  • 88
4

If you don't see the mark from service worker then your page is not controlled by the service worker yet (you can check by inspecting navigator.serviceWorker.controller in the client page). The default behaviour for a page is to become controlled the next time you visit it after SW activation so you have two options:

  • Refresh the page after registration.
  • Use self.skipWaiting() and self.clients.claim() methods during installation and activation respectively to force the service worker to take control of the clients ASAP.

Look at the Service Worker Cookbook, it includes an JSON cache recipe.

Once you fix the problem with control, you need to fix your handler. I think you want to apply a policy of cache-first. If not in cache, then go to network and fill the cache. To do that:

self.onfetch = function (event) {
  var req = event.request;
  return event.respondWith(function cacheFirst() {
    // Open your cache.
    return self.caches.open('v1').then(function (cache) {
      // Check if the request is in there.
      return cache.match(req).then(function (res) {
        // If not match, there is no rejection but an undefined response.
        if (!res) {
          // Go to network.
          return fetch(req.clone()).then(function (res) {
            // Put in cache and return the network response.
            return cache.put(req, res.clone()).then(function () {
              return res;
            });
          });
        }
        return res;
      });
    });
  });
}
Salva
  • 6,507
  • 1
  • 26
  • 25
  • I never seen such implementation using `return self.caches.open('v1').then(function (cache) {` can you give me the link for this appraich – xkeshav Jul 24 '17 at 09:03
-2

I had same problem when I used sw.js with Django Framework. Here I've found solution.

Taras
  • 447
  • 2
  • 7
  • 18