Ionic 3's PWA & Firebase Cloud Messaging registration

风格不统一 提交于 2019-12-03 15:50:24

UPDATE: the below is valid as of today (02/12/2018) and most likely will be less relevant once AngularFire2 supports messaging module. So take the below with that assumption...

OK I researched and finally made it work on my Ionic 3 PWA, so I am posting solution here:

  1. Prerequisites:
    • I created ionic blank app (just a home page)
    • installed angularfire2 and firebase ("angularfire2": "5.0.0-rc.4","firebase": "4.9.1") using npm install, I used specifically 5.0.0-rc.4" cause I had stability issues with latest one;(
    • created config (filename environment.ts in src folder):

export const firebaseConfig = {
    apiKey: "Your Stuff Here from FB",
    authDomain: "YOURAPPNAME.firebaseapp.com",
    databaseURL: "https://YOURAPPNAME.firebaseio.com",
    projectId: "YOURAPPNAME",
    storageBucket: "YOURAPPNAME.appspot.com",
    messagingSenderId: "FROMFIREBASECONEOLE"
};
  1. I modified app.module.ts to add firebase and angularfire2 this way:

...
import { AngularFireModule } from 'angularfire2';
import 'firebase/messaging'; // only import firebase messaging or as needed;
import { firebaseConfig } from '../environment';
import { FirebaseMessagingProvider } from '../providers/firebase-messaging';
...

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    AngularFireModule.initializeApp(firebaseConfig),
    IonicStorageModule.forRoot()
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    FirebaseMessagingProvider,
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Here we also import our provider whose code is below:

  1. in providers folder I created firebase-messaging.ts like this:

import { Injectable } from "@angular/core";
import { FirebaseApp } from 'angularfire2';
// I am importing simple ionic storage (local one), in prod this should be remote storage of some sort.
import { Storage } from '@ionic/storage';

@Injectable()
export class FirebaseMessagingProvider {
  private messaging;
  private unsubscribeOnTokenRefresh = () => {};

  constructor(
    private storage: Storage,
    private app: FirebaseApp
  ) {
    this.messaging = app.messaging();
    navigator.serviceWorker.register('service-worker.js').then((registration) => {
    this.messaging.useServiceWorker(registration);
    //this.disableNotifications()
    this.enableNotifications();
});
  }

  public enableNotifications() {
    console.log('Requesting permission...');
    return this.messaging.requestPermission().then(() => {
        console.log('Permission granted');
        // token might change - we need to listen for changes to it and update it
        this.setupOnTokenRefresh();
        return this.updateToken();
      });
  }

  public disableNotifications() {
    this.unsubscribeOnTokenRefresh();
    this.unsubscribeOnTokenRefresh = () => {};
    return this.storage.set('fcmToken','').then();
  }

  private updateToken() {
    return this.messaging.getToken().then((currentToken) => {
      if (currentToken) {
        // we've got the token from Firebase, now let's store it in the database
        console.log(currentToken)
        return this.storage.set('fcmToken', currentToken);
      } else {
        console.log('No Instance ID token available. Request permission to generate one.');
      }
    });
  }

  private setupOnTokenRefresh(): void {
    this.unsubscribeOnTokenRefresh = this.messaging.onTokenRefresh(() => {
      console.log("Token refreshed");
      this.storage.set('fcmToken','').then(() => { this.updateToken(); });
    });
  }
    
}

Please note I init the firebase app and then in constructor we register ionic's default service worker (service-worker.js) that contains the following right after whatever is there by default:

  1. service-worker.js:

// firebase messaging part:
importScripts('https://www.gstatic.com/firebasejs/4.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.9.0/firebase-messaging.js');

firebase.initializeApp({
  // get this from Firebase console, Cloud messaging section
  'messagingSenderId': 'YOURIDFROMYOURFIREBASECONSOLE' 
});

const messaging = firebase.messaging();

messaging.setBackgroundMessageHandler(function(payload) {
  console.log('Received background message ', payload);
  // here you can override some options describing what's in the message; 
  // however, the actual content will come from the Webtask
  const notificationOptions = {
    icon: '/assets/images/logo-128.png'
  };
  return self.registration.showNotification(notificationTitle, notificationOptions);
});

At this point you also need to make sure you enabled your app as PWA, there is a good guide from Josh Morony and today there was a video stream on youtube that covers it. In TLDR you need to uncomment this in your index.html:

  1. index.html in src uncomment:

 <!-- un-comment this code to enable service worker -->
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('service-worker.js')
        .then(() => console.log('service worker installed'))
        .catch(err => console.error('Error', err));
    }
  </script>
  1. OK almost the last thing - your manifest.json (in src) should have exact line: "gcm_sender_id": "103953800507"

This concludes initial stuff on the client. Please note I didn't implement yet anything to handle notifications while user is in app itself, think for now it just handles when a message is sent from a server while your tab is not in focus (that is what I tested).

  1. Now you want to go to your firebase console and obtain server key (click setting gear icon, then see cloud messaging section there). Copy server key. Also run the client (ionic serve and capture your local token (i just console.logged it). Now try sending yourself the message using a POST method. ( I did it with Postman)

// method: "POST",
//url: "https://fcm.googleapis.com/fcm/send",
    // get the key from Firebase console
    headers: { Authorization: `key=${fcmServerKey}` }, 
    json: {
        "notification": { 
            "title": "Message title",
            "body": "Message body",
            "click_action": "URL to your app?"
        },
        // userData is where your client stored the FCM token for the given user
        // it should be read from the database
        "to": userData.fcmRegistrationKey
    }

So by doing all this I was able to reliable send myself a message WHILE the app was in background. I am yet to handle foreground but this SO question is about how to init default service worker and marry it with FCM.

I hope this will help some learners in future.

I have successfully implemented the process and got success response on API calls. But no notification popup coming on my browser. Any idea?

api: https://fcm.googleapis.com/fcm/send

response got:

{"multicast_id":6904414188195222649,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1545375125056264%e609af1cf9fd7ecd"}]}

cheth the attached url of my console:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!