angular
angular copied to clipboard
Angulars Service Worker - "beforePushNotification"
Which @angular/* package(s) are relevant/related to the feature request?
Angular 17.3.x and @angular/pwa
Description
Angulars ngsw-worker.js shows notification at any time a "push" event occurs. However, receiving push notifications when the app is open and in the foreground is not a good user experience. Angular's service worker does not offer a direct way to determine whether a notification should finally be displayed or not. The "onPush" method cannot be overwritten, at most it can be extended - which does not lead to the desired result.
I tried things with a custom service worker like this:
self.addEventListener("push", (event) => {
console.log("Push event detected, but notifications are suppressed.");
event.preventDefault();
event.stopPropagation();
event.waitUntil(
(async function () {
console.log("Received push", event.data.json());
event.data.json().notification.title = null;
return;
})()
);
});
importScripts("./ngsw-worker.js");
but without success (It doesn't matter whether importScripts is at the top or at the bottom)
Proposed solution
A option to set, or a optional function which will return a boolean, like this:
this.swPush.beforePushNotification = () => { return false; }
Alternatives considered
A way to override functions like "onPush" in the Service Worker itself.
In the end we can always add our own service worker. But that was not my goal. I have found a solution - it's not the best way to go, but it works:
self.addEventListener("push", (event) => {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
if (!event.data) {
return;
}
event.waitUntil(
(async (mySelf) => {
const windowClients = await mySelf.clients.matchAll({ type: "window" });
if (!windowClients || !windowClients[0]) return;
const client = windowClients[0];
if (client && client .visibilityState !== "visible") {
await handlePush(event.data.json());
}
})(self)
);
});
async function broadcast(msg) {
const clients = await self.clients.matchAll();
clients.forEach((client) => {
client.postMessage(msg);
});
}
async function handlePush(data) {
var NOTIFICATION_OPTION_NAMES = [
"actions",
"badge",
"body",
"data",
"dir",
"icon",
"image",
"lang",
"renotify",
"requireInteraction",
"silent",
"tag",
"timestamp",
"title",
"vibrate",
];
await broadcast({
type: "PUSH",
data,
});
if (!data.notification || !data.notification.title) {
return;
}
const desc = data.notification;
let options = {};
NOTIFICATION_OPTION_NAMES.filter((name) => desc.hasOwnProperty(name)).forEach(
(name) => (options[name] = desc[name])
);
await self.registration.showNotification(desc["title"], options);
}
importScripts("./ngsw-worker.js");
The app should ALWAYS display push notifications. See: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe https://web.dev/articles/push-notifications-subscribing-a-user#uservisibleonly_options
Browsers currently support only userVisibleOnly: true.
If the subscription is made with userVisibleOnly: true and the app does not display the notifications, then the browser could revoke the subscription.
See: https://meta.discourse.org/t/ios-notifications-can-lose-permission-to-push-if-the-user-is-currently-active/290225 https://webkit.org/blog/12945/meet-web-push/