How to subscribe to topics with web browser using Firebase Cloud Messaging

23,750

Solution 1

There is no direct API to subscribe clients to topics in the Firebase Cloud Messaging SDK for JavaScript. Instead you can subscribe a token to a topic through the REST API. Calling this API requires that you specify the FCM server key, which means that you should only do this on a trusted environment, such as your development machine, a server you control, or Cloud Functions. This is necessary, since having the FCM server key allows sending messages on your app's behalf to all of your app's users.

It turns out that in my tests I was using an older project, where client API keys were allows to subscribe to topics. This ability has been removed from newer projects for security reasons.

In Node.js you could for example call the REST API to create a relation mapping for an app instance like this:

function subscribeTokenToTopic(token, topic) {
  fetch('https://iid.googleapis.com/iid/v1/'+token+'/rel/topics/'+topic, {
    method: 'POST',
    headers: new Headers({
      'Authorization': 'key='+fcm_server_key
    })
  }).then(response => {
    if (response.status < 200 || response.status >= 400) {
      throw 'Error subscribing to topic: '+response.status + ' - ' + response.text();
    }
    console.log('Subscribed to "'+topic+'"');
  }).catch(error => {
    console.error(error);
  })
}

Where fcm_server_key is the FCM server key, taken from the Firebase console of your project.

Solution 2

Any one looking for php solution find below since you are going to use Api key of server so don't do this on client side

client side fire base js code

// Initialize Firebase
var config = {
    apiKey: "xxxx",
    authDomain: "yyy",
    databaseURL: "zzzz",
    projectId: "aaaa",
    storageBucket: "bbbbb",
    messagingSenderId: "ccc"
  };
firebase.initializeApp(config);

const messaging = firebase.messaging();

messaging.requestPermission()
.then(function() {
  console.log('Notification permission granted.');
  return messaging.getToken();
})
.then(function(token) {

//send this token to server
  console.log(token); // Display user token
})
.catch(function(err) { // Happen if user deney permission

  console.log('Unable to get permission to notify.', err);
});

messaging.onMessage(function(payload){
    console.log('onMessage',payload);
})

server side code by php curl

$headers = array
    ('Authorization: key=' . API_ACCESS_KEY,
    'Content-Type: application/json');

$ch = curl_init();
// browser token you can get it via ajax from client side
$token = 'drVdtCt82qY:APA91bEZb99GvoS9knv-cp5ThVBiYGBjUwl_Ewj2tKaRFwp7HoG347utaNKbgLWmkxpGadABtIg-DspPUh5sC_bc2JrBKVw10Ejg72nYxZgD2wBU-adYJo0yi03lX22s5K2UEp6gwnMv';
curl_setopt($ch, CURLOPT_URL, "https://iid.googleapis.com/iid/v1/$token/rel/topics/testIshakTopic");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, array());
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
echo "The Result : " . $result;

Hope it will help for PHP developers

Solution 3

Franks solution is still valid. However you need to have a server that does the subscribe for you.

function subscribeTokenToTopic(token, topic) {
  fetch('https://myserver.com/'+token+'/rel/topics/'+topic, {
    method: 'POST',
    headers: new Headers({
      'Authorization': 'Bearer '+ oauthToken
    })
  }).then(response => {
    if (response.status < 200 || response.status >= 400) {
      throw 'Error subscribing to topic: '+response.status + ' - ' + response.text();
    }
    console.log('Subscribed to "'+topic+'"');
  }).catch(error => {
    console.error(error);
  })
}

then your server has a rest call something like this:

(java spring)

@RequestMapping(value = "/{token}/rel/topics/{topic}", method = RequestMethod.POST)
public ResponseEntity<?> subscribeTokenToTopic(@PathVariable("token") String token, @PathVariable("topic") String topic)  throws ServletException {
  URL url = new URL("https://iid.googleapis.com/iid/v1/"+token+"/rel/topics/"+topic);
  // make https post call here.. 
  ...
}

Hopefully that makes sense.

Solution 4

    import firebase from 'firebase/app';
    import 'firebase/messaging';

    const config = {
        apiKey: "xxxx",
        authDomain: "xxx",
        databaseURL: "xxx",
        projectId: "xxx",
        storageBucket: "xxxx",
        messagingSenderId: 'xxxxx',
        appId: 'xxxxx',
        measurementId: 'xxxxxx'
      };
      firebase.initializeApp(config);
        try {
              if (firebase.messaging.isSupported()) {
                const messaging = firebase.messaging();
                messaging
                  .getToken({
                    vapidKey: VAPID_KEY
                  })
                  .then((currentToken) => {
                    if (currentToken) {
                                           subscribeTokenToTopic(currentToken,FirebaseAdminTopic);
                    }
                  })
                  .catch((err) => {
                    console.log('Error to get token', err);
                  });

                messaging.onMessage((payload) => {
                  console.log(payload.notification)
                });

                // Otherwise, we need to ask the user for permission
                if (Notification.permission !== 'granted') {
                  Notification.requestPermission();
                }
              } else {
                console.log('firebase messaging not supported');
              }
            } catch (err) {
              console.log(err);
            }
            
            
            
        function subscribeTokenToTopic(token, topic) {
          fetch(`https://iid.googleapis.com/iid/v1/${token}/rel/topics/${topic}`, {
            method: 'POST',
            headers: new Headers({
              Authorization: `key=${FCM_SERVER_KEY}`
            })
          })
            .then((response) => {
              if (response.status < 200 || response.status >= 400) {
                console.log(response.status, response);
              }
              console.log(`"${topic}" is subscribed`);
            })
            .catch((error) => {
              console.error(error.result);
            });
          return true;
        }

 

Share:
23,750

Related videos on Youtube

Derek
Author by

Derek

I am a Java Certified developer with experience in ColdFusion, SQL, XML, Frisbee Golf, HTML, PHP, JavaScript, and more!

Updated on January 02, 2021

Comments

  • Derek
    Derek over 3 years

    I'm trying to find a way to send a notification using Firebase Cloud Messaging to all of my app's users, but I have a web-only app. I've seen solutions that appear to be for Android/iOS, which basically involves having the users auto-subscribe to a topic called "allDevices" and then sending a notification to all users subscribed to that topic. I just can't seem to find any documentation on how to have a web-based user subscribe to a topic. Does anyone know if that's possible, and if it is, is there documentation that I've missed that would cover that?

    Thanks!

  • Derek
    Derek over 7 years
    Thanks, Frank! This looks like the alley I need to go down. Is there something I need to setup with my Firebase project in order to use the IID API? Do I need to predefine a topic somewhere/somehow? I attempted to use this function, but I'm getting a 401 error in the console: "Error subscribing to topic: 401 - [object Promise]". For reference, I have verified that the token is correct (I can send a targeted notification to the specific token) and my apiKey is set properly as well.
  • Derek
    Derek over 7 years
    Oh, and for further context, I'm just learning Firebase. I've gotten the Firebase team's JS messaging quickstart project up and successfully running, so if it helps to know what the code base looks like, this is what I'm using: github.com/firebase/quickstart-js/tree/master/messaging.
  • Frank van Puffelen
    Frank van Puffelen over 7 years
    Topics are automatically created when a device subscribes to them or when you send a message to them (using the API).
  • Derek
    Derek over 7 years
    Okay, cool. That's kind of what I expected. Unfortunately that means there's some other reason why I'm having problems... if I'm understanding correctly the IID reference that you linked to, the 401 error occurs due to an invalid header. I tried adding Content-Type to the header in your code like this: headers: new Headers({ 'Content-Type' : 'application/json', 'Authorization': 'key='+config.apiKey }) Unfortunately, I'm still getting a 401 error. I am perhaps misunderstanding what a 401 error means here?
  • Frank van Puffelen
    Frank van Puffelen over 7 years
    I know this function works, since I use it in multiple projects at the moment. If it doesn't work for you, that is likely something specific to your code/project/setup. Stack Overflow is a notoriously inefficient mechanism to interactively debug your app. If you're having problems getting specific code to work, share the minimal code that reproduces the problem in your question.
  • Derek
    Derek over 7 years
    Thanks for your responses, Frank, I appreciate it. For some reason, it boiled down to my Firebase project's API key. I'm not sure why at this point, but I had a different API key on my project yesterday than what I see today when I look at my project in the Firebase console. The code that gets auto-generated to add Firebase to my web app now shows a different API key than what I wrote down yesterday. So basically, the app works with that generated code, but in order to get your function to work, I had to hard code my different key from yesterday. I have no idea why or how it changed... weird.
  • Frank van Puffelen
    Frank van Puffelen over 7 years
    That sounds weird indeed. Not sure what is going on there. Just make sure that it is a client API key and not something that is not supposed to only be present on the server.
  • Derek
    Derek over 7 years
    Aha! I figured out the key confusion! It turns out I had written down the Cloud Messaging Server Key (found on the Firebase console under Project Settings > Cloud Messaging), which I had to hard code as the API Key in the function you provided, @frank-van-puffelen. This is different from the Web API Key (in Project Settings > General), which is the config.apiKey as generated by Firebase when I click on "Add Firebase to your web app" in the Firebase console. Would you happen to know the difference between these two keys?
  • Frank van Puffelen
    Frank van Puffelen over 7 years
    You should never expose the server key in client-side code. In my tests the code also works with the client-side API key. But I heard rumors that this was unintentional, so it could be that the behavior changed. Interestingly enough though: I just tested and I am still able to subscribe with my client-side API key, so it may be that this behavior was changed recently or only for newer keys.
  • Derek
    Derek over 7 years
    If I shouldn't put the Server Key into my javascript, and the client-side API Key isn't intended to work in this case, is there a recommended solution/practice? Can you obscure/encrypt a Server Key?
  • Derek
    Derek over 7 years
    The Cloud Messaging Server Key is what I got the function to work with. The Web API Key did not work.
  • Frank van Puffelen
    Frank van Puffelen over 7 years
    I asked around and indeed: this will only work with client-side API keys for older projects. For newer projects a server key is needed, which means that you can't safely subscribe to topics from a browser. I'll update my answer.
  • Jos
    Jos about 7 years
    This is an awesome solution.. Thanks a lot.. you saved my day.. (may be even a week, I was going to do a custom implementation using websocket!)
  • Sinan Noureddine
    Sinan Noureddine over 5 years
    So how can we do it without showing the server key?
  • Titan
    Titan over 4 years
    @FrankvanPuffelen any idea why the native app firebase sdk can subscribe to a topic via the client sdk but the web can't?
  • J. Doe
    J. Doe over 3 years
    Isn't the link 'iid.googleapis.com/iid/v1' for GCM? Does it also work for FCM?