Add an example to implement Push Notifications?
leo-petrucci opened this issue · comments
Hey everyone, great job on the plugin, it's been super useful!
While working on my PWA and this plugin I had a lot of confusion around how to get Push Notifications working, I had a hard time finding the information I needed in the docs so I just wanted to share my thoughts here.
1. There's no examples
When Googling "vite pwa push notifications", these are the results I get:
- An app-specific implementation of a service worker with loads of added stuff (https://stackoverflow.com/questions/75988769/vite-pwa-plugin-how-to-add-webpush-notifications)
- The "Getting Started" guide on the docs, which only has a passing mention of push notifications (https://vite-pwa-org.netlify.app/guide/)
- Reddit thread where it's clear nobody really knows what to do (https://www.reddit.com/r/vuejs/comments/12ghvak/pwa_with_vite/)
- Part of an implementation, that doesn't show what a basic service worker would look like (vite-pwa/vite-plugin-pwa#84)
You get the idea.
2. The docs aren't really very informative
This page is the only mention of push notifications in the docs.
This links to a page that I don't think exists anymore in the Workbox docs (It redirects to this homepage https://web.dev/explore/notifications).
And it also links to the Elk app repository, which is a really good implementation, but it's also a huge and extremely complicated app which is sure to overwhelm anyone that doesn't know what they're looking at.
3. Requiring an understanding of Workbox just to implement notifications
I understand that the docs are trying to push people to read the entirety of the Workbox docs to implement notifications, but to me that seems really overkill.
Push Notifications really only require two listeners, it shouldn't require a developer to learn how to use Workbox in its entirety before they can implement it. After all, we don't expect every developer that use vite-pwa
to understand what's going on in their service worker when they first install the plugin, so why should it be different for adding notifications?
Push notifications are arguably the # 1 reason people want to make a PWA, so why make it so difficult for people to find the answer they need?
Actionable points
From this there's a couple of things that I think are missing:
- A definite answer in the docs that clearly states that to get push notifications working you need a custom service worker
- A minimal custom Service Worker example which does two things:
a. Implements the basic functionality implemented by the service worker generated by the plugin (Offline support, and whatever else)
b. Has a minimal implementation of receiving and clicking on notifications
I think we can all agree on the fact that we want PWA adoption to increase, I think doing this will improve the developer experience massively and lower the barrier of entry to making PWAs.
I'm in no way an expert with service workers, however this is my current service worker implementation in Typescript (pieced together from Googling and the Elk repository). If nobody else has a better example I'd love if this was added to the docs.
// ./service-worker/sw.ts
/// <reference lib="WebWorker" />
/// <reference types="vite/client" />
import {
cleanupOutdatedCaches,
createHandlerBoundToURL,
precacheAndRoute,
} from "workbox-precaching";
import { NavigationRoute, registerRoute } from "workbox-routing";
declare const self: ServiceWorkerGlobalScope;
self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") self.skipWaiting();
});
const entries = self.__WB_MANIFEST;
precacheAndRoute(entries);
// clean old assets
cleanupOutdatedCaches();
// only cache pages and external assets on local build + start or in production
if (import.meta.env.PROD) {
// to allow work offline
registerRoute(new NavigationRoute(createHandlerBoundToURL("index.html")));
}
self.addEventListener("push", onPush);
self.addEventListener("notificationclick", onNotificationClick);
export function onPush(event: PushEvent) {
console.log("[Service Worker] Push Received.");
if (event.data) {
const { title, ...rest } = event.data.json();
event.waitUntil(
self.registration.showNotification(title, {
...rest,
})
);
}
}
export function onNotificationClick(event: NotificationEvent) {
const reactToNotificationClick = new Promise((resolve) => {
event.notification.close();
resolve(openUrl(event.notification.data.url));
});
event.waitUntil(reactToNotificationClick);
}
function findBestClient(clients: WindowClient[]) {
const focusedClient = clients.find((client) => client.focused);
const visibleClient = clients.find(
(client) => client.visibilityState === "visible"
);
return focusedClient || visibleClient || clients[0];
}
async function openUrl(url: string) {
const clients = await self.clients.matchAll({ type: "window" });
// Chrome 42-48 does not support navigate
if (clients.length !== 0 && "navigate" in clients[0]) {
const client = findBestClient(clients as WindowClient[]);
await client.navigate(url).then((client) => client?.focus());
}
await self.clients.openWindow(url);
}
And this is my Vite config:
VitePWA({
strategies: "injectManifest",
srcDir: "./service-worker",
filename: "sw.ts",
scope: "/",
devOptions: {
enabled: mode === "development",
},
// ... other configs here
})
For the record, I'm fully willing to write the documentation page myself if given the go-ahead.
Push notifications is out of scope from vite-plugin-pwa
, check this workbox entry about using strategies: https://developer.chrome.com/docs/workbox/the-ways-of-workbox#when_to_use_injectmanifest
The elk.zone repo has push notifications and web shared target api PWA capabilities, check also the PWA cookbook in the docs for some impl. details:
You also need to deal with permissions and push notification backend registration: https://github.com/elk-zone/elk/tree/main/composables/push-notifications.
Since Elk should deal with multiple backend, the logic has some complex logic (you cannot register multiple push notification from the same app, the browser will prevent multiple registrations, will be treated as spam: https://docs.elk.zone/pwa#push-notifications-subscription-logic).
You can also check mastodon sw impl. (and the logic there using react instead vue): https://github.com/mastodon/mastodon/tree/787279ad67cb9bd06b4628943f19ae8054a60b33/app/javascript/mastodon/service_worker
Hey @userquin, thank you for the quick reply.
Push notifications is out of scope from vite-plugin-pwa
That's absolutely understandable, that's why it's not supported by default. However, they are important enough to be mentioned in the documentation.
Is there a specific reason as to why the docs on Push implementation shouldn't be expanded?
As I said I've looked at all the resources and browsed the Discord for a bit, there were quite a few people that were confused by how to do Push. You were there to help them, thankfully, but wouldn't it be better if that info was somewhere official and accessible to all without joining the Discord?
As good as Elk is I don't think linking to the repo and telling people to figure it out is great DX.
Agree with @leo-petrucci , more details on how to enable push notifications on the doc would really be helpful.
Here are my findings for a basic implementation without using injectManifest.
To generate the required VAPID Keys you can use web-push: npx web-push generate-vapid-keys --json
First, you need to create a push service worker listener. This is a separate file that will be imported into the main service worker file generated by workbox.
// ./public/service-worker/push.js
self.addEventListener('push', onPush);
async function onPush(event) {
if (event.data) {
const data = event.data.json();
const { title, ...rest } = data;
// Send the push data to the application
const clients = await self.clients.matchAll();
clients.forEach((client) => client.postMessage(data));
await event.waitUntil(
self.registration.showNotification(title, {
...rest,
}),
);
}
}
Next, you need to import it in vite.config.ts
:
VitePWA({
...
workbox: {
importScripts: ['/service-worker/push.js'],
...
}
})
Finally, you need to register for notifications in your application code.
// Request permission for notifications
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
console.log('Permission not granted for Notification');
return;
}
const registration = await navigator.serviceWorker.ready;
try {
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
// Replace with your own VAPID public key
applicationServerKey:
'',
});
// Send the subscription to your server
// const response = await fetch('/api/subscribe', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify(subscription),
// });
console.log('User is subscribed:', subscription);
} catch (err) {
console.log('Failed to subscribe the user: ', err);
}
// Listen for messages from the service worker
navigator.serviceWorker.addEventListener('message', (event) => {
console.log('Received a message from service worker:', event.data);
});
Example for sending from a server:
const webpush = require('web-push')
webpush.setVapidDetails(
'mailto:test@example.com',
process.env.VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY
)
// pushSubscription is the subscription object you got from the client earlier
webpush.sendNotification(pushSubscription,
JSON.stringify({
title: 'Test notification',
body: 'This is a test notification',
icon: 'https://example.com/icon.png'
})
).catch(error => {
console.error('Error sending push notification:', error);
});
@SavageCore Dude thank you so much! it worked for me, i was about to cry one week stuck in this.
@SavageCore Is there an option to setup firebase push notificacions without using injectManifest? Thanks!