Frontend implementation of the real-time client

In our implementation, we will create a RealTimeClient class to wrap a SockJS object to provide channel-based communication. We will bind the instance of this client to Vue.prototype.$rt so that all of the Vue components can access the real-time client via the instance's $rt property. The following is an overview of the RealTimeClient class.

Let's have a look at the frontend/src/real-time-client.js file:

import Vue from 'vue'
import SockJS from 'sockjs-client'

class RealTimeClient {
constructor () {
...
}
init (serverUrl, token) {
...
}
logout () {
...
}
connect () {
...
}
subscribe (channel, handler) {
...
}
unsubscribe (channel, handler) {
...
}
}

export default new RealTimeClient()

As you can see, besides the constructor, it provides the init(), logout(), connect(), subscribe(), and unsubscribe() APIs. The instance of RealTimeClient is the default export of real-time-client.js. In this way, there will only be one RealTimeClient instance in the application.

Inside the constructor, the properties of RealTimeClient are initialized, as shown here:

constructor () {
this.serverUrl = null
this.token = null
this.socket = null
this.authenticated = false
this.loggedOut = false
this.$bus = new Vue()
this.subscribeQueue = {
/* channel: [handler1, handler2] */
}
this.unsubscribeQueue = {
/* channel: [handler1, handler2] */
}
}

serverUrl is the URL of the WebSocket server and it is set to null by default. token is the real-time token that the client uses to perform authentication on the server side. It is a JWT token string. We will talk about JWTs in detail in the next section. The socket property is an instance of SockJS, which will be created inside the connect() method. The authenticated property is used to mark whether or not the client has been authenticated on the server side. The loggedOut property is used to indicate whether or not the user has logged out and the real-time client has been closed because of them logging out.

The $bus property is an instance of Vue. It serves as the internal event bus of the real-time client. subscribeQueue and unsubscribeQueue hold the subscribing and unsubscribing actions, respectively, before the real-time client has established a connection with the server. Once the connection is established, the real-time client will perform all of the subscribing and unsubscribing actions in the queue, and empty the queue. We will see how this can make using the real-time client a little bit easier.

The init() method is where we initialize the connection. It looks like the following:

init (serverUrl, token) {
if (this.authenticated) {
console.warn('[RealTimeClient] WS connection already authenticated.')
return
}
console.log('[RealTimeClient] Initializing')
this.serverUrl = serverUrl
this.token = token
this.connect()
}

As you can see, we put a guard at the beginning of the method to make sure that the connection won't be initialized again once the real-time client has been authenticated, that is, the connection has been established. At the end of this method, we call the connect() method to perform the actual connection initialization.

The logout() method, which looks like the following, is used to clear the state of the real-time client by resetting its properties to the initial values and then closing the socket:

logout () {
console.log('[RealTimeClient] Logging out')
this.subscribeQueue = {}
this.unsubscribeQueue = {}
this.authenticated = false
this.loggedOut = true
this.socket && this.socket.close()
}

The connect() method is where the connection between the real-time client and the server is established. It looks like the following:

connect () {
console.log('[RealTimeClient] Connecting to ' + this.serverUrl)
this.socket = new SockJS(this.serverUrl + '?token=' + this.token)
this.socket.onopen = () => {
this.authenticated = true
this._onConnected()
}
this.socket.onmessage = (event) => {
this._onMessageReceived(event)
}
this.socket.onerror = (error) => {
this._onSocketError(error)
}
this.socket.onclose = (event) => {
this._onClosed(event)
}
}

As you can see, we establish the connection by creating an instance of SockJS with serverUrl and token as a query parameters. Then, we assign event handlers to the socket instance. We will not go into the details of every event handler here. You can find the implementation in the commit history.

The subscribe() method is the API that the clients of the real-time client will use to subscribe to the channel that they are interested in. It looks like the following:

subscribe (channel, handler) {
if (!this._isConnected()) {
this._addToSubscribeQueue(channel, handler)
return
}
const message = {
action: 'subscribe',
channel
}
this._send(message)
this.$bus.$on(this._channelEvent(channel), handler)
console.log('[RealTimeClient] Subscribed to channel ' + channel)
}

As you can see, at the beginning of the method, we check whether the real-time client is connected to the server. If it isn't, we will add the 'subscribe' action to the queue for processing once the connection is established or restored. Then, we call the internal _send() method to send the subscribe message to the server so that the server will add this client to the subscribers of the channel, and then we bind the channel message handler to the internal event bus so that, when there is a message from the server received, we will notify all of the handlers of this channel by using the internal event bus $emit() method. In this way, we can subscribe to a channel inside BoardPage.vue, as follows:

this.$rt.subscribe('/board/' + this.board.id, this.onRealTimeUpdated)

The unsubscribe() method is the opposite of the subscribe() method, as you can see here:

unsubscribe (channel, handler) {
// Already logged out, no need to unsubscribe
if (this.loggedOut) {
return
}

if (!this._isConnected()) {
this._addToUnsubscribeQueue(channel, handler)
return
}
const message = {
action: 'unsubscribe',
channel
}
this._send(message)
this.$bus.$off(this._channelEvent(channel), handler)
console.log('[RealTimeClient] Unsubscribed from channel ' + channel)
}

As you can see, at the beginning of the method, we check whether the real-time client has already logged out or not. If it has already logged out, we don't need to perform an unsubscribe action anymore because, on the server side, once a real-time client is logged out, all of the subscribe actions will be cleared. When the real-time client isn't logged out, we will check whether it is connected. For an offline real-time client, the unsubscribe actions will be added to the queue for later processing. To unsubscribe from a channel, the real-time client will also send a message to the server with the action's value as 'unsubscribe'. After that, we will remove the handler from the internal event bus.

As you can see, the implementation of the real-time client is quite straightforward. One thing that we haven't mentioned here is where we will invoke the init() method of the real-time client. Since there will be only one real-time client instance in the application, the perfect place to initialize the connection is inside the created() life cycle hook of App.vue.

Another question is—when should we do the initialization? As you can see, the init() method requires two parameters—serverUrl and token. We will pass these two values to the client from the server. A good place to put these two values is the response of the getMyData API (/api/me). Once the response is received, we will use the global event bus that we created in frontend/src/event-bus.js to notify App.vue.

The following is the change we made to App.vue:

<script>
export default {
name: 'App',
created () {
this.$bus.$on('myDataFetched', myData => {
// Initializing the real time connection
this.$rt.init(myData.settings.realTimeServerUrl,
myData.user.token)

})
}
}
</script>

As you can see, inside the created() hook of App.vue, we listen to the myDataFetched event and use the two values to initialize the real-time connection.

For other details of the frontend implementation of the real-time client, you can find them in the commit history.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.41.205