Cloud Messaging for Notifications

In notebook:
FrontEndMasters Serverless
Created at:
2017-10-15
Updated:
2017-10-15
Tags:
backend JavaScript

Tasks to do:

  1. request permission from user to send messages
  2. create a cloud a function to send them a messaeg
  3. install a service worker on the client's browser to receive the message
  //	****		./src/firebase.js		****

import firebase from 'firebase';

const config = {
  ..
};

firebase.initializeApp(config);

export default firebase;

export const database = firebase.database();
export const auth = firebase.auth();
export const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
// **** 1.  ↴
export const messaging = firebase.messaging()

Creates a new file to request the permission

  //	****		request-messaging-permission.js		****

import (database, messaging) from './firebase'

export default function (user) {
  messaging.requestPermission() // this is when to popup appears in the browser
  .then(() => messaging.getToken()) // getToken also returns a Promise
  .then((token) => {
    database.ref('users')
      .child(user.uid)
      .child('token') // get the  token poperty of the `user` object (does not exist at this point)
      set(token)
  })
  .catch(console.error)
}

then

  //	****		actions/auth.js		****

import { auth, database, googleAuthProvider } from '../firebase';
import pick from 'lodash/pick';
// **** 1.  ↴
import registerMessaging from '../request-messaging-permission'

export const signIn = () => {
  return (dispatch) => {
    dispatch({ type: 'ATTEMPTING_LOGIN' });
    auth.signInWithPopup(googleAuthProvider);
  };
};

export const signOut = () => {
  return (dispatch) => {
    dispatch({ type: 'ATTEMPTING_LOGIN' });
    auth.signOut();
  };
};

const signedIn = (user) => {
  return {
    type: 'SIGN_IN',
    email: user.email,
    displayName: user.displayName,
    photoURL: user.photoURL,
    uid: user.uid
  };
};

const signedOut = () => {
  return {
    type: 'SIGN_OUT'
  };
};



export const startListeningToAuthChanges = () => {
  return (dispatch) => {
    auth.onAuthStateChanged((user) => {
      if (user) {
        dispatch(signedIn(user));
        database.ref('users')
                .child(user.uid)
                .set(pick(user, ['displayName', 'email', 'uid', 'photoURL']));
        database.ref('admins').child(user.uid)
                              .once('value')
                              .then((snapshot => {
                                if (snapshot.val()) dispatch({ type: 'SET_AS_ADMIN' });
                              }));
        // **** 2.  ↴
        registerMessaging(user)
      } else {
        dispatch(signedOut());
      }
    });
  };
};


Now this should work, the user should see the allow send messages popup and the token should be registered in the Firebase.

##Find everyone who has given us permission

i.e. we have their token in the database

This will be done with the cloud functions

  //	****		functions/index.js		****


const functions = require('firebase-functions'); // listen to database changes
const admin = require('firebase-admin'); // trigger changes like we were in the web console

export.newMessageAlert = functions.database.ref('/messages/{message}')
  .onWrite(event => {  // ☛ whenever someone writes at this place we get notified
    const message = event.data.val() // get the raw JavaScript object of what's stored there (it has content, the timestamp, but not the user id)
    
    // find out who messaged us
    const getTokens = admin
      .database()
      .ref('users') /// get the users
      .once('value') // so only run this once now
      .then(snapshot => {
        // find the users who have given us permissions
        const tokens = []
        // NOTE: snapshots only have a `forEach` but no other methods (filter, map, reduce)
        snapshot.forEach(user => {
          const token = user.child('token').val()
          if (token) tokens.push(token)
        })
        return tokens // so we just filtered the users list...
      })
      
    // need to get the "author" of the message (photo, etc)
    const getAuthor = admin.auth().getUser(message.uid)
    
    Promise.all([getTokens, getAuthor]) // do these in parallel
      then( ([tokens, author]) => { // use destructuring to get the tokens and author
        // put together a payload to send out
        const payload = { // payloads follow a predefined structure
          notification: {
            title: `Hot Take from ${author.displayName}`,
            body: message.content,
            icon: author.photoURL
          }
        }
        
        // send this payload to all the tokens
        admin.messaing()
          .sendToDevice(tokens, payload)
          .catch(console.error)
        
      })
      
  })

/messages/{message} is the uid of the message and we want to let users know when this changes.

Important, both of our functions that we just wrote, return Promises. (need to call them with .then... or Promise.all([]))

Then in

  //	****		src/index.js		****

...

import { messaging } from './firebase'

...

messaging.onMessage(payload =>{
  console.log(payload)
})

Deploy our Cloud Function

$ firebase deploy --only functions

Debugging Cloud Functions

For now, it's very basic, console.log() type logging

##install service worker

Even if we close our tab, the service worker will continue to run.

A service worker has to have a specific name in Firebase and live in the public folder.

  //	****		public/firebase-messaging-sw.js		****

// .. imports some stuff 
importScripts('.. gstatic/firebase/3.7.0/firebase-app.js')
importScripts('.. gstatic/firebase/3.7.0/firebase-messaging.js')

const config = {
  messagingSenderId: '32p32p2322'
}

firebase.initializeApp(config)

const messaging = firebase.messaging()

messaging.setBackgroundMessageHandler( payload => {
  const title = payload.title
  const options = {
    body: payload.body,
    icon: payload.icon
  }
  self.registration.showNotification(title, options)
})

$ npm run build && firebase deploy

Now, in Chrome, you can turn on Service Workers and simulate offline mode with devtools.

Firebase supports offline first.