mercredi 31 mai 2017

Setup of ember-service-worker-cache-fallback to sync data

I'm a beginner in ember and service workers. My goal is to setup a simple ember app that works offline. I basically have a list of elements that are available through an API (GET/POST).

When I'm online, everything works as expected. I can GET the list and POST new items. When I'm offline the app works, but network requests are not executed once I go back online. All network requests are actually executed while I'm offline (and obviously fail). I would expect that the service worker caches the network requests and executes them only once I'm back online. Is this wrong?

Here some information about my setup:

Ember version:

  • ember-cli: 2.13.1
  • node: 7.10.0
  • os: darwin x64

Service Worker Add-ons (as listed in app/package.json):

"ember-service-worker": "^0.6.6",
"ember-service-worker-asset-cache": "^0.6.1",
"ember-service-worker-cache-fallback": "^0.6.1",
"ember-service-worker-index": "^0.6.1",

I should probably also mention that I use ember-django-adapter in version 1.1.3.

This is my app/ember-cli-build.js

var EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function(defaults) {
  var app = new EmberApp(defaults, {
    'esw-cache-fallback': {
      // RegExp patterns specifying which URLs to cache.
      patterns: [
        'http://localhost:8000/api/v1/(.*)',
      ],

      // changing this version number will bust the cache
      version: '1'
    }
  });

  return app.toTree();
};

My network requests (GET/POST) go to http://localhost:8000/api/v1/properties/.

This is my app/adapters/applications.js

import DS from 'ember-data';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {

  namespace: 'api/v1',
  host: 'http://localhost:8000',
  authorizer: 'authorizer:token',
  headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
  buildURL: function(type, id, record) {
  return this._super(type, id, record) + '/';
  }
});

The service worker registers when I open the app:

(function () {
  'use strict';

  self.addEventListener('install', function installEventListenerCallback(event) {
      return self.skipWaiting();
    });

    self.addEventListener('activate', function installEventListenerCallback(event) {
      return self.clients.claim();
    });

  const FILES = ['assets/connect.css', 'assets/connect.js', 'assets/connect.map', 'assets/failed.png', 'assets/passed.png', 'assets/test-support.css', 'assets/test-support.js', 'assets/test-support.map', 'assets/tests.js', 'assets/tests.map', 'assets/vendor.css', 'assets/vendor.js', 'assets/vendor.map'];
  const PREPEND = undefined;
  const VERSION$1 = '1';
  const REQUEST_MODE = 'cors';

  /*
   * Deletes all caches that start with the `prefix`, except for the
   * cache defined by `currentCache`
   */
  var cleanupCaches = (prefix, currentCache) => {
    return caches.keys().then((cacheNames) => {
      cacheNames.forEach((cacheName) => {
        let isOwnCache = cacheName.indexOf(prefix) === 0;
        let isNotCurrentCache = cacheName !== currentCache;

        if (isOwnCache && isNotCurrentCache) {
          caches.delete(cacheName);
        }
      });
    });
  };

  const CACHE_KEY_PREFIX = 'esw-asset-cache';
  const CACHE_NAME = `${CACHE_KEY_PREFIX}-${VERSION$1}`;
  const CACHE_URLS = FILES.map((file) => {
    return new URL(file, (PREPEND || self.location)).toString();
  });

  /*
   * Removes all cached requests from the cache that aren't in the `CACHE_URLS`
   * list.
   */
  const PRUNE_CURRENT_CACHE = () => {
    caches.open(CACHE_NAME).then((cache) => {
      return cache.keys().then((keys) => {
        keys.forEach((request) => {
          if (CACHE_URLS.indexOf(request.url) === -1) {
            cache.delete(request);
          }
        });
      });
    });
  };

  self.addEventListener('install', (event) => {
    event.waitUntil(
      caches
        .open(CACHE_NAME)
        .then((cache) => {
          return Promise.all(CACHE_URLS.map((url) => {
            let request = new Request(url, { mode: REQUEST_MODE });
            return fetch(request)
              .then((response) => {
                if (response.status >= 400) {
                  throw new Error(`Request for ${url} failed with status ${response.statusText}`);
                }
                return cache.put(url, response);
              })
              .catch(function(error) {
                console.error(`Not caching ${url} due to ${error}`);
              });
          }));
        })
    );
  });

  self.addEventListener('activate', (event) => {
    event.waitUntil(
      Promise.all([
        cleanupCaches(CACHE_KEY_PREFIX, CACHE_NAME),
        PRUNE_CURRENT_CACHE()
      ])
    );
  });

  self.addEventListener('fetch', (event) => {
    let isGETRequest = event.request.method === 'GET';
    let shouldRespond = CACHE_URLS.indexOf(event.request.url) !== -1;

    if (isGETRequest && shouldRespond) {
      event.respondWith(
        caches.match(event.request, { cacheName: CACHE_NAME })
          .then((response) => {
            if (response) {
              return response;
            }
            return fetch(event.request);
          })
      );
    }
  });

  const VERSION$2 = '1';
  const PATTERNS = ['http://localhost:8000/api/v1/(.*)'];

  /**
   * Create an absolute URL, allowing regex expressions to pass
   *
   * @param {string} url
   * @param {string|object} baseUrl
   * @public
   */
  function createNormalizedUrl(url, baseUrl = self.location) {
    return decodeURI(new URL(encodeURI(url), baseUrl).toString());
  }

  /**
   * Create an (absolute) URL Regex from a given string
   *
   * @param {string} url
   * @returns {RegExp}
   * @public
   */
  function createUrlRegEx(url) {
    let normalized = createNormalizedUrl(url);
    return new RegExp(`^${normalized}$`);
  }

  /**
   * Check if given URL matches any pattern
   *
   * @param {string} url
   * @param {array} patterns
   * @returns {boolean}
   * @public
   */
  function urlMatchesAnyPattern(url, patterns) {
    return !!patterns.find((pattern) => pattern.test(decodeURI(url)));
  }

  const CACHE_KEY_PREFIX$1 = 'esw-cache-fallback';
  const CACHE_NAME$1 = `${CACHE_KEY_PREFIX$1}-${VERSION$2}`;

  const PATTERN_REGEX = PATTERNS.map(createUrlRegEx);

  self.addEventListener('fetch', (event) => {
    let request = event.request;
    if (request.method !== 'GET' || !/^https?/.test(request.url)) {
      return;
    }

    if (urlMatchesAnyPattern(request.url, PATTERN_REGEX)) {
      event.respondWith(
        caches.open(CACHE_NAME$1).then((cache) => {
          return fetch(request)
            .then((response) => {
              cache.put(request, response.clone());
              return response;
            })
            .catch(() => caches.match(event.request));
        })
      );
    }
  });

  self.addEventListener('activate', (event) => {
    event.waitUntil(cleanupCaches(CACHE_KEY_PREFIX$1, CACHE_NAME$1));
  });

  const VERSION$3 = '1';
  const INDEX_HTML_PATH = 'index.html';

  const CACHE_KEY_PREFIX$2 = 'esw-index';
  const CACHE_NAME$2 = `${CACHE_KEY_PREFIX$2}-${VERSION$3}`;

  const INDEX_HTML_URL = new URL(INDEX_HTML_PATH, self.location).toString();

  self.addEventListener('install', (event) => {
    event.waitUntil(
      fetch(INDEX_HTML_URL, { credentials: 'include' }).then((response) => {
        return caches
          .open(CACHE_NAME$2)
          .then((cache) => cache.put(INDEX_HTML_URL, response));
      })
    );
  });

  self.addEventListener('activate', (event) => {
    event.waitUntil(cleanupCaches(CACHE_KEY_PREFIX$2, CACHE_NAME$2));
  });

  self.addEventListener('fetch', (event) => {
    let request = event.request;
    let isGETRequest = request.method === 'GET';
    let isHTMLRequest = request.headers.get('accept').indexOf('text/html') !== -1;
    let isLocal = new URL(request.url).origin === location.origin

    if (isGETRequest && isHTMLRequest && isLocal) {
      event.respondWith(
        caches.match(INDEX_HTML_URL, { cacheName: CACHE_NAME$2 })
      );
    }
  });

}());

This is how network requests appear in Chrome:Network request while offline

I assume the problem is in the configuration of ember-service-worker-cache-fallback. But I'm not quite sure about that. Any idea or link to a working example is welcome. I didn't find a lot about ember-service-worker-cache-fallback so far.

Thanks!




Aucun commentaire:

Enregistrer un commentaire