Search code examples
javascriptreactjsfirebasefirebase-cloud-messagingmobx

How I can change a mobx state upon a receival of a background message from firebase?


In my project I have setup the firebase like this:

import { initializeApp } from 'firebase/app';
import {
    getMessaging,
    getToken,
    onMessage,
    isSupported
} from 'firebase/messaging';

import store from './flag';
import { setToken } from './flag';

export const VAPID_KEY = process.env.REACT_APP_FIREBASE_VAPID_KEY

export const firebaseConfig = {
    apiKey:  process.env.REACT_APP_FIREBASE_apiKey,
    authDomain: process.env.REACT_APP_FIREBASE_authDomain,
    databaseURL: process.env.REACT_APP_FIREBASE_databaseUrl,
    projectId: process.env.REACT_APP_FIREBASE_projectId,
    storageBucket: process.env.REACT_APP_FIREBASE_storageBucket,
    messagingSenderId: process.env.REACT_APP_FIREBASE_messagingSenderId,
    appId: process.env.REACT_APP_FIREBASE_appId,
    measurementId:  process.env.REACT_APP_FIREBASE_measurementId
};


//Initialize Firebase Messaging
const InitializeMessaging = () => {
    const app = initializeApp(firebaseConfig);
    isSupported().then(is_supported => {
        if (is_supported) {
            requestForToken().then(fcm_token => {
               store.dispatch(setToken(fcm_token));
            });
        }
    });
};

const requestForToken = () => {
    const messaging = getMessaging();
    return getToken(messaging, {
        vapidKey: VAPID_KEY
    })
    .then(currentToken => {
            if (currentToken) {
                return currentToken;
            } else {
                console.log(
                    'No registration token available. Request permission to generate one.'
                );
            }
        })
        .catch(err => {
            console.error('An error occurred while retrieving token. ', err);
        });
};

InitializeMessaging();

export const onMessageListener = () =>
    
    new Promise(resolve => {
        onMessage(getMessaging(), payload => {
            resolve(payload);
        });
});

And I have a store that upon message receival I change the mobx state (flag.js):

import { configureStore } from '@reduxjs/toolkit'
import { onMessageListener } from './firebase';


const initialState = { flag: true, token: "" }

function flagReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'flag/change') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      flag:action.payload
    }
  }

  if (action.type === 'flag/token') {
    return {
      ...state,
      // and update the copy with the new value
      token:action.payload
    }
  }
  // otherwise return the existing state unchanged
  return state
}

const store = configureStore({ reducer: flagReducer });

export const setFlag = active => ({
    type: 'flag/change',
    payload: active==='true'
});

export const setToken = token => ({
  type: 'flag/token',
  payload: token
});

onMessageListener().then(payload => {
   
})
.catch(err => console.log('failed: ', err));

navigator.serviceWorker.addEventListener(
  'message',
  function(event) {
    const message = event.data.data;
    if(typeof message !== 'undefined' && typeof message['flag'] !== 'undefined' ) {
        store.dispatch(setFlag(message.flag));
    }
  }
);

export default store;

My state has a single flag that is toggled from firebase message.

Also I have the following worker (firebase-messaging-sw.js):

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


// Initialize the Firebase app in the service worker by passing the generated config
const firebaseConfig = {
  apiKey: `REPLACE_WITH_YOUR_FIREBASE_MESSAGING_API_KEY`,
  authDomain: `REPLACE_WITH_YOUR_FIREBASE_MESSAGING_AUTH_DOMAIN`,
  projectId: `REPLACE_WITH_YOUR_FIREBASE_MESSAGING_PROJECT_ID`,
  storageBucket: `REPLACE_WITH_YOUR_FIREBASE_MESSAGING_STORAGE_BUCKET`,
  messagingSenderId: `REPLACE_WITH_YOUR_FIREBASE_MESSAGING_SENDER_ID`,
  appId: `REPLACE_WITH_YOUR_FIREBASE_MESSAGING_APP_ID`,
  measurementId: `REPLACE_WITH_YOUR_FIREBASE_MESSAGING_MEASUREMENT_ID`,
};


firebase.initializeApp(firebaseConfig);

// Retrieve firebase messaging
const messaging = firebase.messaging();

// Handle incoming messages while the app is not in focus (i.e in the background, hidden behind other tabs, or completely closed).
messaging.onBackgroundMessage(function(payload) {
  console.log('Received background message', payload);

  // How I can send the received message upon mobx
 
});

What I want to achieve is when I receive a background message I want also the flag at my state to be modified as well. Is there a way?

What I want is to propagate the message from firebase-messaging-sw.js into flag.js so I can update the redux state as well.

My application uses the state for showing a logo based upon the state flag:

import { connect } from 'react-redux';
import logo from './logo.svg';

function Logo({shown,token}){
    return (
      <img src={logo} className="App-logo" style={{display:!shown?"none":""}} alt="logo" />
    );
}

const mapStateToProps = state => {
    console.log(state);
    return {
      shown: state.flag,
    };
};

export default connect(
    mapStateToProps,
)(Logo);

Solution

  • This is achievable by placing at firebase-messaging-sw.js the following:

    messaging.onBackgroundMessage(function(payload) {
      console.log('Received background message', payload);
    
      // Propagate message upon UI
      if(typeof payload.data !== 'undefined' && typeof payload.data['flag'] !== 'undefined' ) {
        self.clients.matchAll({includeUncontrolled: true}).then(function (clients) {
          //you can see your main window client in this list.
          clients.forEach(function(client) {
              client.postMessage({...payload});
          });
        })
      }
    });
    
    

    And at the flag.js file by placing:

    navigator.serviceWorker.addEventListener(
      'message',
      function(event) {
        const message = event.data.data;
        if(typeof message !== 'undefined' && typeof message['flag'] !== 'undefined' ) {
            store.dispatch(setFlag(message.flag));
        }
      }
    );
    
    

    Code explanation

    What is done at the code above is with:

    self.clients.matchAll({includeUncontrolled: true}).then(function (clients) {
          //you can see your main window client in this list.
          clients.forEach(function(client) {
              client.postMessage({...payload,'type':'firebaseBG'});
          });
        })
    

    We propagate the message back to frontent via a special event that is handled via:

    navigator.serviceWorker.addEventListener(
      'message',
      function(event) {
         // Event handled here
      }
    );
    

    The message payload is available at event via:

        event.data.data;