Angular 7 pwa/ SwPush - push notifications not working

ramya krishna picture ramya krishna · Dec 17, 2018 · Viewed 7.5k times · Source

I'm trying to make push notifications work in Angular 7 using @angular/pwa link and using SwPush. I am not able to get actual push notifications. I am currently working on localhost (by running http-server after doing an ng-build) and my api server is located in cloud. I was able to enable the subscription using swPush.requestSubscription and the subscription is registered successfully on the server. In Chrome, all the api calls get blocked from the service worker itself (failed : from service Worker) while in Firefox, there is no error but the push message does not appear.

I have added the relevant code snippets below. Since there are no specific errors reported, I am not able to proceed further.

Please advise on how to make this work and show notifications.

app.module.ts

import {PushNotificationService} from 'core';
import { ServiceWorkerModule } from '@angular/service-worker';
@NgModule({
declarations: [
    AppComponent,

],
imports: [

    ServiceWorkerModule.register('ngsw-worker.js', { enabled: true })
],
providers: [
    PushNotificationService,
],
exports: [],
bootstrap: [AppComponent]
   })
 export class AppModule {
  }


   app.component.ts
export class AppComponent  {

constructor(private pushNotification :PushNotificationService,
private swPush : SwPush){
this.swPush.messages.subscribe(notification => {
          const notificationData: any = notification;
     const options = {
      body: notificationData.message,
      badgeUrl: notificationData.badgeUrl,
      icon: notificationData.iconUrl
    };
    navigator.serviceWorker.getRegistration().then(reg => {
      console.log('showed notification');
      reg.showNotification(notificationData.title, options).then(res => {
        console.log(res);
      }, err => {
        console.error(err);
      });
    });
  });

}
     isSupported() {
      return this.pushNotification.isSupported;
   }

  isSubscribed() {
  console.log(' ****** profile component' + this.swPush.isEnabled);
  return this.swPush.isEnabled;
}

 enablePushMessages() {
  console.log('Enable called'); 
  this.pushNotification.subscribeToPush();

}

 disablePushMessages(){
  // code for unsubsribe
  }
}

push.notification.service

 export class PushNotificationService {
 public isSupported = true;
 public isSubscribed = false;
 private swRegistration: any = null;
  private userAgent = window.navigator.userAgent;
 constructor(private http: HttpClient, private swPush: SwPush) {
   if ((this.userAgent.indexOf('Edge') > -1) || 
   (this.userAgent.indexOf('MSIE') > -1) || (this.userAgent.indexOf('.Net') 
    > -1)) {
      this.isSupported = false;
    }
}

subscribeToPush() {
// Requesting messaging service to subscribe current client (browser)
  let publickey = 'xchbjhbidcidd'
   this.swPush.requestSubscription({
    serverPublicKey: publickey
   }).then(pushSubscription => {
     console.log('request push subscription ', pushSubscription);
     this.createSubscriptionOnServer(pushSubscription);
      })
  .catch(err => {
    console.error(err);
  });
}

 createSubscriptionOnServer(subscription) {
  let urlName = 'api/user/notificationSubscription';
  let params;
  params = {
  endpoint: subscription.endpoint,
   };
this.http.put<any>(urlName, params, httpOptions).pipe(
  tap((res) => {
    if (res.data) {
      if (res.data.success) {
        alert('Success')
      } else {
        alert('error')
      }
    }
  }));
 }
 }

Answer

cyperpunk picture cyperpunk · Feb 25, 2019

You need to install Angular CLI, PWA for service worker, webpush for generating VAPID keys and http-server for running a mock server. You can do it by running:

npm i -g @angular/cli --save
ng add @angular/pwa --save
npm i webpush --save
npm i http-server -g --save

Now you need to generate VAPID key pair using webpush for using it in front end and backend

web-push generate-vapid-keys --json

Save the generated pair somewhere. Use the below code in the app.component.ts to request a subscription from the user

import { Component } from '@angular/core';
import { SwPush } from '@angular/service-worker';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(swPush: SwPush) {
if (swPush.isEnabled) {
  swPush.requestSubscription({
      serverPublicKey: VAPID_PUBLIC_KEY
    })
    .then(subscription => {
      // send subscription to the server
    })
    .catch(console.error);
}
  }
}

VAPID_PUBLIC_KEY is the public key you got earlier.

Add this on your Angular project inside the node_modules/@angular/service-worker/ngsw-worker.js

this.scope.addEventListener('notificationclick', (event) => {
            console.log('[Service Worker] Notification click Received. event:%s', event);
            event.notification.close();
            if (clients.openWindow && event.notification.data.url) {
                event.waitUntil(clients.openWindow(event.notification.data.url));
            }
        });

You can enter the above code where you find the below line inside the file> it would be in line number 1893.

this.scope.addEventListener('notificationclick', (event) => ..

And you have to build the dist again for this to work. Now use

ng build --prod

to generate the dist and serve it using

http-server ./dist/YOUR_DIST_FOLDER_NAME -p 9999

And in backend file your could should be something like this.

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

const PUBLIC_VAPID = 'PUBLIC_VAPID_KEY';
const PRIVATE_VAPID = 'PRIVATE_VAPID_KEY';

const fakeDatabase = [];

const app = express();

app.use(cors());
app.use(bodyParser.json());

webpush.setVapidDetails('mailto:[email protected]', PUBLIC_VAPID, PRIVATE_VAPID);

app.post('/subscription', (req, res) => {
       const subscription = req.body;
      fakeDatabase.push(subscription);
    });

app.post('/sendNotification', (req, res) => {
  const notificationPayload = {
    {"notification":
       { 
        "body":"This is a message.",
        "title":"PUSH MESSAGE",
        "vibrate":300,100,400,100,400,100,400],
        "icon":"ICON_URL",
        "tag":"push demo",
        "requireInteraction":true,
        "renotify":true,
        "data":
          { "url":"https://google.com"}
       }
    }
  };

  const promises = [];
  fakeDatabase.forEach(subscription => {
    promises.push(webpush.sendNotification(subscription, 
JSON.stringify(notificationPayload)));
  });
  Promise.all(promises).then(() => res.sendStatus(200));
});

app.listen(3000, () => {
  console.log('Server started on port 3000');
});

Inside the url you can enter your url and on clicking notification your push notification will open the given link and focus it in the browser.