Newer
Older
importScripts('vendor/localforage.min.js');
var CACHE = 'calc';
/**
* get path from URL.
* drop schema, host, query string.
*/
const getUrlPath = function (url) {
var path = '/' + url.split('/').slice(3).join('/');
var paramIndex = path.indexOf('?');
if (paramIndex !== -1) {
return path.substring(0, paramIndex);
}
return path;
};
const cachePathList = ['/', '/calc.html'];
const cacheFileExtList = ['.js', '.css', '.png', '.jpg', '.gif'];
/**
* return true if this URL should be cached by service worker.
*/
const shouldCache = function (url) {
const path = getUrlPath(url);
for (let cachePath of cachePathList) {
// since sw.js is scoped at ./, this should work.
if (path.endsWith(cachePath)) {
return true;
}
}
return cacheFileExtList.some(function (ext, index, ar) {
return path.endsWith(ext);
});
};
/**
* return true if this URL is a vendor asset.
*/
const isVendorAsset = function (url) {
const path = getUrlPath(url);
return path.includes('/vendor/');
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
};
/**
* return true if this URL is static asset. see cacheFileExtList.
* files ending in those ext are considered to be static asset.
*/
const isStaticAsset = function (url) {
const path = getUrlPath(url);
if (path.endsWith('/sw.js')) {
return false; // do not cache service worker js file.
}
return cacheFileExtList.some(function (ext, index, ar) {
return path.endsWith(ext);
});
};
/**
* cache timestamp key used in indexeddb.
* each cached url can have a cache timestamp with it.
*/
const cacheTimestampKey = function (url) {
return "timestamp:" + url;
};
// ===============
// event handler
// ===============
self.addEventListener('fetch', function(event) {
/**
* used as fetch(event.request)'s then function.
* cache the resp in service worker's req/resp cache, then return the resp.
*
* if response is not 2xx, do not cache.
*/
const cacheRespAndReturn = function (resp) {
const respClone = resp.clone();
if (! resp.ok) {
return resp;
}
caches.open(CACHE).then(function (cache) {
cache.put(event.request, respClone);
console.log("cached " + event.request.url);
});
return resp;
};
/**
* use cache if it exists. Otherwise, fetch and cache it.
*/
const useServiceWorkerCache = function () {
return event.respondWith(
caches.match(event.request).then(function (resp) {
if (resp) {
return resp;
} else {
return fetch(event.request).then(cacheRespAndReturn);
}
})
);
};
/**
* use cached Response if it's not expired or when network is down.
* try renew cache if it's expired.
*/
const useServiceWorkerCacheIfNotExpired = function () {
// console.log("use service worker cache if not expired");
const url = event.request.url;
const tsKey = cacheTimestampKey(url);
const expireSeconds = 24 * 3600;
/**
* try update service worker req/resp cache and the ts cache.
*/
const tryUpdate = function () {
return fetch(event.request).then(function (resp) {
const respClone = resp.clone();
if (! resp.ok) {
console.log("HTTP ERROR: " + resp.status);
return resp; // just return, don't cache.
}
caches.open(CACHE).then(function (cache) {
cache.put(event.request, respClone);
console.log("cached " + event.request.url);
const ts = new Date().toJSON();
localforage.setItem(tsKey, ts).then(function () {
console.log("cache ts updated to " + ts + ", url=" + event.request.url);
}).catch(function (err) {
console.log("cache ts update failed: " + err);
});
});
return resp;
}).catch(function (_err) {
console.log("fetch failed. try use cached resp.");
return caches.match(event.request); // nothing to do if
// match failed.
});
};
return event.respondWith(
localforage.getItem(tsKey).then(function (tsString) {
if (tsString !== null) {
const now = new Date();
const cacheDate = new Date(tsString);
const diffSeconds = (now - cacheDate) / 1000;
if (diffSeconds > expireSeconds) {
console.log("cache expired");
return tryUpdate();
}
console.log("cache is valid according to cache ts, url=",
event.request.url);
return caches.match(event.request).then(function (resp) {
if (resp) {
return resp;
}
return fetch(event.request);
});
} else {
console.log("no ts cache");
return tryUpdate();
}
}).catch(function (_err) {
console.log("no ts cache");
return tryUpdate();
})
);
};
var url = event.request.url;
if (! shouldCache(url)) {
console.log("not caching " + url);
return fetch(event.request).catch(function (err) {
console.log(err);
});
}
// console.log("will use sw cache on " + url);
if (isVendorAsset(url)) {
return useServiceWorkerCache();
} else {
return useServiceWorkerCacheIfNotExpired();
}
});