Ajax Interception

The Problem

I recently had a problem where I needed to intercept any AJAX based calls from the browser to, well, anything. After a lot of Googling and digging through StackOverflow all I ever found were suggestions for event listeners specific to JQuery or partial solutions that didn’t really work.

There are many instances where you want to grab all the AJAX calls being made. For debugging, for interception to override existing functionality, for parameter injection, etc.

The Solution

The code I wrote is a JQuery plug in but should work for any AJAX calls even if not in JQuery code.

First, if you are not familiar with basic AJAX functionality, it’s all based around the XMLHttpRequest object. I suggest you read W3Schools and Mozilla’s descriptions.

Lets look at the code now. Source code available here: ajaxInterceptor.js

/*
 * Util for intercepting all ajax calls 
 * 
 * Options:
 * open : {
 *   fn : function() {}, // function to call when open is called
 *   scope : xxx         // Scope to execution function in
 * },
 * send  : {
 *   fn : function() {}, // function to call when set is called
 *   scope : xxx         // Scope to execution function in
 * },
 * setRequestHeader  : {
 *   fn : function() {}, // function to call when setRequestHeader is called
 *   scope : xxx         // Scope to execution function in
 * },
 */

(function( $ ) {
	var defaultOptions = {
		open 				:  { },
		send 				:  { },
		setRequestHeader  	:  { }
	}

	var options;
	var aiOpen = window.XMLHttpRequest.prototype.open;
	var aiSend = window.XMLHttpRequest.prototype.send;
	var aiSet  = window.XMLHttpRequest.prototype.setRequestHeader;
	var recurrsion = false;
	
	var methods = {
		init : function(opts) {
			options = $.extend(true, defaultOptions, opts);
			methods.enable();
		},
		
		enable : function() {
			window.XMLHttpRequest.prototype.open = function(method,url,async,uname,pswd) {
				if (options.open.fn) {
					options.open.fn.call(options.open.scope?options.open.scope:this, 
						method,
						url,
						async,
						uname,
						pswd);
				}
				aiOpen.call(this, method,url,async,uname,pswd);
			};
			
			window.XMLHttpRequest.prototype.send = function(data) {
				if (options.send.fn && !recurrsion) {
					recurrsion = true;
					options.send.fn.call(options.send.scope?options.send.scope:this, 
						data);
					recurrsion = false;
				}
				aiSend.call(this, data);
			};
			
			window.XMLHttpRequest.prototype.setRequestHeader = function(key, value) {
				if (options.setRequestHeader.fn) {
					options.setRequestHeader.fn.call(options.setRequestHeader.scope?options.setRequestHeader.scope:this, 
						key, 
						value);
				}
				aiSet.call(this, key, value);
			};
		},
		
		disable : function() {
			window.XMLHttpRequest.prototype.open = aiOpen;
			window.XMLHttpRequest.prototype.send = aiSend;
			window.XMLHttpRequest.prototype.setRequestHeader = aiSet;
		}
	}

	$.fn.ajaxInterceptor = function( method ) {
		if ( methods[method] ) {
			return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on ajaxInterceptor' );
		}    
	};
})(jQuery);

Lets go over the code so you can understand what is going on.

	var defaultOptions = {
		open 				:  { },
		send 				:  { },
		setRequestHeader  	:  { }
	}

First thing I do is set up the defaults that developers can override as well as ensure that I don’t get any ‘undefined’ errors in the javacript.

	var options;
	var aiOpen = window.XMLHttpRequest.prototype.open;
	var aiSend = window.XMLHttpRequest.prototype.send;
	var aiSet  = window.XMLHttpRequest.prototype.setRequestHeader;
	var recurrsion = false;

Next I define some variables but the key thing to pay attention to is the aiOpen, aiSend, and aiSet variables. What this is actually doing is taking the XMLHttpRequest‘s open, send, and setRequestHeader methods and placing them into variables. I can use this later to disable whatever I do in the code by reverting my changes to these variables.

init : function(opts) {
	options = $.extend(true, defaultOptions, opts);
	methods.enable();
},

The init() method does nothing more that take in any options the developer passes in, use them to override the defaultOptions and store the resulting combination of the two into the options variable. After that it calls the enable() method.

window.XMLHttpRequest.prototype.open = function(method,url,async,uname,pswd) {
	if (options.open.fn) {
		options.open.fn.call(options.open.scope?options.open.scope:this, 
			method,
			url,
			async,
			uname,
			pswd);
	}
	aiOpen.call(this, method,url,async,uname,pswd);
};

The first part of the enable method overrides the XMLHttpRequest‘s open method with my method. All my method does is call a method that the developer might have passed via the options and then calls the normal open method. This way we can run whatever method we want before the open is actually executed.

The next two methods are just variations on a theme. The send and setRequestHeader do the exact same thing as open so I won’t go over them (hope you don’t mind)

disable : function() {
	window.XMLHttpRequest.prototype.open = aiOpen;
	window.XMLHttpRequest.prototype.send = aiSend;
	window.XMLHttpRequest.prototype.setRequestHeader = aiSet;
}

As I mentioned before, one of the main reasons I placed the methods in variables is to undo what I overwrote. In this case I’m just restoring the original functionality of the XMLHttpRequest object any time the disable is called.

Example Usage

Lets now show a few ways this can be used. The most common would be to intercept any calls to the AJAX service on the server side. This is in XMLHttpRequest object terms, the send call.

<script src="jquery-1.7.2.min.js"></script>
<script src="ajaxInterceptor.js"></script>
<script>
$(document).ajaxInterceptor({
	send : {
		fn : function(data) { alert('send'); }
	}
});

First we need to bring in the JQuery library and of course the ajaxInterceptor library itself. Next we attach it to the document as that is basically the root of what we want (basically we are just attaching it to one of the most parent objects but really we could attach it to anything).

We then pass in the option “send” and a function that will alert('send') any time send is called.

That’s all there is to it! If you put your code in the fn: function and it will be executed whenever any AJAX send is called.

I should note that the data that is passed in is the same data object that would be passed to the underlying XMLHttpRequest send function. Also, the send can be passed a scope if you want your method to execute in a specific scope.

The next example is how to disable the code if you ever need to.

$(document).ajaxInterceptor("disable");

Again, pretty easy. The code follows the standard JQuery plug-in methodology.

I hope this code helps you out. I really could not find any good examples anywhere that were as encompassing as this one.

About sseaman

Connect with me on Google+
This entry was posted in JavaScript, Programming and tagged , , . Bookmark the permalink.

1 Response to Ajax Interception

Leave a Reply

Your email address will not be published.