SFS2X Docs / ExamplesJS / buddy-messenger
» Buddy Messenger
» Overview
The Buddy Messenger example demonstrates the client-side capabilities of the SmartFoxServer 2X Buddy List API.
Using the Buddy List API, developers can add an instant messenger-like interface to any application, allowing users to see the status of friends (the so-called buddies) in their contact list and communicate with them regardless of the SmartFoxServer Room they are connected to. In fact, in order to optimize the bandwidth usage, in SmartFoxServer messaging and user presence notifications are mostly limited to the users in the same Room. Using the buddy list system, this limit can be overridden in order to offer an improved user experience.
In this example users can add new buddies simply by entering their name, remove them, block them (to stop receiving status notifications and instant messages) and send them messages (clicking the Chat button opens a new tab in the chats panel).
Also, each user can change his/her details and state as buddies to other users thanks to the very flexible Buddy Variable objects featured by SmartFoxServer. Similarly to User Variables and Room Variables, Buddy Variables are stored on the server and broadcasted to the other clients. In particular, when set or updated, a Buddy Variable is broadcasted to all the users referenced in the buddy list of its owner.
Some Buddy Variables are predefined and reserved to store specific informations:
- the online/offline status of the user (a user can be connected to the server but offline in the buddy list system);
- the personal state of the user with respect to its presence in the buddy list system (available, busy, etc. — the list can be customized in the AdminTool's Zone Configurator);
- the user nickname, which can differ from the login username.
Buddy Variables can be online or offline. Offline variables can be accessed even if the user is not connected to SmartFoxServer (for example to store buddy details which are independent from the current session), while the online variables require the user presence. In our example the user's age is saved as an offline Buddy Variable, while his/her current mood as an online one.
>> DOWNLOAD the source files <<
» Running the example
In order to run the example, follow these steps:
- make sure your SmartFoxServer 2X installation contains the BasicExamples Zone definition;
- start SmartFoxServer 2X (v2.13 or later is required);
- make sure WS protocol is enabled in SFS2X configuration (read Server setup in the intro);
- open the /index.html file in a browser (Firefox recommended).
» Code highlights
In order to speed up development and provide a refined user interface, this example makes use of the jQWidgets UI Toolkit. The widgets are distributed for learning purposes only and you are not entitled to use them in a commercial product. If needed, please visit the jQWidgets website to acquire a proper license.
The main index.html file links both the SmartFoxServer API library and the external JavaScript file containing the application logic (see the <head> tag).
As discussed in previous tutorials, the controls and widgets defining the user interface are configured right after the page is loaded. The <div id="main"> tag contains the two sub-divs representing the login and application views. In order to switch from one view to the other, the setView() method is called: depending on the connection state, it updates the UI showing the panels and enabling or disabling buttons. The view is then actually switched using the switchView() function.
» Event listeners setup
The init() method is responsible of instantiating and configuring the usual SmartFox class you should know quite well by now, if you went through the previous tutorials. The method is called by the <body> tag's onLoad event.
function init() { trace("Application started"); // Create configuration object var config = {}; config.host = "127.0.0.1"; config.port = 8080; config.zone = "BasicExamples"; config.debug = true; // Create SmartFox client instance sfs = new SFS2X.SmartFox(config); // Add event listeners sfs.addEventListener(SFS2X.SFSEvent.CONNECTION, onConnection, this); sfs.addEventListener(SFS2X.SFSEvent.CONNECTION_LOST, onConnectionLost, this); sfs.addEventListener(SFS2X.SFSEvent.LOGIN_ERROR, onLoginError, this); sfs.addEventListener(SFS2X.SFSEvent.LOGIN, onLogin, this); sfs.addEventListener(SFS2X.SFSEvent.LOGOUT, onLogout, this); sfs.addEventListener(SFS2X.SFSBuddyEvent.BUDDY_LIST_INIT, onBuddyListInit, this); sfs.addEventListener(SFS2X.SFSBuddyEvent.BUDDY_ERROR, onBuddyError, this); sfs.addEventListener(SFS2X.SFSBuddyEvent.BUDDY_ONLINE_STATE_CHANGE, onBuddyListUpdate, this); sfs.addEventListener(SFS2X.SFSBuddyEvent.BUDDY_VARIABLES_UPDATE, onBuddyListUpdate, this); sfs.addEventListener(SFS2X.SFSBuddyEvent.BUDDY_ADD, onBuddyListUpdate, this); sfs.addEventListener(SFS2X.SFSBuddyEvent.BUDDY_REMOVE, onBuddyListUpdate, this); sfs.addEventListener(SFS2X.SFSBuddyEvent.BUDDY_BLOCK, onBuddyListUpdate, this); sfs.addEventListener(SFS2X.SFSBuddyEvent.BUDDY_MESSAGE, onBuddyMessage, this); // Show LOGIN view setView("login", true); }
It is important to note that most of the buddy-related events are caught by the same onBuddyListUpdate() listener, which causes the list of buddies in the UI to be recreated from scratch, as discussed later on. This has been done on purpose, to simplify the code. A more refined approach would update the specific list item to which each event refers, also discarding those events referred to the current user when needed.
By design instead, all errors related to the Buddy List system are notified through the BUDDY_ERROR event, which causes the onBuddyError() method to be executed.
» Connection and login
The connection and login process is straightforward and follows the usual sequence described in details in previous tutorials: 1) when the Connect button is clicked the SmartFox.connect() method is called; 2) as soon as the connection is established successfully — see the onConnection() listener —, the Login button and username textfield are enabled; 3) on button click, a LoginRequest is sent and the onLogin() listener is called immediately after:
function onLogin(event) { trace("Login successful!" + "\n\tZone: " + event.zone + "\n\tUser: " + event.user + "\n\tData: " + event.data); // Set user name // NOTE: this always a good practice, in case a custom login procedure on the server side modified the username $("#usernameIn").val(event.user.name); $("#usernameLb").html(event.user.name); $("#usernameLb2").html(event.user.name); // Initialize the Buddy List system sfs.send(new SFS2X.InitBuddyListRequest()); // Switch to messenger view setView("messenger", true); }
Unlike the Game Lobby example, in which an initial Room (the lobby) is joined upon login, in this example we don't make use of Rooms at all. The reason is that we want to highlight that the Buddy List system in SFS2X is independent from the Room management, providing a third way for users to communicate, other than public and private messages.
» Buddy List initialization
In order to make the Buddy List system available on the client, the InitBuddyListRequest must be sent after the successful login. This causes the onBuddyListInit() event handler to be called:
function onBuddyListInit(event) { // Populate list of buddies onBuddyListUpdate(event); // SET CURRENT USER DETAILS AS BUDDY // Nick $("#nicknameIn").val(sfs.buddyManager.getMyNickName()); // States var states = sfs.buddyManager.getBuddyStates(); $("#stateDd").jqxDropDownList({source:states}); var state = (sfs.buddyManager.getMyState() != null ? sfs.buddyManager.getMyState() : ""); if (states.indexOf(state) > -1) $("#stateDd").jqxDropDownList({selectedIndex:states.indexOf(state)}); // Online $('#isOnlineCb').jqxCheckBox({checked:sfs.buddyManager.getMyOnlineState()}); // Buddy variables var age = sfs.buddyManager.getMyVariable(BUDDYVAR_AGE); $("#ageIn").jqxNumberInput("val", ((age != null && !age.isNull) ? age.value : 0)); var mood = sfs.buddyManager.getMyVariable(BUDDYVAR_MOOD); $("#moodIn").val((mood != null && !mood.isNull) ? mood.value : ""); isBuddyListInited = true; }
This listener populates the list of buddies in the UI using the same onBuddyListUpdate() method used to handle the changes and discussed later on. Then it synchronizes the user interface (the "My details" panel) retrieving the current user info (nickname, state, age, etc) from the BuddyManager object, which can be accessed from the main SmartFox instance.
All user details are saved as Buddy Variables. A Buddy Variable is similar to the User and Room Variables discussed in other tutorials. The only difference is the logic by which they get propagated to other users. While Room and User Variables are usually broadcast to all clients in the same Room, Buddy Variables updates are sent to all users who have the owner of the variable in their Buddy Lists.
Buddy Variables are particularly useful to store custom user data that must be "visible" to the buddies only, such as a profile, a ranking, a status message, etc, similarly to this example.
The Buddy Variables can also be set as "offline". Offline Buddy Variables are persistent values which are accessible to all users who have the owner in their Buddy List, whether that buddy is online or not.
In particular, the state (available, away, etc) and nickname make use of reserved offline variables: they are predefined variables backed by dedicated methods provided by the BuddyManager class to access their values, as shown in the code snippet above — getMyState() and getMyNickname() respectively.
Note that the state is based on a predefined list configured in the AdminTool's Zone Configurator, so we also have to populate the dropdown in the UI after retrieving all available states by means of the BuddyManager.getBuddyStates() method.
The age and mood are standard Buddy Variables instead; the first one is an offline variable and the second one is a normal variable. They are both accessed in the same way, using the BuddyManager.getMyVariable() method.
We'll discuss how to set all user details mentioned here in the next paragraph, but before moving one we still have to mention the "online state". In fact a user can be online or offline in the Buddy List system. This setting is persistent, so after the Buddy List initialization we have to check what the value was the last time we logged in (just like for the nickname, the state and the age — mood is not persistent, even if it is not yet clear why... we will get to it in a moment): this can be done using the BuddyManager.getMyOnlineState() method, which uses, under the hood, another reserved Buddy Variable.
» Setting the user profile
In this example our user profile is made of a nickname, the state, the age and the mood. Additionally a checkbox allows us to be set as active (online) in the Buddy List system. Only an active user can interact with his buddies, so let's start from it: as soon the checkbox is clicked, the onIsOnlineChange() handler sends the GoOnlineRequest to the server and updates the UI according to the checkbox value. The request causes the BUDDY_ONLINE_STATE_CHANGE event to be fired on all the clients having us in their Buddy List; this in turn executes the onBuddyListUpdate() method.
As discussed before, all the profile info is set in four Buddy Variables, two of which are reserved. All the variables are set when the respective UI buttons are clicked (or the state dropdown selection is changed) using the SetBuddyVariablesRequest like in the following example:
function onSetNicknameBtClick(event) { var nickBV = new SFS2X.SFSBuddyVariable(SFS2X.ReservedBuddyVariables.BV_NICKNAME, $("#nicknameIn").val()); sfs.send(new SFS2X.SetBuddyVariablesRequest([nickBV])); }
The names of the reserved variables are available in the ReservedBuddyVariables enum, specifically as the BV_NICKNAME and BV_STATE constants.
The names of the variables for age and mood instead are set in a couple of global constants, BUDDYVAR_AGE and BUDDYVAR_MOOD respectively:
var BUDDYVAR_AGE = "$age"; var BUDDYVAR_MOOD = "mood";
You should note that the age variable name is prefixed with a dollar sign ($): this is the naming convention used by SmartFoxServer to identify which variables should be treated as "offline", so they should be made persistent. Simple and effective.
Changing one or more variables causes the BUDDY_VARIABLES_UPDATE event to be fired on all the clients having us in their Buddy List; this in turn executes the onBuddyListUpdate() method.
» Buddy List updates
Other than by the two requests discussed in the previous paragraph, the onBuddyListUpdate() handler is triggered by the add, remove and block actions. Of course the first two add a new buddy to the list and remove and existing one from it; the third one prevents a user from sending us Buddy Messages.
These actions are executed when the respective buttons in the UI (below the list of buddies) are clicked. The click handlers send the AddBuddyRequest, RemoveBuddyRequest and BlockBuddyRequest respectively:
function onBlockBuddyBtClick() { var selectedBuddy = $("#buddyList").jqxListBox("getSelectedItem"); var isBlocked = selectedBuddy.originalItem.buddyObj.isBlocked; sfs.send(new SFS2X.BlockBuddyRequest(selectedBuddy.title, !isBlocked)); } function onRemoveBuddyBtClick() { var selectedBuddy = $("#buddyList").jqxListBox("getSelectedItem"); sfs.send(new SFS2X.RemoveBuddyRequest(selectedBuddy.title)); // Remove chat tab if opened var tabIndex = $("#chatTabs ul li").index($("li#tab_" + selectedBuddy.title)); if (tabIndex > -1) $("#chatTabs").jqxTabs("removeAt", tabIndex); } function onAddBuddyBtClick() { if ($("#buddyNameIn").val() != "") { sfs.send(new SFS2X.AddBuddyRequest($("#buddyNameIn").val())); $("#buddyNameIn").val(""); } }
The requests make the server fire the BUDDY_ADD, BUDDY_REMOVE and BUDDY_BLOCK events respectively, all handled by the onBuddyListUpdate() method as discussed at the very beginning of this section. This method is quite long, so we are not showing it here; please locate it in the main.js file.
At the very beginning of the method, a reference to the currently selected buddy in the list is saved before clearing the selection.
var selectedBuddy = $("#buddyList").jqxListBox("getSelectedItem"); clearBuddyListSelection();
The list of our buddies is then retrieved, looping through it. For each buddy the default values for his profile details are set, then the actual values (if set) are retrieved from the Buddy Variables. The name to display in the UI is provided by the getBuddyDisplayName() method, which returns the nickname if set, otherwise the user name. In particular the blocked and online flags and the state value are used to display the proper icon.
After all the details are set, the item object is created and added to the source array for the UI widget.
var buddies = sfs.buddyManager.getBuddyList(); for (var b in buddies) { var buddy = buddies[b]; var name = ""; var age = 0; var mood = ""; var icon = ""; var iconTip = ""; // Name/nickname name = getBuddyDisplayName(buddy); // Age var ageBV = buddy.getVariable(BUDDYVAR_AGE); if (ageBV != null && !ageBV.isNull) age = ageBV.value; // Mood var moodBV = buddy.getVariable(BUDDYVAR_MOOD); if (moodBV != null && !moodBV.isNull) mood = moodBV.value; // Icon if (buddy.isBlocked) { icon = "blocked"; iconTip = "Blocked"; } else { if (!buddy.isOnline) { icon = "offline"; iconTip = "Offline"; } else { var state = buddy.state; if (state == null) state = "Available"; icon = state.toLowerCase(); iconTip = state; } } var item = {}; item.html = "<div><p class='itemTitle'><img src='images/icon_" + icon + ".png' title='" + iconTip + "' width='16' height='16'/><strong>" + name + "</strong>" + (age > 0 ? " (age " + age + ")" : "") + "</p>"; if (mood != "") item.html += "<p class='itemSub'><em>" + mood + "</em></p>"; item.html += "</div>"; item.title = buddy.name; item.buddyObj = buddy; source.push(item); ... }
In the last part of the loop we check if a chat panel is open with the current buddy and in case we update it based on the refreshed buddy details (state icon, name), disabling the controls if the buddy was blocked or went offline.
In the very last part of the method the list of buddies is passed to the UI widget and the previously selected buddy (if any) is selected again.
» Messaging with buddies
When a buddy is selected in the list and the Chat button is clicked, the addChatTab() method is called. This shows the chats panel (if not yet visible) and creates a new tab for the selected buddy. It is now possible to type a message and send it. When the Send button is pressed, the click listener instantiates the BuddyMessageRequest and sends it to the server:
function onSendBtClick(event) { var buddyName = event.currentTarget.name; var buddy = sfs.buddyManager.getBuddyByName(buddyName); var msgInputId = "#chatMsgIn_" + buddy.name; if ($(msgInputId).val() != "") { // Add a custom parameter containing the recipient name, // so that we are able to write messages in the proper chat tab var params = new SFS2X.SFSObject(); params.putUtfString("recipient", buddy.name); var isSent = sfs.send(new SFS2X.BuddyMessageRequest($(msgInputId).val(), buddy, params)); if (isSent) $(msgInputId).val(""); } }
It is important to note that other than the message's text and the object representing the target buddy, we are also attaching an optional, custom SFSObject to the message, containing the name of the recipient. This is a sort of duplicate (because we are already passing the buddy object), but the reason why we use the optional parameter will become clear in a second.
Whenever a Buddy Message is received, this is notified through the BUDDY_MESSAGE event to both the sender and the recipient, so that they can keep the messages sequence synchronized. The onBuddyMessage() listener is in charge of identifying the sender and the recipient, open the chat tab id needed and display the message:
function onBuddyMessage(event) { var isItMe = event.isItMe; var sender = event.buddy; var message = event.message; var buddy; if (isItMe) { var customParams = event.data; // SFSObject var buddyName = customParams.get("recipient"); buddy = sfs.buddyManager.getBuddyByName(buddyName); } else buddy = sender; if (buddy != null) { // Create tab if not existing addChatTab(buddy, false); // Display message var chatAreaId = "#chatAreaPn_" + buddy.name; writeToBuddyChatArea(chatAreaId, "<b>" + (isItMe ? "You" : getBuddyDisplayName(buddy)) + " said:</b><br/>" + message); } }
The default parameters of the event include the SFSBuddy object representing the sender, a flag indicating if we are the sender and the message itself. In case we are the sender, normally we wouldn't have a way to know who the supposed recipient was. This is needed to put the message in the right chat tab for example. So in the code above we retrieve the buddy we are chatting with, directly using the event.buddy property or indirectly by means of the event.data object (which is the custom SFSObject sent by... the sender of course).
To see other advanced uses of SmartFoxServer, including complete turn-based and realtime games, you can now move onwards to the next examples.
NOTE
You should also read the comments to methods and properties in the example source code for additional informations.
» More resources
You can learn more about the SmartFoxServer basics by consulting the following resources: