/**
* operatorAvailability.js
*
* Created by Matthias Seemann on 7.01.2013.
* Copyright (c) 2013 Visisoft GbR. All rights reserved.
*
* @module operatorAvailability
* @desc internal module
*/
define(['underscore', 'api/errorCodes', 'api/configuration', 'api/ManagedError', 'lib/DataFormats','lib/throttledNQueued', 'lib/ajax']
, function(_, ErrorCode, Configuration, ManagedError, DataFormats, throttledNQueued, ajax)
{
/**
* Possible results of the availability check
* @enum {string}
* @readonly
* @memberof LiveSupport.VisitorAPI
*/
var Availability = {
AVAILABLE : 'online',
BUSY : 'busy',
OFFLINE : 'offline'
};
var availabilityCheckScriptTag;
var callbacksByDepartments = {};
var timerByDepartments = {};
/**
* Checks the availability status of the live support service.<br>
* Possible result values are {@link LiveSupport.VisitorAPI.Availability}.<br/>
* While a query is in progress identical queries with the same department id
* do not result in a new network request.<br><br>
* <em>Please note:</em> On Internet Explorer 9 and older this method currently uses JSONP for network I/O.
* Therefore on that browsers this method is internally <em>throttled</em> in that way
* that is executes at most once every 20 seconds. In the meantime incoming calls are queued.
* This is in order to prevent a performance drain from the browser and server.
* <small>(This behaviour is due missing support for Ajax CORS in IE9 and older.)</small>
* <br><br><strong>Throws</strong> {@link LiveSupport.ManagedError} in case the callback is missing.
* @param {resultCallback} callback the user callback which receives a result
* of {@link LiveSupport.VisitorAPI.Availability}
* @param {string} [department=the previously associated department id] a particular yalst department id,
* if empty string any department applies
* @memberof LiveSupport.VisitorAPI
* @see module:operatorAvailability~_availabilityCallback
* @see module:operatorAvailability~abortUnfinishedRequest
* @example
LiveSupport.VisitorAPI.getOperatorAvailability(onAvailabilityDetected, "C");
function onAvailabilityDetected(status){
if (status instanceof Error){
alert("An error has occurred:" + status.toString());
}
else if (status == LiveSupport.VisitorAPI.Availability.AVAILABLE){
var startButton = document.getElementById('start_button_dept_C');
startButton.removeAttribute("disabled");
}
}
*/
function getOperatorAvailability(callback, department)
{
if (typeof callback != "function")
{
throw new ManagedError(ErrorCode.ParameterTypeError
, "Call to getOperatorAvailability is missing callback parameter!");
}
if (!Configuration.configuration.IS_ASSOCIATED)
{
_.defer(_.bind(callback, null, new ManagedError(ErrorCode.ServerNotYetAssociated
, "Illegal API use. Attempt to call a method before associated to a live support server.")));
return;
}
var queryParameters = {rnd: Math.random(), site: Configuration.configuration.PRODUCT_SITE};
if ((typeof department == "string") && (department.length > 0))
{
queryParameters.dept = department;
}
else if ((typeof department != "string")
&& (typeof Configuration.configuration.PRODUCT_DEPARTMENT == "string")
&& (Configuration.configuration.PRODUCT_DEPARTMENT.length > 0))
{
queryParameters.dept = Configuration.configuration.PRODUCT_DEPARTMENT;
}
if (ajax.isCORSSupported() && ajax.isJsonSupported())
{
var didAbortAjaxRequest = false;
var responseDurationTimeout;
queryParameters.ajax = 1;
var request = ajax.getJson(
Configuration.configuration.PRODUCT_URL + 'online.js.php?'
+ DataFormats.urlQueryStringFromObject(queryParameters)
, function(response, error)
{
clearTimeout(responseDurationTimeout);
if (error)
{
if (!didAbortAjaxRequest)
{
callback(new ManagedError(ErrorCode.NetworkError
, "Network error: " + error.message)
);
}
return;
}
if (_.contains(["online", "busy", "offline"], response["availability"]))
{
callback(response["availability"]);
return;
}
var errorText = response["error"] || "Unknown error";
callback(new ManagedError(ErrorCode.InternalError, errorText));
}
);
responseDurationTimeout = setTimeout(function()
{
didAbortAjaxRequest = true;
request.abort();
callback(new ManagedError(ErrorCode.TimeoutError
, "The operatorAvailability request has timed out!"));
}, Configuration.configuration.SERVER_TIMEOUT_SECS * 1000);
}
else
{
/// JSONP for non CORS browsers IE9-
var departmentKey = (typeof queryParameters.dept != "undefined") ? queryParameters.dept : "allDepartments";
if (!(departmentKey in callbacksByDepartments))
{
callbacksByDepartments[departmentKey] = [];
}
callbacksByDepartments[departmentKey].push(callback);
/// collect identical queries
if (callbacksByDepartments[departmentKey].length > 1)
{
return;
}
/// introduce a globally visible callback function for the returned script
/// which is just a curried version of the general _availabilityCallback
/// for JSONP we must use global variables even if loaded as RequireJS module
var namespace = window[Configuration.configuration.GLOBAL_NAMESPACE]
= window[Configuration.configuration.GLOBAL_NAMESPACE] || {};
namespace.VisitorAPI = namespace.VisitorAPI || {};
namespace.VisitorAPI["_availabilityCallback" + departmentKey]
= _.bind(_availabilityCallback, null, departmentKey);
queryParameters.callback = Configuration.configuration.GLOBAL_NAMESPACE
+ ".VisitorAPI._availabilityCallback" + departmentKey;
if (availabilityCheckScriptTag)
{
availabilityCheckScriptTag.parentNode.removeChild(availabilityCheckScriptTag);
}
availabilityCheckScriptTag = document.createElement("script");
var siblingElement = document.getElementsByTagName("script")[0];
siblingElement.parentNode.insertBefore(availabilityCheckScriptTag, siblingElement);
availabilityCheckScriptTag.src = Configuration.configuration.PRODUCT_URL + 'online.js.php?'
+ DataFormats.urlQueryStringFromObject(queryParameters);
timerByDepartments[departmentKey] = setTimeout(_.bind(abortUnfinishedRequest, null, departmentKey)
, Configuration.configuration.SERVER_TIMEOUT_SECS * 1000);
}
}
/**
* Internal callback function for <tt>getOperatorAvailability</tt>.<br>
* It gets called by the Javascript engine during execution of an externally loaded script.
* <br><br><strong>Throws</strong> {@link LiveSupport.ManagedError} in case the user callback can not be found
* @param {string} departmentKey
* @param {string} status
* @function module:operatorAvailability~_availabilityCallback
* @inner
*
*/
function _availabilityCallback(departmentKey, status)
{
if (!(departmentKey in callbacksByDepartments)
|| (typeof callbacksByDepartments[departmentKey].length != "number"))
{
throw new ManagedError(ErrorCode.InternalError,
"user callback for operatorAvailability in department '" + departmentKey + "' not found!");
}
var result;
switch (status)
{
case "offline":
result = Availability.OFFLINE;
break;
case "online":
result = Availability.AVAILABLE;
break;
case "busy":
result = Availability.BUSY;
break;
default:
result = new ManagedError(ErrorCode.InternalError
, "Unexpected result returned from server:" + status);
}
var userCallbacks = callbacksByDepartments[departmentKey];
while (userCallbacks.length)
{
var callback = userCallbacks.shift();
callback(result);
}
var timer = timerByDepartments[departmentKey];
if (timer)
{
clearTimeout(timer);
timerByDepartments[departmentKey] = 0;
}
}
/**
* a <tt>setTimeout</tt> callback.
* <br><br><strong>Throws</strong> {@link LiveSupport.ManagedError} in case the user callback can not be found
* @function module:operatorAvailability~abortUnfinishedRequest
* @param {string} departmentKey
*
*/
function abortUnfinishedRequest(departmentKey)
{
if (!(departmentKey in callbacksByDepartments)
|| (typeof callbacksByDepartments[departmentKey].length != "number"))
{
throw new ManagedError(ErrorCode.InternalError,
"user callback for operatorAvailability in department '" + departmentKey + "' not found!");
}
var userCallbacks = callbacksByDepartments[departmentKey];
while (userCallbacks.length)
{
var callback = userCallbacks.shift();
callback(new ManagedError(ErrorCode.TimeoutError, "The operatorAvailability request has timed out!"));
}
timerByDepartments[departmentKey] = 0;
}
return {Availability : Availability
, getOperatorAvailability : (ajax.isCORSSupported() && ajax.isJsonSupported())
? getOperatorAvailability
: throttledNQueued(getOperatorAvailability, 20000)
};
});