B4J Tutorial [BANano] Beginning Firebase Messaging

Ola

For the past 3 days, I've been trying to make this work. Eish. This is the implementation so far. We are not there yet.

1. You need a service worker file. We will create this after BANano.Build.
2. You need a firebase cloud messaging project linked to a web project.

Reproduction:

1. Goto Firebase.
2. Goto firebase console.
3. Add Project > Project Name > Create Project

On Project Overview
Goto Project Settings > General > Your Apps > Select Web Platform

Ensure you get your config settings, you will use these later.


1609237376737.png


On project settings, goto Cloud Messaging, then Web Push Notifications and generate the key/pair, this is something called a vapidKey, you will use this later.

Update: Just found this for more clarity...

 
Last edited:

Mashiane

Expert
Licensed User
Longtime User
2. Lets build the firebase service worker file. This needs to be done after BANAno.Build.

NB: The service worker should reside on the root folder of your web server

Use the credentials you got from your fb config file.

B4X:
BANano.Build(Publish)
    'USE THIS CODE AFTER BANANO BUILD
    'on chrome DISABLE: chrome://flags/#enable-native-notifications
    'add firebase messaging service worker
    'at root of site.
    File.Copy(File.DirAssets, "firebase-logo.png", Publish, "firebase-logo.png")
    'create the service worker file
    Dim fbm As String = File.Combine(Publish, "firebase-messaging-sw.js")
  
File.WriteString("", fbm, $"//self.addEventListener("notificationclose", console.log);
//self.addEventListener("notificationclick", console.log);

importScripts("https://www.gstatic.com/firebasejs/8.2.1/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.1/firebase-messaging.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.1/firebase-analytics.js");

var firebaseConfig = {
    apiKey: "(redacted)",
    authDomain: "(redacted)",
    projectId: "(redacted)",
    storageBucket: "(redacted)",
    messagingSenderId: "(redacted)",
    appId: "(redacted)",
    measurementId: "(redacted)",
};

firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

messaging.onBackgroundMessage(function(payload) {
  console.log('service worker', payload);
  const notification = payload.notification;
  const notificationTitle = notification.title;
  const notificationOptions = {
    body: notification.body,
    icon: '/firebase-logo.png',
    tag: 'fb'
    requireInteraction: true,
  };
  return self.registration.showNotification(notificationTitle, notificationOptions);
});"$)
 

Attachments

  • firebase-logo.png
    firebase-logo.png
    223.9 KB · Views: 190
Last edited:

Mashiane

Expert
Licensed User
Longtime User
3. Sending Test Messages

On the navBar... you will select Cloud Messaging


1609237567649.png



You will be provided with... enter title and notification text and click send test message. You will need a token / topic etc.

1609238845582.png


Click Send test message and enter the provided token. A notification will appear on your page. These run in the back-ground using a service worker. I've noted these fire twice for now.

NB: Ensure that chrome is set not to display native notifications. e.g. on chrome browser

B4X:
chrome://flags/#enable-native-notifications


To be continued...
 

Mashiane

Expert
Licensed User
Longtime User
that this breaks all other build-in PWA functionalities in BANano
This is very important and I have noted it with thanks. šŸ™ So I am doing this with caution as per...

Firebase is not supported in BANano

and expect anyone else to, anyway.

At least for the app I'm working on, we will have to do without the PWA functionality for now as this is rather crucial for success here.
 

Mashiane

Expert
Licensed User
Longtime User
Our next step is to try and attempt to send messages via the client. We will use a token to do this and send the message using BANanoFetch. This will send a message which can be received when the app has the focus or in background (latter not tested yet)

You can create your user interface to do this anyway you want of course...

1. Get the registered token to send a message to.

ClientSending.gif


We can then use Push.JS to show the notification.

B4X:
'use fetch
    Dim response As BANanoFetchResponse
    Dim error As BANanoObject
    Dim data As BANanoJSONParser
  
    Dim options As BANanoFetchOptions
    options.Initialize
    options.Method = "POST"
    '
    'lets define the headers
    Dim hdrs As Map = CreateMap()
    hdrs.Put("Content-Type", "application/json;")
    hdrs.Put("Authorization", "key=" & firebase.serverkey)
    options.Headers = hdrs
    '
    'lets define the notification
    Dim notification As Map = CreateMap()
    notification.put("title", stitle)
    notification.Put("body", smessage)
    notification.Put("icon", "./assets/firebase-logo.png")
    'lets define the body of the fetch
    Dim body As Map = CreateMap()
    body.put("to", stoken)
    body.put("notification", notification)
    options.Body = banano.ToJson(body)
       
    Dim fetch As BANanoFetch
    fetch.Initialize("https://fcm.googleapis.com/fcm/send", options)
    fetch.Then(response)
    fetch.Return(response.json)
    fetch.Then(data)
    Dim res As Map = data.NextObject
    Dim results As List = res.get("results")
    If results.Size > 0 Then
        Dim result As Map = results.Get(0)
        Dim smessage_id As String = result.Get("message_id")
        vuetify.ShowSnackBarSuccess("Message ID: " & smessage_id)
    End If
    fetch.Else(error)
    Log(error)
    fetch.End

To be continued...
 
Last edited:

alwaysbusy

Expert
Licensed User
Longtime User
We can then use Push.JS to show the notification.
Any particular reason why you just don't use only push.js? Just did a quick test and it is extremely easy to use and does not require a Service Worker.

B4X:
Sub Process_Globals
   ...
   Dim Push As BANanoObject
End Sub

Sub AppStart (Args() as String)
   ...
   BANano.Header.AddJavascriptFile("https://cdnjs.cloudflare.com/ajax/libs/push.js/1.0.8/push.min.js")
   ...
End Sub

Sub Btn_Click()
    Log("Permissions clicked!")
    ' initialize Push
    Push.Initialize("Push")
   
    ' make the callbacks
    Dim onGrantedCB As BANanoObject
    onGrantedCB = BANano.CallBack(Me, "onGranted", Null)
    Dim onDeniedCB As BANanoObject
    onDeniedCB = BANano.CallBack(Me, "onDenied", Null)
   
    ' ask for permission to show notifications
    Push.GetField("Permission").RunMethod("request", Array(onGrantedCB,onDeniedCB))
End Sub

Sub onGranted() 'ignore
    Log("Granted!")
   
    ' on click callback
    Dim OnClickCB As BANanoObject
    OnClickCB = BANano.CallBack(Me, "onClick", Null)
   
    ' some options on how the notification should look
    Dim Options As BANanoObject
    Options.Initialize5
    Options.SetField("body","This is a web notification!")
    Options.SetField("icon", "/assets/banano.jpg")
    Options.SetField("timeout", 5000)
    Options.SetField("onClick", OnClickCB)
   
    ' make the notification   
    Push.RunMethod("create", Array("Hello from BANano!", Options))
   
End Sub

Sub onDenied() 'ignore
    Log("Denied!")
End Sub

Sub onClick() 'ignore
    Log("Clicked!")
End Sub

Alwaysbusy
 

Mashiane

Expert
Licensed User
Longtime User
Any particular reason why you just don't use only push.js? Just did a quick test and it is extremely easy to use and does not require a Service Worker.
Thanks for the push code, you beat me to it as I was just going to write that now for the client side. I wish it was that easy with firebase implementation.

The sending scripts above are just planning tests. The plan is to have a BANano UI to send messages to other devices and not the currently active app. However, on the client side, I do need Push to show the notifications when a user has the webapp opened.

Just to clarify...

Apparently...

1. The firebase messages are received 2 ways, in background mode and also when the app is focused. When the app is running in the background i.e. minimized etc, the service worker is needed and built it within the service worker is a script to show the notification.

In background mode, this code is only available on the service worker

B4X:
messaging.onBackgroundMessage(function(payload) {
  console.log('service worker', payload);
  const notification = payload.notification;
  const notificationTitle = notification.title;
  const notificationOptions = {
    body: notification.body,
    icon: notification.icon,
    tag: 'fb'
    requireInteraction: true,
    vibrate: [300, 100, 400]
  };
  return self.registration.showNotification(notificationTitle, notificationOptions);
});"$)

2. In foreground mode, when the app is active and running, this code is only available on the client side and one has to build the script to show the notification.

B4X:
'when a message is received when app has focus
Sub onMessage(payload As Object)
    Log("foreground: onMessage...")
    Log(payload)

End Sub

Thus, the push script I was going to write, (which you have just did graciously for us), is needed on the client side to build the notification.

So, in background mode, the onMessage is never fired but onBackgroundMessage. When the app is opened on the browser, the onBackgroundMessage is never fired but onMessage. Two sides of a coin kinda. Two apps opened, one sending a message and the other receiving, and these should show notifications whether they are in background / foreground mode.

Thanks a mil for the push code. That was to close this circle. As always, you are a star!

TheMash

Related Article

 
Last edited:

alwaysbusy

Expert
Licensed User
Longtime User
The firebase messages are received 2 ways, in background mode and also when the app is focused. When the app is running in the background i.e. minimized etc, the service worker is needed and built it within the service worker is a script to show the notification.
I think this is only partially right. Service Workers are only needed if you want to receive notifications if the APP is not open. Push.js without it does send notifications even if the browser is minimized or on another tab, as long as the APP is on one of its tabs. (at least this is what I'm seeing here: I've put the create notification in a timer and I could minimize the browser, go to another tab etc and the notifications still popped up).

I see push.js itself has an FCM plugin. Are you using this?

Alwaysbusy
 

Mashiane

Expert
Licensed User
Longtime User
The firebase messages are received 2 ways, in background mode and also when the app is focused.

I think this is only partially right.
For the past 3 days I turned the interweb down looking for all the ways to do this with firebase and that's the best solution I could find using pure firebase messaging code, and as usual, all the code examples are silent on further use of onMessage (foreground) except to console.log it and all the code about onBackgroundMessage (background) only end up with showing an notification. What if one wants to do something else on the client side besides showing a notification. Typical. I even tried a .RunMethod("onBackgroundMessage") and that kicked me out.

Either way, I still think if there was an easier way to implement this using firebase messaging that will work without hurdles, it would be welcomed!

I see push.js itself has an FCM plugin. Are you using this?

I saw that yesterday and have not explored it yet. I need to think around the BANano code needed so that I can make it work I guess. LOL. If that will work with both background & foreground firebase messaging, it will be all good.

Thanks
 

alwaysbusy

Expert
Licensed User
Longtime User
push.js itself has an FCM plugin
Just to let you know that there is stuff in this plugin that needs to be updated to the latest version of Firebase, but it looks like the cleanest way to go. I'll look further into it next year ;-)

Alwaysbusy
 

Mashiane

Expert
Licensed User
Longtime User
So far we have been able to show notifications when they come from the background. We want to also show notifications when a message is received in the foreground.

1609257400882.png


This was possible in the service worker with...

B4X:
messaging.onBackgroundMessage(function(payload) {
  console.log('service worker', payload);
  const notification = payload.notification;
  const notificationTitle = notification.title;
  const notificationOptions = {
    body: notification.body,
    icon: notification.icon,
    requireInteraction: true,
    vibrate: [300, 100, 400]
  };
  return self.registration.showNotification(notificationTitle, notificationOptions);
});"$)

After some gulu-gulu search I came upon a script that I was able to "BANano-ze'

Being...

B4X:
messaging.onMessage(function(payload) {
    console.log("onMessage: ", payload);
    navigator.serviceWorker.getRegistration('/firebase-cloud-messaging-push-scope').then(registration => {
        registration.showNotification(
            payload.notification.title,
            payload.notification
        )
    });
});

and wrote it as...

B4X:
'when a message is received when app has focus
'Reference: https://stackoverflow.com/questions/40411059/create-firebase-notification-with-page-in-foreground-focus/60247902#60247902
Sub onMessage(payload As Map)
    Log("foreground: onMessage...")
    Log(payload)
    Dim notification As Map = payload.Get("notification")
    Dim sbody As String = notification.Get("body")
    Dim sicon As String = notification.Get("icon")
    Dim stitle As String = notification.Get("title")
    '
    Dim nav As BANanoNavigator = banano.Navigator
    
    Dim registration As BANanoObject
    Dim swe As Map
    Dim swr As BANanoPromise = nav.GetField("serviceWorker").RunMethod("getRegistration", "/firebase-cloud-messaging-push-scope")
    banano.Await(swr)
    swr.Then(registration)
    Dim notificationOptions As Map = CreateMap()
    notificationOptions.Put("body", sbody)
    notificationOptions.Put("icon", "./assets/firebase-logo.png")
    notificationOptions.Put("requireInteraction", True)
    notificationOptions.Put("putvibrate", Array(300, 100, 400))
    '
    registration.RunMethod("showNotification", Array(stitle, notificationOptions))
    
    swr.Else(swe)
    vuetify.ShowSnackBarError("We could not show the notification...")
    Log(swe)
    swr.End
End Sub

And this works perfectly!
 

alwaysbusy

Expert
Licensed User
Longtime User
I had a look at your BANanoFirestore lib and its usage in BANanoVuetifyAD3 and this looks like a great implementation. Well done!
(side note: as it does more than the store alone, like Authentication and now messaging too, I think it earns the right to be called BANanoFirebase)

I only took a quick look at your code, but from what I understand the only thing you are missing to have a 'clean' solution in BANano is the Service Worker part, correct?
(Kudos on the great use of BANanoPromise btw!) It appears the BANanoFirestore lib itself does not rely on Vue and could be used in a pure BANano project, right? If so, I think it is worth for me to make the necessary changes in the BANano Core to integrate this in the generated Service Worker. This way, the other BANano PWA functionalities (like caching) could work together with Firebase messaging.

As the FCM plugin of push.js is very outdated and would require for me to almost rewrite it to work again (now it only works 1/3th of the time it appears, missing a lot of messages) I would see on how I could implement this part in the BANano Core as it fits right in my timeline to expand the Service Worker support in BANano (e.g. PostMessage is in the pipeline for bi-directional communication between the app and the Service Worker).

Alwaysbusy
 

alwaysbusy

Expert
Licensed User
Longtime User
Just to verify: when Chrome is completely closed, does the notification come up in your case? (I don't think it can, but just checking...)

I have the 'on click' working (slightly different payload than yours) and using the setBackgroundMessageHandler(). If you have a tab open in the browser, it will go to that tab, else opens a new tab.

Payload:
B4X:
{
  "to":"cSYHfB-AVLxizOKIwb-hfu:APA91bHQOC5sYiBgKlMBvjFl93ixTFuWIA6OEjGV457Hnxgl4vJ-...",
  "data" : {
    "title" : "Alain",
     "body" :  "Alain2",
     "icon": "./assets/banano.jpg",
     "content_available" : true,
     "priority" : "high",
     "url": "http://127.0.0.1:8887/"
   }
}

In the service worker:
B4X:
messaging.setBackgroundMessageHandler(function(payload) {
    console.log('[firebase-messaging-sw.js] Received background message ', payload);
    const notificationTitle = payload.data.title;
    const notificationOptions = {
        body: payload.data.body,
        icon: payload.data.icon,
        image: payload.data.image,
        requireInteraction: true,
        vibrate: [300, 100, 400],
        click_action: payload.data.url, // To handle notification click when notification is moved to notification tray
        data: {
            click_action: payload.data.url
        }
    };

    self.addEventListener('notificationclick', function(event) {
        console.log(event.notification.data.click_action);
        if (!event.action) {
            console.log('Notification Click.');
            event.notification.close();
            var url = event.notification.data.click_action;
            event.waitUntil(
                clients.matchAll({type: 'window'}).then( windowClients => {
                    // Check if there is already a window/tab open with the target URL
                    for (var i = 0; i < windowClients.length; i++) {
                        var client = windowClients[i];
                        // If so, just focus it.
                        if (client.url === url && 'focus' in client) {
                            return client.focus(); // for some reason shows an error in the logs, but does it anyway
                        }
                    }
                    // If not, then open the target URL in a new window/tab.
                    if (clients.openWindow) {
                        return clients.openWindow(url);
                    }
                }));            
            return;
        }else{
            event.notification.close();
        }
    });
    return self.registration.showNotification(notificationTitle,notificationOptions);
});

Alwaysbusy
 
Top