Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript: Overriding XMLHttpRequest.open()

Tags:

How would I be able to override the XMLHttpRequest.open() method and then catch and alter it's arguments?

I've already tried the proxy method but it didn't work, although removing the open over-rid when XMLHttpRequest() was called:

(function() {     var proxied = window.XMLHttpRequest.open;     window.XMLHttpRequest.open = function() {         $('.log').html(arguments[0]);         return proxied.apply(this, arguments);     }; })(); 
like image 933
Trevor Avatar asked Oct 15 '11 04:10

Trevor


2 Answers

You are not modifying the open method inherited by XMLHttpRequest objects but just adding a method to the XMLHttpRequest constructor which is actually never used.

I tried this code in facebook and I was able to catch the requests:

(function() {     var proxied = window.XMLHttpRequest.prototype.open;     window.XMLHttpRequest.prototype.open = function() {         console.log( arguments );         return proxied.apply(this, [].slice.call(arguments));     }; })();  /*     ["POST", "/ajax/chat/buddy_list.php?__a=1", true]     ["POST", "/ajax/apps/usage_update.php?__a=1", true]     ["POST", "/ajax/chat/buddy_list.php?__a=1", true]     ["POST", "/ajax/canvas_ticker.php?__a=1", true]     ["POST", "/ajax/canvas_ticker.php?__a=1", true]     ["POST", "/ajax/chat/buddy_list.php?__a=1", true] */ 

So yeah the open method needs to be added to XMLHttpRequest prototype (window.XMLHttpRequest.prototype) not XMLHttpRequest constructor (window.XMLHttpRequest)

like image 130
Esailija Avatar answered Sep 24 '22 10:09

Esailija


Here is the approach I like to take; mind you, mastering the dark arts of XHR monkey patch is a bit of an art form.

Wrap the whole kit and caboodle in an IIFE. So start off with something like the following:

(function(open, send) {     //...overrides of the XHR open and send methods are now encapsulated within a closure })(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send) 

Any methods can be overridden using this general approach but the above scaffolding sets you up with a way to override (a.k.a monkey patch) both the open and send methods of XMLHttpRequest; in one neat utility function. Notice how the "base" methods (from the API's prototype object) are being fed into the IIFE and assigned to the var's "open" and "send", and safely scoped to the function block.

Now for the guts and what is key to persisting your monkey patch. Now again, this is how I do it and it works.

The general pattern (all within the confines of the IIFE) is to:

1) replicate the method and its arguments, (the signature, in its entirety, per spec/prototype),

2) slip in your mod's, and

3) apply your mod's to the XHR prototype property to ensure all XHR requests pass through your code.

So for example, "open" would look like:

XMLHttpRequest.prototype.open = function(method, url, async, user, password) {       xhrOpenRequestUrl = url;     // update request url, closure variable       open.apply(this, arguments); // reset/reapply original open method }; 

Don't get hung up on the xhrOpenRequestUrl = url; line, this code is copied from an example where I needed the url for later processing. The key takeaway is "open.apply", it cements your tweaks into the XHR open method, if you're not familiar with the "apply" method or the "arguments" object, then now is a good time to learn what they do.

And similarly for the "send" method...

XMLHttpRequest.prototype.send = function(data) {   //...what ever code you need, i.e. capture response, etc.   if (this.readyState == 4 && this.status >= 200 && this.status < 300) {     xhrSendResponseUrl = this.responseURL;     responseData = this.data;  // now you have the data, JSON or whatever, hehehe!   }   send.apply(this, arguments); // reset/reapply original send method } 

Again, the "apply" is critical and it must be done after all of your overrides. So putting it all together now...

(function(open, send) {     // Closure/state var's    var xhrOpenRequestUrl;  // captured in open override/monkey patch    var xhrSendResponseUrl; // captured in send override/monkey patch    var responseData;       // captured in send override/monkey patch     //...overrides of the XHR open and send methods are now encapsulated within a closure     XMLHttpRequest.prototype.open = function(method, url, async, user, password) {       xhrOpenRequestUrl = url;     // update request url, closure variable       open.apply(this, arguments); // reset/reapply original open method    };     XMLHttpRequest.prototype.send = function(data) {        //...what ever code you need, i.e. capture response, etc.       if (this.readyState == 4 && this.status >= 200 && this.status < 300) {          xhrSendResponseUrl = this.responseURL;          responseData = this.data;  // now you have the data, JSON or whatever, hehehe!       }       send.apply(this, arguments); // reset/reapply original send method    }  })(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send) 

Oh and one last thing, your monkey patch can, in turn, be monkey patched! To minimize this possibility the IIFE code should come after all of the other JS in the page. At least all JS that may be monkeying with XHR, but before any AJAX calls that you may be targeting. Also, and similarly, an XHR monkey patch can be injected via Chrome or Web Extension, and also override your override! HA!

Hope that helps!

like image 32
Mark Dalsaso Avatar answered Sep 24 '22 10:09

Mark Dalsaso