I'm trying to attach a callback to the "Send mail" ajax action in Gmail. I've been able to differentiate a Send mail action from other AJAX actions based on the request payload but have been unable to hook into the actual AJAX call.
Thus far, I've tried using overriding the XMLHttpRequest.open() method as detailed here. That hasn't worked. I've also tried overriding XMLHttpRequest.send(). Also failed.
Any thoughts? Much thanks in advance.
Google's trick is that they send the request from inside an iframe which has it's own JavaScript environment. However, since it is loaded from the same origin as the parent, you can still easily manipulate it even from the browser console:
[].slice.apply(document.querySelectorAll('iframe')).forEach(function (iframe) {
try {
var xhrProto = iframe.contentWindow.XMLHttpRequest.prototype;
var origOpen = xhrProto.open;
xhrProto.open = function () {
console.log('DO SOMETHING', arguments);
return origOpen.apply(this, arguments);
};
} catch (e) {}
});
You might want to use a MutationObserver to detect newly added iframes reliably.
Related
I want to send an AJAX DELETE request through Javascript when the user closes the tab. The flow is the following:
When the user attempts to close the tab, a onbeforeunload event takes place, then if the user confirms to leave the page the onunload event takes place and tries to execute the deleteRequest function, which is a synchronous ajax delete request.
window.onbeforeunload = function(){
return 'Are you sure you want to leave?';
};
window.onunload = function(){
deleteRequest();
};
function deleteRequest(){
let url = new URL("http://......");
let request = new XMLHttpRequest();
request.onreadystatechange = function () {
if(request.readyState == 4){
if(request.status === 200){
console.log('success');
}
}
}
request.open("DELETE", url, false);
try{
request.send();
}
catch(e){
console.log(e);
}
}
Unfortunately, it seems that Google Chrome does not support anymore this since when a tab closes it kills all the pending events, and in the console of the browser I can see the following message
DOMException: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://.....': Synchronous XHR in page dismissal. See https://www.chromestatus.com/feature/4664843055398912 for more details.
at deletRequest(.......js:411:17)
at window.onunload
Please note that I have already seen many topics on this issue before on SO but the solutions did not really help since most of them are out of date since the policy of chrome on this changed quite recently, like this or this.
It seems that most people propose navigator.sendBeacon to achieve this, but in the documentation I saw only ways to make a POST request using this, any thoughts? Thanks in advance.
You're pretty much SOL as far as using sendBeacon out of the box goes, as PUT and DELETE cannot be sent as you observed (only POST). For your XMLHttpRequest, Chrome is very explicit about saying, "hey, we used to send these synchronous XHR requests in beforeunload if you used a no-op loop, but we're not supporting that anymore":
Failed to execute 'send' on 'XMLHttpRequest' [...] Synchronous XHR in page dismissal.
Chrome is intentionally disallowing this behavior. You have no option except to modify the backend, i.e., set up an endpoint that will receive sendBeacon POST request like https://example.com/trigger-delete/ and trigger the DELETE call from the backend, or, if it's not your server, you'll actually have to set up a relay server you control to receive the POST (https://myrelay.com/forward-delete/)and then pass it along as a DELETE.
sendBeacon is the only way you're getting out of this, as far as I can tell.
The chrome.webRequest API has the concept of a request ID (source: Chrome webRequest documention):
Request IDs
Each request is identified by a request ID. This ID is unique within a browser session and the context of an extension. It remains constant during the the life cycle of a request and can be used to match events for the same request. Note that several HTTP requests are mapped to one web request in case of HTTP redirection or HTTP authentication.
You can use it to correlate the requests even across redirects. But how do you initially get hold off the id when start a new request with fetch or XMLHttpRequest?
So far, I have not found anything better than to use the URL of the request as a way to make the initial link between the new request and the requestId. However, if there are overlapping requests to the same resource, this is not reliable.
Questions:
If you make a new request (either with fetch or XMLHttpRequest), how do you reliably get access to the requestId?
Does the fetch API or XMLHttpRequest API allow access to the requestId?
What I want to do is to use the functionality provided by the webRequest API to modify a single request, but I want to make sure that I do not accidentally modify other pending requests.
To the best of my knowledge, there is no direct support in the fetch or XHMLHttpRequest API. Also I'm not aware of completely reliable way to get hold of the requestId.
What I ended up doing was installing a onBeforeRequest listener, storing the requestId, and then immediately removing the listener again. For instance, it could look like this:
function makeSomeRequest(url) {
let listener;
const removeListener = () => {
if (listener) {
chrome.webRequest.onBeforeRequest.removeListener(listener);
listener = null;
}
};
let requestId;
listener = (details) => {
if (!requestId && urlMatches(details.url, url)) {
requestId = details.requestId;
removeListener();
}
};
chrome.webRequest.onBeforeRequest.addListener(listener, { urls: ['<all_urls>'] });
// install other listeners, which can then use the stored "requestId"
// ...
// finally, start the actual request, for instance
const promise = fetch(url).then(doSomething);
// and make sure to always clean up the listener
promise.then(removeListener, removeLister);
}
It is not perfect, and matching the URL is a detail that I left open. You could simply compare whether the details.url is identical to url:
function urlMatches(url1, url2) {
return url1 === url2;
}
Note that it is not guaranteed that you see the identical URL, for instance, if make a request against http://some.domain.test, you will see http://some.domain.test/ in your listener (see my other question about the details). Or http:// could have been replaced by https:// (here I'm not sure, but it could be because of other extensions like HTTPS Everywhere).
That is why the code above should only be seen as a sketch of the idea. It seems to work good enough in practice, as long as you do not start multiple requests to the identical URL. Still, I would be interested in learning about a better way to approach the problem.
I load a page from example.com on port 80, then from the loaded page, submit a form to the same server on a different port (as defined in the form action attribute).
(html)
<form id="enabledForm" action="http://example.com:39991/updateEnabled" method="POST">
(javascript)
$('#enabledForm').submit()
This works fine and the data is delivered as expected to the form action url, but the browser is redirected to the address of the POST request instead of staying on the requesting page.
If I use
$('#enabledForm').submit(function (event) {
event.preventDefault();
});
or
$('#enabledForm').submit(function (event) {
return false;
});
then the server receives no data but the page is not redirected.
if I add an alert within the event handler then the alert is not shown.
$('#enabledForm').submit(function (event) {
alert('inside submit event handler');
return false;
});
Clearly I'm soing something wrong but after hours of headbanging and trying everything I can think of I'm stuck.
What to do?
You have two basic options here:
Have the server return a 204 No Content response and forget about using JS entirely
Prevent the submission of the form with JS and send the data to the URL with Ajax instead
No content:
If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent.
How you set the HTTP response status code depends on your server side language / framework. For example:
In Catalyst it would be something like:
$c->response->status(204);
In PHP it would be:
http_response_code(204);
There are many Ajax tutorials out there, so I'm not going to provide another one. The jQuery documentation has a detailed section on Ajax.
Note that since you are working across origins (different ports), you will need to circumvent the Same Origin Policy. The standard way to do that is with CORS.
Sending a form would automatically change your browser URL (and refresh view).You should use an Ajax request to send informations to your server and eventually retrieve completion (success, error, informations...).
Extract your inputs' values to an object via Jquery and send your request via Jquery.post or Jquery.get
$('#enabledForm').submit(function (event) {
//Prevent natual post
event.preventDefault();
//Retrieve inputs values
var data = {}
$('#enabledForm input,textarea').each(function(index){
data[$(this).attr('name')] = $(this).val();
});
//Send request
$.post('http://example.com:3999/updateEnabled',data,function(response){
//Parse response if you want to inform user about success of operation
});
});
I don't know if val() is usable with all of yout inputs, but it's easy to adapt...
I've got a requirement where we need to redirect to a page for JSF 1.2 Ajax call. In our case, we need to redirect to session expired page when the ajax call get fired after the session got expired. We are implementing the session expiry check in a filter and the invocation of httpservletresponse.sendRedirect is redirecting correctly to the session expired page as expected, but the url is not getting changed which is the issue now.
Any hints/soultion either at the client/server side is highly appreciated.
~Ragesh
Finally I managed to find a solution for the above problem.
In the filter, I set the response header "Location" and another custom header which I'll use in the client side to filter the response.
Filter code:
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
httpServletResponse.setHeader("x-timeout-status", "true");
httpServletResponse.setHeader("Location", httpServletResponse
.encodeRedirectURL(sessionexpiryurl));
Richfaces have got a javascript file with various callbacks required during the AJAX call invocation which is packed inside the Richfaces libraries. There is one callback function called "processResponse" which will get invoked upon receiving response for all AJAX call initiated by JSF Ajax components . I've made use of this to handle the redirection functionality.
JS code:
var originalAjaxProcessResponse = A4J.AJAX.processResponse;
A4J.AJAX.processResponse = function(req) {
if (req.getResponseHeader('x-timeout-Status') != undefined && req.getResponseHeader('x-timeout-status') == 'true') {
window.location.href = req.getResponseHeader('Location');
} else {
originalAjaxProcessResponse(req);
}
}
Here we are overriding the method to handle our specific case and delegate the rest of the ajax call response handling to the in-built processing provided by richfaces.
Please let me know if you see any limitation to this solution or have a better solution to this problem
~Ragesh
So I have a bit of a problem. When I ask MooTools to send a request it comes back as failed every time. I can't seem to diagnose the problem either because if I try to get the returned header info the console just gives me "Refused to get unsafe header 'Status'" Message. The only thing I can think of is that the server isn't letting me access outside resources but maybe I just coded it wrong.
Here's the request code:
var finfo = current.textFontData();
var url = 'http://antiradiant.com/clients/TMW/rbwizard/mailer.php?s='+current.size+'&b='+current.box+'&l='+current.lidWood+'&c='+current.cartID+'&f='+finfo.font+'&l1='+finfo.line1+'&l2='+finfo.line2;
console.log(url);
var req = new Request({
url: url,
onSuccess: function() {
console.log('success');
//atc2.send();
},
onFailure: function() {
console.log('failure');
console.log(this.getHeader('Status'));
//atc2.send();
},
onException: function(headerName, value) {
console.log('exception');
console.log(headerName+': '+value);
}
});
req.send();
This code is derived from the resource rb_wizard.js (lines 81-103) on http://tylermorriswoodworking.myshopify.com/pages/recipe-box-wizard?b=maple&l=cherry&s=3x5&c=42042892
Mootools has a class called Request.JSONP that will help with your cross domain problem. Its sub class of the Request class, so your methods should work the same. I believe you need to call .post() or .get() at the end instead of send, but thats about all that should chnge. I'm not sure what version you're running on but here is the link tot he docs Mootools Request.JSONP
The error message "Refused to get unsafe header 'Status'" is spat out by WebKit based browsers (Safari, Chrome, etc) when you violate the cross-domain security model.
Therefore, it seems likely that the code you pasted is located on a domain other than antiradiant.com, and therefore is not allowed (by the browser) to request sites on antiradiant.com.
What I ended up doing was just using an iframe. All I really had to do was send data to another site and not receive any so it worked out.