我是灰烬和服务工作者的初学者。我的目标是设置一个离线工作的简单ember应用程序。我基本上有一个通过API(GET / POST)可用的元素列表。
当我在线时,一切都按预期工作。我可以获取列表和POST新项目。当我离线时,该应用程序可以正常工作,但一旦我重新上线,就不会执行网络请求。所有网络请求实际上都是在我离线时执行的(显然是失败的)。我希望服务工作者缓存网络请求,并且只有在我重新上线后才执行它们。这是错的吗?
这里有一些关于我的设置的信息:
Ember版本:
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。
这是我的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();
};
我的网络请求(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中的显示方式:Network request while offline
我认为问题在于ember-service-worker-cache-fallback的配置。但我对此并不十分肯定。欢迎任何关于工作示例的想法或链接。到目前为止,我没有找到很多关于ember-service-worker-cache-fallback的内容。
谢谢!
答案 0 :(得分: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状态代码响应的网络故障或服务器错误的影响。
注意:根据我的经验,服务工作者在每个用例处理时最好,所以不要盲目地安装我的插件并期望事情以相同的方式为您工作,而是通过大量评论的代码( < 200行),分叉插件并根据您的需要进行调整。享受,
Ps:我还在处理另一个名为ember-service-worker-push-notifications的人,但是对于那些希望从中获益的人来说,他们也会遵循相同的评论。干杯!