A Step-by-Step Tutorial of Building a Simple Peer-to-Peer WebSocket App – Part 4

In Part 1 we took a look at the completed application, then reviewed the starting code in Part 2. In Part 3, we created a WebSocket connection. Now let’s start sending and receiving messages.

If you haven’t followed the previous steps, you can start from Task 6 by pointing your browser to the Task 5 state of the tutorial: https://jsfiddle.net/kaazing/5jth8h12/.

Task 6: Handling incoming messages

Let’s set up our application to handle incoming messages. In this step, we’ll add two JavaScript code snippets to our app. The first is a function that will handle the incoming messages. We’ll call it handleTopicMessage(). Let’s leave the function empty for now.

var handleTopicMessage = function(message) {
};

Next, let’s register the the handleTopicMessage() function to receive a notification when there are incoming messages arriving on our topic:

topicConsumer.setMessageListener(handleTopicMessage);

Here’s the entire JavaScript file. Look for Task 6a and Task 6b in the comments to find the newly added code snippets.

// Variables you can change
//
var WEBSOCKET_URL = "wss://sandbox.kaazing.net/jms";
var TOPIC_NAME = "/topic/myTopic";
var IN_DEBUG_MODE = true;
var DEBUG_TO_SCREEN = true;

// WebSocket and JMS variables
//
var connection;
var session;
var wsUrl;

// Variable for log messages
//
var screenMsg = "";

// JSFiddle-specific variables
//
var runningOnJSFiddle = (window.location.hostname === "fiddle.jshell.net");

// Used for development and debugging. All logging can be turned
// off by modifying this function.
//
var consoleLog = function(text) {
    if (IN_DEBUG_MODE) {
       if (runningOnJSFiddle || DEBUG_TO_SCREEN) {
           // Logging to the screen
           screenMsg = screenMsg + text + "<br>";
           $("#logMsgs").html(screenMsg);
       } else {
        // Logging to the browser console
        console.log(text);
       }
    }
};

var handleException = function(e) {
 consoleLog("EXCEPTION: " + e);
};
// *** Task 6a ***
var handleTopicMessage = function(message) {
};
// *** Task 6a ***
// *** Task 2 ***
var sliderChange = function(sliderValue) {
 consoleLog("Slider changed: " + sliderValue);
};
// *** Task 2 ***
// Connecting...
//
var doConnect = function() {
    // Connect to JMS, create a session and start it.
    //
    var jmsConnectionFactory = new JmsConnectionFactory(WEBSOCKET_URL);
    try {
        var connectionFuture = jmsConnectionFactory.createConnection('', '', function() {
            if (!connectionFuture.exception) {
               try {
                   connection = connectionFuture.getValue();
                   connection.setExceptionListener(handleException);

                   consoleLog("Connected to " + WEBSOCKET_URL);
                   session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                   
                   // *** Task 3 ***
                   // Creating topic
                   var myTopic = session.createTopic(TOPIC_NAME);
                   consoleLog("Topic created...");
                   // *** Task 3 ***
                   // *** Task 4 ***
                   // Creating topic Producer
                   topicProducer = session.createProducer(myTopic);
                   consoleLog("Topic producer created...");
                   // *** Task 4 *** 
                   // *** Task 5 ***
                   // Creating topic Consumer
                   topicConsumer = session.createConsumer(myTopic);
                   consoleLog("Topic consumer created...");
                   // *** Task 5 ***
                   // *** Task 6b ***
                   topicConsumer.setMessageListener(handleTopicMessage);
                   // *** Task 6b *** 
                   connection.start(function() {
                        // Put any callback logic here.
                        //
                        consoleLog("JMS session created");
                   });
             } catch (e) {
                 handleException(e);
             }
         } else {
             handleException(connectionFuture.exception);
         }
     });
 } catch (e) {
     handleException(e);
 }
};

To see and run the updated code in JSFiddle, point your browser to Task 6 state: https://jsfiddle.net/kaazing/gxgtt7pk/.

Task 7: Sending messages

Next, we’ll create a function that will send messages. To test this function, we’ll send a “Hello world…” message.

Create a function, called doSend(). The function takes a parameter (with string value), and it invokes the send() function on the producer. The send() function takes six parameters. The last parameter is a callback that gets invoked after the message has been sent successfully. In our case it’s an anonymous function that we leave empty for now.

var doSend = function(message) {
    topicProducer.send(null, message, DeliveryMode.NON_PERSISTENT, 3, 1, function() {
        
    });
    consoleLog("Message sent: " + message.getText());
};

Now, we’re ready to send a message. Create a text message, containing the “Hello world…” string, then send it.

doSend(session.createTextMessage("Hello world..."));

Run the modified app, and notice the log message indicating that our test message was sent.

Connected to wss://sandbox.kaazing.net/jms
Topic created...
Topic producer created...
Topic consumer created...
JMS session created
Message sent: Hello world...

Here’s the entire JavaScript file. Look for Task 7a and 7b in the comments to find the newly added snippets.

// Variables you can change
//
var WEBSOCKET_URL = "wss://sandbox.kaazing.net/jms";
var TOPIC_NAME = "/topic/myTopic";
var IN_DEBUG_MODE = true;
var DEBUG_TO_SCREEN = true;

// WebSocket and JMS variables
//
var connection;
var session;
var wsUrl;

// Variable for log messages
//
var screenMsg = "";

// JSFiddle-specific variables
//
var runningOnJSFiddle = (window.location.hostname === "fiddle.jshell.net");

// Used for development and debugging. All logging can be turned
// off by modifying this function.
//
var consoleLog = function(text) {
    if (IN_DEBUG_MODE) {
       if (runningOnJSFiddle || DEBUG_TO_SCREEN) {
           // Logging to the screen
            screenMsg = screenMsg + text + "<br>";
           $("#logMsgs").html(screenMsg);
       } else {
        // Logging to the browser console
        console.log(text);
       }
    }
};

var handleException = function(e) {
 consoleLog("EXCEPTION: " + e);
};
// *** Task 6a ***
var handleTopicMessage = function(message) {
};
// *** Task 6a ***
// *** Task 2 ***
var sliderChange = function(sliderValue) {
 consoleLog("Slider changed: " + sliderValue);
};
// *** Task 2 ***
// *** Task 7a ***
// Send a message to the topic.
//
var doSend = function(message) {
 topicProducer.send(null, message, DeliveryMode.NON_PERSISTENT, 3, 1, function() {

 });
 consoleLog("Message sent: " + message.getText());
};
// *** Task 7a *** 
// Connecting...
//
var doConnect = function() {
    // Connect to JMS, create a session and start it.
    //
    var jmsConnectionFactory = new JmsConnectionFactory(WEBSOCKET_URL);
    try {
        var connectionFuture = jmsConnectionFactory.createConnection('', '', function() {
            if (!connectionFuture.exception) {
               try {
                   connection = connectionFuture.getValue();
                   connection.setExceptionListener(handleException);

                   consoleLog("Connected to " + WEBSOCKET_URL);
                   session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                   
                   // *** Task 3 ***
                   // Creating topic
                   var myTopic = session.createTopic(TOPIC_NAME);
                   consoleLog("Topic created...");
                   // *** Task 3 ***
                   // *** Task 4 ***
                   // Creating topic Producer
                   topicProducer = session.createProducer(myTopic);
                   consoleLog("Topic producer created...");
                   // *** Task 4 *** 
                   // *** Task 5 ***
                   // Creating topic Consumer
                   topicConsumer = session.createConsumer(myTopic);
                   consoleLog("Topic consumer created...");
                   // *** Task 5 ***
                   // *** Task 6b ***
                   topicConsumer.setMessageListener(handleTopicMessage);
                   // *** Task 6b *** 
                   connection.start(function() {
                        // Put any callback logic here.
                        //
                        consoleLog("JMS session created");
                        // *** Task 7b ***
                        doSend(session.createTextMessage("Hello world..."));
                        // *** Task 7b *** 
                   });
             } catch (e) {
                 handleException(e);
             }
         } else {
             handleException(connectionFuture.exception);
         }
     });
 } catch (e) {
     handleException(e);
 }
};
To see and run the updated code in JSFiddle, point your browser to the Task 7 state:https://jsfiddle.net/kaazing/afL9tv6z/.

Task 8: Sending the slider’s position as it changes

In this task, we’ll send a message every time the slider’s position changes. As the message arrives with the new value, the slider will adjust to that value. This will be the first time we’ll see the peer-to-peer communication working in our app. To make this happen, let’s add a call to our sliderChange() function that sends a message whenever the slider’s value changes.

var sliderChange = function(sliderValue) {
    consoleLog("Slider changed: " + sliderValue);
    // *** Task 8a ***
    doSend(session.createTextMessage(sliderValue));
    // *** Task 8a ***
};

Next, we’ll adjust the slider to reflect the value received in the message.

var handleTopicMessage = function(message) {
    // *** Task 8b ***
    $("#slider").val(message.getText());
    // *** Task 8b ***
};

To test this, open two browser windows side-by-side, and point them to the same URL: https://jsfiddle.net/kaazing/eeoedsve/.

Change the slider’s position in one window; the slider moves in the other one as well. Here’s the entire JavaScript file. Look for Task 8a and 8b in the comments to find the newly added code.

// Variables you can change
//
var WEBSOCKET_URL = "wss://sandbox.kaazing.net/jms";
var TOPIC_NAME = "/topic/myTopic";
var IN_DEBUG_MODE = true;
var DEBUG_TO_SCREEN = true;

// WebSocket and JMS variables
//
var connection;
var session;
var wsUrl;

// Variable for log messages
//
var screenMsg = "";

// JSFiddle-specific variables
//
var runningOnJSFiddle = (window.location.hostname === "fiddle.jshell.net");

// Used for development and debugging. All logging can be turned
// off by modifying this function.
//
var consoleLog = function(text) {
    if (IN_DEBUG_MODE) {
       if (runningOnJSFiddle || DEBUG_TO_SCREEN) {
           // Logging to the screen
           screenMsg = screenMsg + text + "<br>";
           $("#logMsgs").html(screenMsg);
       } else {
        // Logging to the browser console
        console.log(text);
       }
    }
};

var handleException = function(e) {
 consoleLog("EXCEPTION: " + e);
};
// *** Task 6a ***
var handleTopicMessage = function(message) {
 // *** Task 8b ***
 $("#slider").val(message.getText());
 // *** Task 8b ***
};
// *** Task 6a ***
// *** Task 2 ***
var sliderChange = function(sliderValue) {
 consoleLog("Slider changed: " + sliderValue);
 // *** Task 8a ***
 doSend(session.createTextMessage(sliderValue));
 // *** Task 8a *** 
};
// *** Task 2 ***
// *** Task 7a ***
// Send a message to the topic.
//
var doSend = function(message) {
 topicProducer.send(null, message, DeliveryMode.NON_PERSISTENT, 3, 1, function() {

 });
 consoleLog("Message sent: " + message.getText());
};
// *** Task 7a *** 
// Connecting...
//
var doConnect = function() {
    // Connect to JMS, create a session and start it.
    //
    var jmsConnectionFactory = new JmsConnectionFactory(WEBSOCKET_URL);
    try {
        var connectionFuture = jmsConnectionFactory.createConnection('', '', function() {
            if (!connectionFuture.exception) {
               try {
                   connection = connectionFuture.getValue();
                   connection.setExceptionListener(handleException);

                   consoleLog("Connected to " + WEBSOCKET_URL);
                   session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                   
                   // *** Task 3 ***
                   // Creating topic
                   var myTopic = session.createTopic(TOPIC_NAME);
                   consoleLog("Topic created...");
                   // *** Task 3 ***
                   // *** Task 4 ***
                   // Creating topic Producer
                   topicProducer = session.createProducer(myTopic);
                   consoleLog("Topic producer created...");
                   // *** Task 4 *** 
                   // *** Task 5 ***
                   // Creating topic Consumer
                   topicConsumer = session.createConsumer(myTopic);
                   consoleLog("Topic consumer created...");
                   // *** Task 5 ***
                   // *** Task 6b ***
                   topicConsumer.setMessageListener(handleTopicMessage);
                   // *** Task 6b *** 
                   connection.start(function() {
                        // Put any callback logic here.
                        //
                        consoleLog("JMS session created");
                        // *** Task 7b ***
                        doSend(session.createTextMessage("Hello world..."));
                        // *** Task 7b *** 
                   });
             } catch (e) {
                 handleException(e);
             }
         } else {
             handleException(connectionFuture.exception);
         }
     });
 } catch (e) {
     handleException(e);
 }
};</pre>
<pre>

The video showcasing the completed application gives you an idea how to do this testing. To see the actual demo, you can fast forward to 1:00.
Note: You haven’t completed the application yet, so you won’t see the images. You should, however, see the sliders synchronized across the browsers.

Note: You may notice that, when you move the slider quickly, you experience a somewhat jittery behavior. This is caused by the fact that the messages sent by the browser are received by the same browser. If you move the slider before the messages are received, our logic sets the slider to the value indicated by the received message.

In Part 5 we’ll address this issue. Specifically, you’ll see how to prevent “hearing an echo of our own voice,” where the client that receives the messages is the same client that sent them.