DekGenius.com
[ Team LiB ] Previous Section Next Section

24.2 Creating the Server-Side Application

The server-side portion of the video chat/message center application is contained within a single ASC file, main.asc. Here are the steps necessary to create the server-side part of this application:

  1. Create a new FlashCom application named CommunicationCenterApp. You can do this by making a new directory with this name inside of the FlashCom server's applications directory.

  2. Create a new file inside of the CommunicationCenterApp directory. Name the file main.asc.

  3. Add the following code to main.asc:

    // The application.onConnect(  ) method is invoked each time a new client connects
    // to the application. The newClient parameter is a FlashCom-generated reference
    // to the new client. The username parameter is a value passed to the method by
    // the Flash client.
    application.onConnect = function (newClient, username, password) {
    
      newClient.username = username;
      newClient.password = password;
    
      // The acceptClient(  ) function accepts the client and assigns methods to the
      // client object according to the user type.
      acceptClient(newClient);
    };
    
    // The application.onDisconnect(  ) method is automatically invoked whenever a
    // client disconnects from the application.
    application.onDisconnect = function (disconnectClient) {
    
      // If the client who has disconnected was on a call with the administrator,
      // release that connection by setting the callingClient property to null. Also,
      // if the administrator is still online, invoke the incomingCallEnd(  ) method
      // in the administrator client to clean up things on that end.
      if (application.callingClient == disconnectClient) {
        application.callingClient = null;
        if (application.admin != undefined) {
          application.admin.call("incomingCallEnd", null);
        }
      }
    
      // If the client who has disconnected happens to be the administrator, take
      // care of the necessary loose ends.
      if (application.admin = disconnectClient) {
    
        // If the administrator was on a call, alert the calling client that the call
        // has ended.
        if (application.callingClient != null) {
          application.callingClient.call("adminEndCall", null);
        }
    
        // Set callingClient to null and delete the admin property since there are no
        // more active calls and no administrator logged in.
        application.callingClient = null;
        delete application.admin;
      }
    };
    
    // When the application first starts, this method is invoked automatically to
    // initialize necessary application properties.
    application.onAppStart = function (  ) {
    
      // The callingClient property is assigned a reference to the client object when
      // a client is on a call with the administrator.
      application.callingClient = null;
    
      // The messages shared object is a remote shared object (RSO) that contains all
      // the messages that have been left for the administrator.
      application.messagesSO = SharedObject.get("messages", true);
    
      // The messageNum property of the shared object is used to guarantee unique 
      // message numbers. If the property doesn't exist, initialize it to 0.
      if (application.messagesSO.getProperty("messageNum") == undefined) {
        application.messagesSO.setProperty("messageNum", 0);
      }
    };
    
    // The acceptClient(  ) function is invoked from 
    // the application.onConnect(  ) method.
    function acceptClient(newClient) {
    
      // Make sure a calling client doesn't try to log in with the username "admin".
      if (newClient.username == "admin" && newClient.password != "adminPass") {
        application.rejectConnection(newClient);
        return;
      }
    
      // Accept all other client connections.
      application.acceptConnection(newClient);
    
      // Set the methods available to the client objects.
      newClient.recordMessageInfo = recordMessageInfo;
      newClient.getMessageID = getMessageID;
      newClient.placeCall = placeCall;
      newClient.endCall = endCall;
    
      // If the client is the administrator client, 
      // then take care of a few other tasks.
      if (newClient.username == "admin") {
        // Set the admin property to a reference to the connecting client object.
        application.admin = newClient;
    
        // Set a few administrator-only client methods.
        newClient.acceptIncomingCall = acceptIncomingCall;
        newClient.sendToLeaveMessage = sendToLeaveMessage;
        newClient.removeMessage = removeMessage;
      }
    }
    
    // This function is a method assigned to the administrator client. When this
    // method is invoked, the administrator has accepted an incoming call, and the
    // acceptCall(  ) method on the calling client is invoked.
    function acceptIncomingCall (  ) {
      application.callingClient.call("acceptCall", null);
    }
    
    // This function is a method assigned to the administrator client. It is invoked
    // when the administrator does not answer an incoming call.
    function sendToLeaveMessage (  ) {
      application.callingClient.call("notAvailable", null);
      application.callingClient = null;
    }
    
    // The placeCall(  ) function is assigned to clients as a method. This method is
    // invoked when a client attempts to call the administrator.
    function placeCall (  ) {
    
      // If the administrator is logged in and if there is not already another call 
      // in place, invoke the onIncomingCall(  ) method on the administrator client to 
      // alert the user that there is an incoming call. Otherwise, alert the calling 
      // client that the administrator is not available for a call right now.
      if (application.admin != undefined && application.callingClient == null) {
        application.admin.call("onIncomingCall", null, this.username);
        application.callingClient = this;
      } else {
        this.call("notAvailable", null);
      }
    }
    
    // The endCall(  ) function is a method of all clients, 
    // and it ends the current call by invoking methods on 
    // both the calling client and the administrator client.
    function endCall (  ) {
      application.callingClient("adminEndCall", null);
      application.callingClient = null;
      application.admin.call("incomingCallEnd", null);
    }
    
    // The recordMessageInfo(  ) function is a method of the calling clients, and it 
    // is invoked when the user records a new message so that the information about
    // the message (such as username and time and date) can be stored as well.
    function recordMessageInfo(id) {
    
      // Get the messages array property of the messages shared object. If the
      // property is undefined, create the array.
      var messages = application.messagesSO.getProperty("messages");
      if (messages == undefined) {
        messages = new Array(  );
      }
    
      // Create an object that contains the message id, username, and the date when
      // the message was recorded. Add this object to the messages array.
      var infoObj = {id: id, username: this.username, dateTime: new Date(  )};
      messages.push(infoObj);
    
      // Add the updated info back to the shared object and write the data to disk.
      application.messagesSO.setProperty("messages", messages);
      application.messagesSO.flush(  );
    }
    
    // The removeMessage(  ) function is a method of the administrator client, and it
    // allows the administrator to remove recorded messages.
    function removeMessage(streamName) {
    
      // Use the Stream.get(  ) method to get a reference to the stream on the server.
      var messageStream = Stream.get(streamName);
    
      // Use the clear(  ) method to delete the stream.
      messageStream.clear(  );
    
      // Get the messages property of the shared object, and loop through it to find
      // the information for the stream that has been deleted. When the message
      // information is found, delete it with a splice(  ) method. 
      var messages = application.messagesSO.getProperty("messages");
      for (var i = 0; i < messages.length; i++) {
        if ("message" + messages[i].id == streamName) {
          messages.splice(i, 1);
          break;
        }
      }
    
      // Update the shared object and write it to disk.
      application.messagesSO.setProperty("messages", messages);
      application.messagesSO.flush(  );
    }
    
    // The getMessageID(  ) function returns a unique message number.
    function getMessageID (  ) {
      // Get the messageNum property from the shared object and increment the value
      // stored in the shared object.
      var messageNum = application.messagesSO.getProperty("messageNum");
      application.messagesSO.setProperty("messageNum", messageNum + 1);
      application.messagesSO.flush(  );
      return messageNum;
    }

The ASC file contains a lot of code, but fortunately, most of it is not very complex once you familiarize yourself with it. Let's look at some of the code with a little more explanation as to what is going on and why.

The onConnect( ) method is a method that FlashCom automatically invokes when a new client connects to the application. FlashCom creates a new client object and passes a reference to the object as a parameter to the onConnect( ) method. In addition, we pass two custom parameters: a username and a password. The username is important because it helps the application keep track of what kinds of users are logged in (calling clients or an administrator). The password is null in the case of all calling clients, but it has the value of "adminPass" in the case of the administrator (this is something you will hardcode into the administrator client movie). The password is important because it helps to prevent a calling client from logging in with the username of "admin" (which is the username we use for the administrator).

application.onConnect = function (newClient, username, password) {
  newClient.username = username;
  newClient.password = password;
  acceptClient(newClient);
};

Analogous to the onConnect( ) method, the onDisconnect( ) method is invoked each time a client disconnects from the application. As with the onConnect( ) method, FlashCom passes a parameter to onDisconnect( ) that references the client who has just disconnected. The onDisconnect( ) method is important in this application only when the disconnecting client was on a live call. The server needs to know that the call has ended so it can alert the other client that the call has ended and can set the correct values for properties it uses to track the call status. When the administrator is online, the application keeps track of the administrator using a property named admin. And when a call is in progress, the calling client object is stored as a property named callingClient. See the acceptClient( ) function for more on these two properties.

Therefore, if the disconnecting client happens to be the same client that is stored as the calling client, set the calling client to null (since there is no longer a calling client connected) and invoke a method on the administrator client to tell it that the call has ended. Alternatively, if the disconnecting client is the administrator, invoke a method on any calling client to let it know that the call has ended. Also, it is important to set callingClient to null and delete the admin property so that the application knows who is logged in and what the call status is.

application.onDisconnect = function (disconnectClient) {
  if (application.callingClient == disconnectClient) {
    application.callingClient = null;
    if (application.admin != undefined) {
      application.admin.call("incomingCallEnd", null);
    }
  }
  if (application.admin = disconnectClient) {
    if (application.callingClient != null) {
      application.callingClient.call("adminEndCall", null);
    }
    application.callingClient = null;
    delete application.admin;
  }
};

The onAppStart( ) method is automatically invoked once when the application starts. So this is where you should place actions that should occur only once. Initialize the callingClient property to null (since there is no call in progress when the application is first started) and get a remote shared object for the application to store information about messages left for the administrator. If the shared object has not been created (the first time the application runs), initialize a messageNum property, which the application uses to make sure each message is assigned a unique ID.

application.onAppStart = function (  ) {
  application.callingClient = null;
  application.messagesSO = SharedObject.get("messages", true);
  if (application.messagesSO.getProperty("messageNum") == undefined) {
    application.messagesSO.setProperty("messageNum", 0);
  }
};

The acceptClient( ) function is invoked by the onConnect( ) method every time a new client connects to the application. You should make sure that the client has not tried to log in with the username "admin", unless it is accompanied by the password "adminPass". The calling clients are not asked for a password when they log in, so the password will be undefined. The administrator password is hardcoded into the administrator client movie. So if a calling client tries to log in with the username "admin", reject the connection and exit from the function. Otherwise, accept the connection and assign methods to the client object. If the client is the administrator, also assign some administrator-only methods to the object and assign the client object reference to the admin property. The admin property does two things. First, it lets the application know that the administrator is online (so users can place calls). Second, it gives you a convenient way to invoke methods on the administrator client throughout the server-side code.

function acceptClient (newClient) {
  if (newClient.username == "admin" && newClient.password != "adminPass") {
    application.rejectConnection(newClient);
    return;
  }
  application.acceptConnection(newClient);
  newClient.recordMessageInfo = recordMessageInfo;
  newClient.getMessageID = getMessageID;
  newClient.placeCall = placeCall;
  newClient.endCall = endCall;
  if (newClient.username == "admin") {
    application.admin = newClient;
    newClient.acceptIncomingCall = acceptIncomingCall;
    newClient.sendToLeaveMessage = sendToLeaveMessage;
    newClient.removeMessage = removeMessage;
  }
}

The recordMessageInfo( ) function is assigned as a method of client objects, and it is invoked when the calling client leaves a message. Although the video and audio portion of the message is published completely from the client, this server-side function records information about the message into a remote shared object. This is important because it allows the administrator to retrieve a list of messages. Each entry in the shared object contains the unique message ID, the username of the client leaving the message, and the date and time when the message was recorded.

function recordMessageInfo (id) {
  var messages = application.messagesSO.getProperty("messages");
  if (messages == undefined) {
    messages = new Array(  );
  }
  var infoObj = {id: id, username: this.username, dateTime: new Date(  )};
  messages.push(infoObj);
  application.messagesSO.setProperty("messages", messages);
  application.messagesSO.flush(  );
}

The removeMessage( ) function is a method of the administrator client object, and it removes a data stream (the FLV file) from the server when the administrator chooses that option. The client passes the name of the stream to this method and, using the Stream.get( ) method, retrieves a server-side reference to the stream. Then the clear( ) method removes the stream from the server. However, you also need to remove the message information from the shared object so that it no longer appears in the administrator's list of messages. Do this by looping through all the elements of the messages array until you find the matching entry. Then use the splice( ) method to remove that element, break out of the for loop, and save the updated information back to the shared object.

function removeMessage (streamName) {
  var messageStream = Stream.get(streamName);
  messageStream.clear(  );
  var messages = application.messagesSO.getProperty("messages");
  for (var i = 0; i < messages.length; i++) {
    if ("message" + messages[i].id == streamName) {
      messages.splice(i, 1);
      break;
    }
  }
  application.messagesSO.setProperty("messages", messages);
  application.messagesSO.flush(  );
}
    [ Team LiB ] Previous Section Next Section