-
Notifications
You must be signed in to change notification settings - Fork 1
/
service-worker.js
227 lines (199 loc) · 5.78 KB
/
service-worker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
const DEBUG = true
const CACHE_BASE_NAME = 'ARCONAI-STREAM'
const CACHE_TAG = '0001'
// #region Output Styles
const swStyle = 'background: #333;' // Service Worker Background
const fnStyle = 'background: #EEE;' // Function Background
const styles = {
install: `${swStyle} color: #c8f7c5`,
activate: `${swStyle} color: #c5cbf7`,
delete: `${swStyle} color: #e0ffff`,
fetch: `${swStyle} color: #ffecdb`,
precache: `${fnStyle} color: #2e8856`,
fromNetwork: `${fnStyle} color: #638bb3`,
fromCache: `${fnStyle} color: #bf55ec`,
updateCache: `${fnStyle} color: #808080`,
refresh: `${fnStyle} color: #9d8319`
}
// #endregion
// #region Cache Definitions
// Static cache is loaded once and then always served from cache
const staticCache = {
name: `${CACHE_BASE_NAME}-STATIC-${CACHE_TAG}`,
files: [
'./manifest.json',
'./favicon.ico',
'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css',
'https://code.jquery.com/jquery-3.3.1.slim.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js',
'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js'
]
}
// Update cache is loaded, then when requested it serves from cache
// then in the background it tries to update the cache from network
// then refreshes the client
const updateCache = {
name: `${CACHE_BASE_NAME}-UPDATE-${CACHE_TAG}`,
files: ['/']
}
const CACHES = [staticCache, updateCache]
// #endregion
// #region Service Worker Lifecycle Events
/**
* Install service worker and runs precache
*/
self.addEventListener('install', evt => {
if (DEBUG) console.log('%c[->][install]', styles.install)
evt.waitUntil(precache())
})
/**
* Remove any caches other than the current ones
*/
self.addEventListener('activate', event => {
if (DEBUG) console.log('%c[->][activate]', styles.activate)
event.waitUntil(
// Get all the cache names (keys)
caches.keys().then(cacheNames =>
Promise.all(
// For each cache name
cacheNames.map(key => {
// Whitelist the current cache names
if (CACHES.map(c => c.name).indexOf(key) === -1) {
if (DEBUG) console.log('%c[->][delete]', styles.delete, key)
// Delete the rest
return caches.delete(key)
}
})
)
)
)
})
self.addEventListener('fetch', evt => {
if (DEBUG) console.log('%c[->][fetch]', styles.fetch, evt.request.url)
// If resource is in static cache, return it from the static cache
// If resource is in update cache, return it from the update cache, then update the cache from the network and refresh the client
// If resource is not in the caches, fetch it from the network and don't cache it
evt.respondWith(
// Return from the static cache if valid
fromStaticCache(evt.request).catch(() =>
// return from update cache if valid
fromUpdateCache(evt.request)
.then(response => {
// update and refresh
evt.waitUntil(updateUpdateCache(evt.request).then(refresh))
return response
})
.catch(() =>
// resort to network
fromNetwork(evt.request)
)
)
)
})
// #endregion
// #region Cache Functions
// Accessors for named caches
const fromStaticCache = fromUsingCache(staticCache.name)
const fromUpdateCache = fromUsingCache(updateCache.name)
const updateUpdateCache = updateUsingCache(updateCache.name)
/**
* Populate cache with a set of resources
*/
function precache() {
if (DEBUG) console.log('%c[->][precache]', styles.precache)
return Promise.all(
CACHES.map(({ name, files }) =>
caches.open(name).then(cache => cache.addAll(files))
)
)
}
/**
* Get a resource from the cache
*
* Returns resource
* Throws an error if resource not in cache
*/
function fromUsingCache(cacheName) {
return function fromCache(request) {
return caches.open(cacheName).then(cache =>
cache.match(request).then(matching => {
if (DEBUG && matching) {
console.log(
`%c[<-][cache ${cacheName}]`,
styles.fromCache,
request.url
)
}
return matching || Promise.reject('no-match')
})
)
}
}
/**
* Get a resource from network then store it in the cache
*
* Returns resource
*/
function updateUsingCache(cacheName) {
return function update(request) {
if (DEBUG)
console.log(
`%c[->][update ${cacheName}]`,
styles.updateCache,
request.url
)
return caches
.open(cacheName)
.then(cache =>
fromNetwork(request).then(response =>
cache.put(request, response.clone()).then(() => response)
)
)
}
}
// #endregion
/**
* Get a resource from a URL (within ${timeout}ms if specified)
*
* Returns resource
* If timeout is specified, throws an error if timeout exceeded
*/
function fromNetwork(request, timeout) {
return new Promise((fulfill, reject) => {
var timeoutId = timeout && setTimeout(reject, timeout)
fetch(request).then(function(response) {
if (DEBUG)
console.log(
'%c[<-][network]',
styles.fromNetwork,
request.url,
timeout || ''
)
timeout && clearTimeout(timeoutId)
fulfill(response)
}, reject)
})
}
/**
* Refresh any serviceworker connected client pages
*/
function refresh(response) {
if (DEBUG) console.log('%c[->][refresh]', styles.refresh, response.url)
// Get all attached clients
return self.clients.matchAll().then(clients =>
Promise.all(
// For each client
clients.map(client =>
// Post message
client.postMessage(
// To refresh
JSON.stringify({
type: 'refresh',
url: response.url,
eTag: response.headers.get('ETag')
})
)
)
)
)
}