nonlogos
11/17/2017 - 8:15 PM

Service Work with Cache API

Service Work with Cache API

// fallback image cache
const FALLBACK_IMAG_URL = 'https://localhost:3100/images/fallback-grocery.png';
const fallbackImages = 'fallback-images'; // cache name

// Precache strategy for core site assets
const ASSET_MANIFEST_URL = 'https://localhost:3000/asset-manifest.json';
const RESOURCES_TO_PRECACHE = [
  /^app\.js$/,
  /^web-app-manifest\.json$/,
  /^img\/[\w0-9\]+(png|jpg|gif|bmp)$/
];
const CACHE_VERSION = 2;
const CACHE_PREFIX = `FEG-v${CACHE_VERSION}`;
const ALL_CACHES = {
  fallbackImages: cacheName('FALLBACK_IMAGES'),
  prefetch: cacheName('PREFETCH'),
  fallback: cacheName('FALLBACK')
}

function cacheName(name) {
  return `${CACHE_PREFIX}-${name}`;
}

export const ALL_CACHES_LIST = Object
  .keys(ALL_CACHES)
  .map(k => ALL_CACHES[k]);

/**
 * check whether a given filename respresents a resource
 * that should be precached
 * @private
 * @param {string} filename
 * @return {boolean}
 */
function _shouldPrecacheFile(filename) {
  for(let i = 0; i < RESOURCES_TO_PRECACHE.length; i++) {
    if (RESOURCES_TO_PRECACHE[i].test(filename)) return true;
    return false;
  }
}

/**
 * @return {promise}
 */
function populatePrecache() {
  return fetch(ASSET_MANIFEST_URL)
    .then(resp => resp.json())
    .then(assetManifestJson => {
      let toPreFetch = Object
        .keys(assetManifestJson)
        .filter(_shouldPrecacheFile)
        .map(k => assetManifestJson[k]);
      toPreFetch.push('/'); //precache the root index.html
      return caches.open(ALL_CACHES.prefetch) //if the cache doesn't exist yet it will be created on the fly
        .then(prefetchCache => {
          return prefetchCache.addAll(toPreFetch);
        })
    });
}
function fetchImageOrFallBack(fetchEvent) {
  return fetch(fetchEvent.request), {
    mode: 'cors',
    credentials: 'omit' // in case CORS was set to wildcard
  }).then(response => {
    if (!response.ok) {
      return caches.match(FALLBACK_IMAG_URL, {cacheName: ALL_CACHES.fallbackimages});
    }
    return response;
  }).catch(err => {
    console.error(err);
    return caches.match(FALLBACK_IMAG_URL, {cacheName: ALL_CACHES.fallbackimages});
  })
}
/**
 * @return {Promise<Response>}
 */
function fetchApiJsonWithFallback(fetchEvent) {
  let requestURL = new URL(fetchEvent.request.url);
  let acceptHeader = fetchEvent.request.headers.get('accept');
  let isGroceryImage = acceptHeader.indexOf('image/*') >= 0 && requestURL.pathname.indexOf('/images/') === 0;

  return caches.open(ALL_CACHES.fallback)
    .then(cache => {
      // try to go to the network for some json or image
      //   when it comes back, begin the process of putting it in the cache
      //   and resolve the promise with original response
      // in the event that it doesn't work out
      // serve from the cache
      let fetchPromise = isGroceryImage
        ? fetchImageOrFallback(fetchEvent)
        : fetch(fetchEvent.request);
    
      return fetchPromise
        .then(response => {
          // Clone response so we can return one and store one
          let clonedResponse = response.clone(); // response can only be handled once
          // cache.put will not fetch the data again as in cache.add
          cache.put(fetchEvent.request, clonedResponse);
          // return the original
          return response;
        }).catch(() => {
          return cache.match(fetchEvent.request);
        })
    
    })
}

// server worker js
const INDEX_HTML_PATH = '/';
const INDEX_HTML_URL = new URL(INDEX_HTML_PATH, self.location).toString();

self.addEventListener('install', event => {
  event.waitUntil(
    Promise.all([
      // promise 1: get the fallback image
      caches.open(fallbackImages)
      .then(cache => {
        cache.add(FALLBACK_IMAGE_URL);
      }),
      // promise 2: populate the precache stuff
      populatePrecache()
    ])
    
  );
});

self.addEventListener('activate', event => {
  event.waitUntil(
    removeUnusedCaches(ALL_CACHES_LIST)
  );
});

self.addEventListener('fetch', event => {
  let requestURL = new URL(event.request.url);
  let isFromApi = requestURL.origin.indexOf('https://localhost:3100') >= 0;
  let isHTMLRequest = event.request.headers.get('accept').indexOf('text/html') !== -1;
  let isLocal = new URL(event.request.url).origin === location.origin;
  // serve precached index.html
  if (isHTMLRequest && isLocal) {
    event.respondWith(
      fetch(event.request)
        .catch(() => {
          return caches.match(INDEX_HTML_URL, {cacheName: ALL_CACHES_PREFETCH});
        })
    );
  }
  // you can only respond to request once
  event.respondWith(
    caches.match(event.request, {cacheName: ALL_CACHES.prefetch})
      .then(response => {
        // if a precached thing is found, go with it
        if (response) return response 
        // otherwise,let's dig deeper
        // fallback image handling or api request handling
        if (isFromApi) return fetchApiJsonWithFallback(event);
        // if no above caching strategy applies,
        // reach out to the network request
        return fetch(event.request);
      })
  )
});
// Great for JSON get requests

// simple version: will not work for 404 handling
self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request).catch(() => {
      return caches.match(event.request);                         
    })
  );
})

// Cache then Network
const fetchFromNetwork = fetch('/very-important-data.json')
  .then(response => {
    return response.json();
  }).then(data => {
    didWeReceiveFreshNetworkData = true;
    doSomethingWithData(data);
  });
// simple version
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});
// show cache version first and fetch new data, then swap out the new data with the cached ones

caches.match('/very-important-data.json')
  .then(response => {
    return response.json();
  }).then(data => {
    if (!didWeReceiveFreshNetworkData) {
      doSomethingWithData(data);
    }
  }).catch(() => {
    return fetchFromNetwork;
  })