Cloud Messaging for Notifications
Tasks to do:
- request permission from user to send messages
- create a cloud a function to send them a messaeg
- 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 Promise
s. (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.