How can I change the XMLHttpRequest responseetext received by another function?

I am trying to modify the responseText received by a function that I cannot change. This function creates an XMLHttpRequest that I can connect to, but I could not "wrap" the responseText so that I could modify the content before the original function received 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 also tried to manipulate the reiceiving k () function; in an attempt to achieve my goal, but since it does not depend on any data passed to the function (for example, k (a.responseText);), I was not successful.

Is there any way I can achieve this? I do not want to use js libraries (e.g. jQuery);




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




EDIT2 . Added below is one of the methods that I tried to intercept and modify the .responseText that was added 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 Mj and ff functions are not available globally, they are both contained inside an anonymous function (function () {functions here}) ();




EDIT4 . I changed the accepted answer because AmmarCSE has no problems and difficulties related to jfriend00 answer.

The best answer, explained briefly, is as follows:

Listen to any request that you want to change (make sure your listener intercepts it before the original function is executed, otherwise it makes no sense to change it after the answer has already been used).

Save the original answer (if you want to change it) in a temporary variable

Change the property you want to change to "writeable: true", it will remove any value that it had. In my case, I use

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

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

Now you can set everything you want for your answer, if all you need is to change the original answer, then you can use this data from your temporary variable and then save the changes in the answer.

+14
javascript ajax monkeypatching
Oct 19 '14 at 4:34 on
source share
7 answers

One very easy way is to change the property descriptor for the responseText itself

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

So you can extend XMLHttpRequest as

 (function(proxied) { XMLHttpRequest = function() { //cannot use apply directly since we want a 'new' version var wrapped = new(Function.prototype.bind.apply(proxied, arguments)); Object.defineProperty(wrapped, 'responseText', { writable: true }); return wrapped; }; })(XMLHttpRequest); 

Demo

+8
May 30 '16 at 19:31
source

Edit: see the second version of the code below (it is tested and working). The first has some limitations.




Since you cannot change any of these functions, it seems to you that you need to go after the XMLHttpRequest prototype. Here is 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 makes the monkey patch for the XMLHttpRequest open() method, and then when it is called for the async request, it executes the monkey patch for the onReadyStateChange handler, since it must already be installed. This fixed function will then see the Text response before the onReadyStateChange handler is called so that it can assign it a different value.

And finally, since .responseText is only ready, it replaces the dummy XMLHttpResponse object before onreadystatechange handler. This will not work in all cases, but will work if the onreadystatechange handler uses this.responseText to get the response.




And here is an attempt that overrides the XMLHttpRequest object as our own proxy object. Since this is our own proxy object, we can set the responseText property to whatever we want. For all properties other than onreadystatechange , this object simply redirects the call to get, set, or function to the actual 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 considered all the advanced ways in which Ajax (for example, binary data, downloads, etc.) can be used to see that this proxy is thorough enough to make it all work (I would assume it would not be without any further work, but it works for basic queries, so it seems like the concept is capable).




Other unsuccessful attempts:

  • I tried to output the XMLHttpRequest object and then replace the constructor with my own, but this did not work, because the real XMLHttpRequest function will not allow you to call it as a function to initialize my derived object.

  • I tried just overriding the onreadystatechange handler and changing the .responseText , but this field is read-only, so you cannot change it.

  • I tried to create a dummy object that is dispatched as the this object when onreadystatechange called, but a lot of code does not reference this , but rather has the actual object stored in the local variable in closure - thus defeating the dummy object.

+15
Oct 19 '14 at 5:54 on
source

Upon request, I include an example snippet below showing how to modify the XMLHttpRequest response before the original function can receive it.

 // In this example the sample response should be // {"data_sample":"data has not been modified"} // and we will change it into // {"data_sample":"woops! All data has gone!"} /*---BEGIN HACK---------------------------------------------------------------*/ // here we will modify the response function modifyResponse(response) { var original_response, modified_response; if (this.readyState === 4) { // we need to store the original response before any modifications // because the next step will erase everything it had original_response = response.target.responseText; // here we "kill" the response property of this request // and we set it to writable Object.defineProperty(this, "responseText", {writable: true}); // now we can make our modifications and save them in our new property modified_response = JSON.parse(original_response); modified_response.data_sample = "woops! All data has gone!"; this.responseText = JSON.stringify(modified_response); } } // here we listen to all requests being opened function openBypass(original_function) { return function(method, url, async) { // here we listen to the same request the "original" code made // before it can listen to it, this guarantees that // any response it receives will pass through our modifier // function before reaching the "original" code this.addEventListener("readystatechange", modifyResponse); // here we return everything original_function might // return so nothing breaks return original_function.apply(this, arguments); }; } // here we override the default .open method so that // we can listen and modify the request before the original function get its XMLHttpRequest.prototype.open = openBypass(XMLHttpRequest.prototype.open); // to see the original response just remove/comment the line above /*---END HACK-----------------------------------------------------------------*/ // here we have the "original" code receiving the responses // that we want to modify function logResponse(response) { if (this.readyState === 4) { document.write(response.target.responseText); } } // here is a common request var _request = new XMLHttpRequest(); _request.open("GET", "https://gist.githubusercontent.com/anonymous/c655b533b340791c5d49f67c373f53d2/raw/cb6159a19dca9b55a6c97d3a35a32979ee298085/data.json", true); _request.addEventListener("readystatechange", logResponse); _request.send(); 
+3
Jan 10 '17 at 10:01 on
source

You can wrap the getter for responseText in the prototype with a new function and make changes there.

Here is a simple example that adds the html <!-- TEST --> comment to the response text:

 (function(http){ var get = Object.getOwnPropertyDescriptor( http.prototype, 'responseText' ).get; Object.defineProperty( http.prototype, "responseText", { get: function(){ return get.apply( this, arguments ) + "<!-- TEST -->"; } } ); })(self.XMLHttpRequest); 

The above function will change the response text for all requests.

If you want to make changes in only one request, do not use the above function, but simply specify the recipient for a separate request:

 var req = new XMLHttpRequest(); var get = Object.getOwnPropertyDescriptor( XMLHttpRequest.prototype, 'responseText' ).get; Object.defineProperty( req, "responseText", { get: function() { return get.apply(this, arguments) + "<!-- TEST -->"; } } ); var url = '/'; req.open('GET', url); req.addEventListener( "load", function(){ console.log(req.responseText); } ); req.send(); 
+3
Jan 12 '17 at 9:19 on
source

I needed to intercept and modify the response of the request, so I came up with some code. I also found that some sites like to use answer as well as responseText, so my code modifies both.

The code

 var open_prototype = XMLHttpRequest.prototype.open, intercept_response = function(urlpattern, callback) { XMLHttpRequest.prototype.open = function() { arguments['1'].match(urlpattern) && this.addEventListener('readystatechange', function(event) { if ( this.readyState === 4 ) { var response = callback(event.target.responseText); Object.defineProperty(this, 'response', {writable: true}); Object.defineProperty(this, 'responseText', {writable: true}); this.response = this.responseText = response; } }); return open_prototype.apply(this, arguments); }; }; 

the first parameter of the intercept_response function is a regular expression that matches the request URL, and the second parameter is the function that will be used to respond to change it.

Usage example

 intercept_response(/fruit\.json/i, function(response) { var new_response = response.replace('banana', 'apple'); return new_response; }); 
+3
Mar 31 '17 at 15:33
source

I ran into the same issue when I was doing a Chrome extension to allow cross origin API calls. This worked in Chrome. (Update: it does not work in the latest version of Chrome).

 delete _this.responseText; _this.responseText = "Anything you want"; 

The fragment runs inside the XMLHttpRequest.prototype.send file, which redirects requests to the background extensions script and replaces all the properties with the response. Like this:

 // Delete removes the read only restriction delete _this.response; _this.response = event.data.response.xhr.response; delete _this.responseText; _this.responseText = event.data.response.xhr.responseText; delete _this.status; _this.status = event.data.response.xhr.status; delete _this.statusText; _this.statusText = event.data.response.xhr.statusText; delete _this.readyState; _this.readyState = event.data.response.xhr.readyState; 

This does not work in Firefox, but I found a solution that worked:

 var test = new XMLHttpRequest(); Object.defineProperty(test, 'responseText', { configurable: true, writable: true, }); test.responseText = "Hey"; 

This does not work in Chrome, but this work in both Chrome and Firefox:

 var test = new XMLHttpRequest(); var aValue; Object.defineProperty(test, 'responseText', { get: function() { return aValue; }, set: function(newValue) { aValue = newValue; }, enumerable: true, configurable: true }); test.responseText = "Hey"; 

The latter was copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

None of the solutions work in Safari. I tried to create a new writeable XMLHttpRequest, but he was not allowed to open or send it. I have also tried this solution: https://stackoverflow.com/a/312969/ Unfortunately, the same error appeared in Safari:

TypeError: Attempting a custom attribute of a non-configurable property.

+2
May 18 '15 at 21:45
source

Primary functional variables are great things! function f() {a; b; c; } function f() {a; b; c; } is exactly the same as var f = function () {a; b; c; } var f = function () {a; b; c; } var f = function () {a; b; c; } This means that you can override functions as needed. Do you want to wrap the Mj function to return a modified object? No problems. The fact that the responseText field is read-only is a pain, but if that is the only field you need ...

 var Mj_backup = Mj; // Keep a copy of it, unless you want to re-implement it (which you could do) Mj = function (a, b, c, d, e) { // To wrap the old Mj function, we need its args var retval = Mj_backup(a,b,c,d,e); // Call the original function, and store its ret value var retmod; // This is the value you'll actually return. Not a true XHR, just mimics one retmod.responseText = retval.responseText; // Repeat for any other required properties return retmod; } 

Now, when your page code calls Mj (), it will call your wrapper instead (which, of course, will still call the original Mj).

+1
Oct 19 '14 at 5:29
source



All Articles