quickstart-js: setBackgroundMessageHandler never fired

The background handler is not working: no matter what I do, the sw always use the regular onMessage, displaying the actual notification I sent instead of the custom one set on the handler.

My code is the exact same as the one in the repo:

messaging.setBackgroundMessageHandler(function(payload) {
    console.log('[firebase-messaging-sw.js] Received background message ', payload);
    // Customize notification here
    const notificationTitle = 'Background Message Title';
    const notificationOptions = {
        body: 'Background Message body.',
        icon: '/Content/Img/logo-amezze-120.png'
    };

    return self.registration.showNotification(notificationTitle,
        notificationOptions);
});

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 6
  • Comments: 28

Most upvoted comments

Turns out the problem was in my json. There’s a footnote on the documentation that says if your json defines notification property, then the setBackgroundMessageHandler is sort of ignored.

"Note: If you set notification fields in your HTTP or XMPP send request, those values take precedence over any values specified in the service worker." https://firebase.google.com/docs/cloud-messaging/js/receive

So, this won’t trigger the handler:

{
    "to": "abc",
        "notification": {
        "title": "Hello",
        "body": "world"
    }
}

but this will:

{
    "to":  "abc",

    "data": {
        "notification": {
        "title": "Hello",
        "body": "world"
    }
    }
}

This way you can send the notification in the data.

Okay, this is what we call ‘GAMBIARRA’ in Brazil, (or, workaround for fancy people). It works in Chrome, and Firefox, didn’t tested in other browsers…

Make sure to call this BEFORE setBackgroundMessageHandler. It will basically bypass firebase code at this line (https://github.com/firebase/firebase-js-sdk/blob/master/packages/messaging/src/controllers/sw-controller.ts#L212)

Then, you can access the ‘notification’ data as ‘_notification’ inside the handler

/*
 * We need a generic class to hold data and methods, that inherit from Event
 */
class CustomPushEvent extends Event {
  constructor(data) {
    super('push')
    
    Object.assign(this, data)
    this.custom = true
  }
}

/*
 * Overrides push notification data, to avoid having 'notification' key and firebase blocking
 * the message handler from being called
 */
self.addEventListener('push', (e) => {
  // Skip if event is our own custom event
  if (e.custom) return;

  // Kep old event data to override
  let oldData = e.data

  // Create a new event to dispatch
  let newEvent = new CustomPushEvent({
    data: {
      json() {
        let newData = oldData.json()
        newData._notification = newData.notification
        delete newData.notification
        return newData
      },
    },

    waitUntil: e.waitUntil.bind(e),
  })

  // Stop event propagation
  e.stopImmediatePropagation()

  // Dispatch the new wrapped event
  dispatchEvent(newEvent)
})

I used ivanseidel’s code, and I was facing the issue of duplicating notifications. I did some tinkering with the code and now it works as expected. Hope this helps everyone. A life-saving GAMBIARRA!!

Wonderful, I had the audacity to modify a little and create a repository to collaborate with the evolution of this gambiarra!

https://github.com/BrunoS3D/firebase-messaging-sw.js

https://github.com/BrunoS3D/firebase-messaging-sw.js/blob/main/firebase-messaging-sw.js

I used ivanseidel’s code, and I was facing the issue of duplicating notifications. I did some tinkering with the code and now it works as expected. Hope this helps everyone. A life-saving GAMBIARRA!!


firebase.initializeApp({
...
});

class CustomPushEvent extends Event {
    constructor(data) {
        super('push')

        Object.assign(this, data)
        this.custom = true
    }
}

/*
 * Overrides push notification data, to avoid having 'notification' key and firebase blocking
 * the message handler from being called
 */
self.addEventListener('push', (e) => {
    // Skip if event is our own custom event
    if (e.custom) return;

    // Kep old event data to override
    let oldData = e.data

    // Create a new event to dispatch, pull values from notification key and put it in data key, 
    // and then remove notification key
    let newEvent = new CustomPushEvent({
        data: {
            ehheh: oldData.json(),
            json() {
                let newData = oldData.json()
                newData.data = {
                    ...newData.data,
                    ...newData.notification
                }
                delete newData.notification
                return newData
            },
        },
        waitUntil: e.waitUntil.bind(e),
    })

    // Stop event propagation
    e.stopImmediatePropagation()
    
    // Dispatch the new wrapped event
    dispatchEvent(newEvent)
})
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function (payload) {
    const notificationTitle = payload.data.title;
    const notificationOptions = {
        body: payload.data.body,
        icon: payload.data.icon
    };

    return self.registration.showNotification(notificationTitle,
        notificationOptions);
});

self.addEventListener('notificationclick', function (event) {
    self.clients.openWindow('https://github.com/Suketu-Patel')
    //close notification after click
    event.notification.close()
})

I am wondering why the included notification should take precedence over the service worker… It is not some kind of hard limitation, since the firebase-messaging.js code clearly handles the notification part (it is even easy to understand from the minified code).

The problem is that this “feature” ruins my efforts to use the same messages for different platforms (eg. Web and Android), since I do not want to populate the “click_action” field in the sent message (as it would break the click handling in Android), but I still want to send the user to a page when the notification is clicked on the web.

It would be easy to achieve this if I could eg. include the desired target url in a special field of the data payload and use the service worker to inject it as a click_action in the generated notification…

@gauntface Is there any progress on resolving this issue? Specifically, on resolving the fact that setBackgroundMessageHandler is not called when the notification object is specified and notificationclick events are bypassed when the notification object click_action is specified?

I converted @ivanseidel solution (btw thank you) to old good javascript, and I’m posting here to help someone who needs it:

/*
* Overrides push notification data, to avoid having 'notification' key and firebase blocking
* the message handler from being called
*/
self.addEventListener('push', function (e) {
  // Skip if event is our own custom event
  if (e.custom) return;

  // Create a new event to dispatch
  var newEvent = new Event('push');
  newEvent.waitUntil = e.waitUntil.bind(e);
  newEvent.data = {
     json: function() {
         var newData = e.data.json();
         newData._notification = newData.notification;
         delete newData.notification;
         return newData;
     },
  };     
  newEvent.custom = true;          

  // Stop event propagation
  e.stopImmediatePropagation();

  // Dispatch the new wrapped event
  dispatchEvent(newEvent);
});

@nemphys I have been having the exact same problem. It seems like a catch 22. You must include the “notification” object for IOS to display a notification when the app is not in the foreground. If you include the “notification” object then you cannot customize the “click_action” in a Web app. If you specify a “click_action” as a URL then Android will not open your app when it is clicked (IOS opens your app fine surprisingly). If you are not worried about IOS then you could implement the FirebaseMessagingService in your Android app and remove the “notification” object. You could then use custom data keys and control the code to show the notification yourself in both the Web app and the Android app. Doing this would cause IOS not to show any notifications when your app is not in the foreground though.

I completely agree with you, they should allow you to override the notification code (to set a custom “click_action”) even when a "notification"object is specified. Or they should allow for an “android_click_action” which overrides the “click_action” for Android apps.

This should be better documented. It’s not intuitive and definitely a gotcha.

I had the same problem, but not due to the “notification” field. The problem was the “content_available” field.

This was NOT working (despite the lack of “notification”):

{
   "content_available": true,
   "data": {
      "aaa": "bla bla"
      "bbb": "bli bli"
   }
}

This is working:

{
   "content_available": false,
   "data": {
      "aaa": "bla bla"
      "bbb": "bli bli"
   }
}

Sounds like a bug, since according to the FCM doc this “content_available” flag is only for iOS… https://firebase.google.com/docs/cloud-messaging/concept-options

@marcosbrigante your solution works but causes the notification to display 4 times (original, new, original, new) any idea why that is?

How can you cutomize the click_action? My messages are sent without the notification property. I tried to add the click_action property to the notificationOptions in my BackgroundMessageHandler like this:

...
const notificationOptions = {
    body: 'Background Message body.',
    icon: '/Content/Img/logo-amezze-120.png'
    click_action: 'http://localhost:30321/'
};
...

But nothing happens when I click on the notification. I read somewhere, that this is not supported. So created an Event Listener, that reacts to a click on the push notification, something like this:

self.addEventListener('notificationclick', function (event) {
    console.log('On notification click: ', event.notification.tag);
    event.notification.close();

    // This looks to see if the current is already open and
    // focuses if it is
    event.waitUntil(clients.matchAll({
        type: "window"
    }).then(function (clientList) {
        for (var i = 0; i < clientList.length; i++) {
            var client = clientList[i];
            if (client.url == '/' && 'focus' in client)
                return client.focus();
        }
        if (clients.openWindow)
            return clients.openWindow('http://localhost:30321/');
    }));
});

(I got this from here: https://developer.mozilla.org/en-US/docs/Web/API/WindowClient)

By trying this, the clientList is empty. So by clicking on the notification it opens a new window even if the tab, where my application is running, is still open. Anyone have an idea how to fix this?

@TitanArmy Right now, I’m not using this callback in my code it’s there so that I don’t get any error. There is another package @notifee/react-native that is used for local notifications and other stuff, I’m handling background events/messages using that.

Currently the implementation is something like this:

image image

@Birowsky, This is what I was told when I asked the Firebase team for a solution for the meantime and how I would know if they had changed anything in the future.

_

You can make the web application subscribe to a different topic from the mobile apps instead of subscribing them all in the same topic. By doing this, you can separate the web app from the mobile apps since there is no way to filter the notification messages to just the web apps for now.

We value your feedback and we will keep it in consideration moving forward. Also, you can join our Firebase Google Group mailing list to get notified on any Firebase releases, announcements and community discussions or you can keep an eye out on our release notes for any further updates.

_