8 Developing Rich Communication Services Applications

This chapter shows how you can use the Oracle Communications WebRTC Session Controller JavaScript application programming interface (API) library to develop Rich Communication Services (RCS) applications.

About Rich Communication Services

Rich Communications Services is a system created by the Groupe Speciale Mobile Association (GSMA) which allows telecommunication providers running IP Media Subsystem (IMS) networks to extend those networks with a variety of features, including enhanced phone book capabilities, messaging options, and expanded options during calls. For more details on RCS, see http://www.gsma.com/network2020/rcs/.

About WebRTC Session Controller RCS Support

The WebRTC Session Controller JavaScript SDK provides support for integrating RCS functionality into your WebRTC Session Controller web-based applications. For more details on any of the APIs described in this chapter, see Oracle Communications WebRTC Session Controller JavaScript API Reference.

The following RCS services are implemented:

Prerequisites

Before continuing, make sure you thoroughly review and understand the JavaScript API discussed in the following chapters:

About the Examples in This Chapter

The examples and descriptions in this chapter are kept intentionally straightforward in order to illustrate the functionality of the WebRTC Session Controller JavaScript API without obscuring it with user interface code and other abstractions and indirections. Since it is likely that use cases for production applications will take many forms, the examples assume no pre-existing interface schemas except when absolutely necessary, and then, only with the barest minimum of code. For example, if a particular method requires arguments such as a user name, a code example will show a plain string username such as "bob@example.com" being passed to the method. It is assumed that in a production application, you would retrieve information using some sort of form-based user interface.

Note:

The examples in this chapter are not intended to be "plug-and-play" examples; error checking and security are ignored in favor of concept illustration.

Capabilities Exchange

WebRTC Session Controller implements support for RCS capabilities exchange which lets two endpoints describe their capabilities such as video streaming, audio streaming, and file transfer so that they can negotiate support within a shared application session.

Sample Capability Exchange HTML File

In Example 8-1, the sample HTML file contains three check boxes which let you set local client "capabilities," a button and an associated input text box to submit query enquiries. Two div elements serve as targets for displaying capabilities from the remote host, queryResult, and for showing incoming query requests, queryFromResult.

Note:

The capabilities in this example are simple arbitrary strings and do not actually represent the capabilities of the host browser. They are provided for example purposes only. For information on RCS capabilities strings, see http://www.gsma.com/network2020/rcs/specs-and-product-docs/.

The required SDK files that must be included for this sample are:

  • wsc-common.js: Shared common library utility functions.

  • wsc-capability.js: Capabilities SDK functions.

Example 8-1 Capability Exchange Sample HTML File

<!DOCTYPE HTML>
<html>
<head>
    <title>WebRTC Session Controller Capabilties Exchange Example</title>
    <script type="text/javascript" src="/api/wsc-common.js"></script>
    <script type="text/javascript" src="/api/wsc-capability.js"></script>
</head>
<body>
  <h1>WebRTC Session Controller Capabilties Exchange Example</h1>
  <div id="mainPanel">
      <div class="container" id="myCapabilities">
        <p>My capabilities:</p>
        <input type="checkbox" id="audio" checked>IM/Chat (IM)
        <input type="checkbox" id="video" checked>Video share (VS)
        <input type="checkbox" id="file">File transfer (FT)
      </div>

      <div class="container" id="query">
        <button id="queryBtn">Query: </button>
        <input type="text" id="target" size="40">
        <div id="queryResult" class="content"></div>
      </div>

      <div class="container" id="queryFrom">
        <p>Capabilties query received from: </p>
        <div id="queryFromResult" class="content"></div>
      </div>
  </div>
</body>
</html>

Initiate a Capability Exchange Query

In Example 8-2, the following occurs:

  1. A set of global variables are initialized for a variety of utility values.

  2. A new capabilityPackage object is instantiated using the current Session as an argument.

  3. A new capabilityExchange object is instantiated using the capabilityPackage object's createCapabilityExchange method.

  4. The event handler, onQueryRequest, is bound to the capabilityExchange object's onQueryRequest listener. This event handler processes the capabilities of the remote host. See Example 8-4.

  5. The event handler, onQueryResponse, is bound to the capabilityExchange object's onQueryResponse listener. This event handler processes queries from the remote host. See Example 8-3.

  6. The event handler, onErrorResponse, is bound to the capabilityExchange object's onErrorResponse listener. This event handler is triggered when any errors occur during the capabilities exchange. See Example 8-5.

  7. The queryCapability utility function is bound to the click event of the query button, queryBtn, and the function getMyCapabilties reads the state of each of the three check boxes and creates an array of values.

Example 8-2 Initiating a Capability Exchange Query

var 
  capabilityPackage,
  capabilityExchange,
  queryCounter = 0,
  queryFromCounter = 0,
  _A = "IM/Chat",
  _V = "Video share",
  _F = "File transfer";

capabilityPackage = new wsc.CapabilityPackage(wscSession);
capabilityExchange = capabilityPackage.createCapabilityExchange();
capabilityExchange.onQueryResponse = onQueryResponse;
capabilityExchange.onQueryRequest = onQueryRequest;
capabilityExchange.onErrorReponse = onErrorResponse;

var queryButton = document.getElementById("queryBtn");
queryButton.addEventListener("click", queryCapability);

function queryCapability() {
  var target = document.getElementById("target").value.trim();
  console.log("Query capability of:", target);
  capabilityExchange.enquiry(getMyCapabilities(), target);
}

function getMyCapabilities() {
  var myCs = [];
  if (document.getElementById("audio").checked) {
    myCs.push(_A);
  }
  if (document.getElementById("video").checked) {
    myCs.push(_V);
  }
  if (document.getElementById("file").checked) {
    myCs.push(_F);
  }
  return myCs.join(";");
}

The actual query itself is sent using the enquiry method of the capabilityExchange object.

Handle a Capability Query Response

You define the onQueryResponse event handler to process query responses from remote hosts. In Example 8-3, the variable, capabilities, is returned from the incoming query object's targetCapability method. If capabilities exists, the array is unwrapped using a for loop, and the various capabilities are parsed and displayed along with the target name using the queryResult div element as a container. The queryCounter variable is used to prevent naming collisions during multiple requests.

Example 8-3 onQueryResponse Sample Code

function onQueryResponse(query) {
  var capabilities = query.targetCapability;
  var from = query.target;

  var queryResultEle = document.getElementById("queryResult");
  queryResultEle.innerHTML = queryItemEle;
  document.getElementById("queryName" + queryCounter).textContent = from + ": ";

  if (capabilities) {
    var cArray = capabilities.split(";");
    for (var i = 0; i < cArray.length; i++) {
      var c = cArray[i];
      if (c == _A) {
        document.getElementById("queryCoa" + queryCounter).className += " cEnable";
        showElement("queryCoa"+ queryCounter);
      } else if (c == _V) {
        document.getElementById("queryCov" + queryCounter).className += " cEnable";
        showElement("queryCov"+ queryCounter);
      } else if (c == _F) {
        document.getElementById("queryCof" + queryCounter).className += " cEnable";
        showElement("queryCof"+ queryCounter);
      } else {
        console.log(from, "has capability:", c);
      }
    }
  }
}

Handle an Incoming Capability Query

You define the onQueryRequest event handler to process incoming query requests. In Example 8-4, span elements are concatenated together using the queryFromCounter value to make sure that they are distinct between multiple queries. The span elements are bound to the queryFromResult content div element, and the same for loop from Example 8-3 is used to display each capability entry if it is defined in the capabilities array. Finally, the capabilityExchange object's respond method is called to return the local capabilities to the remote endpoint.

Example 8-4 onQueryRequest Sample Code

function onQueryRequest(query) {
    var capabilities = query.initiatorCapability;
    var from = query.initiator;

    queryFromCounter = queryFromCounter + 1;

    var nameS = '<span id="queryFromName' + queryFromCounter + '"></span>&nbsp;&nbsp;';
    var coaS = '<span id="queryFromCoa' + queryFromCounter + '"
                                          hidden="true">IM</span>&nbsp;&nbsp;';
    var covS = '<span id="queryFromCov' + queryFromCounter + '"
                                          hidden="true">VS</span>&nbsp;&nbsp;';
    var cofS = '<span id="queryFromCof' + queryFromCounter + '" 
                                          hidden="true">FT</span>';
    var queryFromItemEle = nameS + coaS + covS + cofS;

    var queryFromResultEle = document.getElementById("queryFromResult");
    queryFromResultEle.innerHTML = queryFromItemEle;
    document.getElementById("queryFromName" + queryFromCounter).textContent = from + ": ";

  if (capabilities) {
    var cArray = capabilities.split(";");
    for (var i = 0; i < cArray.length; i++) {
      var c = cArray[i];
      if (c == _A) {
        document.getElementById("queryFromCoa" + queryFromCounter).className += " cEnable";
        showElement("queryFromCoa"+ queryFromCounter);
      } else if (c == _V) {
        document.getElementById("queryFromCov" + queryFromCounter).className += " cEnable";
        showElement("queryFromCov"+ queryFromCounter);
      } else if (c == _F) {
        document.getElementById("queryFromCof" + queryFromCounter).className += " cEnable";
        showElement("queryFromCof"+ queryFromCounter);
      } else {
        console.log(from, "has capability:", c);
      }
    }
  }
  capabilityExchange.respond(query, getMyCapabilities());
}

Handle Capability Exchange Errors

You define the onErrorResponse event handler to process error conditions.

Example 8-5 onErrorResponse Sample Code

function onErrorResponse (error) {
  console.log("Error action: "+error.action);
  console.log("Error code: "+error.errorCode);
  console.log("Error reason: "+error.errorReason);
}

Initiate a Capability Exchange Request in a Call

To initiate a capability exchange request in an active call, you simply initiate the capabilityPackage using a Call object instead of a session object:

Example 8-6 Initiating a Capability Exchange in a Call

var
   audioMediaDirection = wsc.MEDIADIRECTION.SENDRECV,
   videoMediaDirection = wsc.MEDIADIRECTION.SENDRECV,
   callConfig = new wsc.CallConfig(audioMediaDirection,videoMediaDirection);

var myCall = callPackage.createCall("alice@example.com", callConfig);
myCall.start();

capabilityPackage = new wsc.CapabilityPackage(myCall);
capabilityExchange = capabilityPackage.createCapabilityExchange();
capabilityExchange.onQueryResponse = onQueryResponse;
capabilityExchange.onQueryRequest = onQueryRequest;
capabilityExchange.onErrorReponse = onErrorResponse;

var queryButton = document.getElementById("queryBtn");
queryButton.addEventListener("click", queryCapability);

// Continue with your workflow...

Sending a Standalone Message

The RCS standard defines a simple way an application can send text messages between two endpoints, which is implemented in WebRTC Session Controller in the wsc.MessagingPackage class.

Messaging Sample HTML File

The sample HTML file for messaging examples contains the following elements:

  • A div element, statusArea, used to display application status messages.

  • A form input text box, msgTarget, used to input a message recipient's ID.

  • A div element, history, used as a container to store message history.

  • A form input text box, msgContent, used to input a message for the recipient.

  • A form input button, msgSend, used to send the content of the msgContent text box to the recipient.

The required SDK files that must be included for this sample are:

  • wsc-common.js: Shared common library utility functions.

  • wsc-messaging.js: Messaging SDK functions.

Example 8-7 Messaging Sample HTML File

<!DOCTYPE html>
<html>
  <head>
    <title>WebRTC Session Controller Messaging Example</title>
    <script type="text/javascript" src="/api/wsc-common.js"></script>
    <script type="text/javascript" src="/api/wsc-messaging.js"></script> 
  </head>
  <body>
    <h1>WebRTC Session Controller Messaging Example</h1>
    <div id="statusArea"></div>
    <h4>Recipient:</h4> 
    <p><input type="text" name="msgTarget" id="msgTarget" size="40"/></p>
    <div id="history"></div>
    <h4>Message Content:</h4>
    <p>
      <input type="text" name="msgContent" id="msgContent" size="50"/>
      <input type="button" name="msgSend" id="msgSend" value="Send"/>
    </p>
  </body>
</html>

Send a Message

In Example 8-8, the following occurs:

  1. A new messagePackage object is instantiated using the current Session as an argument.

  2. A new messaging object is instantiated using the messagePackage object's createMessaging method.

  3. The event handler, onNewMessage, is bound to the messaging object's onNewMessage listener. This event handler processes incoming messages. See Example 8-9.

  4. The event handler, onSuccessResponse, is bound to the messaging object's onSuccessResponse listener. This event handler processes success responses. See Example 8-10.

  5. The event handler, onErrorResponse, is bound to the messaging object's onErrorResponse listener. This event handler is triggered when any errors occur during the message exchange. See Example 8-11.

  6. The sendMsg utility function is bound to the click event of the button, msgSend, and the contents of the msgTarget edit box is read, and assigned as the message recipient, unless the edit box is empty, in which case the statusArea div element is updated with an error.

Example 8-8 Sending a Message

messagePackage = new wsc.MessagingPackage(wscSession);
messaging = messagePackage.createMessaging();
messaging.onNewMessage = onNewMessage;
messaging.onSuccessResponse = onSuccessResponse;
messaging.onErrorResponse = onErrorResponse;

document.getElementById("msgSend").onclick = function() {
  sendMsg();
};

function sendMsg() {
  var msg = document.getElementById("msgContent").value;
  var target = document.getElementById("msgTarget").value;
  if (msg && msg != "" && target && target != "") {
    var msgId = messaging.send(msg, target);
    console.log("The sent message ID is: " + msgId);
  } else if (target == "") {
    document.getElementById("statusArea").innerHTML = "Please enter a recipient.";
  }
}

Handle an Incoming Message

You define the onNewMessage event handler to process incoming messages. When a new message comes in, use the accept method to accept the message or the reject method to reject it. In Example 8-9, depending on the value of the msgRejected boolean which would be handled in additional user interface code you would supply, the message is either accepted or rejected, and, if accepted, the message is added to the history div element using the updateHistory utility function. A message rejection returns an SIP 603 error with the status Decline.

Example 8-9 onNewMessage Sample Code

function onNewMessage(chatMessage) {
  var
    initiator = chatMessage.initiator,
    msg = chatMessage.content,

  if (msgRejected) {
    messaging.reject(chatMessage, 603, "Decline");
  } else {
    messaging.accept(chatMessage);
    updateHistory(initiator, msg, true);
  }
}

function updateHistory(initiator, msg, newMessage) {
  var history = document.getElementById("history");
  var d = new Date();
  var ds = d.toLocaleTimeString();
  var newMsg = "(" + ds + ") " + initiator + ": " + msg;
  if (newMessage) {
    newMsg = "<div id='inChatMessage'>" + newMsg + "</div>";
    document.getElementById("msgTarget").value = initiator;
  } else {
    newMsg = "<div id='outChatMessage'>" + newMsg + "</div>";
  }
  history.innerHTML = history.innerHTML + newMsg;
};

Handle Messaging Success Events

You define the onSuccessResponse event handler to process a successful message transmission, and update the statusArea div element accordingly.

Example 8-10 onSuccessResponse Sample Code

function onSuccessResponse(message) {
  var content = message.content;
  document.getElementById("statusArea").innerHTML = "Send message \"" + content
                                                                + "\" succeeded.";
}

Handle Messaging Error Events

You define the onErrorResponse event handler to process a message transmission failure and update the statusArea div element accordingly.

Example 8-11 onErrorResponse Sample Code

function onErrorResponse(message, extHeaders) {
  var content = message.content;
  document.getElementById("statusArea").innerHTML = "Send message \"" + content
                                                                + "\" failed.";
}

Creating an RCS Chat Application

The WebRTC Session Controller JavaScript SDK lets you implement a one to one or one to many chat application as defined by the RCS specification. For more information on the RCS chat specification, see http://www.gsma.com/network2020/rcs/specs-and-product-docs/.

Chat Sample HTML File

The sample HTML file for chat examples contains the following elements:

  • A div element, statusArea, used to display application status messages.

  • A form input text box, target, used to input a chat request recipient's ID.

  • A form input text box, participants, used to add additional recipients to a group chat.

  • A form input button, chatButton, used to initiate a chat session request.

  • A form input button, endChatButton, used to terminate a chat session.

  • A div element, history, used as a container to display chat message history.

  • A form input text box, msgContent, used to enter a chat message.

  • A form input button, msgSend, used to send the content of the msgContent text box to the recipient.

The required SDK files that must be included for this sample are:

  • wsc-common.js: Shared common library utility functions.

  • wsc-chat.js: Chat SDK functions.

Example 8-12 Chat Sample HTML File

<!DOCTYPE HTML>
<html>
<head>
  <title>WebRTC Session Controller Chat Example</title>
  <script type="text/javascript" src="/api/wsc-common.js"></script>
  <script type="text/javascript" src="/api/wsc-chat.js"></script> 
</head>
<body>
  <div id="mainPanel">
    <div id="statusArea"></div>
    <br/>
    <p>To: <input type='text' name='target' id='target' size='30'/></p>
    <p>Add participants: <input type='text' name='participants' id='participants' size='70'/></p>
    <p>
      <input type='button' name='chatButton' id='chatButton' value='Chat'
                                                                onclick='startChat()'/>&nbsp; 
      <input type='button' name='endCallButton' id='endChatButton' value='End Chat'
                                                                onclick='end()'/>
    </p>
    <div id='history'></div>
    <p>
      <input type='text' name='msgContent' id='msgContent'/>&nbsp;
      <input type='button' name='msgSend' id='msgSend' value='Send'/>
    </p>
  </div>
</body>
</html>

Implementing Chat

To determine whether a remote endpoint is a WebRTC compliant browser or an RCS compliant application, you can configure a capabilities exchange as described in "Capabilities Exchange." For browser to RCS client, the WebRTC Session Controller JavaScript SDK uses Message Session Relay Protocol (MSRP) and Session Description Protocol (SDP) to negotiate with the target and configure the WebSocket connection with WebRTC Session Controller Media Engine (Media Engine). For browser to browser chat sessions, your application can use the CallPackage data channel functions as described in "Setting Up Data Transfers in Your Applications."

Initiate the Chat Session

In Example 8-13, the following occurs:

  1. Global variables for a chat object, wscChat, and a chat package, chatPackage are initialized and the contents of the target text box are retrieved and assigned to the target variable.

  2. A new chatPackage object is instantiated using the current Session as an argument.

  3. The event handler, onIncomingChat, is bound to the chatPackage object's onIncomingChat listener. This event handler processes incoming chat requests. See Example 8-15.

  4. The target variable is tested to make sure it is not zero length, and a chatConfig object, along with an empty array of acceptTypes are declared.

  5. The acceptTypes array is initialized, the chatConfig.setMaxSize instance variable is initialized, as is the chatConfig.acceptTypes instance variable is initialized with the acceptTypes array. The possible values for a chatConfig object are as follows:

    • acceptTypes: An array of media types the endpoint is willing to accept. May contain zero or more media types or a wildcard, "*".

    • acceptWrappedTypes: An array of media types that an endpoint is willing to receive in an MSRP message with multipart content. May not be used as the outermost type of the message. May contain zero or more media types or a wildcard, "*".

    • maxSize: A whole numeric value indicating the maximum message size specified in octets the endpoint is capable of receiving.

  6. A chat object, wscChat, is created using the chatPackage object's createChat method with the target as an argument.

  7. The event handler, onStateChange, is bound to the chat object's onStateChange listener. This event handler handles changes in the state of the chat object. See Example 8-16.

  8. The event handler, onConnectionStateChange, is bound to the chat object's onConnectionStateChange listener. This event handler handles changes in the connection state of the chat object. See Example 8-17.

  9. The event handler, onChatMessage, is bound to the chat object's onChatMessage listener. This event handler handles incoming chat messages. See Example 8-18.

  10. The event handler, onMessageSendSuccess, is bound to the chat object's onMessageSendSuccess listener. This event handler handles successful message transmission events. See Example 8-19.

  11. The event handler, onMessageSendFailure, is bound to the chat object's onMessageSendFailure listener. This event handler handles failed message transmission events. See Example 8-20.

  12. The event handler, onMessageTyping, is bound to the chat object's onMessageTyping listener. This event handler is triggered when the remote party is actively typing. See Example 8-21.

  13. The event handler, onMessageTypingStop, is bound to the chat object's onMessageTypingStop listener. This event handler is triggered when the remote party stops typing. See Example 8-22.

  14. Add additional chat participants to the chat using the chat object's addParticipants method.

  15. Toggle the security of the chat session's transport layer using the chat object's setSecure method.

  16. Finally, the chat object's start method is used to start the chat session taking the chatConfig object as an argument.

Example 8-13 Initiating the Chat Session

var 
    wscChat,
    chatPackage;
    target = document.getElementById("target").value;

chatPackage = new wsc.ChatPackage(wscSession);
chatPackage.onIncomingChat = onIncomingChat;

if (target != "") {
  var 
     chatConfig = {}, 
     acceptTypes = [];

  acceptTypes.push('text/plain');
  acceptTypes.push('message/cpim');

  chatConfig.selfMaxSize = 1024;
  chatConfig.acceptTypes = acceptTypes;

  wscChat = chatPackage.createChat(target);

  wscChat.onStateChange = onStateChange;
  wscChat.onConnectionStateChange = onConnectionStateChange;
  wscChat.onChatMessage = onChatMessage;
  wscChat.onMessageSendSuccess = onMessageSendSuccess;
  wscChat.onMessageSendFailure = onMessageSendFailure;
  wscChat.onMessageTyping = onMessageTyping;
  wscChat.onMessageTypingStop = onMessageTypingStop;

  wscChat.start(chatConfig);
}

Within the chat session, use the following methods as required:

  • send: Send a message in the chat session. See "Send a Chat Message" for more information.

  • end: End the chat session.

Once the chat session is ended using the wsc.Chat object end method, use the wsc.ChatPackage close method to terminate all sessions and release resources.

Send a Chat Message

Send a chat message to a target address using the Chat object's send method.

In Example 8-14, the form send button, msgSend, has its onclick event bound to an anonymous function that retrieves the text value from the msgContent form text box, and, if the value is not empty, passes it to the sendChatMessage function.

The sendChatMessage function checks to see if the message is a Common Profile for Instant Messaging (CPIM) format and creates the payload accordingly, or creates a plain text payload if not.

Note:

The isCpimMessage as well as the values of the cpim variable must be set and retrieved using your own mechanisms in your application.

Finally, the message is sent using the Chat objects send method, and a utility function, updateHistory is called to append the message along with a date and message initiator to the history content div element.

Example 8-14 Sending a Chat Message

document.getElementById("msgSend").onclick = function() {
  var msg = document.getElementById("msgContent").value;
  if (msg && msg != "") {
    sendChatMessage(msg);
  }
};

function sendChatMessage(msg) {

  var chatMessage;

  if (isCpimMessage) {
    var cpim = "From: alice@example.com\r\n" +
               "To: bob@example.com\r\n" +
               "DateTime: 2015-12-08T00:00:00-00:00\r\n" +
               "Content-Type: text/plain\r\n" +
               "Content-Length: 1\r\n\r\n" +
               msg;
    chatMessage = 
        {
          contentType : 'message/cpim',
          content : cpim
        };
  } else {
    chatMessage = 
    {
      contentType : 'text/plain',
      content : msg
    };
  }

  wscChat.send(chatMessage);

  updateHistory(wscSession.userName, msg);

  function updateHistory(initiator, msg) {
    var
      d = new Date(),
      ds = d.toLocaleTimeString(),
      title = "(" + ds + ") " + initiator + "\r\n:";
      title = "<div id='outChatMessage'>" + title + "</div>";

    var newMsg = title + msg;
    document.getElementById("history").innerHTML += newMsg;
  }
}

Handle Incoming Chat Requests

Define the onIncomingChat event handler to process incoming chat session requests.

When responding to an incoming chat session request, use the following methods as required:

  • accept: Accept the chat invitation.

  • decline: Decline the chat invitation.

  • getInitiator: Return the initiator of the chat session request.

In Example 8-15, the statusArea div element is used as the target of a showRequest function that creates a status message and an interface that allows a user to accept or decline the chat invitation. The status message and user interface are then displayed in the statusArea div element. If the session is accepted, the chat event handlers are initialized in the same manner as Example 8-13.

Example 8-15 onIncomingChat Sample Code

function onIncomingChat(chat) {
  document.getElementById("statusArea").innerHTML = showRequest(chat);

  document.getElementById("acceptButton").onclick = function() {
    chat.accept(chatConfig);
  };
  document.getElementById("declineButton").onclick = function() {
    chat.decline();
  };

  function showRequest(chat) {
    var
      initiator = chat.getInitiator(),
      message = initiator + " is requesting a chat session.";

    var display = message +
      "<input type='button' name='acceptButton' id='acceptButton' value='Accept' onclick=''/>" +
      "<input type='button' name='declineButton' id='declineButton' value='Decline' onclick=''/>";
    return display;
  }

  chat.onStateChange = onStateChange;
  chat.onConnectionStateChange = onChatConnectionStateChange;
  // Continue configuring chat callbacks...
}

Handle Chat Signaling State Changes

Define the onStateChange event handler to process changes in the chat signaling state. The wsc.CALLSTATE enum defines the possible call states. For a complete list of wsc.CALLSTATE values see the Oracle Communications WebRTC Session Controller JavaScript API Reference.

Example 8-16 onStateChange Sample Code

function onStateChange(chat, callState) {
  console.log("Chat state: " + callState.state);
  switch (callState.state) {
    case wsc.CALLSTATE.ESTABLISHED:
      // Handle an established call (chat) state as required...
      break;
    case wsc.CALLSTATE.UPDATED:
      // Handle an updated call (chat) state as required...
      break;
    case wsc.CALLSTATE.UPDATE_FAILED:
      // Handle an update failed call (chat) state as required...
      break;
    case wsc.CALLSTATE.ENDED:
      // Handle an ended call (chat) state as required...
      break;
    case wsc.CALLSTATE.FAILED:
      // Handle a failed call (chat) state as required...
      break;
    default:
      break;
  }
}

Handle Chat Connection State Changes

Define the onConnectionStateChange to process changes in chat connection state over MSRP. The ConnectionStateEnum defines the possible connection states.

Example 8-17 onConnectionStateChange Sample Code

function onConnectionStateChange(state) {
  console.log("Chat state: " + state.state);
  switch (state.state) {
    case wsc.ConnectionStateEnum.INIT:
      // Handle the INIT state...
      break;
    case wsc.ConnectionStateEnum.ESTABLISHED:
      // Handle the ESTABLISHED state...
      break;
    case wsc.ConnectionStateEnum.ERROR:
      // Handle the ERROR state...
      break;
    case wsc.ConnectionStateEnum.CLOSED:
      // Handle the CLOSED state...
      break;
  }
}

Handle Incoming Chat Messages

Define the onChatMessage event handler to process incoming chat messages. In Example 8-18, after initializing a set of variables, the function examines the content type of the message, cType, to see whether it is in a CPIM or plain text, and sets the textContent variable accordingly. It next pulls the initiating user name from either the MSRP message or the JSON header. It replaces angle brackets with their named elements in order to preserve the integrity of the HTML, and then calls the updateHistory function to append an entry to the history content div element, including a date.

Example 8-18 onChatMessage Sample Code

function onChatMessage(msg) {
  console.log("Received a chat message: " + msg.content);
  var
    content = msg.content,
    textContent = null,
    cType = msg.contentType,
    initator = null;

  if (cType == 'message/cpim') {
    initator = extractText(content, "From:", "\r\n");
    var textContent = content.substring(content.indexOf("Content-Type"));
    var startIndex = textContent.indexOf("\r\n\r\n");
    textContent = textContent.substring(startIndex);
    if (textContent.indexOf("\r\n\r\n") == 0) {
      textContent.replace("\r\n\r\n", "");
    }
  } else {
    textContent = content;
  }
 
  if (!initator) {
    if (wscChat.getInitiator() === wscChat.session.getUserName()) {
      initator = wscChat.getTarget();
    } else {
      initator = wscChat.getInitiator();
    }
  }

  textContent = textContent.replace("<", "&lt;");
  textContent = textContent.replace(">", "&gt;");

  updateHistory(initator, textContent, true);

  function updateHistory(initiator, msg) {
    var
      d = new Date(),
      ds = d.toLocaleTimeString(),
      title = "(" + ds + ") " + initiator + "\r\n:";

    title = "<div id='inChatMessage'>" + title + "</div>";
    var newMsg = title + msg;

    document.getElementById("history").innerHTML += newMsg;
  }
}

Handle Message Transmission Success and Failure Events

Define the onMessageSendSuccess event handler to process notification of a successful message transmission. In Example 8-19, the only result is a log notification, but you might wish to update a status area or provide a more dynamic notification system.

Example 8-19 onMessageSendSuccess Sample Code

function onMessageSendSuccess(msgId) {
  console.log("Message successfully sent. ID: " + msgId);
}

Define the onMessageSendFailure event handler to process notification of a failed message transmission. As with Example 8-19, the only result is a log notification, but, again, you may wish to create a customized notification in your own application.

Example 8-20 onMessageSendFailure Sample Code

function onMessageSendFailure(msgId) {
  console.log("Message transfer failed. ID: " + msgId);
}

Handle Participant Typing Notifications

Optionally, you can define onMessageTyping and onMessageTypingStop event handlers to process changes in user interface state when tracking a remote user's data input. In Example 8-21 and Example 8-22, only log messages are generated, but you could also update your chat history window or other user interface device with the changing statuses.

Example 8-21 onMessageTyping Sample Code

function onMessageTyping() {
  console.log(wscChat.getTarget() + " is typing...");
}

Example 8-22 onMessageTypingStop Sample Code

function onMessageTypingStop() {
  console.log(wscChat.getTarget() + " has stopped typing...");
}

Implementing File Transfer

The WebRTC Session Controller JavaScript SDK lets you implement a one to one or one to many file transfer application as defined by the RCS specification. For more information on the RCS chat specification, see http://www.gsma.com/network2020/rcs/specs-and-product-docs/.

Note:

Multiple file transfer in a single session is not supported.

File Exchange Example HTML File

The sample HTML file for messaging examples contains the following elements:

  • A div element, statusArea, used to display application status messages.

  • A form text input box, target, into which a recipient address is entered.

  • A file input button, selectFilesButton, used to select files to transfer to a remote party.

  • A button input, filesButton, used to start a file transfer, the onclick event of which is bound to a sendFile function.

  • A button input, endFtButton, used to cancel a file transfer, the onclick event of which is bound to the endFt function.

  • A button input, cleanButton, used to hide the file transfer progress bar, the onclick event of which is bound to the hideFileTransferProgress function.

  • A form input button, msgSend, used to send the content of the msgContent text box to the recipient.

The required SDK files that must be included for this sample are:

  • wsc-common.js: Shared common library utility functions.

  • wsc-filetransfer.js: Messaging SDK functions.

Example 8-23 File Transfer HTML Sample

<!DOCTYPE HTML>
<html>
<head>
  <title>WebRTC Session Controller File Transfer Example</title>
  <script type="text/javascript" src="/api/wsc-common.js"></script>
  <script type="text/javascript" src="/api/wsc-filetransfer.js"></script> 
</head>
<body>
  <div id="mainPanel">
    <div id="statusArea"></div>
    <br/>
    <p>To: <input type='text' name='target' id='target' size='30'/></p>
    <p>
      <input type='file' multiple name='selectFileButton' id='selectFileButton'/>
      <input type='button' name='filesButton' id='fileButton' value='Send'
                                            onclick='sendFile()'/>&nbsp;
      <input type='button' name='endFtButton' id='endFtButton' value='Cancel transfers'
                                            onclick='endFt()'/>&nbsp;
      <input type='button' name='cleanButton' id='cleanButton' value='Clear progress'
                                            onclick='hideFileTransferProgress()'/>
    </p>
    <div id="messageArea">
    </div>
  </div>
</body>
</html>

Setup a File Transfer Session

In Example 8-13, the following occurs:

  1. Global variables for a file transfer object, wscFileTransfer, a file transfer package, fileTransferPackage, and a fileConfig array are initialized and the contents of the target text box are retrieved and assigned to the target variable.

  2. A new fileTransferPackage object is instantiated using the current Session as an argument.

  3. The event handler, onFileTransfer, is bound to the fileTransferPackage object's onFileTransfer listener. This event handler processes incoming file transfer requests. See Example 8-26.

  4. A message transfer object, wscFileTransfer, is created using the fileTransferPackage object's createFileTransfer method with the target as an argument.

  5. The event handler, onStateChange, is bound to the wscFileTransfer object's onStateChange listener. This event handler handles changes in the state of the file transfer object. See Example 8-16.

  6. The event handler, onSessionStateChange, is bound to the wscFileTransfer object's onSessionStateChange listener. This event handler handles changes in the session state of the file transfer object. See Example 8-17.

  7. The event handler, onFileData, is bound to the wscFileTransfer object's onFileData listener. This event handler handles incoming file data. See Example 8-18.

  8. The event handler, onProgress, is bound to the wscFileTransfer object's onProgress listener. This event handler handles file transfer progress events. See Example 8-19.

  9. The event handler, onFileTransferSuccess, is bound to the wscFileTransfer object's onFileTransferSuccess listener. This event handler handles successful file transmission events. See Example 8-20.

  10. The event handler, onFileTransferFailure, is bound to the wscFileTransfer object's onFileTransferFailure listener. This event handler handles failed file transmission events. See Example 8-20.

  11. The fileConfig object is initialized. The file field is the actual instance of the file itself. The props object can have the following properties:

    • name: A string containing the file name.

    • size: An integer indicating the file size in octets.

    • type: A string indicating the MIME type of the file.

    • hashes: An array containing the hash computation of the file: {algorithmName: "value: xxxx"}.

    • disposition: A string with value render or attachment. The disposition value tells the receiving endpoint how to handle the file. The value render indicates that the file should be automatically rendered by the endpoint, for example a GIF or JPEG image file. The value attachment indicates that the file should not be rendered and should be treated as a downloadable attachment, for example an EXE or other such BLOB. If the disposition attribute is not specified, render is implied.

    • description: A string description of the file.

    • creationTime: A string containing the file creation date and time.

    • modificationTime: A string containing the file modification date and time.

    • readTime: A string containing the time and date the file was last read.

    • icon: A string containing the Content ID URL (cid:content-id) for the file. In the case of images, usually renders as a file icon.

    • startOffset: A string indicating the octet position in the file where the file transfer should start. The first octet of a file is indicated by the ordinal number 1.

    • stopOffset: A string indicating the octet position in the file where the file transfer should end, including the specified octet. If the total file size is not known, use the "*" wildcard.

    • direction: A string indicating the transfer direction for the file. To push a file, use send and to pull a file use receive.

  12. Add additional file transfer recipients to the fileTransfer session using the fileTransfer object's addParticipants method.

  13. Toggle the security of the fileTransfer session's transport layer using the fileTransfer object's setSecure method.

  14. Finally, the wscFileTransfer object's start method is used to start the file transfer session taking the fileConfig object as an argument.

Example 8-24 Instantiating a File Transfer Session

var 
    wscFileTransfer,
    fileTransferPackage,
    fileConfigs = [],
    target = document.getElementById("target").value;

fileTransferPackage = new wsc.FileTransferPackage(wscSession);
fileTransferPackage.onFileTransfer = onFileTransfer;

wscFileTransfer = fileTransferPackage.createFileTransfer(target);

wscFileTransfer.onStateChange = onStateChange
wscFileTransfer.onSessionStateChange = onSessionStateChange
wscFileTransfer.onFileData = onFileData;
wscFileTransfer.onProgress = onFileProgress;
wscFileTransfer.onFileTransferSuccess = onFileTransferSuccess;
wscFileTransfer.onFileTransferFailure = onFileTransferFailure;

fileConfig.file = myFileName;
fileConfig.props = null;

wscFileTransfer.start(fileConfig);

Control and Return Information on the File Transfer

Within the file transfer session, use the following methods as required:

  • abort: Abort the file transfer session.

  • getInitiator: Return the initiating address of the file transfer session.

  • getTarget: Return the file transfer session recipient.

Terminate the File Transfer Session

Once the file transfer session is ended using the wscFileTransfer object end method, use the wscFileTransferPackage object close method to terminate all sessions and release resources.

Send a File from Your Application

In Example 8-25, the selectedFiles variable is initialized, the recipient's address is retrieved from the text box, target, and assigned to the variable, recipient, and the selectFiles function is bound to the onchange event of the selectFilesButton input button. When the onchange event fires, the file selected in the file browser of the selectFilesButton input button is assigned to selectedFile.

Next, when the input button, fileButton, is clicked, the sendFile function bound to it is triggered. In the sendFile function, both the recipient and selectedFile variables are checked to make sure they are not empty, and, if valid a new wscFileTransfer object is created and initialized, and the various event handlers for the wscFileTransfer object are bound to their respective listeners as in "Setup a File Transfer Session."

Then, selectedFile is retrieved from the input user interface, using the document object's querySelector method, and a fileConfig object is created.

Finally, the file transfer is started using the wscFileTransfer object's start method.

Example 8-25 Sending One or More Files

var
    selectedFile,
    recipient = document.getElementById("target").value;

document.querySelector("#selectFilesButton").onchange = selectFile;

function selectFile() {
  var 
    fileInput = document.querySelector("#selectFileButton");

  selectedFile = fileInput.file;
  }
}

function sendFile() {
  if (recipient != "") {
    if (selectedFile) {
      wscFileTransfer = fileTransferPackage.createFileTransfer(target);

      wscFileTransfer.onCallStateChange = onStateChange
      wscFileTransfer.onSessionStateChange = onSessionStateChange
      wscFileTransfer.onFileData = onFileData;
      wscFileTransfer.onProgress = onFileProgress;
      wscFileTransfer.onFileTransferSuccess = onFileTransferSuccess;
      wscFileTransfer.onFileTransferFailure = onFileTransferFailure;

      var fileConfigs = [];
      var file = selectedFile[0], fileConfig = {};

      fileConfig.file = file;
      fileConfig.props = null;
      fileConfigs.push(fileConfig);

      wscFileTransfer.start(fileConfigs);

    }
  }
}

Handle Incoming File Transfer Requests

Define the onFileTransfer event handler to process incoming file transfer session requests.

When responding to an incoming file transfer session request, use the following methods as required:

  • accept: Accept the file transfer invitation.

  • decline: Decline the file transfer invitation.

  • getInitiator: Return the initiator of the file transfer session request.

In Example 8-15, the statusArea div element is used as the target of a showRequest function that retrieves the file names for the incoming fileTransfer objects, creates a status message, and an interface that allows a user to accept or decline the file transfer invitation. The status message and interface are rendered in the statusArea div element. If the session is accepted, the event handlers are initialized in the same manner as Example 8-24.

Example 8-26 onFileTransfer Sample Code

function onFileTransfer(fileTransfer) {
  document.getElementById("statusArea").innerHTML = showFileRequest(fileTransfer);

  document.getElementById("acceptButton").onclick = function() {
    fileTransfer.accept();
  };
 
  document.getElementById("declineButton").onclick = function() {
    fileTransfer.decline();
  };

  function showFileRequest(fileTransfer) {
    var
      initiator = fileTransfer.getInitiator(),
      fileConfigs = fileTransfer.getFileConfigs();
      if (fileConfigs.length > 1) {
        message = initiator + " wants to send you some files.";
      } else {
        message = initiator + " wants to send you a file.";
      }

    for (var i=0; i<fileConfigs.length; i++) {
      message += fileConfigs[i].props.name;
    }

    var display = message +
      "<input type='button' name='acceptButton' id='acceptButton' value='Accept' onclick=''/>" +
      "<input type='button' name='declineButton' id='declineButton' value='Decline' onclick=''/>";

    return display;
  }

  wscFileTransfer.onCallStateChange = onStateChange
  wscFileTransfer.onSessionStateChange = onSessionStateChange
  wscFileTransfer.onFileData = onFileData;
  // Continue intializing wscFileTransfer...
}

Handle File Transfer Signaling State Changes

Define the onStateChange event handler to process changes in the chat signaling state. The wsc.CALLSTATE enum defines the possible call states. For a complete list of wsc.CALLSTATE values see the Oracle Communications WebRTC Session Controller JavaScript API Reference.

Example 8-27 onStateChange Sample Code

function onStateChange(ft, callState) {
  console.log("File transfer state: " + callState.state);
  switch (callState.state) {
    case wsc.CALLSTATE.ESTABLISHED:
      // Handle an established call (file transfer) state as required...
      break;
    case wsc.CALLSTATE.UPDATED:
      // Handle an updated call (file transfer) state as required...
      break;
    case wsc.CALLSTATE.RESPONDED:
      // Handle an responded call (file transfer) state as required...
      break;
    case wsc.CALLSTATE.ENDED:
      // Handle an ended call (file transfer) state as required...
      break;
    case wsc.CALLSTATE.FAILED:
      // Handle an failed call (file transfer) state as required...
      break;
    default:
      break;
  }
}

Handle File Transfer Connection State Changes

Define the onConnectionStateChange to process changes in file transfer connection state over MSRP. The ConnectionStateEnum defines the possible connection states.

Example 8-28 onConnectionStateChange Sample Code

function onConnectionStateChange(state) {
  console.log("File transfer state: " + state.state);
  switch (state.state) {
    case wsc.ConnectionStateEnum.INIT:
      // Handle the INIT state...
      break;
    case wsc.ConnectionStateEnum.ESTABLISHED:
      // Handle the ESTABLISHED state...
      break;
    case wsc.ConnectionStateEnum.ERROR:
      // Handle the ERROR state...
      break;
    case wsc.ConnectionStateEnum.CLOSED:
      // Handle the CLOSED state...
      break;
  }
}

Handle Message Transmission Success and Failure Events

Define the onFileTransferSuccess event handler to process notification of a successful file transmission. In Example 8-29, the only result is a log notification, but you might wish to update a status area or provide a more dynamic notification system.

Example 8-29 onFileTransferSuccess Sample Code

function onFileTransferSuccess(fileTransferId) {
  console.log("File successfully sent. ID: " + fileTransferId);
}

Define the onFileTransferFailure event handler to process notification of a failed file transmission. As with Example 8-30, the only result is a log notification, but, again, you may wish to create a customized notification in your own application.

Example 8-30 onFileTransferFailure Sample Code

function onFileTransferFailure(fileTransferId) {
  console.log("File transfer failed. ID: " + fileTransferId);
}

Handle File Data Transmission

Define the onFileData event handler to process the data received from a file transmission.

Data is received in a fileData object comprising the following elements:

  • fileTransferID: A string containing the file transfer ID (defined by WebRTC Session Controller).

  • range: A FileDataRange object containing the total range of data in the file. This object comprises the following properties:

    • start: A number indicating the first byte of the data.

    • end: A number indicating the final byte of the data.

    • total: A number indicating the total size of the data in bytes.

  • content: An 8-bit unsigned integer array containing the actual file data.

In Example 8-31, the data object is processed and assigned to cachedFileData as it comes in, pushed onto the cachedFileData array if it is text, or concatenated to the array if it is binary. As the data comes in, the progress bar percentage is calculated using the range.end and range.total properties of the data object. If the progress bar for the currently transferring file has not already been added to the HTML document, the showFileTransferProgress function is called.

The showFileTransferProgress function finds the current fileConfig using its fileTransferId argument and comparing that to the fileTransferIDs of the fileTransfer object. If a fileTransferArea div element does not already exist in the HTML page, one is create and inserted before the buttonArea div element. The insertFileTransferProgress function adds both a progress bar and a cancel button to the fileTransferArea div element, either inserting or appending depending upon the state of the div element.

With the progress bar added, it is updated as the file transfer progresses.

Once the file transfer is complete, the cachedFileData array is assigned to a Binary Large Object (BLOB) file, the file name is pulled from the fileConfigs array, and the saveToDisk function is used to save the BLOB to the user's local disk.

Example 8-31 onFileData Sample Code

var
    cachedFileData = [];

function onFileData(data) {
  var fileTransferId = data.fileTransferId;
  console.log("Received file data. ID: " +
                              fileTransferId + ", range: " + JSON.stringify(data.range));

  if (!cachedFileData[fileTransferId]) {
    cachedFileData[fileTransferId] = [];
  }

  if (data.content instanceof String) {
    cachedFileData[fileTransferId].push(data.content);
  } else {
    cachedFileData[fileTransferId] = cachedFileData[fileTransferId].concat(data.content);
  }

  // Update the progress bar...
  var progressPercent = Math.ceil(data.range.end/data.range.total*100);
  if (!document.getElementById(fileTransferId)) {
    showFileTransferProgress(fileTransferId);
  }

  var progressBar = document.getElementById(fileTransferId).childNodes[1];

  if (progressBar) {
    progressBar.style.width = progressPercent+"%";
  }

  // File transfer finished...
  if (data.range.end == data.range.total) {
    var blob = new Blob(cachedFileData[fileTransferId]),
    fileConfigs = wscFileTransfer.getFileConfigs(),
    fileName = null;

    for (var i=0; i<fileConfigs.length; i++) {
      if (fileConfigs[i].props.fileTransferId == fileTransferId) {
        fileName = fileConfigs[i].props.name;
        break;
      }
    }

    blob.url = (window.URL || window.webkitURL).createObjectURL(blob);
    if (fileName) {
      saveToDisk(blob.url, fileName);
    }
    var fileTransferProgressElem = document.getElementById(fileTransferId);
    if (fileTransferProgressElem) {
      fileTransferProgressElem.hidden = true;
      var cancelBtn = fileTransferProgressElem.nextSibling;
      if (cancelBtn.type == "button") {
        cancelBtn.hidden = true;
      }
    }
  }
}

function showFileTransferProgress(fileTransferId) {
  var
    fileTransferDisplayArea = document.getElementById("fileTransferArea"),
    fileConfigs = wscFileTransfer.getFileConfigs(),
    fileConfig = null;
  
  for (var i=0; i<fileConfigs.length; i++) {
    if (fileConfigs[i].props.fileTransferId == fileTransferId) {
      fileConfig = fileConfigs[i];
      break;
    }
  }

  if (fileTransferDisplayArea == null) {
    var fileTransferArea = document.createElement("div");
    fileTransferArea.id = 'fileTransferArea';
    var buttonArea = document.getElementById("buttonArea");
    buttonArea.parentNode.insertBefore(fileTransferArea, buttonArea.nextSibling);
    insertFileTransferProgress(fileTransferArea, fileConfig);
  } else {
    insertFileTransferProgress(fileTransferDisplayArea, fileConfig);
  }
  
  function insertFileTransferProgress(fileTransferDisplayArea, fileConfig) {
    var
      fileNameNode,
      progressBar,
      lastProgressChild,
      cancelBtn,
 
    fileTransferProgressDisplay = document.createElement("div");
    fileTransferProgressDisplay.id = fileTransferId;
    fileTransferProgressDisplay.className = "progress";
    fileTransferProgressDisplay.style.width = "85%";
    fileTransferProgressDisplay.style.float = "left";
 
    fileNameNode = document.createElement("div");
    fileNameNode.innerText = fileConfig.props.name;
    fileNameNode.style.position = "absolute";
    fileNameNode.style.textAlign = "center";
    fileNameNode.style.left = "3%";
    fileNameNode.style.color = "white";
    fileTransferProgressDisplay.appendChild(fileNameNode);

    // Append progress bar
    progressBar = document.createElement("div");
    progressBar.className = "bar";
    progressBar.style.width = "0%";
    fileTransferProgressDisplay.appendChild(progressBar);

    // Append the cancel button
    cancelBtn = document.createElement("button");
    cancelBtn.id = "cancelBtn";
    cancelBtn.type = "button";
    cancelBtn.innerText = "Cancel";
    cancelBtn.style.marginLeft = "2%";
    cancelBtn.style.marginBottom = "20px";
    cancelBtn.onclick = function() {
      wscFileTransfer.abort(fileConfig);
      cancelBtn.disabled = 'true';
    };
 
    if (fileTransferDisplayArea.hasChildNodes()) {
      lastProgressChild = fileTransferDisplayArea.lastChild;
      fileTransferDisplayArea.insertBefore(fileTransferProgressDisplay,
                                                         lastProgressChild.nextSibling);
      fileTransferDisplayArea.appendChild(cancelBtn);
    } else {
      fileTransferDisplayArea.appendChild(fileTransferProgressDisplay);
      fileTransferDisplayArea.appendChild(cancelBtn);
    }
  }
}

Handle File Transfer Progress Updates

Define the onProgress event handler for handling file transmission progress data. The onProgress event handler returns a fileProgressData object which is the same as the fileData object but lacks the actual file content data:

  • fileTransferID: A string containing the file transfer ID (defined by WebRTC Session Controller).

  • range: A FileDataRange object containing the total range of data in the file. This object comprises the following properties:

    • start: A number indicating the first byte of the data.

    • end: A number indicating the final byte of the data.

    • total: A number indicating the total size of the data in bytes.

In Example 8-32, when a progress event occurs, the fileTransferId data object attribute is passed to the showFileTransferProgress function described in "Handle File Data Transmission," and the progress bar updated in the same manner.

Example 8-32 onProgress Sample Code

function onProgress(data) {
  var 
    fileTransferId = data.fileTransferId;

  if (!document.getElementById(fileTransferId)) {
    showFileTransferProgress(fileTransferId);
  }

  var
    progressPercent = Math.ceil(data.range.end/data.range.total*100),
    progressBar = document.getElementById(fileTransferId).childNodes[1];

  if (progressBar) {
    progressBar.style.width = progressPercent+"%";
  }
}

function showFileTransferProgress(fileTransferId) {
  // See Example 8-31...
}