Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Receiving one notification for each tab in browser in foreground with FCM

I'm using FCM API to receive push notifications from browser. The firebase-messaging-sw.js works as expected and messaging.setBackgroundMessageHandler fires only once when the web app is in background. However, when the app is in foreground, I'm receiving one notification for each browser tab (if I have the app opened in 3 tabs, I receive 3 notifications). I wonder how should I handle this, since I can't find any reference to this issue. This is the code for FCM messages in the foreground:

import NotificationActionCreators from '../actions/NotificationActionCreators';
import NotificationService from './NotificationService';
import LocalStorageService from './LocalStorageService';
import { FIREBASE_SCRIPT, FCM_URL, FCM_API_KEY, FCM_AUTH_DOMAIN, FCM_PROJECT_ID, FCM_SENDER_ID, PUSH_PUBLIC_KEY } from '../constants/Constants';


class ServiceWorkerService {

constructor() {
    this._messaging = null;
    this._subscriptionData = null;
}

// This function is called once
init() {
    this.loadScript(FIREBASE_SCRIPT, () => this.onFirebaseLoaded());
}

onFirebaseLoaded() {
    // Initialize Firebase
    let config = {
        apiKey: FCM_API_KEY,
        authDomain: FCM_AUTH_DOMAIN,
        projectId: FCM_PROJECT_ID,
        messagingSenderId: FCM_SENDER_ID
    };
    firebase.initializeApp(config);
    this._messaging = firebase.messaging();

    this.requestPermission();

    // Callback fired if Instance ID token is updated.
    this._messaging.onTokenRefresh(() => {
        this._messaging.getToken()
            .then((refreshedToken) => {
                console.log('Token refreshed.');
                NotificationActionCreators.unSubscribe(this._subscriptionData).then(() => {
                    // Indicate that the new Instance ID token has not yet been sent to the
                    // app server.
                    this.setTokenSentToServer(false);
                    // Send Instance ID token to app server.
                    this.sendTokenToServer(refreshedToken);
                }, () => console.log('Error unsubscribing user'));
            })
            .catch(function(err) {
                console.log('Unable to retrieve refreshed token ', err);
            });
    });

    // Handle incoming messages.
    // *** THIS IS FIRED ONCE PER TAB ***
    this._messaging.onMessage(function(payload) {
        console.log("Message received. ", payload);
        const data = payload.data;

        NotificationActionCreators.notify(data);
    });
}

requestPermission() {
    console.log('Requesting permission...');
    return this._messaging.requestPermission()
        .then(() => {
            console.log('Notification permission granted.');
            this.getToken();
        })
        .catch(function(err) {
            console.log('Unable to get permission to notify.', err);
        });
}

getToken() {
    // Get Instance ID token. Initially this makes a network call, once retrieved
    // subsequent calls to getToken will return from cache.
    return this._messaging.getToken()
        .then((currentToken) => {
            if (currentToken) {
                this.sendTokenToServer(currentToken);
            } else {
                // Show permission request.
                console.log('No Instance ID token available. Request permission to generate one.');
                this.setTokenSentToServer(false);
            }
        })
        .catch(function(err) {
            console.log('An error occurred while retrieving token. ', err);
            this.setTokenSentToServer(false);
        });
}

sendTokenToServer(currentToken) {
    const subscriptionData = {
        endpoint: FCM_URL + currentToken,
        platform: 'Web'
    };
    if (!this.isTokenSentToServer()) {
        console.log('Sending token to server...');
        this.updateSubscriptionOnServer(subscriptionData);
    } else {
        console.log('Token already sent to server so won\'t send it again ' +
            'unless it changes');
    }
    this._subscriptionData = subscriptionData;
}

isTokenSentToServer() {
    return LocalStorageService.get('sentToServer') == 1;
}

setTokenSentToServer(sent) {
    LocalStorageService.set('sentToServer', sent ? 1 : 0);
}

updateSubscriptionOnServer(subscriptionData) {
    if (subscriptionData) {
        NotificationActionCreators.subscribe(subscriptionData);
        this.setTokenSentToServer(true);
        this._subscriptionData = subscriptionData;
    } else {
        console.log('Not subscribed');
    }
}

unSubscribe() {
    this.removeSetTokenSentToServer();
    return this._messaging.getToken()
        .then((currentToken) => {
            return this._messaging.deleteToken(currentToken)
                .then(() => {
                    console.log('Token deleted.');
                    return NotificationActionCreators.unSubscribe(this._subscriptionData);
                })
                .catch(function(err) {
                    console.log('Unable to delete token. ', err);
                    return new Promise(function(resolve, reject) {
                        reject(error)
                    });
                });
       })
        .catch(function(err) {
            console.log('Error retrieving Instance ID token. ', err);
            return new Promise(function(resolve, reject) {
                reject(error)
            });
        });
    }
}

removeSetTokenSentToServer() {
    LocalStorageService.remove('sentToServer');
}

loadScript = function (url, callback) {
    let head = document.getElementsByTagName('head')[0];
    let script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    script.onload = callback;

    // Fire the loading
    head.appendChild(script);
}
}

Is there any way to show the notification just for the first tab found?

like image 271
Manolo Avatar asked Jun 02 '17 09:06

Manolo


1 Answers

The only way I've found to achieve this is to check and set a "notification id" variable in the local storage with a setTimeout with a random time:

this._messaging.onMessage(function(payload) {
    const data = payload.data;
    // This prevents to show one notification for each tab
    setTimeout(() => {
        if (localStorage.getItem('lastNotificationId')) != parseInt(data.notId)) {
            localStorage.setItem('lastNotificationId', parseInt(data.notId))
            NotificationActionCreators.notify(data);
        }
    }, Math.random() * 1000);
});

The notId is sent within the push notification and is an identifier for the notification.

like image 161
Manolo Avatar answered Oct 10 '22 13:10

Manolo