-
Notifications
You must be signed in to change notification settings - Fork 0
/
sw.js
144 lines (125 loc) · 5.51 KB
/
sw.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
///////////////////////////////////
// The best(er) service worker //
// on the planet //
// //
// Suck it WorkboxJS //
///////////////////////////////////
const APP_VERSION = 6.10
// Document Cache is a cache of document files - html, js, css, etc
const DOCUMENT_CACHE_NAME = `DOC`
var DOCUMENT_CACHE = null
// Resource Cache is a cache of almost always static resources - images, fonts, and everything in the Texts folder
const RESOURCE_VERSION = 6.10
const RESOURCE_CACHE_NAME = `RESv${RESOURCE_VERSION.toFixed(2)}`
var RESOURCE_CACHE = null
// Custom extensions
String.prototype.containsAny = function (substrings=[]) {
return substrings.some(substring => this.includes(substring))
}
// For Debugging
IS_TESTING = self.registration.scope.includes("127.0.0.1")
STOP_CACHING = IS_TESTING
var log = (text, color="white") => IS_TESTING ? console.log(`%c${text}`, `color: black; background-color: ${color};`) : 0
self.addEventListener("install", event => {
event.waitUntil((async () => {
await self.skipWaiting()
})())
});
self.addEventListener("activate", event => {
log("Service Worker activated")
// Remove obsolete caches
event.waitUntil((async () => {
await Promise.allSettled([clients.claim(), load_both_caches(), delete_obsolete_caches()])
})())
});
async function load_both_caches() {
await caches.open(`${APP_VERSION.toFixed(2)}`)
DOCUMENT_CACHE = await caches.open(DOCUMENT_CACHE_NAME)
RESOURCE_CACHE = await caches.open(RESOURCE_CACHE_NAME)
}
async function delete_obsolete_caches() {
let cache_names = await caches.keys()
await Promise.all(cache_names.map(cache_name => {
if (![DOCUMENT_CACHE_NAME, RESOURCE_CACHE_NAME, `${APP_VERSION.toFixed(2)}`].includes(cache_name)) {
log(`Deleting obsolete cache: '${cache_name}'`, "rgb(255, 128, 128)")
return caches.delete(cache_name)
}
}))
}
self.addEventListener("fetch", request_event => {
request_event.respondWith(STOP_CACHING ? fetch(request_event.request) : get_request(request_event))
});
async function get_request(request_event) {
let request = request_event.request
let url = request.url
if(DOCUMENT_CACHE == null || RESOURCE_CACHE == null) {
await load_both_caches()
}
// Check if the request is for a document
if(url.match(/\/$/) || url.containsAny([".html", ".js", ".css"]) && !url.includes(".json") && !url.includes("apis.google.com")) {
/**
* So here's the game plan:
* Check if a cache version exists.
* | --- If it doesn't, then return a simple fetch request with no timeout
* | --- If it does call a fetch request that times out after 'x' seconds, defaulting to the cache version
* This ensures that the user gets the latest possible version, as fast as possible
*
* PS: In all possible cases, cache the request after network-fetching it
*/
// Check if a cache version exists
let cache_match = await DOCUMENT_CACHE.match(request, { ignoreVary: true })
if(cache_match == undefined || cache_match == null) {
// A cached version DOESN'T exist
log("A cached version DOESN'T exist, performing a network request", "rgb(128, 128, 255)")
let network_match = await fetch(request).catch(err => null)
if(network_match) {
DOCUMENT_CACHE.put(request, network_match.clone())
}
return network_match
}
else {
// A cached version DOES exist
log("A cached version DOES exist", "rgb(128, 128, 255)")
log("Performing network match *in background*")
const SECONDS_TO_TIMEOUT = 5
const abort_controller = new AbortController()
const abort_signal = abort_controller.signal
const timeout_id = setTimeout(() => abort_controller.abort(), SECONDS_TO_TIMEOUT*1000)
// Perform a network request
fetch(request, {signal: abort_signal}).then(data => {
clearTimeout(timeout_id)
log("Network match completed before timeout", "rgb(128, 255, 128)")
return data
}).catch(err => {
if(err.name == "AbortError") {
log("Network request took too long, returning cached version", "rgb(255, 128, 128)")
return null
}
throw err
}).catch(err => null).then(data => {
if(data == undefined || data == null) {
// Do nothing if it fails
}
else {
// Save it to cache for next time
DOCUMENT_CACHE.put(request, data.clone())
return data
}
})
// Send cached match regardless
return cache_match
}
}
// Check if the request is for a resource
else if(url.containsAny([".json", "fonts/", "images/", "fonts.googleapis.com"])) {
// Perform a cache request
let match = await RESOURCE_CACHE.match(request, { ignoreVary: true })
if (match != undefined && match != null) return match
// Since a cached version doesn't exist, perform a network request
match = await fetch(request)
RESOURCE_CACHE.put(request, match.clone())
return match
}
// Doesn't belong to either type of cache, so perform a network request
return await fetch(request)
}