The support representative uses the Live Support Lobby as a holding area for customers that are waiting in queue for support. The lobby's sole purpose is to keep a list of clients that need support. Representatives connect to the lobby to connect to the customer they wish to help. When connected, they open a unique Live Support Session, which you will build in the next section. All customers and representatives use only one instance of the lobby. The Server-Side ActionScriptThe Live Support Lobby uses only one Flash Communication Server UI component, the ConnectionLight. On the server, this application is named LiveSupportLobby. To get ready, create a folder in your FlashCom applications directory called LiveSupportLobby. Then create an empty file called main.asc. This, as always, will be the starting point for creating the application. Next, load the components.asc file to load all the base code for using the components : load( "components.asc" ); You need to consider what the application will require when it starts. The application.onAppStart method is always called before any users are connected. Just as its name implies, application.onAppStart is called when the application instance is started or run for the first time. This is where you place ActionScript that needs to run before accepting connections. There are four tasks this application needs to do:
Initializing the Application (onAppStart)You can Create an ActionScript object that will be used to look up a client: application.onAppStart = function(){ /* Holds all connect clients, key: username value: Server-side client Object */ this.supportClients = new Object(); Later, you will add clients to this object in the application.onConnect method. To store representative connections, create a second new ActionScript object: /* Holds all connect reps, key: username value: Server-side client Object */ this.supportReps = new Object(); Now you have a way to keep track of both customers and representatives, but you will want a way to share the customer list with other applications. You could create a SharedObject that holds the supportClients object, but that object was created for a way to easily look up customers. In the future, your application may require maintaining the order in which customers connect. To build for this feature, the application will use a separate array for the shared user list: /* The list of users saved in a SharedObject */ this.userList = new Array(); And of course, create the SharedObject: /* The SharedObject that holds the list of users */ this.users_so = SharedObject.get( "supportUsers" ); this.users_so.setProperty( "userList", this.userList ); This SharedObject is named supportUsers and is stored in the variable users_so. The property, userList, is then set to match the userList array you just created. Notice that you do not check to see if userList already existed. The application will never start with users already in queue anyway, because it was never there to accept them. The last item on the list is to validate the representative's connection request. To do this, create another ActionScript Object: /* Object that is used for rep validation */ this.rep_lookup = new Object(); This will be used later to look up a representative by username and match that to their password. Go ahead and add a couple of users below the previous ActionScript: /* The representatives */ this.rep_lookup["greg"] = "burch"; this.rep_lookup["kevin"] = "towes"; this.rep_lookup["jessica"] = "smith"; Note In a real-world scenario, you may want a more robust and secure validation system. Using Flash Remoting MX, you could connect to a ColdFusion web service that checks the username and password rather than having a hard-coded object, as was in this example. My username is greg and my password is burch. That is all you need in your application.onAppStart to get going! Listing 16.1 is the complete ActionScript. Comments used earlier have been removed to save space. Listing 16.1 Full listing of SSAS onAppStartapplication.onAppStart = function(){ this.supportClients = new Object(); this.supportReps = new Object(); this.userList = new Array(); this.users_so = SharedObject.get( "supportUsers" ); this.users_so.setProperty( "userList", this.userList ); this.rep_lookup = new Object(); this.rep_lookup["greg"] = "burch"; this.rep_lookup["kevin"] = "towes"; this.rep_lookup["jessica"] = "smith"; } Handling Connection Requests (onConnect)Next, the application needs an application.onConnect method. This method is called whenever a user connects. This is where the server will decide if the user that is trying to connect is a customer, a representative, or an invalid user. You will be building your own application.onConnect validation scheme. On the server-side, there are two possibilities when a user connects:
The first scenario would mean the user connecting is a customer. The second means a representative is trying to connect. Customers require only a username in our application because there is no reason to validate their identity. The representative, however, needs to be validated because they will be using administrative controls, such as connecting to a user to help them, opening browser windows on the client's machine, and ending Live Support Sessions. A simple if statement determines which type the user is. First check if they sent a password. If they didn't, that means they are a customer: application.onConnect = function( newClient, username, pass ) { if( pass == null ){ trace( "Client Connected- "+username ); }else if... If the password is not null (meaning the user supplied a password), this ActionScript challenge calls a separate method, called isRep(), which you will build later. The purpose of isRep() is to check the username and password of a representative. The code looks like this: else if( this.isRep( username, pass ) ){ trace( "Rep Connected- "+username ); }else... If the user supplied a username and password that did not match, you have found an invalid user: }else{ trace( "Invalid User" ); } Now you will need to assemble each case with the appropriate ActionScript operations. In the first case, the user is a customer. That means you have to put that customer's name in both of the ActionScript objects associated with a customer. You also need to update the users_so SharedObject: if( pass == null ){ trace( "Client Connected- "+username ); /* Store this client in the supportClientsObject */ this.supportClients[username] = newClient; /* Put it in the SharedObjects userList array */ this.userList.push(username); /* Update the SharedObject */ this.users_so.setProperty( "userList", this.userList ); }else if... When a representative's connection has been accepted, store a variable in the client object that identifies the user as a representative. Later, when the user disconnects, you will use this variable to determine which list to remove the user from. Lastly, add the representive's name to the representatives ActionScript object: }else if( this.isRep( username, pass ) ){ trace( "Rep Connected- "+username ); /* Store that the connected user is a rep, for later use. */ newClient.isRep = true; /* Store this rep in the supportReps object */ this.supportReps[username] = newClient; }else... The invalid user is handled in the last case by rejecting the connection to the Flash Communication Server. To reject connections, use the application.rejectConnection method. Rejecting a connection invokes the NetConnection's onStatus function on the Flash player, sending it the Information object, NetConnection.connect.rejected . The script looks like this: else{ trace( "Invalid User" ); /* Incorrect password */ application.rejectConnection( newClient ); /* Nothing more todo, return */ return false; } Now that you have identified the user, let's move on. Declare the username within the Flash Communication Server's UI component framework so the username is recognized by any UI components used in the application: /** * Inform the UI Component Framework of the username * this name will be used by all UI components. */ gFrameworkFC.getClientGlobals( newClient ).username = username; Finally, but most importantly, you have to accept the connection. To do this, use the method application.acceptConnection : /* accept the connection */ this.acceptConnection( newClient ); } Listing 16.2 shows you the complete code, with comments removed to save space. Listing 16.2 Full listing of SSAS onConnectapplication.onConnect = function( newClient, username, pass ) { if( pass == null ){ trace( "Client Connected- "+username ); this.supportClients[username] = newClient; this.userList.push(username); this.users_so.setProperty( "userList", this.userList ); }else if( this.isRep( username, pass ) ){ trace( "Rep Connected- "+username ); newClient.isRep = true; this.supportReps[username] = newClient; }else{ application.rejectConnection( newClient ); return false; } gFrameworkFC.getClientGlobals( newClient ).username = username; this.acceptConnection( newClient ); } Handling User Disconnections (onDisconnect)Now that the application can handle connection requests, it must also handle client disconnections. There are two user disconnect cases to handle: a representative and a customer disconnect. So, first check the isRep property set in the Client object. If isRep is true, remove the username from the array holding all of the representatives by retrieving the username from the global storage: application.onDisconnect = function( oldClient ){ /* Check to see if the client that disconnected is a rep */ if( oldClient.isRep ){ var username = gFrameworkFC.getClientGlobals(oldClient ).username; /* remove it from the supportReps object */ delete application.supportReps[username]; }else... A separate method will be used to clean up customers because there are a few more data bindings. A new function, removeUser , will be called receiving one parameter, the Client object: }els1e{ /** * Call the removeUser method passing it the client object. We create this * method to clean up several things that were stored about the Customer. */ this.removeUser(oldClient); } } Next, create two helper methods that you have already referenced, isRep and removeUser . The isRep function is very simple. It is used to validate representatives. It will validate the parameter's username and password against the rep_lookup object created in the application.onAppStart method. Here's how it works: application.isRep = function( username, pass ){ /* If they don't have a password, they can't be a rep */ if( pass==null pass=="" ) return false; Now, just return the comparison between the value in the username property of rep_lookup and the given password: /* Return the comparison of the password */ return this.rep_lookup[username]==pass; } The method removeUser is called from the onDisconnect event and is used to clear a user (customer or representative) from the application. A customer's username is stored within the userList array as well as the supportClients object. To remove it from the object, you can simply reference it by the username. For the list, however, it's a bit trickier. You must first loop through the list until you find a match for the username, and then use the Array.splice() method with the array to delete the item: application.removeUser=function( oldClient ){ var username = gFrameworkFC.getClientGlobals( oldClient ).username; /* Remove the user from the supportClients object */ delete this.supportClients[username]; /* Find the user in the userList array and remove it */ for( var i=0; i<this.userList.length; i++ ){ if( this.userList[i] == username ){ this.userList.splice( i ,1 ); this.users_so.setProperty( "userList", this.userList ); return true; } } } Listing 16.3 shows a comment-free listing of all the onDisconnect methods and the custom functions that are used to clean up after a user disconnects. Listing 16.3 Full listing of SSAS onAppStartapplication.onDisconnect = function(oldClient){ if(oldClient.isRep){ var username = gFrameworkFC.getClientGlobals( oldClient ).username; delete this.supportReps[username]; }else{ this.removeUser( oldClient ); } } application.isRep = function( username, pass ){ /* If they don't have a password, they can't be a rep */ if( pass==null pass=="" ) return false; /* Return the comparison of the password */ return this.rep_lookup[username]==pass; } application.removeUser=function( oldClient ){ var username = gFrameworkFC.getClientGlobals( oldClient ).username; /* Remove the user from the supportClients object */ delete this.supportClients[username]; /* Find the user in the userList array and remove it */ for( var i=0; i<this.userList.length; i++ ){ if( this.userList[i] == username ){ this.userList.splice( i ,1 ); this.users_so.setProperty( "userList", this.userList ); return true; } } } Create a Unique Application InstanceThe custom method connectionToClient is called from the Flash player to open a Live Support Session (application instance). This method is placed in the prototype of the client so it is available to each Flash player connected to the application. The connectToClient method requires two string parameters: rep and client: Client.prototype.connectToClient = function( rep, client ){... This method is responsible for creating application instances used to establish a secure communication channel between the representative and the customer. It informs the Flash player of both the customer and representative what application instance to connect to. The variable, instanceId , is incremented each time an instance is created. The variable is incremented to make sure that each Live Support Session is unique. To initialize an instanceId variable, add the following lines to the end of the application.onAppStart method you scripted earlier: /** * This is incremented for each support session and used * to generate instances of the applications. */ this.instanceId = 0; This function performs three specific tasks:
Listing 16.4 shows the code that accomplishes these tasks. Note The LiveSupport application will be created in the next section. Listing 16.4 Full listing of connectionToClientClient.prototype.connectToClient = function( rep, client ){ /** * We are going to launch a new instance of the LiveSupport application * so we need to increment the instanceId variable */ application.instanceId++; /* The URI that we are going to pass to the client */ var rStr = "rtmp:/LiveSupport/"+application.instanceId; /* Tell both the rep and the client to connect to the new URI */ application.supportReps[rep].call("startSupport", null, rStr); application.supportClients[client].call("startSupport", null, rStr); } Note I refer to application.instanceId and not this.instanceId because this ActionScript exists within the Client object, not the Application object where instanceId exists. The last two lines call the startSupport method for both users. The startSupport methods are defined on the client-side ActionScript and will handle the new URIs they have been assigned. That is it for the server-side code for the lobby. The lobby is the most complicated part of the entire support application because it uses a lot of custom ActionScript. The entire LiveSupport application is made up of the Flash Communication components so there is not much additional ActionScript required. The Client-Side ActionScriptThe client-side (Flash) ActionScript will handle both the Lobby and the Support Tool scenarios in one application. A customer will use a different Flash movie than a representative. You will begin by building the ActionScript needed for the user to connect and wait in the lobby. Then you will construct the user interfaces for the customer user and the representative. Exercise 16.1: Building the Client User Interface
Listing 16.5 shows the complete code with limited comments. Use the code in Listing 16.5 to confirm your ActionScrtipt. Listing 16.5 Full Flash ActionScript for Frame 1, ActionScript layernc = new NetConnection(); onLogin = function(){ username = loginField.text; nc.connect("rtmp:/LiveSupportLobby",username); connectionLight_mc.connect(nc); statusField.text="A Representitive Will be with you shortly. Thank you for your patience." loginField.selectable = false; loginField.background = true; loginField.backgroundColor = 0xCCCCCC; loginField.textColor = 0x999999; login_btn.setEnabled(false); } stop(); That does it for the lobby part of the application. Figure 16.2 shows the completed application. In the section "Live Support Session," you will be building upon this movie to handle live support sessions, as well. Next, you will create the Flash movie used by the representative user. Figure 16.2. The user has logged in and is waiting for a representative.
The Representative User InterfaceThe representative user interface consists of two interface screens: one for the representative to log in and one to select the user to provide support. Start by first creating a new Flash movie called Representative_Support_ App.fla. Open the movie in Flash MX, and then create three new layers as follows :
Now you're ready to start by building the Login interface. Exercise 16.2: Representative Login InterfaceFollow these steps to create the Representative Login interface:
Exercise 16.3: Representative Customer Selection InterfaceNow you are ready to create the user list screen.
The lobby part of the representative Flash movie is done. In the next section, you will create the Live Support Session that will build on this movie. Listing 16.6 shows a complete and comment-free version of the code for this exercise. Listing 16.6 Flash ActionScript listing for representative interfacenc = new NetConnection(); onLogin = function(){ username = this.loginField.text; password = this.passwordField.text; nc.connect("rtmp:/LiveSupportLobby",this.username,this.password); connectionLight_mc.connect(nc); supportUsers = SharedObject.getRemote("supportUsers",nc.uri); supportUsers.onSync = function(args){ user_lsb.removeAll(); for(var i in this.data["userList"]){ user_lsb.addItem(this.data["userList"][i]); } } supportUsers.connect(nc); gotoAndStop(2); } onConnectClient = function(){ var user = this.user_lsb.getSelectedItem().label; if(user!=null){ this.nc.call("connectToClient",null,this.username,user); } } stop(); |