{"id":366,"date":"2019-05-08T09:04:30","date_gmt":"2019-05-08T09:04:30","guid":{"rendered":"https:\/\/www.visionmate.se\/en\/?p=366"},"modified":"2019-05-10T13:00:40","modified_gmt":"2019-05-10T13:00:40","slug":"push-notifications-in-grails-and-react-js-based-web-projects","status":"publish","type":"post","link":"https:\/\/www.visionmate.se\/en\/2019\/05\/08\/push-notifications-in-grails-and-react-js-based-web-projects\/","title":{"rendered":"Push notifications in Grails and React.js based web projects"},"content":{"rendered":"<p>This article is more technical oriented and targeting other developers, not the final client. Usually, systems require communication from the server to the client. This is essential because some events occur on the server and a notification must be sent to the client. A simple use case is a chat, where users can exchange messages between them. We can use other technologies too, for example, the Message Queue system. Depending\u00a0on your needs you can choose the most suitable one.<\/p>\n<p><strong>Description<\/strong><\/p>\n<p>In this post we will discuss push messages using WebSocket. My goal is to describe the pedagogy behind the work, when it comes to delivery and production, we obviously have simpler routines at the company to accommodate each client order.<\/p>\n<p><strong>Goals\u00a0<\/strong><\/p>\n<p>To be able to implement push notifications in web applications<\/p>\n<p><strong>Requirements\u00a0<\/strong><\/p>\n<p>Grails 3.* (<strong>Dependencies<\/strong>: Grails Spring WebSocket, Grails Spring Security Rest)<\/p>\n<p>React.js (<strong>Dependencies<\/strong>: Sock.js client, Stomp.js)<\/p>\n<p><strong>1 &#8211; Server configuration<\/strong><\/p>\n<p>We need to configure our WebSocket message agent to allow our system to send and receive messages through secure channels.<\/p>\n<p>The following code (<em>configureClientInboundChannel <\/em>method) shows how to authenticate through WebSocket. We need to be sure that only an authorized user can connect, subscribe and receive messages from our server. Our system uses Spring Security Rest and, consequently, users are authenticated through the access token in each request.<\/p>\n<p>We also configure the endpoint in which our message agent will listen to subscription requests and messages (<em>registerStompEndpoints <\/em>method)<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">import grails.plugin.springsecurity.rest.token.storage.TokenStorageService\r\nimport org.springframework.beans.factory.annotation.Autowired\r\nimport org.springframework.context.annotation.Bean\r\nimport org.springframework.context.annotation.Configuration\r\nimport org.springframework.messaging.simp.SimpMessageType\r\nimport org.springframework.messaging.simp.config.ChannelRegistration\r\nimport org.springframework.messaging.simp.stomp.StompHeaderAccessor\r\nimport org.springframework.messaging.support.ChannelInterceptorAdapter\r\nimport org.springframework.messaging.support.MessageBuilder\r\nimport org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer\r\nimport org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker\r\nimport org.springframework.web.socket.config.annotation.StompEndpointRegistry\r\nimport org.springframework.messaging.Message\r\nimport org.springframework.messaging.MessageChannel\r\n \r\n\r\n@Configuration\r\n@EnableWebSocketMessageBroker\r\nclass DefaultWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {\r\n\r\n   public static final String AUTHORIZATION = 'Authorization'\r\n   public static final String BEARER = 'Bearer'\r\n\r\n   @Autowired\r\n   TokenStorageService tokenStorageService\r\n \r\n\r\n@Override\r\nvoid registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {\r\n   stompEndpointRegistry.addEndpoint(\"\/ws\")\r\n         .setAllowedOrigins(\"*\")\r\n         .withSockJS()\r\n         .setSessionCookieNeeded(false)\r\n}\r\n \r\n\r\nvoid configureClientInboundChannel(ChannelRegistration registration) {\r\n   registration.setInterceptors(new ChannelInterceptorAdapter() {\r\n      Message&lt;Object&gt; preSend(Message&lt;Object&gt; message,  MessageChannel channel) {\r\n         String token = null\r\n         Boolean access = false\r\n         StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message)\r\n         List tokens = accessor.getNativeHeader(AUTHORIZATION)\r\n         accessor.removeNativeHeader(AUTHORIZATION)\r\n\r\n         if(tokens?.size &gt; 0) {\r\n            token = tokens[0]\r\n         }\r\n\r\n         if (token?.startsWith(BEARER)) {\r\n            token = token.substring(BEARER.length()).trim()\r\n         }\r\n\r\n         switch (accessor.messageType){\r\n            case SimpMessageType.CONNECT: access = (tokenStorageService.loadUserByToken(token))?true:false\r\n               break\r\n            case SimpMessageType.SUBSCRIBE: access = (tokenStorageService.loadUserByToken(token))?true:false\r\n               break\r\n         }\r\n         accessor.setLeaveMutable(true)\r\n         return (access)?MessageBuilder.createMessage(message.payload, accessor.messageHeaders):null\r\n      }\r\n   })\r\n}<\/pre>\n<p><strong>2 &#8211;\u00a0Sending notifications<\/strong><\/p>\n<p>We use Grails Spring WebSocket and it will provide us with the necessary functionality ready to use according to the configuration.<\/p>\n<p>In our example, the messages will have a related action, in order to delete, update or create messages in the client. This is also possible if we create different <em>topics<\/em> for that purpose, but in this case it is not relevant.<\/p>\n<p><em>BrokerService<\/em> will be responsible for sending messages to a destination using brokerMesssagingService.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">class BrokerService {\r\n\r\n    def brokerMessagingTemplate\r\n    \/**\r\n     * Send message to destination\r\n     * @param destination\r\n     * @param message\r\n     *\/\r\n    void send(String destination, Map message) {\r\n        brokerMessagingTemplate.convertAndSend destination, (message as JSON) as String\r\n    }\r\n}<\/pre>\n<p><strong>3 &#8211;\u00a0Web client configuration<\/strong><\/p>\n<p>In the web client we use Sock.js and Stomp.js to subscribe to our messaging channel.<\/p>\n<p>To subscribe to a channel in the connection request, we send the header configuration with the user&#8217;s token as authorization.<\/p>\n<p>We set as parameter a callback function that will be executed after receiving a message from the server.<\/p>\n<p>The unsubscribe method allows you to delete a subscription, the user will not receive anymore push notifications afterwards.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">import {SERVER_URL} from \"..\/config\";\r\nimport headers from \"..\/security\/headers\";\r\nimport SockJS from 'sockjs-client';\r\nimport Stomp from 'stompjs';\r\n\r\n\/**\r\n * WebSockets support\r\n *\/\r\nconst WebSocket = (endpoint) =&gt; {\r\n\r\n    const socket = SockJS(`${SERVER_URL}\/${endpoint}`);\r\n    const client = Stomp.over(socket);\r\n\r\n    function subscribe(destination, callback) {\r\n        client.connect(headers(), function(frame) {\r\n            client.subscribe(destination, function(message){\r\n              callback(JSON.parse(message.body))\r\n              }, headers());\r\n           });\r\n        return client;\r\n    }\r\n\r\n    function unsubscribe(client){\r\n        for (const sub in client.subscriptions) {\r\n            if (client.subscriptions.hasOwnProperty(sub)) {\r\n                client.unsubscribe(sub);\r\n            }\r\n        }\r\n    }\r\n\r\n    return {\r\n        subscribe,\r\n        unsubscribe\r\n    }\r\n\r\n};\r\n\r\nexport default WebSocket;<\/pre>\n<p><strong>4 &#8211; Subscription and reception of notifications<\/strong><\/p>\n<p>We will place the subscription call on <em>componentDidMount<\/em> using a class based on React Component.<\/p>\n<p>We also need to implement the functionality that will be executed once we receive the notifications.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">import React from 'react';\r\nimport WebSocket from \"..\/..\/api\/WebSocket\";\r\nimport {ACTION_STATUS} from '..\/..\/constant';\r\nimport MessageList from \"..\/..\/components\/MessageList\";\r\nimport { notifyAddedMessage, notifyDeletedMessage,  notifyUpdatedMessage} from \"..\/..\/actions\/actions\";\r\n\r\n\r\n\/**\r\n * Real-time messages\r\n *\/\r\nclass MessagePanel extends React.Component {\r\n\r\n    componentDidMount() {\r\n        this.subscribe();\r\n\r\n    }\r\n\r\n    componentWillUnmount() {\r\n        if(this.state.client !== undefined)\r\n            WebSocket('ws').unsubscribe(this.state.client);\r\n    }\r\n\r\n    subscribe() {\r\n        const callback = (message) =&gt; {\r\n            switch (message.action){\r\n                case ACTION_STATUS.DELETED:\r\n                    this.props.notifyDeletedMessage(message.id);\r\n                    break;\r\n                case ACTION_STATUS.UPDATED:\r\n                    this.props.notifyUpdatedMessage(message);\r\n                    break;\r\n                default:\r\n                    this.props.notifyAddedMessage(message);\r\n            }\r\n        };\r\n        callback.bind(this);\r\n\r\n        let client;\r\n        if (user instanceof Object) {\r\n            client = WebSocket('ws').subscribe(\u2018\/topic\/messages\u2019, callback);\r\n            this.setState({client});\r\n        }\r\n    }\r\n\r\n    render() {\r\n        return (\r\n            &lt;MessageList\/&gt;\r\n        )\r\n    }\r\n}<\/pre>\n<p><strong>Conclusion<\/strong><\/p>\n<p>Push notifications make our system more attractive and useful, it also reduces server latency and improves user experience. In many cases, we can add it using WebSockets in an easy way and with good results.<\/p>\n<p><strong>References<\/strong><\/p>\n<p><a href=\"https:\/\/docs.spring.io\/spring\/docs\/5.0.0.BUILD-SNAPSHOT\/spring-framework-reference\/html\/websocket.html\" target=\"_blank\" rel=\"nofollow noopener\">https:\/\/docs.spring.io\/spring\/docs\/5.0.0.BUILD-SNAPSHOT\/spring-framework-reference\/html\/websocket.html<\/a><\/p>\n<p>&nbsp;<\/p>\n<p>\/<a href=\"https:\/\/www.visionmate.se\/about\/\">Carlos<\/a> Senior Developer at Visionmate<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article is more technical oriented and targeting other developers, not the final client. Usually,&#8230;<\/p>\n","protected":false},"author":1,"featured_media":367,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[7,24,4],"class_list":["post-366","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-grails","tag-pushnotifications","tag-react"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/posts\/366","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/comments?post=366"}],"version-history":[{"count":4,"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/posts\/366\/revisions"}],"predecessor-version":[{"id":371,"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/posts\/366\/revisions\/371"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/media\/367"}],"wp:attachment":[{"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/media?parent=366"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/categories?post=366"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.visionmate.se\/en\/wp-json\/wp\/v2\/tags?post=366"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}