Tutorial 2: Sending real-time events

>Tutorial: sending events with APIDEO
>Level: beginner
  • English
  • Français

In this second tutorial, we will learn how to send events and messages from one browser to another in real time, using Apideo. We will use this events sending capability to implement a simple chat application.

The goal of this tutorial is to develop 1 web page. This web page will contain a simple textbox, and a text area, like any chat application. Anyone typing in the textbox will send its text to all the other browsers that are on the same page.

We assume that you already have a basic understanding of Apideo. Particularly, we assume that you know how to connect to Apideo, and how to connect to a room. If you haven't read anything about Apideo, we strongly advise you to read the getting started tutorial first.

What are events?

In Apideo, an event is a message that is sent from one browser to other browsers that are in the same room. An event is sent from one room, and can only be sent to a browser that has joined the same room.

However, being in a room in not enough to receive an event. You also need to explicitly listen to that event. In order to listen to some kind of events, each event has a "type".

The first step is therefore to join a room and send events:

ApideoRoom.sendEvent(message, eventType, supressEcho)

message is the message to be sent. It can be a string, a number or any kind of JSON object...

eventType is the type of the event sent. This is this type we must listen to if we want to receive this event. Please note that a type is a string. Also, avoid putting dots (.) in the type unless you know what you are doing. Please see Events, advanced topic if you want more details about events.

supressEcho is a boolean parameter (true or false). If set to true, the browser that is sending the message will not receive the event, even if it is listening explicitly to it. By default, suppressEcho is set to false.

Sending events won't do anything if we don't listen to them. We listen to events through the ApideoRoom.registerListener function.

ApideoRoom.registerListener(listenerFunction, eventType)

listenerFunction is a function to be executed when a message is received.

eventType is the eventtype we are listening to.

Developing a simple chat application

With the capability to send event, developing chat applications becomes really easy.

Let's develop a basic chat application. Let's start with the skeleton of the application.

Template for chat.html
<html>
    <head>
        <script src="http://api.apideo.com/apideo.js"></script>
        <script type="text/javascript">
            function init() {
                myRoomFactory = Apideo.connect("112233445566778899")
                myRoom = myRoomFactory.joinRoom("mychatroom")
            }
        </script>				
    </head>
    <body onload="init()">
        <textarea id="chat_box" rows="20" cols="40" readonly="readonly">
        </textarea><br />
        <input id="chat_message" type="text" size="32"
                onkeypress="if(event.keyCode == 13) sendChat()" />
        <input type="button" value="Send" id="chat_send" onclick="sendChat()" />
    </body>
</html>

The template for the chat application contains nothing that should surprise you. The <body> tag contains a <textarea> that will receive the text we typed. It also contains an input text field and a send button. When we press enter in the text field, or when the button is clicked, the sendChat() function is called. This function does not yet exist, so let's create it!

function sendChat() {
	var msgTxtBox = document.getElementById("chat_message")
	myRoom.sendEvent({text: msgTxtBox.value, date: new Date()}, "chatMessages");
	msgTxtBox.value = ""
}

This function retrieves the message typed into the text box. It uses the sendEvent function to send the message. In this tutorial, we are sending a JSON object with the message and the current date. We will display both in the text area. The message type is "chatMessages" so we will have to listen to those message. We will do that with the code below:

//This line has to be placed in the init function
myRoom.registerListener(onChatReceive, "chatMessages")
 
function onChatReceive(msg, senderId, triggeredListener) {
	var element = document.getElementById("chat_box")
	element.value += msg.date + " : " + msg.text + "\n"
	element.scrollTop = element.scrollHeight
}

With the first line of code, we are registering the listener for messages whose type is "chatMessages". Each message received will trigger the function onChatReceive. When the trigger executes, the function is always called with 3 parameters:

msg is the message that has been sent, as we can expect.

senderId is the unique Id of the browser that sent the message. We will learn more about those id in the next tutorial. For now, let's just ignore that parameter.

triggeredListener is usually equal to the type of the message sent. This is actually slightly more complicated if you are taking advantage of the hierarchical organization of listeners. Have a look at the Events, advanced topic if you want more details about those.

The function does simply use the message received to add content to the text area. The last line of the onChatReceive function is putting the scroll bar at the bottom of the text area, so that the user will always see the last message.

That's it! We have our chat application! Was it not easy?

Below is the full code for the application.

chat.html
<html>
    <head>
        <script src="http://api.apideo.com/apideo.js"></script>
        <script type="text/javascript">
            function init() {
                myRoomFactory = Apideo.connect("112233445566778899")
                myRoom = myRoomFactory.joinRoom("mychatroom")
                myRoom.registerListener(onChatReceive, "chatMessages")
            }
 
            function sendChat() {
                var msgTxtBox = document.getElementById("chat_message")
                myRoom.sendEvent({text: msgTxtBox.value, date: new Date()},
                    "chatMessages")
                msgTxtBox.value = ""
            }
 
            function onChatReceive(msg, senderId, triggeredListener) {
                var element = document.getElementById("chat_box")
                element.value += msg.date + " : " + msg.text + "\n"
                element.scrollTop = element.scrollHeight
            }
        </script>				
    </head>
    <body onload="init()">
        <textarea id="chat_box" rows="20" cols="40" readonly="readonly">
        </textarea><br />
        <input id="chat_message" type="text" size="32"
            onkeypress="if(event.keyCode == 13) sendChat()" />
        <input type="button" value="Send" id="chat_send"
            onclick="sendChat()" />
    </body>
</html>

A note about security

At this early point in developing applications with Apideo, it is important to start talking about security, since security must be thought at the beginning of each project.

You must never forget that the Javascript code is executed on a browser, not on the Apideo server. Therefore, a malicious user could tweak its browser to execute something that you did not expected. Therefore, a malicious user could send unexpected messages. When developing application that are going to be executed by numerous users on Internet, make sure that you never rely on the client. The security section of this manual will help you with setting up from a basic security to advanced security with tokens.

In this section, we will just talk about one of the most important aspects of the security: avoiding script injection.

Let's take a basic sample, by making our application insecure. For instance, you decide that you would prefer writing in a <div> that in a <textarea>. Therefore, you rewrite the onChatReceive function this way:

<div id="chat_box"></div>
 
function onChatReceive(msg, senderId, triggeredListener) {
	var element = document.getElementById("chat_box")
	element.innerHTML += msg.date + " : " + msg.text + "<br/>"
	element.scrollTop = element.scrollHeight
}

Seems perfectly logical, does it? We just replaced the call to element.value with element.innerHTML. Well, by doing this, we opened a huge security hole in our chat application.

Just imagine that a malicious user sends the other users connected to the chat a message with some HTML and script embeded in it. For instance, what if he sends something like this:

msg="<script>alert('F**k you!')</script>"
or
msg="<img src='somepictureyoudontwanttosee.jpg' />"

This are only a few samples of HTML or Javascript injection. These samples are mostly harmless, but do not think that Javascript injection is always an "harmless" attack. You would be surprised to see what an attacker can do with only a few lines of Javascript: identity theft, illegal transfers via your bank account if you are connected to your bank website, etc...

The goal of this tutorial is not to list all the things that can go wrong if a code injection is performed, but how to prevent those. If you want to learn more about what the consequences of a Javascript injection can be, you can google "Javascript XSS" or "Javascript CSRF".

Luckily, preventing code injection is pretty easy. You just have to remember that every piece of data that comes from an event could be compromised by an attacker. Therefore, you will have to protect any data that comes from an event before displaying it.

By replacing the onChatReceive function with this, we solve the problem. Any < will be replace with &lt;. Therefore, no tag will execute. They will be displayed instead.

<div id="chat_box"></div>
 
function onChatReceive(msg, senderId, triggeredListener) {
	var element = document.getElementById("chat_box")
        var fullMsg = msg.date + " : " + msg.text + "<br/>"
        var protectedMsg = fullMsg.replace(/</g, "&lt;")
	element.innerHTML += protectedMsg
	element.scrollTop = element.scrollHeight
}

Protecting from your Javascript code from injection is easy, but you have to do it systematically.

Where do we go from here?

Our chat application is really quite simple so far. It can be improved in a lot of different ways. Usually, chat applications like IRC require a user to provide a nickname. When the nickname is provided, the user enters the room and sees a list of all other users. When a user comes in or when a user goes out, the list is dynamically changed. Using events, we could easily add a nickname to the list when a user enters the room. However, it is more difficult to remove a user automatically. Indeed, we are not sure that the user's browser will send a message. What if the Internet connection is lost? What if the user closes its browser? For all those reasons, events alone are not enough. We need to be able track users and connections to room. And the good news is that the topic of the next tutorial is... user management!