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.
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:
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.
Tried just overriding the onreadystatechange
handler and changing .responseText
, but that field is read-only so you can't change it.
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.