push-notifications-client-codelab-complete (source) to see the complete code.
This codelab is known to work with the following operating system and browser combinations:
This codelab is known to not work with the following operating systems (or operating system and browser combinations):
The code editor that you see to the right of these instructions will be called the Glitch UI throughout this codelab.
If you're in a Chrome incognito or guest window, you may have trouble completing the codelab. Consider using a signed-in profile instead.
Before you can get push notifications working, you need to set up your server and client with authentication keys. See Sign your web push protocol requests to learn why.
npx web-push generate-vapid-keys
. Copy the private key and public key values..env
and update VAPID_PUBLIC_KEY
and VAPID_PRIVATE_KEY
. Set VAPID_SUBJECT
to mailto:test@test.test
. All of these values should be wrapped in double quotes. After making your updates, your .env
file should look similar to this:VAPID_PUBLIC_KEY="BKiwTvD9HA…"
VAPID_PRIVATE_KEY="4mXG9jBUaU…"
VAPID_SUBJECT="mailto:test@test.test"
Environment variable values (the stuff in .env
) are unique to a single Glitch project. If you remix your project, the values in .env
won't get copied over.
public/index.js
.VAPID_PUBLIC_KEY_VALUE_HERE
with the value of your public key.Your client will eventually need a service worker to receive and display notifications. It's best to register the service worker as early as possible. See Receive and display the pushed messages as notifications for more context.
// TODO add startup logic here
comment with the following code:// TODO add startup logic here
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.register('./service-worker.js').then(serviceWorkerRegistration => {
console.info('Service worker was registered.');
console.info({serviceWorkerRegistration});
}).catch(error => {
console.error('An error occurred while registering the service worker.');
console.error(error);
});
subscribeButton.disabled = false;
} else {
console.error('Browser does not support service workers or push messages.');
}
subscribeButton.addEventListener('click', subscribeButtonHandler);
unsubscribeButton.addEventListener('click', unsubscribeButtonHandler);
Key Term: The tab that you just opened will be referred to as the app tab throughout this codelab.
Control+Shift+J
(or Command+Option+J
on Mac) to open DevTools.Service worker was registered.
logged to the Console.The previous instruction assumes that you're using Google Chrome and Chrome DevTools.
You should never request permission to send push notifications on page load. Instead, your UI should ask the user if they want to receive push notifications. Once they explicitly confirm (with a button click, for example) then you can start the formal process for getting push notification permission from the browser.
public/index.js
replace the // TODO
comment in subscribeButtonHandler()
with the following code:// TODO
// Prevent the user from clicking the subscribe button multiple times.
subscribeButton.disabled = true;
const result = await Notification.requestPermission();
if (result === 'denied') {
console.error('The user explicitly denied the permission request.');
return;
}
if (result === 'granted') {
console.info('The user accepted the permission request.');
}
If you're in an incognito or guest window, your browser may deny the request automatically. Keep an eye out for any browser UI indicating that the request was blocked automatically.
The subscription process involves interacting with a web service controlled by the browser vendor that's called a push service. Once you get the push notification subscription information you need to send it to a server and have the server store it in a database long-term. See Subscribe the client to push notifications for more context about the subscription process.
subscribeButtonHandler()
:subscribeButton.disabled = true;
const result = await Notification.requestPermission();
if (result === 'denied') {
console.error('The user explicitly denied the permission request.');
return;
}
if (result === 'granted') {
console.info('The user accepted the permission request.');
}
const registration = await navigator.serviceWorker.getRegistration();
const subscribed = await registration.pushManager.getSubscription();
if (subscribed) {
console.info('User is already subscribed.');
notifyMeButton.disabled = false;
unsubscribeButton.disabled = false;
return;
}
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY)
});
notifyMeButton.disabled = false;
fetch('/add-subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
});
The userVisibleOnly
option must be true
. It may one day be possible to push messages without displaying user-visible notifications (silent pushes) but browsers currently don't allow that capability because of privacy concerns.
The applicationServerKey
value relies on a utility function that converts a base64 string to a Uint8Array. This value is used for authentication between your server and the push service.
After a user has subscribed to push notifications, your UI needs to provide a way to unsubscribe in case the user changes their mind and no longer wants to receive push notifications.
// TODO
comment in unsubscribeButtonHandler()
with the following code:// TODO
const registration = await navigator.serviceWorker.getRegistration();
const subscription = await registration.pushManager.getSubscription();
fetch('/remove-subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({endpoint: subscription.endpoint})
});
const unsubscribed = await subscription.unsubscribe();
if (unsubscribed) {
console.info('Successfully unsubscribed from push notifications.');
unsubscribeButton.disabled = true;
subscribeButton.disabled = false;
notifyMeButton.disabled = true;
}
As mentioned before, you need a service worker to handle the receiving and displaying of messages that were pushed to the client from your server. See Receive and display the pushed messages as notifications for more detail.
public/service-worker.js
and replace the // TODO
comment in the service worker's push
event handler with the following code:// TODO
let data = event.data.json();
const image = 'https://cdn.glitch.com/614286c9-b4fc-4303-a6a9-a4cef0601b74%2Flogo.png?v=1605150951230';
const options = {
body: data.options.body,
icon: image
}
self.registration.showNotification(
data.title,
options
);
You can customize the notification in lots of ways. See the parameters of ServiceWorkerRegistration.showNotification()
to learn more.
The call to self.skipWaiting()
in your service worker's install
listener is important to understand. See Skip the waiting phase for an explanation. Without it, the code changes that you make to your service worker wouldn't take effect immediately. You may or may not want to use this feature on your own website depending on your needs, but either way it's important to understand its effect.
In the real-world, you'll probably use the notification as a way to re-engage your user and prompt them to visit your site. To do that, you need to configure your service worker a bit more.
// TODO
comment in the service worker's notificationclick
event handler with the following code:// TODO
event.notification.close();
event.waitUntil(self.clients.openWindow('https://web.dev'));
https://web.dev
.ServiceWorkerRegistration.showNotification()
to discover all of the different ways you can customize notifications.