Event Subscriptions and Messaging

The subscriptions and messaging APIs in ICWS provide a way for applications to be informed when changes to the Interaction Center system take place. For more information about the subscription concept, see the Design Principles page.

This topic contains the following sections:

Managing Subscriptions

Subscriptions are how client applications express interest in receiving messages about certain changes in the Interaction Center system. Resources with paths starting with /icws/{sessionId}/messaging/subscriptions/... are provided to manipulate each subscription. Subscriptions are created and modified using the PUT method and canceled by using the DELETE method. Every subscription has an associated message type that client applications can expect when that subscription is active.

Back to top

JavaScript Example

An example subscription is for the statuses of various users in the Interaction Center system. To create this subscription, the client application must use the PUT /icws/{sessionId}/messaging/subscriptions/status/user-statuses method and provide a list of userIds that the application is interested in receiving status information for. The following shows an example of how a client application might create a user status subscription for two users whose userIds are user1 and user2.

// This example snippet assumes the existence of a helper
// method for sending a request to the active ICWS session.
var sendRequest = function(httpMethod, path, requestPayload, responseCallback) { };

// The type of the message associated with the user-statuses subscription
userStatusMessageType = 'urn:inin.com:status:userStatusMessage'

/**
 * Starts the subscription for users statuses for user1 and user2.
 * @see stopUserStatusSubscription
 */
function startUserStatusSubscription() {
    // The PUT request requires a list of userIds
    payload = { userIds:['user1', 'user2'] };

    session.sendRequest('PUT', '/messaging/subscriptions/status/user-statuses', payload, 
        function(status, jsonResponse) {
            if ((status >= 200) && (status <= 299)) {
                // Subscription was started
            }
            else {
                // There was an error starting the subscription.
                // Details are in the jsonResponse.
                throw new Error(jsonResponse)
            }
        });
}

/**
 * Stops the subscription for users statuses.
 * @see startUserStatusSubscription
 */
function stopUserStatusSubscription() {
    // The DELETE request does not take any payload values.
    payload = {};

    session.sendRequest('DELETE', '/messaging/subscriptions/status/user-statuses', payload, 
        function(status, jsonResponse) {
            if ((status >= 200) && (status <= 299)) {
                // Subscription was stopped
            }
            else {
                // There was an error stopping the subscription.
                // Details are in the jsonResponse.
                throw new Error(jsonResponse)
            }
        });
}

function displayUserStatuses(userStatusMessage) {
    var statusIndex, statusCount, currentStatus;
    
    if (!userStatusMessage.isDelta) {
        // If the message is not a delta, we must clear all previous 
        // information and prepare for refreshed information
        clearStatusDisplay();
    }
    
    // process the userStatusMessage to update the display
    statusCount = userStatusMessage.userStatusList.length;
    for (statusIndex = 0; statusIndex < statusCount; statusIndex++) {
        currentStatus = userStatusMessage.userStatusList[statusIndex];
        
        updateStatusForUser(currentStatus.userId, currentStatus.statusId);
    }
}

// Register a callback with the message channel (see example in the Retrieving Messages section)
// Note: The registration happens before the subscription is started to ensure no messages are missed.
registerMessageCallback(userStatusMessageType, displayUserStatuses);

// Start the subscription
startUserStatusSubscription();

// The subscription can be stopped using the following:
stopUserStatusSubscription();

Back to top

Retrieving Messages

There are two potential ways of receiving event messages in a client application:

With the first method - short-polling - event messages are queued in the messaging channel on the ICWS server until the client application requests the contents where upon the messages are removed from the queue and sent to the application. The messages are retrieved through the GET /icws/{sessionId}/messaging/messages method. Client applications should periodically poll this resource to obtain the messages.

With the second method - server-sent events - event messages are sent in the messaging channel on the ICWS server as they become available. In this case, it is not necessary to periodically poll the GET /icws/{sessionId}/messaging/messages resource; however, an appropriate event source object must be instantiated. Prior to utilizing server-sent events, client applications should ensure that this method is supported on the ICWS server. This can be accomplished by checking the GET /icws/connection/features resource for the messaging feature. Server sent events are supported for messaging version 2 and later. Alternatively, a client application can retrieve the supported feature versions from the features property of the 201 HTTP response of POST /icws/connection if the include query parameter is supplied that includes the features value. For more information about handling versioning, see the Versioning page.

Note

At the time of writing, server-sent events are not supported on Internet Explorer 11 (and older) or Microsoft Edge. According to https://dev.windows.com/microsoft-edge/platform/status/serversenteventseventsource, it is under consideration for a future release of Microsoft Edge. When writing client applications, this should be taken into consideration.

The ICWS server will attempt to queue all messages until the queue becomes full. When messages are no longer able to be enqueued or sent due to a full queue, the session will be terminated by the server. To prevent this situation, it is important for client applications to retrieve messages on a regular basis if using short-polling, or to ensure that a proper event source is setup in case of server-sent events. If using short-polling, it is recommended to poll every second to help ensure messages are received in a timely manner and to help prevent disconnects.

Back to top

JavaScript Example

The following provides a simple example of retrieving pending messages from JavaScript.

// This example snippet assumes the existence of a helper
// method for sending a request to the active ICWS session.
var sendRequest = function(httpMethod, path, requestPayload, responseCallback) { };

// This example snippet assumes the existence of a helper function:
// A function determining whether server-sent events messaging is supported by inspecting the messaging feature version (supported in messaging version 2).
var isServerSentEventsSupportedOnServer = function() {};

// Dictionary of ICWS message __type ID to the callback (type: icwsMessageCallback)
// to invoke when that message is received.
var icwsMessageCallbacks = {};
var messageProcessingTimerId;
var eventSourceInstance;

// Polling interval for retrieving ICWS message queue.
var ICWS_MESSAGE_RETRIEVAL_INTERVAL_MS = 1000;

/**
 * Sets the callback for a particular type of ICWS message.
 * @param {String} messageType The ICWS message type. (ex: urn:inin.com:status:userStatusMessage)
 * @param {icwsMessageCallback} messageCallback The callback to invoke with the message details.
 * @throws {Error} The messageCallback was undefined.
 * @throws {Error} A callback is already registered for the specified messageType.
 */
function registerMessageCallback(messageType, messageCallback) {
    if (messageCallback === undefined) {
        throw new Error('Invalid argument "messageCallback".');
    }
    
    if (!icwsMessageCallbacks[messageType]) {
        icwsMessageCallbacks[messageType] = messageCallback;
    } else {
        throw new Error('Message callback already registered for message type: ' + messageType);
    }
};

/**
 * Starts the message processing mechanism, if not already running.
 * @see stopMessageProcessing
 */
function startMessageProcessing() {
    if (isServerSentEventsSupportedOnServer() && typeof EventSource !== 'undefined') {
        if (!eventSourceInstance || eventSourceInstance.readyState === EventSource.CLOSED) {
            var messagingUrl = getSessionUrl('/messaging/messages');
 
            eventSourceInstance = new EventSource(messagingUrl, {
                withCredentials: true // Allows the Cookie HTTP request header to be sent
            });
 
            eventSourceInstance.onmessage = onServerSentEventMessage;
        }
    } else {
        // Only send the next request once the previous result has been received.
        function runTimerInstance() {
            messageProcessingTimerCallback();
            messageProcessingTimerId = setTimeout(runTimerInstance, ICWS_MESSAGE_RETRIEVAL_INTERVAL_MS);
        }
        
        if (!messageProcessingTimerId) {
            runTimerInstance();
        }
    }
}

/**
 * Stops the message processing mechanism, if running.
 * @see startMessageProcessing
 */
function stopMessageProcessing() {
    if (eventSourceInstance) {
        if (eventSourceInstance.readyState !== EventSource.CLOSED) {
            eventSourceInstance.stop();
        }
    } else if (!!messageProcessingTimerId) {
        clearTimeout(messageProcessingTimerId);
        messageProcessingTimerId = null;
    }
}

/**
 * Implements the message processing mechanism timer callback.
 * @see startMessageProcessing
 * @see stopMessageProcessing
 */
function messageProcessingTimerCallback() {
    var payload, messageIndex, messageCount, jsonMessage, messageType, messageCallback;
    
    // The messaging GET request does not take any payload values.
    payload = {};

    sendRequest('GET', '/messaging/messages', payload, function(status, jsonResponse) {
        if ((status >= 200) && (status <= 299)) {
            // Process retrieved messages.
            messageCount = jsonResponse.length;
            for (messageIndex = 0; messageIndex < messageCount; messageIndex++) {
                jsonMessage = jsonResponse[messageIndex];
                messageType = jsonMessage.__type;
                
                // For each message, invoke a registered message callback if there is one.
                messageCallback = icwsMessageCallbacks[messageType];
                if (messageCallback) {
                    messageCallback(jsonMessage);
                } else {
                    // No registered message handler.
                }
            }
        }
    });
    
 /**
 * Handles eventSourceInstance's message event and relays it to the correct callback.
 * @param {Event} event The onmessage event data.
 * @see stopMessageProcessing
 */
function onServerSentEventMessage(event) {
    var message, messageType, messageCallback;
    try {
       // Process the data off of the event. It is a JSON string.
       var message = JSON.parse(event.data);
       messageType = message.__type;
 
       // Invoke a registered message callback if there is one.
       messageCallback = icwsMessageCallbacks[messageType];
       if (messageCallback) {
           messageCallback(jsonMessage);
       } else {
           // No registered message handler.
       }
    }
    catch (error) {
       // Failed to process message.
    }
}
}

Back to top