-
-
Save modster/84dbeacc2e3cd7cc5ae18c7fc165c421 to your computer and use it in GitHub Desktop.
Code for Service Workers 8 - Messaging between Clients and Workers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const APP = { | |
SW: null, | |
init() { | |
//called after DOMContentLoaded | |
//register our service worker | |
APP.registerSW(); | |
document | |
.getElementById('colorForm') | |
.addEventListener('submit', APP.saveColor); | |
document.querySelector('h2').addEventListener('click', (ev) => { | |
//send a message to the service worker | |
//have it bounce back to all pages sharing that sw | |
}); | |
}, | |
registerSW() { | |
if ('serviceWorker' in navigator) { | |
// Register a service worker hosted at the root of the site | |
navigator.serviceWorker.register('/sw.js').then( | |
(registration) => { | |
APP.SW = | |
registration.installing || | |
registration.waiting || | |
registration.active; | |
}, | |
(error) => { | |
console.log('Service worker registration failed:', error); | |
} | |
); | |
//listen for the latest sw | |
navigator.serviceWorker.addEventListener('controllerchange', async () => { | |
APP.SW = navigator.serviceWorker.controller; | |
}); | |
//listen for messages from the service worker | |
navigator.serviceWorker.addEventListener('message', APP.onMessage); | |
} else { | |
console.log('Service workers are not supported.'); | |
} | |
}, | |
saveColor(ev) { | |
ev.preventDefault(); | |
let name = document.getElementById('name'); | |
let color = document.getElementById('color'); | |
let strName = name.value.trim(); | |
let strColor = color.value.trim(); | |
if (strName && strColor) { | |
let person = { | |
id: Date.now(), | |
name: strName, | |
color: strColor, | |
}; | |
console.log('Save', person); | |
//send the data to the service worker | |
//, otherAction: 'hello' | |
APP.sendMessage({ addPerson: person }); | |
} | |
}, | |
sendMessage(msg) { | |
//send some structured-cloneable data from the webpage to the sw | |
if (navigator.serviceWorker.controller) { | |
navigator.serviceWorker.controller.postMessage(msg); | |
} | |
}, | |
onMessage({ data }) { | |
//got a message from the service worker | |
console.log('Web page receiving', data); | |
}, | |
}; | |
document.addEventListener('DOMContentLoaded', APP.init); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Intro to Service Workers</title> | |
<!-- import google fonts --> | |
<link rel="preconnect" href="https://fonts.gstatic.com" /> | |
<link | |
href="https://fonts.googleapis.com/css2?family=Lato&family=Montserrat:wght@400;700&display=swap" | |
rel="stylesheet" | |
/> | |
<link rel="stylesheet" href="main.css" /> | |
</head> | |
<body> | |
<header> | |
<h1>Intro to Service Workers</h1> | |
<h2>Messaging between Clients and Service Workers</h2> | |
</header> | |
<!-- | |
IMAGES | |
https://picsum.photos/id/1011/5472/3648 | |
https://picsum.photos/id/1016/3844/2563 | |
--> | |
<main> | |
<form id="colorForm" name="colorForm"> | |
<p> | |
<label for="name">Character Name</label> | |
<input type="text" id="name" name="name" /> | |
</p> | |
<p> | |
<label for="color">Favourite Colour</label> | |
<input type="color" id="color" name="color" /> | |
</p> | |
<p> | |
<button id="btnSave">Save</button> | |
</p> | |
</form> | |
</main> | |
<script defer src="./app.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html { | |
font-size: 20px; | |
font-family: 'Montserrat', sans-serif; | |
line-height: 1.5; | |
background-color: #222; | |
color: #eee; | |
} | |
body { | |
min-height: 100vh; | |
background-color: inherit; | |
color: inherit; | |
} | |
header, | |
main { | |
margin: 1rem 2rem; | |
} | |
main { | |
display: flex; | |
flex-direction: row; | |
flex-wrap: wrap; | |
gap: 1rem; | |
justify-content: space-around; | |
} | |
h1 { | |
color: orangered; | |
font-weight: 700; | |
} | |
h2 { | |
color: orange; | |
font-weight: 700; | |
} | |
p { | |
font-family: 'Lato', sans-serif; | |
font-weight: 400; | |
} | |
form { | |
outline: 1px solid #999; | |
} | |
form p { | |
display: flex; | |
flex-direction: column; | |
justify-content: flex-start; | |
align-items: flex-start; | |
padding: 0.5rem 1rem; | |
} | |
label, | |
input { | |
font-size: 1rem; | |
margin: 0.5rem 0; | |
} | |
main img { | |
width: clamp(200px, 400px, 600px); | |
} | |
main a { | |
color: orange; | |
text-decoration-style: wavy; | |
} | |
button { | |
font-size: 1rem; | |
background-color: cornflowerblue; | |
color: white; | |
padding: 0.25rem 2rem; | |
border: none; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const version = 4; | |
let staticName = `staticCache-${version}`; | |
let dynamicName = `dynamicCache`; | |
let imageName = `imageCache-${version}`; | |
let options = { | |
ignoreSearch: false, | |
ignoreMethod: false, | |
ignoreVary: false, | |
}; | |
//starter html and css and js files | |
let assets = ['/', '/index.html', '/css/main.css', '/js/app.js', '/404.html']; | |
//starter images | |
let imageAssets = ['/img/1011-800x600.jpg', '/img/distracted-boyfriend.jpg']; | |
self.addEventListener('install', (ev) => { | |
// service worker has been installed. | |
//Extendable Event | |
console.log(`Version ${version} installed`); | |
// build a cache | |
ev.waitUntil( | |
caches | |
.open(staticName) | |
.then((cache) => { | |
cache.addAll(assets).then( | |
() => { | |
//addAll == fetch() + put() | |
// console.log(`${staticName} has been updated.`); | |
}, | |
(err) => { | |
console.warn(`failed to update ${staticName}.`); | |
} | |
); | |
}) | |
.then(() => { | |
caches.open(imageName).then((cache) => { | |
cache.addAll(imageAssets).then( | |
() => { | |
console.log(`${imageName} has been updated.`); | |
}, | |
(err) => { | |
console.warn(`failed to update ${staticName}.`); | |
} | |
); | |
}); | |
}) | |
); | |
}); | |
self.addEventListener('activate', (ev) => { | |
// when the service worker has been activated to replace an old one. | |
//Extendable Event | |
console.log('activated'); | |
// delete old versions of caches. | |
ev.waitUntil( | |
caches.keys().then((keys) => { | |
return Promise.all( | |
keys | |
.filter((key) => { | |
if (key != staticName && key != imageName) { | |
return true; | |
} | |
}) | |
.map((key) => caches.delete(key)) | |
).then((empties) => { | |
//empties is an Array of boolean values. | |
//one for each cache deleted | |
}); | |
}) | |
); | |
}); | |
self.addEventListener('fetch', (ev) => { | |
// Extendable Event. | |
ev.respondWith( | |
caches.match(ev.request).then((cacheRes) => { | |
return ( | |
cacheRes || | |
Promise.resolve().then(() => { | |
let opts = { | |
mode: ev.request.mode, //cors, no-cors, same-origin, navigate | |
cache: 'no-cache', | |
}; | |
if (!ev.request.url.startsWith(location.origin)) { | |
//not on the same domain as my html file | |
opts.mode = 'cors'; | |
opts.credentials = 'omit'; | |
} | |
return fetch(ev.request.url, opts).then( | |
(fetchResponse) => { | |
//we got a response from the server. | |
if (fetchResponse.ok) { | |
return handleFetchResponse(fetchResponse, ev.request); | |
} | |
//not ok 404 error | |
if (fetchResponse.status == 404) { | |
if (ev.request.url.match(/\.html/i)) { | |
return caches.open(staticName).then((cache) => { | |
return cache.match('/404.html'); | |
}); | |
} | |
if ( | |
ev.request.url.match(/\.jpg$/i) || | |
ev.request.url.match(/\.png$/i) | |
) { | |
return caches.open(imageName).then((cache) => { | |
return cache.match('/img/distracted-boyfriend.jpg'); | |
}); | |
} | |
} | |
}, | |
(err) => { | |
//this is the network failure | |
//return the 404.html file if it is a request for an html file | |
if (ev.request.url.match(/\.html/i)) { | |
return caches.open(staticName).then((cache) => { | |
return cache.match('/404.html'); | |
}); | |
} | |
} | |
); | |
}) | |
); | |
}) //end of match().then() | |
); //end of respondWith | |
}); //end of fetch listener | |
const handleFetchResponse = (fetchResponse, request) => { | |
let type = fetchResponse.headers.get('content-type'); | |
// console.log('handle request for', type, request.url); | |
if (type && type.match(/^image\//i)) { | |
//save the image in image cache | |
// console.log(`SAVE ${request.url} in image cache`); | |
return caches.open(imageName).then((cache) => { | |
cache.put(request, fetchResponse.clone()); | |
return fetchResponse; | |
}); | |
} else { | |
//save in dynamic cache - html, css, fonts, js, etc | |
// console.log(`SAVE ${request.url} in dynamic cache`); | |
return caches.open(dynamicName).then((cache) => { | |
cache.put(request, fetchResponse.clone()); | |
return fetchResponse; | |
}); | |
} | |
}; | |
self.addEventListener('message', (ev) => { | |
let data = ev.data; | |
//console.log({ ev }); | |
let clientId = ev.source.id; | |
// console.log('Service Worker received', data, clientId); | |
if ('addPerson' in data) { | |
let msg = 'Thanks. Pretend I did something with the data.'; | |
sendMessage( | |
{ | |
code: 0, | |
message: msg, | |
}, | |
clientId | |
); | |
} | |
if ('otherAction' in data) { | |
let msg = 'Hola'; | |
sendMessage({ | |
code: 0, | |
message: msg, | |
}); | |
} | |
}); | |
const sendMessage = async (msg, clientId) => { | |
let allClients = []; | |
if (clientId) { | |
let client = await clients.get(clientId); | |
allClients.push(client); | |
} else { | |
allClients = await clients.matchAll({ includeUncontrolled: true }); | |
} | |
return Promise.all( | |
allClients.map((client) => { | |
// console.log('postMessage', msg, 'to', client.id); | |
return client.postMessage(msg); | |
}) | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment