Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monkey patch XMLHTTPRequest.onreadystatechange

How would go about monkey patching the XMLHTTPRequest's onreadystatechange function. I'm trying to add a function that would be called when every ajax request made from a page come back.

I know this sounds like a terrible idea, but the use case is quite peculiar. I want to use a certain SDK with a console (jqconsole) but show status and results from ajax calls within the console without modifying the external SDK.

I've looked at this post which had great info, but nothing on monkey patching the callback which seem to exceed my JavaScript skills.

P.S Can't use jQuery since it only supports ajax calls made from jQuery not from XMLHTTPRequests directly which is the case here.

like image 909
MGA Avatar asked Sep 25 '12 02:09

MGA


1 Answers

To monkey-patch XMLHttpRequests, you need to know how an AJAX request is generally constructed:

  1. Constructor invocation
  2. Preparation the request (setRequestHeader(), open())
  3. Sending the request (.send).

General-purpose patch

(function(xhr) {
    function banana(xhrInstance) { // Example
        console.log('Monkey RS: ' + xhrInstance.readyState);
    }
    // Capture request before any network activity occurs:
    var send = xhr.send;
    xhr.send = function(data) {
        var rsc = this.onreadystatechange;
        if (rsc) {
            // "onreadystatechange" exists. Monkey-patch it
            this.onreadystatechange = function() {
                banana(this);
                return rsc.apply(this, arguments);
            };
        }
        return send.apply(this, arguments);
    };
})(XMLHttpRequest.prototype);

The previous assumed that onreadystatechange was assigned to the onreadystatechange handler. For simplicity, I didn't include the code for other events, such as onload. Also, I did not account for events added using addEventListener.

The previous patch runs for all requests. But what if you want to limit the patch to a specific request only? A request with a certain URL or async flag and a specific request body?

Conditional monkey-patch

Example: Intercepting all POST requests whose request body contains "TEST"

(function(xhr) {
    function banana(xhrInstance) { // Example
        console.log('Monkey RS: ' + xhrInstance.readyState);
    }
    // 
    var open = xhr.open;
    xhr.open = function(method, url, async) {
        // Test if method is POST
        if (/^POST$/i.test(method)) {
            var send = this.send;
            this.send = function(data) {
                // Test if request body contains "TEST"
                if (typeof data === 'string' && data.indexOf('TEST') >= 0) {
                    var rsc = this.onreadystatechange;
                    if (rsc) {
                        // Apply monkey-patch
                        this.onreadystatechange = function() {
                            banana(this);
                            return rsc.apply(this, arguments);
                        };
                    }
                }
                return send.apply(this, arguments);
            };
        }
        return open.apply(this, arguments);
    };
})(XMLHttpRequest.prototype);

The main techniques used is the transparent rewrite using...

var original = xhr.method; 
xhr.method = function(){
    /*...*/;
    return original.apply(this, arguments);
};

My examples are very basic, and can be extended to meet your exact wishes. That's up to you, however.

like image 68
Rob W Avatar answered Oct 12 '22 23:10

Rob W