0

我是 ember 和服务人员的初学者。我的目标是设置一个可以离线工作的简单 ember 应用程序。我基本上有一个可通过 API (GET/POST) 获得的元素列表。

当我在线时,一切都按预期工作。我可以获取列表并发布新项目。当我离线时,应用程序可以工作,但是一旦我重新上线,网络请求就不会执行。所有网络请求实际上都是在我离线时执行的(显然失败了)。我希望服务人员缓存网络请求并仅在我重新联机后执行它们。这是错的吗?

这里有一些关于我的设置的信息:

灰烬版本:

  • 余烬-cli:2.13.1
  • 节点:7.10.0
  • 操作系统:达尔文 x64

Service Worker 附加组件(如 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",

我可能还应该提到我在 1.1.3 版本中使用了 ember-django-adapter。

这是我的应用程序/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();
};

我的网络请求 (GET/POST) 转到http://localhost:8000/api/v1/properties/

这是我的 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) + '/';
  }
});

当我打开应用程序时,服务人员会注册:

(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 })
      );
    }
  });

}());

这是网络请求在 Chrome 中的显示方式:离线时的网络请求

我认为问题出在 ember-service-worker-cache-fallback 的配置中。但我不太确定。欢迎任何想法或指向工作示例的链接。到目前为止,我没有找到很多关于 ember-service-worker-cache-fallback 的信息。

谢谢!

4

1 回答 1

1

您所描述的是 ember-service-worker-cache-fallback 的正确和预期行为,即如果不可能,首先尝试从网络获取,然后回退以从服务工作者中的缓存版本获取。

我相信您正在寻找的是某种失败请求的排队机制。这不在 ember-service-worker-cache-fallback 的范围内。

不过不要害怕,我也有类似的抱负,并提出了自己的解决方案,称为ember-service-worker-enqueue。这是一个 ember-service-worker 插件,它使用 Mozilla 的 Localforage 仅将失败的突变请求(例如 POST、PUT、PATCH、DELETE)排队,然后在网络稳定时发送它们。

它非常适合保护您的 ember 应用程序免受网络故障或响应 5xx 状态代码的服务器错误的影响。

注意:根据我的经验,Service Worker 最好针对每个用例进行处理,所以不要盲目地安装我的插件并期望事情对你来说以同样的方式工作,而是通过大量注释的代码(< 200 行),分叉插件并调整它以满足您的需求。享受,

Ps:我还在研究另一个名为ember-service-worker-push-notifications 的项目,但仍处于早期阶段,但对于希望从中受益的任何人,我都会遵循同样的重磅评论。干杯!

于 2017-06-06T23:45:24.200 回答