Source: ajaxMonitor.js

/*!
 * ajaxMonitor
 * https://github.com/Voliware/AjaxPlus
 * Licensed under the MIT license.
 */

/**
 * Monitors all ajax requests made in the application.
 * If enough requests fail in a certain time frame,
 * AjaxMonitor determines the connection to be lost.
 * It then begins a heartbeat and blocks all other requests
 * until at least on heartbeat requests is responded to
 */
class AjaxMonitor extends EventSystem {

	/**
	 * Constructor
	 * @param {object} options
	 * @param {string} options.heartbeatUrl - URL to send a heartbeat req to
	 * @param {number} [options.maxFailures=4] - the max amount of ajax failures
	 * that can occur in the ajaxTimeout setting, before the monitor determines
	 * the connection ist lost
	 * @param {number} [options.resetTimer=10000] - the time in which the max amount of
	 * failures can occur before the monitor determines the connection is lost
	 * @param {number} [options.ajaxTimeout=0] - the global timeout for all ajax reqs
	 * @param {boolean} [options.abortOnDisconnecttrue] - whether to abort all requests
	 * before they can be made if AjaxMonitor is "disconnected"
	 * @returns {AjaxMonitor}
	 */
	constructor(options){
		if (!isDefined(options))
			throw new ReferenceError("AjaxMonitor.constructor: options.heartbeatUrl argument is required.");

		super();
		var self = this;
		var defaults = {
			heartbeatUrl : '',
			maxFailures : 4,
			ajaxTimeout : 0,
			resetTimer : 10000,
			abortOnDisconnect : true
		};
		this.settings = $.extend(defaults, options);

		// set up a heart beat for determining
		// connection status. Don't start it yet.
		this.heartbeat = new AjaxModule({
			url : this.settings.heartbeatUrl,
			data : {heartbeat:1},
			interval : 5000
		});

		this.interval = null;
		this.failures = 0;
		this.isConnected = true;

		// set the global ajax timeout
		if(this.settings.ajaxTimeout > 0)
			$.ajaxSetup({timeout : this.settings.ajaxTimeout});

		// intercept all ajax requests
		(function(open) {
			XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
				this.addEventListener("readystatechange", function() {
					if(this.readyState === 1){
						// stop all ajax attempts if we're disconnected
						// but allow heartbeat to always go through
						if(self.settings.abortOnDisconnect && !self.isConnected && url.indexOf('heartbeat') === -1){
							this.abort();
						}
					}
				}, false);
				this.addEventListener("abort", function() {
					self._fail();
				}, false);
				this.addEventListener("error", function() {
					self._fail();
				}, false);
				this.addEventListener("timeout", function() {
					self._fail();
				}, false);
				this.addEventListener("load", function() {
					self._done();
				}, false);
				return open.apply(this, arguments);
			};
		})(XMLHttpRequest.prototype.open);

		return this;
	}

	/**
	 * Success handler when any
	 * ajax req is successful
	 * @returns {AjaxMonitor}
	 * @private
	 */
	_done(){
		this.failures = 0;
		this.checkLimit();
		return this;
	}

	/**
	 * Failure handler when
	 * any ajax req fails
	 * @returns {AjaxMonitor}
	 * @private
	 */
	_fail(){
		this.failures++;
		this.checkLimit();
		return this;
	}

	/**
	 * Start the monitor
	 * @returns {AjaxMonitor}
	 */
	start(){
		this.interval = setInterval(this.reset, this.settings.interval);
		return this;
	}

	/**
	 * Stop the monitor
	 * @returns {AjaxMonitor}
	 */
	stop(){
		clearInterval(this.interval);
		return this;
	}

	/**
	 * Reset the recorded failure count
	 * @returns {AjaxMonitor}
	 */
	reset(){
		this.failures = 0;
		return this;
	}

	/**
	 * Check if enough failures have occurred
	 * in the given time frame
	 * @returns {AjaxMonitor}
	 */
	checkLimit(){
		var prevState = this.isConnected;
		this.isConnected = this.failures <= this.settings.maxFailures;

		// only trigger on change from prev state
		if(prevState !== this.isConnected) {
			if (this.isConnected){
				this.trigger('connected');
				this.start();
				this.heartbeat.stop();
			}
			else{
				this.trigger('disconnected');
				this.stop();
				this.heartbeat.start();
			}
		}
		return this;
	}
}