Reputation: 1222
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?
Upvotes: 18
Views: 29534
Reputation: 333
The accepted answer used to work for me on Twitter up until recently for some reason. So here's another one that works:
var open_prototype = XMLHttpRequest.prototype.open
unsafeWindow.XMLHttpRequest.prototype.open = function() {
this.addEventListener('readystatechange', function(event) {
if ( this.readyState === 4 ) {
var response = event.target.responseText.replaceAll("a", "b");
Object.defineProperty(this, 'response', {writable: true});
Object.defineProperty(this, 'responseText', {writable: true});
this.response = this.responseText = response;
}
});
return open_prototype.apply(this, arguments);
};
You can filter on URL with event.target.responseURL
Upvotes: 1
Reputation: 652
In my opinion, to intercept the response of XMLHttpRequest
, a more modern solution will be extending the original XMLHttpRequest and overwrite it in the window
object:
const { interceptXhrResponse } = (function () {
let interceptionRules = [];
/**
* Function to intercept responses for given URL patterns
* @param {RegExp} urlPattern - Regular expression to match the (canonicalized) URL
* @param {Function} responseHandler - Function to handle the intercepted response
*/
function interceptXhrResponse(urlPattern, responseHandler) {
interceptionRules.push({ urlPattern, responseHandler });
}
// Function to find specific handler for the URL and return modified response
function handleInterceptedResponse(response, url) {
const interceptionRule = interceptionRules.find(({ urlPattern }) =>
urlPattern.test(url)
);
if (interceptionRule) {
const { responseHandler } = interceptionRule;
return responseHandler(response, url);
}
return response;
}
const OriginalXMLHttpRequest = window.XMLHttpRequest;
class XMLHttpRequest extends OriginalXMLHttpRequest {
get responseText() {
// If the request is not done, return the original responseText
if (this.readyState !== 4) {
return super.responseText;
}
return handleInterceptedResponse(super.responseText, this.responseURL);
}
get response() {
// If the request is not done, return the original response
if (this.readyState !== 4) {
return super.response;
}
return handleInterceptedResponse(super.response, this.responseURL);
}
}
window.XMLHttpRequest = XMLHttpRequest;
return { interceptXhrResponse };
})();
The upper code exposes the interceptXhrResponse
function, allowing you to specify a URL pattern with a regular expression and a corresponding response handler. You can return everything you want in the handler to change the response.
For example:
interceptXhrResponse(/.+/, (response, url) => {
return `Response of ${url}: Intercepted. Original response length: ${String(response).length}`
})
Then we can try to initiate an XMLHttpRequest
:
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://stackoverflow.com/404')
xhr.send()
xhr.onloadend = () => {
console.log(xhr.responseText)
}
Output:
Response of https://stackoverflow.com/404: Intercepted. Original response length: 63486
Upvotes: 2
Reputation: 174
//
// 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();
}
Upvotes: 15
Reputation: 21
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>
Upvotes: 2
Reputation: 32912
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)
Upvotes: 0