<!DOCTYPE html>
<html>
<head>
<script src="helper.js"></script>
<script src="sw-registration.js"></script>
</head>
<body>
<h1>Service Worker events</h1>
<button type="button" onclick="registerWorker()">Register SW</button>
<button type="button" onclick="unregisterWorker()">Unregister SW</button>
<button type="button" onclick="clearOutput()">Clear output</button>
<pre id="output"></pre>
</body>
</html>
/**
* Handler for messages coming from SW registration
*/
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener("message", function(event) {
writeLine("event " + event.data.type + ":" + event.data.message + " " + event.data.client)
if(event.data.type === "check" && !event.data.message){
removeServiceWorkerScreenCover()
}
});
}
/**
* Wrapped in a function as it helps testing
*/
function registerWorker() {
if ('serviceWorker' in navigator) {
addServiceWorkerScreenCover();
navigator.serviceWorker.register('sw.js').then(function(reg) {
reg.onupdatefound = function() {
var installingWorker = reg.installing;
installingWorker.onstatechange = function() {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
writeLine("new or updated content available")
removeServiceWorkerScreenCover();
} else {
writeLine("content available offline");
removeServiceWorkerScreenCover();
}
break;
}
};
};
if (reg.active) {
// here I know that `service-worker.js` was already installed
// but not sure it there are changes
writeLine("service worker is active")
navigator.serviceWorker.controller.postMessage("check");
}
}).catch(function(e) {
writeLine("Error during service worker registration")
});
} else {
window.alert(`I'm affraid this demo won't work in this browser, please try latest Chrome or Firefox for the best results`);
}
}
/**
* Utility function that unregisters SW, but you still need to reload to see SW removed completely
* This does not delete items in Cache
*/
function unregisterWorker() {
navigator.serviceWorker.getRegistrations()
.then(function(registrations) {
return Promise.all(
registrations.map(function(reg) {
return reg.unregister().then(function(boolean) {
if (boolean) {
writeLine("unregistered")
} else {
writeLine("unable to deregister")
}
});
})
)
})
.then(function() {
writeLine("unregistrations complete")
})
}
# Checking active Service Worker
Here **SW** is Service Worker
Issue: no event handler in the case when active **SW** will not be updated
Solution: `postMessage` communication between **SW** client and **SW** registration
onload:
- **SW** registration is always invoked
- **SW** client sets up handler for `postMessage` coming from **SW** registration
- in case of **new registration** `onupdatefound` will be called
- `onstatechange` will be setup
- `onstatechange` will be called
- `installingWorker.state` changes to `installed`
- there is no `navigator.serviceWorker.controller` found
- in case of **update** `onupdatefound` will be called
- `serviceWorkerRegistration.active` is set - always before updates
- `onstatechange` will be setup
- `onstatechange` will be called
- `installingWorker.state` changes to `installed`
- `navigator.serviceWorker.controller` exists
- otherwise
- `serviceWorkerRegistration.active` is set
- `postMessage` to **SW** registration with name **check**
- **SW** registration checks `message` in handler, if it matches **check**, then checks for changes
- **check** looks for changes between **SW** registration and **cache**
- value is returned to **SW** client via `postMessage`
// Most of the content here is from `sw-precache` library
// Precache config
//////////////////////////////
var cacheName = 'v1';
var precacheConfig = [
["helper.js", "a"],
["sw-registration.js", "b"],
["index.html", "c"]
]
var hashParamName = '_sw-precache';
var urlsToCacheKeys = new Map(
precacheConfig.map(function(item) {
var relativeUrl = item[0];
var hash = item[1];
var absoluteUrl = new URL(relativeUrl, self.location);
var cacheKey = createCacheKey(absoluteUrl, hashParamName, hash, false);
return [absoluteUrl.toString(), cacheKey];
})
);
function setOfCachedUrls(cache) {
return cache.keys().then(function(requests) {
return requests.map(function(request) {
return request.url;
});
}).then(function(urls) {
return new Set(urls);
});
}
function createCacheKey(originalUrl, paramName, paramValue,
dontCacheBustUrlsMatching) {
// Create a new URL object to avoid modifying originalUrl.
var url = new URL(originalUrl);
// If dontCacheBustUrlsMatching is not set, or if we don't have a match,
// then add in the extra cache-busting URL parameter.
if (!dontCacheBustUrlsMatching ||
!(url.toString().match(dontCacheBustUrlsMatching))) {
url.search += (url.search ? '&' : '') +
encodeURIComponent(paramName) + '=' + encodeURIComponent(paramValue);
}
return url.toString();
}
//=================
/**
* Put configured files to cache
*/
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return setOfCachedUrls(cache).then(function(cachedUrls) {
return Promise.all(
Array.from(urlsToCacheKeys.values()).map(function(cacheKey) {
// If we don't have a key matching url in the cache already, add it.
if (!cachedUrls.has(cacheKey)) {
return cache.add(new Request(cacheKey, {
credentials: 'same-origin',
redirect: 'follow'
}));
}
})
);
});
}).then(function() {
// Force the SW to transition from installing -> active state
return self.skipWaiting();
})
);
});
/**
* Purge redundant files and take control of clients
*/
self.addEventListener('activate', function(event) {
var setOfExpectedUrls = new Set(urlsToCacheKeys.values());
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.keys().then(function(existingRequests) {
return Promise.all(
existingRequests.map(function(existingRequest) {
if (!setOfExpectedUrls.has(existingRequest.url)) {
return cache.delete(existingRequest);
}
})
);
});
}).then(function() {
return self.clients.claim();
})
);
});
/**
* Import additional functionality
* mimick sw-precache library
*/
importScripts('sw-helper.js');
function getOutputEl() {
return document.getElementById("output")
}
function getOutputText() {
return getOutputEl().innerHTML
}
function setOutputText(text) {
getOutputEl().innerHTML = text
}
function writeLine(text) {
setOutputText(getOutputText() + "\n" + text)
}
function clearOutput() {
setOutputText("")
}
var COVER_ID = "service-worker-screen-cover";
function addServiceWorkerScreenCover() {
var screenCover = '<div id="' + COVER_ID + '" style="' +
' position: absolute;' +
' top: 0;' +
' left: 0;' +
' right: 0;' +
' bottom: 0;' +
' background: green;' +
' z-index: 9999999999;' +
' position: fixed;' +
'">' +
' <div>' +
' <p>Loading ...</p>' +
' </div>' +
'</div>';
var bodyElements = document.querySelectorAll('body script');
if(!bodyElements.length){
bodyElements = document.querySelectorAll('body div');
}
var lastBodyEl = bodyElements[bodyElements.length - 1];
var newDiv = document.createElement("div");
newDiv.innerHTML = screenCover;
document.body.insertBefore(newDiv, lastBodyEl);
}
function removeServiceWorkerScreenCover(){
var el = document.getElementById(COVER_ID);
if(el){
el.parentNode.removeChild(el);
}
}
/**
* Handler for messages coming from clients that registered this SW
*/
self.addEventListener('message', function(event) {
if (event.data === "check")
hasChanges().then(function(changed) {
sendMessageToAllClients(event.data, changed)
})
else
sendMessageToAllClients(event.data)
})
/**
* Utility function to send messages back to clients using this worker
*/
function sendMessageToAllClients(type, msg) {
self.clients.matchAll().then(function(clientList) {
clientList.forEach(function(client) {
client.postMessage({
client: client.id,
type: type,
message: msg
});
})
})
}
/**
* Utility function to check if this SW has changes that affect cache
* Logic taken from `install` `activate` events.
*
* It also could be some just a timestamp generated on server
*
* It DOES NOT reflect the logic of a browser:
* > If there is even a byte's difference in the service worker file
* > compared to what it currently has, it considers it new.
* ^^ taken from https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
*/
function hasChanges() {
return caches.open(cacheName)
.then(function(cache) {
var changed = false
return setOfCachedUrls(cache).then(function(cachedUrls) {
Array.from(urlsToCacheKeys.values()).forEach(function(cacheKey) {
if (!cachedUrls.has(cacheKey)) {
changed = true
}
})
return changed;
}).then(function() {
if (changed) {
return changed
}
return cache.keys().then(function(existingRequests) {
var setOfExpectedUrls = new Set(urlsToCacheKeys.values());
existingRequests.forEach(function(existingRequest) {
if (!setOfExpectedUrls.has(existingRequest.url)) {
changed = true
}
})
return changed;
});
});
});
}