Source: shouldInvite.js

/**
 * yalst-trunk: shouldInvite.js
 *
 * Created by Matthias Seemann on 4.06.2014.
 * Copyright (c) 2014 Visisoft OHG. All rights reserved.
 *
 */

/**
 * The callback taken by {@link LiveSupport.VisitorAPI.shouldInvite}.
 *
 * @callback shouldInviteCallback
 * @param {boolean} shouldDisplayActiveInvitation If `true` the calling web page should present an visual layer to invite the visitor to a chat.
 * @param {Error} error  Error set if a network error has occurred.
 */

define(['underscore', 'api/errorCodes', 'api/configuration', 'api/ManagedError', 'lib/DataFormats','lib/ajax']
, function(_, ErrorCode, Configuration, ManagedError, DataFormats, ajax)
{
	var
		jsonPScriptTag;

	/**
	 * Checks whether the embedding page should present a chat invitation layer at this time.<br>
	 * Using this method allows such a distribution of chat invitations among all visitors so that
	 * the chat load for the operators is kept constant. <br>
	 * The result of the call depends on the number of available operators, number of running chats
	 * , the priority parameter and the number of chat invitations given by yalst recently
	 * to other visitors.
	 * For optimal chat workload of the support agents
	 * the caller should always present a chat invitation to the visitor if the result is <tt>true</tt>.
	 * <br><strong>Throws</strong> {@link LiveSupport.ManagedError} in case any or a single parameter is given which is neither a number nor a function
	 *
	 * @memberof LiveSupport.VisitorAPI
	 *
	 * @param {Number} [priority=0] a number from 0 to 100.
	 * If invitation priority is enabled, and the number of available chat slots is below
	 * the configured threshold, the importance of a chat invitation
	 * is assessed by this parameter.
	 *
	 * @param {shouldInviteCallback} onResult result callback
	 *
	 * @since 2.1
	 *
	 * @example
	 // check if a web page visitor of highest priority should receive an invitation for a support chat
	 LiveSupport.VisitorAPI.shouldInvite(100
	, function(shouldInvite, error){
		if (error)
		{
			// deal with the error, description in error.message
		}
		else
		{
			if (shouldInvite){
				$('#invitationLayer').show();
			}
		}
	}
);
	 *
	 */
	function shouldInvite(priority, onResult)
	{
		var priorityPercent = 0;
		var callback = function(yesno, error){};

		/// Parameter Inspection

		switch (arguments.length)
		{
			case 0:
				throw new ManagedError(ErrorCode.ParameterTypeError
					, "Call to shouldInvite has no parameters!");
			case 1:
				if (typeof priority == "number")
				{
					priorityPercent = priority;
				}
				else if (typeof priority == "function")
				{
					//noinspection JSValidateTypes
					callback = priority;
				}
				else
				{
					throw new ManagedError(ErrorCode.ParameterTypeError
					, "Call to shouldInvite has wrong parameter!");
				}
				break;
			case 2:
				if (typeof priority == "number" && typeof onResult == "function")
				{
					priorityPercent = priority;
					//noinspection JSValidateTypes
					callback = onResult;
				}
				else
				{
					throw new ManagedError(ErrorCode.ParameterTypeError
					, "Call to shouldInvite has wrong parameter types!");
				}
				break;
		}

		/// Check our Sanity

		if (!Configuration.configuration.IS_ASSOCIATED)
		{
			_.defer(_.bind(callback, null, undefined
				, new ManagedError(ErrorCode.ServerNotYetAssociated
				, "Illegal API use. Attempt to call a method before associated to a live support product.")
			));

			return;
		}

		if (priorityPercent < 0 || 100 < priorityPercent)
		{
			_.defer(_.bind(callback, null, undefined
				, new ManagedError(ErrorCode.ParameterTypeError, "Parameter out of range.")
			));

			return;
		}


		/// Wire Result Reception Handlers


		function onApiResult(response)
		{
			if (response["shouldInvite"] !== undefined
				&& typeof response["shouldInvite"] == "boolean")
			{
				callback(response["shouldInvite"]);

				return;
			}

			var errorText = response["error"] || "Unknown error";

			callback(undefined, new ManagedError(ErrorCode.InternalError, errorText));
		}

		var queryParameters = {rnd: Math.random()
			, site: Configuration.configuration.PRODUCT_SITE
			, priority: priorityPercent
		};


		/// Network Request


		if (ajax.isCORSSupported() && ajax.isJsonSupported()) /// JSON (IE10+)
		{
			var didAbortAjaxRequest = false;
			var responseDurationTimer;

			var request = ajax.getJson(
				Configuration.configuration.PRODUCT_URL + 'visitor-api.shouldinvite.php?'
				+ DataFormats.urlQueryStringFromObject(queryParameters)
			, function(response, error)
				{
					clearTimeout(responseDurationTimer);

					if (error)
					{
						if (!didAbortAjaxRequest)
						{
							callback(undefined, new ManagedError(ErrorCode.NetworkError
								, "Network error: " + error.message)
							);
						}

						return;
					}

					onApiResult(response);
				}
			);

			responseDurationTimer = setTimeout(function()
			{
				didAbortAjaxRequest = true;

				request.abort();

				callback(undefined, new ManagedError(ErrorCode.TimeoutError
					, "The shouldInvite request has timed out!"));

			}, Configuration.configuration.SERVER_TIMEOUT_SECS * 1000);
		}
		else
		{
			/// JSONP for non CORS browsers IE9-

			var jsonPCallbackName = _.uniqueId('_shouldInviteCallback');

			/// 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[jsonPCallbackName] =
				function(response)
				{
					clearTimeout(responseDurationTimer);

					onApiResult(response);
				};

			queryParameters.callback = Configuration.configuration.GLOBAL_NAMESPACE
				+ ".VisitorAPI." + jsonPCallbackName;

			if (jsonPScriptTag)
			{
				jsonPScriptTag.parentNode.removeChild(jsonPScriptTag);
			}

			jsonPScriptTag = document.createElement("script");
			var siblingElement = document.getElementsByTagName("script")[0];
			siblingElement.parentNode.insertBefore(jsonPScriptTag, siblingElement);

			jsonPScriptTag.src = Configuration.configuration.PRODUCT_URL + 'visitor-api.shouldinvite.php?'
				+ DataFormats.urlQueryStringFromObject(queryParameters);

			responseDurationTimer = setTimeout(function()
				{
					callback(undefined, new ManagedError(ErrorCode.TimeoutError
						, "The shouldInvite request has timed out!"));

					/// in case the network request eventually completes,
					// prevent result callbacks being called a second time
					queryParameters.callback = function(){};
				}
				, Configuration.configuration.SERVER_TIMEOUT_SECS * 1000
			);
		}
	}


	return shouldInvite;
});