Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I modify the XMLHttpRequest responsetext received by another function?

I am trying to modify the responseText received by a function that I cannot modify. This function creates a XMLHttpRequest that I can attach to, but I have been unable to "wrap" the responseText in a way that allows me to modify the content before the original function receives it.

Here's the full original function:

function Mj(a, b, c, d, e) {     function k() {         4 == (m && 'readyState' in m ? m.readyState : 0) && b && ff(b) (m)     }     var m = new XMLHttpRequest;     'onloadend' in m ? m.addEventListener('loadend', k, !1)  : m.onreadystatechange = k;     c = ('GET').toUpperCase();     d = d || '';     m.open(c, a, !0);     m.send(d);     return m } function ff(a) {     return a && window ? function () {         try {             return a.apply(this, arguments)         } catch(b) {             throw jf(b),                 b;         }     } : a } 

I have also tried to manipulate the reiceiving function k(); in an attempt to reach my goal, but since it doesn't depend on any data passing to the function (for example k(a.responseText);) I had no success.

Is there any way that I can achieve this? I do not wish to use js libraries (such as jQuery);


EDIT: I understand that I cannot change .responseText directly since it is read-only, but I am trying to find a way to change the content between the response and receiving function.


EDIT2: Added below one of the methods I have tried to intercept and change .responseText which has been addapted from here: Monkey patch XMLHTTPRequest.onreadystatechange

(function (open) { XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {     if(/results/.test(url)) {       console.log(this.onreadystatechange);         this.addEventListener("readystatechange", function () {             console.log('readystate: ' + this.readyState);             if(this.responseText !== '') {                 this.responseText = this.responseText.split('&')[0];             }         }, false);     }     open.call(this, method, url, async, user, pass); }; })(XMLHttpRequest.prototype.open); 

EDIT3: I forgot to include that the functions Mj and ff are not globally available, they are both contained inside an anonymous function (function(){functions are here})();


EDIT4: I have changed the accepted answer because AmmarCSE's does not have any of the problems and complexity linked to jfriend00's answer.

The best answer explained in short is as follows:

Listen to whichever request you want to modify (make sure your listener will intercept it before the original function destination does, otherwise there is no point in modifying it after the response has already been used).

Save the original response (if you want to modify it) in a temporary variable

Change the property you want to modify to "writable: true", it will erase whichever value it had. In my case I use

Object.defineProperty(event, 'responseText', {     writable: true }); 

Where event is the object returned by listening to the load or readystatechange event of the xhr request

Now you can set anything you want for your response, if all you wanted was to modify the original response then you can use that data from your temporary variable and then save the modifications in the response.

like image 817
Shadow Avatar asked Oct 19 '14 04:10

Shadow


People also ask

What is the difference between response and responseText?

The response is interpreted into a ArrayBuffer , Blob , Document , JavaScript object, or a DOMString , depending on the value of XMLHttpRequest. responseType . responseText , on the other hand is the raw text, and you can handle it however you want.

What is XHR responseText?

responseText. The read-only XMLHttpRequest property responseText returns the text received from a server following a request being sent.

What is the role of responseText property in AJAX?

AJAX – Server responseText property While dealing with an asynchronous request, the value of the “responseText” property comprises the current response received from the server, even if it has not responded completely. This property returns the server response as a string.


1 Answers

Edit: See the second code option below (it is tested and works). The first one has some limitations.


Since you can't modify any of those functions, it appears you have to go after the XMLHttpRequest prototype. Here's one idea (untested, but you can see the direction):

(function() {     var open = XMLHttpRequest.prototype.open;      XMLHttpRequest.prototype.open = function(method, url, async, user, password) {         var oldReady;         if (async) {                oldReady = this.onreadystatechange;             // override onReadyStateChange             this.onreadystatechange = function() {                 if (this.readyState == 4) {                     // this.responseText is the ajax result                     // create a dummay ajax object so we can modify responseText                     var self = this;                     var dummy = {};                     ["statusText", "status", "readyState", "responseType"].forEach(function(item) {                         dummy[item] = self[item];                     });                     dummy.responseText = '{"msg": "Hello"}';                     return oldReady.call(dummy);                 } else {                     // call original onreadystatechange handler                     return oldReady.apply(this, arguments);                 }             }         }          // call original open method         return open.apply(this, arguments);     }  })(); 

This does a monkey patch for the XMLHttpRequest open() method and then when that is called for an async request, it does a monkey patch for the onReadyStateChange handler since that should already be set. That patched function then gets to see the responseText before the original onReadyStateChange handler is called so it can assign a different value to it.

And, finally because .responseText is ready-only, this substitutes a dummy XMLHttpResponse object before calling the onreadystatechange handler. This would not work in all cases, but will work if the onreadystatechange handler uses this.responseText to get the response.


And, here's an attempt that redefines the XMLHttpRequest object to be our own proxy object. Because it's our own proxy object, we can set the responseText property to whatever we want. For all other properties except onreadystatechange, this object just forwards the get, set or function call to the real XMLHttpRequest object.

(function() {     // create XMLHttpRequest proxy object     var oldXMLHttpRequest = XMLHttpRequest;      // define constructor for my proxy object     XMLHttpRequest = function() {         var actual = new oldXMLHttpRequest();         var self = this;          this.onreadystatechange = null;          // this is the actual handler on the real XMLHttpRequest object         actual.onreadystatechange = function() {             if (this.readyState == 4) {                 // actual.responseText is the ajax result                  // add your own code here to read the real ajax result                 // from actual.responseText and then put whatever result you want                 // the caller to see in self.responseText                 // this next line of code is a dummy line to be replaced                 self.responseText = '{"msg": "Hello"}';             }             if (self.onreadystatechange) {                 return self.onreadystatechange();             }         };          // add all proxy getters         ["status", "statusText", "responseType", "response",          "readyState", "responseXML", "upload"].forEach(function(item) {             Object.defineProperty(self, item, {                 get: function() {return actual[item];}             });         });          // add all proxy getters/setters         ["ontimeout, timeout", "withCredentials", "onload", "onerror", "onprogress"].forEach(function(item) {             Object.defineProperty(self, item, {                 get: function() {return actual[item];},                 set: function(val) {actual[item] = val;}             });         });          // add all pure proxy pass-through methods         ["addEventListener", "send", "open", "abort", "getAllResponseHeaders",          "getResponseHeader", "overrideMimeType", "setRequestHeader"].forEach(function(item) {             Object.defineProperty(self, item, {                 value: function() {return actual[item].apply(actual, arguments);}             });         });     } })(); 

Working demo: http://jsfiddle.net/jfriend00/jws6g691/

I tried it in the latest versions of IE, Firefox and Chrome and it worked with a simple ajax request.

Note: I have not looked into all the advanced ways that Ajax (like binary data, uploads, etc...) can be used to see that this proxy is thorough enough to make all those work (I would guess it might not be yet without some further work, but it is working for basic requests so it looks like the concept is capable).


Other attempts that failed:

  1. Tried to derive from the XMLHttpRequest object and then replace the constructor with my own, but that didn't work because the real XMLHttpRequest function won't let you call it as a function to initialize my derived object.

  2. Tried just overriding the onreadystatechange handler and changing .responseText, but that field is read-only so you can't change it.

  3. Tried creating a dummy object that is sent as the this object when calling onreadystatechange, but a lot of code doesn't reference this, but rather has the actual object saved in a local variable in a closure - thus defeating the dummy object.

like image 168
jfriend00 Avatar answered Oct 20 '22 14:10

jfriend00