Intercept XMLHttpRequest and modify responseText - javascript

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?

//
// 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();
}

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>

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)

Related

Modify headers of only POST XMLHttpRequest

(function() {
var send = XMLHttpRequest.prototype.send,
token = document.getElementsByTagName('meta')['csrf-token'].content;
XMLHttpRequest.prototype.send = function(data) {
this.setRequestHeader('X-CSRF-Token', token);
return send.apply(this, arguments);
};
}());
I am intercepting all the calls to append X-CSRF-Token to the request header. Is there a way to limit this just to post calls? Cannot use jQuery.ajaxPrefilter() as it doesn't intercept all the calls I want.
I couldn't find a way to detect method used for an AJAX call, but you can try:
Override open method to verify wich method is used for the call
Add a custom property for token
On the send method, evaluate that property to add or not the header
(function() {
var proxied = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function() {
this.token = (arguments[0].toUpperCase() == 'POST')
? document.getElementsByTagName('meta')['csrf-token'].content
: null;
return proxied.apply(this, [].slice.call(arguments));
};
var send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(data) {
if(this.token) {
this.setRequestHeader('X-CSRF-Token', token);
}
return send.apply(this, arguments);
};
})();
I've used this answer for overriding open method.
In strict mode, this.token = ... could fail. If it's your case, just use:
let token = (arguments[0].toUpperCase() == 'POST')
? document.getElementsByTagName('meta')['csrf-token'].content
: null;
Object.defineProperty(this, 'token', token);
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
It doesn't look right for me to modify native methods.
I'd rather create some helpers to work with requests.
For example:
// base helper that will be used for any type of requests (POST/GET/PUT/DELETE).
function makeRequest(url, settings) {
// do what ever you need here to setup a XMLHttpRequest
}
function makePostRequest(url, body) {
makeRequest(
example.com,
{
body,
headers: { 'X-CSRF-Token': token }
}
);
}
function makeGetRequest() {...}
function makePostRequest() {...}
function makeDeleteRequest() {...}
As a result you will have useful helpers to work with requests and you don't need to modify XMLHttpRequest prototype.

XMLHttpRequest is not a function

I'm trying to write some client-side JavaScript using XMLHttpRequest:
$('#someId').on('input', function() {
var req = XMLHttpRequest();
// …
});
but I get the following error:
XMLHttpRequest is not a function. (In 'XMLHttpRequest()', 'XMLHttpRequest' is an instance of XMLHttpRequestConstructor)
How to fix this?
missed new, must be:
$('#someId').on('input', function() {
var req = new XMLHttpRequest();
// …
});
you can read more about XHRHttpRequest here -
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
and how to work with it here -
https://developer.mozilla.org/ru/docs/XMLHttpRequest
(only this page translation exists yet, but google translate can help a lot :) )
p.s. If you are using jQuery - better to use $.ajax() as #synthet1c said.
Read more about it here - http://api.jquery.com/jquery.ajax/
If you are already using jQuery, you can make ajax requests with $.ajax() method:
Example:
$('#someId').on('input', function() {
$.ajax({
url: 'some_file.php',
data: {
postparam_1: 'ok',
postparam_2: 'no'
},
method: 'get',
success: function(x) {
alert(x); // string result from server
},
error: function() {
alert('Error!');
}
});
});
If you want to use It in your app you have to retrieve XmlHttpRequest object that works across all browsers.
var XMLHttpFactories = [
function () {return new XMLHttpRequest()},
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
];
function createXMLHTTPObject() {
var xmlhttp = false;
for (var i=0;i<XMLHttpFactories.length;i++) {
try {
xmlhttp = XMLHttpFactories[i]();
}
catch (e) {
continue;
}
break;
}
return xmlhttp;
}
XMLHttpRequest is a constructor and not a (usual) function in JavaScript and you need to use new XMLHttpRequest()
$('#someId').on('input', function() {
var req = new XMLHttpRequest();
// …
});
Refer this MDN article on using XMLHttpRequest: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest

Save JavaScript prototype based objects in sessionStorage?

var obj = {
conn : null,
first : function(thisIdentity) {
"use strict";
var myObj = this;
$(document).on('click', thisIdentity, function(e) {
e.preventDefault();
$.ajax ({
url : some value,
// other parameters
success : function(data) {
myObj.conn = new Connection(data.user_id, "127.0.0.1:80");
sessionStorage.setItem('connection', JSON.stringify(myObj.conn));
}
});
},
second : function(thisIdentity) {
"use strict";
var myObj = this;
var conntn = sessionStorage.getItem('connection');
$(document).on('click', thisIdentity, function(e) {
e.preventDefault();
$.ajax ({
url : some value,
// other parameters
success : function(data) {
var parsedConnection = JSON.parse(conntn);
parsedConnection.sendMsg(data.id, data.nid);
}
});
}
};
var Connection = (function() {
function Connection(uid, url) {
this.uid = uid;
this.open = false;
this.socket = new WebSocket("ws://"+url);
this.setupConnectionEvents();
},
Connection.prototype = {
sendMsg : function(id, nid) {
alert("Working");
},
// other functions
}
})();
So connection is made in the AJAX callback function of first and I store the object in the sessionStorage via JSON but when I use it in the AJAX callback of second then error is coming that
TypeError: parsedConnection.sendMsg is not a function
Now I understand that may be it is because JSON can be used to store plain objects not prototype-based objects.
My question is : Can any one tell me how to store prototype-based objects via JSON or any other way to implement this?
I don't want to use eval. Any code, reference would be much appreciated. Thanks!
UPDATE
I did as #Dan Prince mentioned but then a new problem occurred that now when in sendMsg function I use
this.socket.send(JSON.stringify({
action: 'message',
rec: receiver,
msg: message
}));
Then it stays
InvalidStateError: An attempt was made to use an object that is not,
or is no longer, usable
Any inputs? Thanks!
You could probably hack your own solution into place by storing the prototype as a property of the object, then reinstantiating it with Object.create after you read it, but the real question is why do you want to do this in the first place?
I would suggest writing a serialize method on Connection's prototype, which exposes only the essential information (there's no sense serializing a web socket for example).
Connection.prototype.toJSON = function() {
return JSON.stringify({
uid: this.uid,
url: this.url,
open: this.open
});
};
Then use this method when you save the connection object into session storage.
myObj.conn = new Connection(data.user_id, "127.0.0.1:80");
sessionStorage.setItem('connection', myObj.conn.toJSON());
Each saved connection now has the minimum amount of data you need to call the constructor and recreate the instance.
When you load a connection from session storage, parse it and pass the values back into the constructor.
var json = sessionStorage.getItem('connection');
var data = JSON.parse(json);
var connection = new Connection(data.uid, data.url)
// ...
connection.sendMsg(data.id, data.nid);
This will recreate the correct prototype chain in a natural and predictable way.
It's hard to see exactly what you are trying to achieve in every respect, but let's assume :
for various DOM elements, a click handler (delegated to document) will cause asynchronously derived data to be sent via socket.send().
the socket is to be initialized with an asynchronously derived uri.
the socket is to be kept available for immediate reuse.
data by which the socket is initialized is to be cached in local storage for future sessions. (It makes no sense to store the socket itself).
In addition, we need to acknowledge that a socket consume resources should really be disposed of if its resuse is not immediate.
The whole strategy is abnormally complex. The overhead of performing an ajax operation once per session to obtain a uri would typically be accepted, as would the creation of a socket each time one is needed. However, it's an intersting exercise to write something with all the stated characteristics.
This may not be 100% correct but could possibly give you some ideas, including the use of promises to cater for several asynchronisms. Here goes ...
var obj = {
conn: null,
init: function(thisIdentity) {
// It makes sense to attach the click handler only *once*, so let's assume this is an init function.
"use strict";
var myObj = this;
$(document).on('click', thisIdentity, function(e) {
e.preventDefault();
$.ajax ({
url : some value,
// other parameters
}).then(function(data) {
myObj.send(JSON.stringify({
'id': data.id,
'nid': data.nid
}));
});
});
},
send: function(data) {
"use strict";
var myObj = this;
return myObj.getSocket().then(function(socket) {
socket.send(data);
}).then(function() {
// by disposing in later event turn, a rapid series of send()s has at least a chance of using the same socket instance before it is closed.
if(socket.bufferedAmount == 0) { // if the socket's send buffer is empty, then dispose of it.
socket.close();
myObj.conn = null;
}
});
},
getSocket: function() {
"use strict";
var myObj = this;
//1. Test whether or not myObj.conn already exists ....
if(!myObj.conn) {
//2 .... if not, try to recreate from data stored in local storage ...
var connectionData = sessionStorage.getItem('connectionData');
if(connectionData) {
myObj.conn = myObj.makeSocket(connectionData.user_id);
} else {
//3. ... if connectionData is null, perform ajax.
myObj.conn = $.ajax({
url: some value,
// other parameters
}).then(function(data) {
sessionStorage.setItem('connectionData', JSON.stringify(data));
return myObj.makeSocket(data.user_id);
});
}
}
return myObj.conn; // note: myObj.conn is a *promise* of a socket, not a socket.
},
makeSocket: function(uid) {
"use strict";
var myObj = this;
var uri = "127.0.0.1:80"; // if this is invariant, it can be hard-coded here.
// return a *promise* of a socket, that will be resolved when the socket's readystate becomes OPEN.
return $.Deferred(function(dfrd) {
var socket = new WebSocket("ws://" + uri);
socket.uid = uid;
socket.onopen = function() {
myObj.setupConnectionEvents();// not too sure about this as we don't know what it does.
dfrd.resolve(socket);
};
}).promise();
}
};
Under this scheme, the click handler or anything else can call obj.send() without needing to worry about the state of the socket. obj.send() will create a socket if necessary.
If you were to drop the requirement for storing data between sessions, then .send() and .getSocket() would simplify to the extent that you would probably choose to roll what remains of .getSocket() into .send().

Cannot parse JSON?

Here's my javascript file
var callAjax = function(relative_path){
var Ajax = new XMLHttpRequest();
Ajax.onreadystatechange = function() {
//Since what we are calling a local file. we cannot get a 200 OK Status.
//So We check only the readystate
if(Ajax.readyState==4){
serialized = Ajax.responseText;
alert(serialized);
// ^^ alerts fine.
return serialized;
}
}
Ajax.open("GET",relative_path, true);
Ajax.send();
};
var readSettings = function(){
var data = callAjax('settings.json');
obj = JSON.parse(data);
alert(obj);
}
Now when i call readSettings() somewhere in my html, the first alert (in the callAjax functions alerts the JSON correctly. but the second one does not. When i see the console, the error is :
[21:04:02.233] SyntaxError: JSON.parse: unexpected character # file:///home/cipher/Codes/looma-f5/js/looma.js:23
My settings.json is:
{
"classes": 8,
"config": "classConfig",
"locale": {
"en": "localeEn"
},
"defaultLocale": "en"
}
I ran the JSON through online tools, it looks good. Why is firefox not parsing these?
You're not returning any value from the callAjax function. You need to put your code that uses the result inside the onreadystatchange handler.
You do have a return statement, but it's inside the callback, which returns to the internal caller of the callback, and which is invoked after your callAjax returned.
Since it seems that callAjax is fairly generic, a good approach is to have that function accept a callback as an argument, then invoke it, passing in the response.
// receive a callback----vv
var callAjax = function(relative_path, callback){
var Ajax = new XMLHttpRequest();
Ajax.onreadystatechange = function() {
if(Ajax.readyState==4){
serialized = Ajax.responseText;
alert(serialized);
// vv---invoke the callback
callback(serialized);
}
}
Ajax.open("GET",relative_path, true);
Ajax.send();
};
var readSettings = function(){
// pass a callback ----------------------vv
var data = callAjax('settings.json', function(data) {
var obj = JSON.parse(data);
alert(obj);
});
}
If the caller of readSettings needs to work with the response, then you could have readSettings also receive a callback, and then either pass it on directly, or wrap it in another function so that it can first do the parsing.
I'll give an example that assumes that it needs to be wrapped.
// receive a callback----------vv
var readSettings = function(callback){
// pass a callback that wraps the first---vv
var data = callAjax('settings.json', function(data) {
// parse the response data
var obj = JSON.parse(data);
// invoke the callback, passing it the parsed object
callback(obj);
});
}
readSettings(function(settings_obj) {
alert(settings_obj);
});
The problem is that you can JSON.parse immediately after callAjax returns. Before the onreadystate callback is executed. Due to asynchronous nature of the operation you should trigger parsing from the callback.
The "A" in AJAX is for asynchronous. After submitting the XMLHttpRequest, the callAjax function will return immediately, without waiting for the request to complete. Thus, the JSON.parse will be called with the return value of callAjax (which isn't the JSON you're looking for), generating the error.
Some time later, the XMLHttpRequest will complete and the onreadystatechange callback will be run. You return the JSON from this function, but the JSON doesn't go anywhere because it's returning from the callback, not from callAjax.
You must perform the JSON parsing and subsequent activities as a result of the onreadystatechange event.

Check something synchronous before every Backbone HTTP request

In every authenticated requests (GET, POST, etc) of my Backbone/Marionette application I must to attach an accessToken.
I store this accessToken and expireDate in the localStorage.
To check if the accessToken is expired I call this method: user.checkToken().
If is expired, the method renew the accessToken with a POST request to my backend.
Where should I put this check? I mean, in which part of the application?
Should I rewrite my on Backbone.sync method or use ajax.setup "beforeSend" ?
Thanks in advance for your advices/idea.
Backbone uses jQuery (see the note for a solution that may work with Zepto) for ajax requests, so you can use (as suggested by Edward) jQuery.ajaxPrefilter.
I did a little test for this task, let me know if there's any problem:
function tokenIsExpired() {
return true;
}
function createPromiseFunction(method, jqXHRsource, jqXHR) {
return function() {
jqXHRsource[method] = function(f) {
if (f) {
jqXHR[method] = function() {
f.apply(this, arguments);
};
}
return this;
};
}
}
function updateToken() {
return $.ajax({
url: '',
method: 'GET',
data: {some:'data', here:'yes'},
success: function() {
// update the token sir
console.log('token call done')
},
skipTokenCheck: true // required
});
}
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
/*
* check if token is expired every time a new ajax request is made
* if it is expired, aborts the current requests, updated the token
* and eventually does the original request again.
*/
if (!options.skipTokenCheck && tokenIsExpired()) {
// at this point no callback should have be added to the promise object
var methodsNames = [
'done',
'always',
'fail',
'progress',
'then'
];
var methods = {};
// copy the callbacks when they're added to the old request
for (var i = 0; i < methodsNames.length; i++) {
var name = methodsNames[i];
createPromiseFunction(name, jqXHR, methods)();
};
jqXHR.abort();
// TODO: error checks
updateToken().done(function() {
console.log('done');
var newReq = $.ajax($.extend(originalOptions, {skipTokenCheck: true}));
for (var i = 0; i < methodsNames.length; i++) {
var name = methodsNames[i];
var f = methods[name];
if (f) {
newReq[name](f);
}
};
});
}
});
var p = $.get('.');
p.done(function() { console.log(arguments); }).fail(function() {
console.log('fail');
});
Looks like that ajaxPrefilter doesn't work with Zepto. Alternatively you can use the ajaxBeforeSend event.
Returning false in the beforeSend function will cancel the request.
Should be easy to adapt the code I posted above.
Overwrite your model's sync() function and do whatever you need to do.. Something like:
var MyModel = Backbone.Model.extend({
sync: function() {
// Put your code here
Backbone.Model.prototype.sync.apply(this, arguments);
}
});
Edit #1:
Not sure where you get user (as well as other variables) from but here it is:
var MyModel = Backbone.Model.extend({
sync: function() {
user.checkToken().done(_.bind(function(){
Backbone.Model.prototype.sync.apply(this, [ method, model, options ]);
});
}, this);
});

Categories

Resources