1 |
|
/* global self, caches, fetch, URL, Response */
|
2 |
|
'use strict';
|
3 |
|
|
4 |
|
var config = {
|
5 |
|
version: 'v0',
|
6 |
|
staticCacheItems: [
|
7 |
|
'/offline/'
|
8 |
|
],
|
9 |
|
cachePathPattern: /^\/static\/.*/,
|
10 |
|
handleFetchPathPattern: /.*/,
|
11 |
|
offlineImage: '<svg role="img" aria-labelledby="offline-title"'
|
12 |
|
+ ' viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">'
|
13 |
|
+ '<title id="offline-title">Offline</title>'
|
14 |
|
+ '<g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/>'
|
15 |
|
+ '<text fill="#9B9B9B" font-family="Times New Roman,Times,serif" font-size="72" font-weight="bold">'
|
16 |
|
+ '<tspan x="93" y="172">offline</tspan></text></g></svg>',
|
17 |
|
offlinePage: '/offline/'
|
18 |
|
};
|
19 |
|
|
20 |
|
function cacheName (key, opts) {
|
21 |
|
return `${opts.version}-${key}`;
|
22 |
|
}
|
23 |
|
|
24 |
|
function addToCache (cacheKey, request, response) {
|
25 |
|
if (response.ok && cacheKey !== null) {
|
26 |
|
var copy = response.clone();
|
27 |
|
caches.open(cacheKey).then( cache => {
|
28 |
|
cache.put(request, copy);
|
29 |
|
});
|
30 |
|
}
|
31 |
|
return response;
|
32 |
|
}
|
33 |
|
|
34 |
|
function fetchFromCache (event) {
|
35 |
|
return caches.match(event.request).then(response => {
|
36 |
|
if (!response) {
|
37 |
|
throw Error(`${event.request.url} not found in cache`);
|
38 |
|
}
|
39 |
|
return response;
|
40 |
|
});
|
41 |
|
}
|
42 |
|
|
43 |
|
function offlineResponse (resourceType, opts) {
|
44 |
|
if (resourceType === 'image') {
|
45 |
|
return new Response(opts.offlineImage,
|
46 |
|
{ headers: { 'Content-Type': 'image/svg+xml' } }
|
47 |
|
);
|
48 |
|
} else if (resourceType === 'content') {
|
49 |
|
return caches.match(opts.offlinePage);
|
50 |
|
}
|
51 |
|
return undefined;
|
52 |
|
}
|
53 |
|
|
54 |
|
self.addEventListener('install', event => {
|
55 |
|
function onInstall (event, opts) {
|
56 |
|
var cacheKey = cacheName('static', opts);
|
57 |
|
return caches.open(cacheKey)
|
58 |
|
.then(cache => cache.addAll(opts.staticCacheItems));
|
59 |
|
}
|
60 |
|
|
61 |
|
event.waitUntil(
|
62 |
|
onInstall(event, config).then( () => self.skipWaiting() )
|
63 |
|
);
|
64 |
|
});
|
65 |
|
|
66 |
|
self.addEventListener('activate', event => {
|
67 |
|
function onActivate (event, opts) {
|
68 |
|
return caches.keys()
|
69 |
|
.then(cacheKeys => {
|
70 |
|
var oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0);
|
71 |
|
var deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey));
|
72 |
|
return Promise.all(deletePromises);
|
73 |
|
});
|
74 |
|
}
|
75 |
|
|
76 |
|
event.waitUntil(
|
77 |
|
onActivate(event, config)
|
78 |
|
.then( () => self.clients.claim() )
|
79 |
|
);
|
80 |
|
});
|
81 |
|
|
82 |
|
self.addEventListener('fetch', event => {
|
83 |
|
|
84 |
|
function shouldHandleFetch (event, opts) {
|
85 |
|
var request = event.request;
|
86 |
|
var url = new URL(request.url);
|
87 |
|
var criteria = {
|
88 |
|
matchesPathPattern: opts.handleFetchPathPattern.test(url.pathname),
|
89 |
|
isGETRequest : request.method === 'GET',
|
90 |
|
isFromMyOrigin : url.origin === self.location.origin
|
91 |
|
};
|
92 |
|
var failingCriteria = Object.keys(criteria)
|
93 |
|
.filter(criteriaKey => !criteria[criteriaKey]);
|
94 |
|
return !failingCriteria.length;
|
95 |
|
}
|
96 |
|
|
97 |
|
function onFetch (event, opts) {
|
98 |
|
var request = event.request;
|
99 |
|
var url = new URL(request.url);
|
100 |
|
var acceptHeader = request.headers.get('Accept');
|
101 |
|
var resourceType = 'static';
|
102 |
|
var cacheKey;
|
103 |
|
|
104 |
|
if (acceptHeader.indexOf('text/html') !== -1) {
|
105 |
|
resourceType = 'content';
|
106 |
|
} else if (acceptHeader.indexOf('image') !== -1) {
|
107 |
|
resourceType = 'image';
|
108 |
|
}
|
109 |
|
|
110 |
|
cacheKey = null;
|
111 |
|
if (opts.cachePathPattern.test(url.pathname)) {
|
112 |
|
cacheKey = cacheName(resourceType, opts);
|
113 |
|
}
|
114 |
|
|
115 |
|
/* always network first */
|
116 |
|
if (true || resourceType === 'content') {
|
117 |
|
event.respondWith(
|
118 |
|
fetch(request)
|
119 |
|
.then(response => addToCache(cacheKey, request, response))
|
120 |
|
.catch(() => fetchFromCache(event))
|
121 |
|
.catch(() => offlineResponse(resourceType, opts))
|
122 |
|
);
|
123 |
|
} else {
|
124 |
|
event.respondWith(
|
125 |
|
fetchFromCache(event)
|
126 |
|
.catch(() => fetch(request))
|
127 |
|
.then(response => addToCache(cacheKey, request, response))
|
128 |
|
.catch(() => offlineResponse(resourceType, opts))
|
129 |
|
);
|
130 |
|
}
|
131 |
|
}
|
132 |
|
if (shouldHandleFetch(event, config)) {
|
133 |
|
onFetch(event, config);
|
134 |
|
}
|
135 |
|
|
136 |
|
});
|
137 |
|
-
|