Always throws registration failed error while subscribing push notifications

Viewed 1028

I am working on solutions using which i can send desktop push notification to subscribed clients.

I have created basic solution in where whenever user click on button i ask user for whether they want to allow notifications for my app or not!

I am getting an error of "Registration failed - permission denied" whenever i click on button for first time.

So that i am not able to get required endpoints to save at backend

Here is my code

index.html

    <html>
       <head>
          <title>PUSH NOT</title>
          <script src="index.js"></script>
       </head>
       <body>
          <button onclick="main()">Ask Permission</button>
       </body>
    </html>

index.js

const check = () => {
  if (!("serviceWorker" in navigator)) {
    throw new Error("No Service Worker support!");
  } else {
      console.log("service worker supported")
  }
  if (!("PushManager" in window)) {
    throw new Error("No Push API Support!");
  } else {
      console.log("PushManager worker supported")
  }

};

const registerServiceWorker = async () => {
  const swRegistration = await navigator.serviceWorker.register("/service.js?"+Math.random());
  return swRegistration;
};

const requestNotificationPermission = async () => {
  const permission = await window.Notification.requestPermission();

  // value of permission can be 'granted', 'default', 'denied'
  // granted: user has accepted the request
  // default: user has dismissed the notification permission popup by clicking on x
  // denied: user has denied the request.
  if (permission !== "granted") {
    throw new Error("Permission not granted for Notification");
  }
};

const main = async () => {
  check();
  const swRegistration = await registerServiceWorker();
  const permission = await requestNotificationPermission();
};
// main(); we will not call main in the beginning.

service.js

// urlB64ToUint8Array is a magic function that will encode the base64 public key
// to Array buffer which is needed by the subscription option
const urlB64ToUint8Array = base64String => {
  const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding)
    .replace(/\-/g, "+")
    .replace(/_/g, "/");
  const rawData = atob(base64);
  const outputArray = new Uint8Array(rawData.length);
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
};

const saveSubscription = async subscription => {
  console.log("Save Sub")
  const SERVER_URL = "http://localhost:4000/save-subscription";
  const response = await fetch(SERVER_URL, {
    method: "post",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(subscription)
  });
  return response.json();
};
self.addEventListener("activate", async () => {
  try {
   const applicationServerKey = urlB64ToUint8Array(
      "BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM"
    );  
    const options = { applicationServerKey, userVisibleOnly: true };
    const subscription = await self.registration.pushManager.subscribe(options);
    console.log(JSON.stringify(subscription))
    const response = await saveSubscription(subscription);
  } catch (err) {
    console.log(err.code)
    console.log(err.message)
    console.log(err.name)
    console.log('Error', err)
  }
});

self.addEventListener("push", function(event) {
  if (event.data) {
    console.log("Push event!! ", event.data.text());
  } else {
    console.log("Push event but no data");
  }
});

Also i have created a bit of backend as well

const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const webpush = require('web-push') 

const app = express();
app.use(cors());
app.use(bodyParser.json());

const port = 4000;

app.get("/", (req, res) => res.send("Hello World!"));

const dummyDb = { subscription: null }; //dummy in memory store

const saveToDatabase = async subscription => {
  // Since this is a demo app, I am going to save this in a dummy in memory store. Do not do this in your apps.
  // Here you should be writing your db logic to save it.
  dummyDb.subscription = subscription;
};

// The new /save-subscription endpoint
app.post("/save-subscription", async (req, res) => {
  const subscription = req.body;
  await saveToDatabase(subscription); //Method to save the subscription to Database
  res.json({ message: "success" });
});

const vapidKeys = {
  publicKey:
    'BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM',
  privateKey: 'mHSKS-uwqAiaiOgt4NMbzYUb7bseXydmKObi4v4bN6U',
}

webpush.setVapidDetails(
  'mailto:janakprajapati90@email.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey
)

const sendNotification = (subscription, dataToSend='') => {
  webpush.sendNotification(subscription, dataToSend)
}

app.get('/send-notification', (req, res) => {
  const subscription = {endpoint:"https://fcm.googleapis.com/fcm/send/dLjyDYvI8yo:APA91bErM4sn_wRIW6xCievhRZeJcIxTiH4r_oa58JG9PHUaHwX7hQlhMqp32xEKUrMFJpBTi14DeOlECrTsYduvHTTnb8lHVUv3DkS1FOT41hMK6zwMvlRvgWU_QDDS_GBYIMRbzjhg",expirationTime:null,keys:{"p256dh":"BE6kUQ4WTx6v8H-wtChgKAxh3hTiZhpfi4DqACBgNRoJHt44XymOWFkQTvRPnS_S9kmcOoDSgOVD4Wo8qDQzsS0",auth:"CfO4rOsisyA6axdxeFgI_g"}} //get subscription from your databse here.
  const message = 'Hello World'
  sendNotification(subscription, message)

  res.json({ message: 'message sent' })
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

Please help me

2 Answers

Try the following code:

index.js

const check = () => {
    if (!("serviceWorker" in navigator)) {
        throw new Error("No Service Worker support!");
    } else {
        console.log("service worker supported")
    }
    if (!("PushManager" in window)) {
        throw new Error("No Push API Support!");
    } else {
        console.log("PushManager worker supported")
    }

};

const saveSubscription = async subscription => {
    console.log("Save Sub")
    const SERVER_URL = "http://localhost:4000/save-subscription";
    const response = await fetch(SERVER_URL, {
        method: "post",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify(subscription)
    });
    return response.json();
};

const urlB64ToUint8Array = base64String => {
    const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
    const base64 = (base64String + padding)
        .replace(/\-/g, "+")
        .replace(/_/g, "/");
    const rawData = atob(base64);
    const outputArray = new Uint8Array(rawData.length);
    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
};

const registerServiceWorker = async () => {
    return navigator.serviceWorker.register("service.js?"+Math.random()).then((swRegistration) => {
        console.log(swRegistration);
        return swRegistration;
    });
};

const requestNotificationPermission = async (swRegistration) => {
    return window.Notification.requestPermission().then(() => {
        const applicationServerKey = urlB64ToUint8Array(
            "BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM"
        );
        const options = { applicationServerKey, userVisibleOnly: true };
        return swRegistration.pushManager.subscribe(options).then((pushSubscription) => {
            console.log(pushSubscription);
            return pushSubscription;
        });
    });
};

const main = async () => {
    check();
    const swRegistration = await registerServiceWorker();
    const subscription = await requestNotificationPermission(swRegistration);
    // saveSubscription(subscription);
};

service.js

self.addEventListener("push", function(event) {
    if (event.data) {
        console.log("Push event!! ", event.data.text());
    } else {
        console.log("Push event but no data");
    }
});

I can think of three reasons that the permission is denied

1) your site is not on https (including localhost that is not on https), the default behaviour from chrome as far as i know is to block notifications on http sites. If that's the case, click on the info icon near the url, then click on site settings, then change notifications to ask

2) if you are on Safari, then safari is using the deprecated interface of the Request permission, that is to say the value is not returned through the promise but through a callback so instead of

Notification.requestPermission().then(res => console.log(res)) 

it is

Notification.requestPermission(res => console.log(res))

3) Your browser settings are blocking the notifications request globally, to ensure that this is not your problem run the following code in the console (on a secured https site)

Notification.requestPermission().then(res => console.log(res))

if you receive the alert box then the problem is something else, if you don't then make sure that the browser is not blocking notifications requests

Related