16

I'm trying to build a script that will act as a proxy/wrapper for the native XMLHttpRequest object enabling me to intercept it, modify the responseText and return back to the original onreadystatechange event.

The context being, if the data the app is trying to receive is already available in local storage, to abort the XMLHttpRequest and pass the locally stored data back into the apps success/failure callback methods. Assume I have no control over the apps existing AJAX callback methods.

I had originally tried the following idea..

var send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(data){
   //Do some stuff in here to modify the responseText
   send.call(this, data);
};

But as I have now established, the responseText is read only.

I then tried taking a step back, writing my own full native proxy to XMLHttpRequest, ultimately ending up writing my own version of the native methods. Similar to what is discussed here...

http://www.ilinsky.com/articles/XMLHttpRequest/#implementation-wrapping

But it rapidly got confusing, and still have the difficulty of returning the modified data back into the original onReadyStateChange method.

Any suggestions? Is this even possible?

Tarek Hallak
  • 18,422
  • 7
  • 59
  • 68
umpljazz
  • 1,182
  • 4
  • 11
  • 21

3 Answers3

12

//
// firefox, ie8+ 
//
var accessor = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'responseText');

Object.defineProperty(XMLHttpRequest.prototype, 'responseText', {
 get: function() {
  console.log('get responseText');
  return accessor.get.call(this);
 },
 set: function(str) {
  console.log('set responseText: %s', str);
  //return accessor.set.call(this, str);
 },
 configurable: true
});


//
// chrome, safari (accessor == null)
//
var rawOpen = XMLHttpRequest.prototype.open;

XMLHttpRequest.prototype.open = function() {
 if (!this._hooked) {
  this._hooked = true;
  setupHook(this);
 }
 rawOpen.apply(this, arguments);
}

function setupHook(xhr) {
 function getter() {
  console.log('get responseText');

  delete xhr.responseText;
  var ret = xhr.responseText;
  setup();
  return ret;
 }

 function setter(str) {
  console.log('set responseText: %s', str);
 }

 function setup() {
  Object.defineProperty(xhr, 'responseText', {
   get: getter,
   set: setter,
   configurable: true
  });
 }
 setup();
}
EtherDream
  • 144
  • 1
  • 3
  • 1
    This was overkill for what I needed, but the key takeaway for me was `var rawOpen = XMLHttpRequest.prototype.open;` and `rawOpen.apply(this, arguments);`, which allowed me to plug in my own override (as you show) but still let the call pass-through as normal so that the caller doesn't know anything different. – Tyler Forsythe Jul 17 '15 at 22:07
  • 2
    Don't understand why do you `delete xhr.responseText` and then `var ret = xhr.responseText` – mannok Jan 30 '20 at 08:04
  • What's the intent of setup() inside of getter() function? I get maximum call stack... – Lance Mar 10 '23 at 17:36
2

The following script perfectly intercept the data before sending via XMLHttpRequest.prototype.send

<script>
(function(send) { 

        XMLHttpRequest.prototype.send = function(data) { 

            this.addEventListener('readystatechange', function() { 

            }, false); 

            console.log(data); 
            alert(data); 

        }; 

})(XMLHttpRequest.prototype.send);
</script>
bummi
  • 27,123
  • 14
  • 62
  • 101
  • 1
    Also found a great similar solution here: https://medium.com/@gilfink/quick-tip-creating-an-xmlhttprequest-interceptor-1da23cf90b76 – falsarella Oct 14 '19 at 14:24
0

Your step-back is an overkill: you may add your own getter on XMLHttpRequest: (more about properties)

Object.defineProperty(XMLHttpRequest.prototype,"myResponse",{
  get: function() {
    return this.responseText+"my update"; // anything you want
  }
});

the usage:

var xhr = new XMLHttpRequest();
...
console.log(xhr.myResponse); // xhr.responseText+"my update"

Note on modern browsers you may run xhr.onload (see XMLHttpRequest2 tips)

Jan Turoň
  • 31,451
  • 23
  • 125
  • 169
  • Thanks. I've tried using this idea, but I need xhr.responseText to be changed. By adding the new value, 'myResponse', would mean having to change the app code in the onReadyStateChange event to use .myResponse rather than .responseText. – umpljazz Jun 06 '13 at 11:13