web-push-libs / web-push

Web Push library for Node.js
Other
3.26k stars 305 forks source link

Unsubscribed due to error DELIVERY_PERMISSION_DENIED #885

Open mhabsaoui opened 7 months ago

mhabsaoui commented 7 months ago

NOTE: Please test in a least two browsers (i.e. Chrome and Firefox). This helps with diagnosing problems quicker.

Setup

Operating System: Linux (Ubuntu 22.04 LTS) Node Version: 21.5.0 web-push Version: ^3.6.6

Chrome : Version 121.0.6167.160 (Official Build) (64-bit) Edge: Version 121.0.2277.106 (Official build) (64-bit)

Problem

We have a published a Chrome web addon (on Chrome Store) and we wanted to send Push notifications to users having installed on their browser.

So, to add this new feature:

In a normal scenario, all works like a charm and the notifications are gracefully poping on desktop.

Now, when I decide to deactivate my web addon (i.e. from Extensions manage page in Dev Mode), and my ServiceWorker remains registered, and I ignit a notification Push from server, I encounter errors on both sides :

WebPushError: Received unexpected response code
    at IncomingMessage.<anonymous> (/app/node_modules/web-push/src/web-push-lib.js:378:20)
    at IncomingMessage.emit (node:events:530:35)
    at endReadableNT (node:internal/streams/readable:1696:12)
    at processTicksAndRejections (node:internal/process/task_queues:82:21) {
  statusCode: 410,
  headers: {
    'content-type': 'text/plain; charset=utf-8',
    'x-content-type-options': 'nosniff',
    'x-frame-options': 'SAMEORIGIN',
    'x-xss-protection': '0',
    date: 'Thu, 08 Feb 2024 16:37:48 GMT',
    'content-length': '47',
    'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000'
  },
  body: 'push subscription has unsubscribed or expired.\n',
  endpoint: 'https://fcm.googleapis.com/fcm/send/f7MxvCg_.....

Screenshot from 2024-02-09 16-42-32

So, from the push server point of view I understand the problem because the subscription has been unsubscribed by the PushManager associated with the ServiceWorker.

But I don't understand why this unsubscription happens ?? Is it because the ServiceWorker can't dispatch the push message to the deactivated addon, without a subscription's endpoint, and this causes the unsubscription due to this error ??

Expected

To have the push dispatched and have the notification, as usual when the web addon is activated.

Features Used

Example / Reproduce Case

// Event listener for push event.
self.addEventListener("push", (event) => {
  console.debug("Push event", event);
  const { title = "Test title", body = "Test message." } = event.data?.json();
  const icon = "icons/icon_1024x1024.png";
  event.waitUntil(
    self?.registration?.showNotification(title, {
      body,
      icon,
    }) // Show a notification with title and message
  ); // Keep the service worker alive until the notification is created
});

self.addEventListener("pushsubscriptionchange", (event) => {
  console.debug(event);
});
export const registerServiceWorker = async () => {
  try {
    await navigator?.serviceWorker.register("./serviceWorker.js"); // Register a Service Worker
  } catch (error) {
    console.error(`Service worker registration failed: ${error}`);
  }
};

export const subscribeToNotification = async () => {
  await navigator?.serviceWorker?.ready
    .then(async (registration) => {
      const subscription = await registration?.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
      });
      if (subscription) {
        const subscriptionJson = subscription.toJSON();
        currentBrowser.runtime.setUninstallURL(
          `${SERVER_URL}/subscription/remove/${subscriptionJson.keys.auth}`,
          () =>
            console.debug(
              "UninstallURL",
              `${SERVER_URL}/subscription/remove/${subscriptionJson.keys.auth}`
            )
        ); // Set uninstall URL to remove notification subscription on addon uninstall 
        await fetchApi({
          url: `${SERVER_URL}/subscription`,
          reqInit: {
            body: JSON.stringify(subscription.toJSON()),
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
          },
        });
      }
      registration.active.onstatechange = (event) =>
        console.info(`SW state change: ${event}`); // ServiceWorker state change event
      registration.active.onerror = (event) =>
        console.error(`SW error: ${event}`); // ServiceWorker error event
    })
    .catch((error) => console.error(error));
};
notificationRouter
 .get('/notification/push', async (req, res) => {
    try {
      const notifications =
        (await (await db())
          ?.collection(`${NOTIFICATION_DB_COLL_NAME}`)
          ?.find({})
          .toArray()) ?? [];
      const subscriptions = await getSubscriptions();
      if (notifications?.length > 0 && subscriptions?.length > 0) {
        subscriptions?.map(async ({ endpoint, keys }) => {
          console.debug({
            endpoint,
            keys,
          });
          await webPush
            .sendNotification(
              { endpoint, keys },
              JSON.stringify(notifications.pop()),
              pushOptions
            )
            .then((response) => {
              console.debug(response);
              res.json({
                message: `Notification Push done for ${subscriptions?.length} subscriptions`,
              });
            })
            .catch((error) => {
              console.error(error);
              if (error?.statusCode === 410) deleteSubscription(keys?.auth);
            });
        });
      } else
        res.json({
          message: `Notification Push not done because no notification exists`,
        });
    } catch (error) {
      console.error(error);
      res.json({ message: `Notification push error`, error });
    }
  });

Other

I have tried to debug using either ServiceWorker registration onerror or pushsubscriptionchange events, but in vain...