How can I cache external URLs using service worker?

Faced the same issue while implementing caching in service worker.The issue is how the icons are actually fetched from the server.

1.Request is made via the main url(from the application)(Highlighted in yellow). This is a static request which you are trying to cache i suppose.

2.Actual dynamic network request made to get the icon(Highlighted in red).

enter image description here

To fix this, you need to dynamically populate the cache(Something like this)-

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
    .then((response)=>{
      if(response){
        return response;
      }
      else{
        return fetch(event.request) // response of requests
        .then((res)=>{
          return caches.open('dynamic') //create dynamic cache
          .then((cache)=>{
            cache.put(event.request.url,res.clone());
            return res;
          })
        })
      }
    })
    .catch(()=>{})
  )
});

I had a read of the sw-toolbox docs and figured out how to do it. Just had to add this to my runtime caching:

// cache fonts hosted on google CDN
global.toolbox.router.get(/googleapis/, global.toolbox.fastest);

I can't see a way to cache the request as the service worker only responds to URLs within my app domain.

That's not correct. A service worker that's actively controlling a page will have an opportunity to intercept and respond to network requests for cross-origin resources; the standard fetch event will fire, and event.request.mode will either be "cors" or "no-cors", depending on the context of the request made by the page.

In short, as long as there's a service worker in control of a page, when that page makes any network request, for either same- or cross-origin resource, the service worker will be able to respond to the fetch event.


TLDR: Try Option 3. You'll thank me later.

From Google Docs:

By default, fetching a resource from a third party URL will fail if it doesn't support CORS. You can add a no-CORS option to the Request to overcome this, although this will cause an 'opaque' response, which means you won't be able to tell if the response was successful or not.

So

Option 1

Add no-cors header

var CACHE_NAME = 'my-site-cache-v1';
var urlsToPrefetch = [
  '/',
  '/styles/main.css',
  '/script/main.js',
  'https://fonts.googleapis.com/icon?family=Material+Icons'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        // Magic is here. Look the  mode: 'no-cors' part.
        cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {
           return new Request(urlToPrefetch, { mode: 'no-cors' });
        })).then(function() {
          console.log('All resources have been fetched and cached.');
        });
      })
  );
});

As OP said, when the resource is updated, it's hard to get the latest copy in this scenario. And another issue is, as I said you won't know whether the response was a success or not.

Option 2

Or like OP said, we can create a proxy server: Something simple as (Pseudocode, not tested, Node Express code)

var request = require('request');
app.get('/library', function(req,res) {
  // read the param 
  var thirdPartyUrl = req.query.thirdPartyUrl;
  request(thirdPartyUrl).pipe(res);
});

And when you goto /library?thirdPartyUrl=https://fonts.googleapis.com/icon?family=Material+Icons should give you the response and cache it how we normally cache our response. For Ex: remove no-cors & replace urlsToPrefetch with below value:

var urlsToPrefetch = [
      '/',
      '/library?thirdPartyUrl=https://fonts.googleapis.com/icon?family=Material+Icons',
      '/library?thirdPartyUrl=https://fonts.googleapis.com/icon?family=Roboto'
    ];

Option 3

I think this is the best and easier way. Use workbox. We've tried to create PWA with and without workbox and using workbox was simple.

Read about workbox: https://developers.google.com/web/tools/workbox/

Implement a route like this after the initial setup:

workbox.routing.registerRoute(
  new RegExp('^https://third-party.example.com/images/'),
  new workbox.strategies.CacheFirst({
    cacheName: 'image-cache',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200],
      })
    ]
  })
);