If you have tried CRA (create react app), have you ever wonder what does this file - `/src/serviceWorker.js` do? In this article, I will demonstrate what we can do by implementing the service worker into our application.
Before we start, the service worker might be buggy once we didn't handle it properly, therefore, I highly recommend you check this article to know a few important knowledge beforehand - Offline-First Considerations
Agenda
- Register Service Worker
- Event: install
- Event: activate
- Event: fetch
- Event: message
- Event: updatefound & statechange
- Web Push Notification
- Event: push
Register Service Worker
At first, we need to register service worker.
navigator.serviceWorker
.register("/sw.js")
.then((reg) => {
// no controller exist, page wasn't loaded via a service worker
if (!navigator.serviceWorker.controller) {
return;
}
if (reg.waiting) {
// If we have a new version of the service worker is waiting,
// we can display the message to the user and allow them
// to trigger updates manually.
// Otherwise, the browser will replace the service worker
// when the user closes or navigate away from all tabs using
// the current service worker.
return;
}
if (reg.installing) {
// If we have a new service worker is installing, we can
// tracking the status and display the message once the
// installation is finished.
return;
}
});
Event: install
The install
event is the first event a service worker gets, and it only happens once.
We can cache the pages here.
const urlsToCache = ["/faq", "/contact"];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
Event: activate
Once your service worker is ready to control clients, we'll get an activate
event.
It's common to delete the old caches here.
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
const promiseArr = cacheNames.map((item) => {
if (item !== CACHE_NAME) {
return caches.delete(item);
}
});
return Promise.all(promiseArr);
})
);
});
Event: fetch
We can intercept the request and custom the response in the fetch
event.
self.addEventListener("fetch", (event) => {
// hijacking path and return a mock HTML content
if (event.request.url.includes("/faq")) {
event.respondWith(
new Response("<div>Mock FAQ Page</div>", {
headers: { "Content-Type": "text/html" },
})
);
}
// hijacking API request and return mock response in JSON format
if (event.request.url.includes("/api/users")) {
const data = [
{
id: "0001",
name: "andrew",
},
];
const blob = new Blob(
[JSON.stringify(data, null, 2)],
{ type: "application/json" }
);
const init = { status: 200, statusText: "default mock response" };
const defaultResponse = new Response(blob, init);
event.respondWith(defaultResponse);
}
// Stale-while-revalidate:
// return the cached version if it exists. At the same time,
// send a request to get the latest version and update the cache
const requestUrl = new URL(event.request.url);
if (requestUrl.pathname.startsWith("/avatars/")) {
const response = caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((response) => {
const networkFetch = fetch(event.request)
.then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return response || networkFetch;
});
});
event.respondWith(response);
return;
}
});
Event: message
We can use postMessage to communicate with the service worker.
Here, we bind a click event to send a postMessage to the service worker.
In the service worker, we can listen to the message
event to received the postMessage.
- send message to service worker
function handleClickEven() {
worker.postMessage({ action: "skipWaiting" });
}
- receive message
self.addEventListener("message", (event) => {
if (event.data.action === "skipWaiting") {
// skip waiting to apply the new version of service worker
self.skipWaiting();
}
});
Event: updatefound & statechange
We can listen to the updatefound
event to see if we have a new service worker.
If there is a service worker is installing, we listen to the statechange
event,
once the install is finished, we can display a message to notify our users.
self.addEventListener("updatefound", () => {
if (reg.installing) {
reg.installing.addEventListener("statechange", () => {
if (worker.state == "installed") {
// display a message to tell our users that
// there's a new service worker is installed
}
});
}
});
Web Push Notification
We can use a service worker to handle the notification.
Here, we ask permission to display the notification, if the user agrees,
then we can get the subscription information.
- Get permission & subscription
if (Notification && Notification.permission === "default") {
Notification.requestPermission().then((result) => {
if (result === "denied") {
return;
}
if (result === "granted") {
if (navigator && navigator.serviceWorker) {
navigator.serviceWorker.ready.then((reg) => {
reg.pushManager
.getSubscription()
.then((subscription: any) => {
if (!subscription) {
// we need to encrypt the data for web push notification,
// I use web-push to generate the public and private key.
// You can check their documentation for more detail.
// https://github.com/web-push-libs/web-push
const vapidPublicKey = "xxxxx";
const applicationServerKey =
urlBase64ToUint8Array(vapidPublicKey);
return reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey,
});
}
return subscription;
})
.then((sub) => {
// Get the subscription information here, we need to
// create an API and save them into our database
/*
{
endpoint:
"https://fcm.googleapis.com/fcm/send/xxx",
keys: {
auth: "xxx",
p256dh: "xxx",
},
};
*/
});
});
}
}
});
}
- send notification
const webpush = require('web-push');
webpush.setVapidDetails("mailto:oahehc@gmail.com", "my_private_key");
const pushConfig = {
endpoint: sub.endpoint,
keys: {
auth: sub.keys.auth,
p256dh: sub.keys.p256dh,
},
};
webpush
.sendNotification(
pushConfig,
JSON.stringify({ title: "Test Title", content: "Test Content" })
)
.catch((err) => {
console.log(err);
});
Event: push
We can receive the web push message by listening to the push
event.
self.addEventListener("push", (event) => {
if (event.data) {
try {
data = JSON.parse(event.data.text());
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.content,
icon: "/icon-192.png",
badge: "/badge-192.png",
})
);
} catch (e) {
console.error('push event data parse fail');
}
}
});
Conclusion
That's it, I hope this article can help you get familiar with the service worker.
Top comments (0)