/**
 * Service worker responsible for providing offline support to the WebPlayer
 * @author Michal Drewniak (michal@enplug.com)
 */

"use strict";

/**
 * Offline support configuration.
 * @type {Object}
 * @property {string} cacheVersion - Version of the cache. This should be updated any time we want
 *     the previous version of the cache removed.
 * @property {string} cacheName - Name of the cache. This is usually the same as the application
 *     name or ID.
 * @property {Array.<string>} = A list of static URLs which should be cached.
 * @property {Object.<string, number>} - A mapping of URLs (or their parts) to time in minutes.
 *     The URL denotes for which locations cached responses should be refreshed. Time denotes
 *     after which time (in minutes), given response should be refreshed. Time set to 0 means that
 *     a given response should never be cached.
 */
var config = <% config %>;

// End of config. There shouldn't be any need to edit code below.


/**
 * Installs service worker and adds static resources to cache.
 */
self.addEventListener('install', function (event) {
  event.waitUntil(caches.open(config.cacheVersion).then((cache) => {
    return cache.addAll(config.staticResources)
      .then(() => {
        console.log(`[<% app_name %>] Offline support service worker v${config.cacheVersion} installed.`);
        self.skipWaiting();
      })
      .catch((error) => {
        console.warn(`[<% app_name %>] Error adding static resources to cache ${config.cacheVersion}.`, error);
      })
  }));
});


/**
 * Activate phase. Clears all non-whitelisted cache.
 */
self.addEventListener('activate', (event) => {
  var cacheWhitelist = [config.cacheVersion];

  event.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames.map(function (cacheName) {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            console.log(`[<% app_name %>] Deleting cache ${cacheName}`);
            return caches.delete(cacheName);
          }
        })
      ).then(() => self.clients.claim());
    })
  );
});


/**
 * Captures requests and either exectures request to the server or returns a cached version.
 */
self.addEventListener("fetch", (event) => {

  function cacheResponse(response) {
    // Fetching new data resulted in a valid response. Cache it.
    var responseToCache = response.clone();
    caches.open(config.cacheVersion).then(
      (cache) => {
        // Cache response and update the time of caching this particular request.
        if (response && response.ok) {
          cache.put(cacheUrl, responseToCache);
        } else {
          return caches.match(cacheUrl);
        }
      },
      (error) => {
        console.warn(`[player-web Unable to cache ${cacheUrl}`, error)
      }
    );
    // Return the response.
    return response;
  }


  // Ignore POST requests
  if (event.request.method === 'POST') {
    return false;
  }

  // Get rid of the app token to generalize URL for caching. Having unique URLs for each request
  // will only make the cache get bigger over time.
  var cacheUrl = event.request.url.replace(/\?apptoken=[^&]*/g, '');

  if (cacheUrl.startsWith('blob:') || cacheUrl.endsWith('.mp4')) {
    console.log(`[player-web|offline] Caching prevented: URL starts with blob: or ends with .mp4.`);
    return false;
  }

  for (const noCacheUrl of config.noCacheUrls) {
    if (event.request.url.indexOf(noCacheUrl) >= 0) {
      return false;
    }
  }

  const originalRequest = event.request;

  // We need to explicitly change the reqest to CORS request so that cross-domain resources
  // get properly cached.
  var fetchRequest = new Request(event.request.url, {
    headers: originalRequest.headers,
    mode: 'cors',
    credentials: 'omit',
    referrer: 'no-referrer',
  });

  event.respondWith(fetch(fetchRequest)
    .then((fetchResponse) => {
      if (!fetchResponse || fetchResponse.status !== 200) {
        throw Error('CORS request failed.');
      }
      // Propagate correct response down the Promise chain.
      return fetchResponse;
    })

    // CORS request has failed. We need attempt to refetch the request as no-cors (which is still preferable)
    // then a failed response. We want to propagate down the Promise chain the response to our no-CORS request.
    .catch((err) => {
      console.log(`${TAG} CORS request failed. Attempting fetch original request.`);
      return fetch(originalRequest);
    })
    .then((response) => cacheResponse(response))
    .catch(() => fetch(originalRequest))
    .then((response) => cacheResponse(response))
    .catch(() => {
      console.log(`[player-web Unable to fetch ${cacheUrl}. Attempting to return it from cache.`);
      return caches.match(cacheUrl);
    }));
});
