Here I have this code, which runs a request to google analytics (I checked, that part works in a popup script) and I would like it to send a daily "order" so to speak to let me know how many daily uses I have installed.
I have set the interval to 5 seconds for testing purposes, but this does not seem to work anyway.
function doSomething() {
chrome.storage.sync.get('session', function(result){
var val = result.session;
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "http://bat.global.cf:8000/checkok.html?rando="+val);
xhttp.send();
xhttp.open("GET", "https://www.google-analytics.com/collect?v=1&tid=UA-232507651-1&cid="+val+"&t=pageview&dp=%2Fdaily24hCheck");
xhttp.send();
});
}
setInterval(doSomething, 5000); // Time in milliseconds
/*Anonymous anylitics code for a background script end*/
Is there something I am missing about background scripts? The request does not seem to be sent, how can I fix this?
You are missing the fact that they are not persistently executing. See the Migrating to Service Workers documentation:
It's common for web developers to perform delayed or periodic operations using the setTimeout or setInterval methods. These APIs can fail in service workers, though, because the scheduler will cancel the timers when the service worker is terminated.
The idiomatic way to achieve what you want here is to use the chrome.alarms API, which is also discussed at that link.
Note that "armed" alarms won't survive a full extension restart (e.g. if the browser is restarted), so you probably should save the last time your alarm code has successfully executed and check if you need to send a beacon / when should you schedule a new alarm on extension start (e.g. through chrome.runtime.onStartup).
I'm trying to find out when a user left a specified page. There is no problem finding out when he used a link inside the page to navigate away but I kind of need to mark up something like when he closed the window or typed another URL and pressed enter. The second one is not so important but the first one is. So here is the question:
How can I see when a user closed my page (capture window.close event), and then... doesn't really matter (I need to send an AJAX request, but if I can get it to run an alert, I can do the rest).
Updated 2021
TL;DR
Beacon API is the solution to this issue (on almost every browser).
A beacon request is supposed to complete even if the user exits the page.
When should you trigger your Beacon request ?
This will depend on your usecase. If you are looking to catch any user exit, visibilitychange (not unload) is the last event reliably observable by developers in modern browsers.
NB: As long as implementation of visibilitychange is not consistent across browsers, you can detect it via the lifecycle.js library.
# lifecycle.js (1K) for cross-browser compatibility
# https://github.com/GoogleChromeLabs/page-lifecycle
<script defer src="/path/to/lifecycle.js"></script>
<script defer>
lifecycle.addEventListener('statechange', function(event) {
if (event.originalEvent == 'visibilitychange' && event.newState == 'hidden') {
var url = "https://example.com/foo";
var data = "bar";
navigator.sendBeacon(url, data);
}
});
</script>
Details
Beacon requests are supposed to run to completion even if the user leaves the page - switches to another app, etc - without blocking user workflow.
Under the hood, it sends a POST request along with the user credentials (cookies), subject to CORS restrictions.
var url = "https://example.com/foo";
var data = "bar";
navigator.sendBeacon(url, data);
The question is when to send your Beacon request. Especially if you want to wait until the last moment to send session info, app state, analytics, etc.
It used to be common practice to send it during the unload event, but changes to page lifecycle management - driven by mobile UX - killed this approach. Today, most mobile workflows (switching to new tab, switching to the homescreen, switching to another app...) do not trigger the unload event.
If you want to do things when a user exits your app/page, it is now recommended to use the visibilitychange event and check for transitioning from passive to hidden state.
document.addEventListener('visibilitychange', function() {
if (document.visibilityState == 'hidden') {
// send beacon request
}
});
The transition to hidden is often the last state change that's reliably observable by developers (this is especially true on mobile, as users can close tabs or the browser app itself, and the beforeunload, pagehide, and unload events are not fired in those cases).
This means you should treat the hidden state as the likely end to the user's session. In other words, persist any unsaved application state and send any unsent analytics data.
Details of the Page lifecyle API are explained in this article.
However, implementation of the visibilitychange event, as well as the Page lifecycle API is not consistent across browsers.
Until browser implementation catches up, using the lifecycle.js library and page lifecycle best practices seems like a good solution.
# lifecycle.js (1K) for cross-browser compatibility
# https://github.com/GoogleChromeLabs/page-lifecycle
<script defer src="/path/to/lifecycle.js"></script>
<script defer>
lifecycle.addEventListener('statechange', function(event) {
if (event.originalEvent == 'visibilitychange' && event.newState == 'hidden') {
var url = "https://example.com/foo";
var data = "bar";
navigator.sendBeacon(url, data);
}
});
</script>
For more numbers about the reliability of vanilla page lifecycle events (without lifecycle.js), there is also this study.
Adblockers
Adblockers seem to have options that block sendBeacon requests.
Cross site requests
Beacon requests are POST requests that include cookies and are subject to CORS spec. More info.
There are unload and beforeunload javascript events, but these are not reliable for an Ajax request (it is not guaranteed that a request initiated in one of these events will reach the server).
Therefore, doing this is highly not recommended, and you should look for an alternative.
If you definitely need this, consider a "ping"-style solution. Send a request every minute basically telling the server "I'm still here". Then, if the server doesn't receive such a request for more than two minutes (you have to take into account latencies etc.), you consider the client offline.
Another solution would be to use unload or beforeunload to do a Sjax request (Synchronous JavaScript And XML), but this is completely not recommended. Doing this will basically freeze the user's browser until the request is complete, which they will not like (even if the request takes little time).
1) If you're looking for a way to work in all browsers, then the safest way is to send a synchronous AJAX to the server. It is is not a good method, but at least make sure that you are not sending too much of data to the server, and the server is fast.
2) You can also use an asynchronous AJAX request, and use ignore_user_abort function on the server (if you're using PHP). However ignore_user_abort depends a lot on server configuration. Make sure you test it well.
3) For modern browsers you should not send an AJAX request. You should use the new navigator.sendBeacon method to send data to the server asynchronously, and without blocking the loading of the next page. Since you're wanting to send data to server before user moves out of the page, you can use this method in a unload event handler.
$(window).on('unload', function() {
var fd = new FormData();
fd.append('ajax_data', 22);
navigator.sendBeacon('ajax.php', fd);
});
There also seems to be a polyfill for sendBeacon. It resorts to sending a synchronous AJAX if method is not natively available.
IMPORTANT FOR MOBILE DEVICES : Please note that unload event handler is not guaranteed to be fired for mobiles. But the visibilitychange event is guaranteed to be fired. So for mobile devices, your data collection code may need a bit of tweaking.
You may refer to my blog article for the code implementation of all the 3 ways.
I also wanted to achieve the same functionality & came across this answer from Felix(it is not guaranteed that a request initiated in one of these events will reach the server).
To make the request reach to the server we tried below code:-
onbeforeunload = function() {
//Your code goes here.
return "";
}
We are using IE browser & now when user closes the browser then he gets the confirmation dialogue because of return ""; & waits for user's confirmation & this waiting time makes the request to reach the server.
Years after posting the question I made a way better implementation including nodejs and socket.io (https://socket.io) (you can use any kind of socket for that matter but that was my personal choice).
Basically I open up a connection with the client, and when it hangs up I just save data / do whatever I need. Obviously this cannot be use to show anything / redirect the client (since you are doing it server side), but is what I actually needed back then.
io.on('connection', function(socket){
socket.on('disconnect', function(){
// Do stuff here
});
});
So... nowadays I think this would be a better (although harder to implement because you need node, socket, etc., but is not that hard; should take like 30 min or so if you do it first time) approach than the unload version.
The selected answer is correct that you can't guarantee that the browser sends the xhr request, but depending on the browser, you can reliably send a request on tab or window close.
Normally, the browser closes before xhr.send() actually executes. Chrome and edge look like they wait for the javascript event loop to empty before closing the window. They also fire the xhr request in a different thread than the javascript event loop. This means that if you can keep the event loop full for long enough, the xhr will successfully fire. For example, I tested sending an xhr request, then counting to 100,000,000. This worked very consistently in both chrome and edge for me. If you're using angularjs, wrapping your call to $http in $apply accomplishes the same thing.
IE seems to be a little different. I don't think IE waits for the event loop to empty, or even for the current stack frame to empty. While it will occasionally correctly send a request, what seems to happen far more often (80%-90% of the time) is that IE will close the window or tab before the xhr request has completely executed, which result in only a partial message being sent. Basically the server receives a post request, but there's no body.
For posterity, here's the code I used attached as the window.onbeforeunload listener function:
var xhr = new XMLHttpRequest();
xhr.open("POST", <your url here>);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
var payload = {id: "123456789"};
xhr.send(JSON.stringify(payload));
for(var i = 0; i < 100000000; i++) {}
I tested in:
Chrome 61.0.3163.100
IE 11.608.15063.0CO
Edge 40.15063.0.0
Try this one. I solved this problem in javascript, sending ajax call to server on browse or tab closing. I had a problem with refreshing page because on onbeforeunload function including refreshing of the page. performance.navigation.type == 1 should isolate refresh from closing (on mozzila browser).
$(window).bind('mouseover', (function () { // detecting DOM elements
window.onbeforeunload = null;
}));
$(window).bind('mouseout', (function () { //Detecting event out of DOM
window.onbeforeunload = ConfirmLeave;
}));
function ConfirmLeave() {
if (performance.navigation.type == 1) { //detecting refresh page(doesnt work on every browser)
}
else {
logOutUser();
}
}
$(document).bind('keydown', function (e) { //detecting alt+F4 closing
if (e.altKey && e.keyCode == 115) {
logOutUser();
}
});
function logOutUser() {
$.ajax({
type: "POST",
url: GWA("LogIn/ForcedClosing"), //example controller/method
async: false
});
}
Im agree with Felix idea and I have solved my problem with that solution and now I wanna to clear the Server Side solution:
1.send a request from client side to server
2.save time of the last request recived in a variable
3.check the server time and compare it by the variable of last recived
request
4.if the result is more than the time you expect,start running the
code you want to run when windows closed...
Use:
<body onUnload="javascript:">
It should capture everything except shutting down the browser program.
I am using chrome.identity.launchWebAuthFlow to retrieve an authentication code from my Rails app which is set up as an OAuth2 provider using the Doorkeeper gem (the Doorkeeper side of things is working).
So I send then request to my server with this method from the Chrome extension:
requestGrant: function(){
chrome.identity.launchWebAuthFlow(
{
'url': authService.grantEndPoint(),
'interactive': true
}, function(redirect_url){
/* Extract auth code from redirect_url */
var code = authService.extractCode(redirect_url);
alert(code);
authService.getAccessToken(code);
}); //launchWebAuthFlow ends here
}
And my server receives the request and redirects to
https://<my_chrome_extension_name>.chromiumapp.org/oauth2?code=<access_code_generated_by_oauth>
with a 302 found.
But, the Chrome extension immediately closes, and the callback function of launchWebAuthFlow does not run. I know it is not running because I call alert() in the callback (doesn't run), and make another request to my server for the access token (server does not receive the request).
I think part of the problem is that the chrome extension might be closing right when launchWebAuthFlow is called, because the window launchWebAuthFlow opens is a web view of the servers authorization flow, and the extension closes (or is the auth flow just being displayed through the extension?)
For some reason launchWebAuthFlow's default behavior is to close the window according the documentation, but the callback still isn't running.
How can I get the callback function to run, and prevent the chrome extension window from closing?
I was running the launchWebAuthFlow in non background script. I moved the auth logic to a background script and it works fine.
I too was experiencing almost the same issue. I was trying to launch the webauthflow from my popup.html but the popup.html would close once the auth flow began, aborting the code that would execute upon succesful return of a token.
My suggestion is to instead take care of authentication in options.html.
(https://developer.chrome.com/extensions/optionsV2)
This will launch a popup modal that stays open even after your auth flow opens, (as opposed to popup.html closing when it loses focus), meaning the rest of your code will execute.
Hope this helps.
I'm working with a web app that locks resources on a server. To unlock them, it has to delete a resource on the server with an HTTP DELETE. I know this isn't reliable and there's a periodic cleanup running as well which unlocks them, but the goal is to unlock the resource as soon as possible.
I cannot change the locking architecture (it's not my system), I just have to make the best stab at unlocking.
One point where I need to unlock is when the tab or browser is closed. First, I'm handling the onbeforeunload and if the document is dirty, prompting the user for confirmation that they want to close:
$window.onbeforeunload = function() {
if (documentIsDirty) {
return "Some prompt text";
}
};
I can't unlock from within onbeforeunload, as the user may choose to cancel the close. But there's no event (correct me if I'm wrong) between onbeforeunload and onunload.
If I try to make the call from in onunload, then the tab/session gets destroyed as soon as the onunload function returns. Trouble is, that's before the http request has completed, and it turns out that the resource doesn't actually get unlocked.
$window.onunload = function() {
$http.delete('/a/lock/url');
// I've tried forcing a digest cycle to try and flush the request
// through, but it never gets sent either way
$rootScope.$digest();
};
Now, I know it's anathema to actually block in Javascript, but it appears that once onload returns, that's all she wrote.
Is there any way to block until the http request has actually completed, and prevent onunload from returning until then?
[UPDATE]
Solution was as below - use XMLHttpRequest synchronously. It's noisily deprecated but does (at the time of writing) still work at least in Chrome.
var request = new XMLHttpRequest();
request.open('DELETE', url, false);
request.setRequestHeader('X-XSRF-TOKEN', myXSRFToken);
request.send();
$http will always do requests asynchronously because internally it's just using XMLHttpRequest and always passing true as the third parameter to a request's open function. From the MDN documentation for XMLHttpRequest's open function:
An optional Boolean parameter, defaulting to true, indicating whether or not to perform the operation asynchronously. If this value is false, the send()method does not return until the response is received.
If you want to do a synchronous request you can just use XMLHttpRequest directly and pass false as the third parameter to open. Since this is when the site is closing it's not really necessary to use Angular's $http anyway.
Recently, Have faced same issue while doing this kinda activity and resolved it by using navigator.sendBeacon() .The navigator.sendBeacon() method asynchronously sends a small amount of data over HTTP to a web server. For latest browser you could do like
window.addEventListener('beforeunload', function (event) {
data = new FormData();
// for CSRF Token
token = $('meta[name="csrf-token"]').attr('content');
data.append("key", value);
data.append("authenticity_token", token);
navigator.sendBeacon("URL", data);
// Cancel the event as stated by the standard.
event.preventDefault();
// Chrome requires returnValue to be set.
event.returnValue = 'Are you sure you want to leave this page without saving?';
});
For more details checkout Navigator.sendBeacon() and Window: beforeunload event
I've been writing a chrome extension using the Spotify API and just got to the part where I need OAUTH to finish it up.
I've been looking at the spotify oath page (https://developer.spotify.com/web-api/authorization-guide/)
and it says you can just make a GET call and it'll take you to the page to log in, however when I implement it, it does nothing. I feel like I am missing some code but I am not sure what I am missing.
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// Just alerting it to see what it came up with
alert(xhr.responseText);
}
}
xhr.open("GET", "https://accounts.spotify.com/authorize/?client_id=<clientID>&response_type=code&redirect_uri=https%3A%2F%2Fwww.google.com%2F", true);
xhr.send();
that is the code I have now (with client id filled in), and running it does nothing. (I specified http://www.google.com/ as my callback for now)
An XHR request will not, under any circumstance, open a page.
Running this code should return you the page contents, but you do nothing with it - certainly not properly display it for the user to interact with.
It seems like you need to read more on OAuth in general. That said:
Chrome extension API provides a specific tool for this: chrome.identity.launchWebAuthFlow(). It will take care of showing the auth page to the user and passing the token back to you. You need to use a specific "virtual" URL for it - see the chrome.identity documentation for more details. Also note that this API can't be used in a content script, you'll need to delegate this to the background page.