Creating Room Modules with JavaScript

The following steps describe how to deploy an example JavaScript room module on Union Server. The module is a chat bot that sends "hello" and "goodbye" greetings to all users that join and leave a chat room. (See also Creating Room Modules with Java.)

Step 1: Write the module

Add the code below to a new file named "GreeterBot.js", and save that file in Union Server's /modules/ directory.

importClass(net.user1.union.core.event.RoomEvent);
importClass(net.user1.union.api.Client);

var moduleContext;
var wrapper;

// This method must be implemented by every room module script.
function init(ctx, wrap) {
  moduleContext = ctx;
  wrapper = wrap;

  // This is a script, so we have to register for
  // events through the room's wrapper.
  wrapper.addRoomEventListener(RoomEvent.ADD_CLIENT, "onRoomAddClient");
  wrapper.addRoomEventListener(RoomEvent.REMOVE_CLIENT, "onRoomRemoveClient");
}

function onRoomAddClient(evt) {
  var args = ["Welcome Guest" + evt.getClient().getClientID()];
  moduleContext.getRoom().sendMessage("CHAT_MESSAGE", args);
}

function onRoomRemoveClient(evt) {
  var args = ["Goodbye Guest" + evt.getClient().getClientID() + "."];
  moduleContext.getRoom().sendMessage("CHAT_MESSAGE", args);
}

// This method must be implemented by every room module script.
function shutdown() {
  wrapper.removeRoomEventListener(RoomEvent.ADD_CLIENT, "onRoomAddClient");
  wrapper.removeRoomEventListener(RoomEvent.REMOVE_CLIENT, "onRoomRemoveClient");
}

Step 2: Deploy the GreeterBot module

Room modules can be deployed at runtime via client-side code or at server-startup time via union.xml. In this example, we'll deploy the GreeterBot module using client-side code, first in ActionScript, then in JavaScript. In ActionScript, a room's modules are specified via the RoomManager's createRoom() method. For example,

var modules:RoomModules = new RoomModules();
modules.addModule("GreeterBot.js", ModuleType.SCRIPT);
chatRoom = reactor.getRoomManager().createRoom("chatRoom", null, null, modules);

The following code uses the preceding technique to deploy the GreeterBot module in a chat application for the room named "chatRoom".

package {
  import flash.display.Sprite;
  import flash.events.KeyboardEvent;
  import flash.text.TextField;
  import flash.text.TextFieldType;
  import flash.ui.Keyboard;

  import net.user1.reactor.IClient;
  import net.user1.reactor.Reactor;
  import net.user1.reactor.ReactorEvent;
  import net.user1.reactor.Room;
  import net.user1.reactor.RoomModuleType;
  import net.user1.reactor.RoomModules;

  public class UnionChatPart1 extends Sprite {
    // Union objects
    protected var reactor:Reactor;
    protected var chatRoom:Room;
    // User interface objects
    protected var incomingMessages:TextField;
    protected var outgoingMessages:TextField;

    public function UnionChatPart1 () {
      // Create the user interface
      buildUI();
      // Make the Reactor object
      reactor = new Reactor();
      // Run readyListener() when the connection is ready
      reactor.addEventListener(ReactorEvent.READY, readyListener);
      // Connect to the server
      reactor.connect("tryunion.com", 80);
    }

    // Method invoked when the connection is ready
    protected function readyListener (e:ReactorEvent):void {
      incomingMessages.appendText("Connected to Union\n");

      var modules:RoomModules = new RoomModules();
      modules.addModule("GreeterBot.js", RoomModuleType.SCRIPT);   

      chatRoom = reactor.getRoomManager().createRoom("chatRoom", null, null, modules);
      chatRoom.addMessageListener("CHAT_MESSAGE", chatMessageListener);
      chatRoom.join();
    }

    // Creates the user interface
    protected function buildUI ():void {
      incomingMessages = new TextField;
      incomingMessages.border = true;
      incomingMessages.background = true;
      incomingMessages.width = 399;
      incomingMessages.height = 200;

      outgoingMessages = new TextField;
      outgoingMessages.type = TextFieldType.INPUT;
      outgoingMessages.border = true;
      outgoingMessages.background = true;
      outgoingMessages.width = 399;
      outgoingMessages.height = 20;
      outgoingMessages.y = 210;
      outgoingMessages.addEventListener(KeyboardEvent.KEY_UP, keyUpListener);
      addChild(incomingMessages);
      addChild(outgoingMessages);
    }

    // Keyboard listener for outgoingMessages
    protected function keyUpListener (e:KeyboardEvent):void {
      if (e.keyCode == Keyboard.ENTER) {
        chatRoom.sendMessage("CHAT_MESSAGE",
                             true,
                             null,
                             outgoingMessages.text);
        outgoingMessages.text = "";
      }
    }

    // Method invoked when a chat message is received
    protected function chatMessageListener (fromClient:IClient,
                                            messageText:String):void {
      if (fromClient != null) {
        incomingMessages.appendText("Guest"
                                    + fromClient.getClientID()
                                    + " says: "
                                    + messageText + "\n");
      } else {
        incomingMessages.appendText("Union says: "
                                    + messageText + "\n");
      }
    }
  }
}

Here's the equivalent client-side chat code in JavaScript/HTML:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Union Chat for JavaScript, with GreeterBot</title>

<!--CSS-->
<style type="text/css">
#chatPane {
  border: inset 2px;
  height: 100px;
  width: 400px;
  overflow: auto;
  padding: 5px;
  margin-bottom: 5px
}
</style>

<!--Load the Orbiter JavaScript library (non-minified version). Use during development.-->
<script type="text/javascript" src="http://cdn.unioncloud.io/Orbiter_latest.js"></script>
<!--Load the Orbiter JavaScript library (minified version). Use for production.-->
<!--<script type="text/javascript" src="http://cdn.unioncloud.io/Orbiter_latest_min.js"></script>-->

<!--Chat code-->
<script type="text/javascript">
//==============================================================================
// VARIABLES
//==============================================================================
var orbiter;
var chatRoom;

//==============================================================================
// INITIALIZATION
//==============================================================================
function init () {
  // Create the Orbiter instance, used to connect to and communicate with Union,
  // then enable automatic reconnection (one attempt every 15 seconds)
  orbiter = new net.user1.orbiter.Orbiter();
  orbiter.getConnectionMonitor().setAutoReconnectFrequency(15000);
  orbiter.getLog().setLevel(net.user1.logger.Logger.DEBUG);

  // If required JavaScript capabilities are missing, abort
  if (!orbiter.getSystem().isJavaScriptCompatible()) {
    displayChatMessage("Your browser is not supported.");
    return;
  }

  // Register for Orbiter's connection events
  orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, readyListener, this);
  orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, closeListener, this);

  displayChatMessage("Connecting to Union...");

  // Connect to Union Server
  orbiter.connect("localhost", 9100);
}

//==============================================================================
// ORBITER EVENT LISTENERS
//==============================================================================
// Triggered when the connection is ready
function readyListener (e) {
  displayChatMessage("Connected.");
  displayChatMessage("Joining chat room...");

  // Deploy the GreeterBot module when creating the room
  var modules = new net.user1.orbiter.RoomModules();
  modules.addModule("GreeterBot.js", net.user1.orbiter.ModuleType.SCRIPT);
  chatRoom = orbiter.getRoomManager().createRoom("chatRoom", null, null, modules);
  chatRoom.addMessageListener("CHAT_MESSAGE", chatMessageListener);
  chatRoom.join();
}

// Triggered when the connection is closed
function closeListener (e) {
  displayChatMessage("Orbiter connection closed.");
}

//==============================================================================
// CHAT ROOM EVENT LISTENERS
//==============================================================================
// Triggered when the room is joined
function joinRoomListener (e) {
  displayChatMessage("Chat ready!");
  displayChatMessage("Number of people now chatting: " + chatRoom.getNumOccupants());
}

// Triggered when another client joins the chat room
function addOccupantListener (e) {
  if (chatRoom.getSyncState() != net.user1.orbiter.SynchronizationState.SYNCHRONIZING) {
    displayChatMessage("User" + e.getClientID() + " joined the chat."
                       + " People chatting: " + chatRoom.getNumOccupants());
    orbiter.getClientManager().getClient(e.getClientID()).sendMessage("CHAT_MESSAGE", "Private hi to you!");
  }
}

// Triggered when another client leaves the chat room
function removeOccupantListener (e) {
  displayChatMessage("User" + e.getClientID() + " left the chat."
                     + " People chatting: " + chatRoom.getNumOccupants());
}

//==============================================================================
// CHAT SENDING AND RECEIVING
//==============================================================================
// Sends a chat message to everyone in the chat room
function sendMessage () {
  var outgoing = document.getElementById("outgoing");
  if (outgoing.value.length > 0) {
    chatRoom.sendMessage("CHAT_MESSAGE", "true", null, outgoing.value);
    outgoing.value = "";
    // Focus text field again after submission (required for IE8 only)
    setTimeout(function () {outgoing.focus();}, 10);
  }
}

// Triggered when a chat message is received
function chatMessageListener (fromClient, message) {
  // When the server sends a message, the fromClient is null, so treat any
  // message whose fromClient is null as a message from GreeterBot.
  if (fromClient === null) {
    displayChatMessage("Union Server: " + message);
  } else {
    displayChatMessage("User" + fromClient.getClientID() + ": " + message);
  }
}

// Displays a single chat message
function displayChatMessage (message) {
  // Make the new chat message element
  var msg = document.createElement("span");
  msg.appendChild(document.createTextNode(message));
  msg.appendChild(document.createElement("br"));

  // Append the new message to the chat
  var chatPane = document.getElementById("chatPane");
  chatPane.appendChild(msg);

  // Trim the chat to 500 messages
  if (chatPane.childNodes.length > 500) {
    chatPane.removeChild(chatPane.firstChild);
  }
  chatPane.scrollTop = chatPane.scrollHeight;
}
</script>
</head>

<body onload="init()">
<!--Contains the incoming chat messages-->
<div id="chatPane"></div>

<!--The outgoing chat form-->
<div>
  <input type="text" id="outgoing" style="width:340px" onkeydown="if (event.keyCode == 13) sendMessage()"/>
  <input type="submit" value="Send" style="width:60px" onclick="sendMessage()"/>
</div>

</body>
</html>

Step 3 (Optional): Include additional JavaScript files

To load additional JavaScript files for use in a module, follow these steps:

  1. Place your additional script files in the "modules" directory.
  2. Add the following function to your module's main JavaScript file.
  3. function require(filename) {
        new javax.script.ScriptEngineManager()
            .getEngineByName("JavaScript")
            .eval(new java.io.FileReader(new java.io.File("modules/" + filename)));
    }
    
  4. For each file you wish to load, invoke require(), specifying the name of the file to load. For example,
    require("example.js");