sw-toolbox icon indicating copy to clipboard operation
sw-toolbox copied to clipboard

Offline fallback with routers.

Open nicholasgriffintn opened this issue 9 years ago • 6 comments

Hey,

Over the past couple of days, I have been looking for the best ways to implement offline fallback with sw-toolbox, I've found some that simply don't work well, and others that don't do what I was looking for completely.

That got me thinking that it would be awesome if sw-toolbox had all of that built in.

We have routers with different cache settings around our service worker, so using something like the following code would not work as it overrides everything else:

global.toolbox.router.get('/(.*)', function(request, values, options) {
  return global.toolbox.networkOnly(request, values, options).catch(function(error) {
    if (request.method === 'GET' && request.headers.get('accept').includes('text/html')) {
      return toolbox.cacheOnly(new Request('offline'), values, options);
    }
    throw error;
  });
}, {}, {networkTimeoutSeconds: 3});

It would be great if there was an offline fallback for something like the default route, if there's any way to do this please let me know.

Thanks.

nicholasgriffintn avatar Sep 19 '16 20:09 nicholasgriffintn

I think it's cleanest to implement that bit of logic independent of sw-toolbox:

const OFFLINE_URL = '/path/to/offline.html';
toolbox.precache(OFFLINE_URL);

// Make sure this fetch event handler appears before any routing logic.
self.addEventListener('fetch', event => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => caches.match(OFFLINE_URL))
    );
  }
});

// Any toolbox.router.get() logic can go here.

I'm not sold on baking in special-purpose logic in sw-toolbox to handle failures due to offline navigations. What I would be in favor of would be an alternative to toolbox.router.get(), maybe called toolbox.router.navigation(), that behaved similarly to get(), but automatically performed an additional event.request.mode === 'navigate' check for you. That would make it easier to write handlers that only matched navigation requests, and not subresource requests.

Does that sound like something that would help with your use case, @nicholasgriffintn?

CC: @addyosmani @gauntface

jeffposnick avatar Sep 19 '16 20:09 jeffposnick

@jeffposnick Sorry, I am late at replying after our conversation on Twitter, I was just implementing something similar to the code you sent, but a little bit edited to work with the install and activate events that we already have, and that does serve the purpose.

That said, I can't help but think that having this kind of integration into sw-toolbox would be a good thing. I would like to serve offline fallbacks for various routers, whilst keeping the great caching functionality that sw-toolbox has, with the easy to understand and write code.

Something similar to the following would be awesome, where i could just type in a single line to enable fallbacks for an image, or HTML (image is one I just thought of, but it would be awesome).

self.toolbox.router.get('/a-page/', self.toolbox.networkFirst, {
    cache: {
        networkTimeoutSeconds: 3,
        name: 'pageCache',
        maxEntries: 50,
        maxAgeSeconds: 1 * 24 * 60 * 60,
        offlineFallbackpage: '/offline'
    }
});


self.toolbox.router.get('/assets/(.*)', self.toolbox.cacheFirst, {
    cache: {
        networkTimeoutSeconds: 3,
        name: 'imageCache',
        maxEntries: 50,
        maxAgeSeconds: 1 * 24 * 60 * 60,
        offlineFallbackimage: '/offline.png'
    }
});

If an alternative like "toolbox.router.navigation()" could do similar functions to what ".get" does, or if the two could be loaded together, then that would be fine as well.

nicholasgriffintn avatar Sep 19 '16 21:09 nicholasgriffintn

I'm not in favor of something resource-type-specific, like offlineFallbackpage, or offlineFallbackimage. You'd end up with a large number of similarly-named options that were tied to the types of resources.

I can see the benefit of do something more generic, like fallbackCacheKey, which would allow you to specify the URL/Request that would be used to look up a cached response for a given handler, instead of the default behavior, where it unconditionally uses event.request as the cache key.

(I still kind of like toolbox.router.navigation(), but that could be something done independent of this new option.)

jeffposnick avatar Sep 19 '16 21:09 jeffposnick

I see what you mean, that would cause problems, I think as long as it offers those same capabilities like cache name, network timeout, max entries and the ability to cache pages as well, all from within sw-toolbox's easy code, I think any kind of implementation for fallback could work great for all users.

nicholasgriffintn avatar Sep 19 '16 21:09 nicholasgriffintn

This might be me misunderstanding the use case but couldn't you get the same affect by writing:

const addFallback = (cacheFunc, offlineAsset) => {
  return (request, values, options) => {
    return cacheFunc(request, values, options)
    .catch(err => {
      return fetch(offlineAsset);
    });
  };
};

self.toolbox.router.get(
  '/assets/(.*)',
  addFallback(self.toolbox.cacheFirst, '/offline.png'),
  {
    cache: {
        networkTimeoutSeconds: 3,
        name: 'imageCache',
        maxEntries: 50,
        maxAgeSeconds: 1 * 24 * 60 * 60,
    }
  }
);

gauntface avatar Sep 21 '16 18:09 gauntface

@gauntface The would work, however, using it with "/(.*)" would overtake other routers, which was mostly the problem, whereas the default router doesn't overwrite, unless that would work with the default router of course.

nicholasgriffintn avatar Sep 21 '16 20:09 nicholasgriffintn