Blogs

Firebase Cloud Messaging setup for JHipster generated Angular 11 application

Category
Software development
Firebase Cloud Messaging setup for JHipster generated Angular 11 application

In our previous post we have demonstrated the power and the ease of use of the JHipster code generator by building the famous Spring “Pet Clinic” application, with the Spring Boot / Angular as its tech stack. In this blog, we will show how to integrate Angular-based applications with Google’s Firebase Cloud Messaging (FCM) service.

Google’s Firebase cloud messaging provides an infrastructure along with the API abstraction to send messages across a range of devices, including Android, iOS, and Web apps. FCM can be used to send messages directly to a specific device, or to a topic, for messages to be consumed by multiple devices subscribed to it. Later in the showcase for this blog.

That said, in this exercise we will build from scratch JHipster generated Spring Boot/Angular (v 11.x) application whose backend will provide the continuous feed of messages to a FCM topic, which will be consumed by the Angular web client.

To be able to hands-on follow this blog post, you will need to have the following tools in place: Git, Java JDK, Nodejs, npm and JHipster (version 7.0.1. at the time of writing of this post). For the detailed instructions on how to setup your dev environment please take a look at JHipster officialsetup guide.

1. Create JHipster project and configure Google Firebase SDK

The first step is to create application skeleton. Open the terminal and issue the following commands:

mkdir jhfcm 
cd jhfcm/ 
jhipster

When prompted to do so, answer the JHipster wizard’s questions as shown in the image (and the listing) bellow:

After the scripts finishes target folder will contain generated application that you can start simply by running:

./gradlew

If you would also like to start Angular front end client separately with live reload server, open a new terminal in the same folder and type:

./npm start

The next step is to add Google Firebase SDK libraries. To do so, open build.gradle file and add the following library.

dependencies { 
...
  implementation 'com.google.firebase:firebase-admin:7.1.1' 
}

Be sure to check Google documentation for the latest version of firebase-admin library.

The next step is to configure Firebase project. Projects serve as container for our apps (iOS, Andorid, Web, …).


1. Go to https://console.firebase.google.com and sign up for an account if you already do not have one.


2. Click the Add Project button to create new project and follow these steps:

  • Fill in the new project name in the input field (“jh-lab” in my case)
  • Click the Create project button and click the continue button in steps 2-3 (optionally enable/disable Google Analytics)
  • Now we have created a new Firebase project which we will use in this post

3. Then navigate to the “Cloud Messaging” card click it and press register app link

  • Enter the name of the Jhipster web app (jhfcm in my case)
  • And press “Register app” button!

4. Finally, go to the “Project settings” -> service Accounts tab to create a new service account. We will use this service account to connect our web app to Firebase.

  • Press “Generate new private key” and download the generated json file to your local machine
  • Rename this file as firebase-service-account.json and save it in your project in folder src/java/resources/

5. Now we need to configure Google firebase SDK in our application. To do so add the following lines to the end of the application.yml file in the src/main/resources/config folder.

# application:
jhfcm: 
  fcm:
    sa-json-file: firebase-service-account.json 
    project-id: jh-lab-5417a

Also, make sure to enter correct project-id of your web project (we will need it later), you can find it on the “Project settings” >> General tab.

Next step is to configure firebase SDK client instance to use this key. Create FcmConfig.java file in src/main/java/ with the following content:

package com.ag04.jhfcm.config;

import java.io.IOException;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.FirebaseMessaging;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
/**
 * Firebase Cloud Messaging related configurations.
 * 
 * @author dmadunic
 */
@Configuration
public class FcmConfig {

    @Value("${jhfcm.fcm.sa-json-file}")
    String fcmSaJsonFile;

    @Bean
    public FirebaseMessaging firebaseMessaging() throws IOException {
        GoogleCredentials googleCredentials = GoogleCredentials
            .fromStream(new ClassPathResource(fcmSaJsonFile).getInputStream());
        FirebaseOptions firebaseOptions = FirebaseOptions
            .builder()
            .setCredentials(googleCredentials)
            .build();

        FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "jh-fcm");
        return FirebaseMessaging.getInstance(app);
    }
}

With this, we have setup our playground, and are ready for the next step: implement producer of Firebase cloud messages.

2. Publishing messages: Jokes producer

Messages our applicaiton backend will publish to FCM topic will be the famous “Chuck Norris jokes.” There is publicly accessible API at “https://api.chucknorris.io/” from where app will periodically pull jokes and then deliver them to Firebase cloud for further delivery.

To simplify the implementation of the component that will fetch Chuck Norris jokes we will use unirest library, so add the following two lines to build.gradle:

dependencies { 
...
   compile group: 'com.konghq', name: 'unirest-java', version: '3.11.04'
   compile group: 'com.konghq', name: 'unirest-objectmapper-jackson', version: 
'3.11.04'
}

The section of code below shows the implementation of the job (SendJokesJob.java) that runs every 60 seconds, fetches one Joke from the rest API and publishes it to the FCM topic named “chuck-jokes” (topic name is defined in the application properties file with the “jhfcm.fcm.topic.jokes” property).

package com.ag04.jhfcm.producer;
...

@Component
public class SendJokesJob {  
private static final Logger log = LoggerFactory.getLogger(SendJokesJob.class);
    private static final String MSG_TTL = "300"; // seconds

    @Value("${jhfcm.fcm.topic.jokes}")
    private String jokeTopic;
    private long count = 0;
    private final FirebaseMessaging firebaseMessaging;

    public SendJokesJob(FirebaseMessaging firebaseMessaging) {
        this.firebaseMessaging = firebaseMessaging;
    }

    @Scheduled(initialDelay = 60000, fixedDelay = 30000)
    public void sendChuckQuotes() {
        JsonNode jokeResponse = Unirest.get("http://api.icndb.com/jokes/random").asJson().getBody();
        String id = jokeResponse.getObject().getJSONObject("value").getString("id");
        String joke = jokeResponse.getObject().getJSONObject("value").getString("joke");
        count++;
        Map<String, String> data = new HashMap<>();
        data.put("id", id);
        data.put("seq", String.valueOf(this.count));
        data.put("joke", joke);
        data.put("ts", String.valueOf(Instant.now())); // message timestamp
        try {
            log.debug("--> Sending FCM message (id={}) to topic='{}'", id, jokeTopic);
            String messageId = sendDataMessage(data, jokeTopic);
            log.info("--> FCM message sent to topic='{}' wiht messageId={}", jokeTopic, messageId);
        } catch (FirebaseMessagingException e) {
            log.error("FAILED to send chuck joke message:", e);
        }
    }
    // rest of the class omitted ...

}

The core of the interaction with the Firebase messaging API is in the sendDataMessage() method, located in the same file:

private String sendDataMessage(Map<String, String> data, String topic) throws FirebaseMessagingException {  
        Message message = Message
            .builder()
            .setTopic(topic)
            .putAllData(data)
            .setWebpushConfig(WebpushConfig
                .builder()
                .putHeader("ttl", MSG_TTL)
                .build()
        ).build();
        return firebaseMessaging.send(message);
}

In this example, a simple data message which consists only of block of data, no notification title or body, or any other platform specific options is built. In such setup, it is responsibility of the client/device that receives this message to process it and decide how to display its content to the user.

Google SDK provides numerous options for customizing how the message should be handled and displayed by clients, for more refer to the official docs on building FCM messages: https://firebase.google.com/docs/cloud-messaging/send-message

3. Topic subscription management

Devices/Clients that want to consume messages put to the “chuck-jokes” FCM topic first need to subscribe to it with their Firebase token.

Example of the simple Controller with two basic Rest API methods to manage subscription to and from “chuck-jokes” topic for devices/clients can be found bellow.

package com.ag04.jhfcm.web.rest;
// imports omitted ...

/**
 * Controller for FCM topic subscription management (subscribe/unsubscribe).
 * 
 * @author dmadunic
 */
    @RestController
    @RequestMapping("/api")
    public class FcmRegistrationController {

    private final Logger log = LoggerFactory.getLogger(FcmRegistrationController.class);

    @Value("${jhfcm.fcm.topic.jokes}")
    private String jokeTopic;

    private final FirebaseMessaging firebaseMessaging;

    public FcmRegistrationController(FirebaseMessaging *firebaseMessaging*) {
        this.firebaseMessaging = firebaseMessaging;
    }

    @PostMapping("/fcm/registration")
    public ResponseEntity<FcmTopicSubscriptionResult> subscribe(@RequestBody String token) throws FirebaseMessagingException {
        log.debug("Processing Topic subscription request for token='{}'", token);
        FcmTopicSubscriptionResult result;

        TopicManagementResponse response = firebaseMessaging.subscribeToTopic(Collections.singletonList(token), jokeTopic);
        if (response.getSuccessCount() > 0) {
            log.info("--> FINISHED processing subscription to topic: '{}' for token '{}'", jokeTopic, token);
            result = new FcmTopicSubscriptionResult(jokeTopic, true);
        } else {
            String reason = getErrorReason(response);
            log.error("--> FAILED processing subscription to topic: '{}'! Reason: {} / for token={}", new Object[] { jokeTopic, reason, token });
            result = new FcmTopicSubscriptionResult(jokeTopic, false, reason);
        }
        return ResponseEntity.ok(result);
    }

    @PutMapping("/fcm/registration")
    public ResponseEntity<FcmTopicSubscriptionResult> unsubscribe(@RequestBody String token) throws FirebaseMessagingException {
        log.debug("Processing Topic unsubscription request for token='{}'", token);
        FcmTopicSubscriptionResult result;

        TopicManagementResponse response = firebaseMessaging.unsubscribeFromTopic(Collections.singletonList(token), jokeTopic);
        if (response.getSuccessCount() > 0) {
            log.info("--> FINISHED processing unsubscription from topic: '{}' for token '{}'", jokeTopic, token);
            result = new FcmTopicSubscriptionResult(jokeTopic, true);
        } else {
            String reason = getErrorReason(response);
            log.error("--> FAILED processing unsubscription from topic: '{}'! Reason: {} / for token={}", new Object[] { jokeTopic, reason, token });
            result = new FcmTopicSubscriptionResult(jokeTopic, false, reason);
        }
        return ResponseEntity.ok(result);
    }

    private String getErrorReason(TopicManagementResponse *response*) {
        String reason = response.getErrors().get(0).getReason();
        return reason;
    }

    public class FcmTopicSubscriptionResult {
        private String topic;
        private boolean success;
        private String errorCode;

        public FcmTopicSubscriptionResult(String topic, boolean success) {
            this(topic, success, null);
        }

        public FcmTopicSubscriptionResult(String topic, boolean success, String errorCode) {
            this.topic = topic;
            this.sucess = success;
            this.errorCode = errorCode;
        }
     // get/set methods omitted ...
    }
}

Controller code is rather simple and does not require much explaining. Both methods return HTTP status 200 with response body consisting of FcmTopicSubscriptionResult object. Property “success” of FcmTopicSubscriptionResult indicates if requested operation finished with success or not, and in case of failure property errorCode contains exact error code otherwise it is empty.

Finally, we will exempt endpoints of this controller from any security. To do so edit the class com.ag04.jhfcm.config.SecurityConfiguration.java and add the following line to the list of free to access endpoints inside configure() method:

@Override
public void configure(HttpSecurity http) throws Exception {
 ...
 .antMatchers("/api/fcm/registration").permitAll()
 ...
}

The next step: implement web consumer for messages from our topic, is a little bit more trickier 😉

4. Setup Angular Web Applicaiton

4.1. Configure Service Worker

For Web application to be able to receive Firebase messages it needs to have configured service worker. For more on concept of service workers see this link. Angular application generated by JHipster already by default have webapp manifest file, so we only need to configure our Firebase messaging service worker.

Inside “/src/main/webapp/” folder create the file: “firebase-messaging-sw.js” with the following content:

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

const firebaseConfig = {
    apiKey: "AIzaSyDSt-ls4ETYEzIOh1ZGEuo7387PIJp_8E8",
    authDomain: "jh-lab-5417a.firebaseapp.com",
    projectId: "jh-lab-5417a",
    storageBucket: "jh-lab-5417a.appspot.com",
    messagingSenderId: "544462390526",
    appId: "1:544462390526:web:9cce3bcbd66c7db395d1af"
};

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

Values for the firebaseConfig variable, you can find in Firebase console under “Project settings” General tab (see the image below):

To register this Service worker in Angular app we need to modify app.module.ts file and add the line as shown in the following code snipet:

@NgModule({
  imports: [
    BrowserModule,
    SharedModule,
    HomeModule,
    // jhipster-needle-angular-add-module JHipster will add new module here
    EntityRoutingModule,
    AppRoutingModule,
    // Set this to true to enable service worker (PWA)
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: false }),
    ServiceWorkerModule.register('firebase-messaging-sw.js', { enabled: true }), // Add me!
    HttpClientModule,
    NgxWebstorageModule.forRoot({ prefix: 'jhi', separator: '-', caseSensitive: true }),
  ],
  providers: [
  ... 

Next step is to modify angular.json file by adding the line with the firebase-messaging-sw.js in the assets block:

“assets”: [
 “src/main/webapp/content”,
 “src/main/webapp/favicon.ico”,
 “src/main/webapp/firebase-messaging-sw.js”,
 “src/main/webapp/manifest.webapp”,
 “src/main/webapp/robots.txt”,
 …

This has fixed build for the angular app to also include and package our Service Worker configuration.

4.2 Adjust Angular application security polices

For service worker to function properly we also need to tweak a little general web security policies of the angular application served by our backend. Go ahead and open file: com.ag04.jhfcm.config.SecurityConfiguration.java and change the following method to look as shown below:

@Override
public void configure(HttpSecurity http) throws Exception {

        String securityPolicy = "default-src 'self'; frame-src 'self' data:;" +
            " script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com " + 
            " https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js " + 
            " https://www.gstatic.com/firebasejs/8.6.1/firebase-messaging.js;" + 
            " connect-src * 'self' https://firebaseinstallations.googleapis.com/v1/" +
            " https://fcmregistrations.googleapis.com/v1/projects/" + fcmProjectId + "/registrations" +
            " https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js" +
            " https://www.gstatic.com/firebasejs/8.6.1/firebase-messaging.js;" +
            " style-src * 'self' 'unsafe-inline';" +
            " img-src * 'self' data:;" +
            " font-src * 'self' data:";

        // @formatter:off
        http
            .csrf()
            .disable()
            .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling()
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport)
        .and()
            .headers()
            .contentSecurityPolicy(securityPolicy)
            //.contentSecurityPolicy(jHipsterProperties.getSecurity().getContentSecurityPolicy())
        .and()
        // rest of the method omitted ....

Make sure to set correct version for the Google Firebase javascript libraries (8.6.1 in this case). Add the definition for the fcmProjectId at the top of the class, so it can be referenced in securityPolicy variable.

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    ...    @Value("${jhfcm.fcm.project-id}")
    private String fcmProjectId;
    ...

At this point we can test our application setup by running it with the following command(s):

./gradlew

Now, if you open the url: “http://localhost:8080/” in chrome browser, in “Developer tools” under the Application tab you should see your Firebase messaging service worker registered.

With all this pieces of the configuration in the place we are ready to implement Angular based consumer of the Firebase messages.

5. Firebase messages consumer application


5.1 Add and configure angular/firebase messaging libraries

To simplify integration of our angular application with the FCM platform, we will use support libraries developed and maintained by Google. Open the package.json file and add the support firebase libraries to the end of the dependencies block:

"dependencies": {
    ...
    "@angular/fire": "^6.1.5",
    "firebase": "^8.6.1"
  },

Now run npm install to add these libraries to the project.
Modify app.module.ts to import and configure new firebase libraries, by adding these lines:

...
import { AngularFireMessagingModule } from '@angular/fire/messaging';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFireModule } from '@angular/fire';
import { firebase } from './shared/firebase/firebase.constants';

@NgModule({
  imports: [
    BrowserModule,
    SharedModule,
    HomeModule,
    // jhipster-needle-angular-add-module JHipster will add new module here
    EntityRoutingModule,
    AppRoutingModule,
    AngularFireAuthModule,
    AngularFireMessagingModule,
    AngularFireModule.initializeApp(firebase),
    // Set this to true to enable service worker (PWA)
...

Now let us create “firebase” folder inside “src/main/webapp/app”, this is the place we will put everything related to firebase platform.
First, we will place all firebase related configuration values into separate file: “src/main/webapp/app/firebase/firebase.constants.ts”:

export const firebase = {  
     apiKey: "AIzaSyDSt-ls4ETYEzIOh1ZGEuo7387PIJp_8E8",  
     authDomain: "jh-lab-5417a.firebaseapp.com",  
     projectId: "jh-lab-5417a",  
     storageBucket: "jh-lab-5417a.appspot.com",  
     messagingSenderId: "544462390526",  
     appId: "1:544462390526:web:9cce3bcbd66c7db395d1af"  
   }; 

export const publicVapidKey ='BDKO3UXoRr6T_NK52lAvw4fMsYiw6o6ldvUuN1dIJZutJ91wudxhz6_Efl6QiWiM5KNnAaSEpkpuN4pCEaJHIhM';

Values for the firebase constant are the same as those in the previously created “firebase- messaging-sw.js” file.

The other exported constant, publicVapidKey is the “Voluntary Application Server Identification” and represent the credentials of your Web application. This value needs to generate in the Google Firebase console. To do so, go to “Project settings” >> “Cloud messaging” tab and scroll down to Web configuration. Press “Generate key pair” button to generate key value and copy/paste its value from Firebase console to the firebase.constants.ts file.

Now the configuration part of the work is done, and it is finally time to write some code.

5.2. Angular: model/service/components …

To begin with we will need some model classes, one that will encapsulate Joke data, and the other to store the result returned by previously created FcmRegistrationController subscription management methods.

For that we will use the following two classes:

export interface IJoke {
    id?: number;
    joke?: string;
    seq?: number;
    time?: string;
}

export class Joke implements IJoke {
    constructor(public id?: number, public joke?: string, public seq?: number, public time?: string) {}
}

(src/main/webapp/app/firebase/joke.model.ts)

export interface ITopicSubscriptionResult {
    topic?: string;
    success?: boolean;
    errorCode?: string;
}
  
export class TopicSubscriptionResult implements ITopicSubscriptionResult {
    constructor(public topic?: string, public success?: boolean, public errorCode?: string) {}
}

(src/main/webapp/app/firebase/topic-subscription-result.model.ts)

The next step is to implement FcmService, a service that will handle all Firebase messaging related tasks.

Start by creating the file “src/main/webapp/app/firebase/fcm.service.ts” with the following contentn:

import { Injectable } from '@angular/core';
import { AngularFireMessaging } from '@angular/fire/messaging';
import { AlertService } from 'app/core/util/alert.service';
import { Observable, Subject } from 'rxjs';

import { HttpClient } from '@angular/common/http';
import { SERVER_API_URL } from 'app/app.constants';
import { IJoke, Joke } from 'app/firebase/joke.model';
import { publicVapidKey } from 'app/firebase/firebase.constants';
import { ITopicSubscriptionResult } from 'app/firebase/topic-subscription-result.model';

 
@Injectable({ providedIn: 'root' })
export class FcmService {
  public resourceUrl = SERVER_API_URL + 'api/fcm/registration';
  fcmToken!: string | null;

  // storage for Joke messages
  public jokes = new Subject<IJoke>();

  constructor(
    protected http: HttpClient,
    protected angularFireMessaging: AngularFireMessaging,
    protected alertService: AlertService
  ) {
    this.angularFireMessaging.usePublicVapidKey(publicVapidKey).then(() => {
      this.getFcmToken();
    });
  }
 
  getFcmToken(): void {
    // 1. do we already have a token ...
    if (this.fcmToken != null) {
      // eslint-disable-next-line no-console
      console.log('Token already acquired! Using the existing one: token=', this.fcmToken);
      return;
    }
    // 2 if not ask user for permision and get tokn for further use ...
    this.angularFireMessaging.requestToken.subscribe(
      token => {
        this.alertService.addAlert({ type: 'success', message: 'fcm.permission-granted' });
        this.fcmToken = token;
      },
      err => {
        this.alertService.addAlert({ type: 'warning', message: 'fcm.permission-failed' });
        // eslint-disable-next-line no-console
        console.error('Unable to get permission to notify.', err);
      }
    );
  }
}

In the constructor method of this class we are doing two things:

1. Asking user for permission to allow FCM notifications
2. Requesting an FCM token, one we will later use to subscribe user to jokes topic, and store it in local variable fcmToken.

All of this is done by the requestToken() method of the AngularFIreMessaging:

this.angularFireMessaging.requestToken.subscribe(
 token => {
 …
 },
 err => {
 …
 }
 );

For more details see the official docs for the Angular Fire Messaging library.


Also as can be seen from the snippet above, FcmService has the jokes property which is of type RxJs Subject (a special type of Observable).

// storage for Joke messages
public jokes = new Subject<IJoke>();

We will use this property , to store received FCM joke messages, and multi cast them to any Angular component interested in them. In our case, the component we will use to display received Joke messages will be the HomeComponent located in “src/main/webapp/app/home/” folder.

Open the home.component.html file by adding the following lines bellow the <span class="hipster img-fluid rounded"></span>.

<div class="row">
  <div class="col-md-4">
    <span class="hipster img-fluid rounded"></span>
    <br/>
    <div>
      <p class="font-weight-italics">{{latestJoke.joke}}</p>
      <p class="font-weight-light">{{latestJoke.time}}</p>
    </div>
    <br/>
    <button type="button" class="btn btn-primary" (click)="subscribeToJokes()">Subscribe</button>
    <button type="button" class="btn btn-secondary" (click)="unsubscribeFromJokes()">Unsubscribe</button>
  </div>

This has added the placeholders for joke content and its timestamp, and two buttons that can be used to subscribe/unsubscribe from jokes topic on Firebase cloud messaging.

Now, open home.component.ts and modify it by changing its constructor() and ngOnInit() methods and by adding the:

1. latestJoke property
2. subscribeToJokes() and unsubscribeFromJokes() methods as shown in the code snippet below:

...
import { FcmService } from 'app/firebase/fcm.service'
import { Joke } from 'app/firebase/joke.model';

@Component({
  selector: 'jhi-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss'],
})
export class HomeComponent implements OnInit, OnDestroy {
  account: Account | null = null;
  authSubscription?: Subscription;

  latestJoke  = new Joke(-1, "Latest Chuck joke... (comming soon)", 0, undefined);

  constructor(private accountService: AccountService, 
      private router: Router, 
      private fcmService: FcmService
  ) {}

  ngOnInit(): void {
    this.authSubscription = this.accountService.getAuthenticationState().subscribe(account => (this.account = account));
    this.fcmService.jokes.subscribe(joke => {
      this.latestJoke = joke;
    });
  } 

  // --- FCM methods -----------------------------------------------------------

  subscribeToJokes(): void {
    this.fcmService.subscribeToJokeTopic();
  }

  unsubscribeFromJokes(): void {
    this.fcmService.unsubscribeFromJokeTopic();
  }
...

Finally, what remains to be done is to add the implementation for subscribeToJokeTopic() and unsubscribeFromJokeTopic() methods to the end of FcmService.

Code snippet bellow contains these final changes:

subscribeToJokeTopic(): void {
    this.http.post<ITopicSubscriptionResult>(`${this.resourceUrl}`, this.fcmToken, { observe: 'body' }).subscribe(
      (result: ITopicSubscriptionResult) => {
        if (result.success) {
          // eslint-disable-next-line no-console
          console.log('--> Subscribed to topic: ', result.topic);
          this.alertService.addAlert({ type: 'success', message: 'fcm.topic.subscribe-success' });
        } else {
          // eslint-disable-next-line no-console
          console.error('Failed to subscribe to Topic.', result.topic, result.errorCode);
          this.alertService.addAlert({ type: 'warning', message: 'fcm.topic.subscribe-failed' });
        }
      }
    );
  }

  unsubscribeFromJokeTopic(): void {
    this.http.put<ITopicSubscriptionResult>(`${this.resourceUrl}`, this.fcmToken, { observe: 'body' }).subscribe(
      (result: ITopicSubscriptionResult) => {
        if (result.success) {
          // eslint-disable-next-line no-console
          console.log('--> Unubscribed from topic: ', result.topic);
          this.alertService.addAlert({ type: 'success', message: 'fcm.topic.unsubscribe-success' });
        } else {
          // eslint-disable-next-line no-console
          console.error('Failed to unsubscribe from Topic.', result.topic, result.errorCode);
          this.alertService.addAlert({ type: 'warning', message: 'fcm.topic.unsubscribe-failed' });
        }
      }
    );
}

If you now start spring backend rest-api by issuing the following command:

./gradlew

followed by (in the new terminal window)

npm start

a new browser tab will be opened and you will be asked for permission to accept notifications from Firebase Cloud messaging, as shown in the following image.

Allow the browser to show notifications for site http://localshot:9000 and press “Subscribe” button. Voila! The next time Chuck Norris Joke is published to FCM topic by spring rest-api it will be updated on homepage.

Once you had enough of Chuck Norris quotes of wisdom, simply unsubscribe from topic by pressing “Unsubsribe” button.

7. Conclusion

Web client implementation as described in this blog post is far from production ready, however it still has all of the major building blocks necessary to integrate firebase cloud messaging: request user for permission to receive notification, subscribe/unsubscribe to/from topic and handle received message. As such I hope it will be of help to anyone planning to integrate this Google service in his applicaiton(s).

Happy coding!
Full source code of this demo application is available at GitHub Jhfcm repository

Additional Resources

Next

Blog

Testing Spring Data JPA using ScalaTest

Company

Our people really love it here

How it all started

Est. in 2014., gathering eight employees with eyes set on the future. No matter how set they were, they couldn’t predict the success and extent of growth that would ensue. Today there are more than 100 of us, and people are here to stay.

Stability in unstable times

The turmoil of 2020 caused great inconvenience for people all over the world. However, this did not affect our business. Quite the opposite — we not only kept all jobs and salaries intact, but we also grew in size. And we keep expanding. 

Contact

We’d love to hear from you