I've got an application that I intend to set a lock flag in my database that would exclude others from viewing that same page if set.
However, once set - I have no idea how to "unset" it. I could make it up to the user to unset the flag, but that seems unnecessary.
I'd want to simply look for the browser to leave the page, make a call to the database, and unlock the page.
How does one do this "type" (not looking for the exact way) of thing with JSF/Javascript/jQuery (all options)
There's really not a reliable way to do this, that I've seen anyway.
You can use the browser's onbeforeunload event to tell the server, "Hey I'm leaving the page now.". The issue is you can't actually block the page from unloading. If the user is actually closing the browser, any open sockets are going to be closed immediately. Your web server may or may not get the request in time. I've had very flaky results with this approach.
One approach that might work is to employ some sort of timeout mechanism. The page would ping the server every 30 seconds or what not, saying "I'm still here." If the server did not get this update after a few minutes, it would invalidate the session and free up that document. Perhaps this could be optimized by checking for the last ping when someone new came along. One issue with this is if someone left the page, the next user might have to wait a minute or two before they could go to the page. You'd then have to find a ping frequency that doesn't flood your server with traffic, but also doesn't make the next user have to wait too long.
It's also possible to combine these two methods. When the user leaves the page, trap the onbeforeunload event and immediately invalidate the session. However, if it didn't work, the session would time out after a minute of not being pinged.
Are there better solutions?
If you really need to lock a document in a web app so multiple users can't edit it, you might want to investigate your overall design. Are you afraid of users clobbering data? If so, maybe employ a mechanism that can resolve merge conflicts, or detect if both sets of changes can be combined.
If you wanted to go truly Web 2.0, you could design something similar to Google Docs, where changes appear live as they're made. No need for a Save button anywhere!
Sending a "keep-alive signal" might be an option. Something along these lines on the frontend side, combined with session cookies.
setInterval(function() {
var img = new Image();
var src = "http://examle.com/keepalive.gif?cachebuster=" +
Math.ceil(Math.random() * 10000 );
}, 1000);
Related
I need to kill the session when the user closes the browser or redirects into some other page.
I can see the following options of achieving this functionality:
Use no session login. It's not my case, because I'd have to change a lot and I also use sessions for some other data.
I could use something like this:
window.onunload = window.onbeforeunload = (function () {
...
})
And from this code call the action that cleans the session and performs logoff.
Sounds nasty but what is also important - this JavaScript code works only in IE.
I could create some nasty code that uses some dummy calls, let's say every minute, just to say the server that the user is still alive.
But it's really nasty. It would use useless load on the server, lots of useless calls and in the case if some call was lost(because of the connection issue or something) the user would logg off.
Any other options?
You've left off #4: Don't do anything, have sessions time out after a reasonable period (say, 20 minutes); if they try to do something on that page after being gone for 20 minutes, just show a page telling them their session has expired and to log in again. That's usually the simplest option.
If you don't want to do that, #3 is really your only viable option, but once/minute is probably overkill. Set the session timeout to 20 minutes, remember when the user has done something, and if they're idle for (say) 15 minutes do a proactive call on their behalf. But even then, I'd limit how much I'd do this, after a couple of hours you might want to just redirect them to the login page.
I think this answer is the right way to go:
In javascript, how can I uniquely identify one browser window from another which are under the same cookiedbased sessionId
Set a unique window id:
window.windowIdClient = "{978d-478ahjff-3849-dfkd-38395434}"; //or another randomly generated id.
Store that windowId in the database, along with the ip-address and the session-id. If those three do not match than the user is logged out.
In addition, if didn't think of T.J. Crowder's option, I use it myself.
Is there a way to bind a jQuery event to pop off after a visitor holds a button for more than, say, 3 seconds? In this case I'd like to do something after a user holds f5 for 3 seconds.
I've found a solution that prevents the refresh when user presses f5 here:
Disable F5 and browser refresh using javascript
However, this isn't quite what I'm looking for as I'd like to allow my users to refresh pages but prevent them from placing a rock on the f5 button so to speak (it's an online game where you can do tasks by refreshing the page).
Is what I'm trying to do even possible?
EDIT: I am looking for a short-term, quick, client-side fix. The server side of things will be solved later on.
This is the kind of problem you really need to solve server side. If the task can be accomplished by refreshing the page, then it can be accomplished by a bot or a line-or-two of script that disables your check. Doing this client side won't really help you.
All you need to do in that case is on the users session, track the last time that action X was performed, and don't perform it again unless more than Y seconds have passed, and tell the user off for trying to do it too fast.
Edit: As a quick hack, what you could possibly do is configure your webserver to supply a cache-control header in the response for affected scripts with a value set to have the content expire in Y seconds. This may (emphasis on may, test this properly first!) prevent the client from re-requesting the page on a normal 'F5', however a force refresh (ctrl+f5 in some browsers) won't be affected by this.
Although again if the issue is that users are exploiting this issue, fixing it client side won't work for very long and users will quickly discover another way around.
PhonicUK's answer (do it server side) is the only correct way to handle this. But you said in a comment on that answer:
I was just asked to apply a quick client-side fix for now. That's why I'm asking if it's possible.
It's possible to do something that will get bypassed, like this (here I'm assuming you use a jQuery cookie plugin):
(function($) {
$(document).on("keydown", function(e) {
var lastRefresh, now;
if (e.which == 116) {
now = new Date().getTime();
lastRefresh = $.cookie("lastRefresh");
if (lastRefresh && (now - parseInt(lastRefresh, 10)) < 3000) {
e.preventDefault();
}
$.cookie("lastRefresh", now);
}
});
})(jQuery);
Again, though, the users will find a way around it.
Be sure to run the script through the Closure compiler or a really good obfuscator. :-)
I have an ajax heavy website that breaks (or shows incorrect data) when users have it open in multiple browser windows at the same time. So I would like to enforce only allowing the user to be logged in to the website in one tab at a time, whether it is on the same computer or even multiple computers.
I am looking for ideas on how to do this.
Is there any JavaScript method to tell if a certain page is already open in another tab?
Perhaps there is another solution that could involve the server side..
For instance, the client could message the server every say, 1 minute. If the server gets messages from a certain users at a frequency higher than one message per minute, it knows that it is open in more than one window or tab. It can then let one of the clients know that it needs to shout an error to the user.
The idea of messaging the server every one minute does not sit that well with me though.
Any other ideas out there?
EDIT: some people are wondering why I have this problem in the first place. Here it goes:
This is a time tracking application that is fully ajax. You can browse/create/delete/modify timers, projects and clients with ajax, without ever leaving the page. If the website is open in multiple tabs, things will get inconsistent very quickly. Errors usually even occur. For instance, user creates a project and then starts a timer in tab1, tab2 will not show these changes. And since it is all ajax, it will not simply sync when the user clicks some button in the second tab.
Having read the update in your question, what I would really suggest is using WebSocket where available, falling back to Flash socket, long polling and forever iframe for older browsers (actually I'd use Socket.IO to make it all easy - you can use a similar abstraction for whatever environment you are using). That way you can make all of your windows and tabs consistent in real time - problem solved.
That having been said if you don't want to do it for some reason (though what you are trying to do would be a perfect application for WebSockets so think about it) you might use sessionStorage and localStorage to distinguish sessions between tabs or windows for the same logged in user, but it is not widely available yet - see the compatibility table so it would be probably easier to go real-time with a socket.io-like solution where there are a lot of fallbacks available than to restrict visitors to one tab - not to mention the user experience.
There's no way to get information about other tabs/windows in javascript (and for good reason).
The best way I can think to do it would be to print a unique identifier (a timestamp should work reasonably well) in the javascript code for each page, and then it periodically ping the server with that unique ID, and associate it on the server with the user. This way if you have more than one ID belonging to a single user being pinged within a given interval, you can send back a response to the page to warn the user that having multiple tabs open will result in unexpected behavior.
(Like Caspar said above though, you should really figure out why the unexpected behavior is happening and fix that rather than force the user to act a certain way)
This is pretty lo-fi, but I think the simplicity may make it work: you could try having the login open the session in a named window (or change the name of the current window). Then, on load inside the application, check to see if the browser window name is the one you've allowed them to use; if not, pop up an alert, close the window, focus on the named window, if still there. (If not there--i.e., they've already closed the other window--you could let this one stay open, and change the name to the correct name.)
So you're essentially using window.name and window.opener. Rough idea, but an idea.
I have a similar situation and the solution I use is:
on server: at every login you create an unique ID, save it (ex. database) and return it to client.
on client: on every transaction you send this ID to server as a parameter.
on server: if saved and received ID match then allow the request to execute if not refuse it with an error code.
on client: if transaction failed with specific code then you know that "ID" verification failed and you logout user.
So in this way if the same credentials will be used again in any other tab, browser, PC, country,... the old tab will logout user on next transaction request. Or in other words limiting only one opened page per user on the whole world.
Edit:
As I have stopped using html requests for any data communication and use websockets, I register user on server and if same user wants to login from some other location I close the previously used socket (the page automatically logs out).
In this way I also have a way to trigger full page reloads from server in case admin does something that influences users.
Simply use cookies.
$(window).on('beforeunload onbeforeunload', function(){
document.cookie = 'ic_window_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
});
function validateCallCenterTab() {
var win_id_cookie_duration = 10; // in seconds
if (!window.name) {
window.name = Math.random().toString();
}
if (!getCookie('ic_window_id') || window.name === getCookie('ic_window_id')) {
// This means they are using just one tab. Set/clobber the cookie to prolong the tab's validity.
setCookie('ic_window_id', window.name, win_id_cookie_duration);
} else if (getCookie('ic_window_id') !== window.name) {
// this means another browser tab is open, alert them to close the tabs until there is only one remaining
var message = 'You cannot have this website open in multiple tabs. ' +
'Please close them until there is only one remaining. Thanks!';
$('html').html(message);
clearInterval(callCenterInterval);
throw 'Multiple call center tabs error. Program terminating.';
}
}
callCenterInterval = setInterval(validateCallCenterTab, 3000);
}
I'm working on something similar to a pastebin (yeah, it's that generic) but allowing for multiple user editing. The obvious problem is that of multiple users attempting to edit the same file. I'm thinking along the lines of locking down the file when one user is working on it (it's not the best solution, but I don't need anything too complex), but to prevent/warn the user I'd obviously need a system for monitoring each user's edit sessions. Working with database and ajax, I'm thinking of two solutions.
The first would be to have the edit page ping the server at a arbitrary interval, say a minute, and it would update the edit session entry in the db. Then the next time a script request to edit, it checks for the most recent ping, and if the most recent was another arbitrary time ago, say five minute, then we assume that the previous user had quited and the file can be edited again. Of course, the problem with this method is that the assumption that the previous user had quited is simply an assumption. He could be having flaky wi-fi connection and simply dropped out for ten minutes, all the time with the window still open.
Of course, to deal with this problem, we'd have to have the server respond to new request from previously closed sessions with an error, telling the client side to point out to the user that his session has ended, and then deal with it by, say, saving it as another file on the server and asking the user to manually merge it, etc. It goes without saying that this is rather horrible for the end user.
So I've came around to think of another solution. It may also be possible to get a unload event to fire when the user's session ends, but I cannot be sure whether this will work reliably.
Does anybody has any other, more elegant solution to this problem?
If you expect the number of concurrent edits to the file to be minor, you could just store a version number for the file in the db, and when the user downloads the file into their browser they also get the version number. They are only allowed to upload their changes if the version number matches. First one to upload wins. When a conflict is detected you should send back the latest file and the user's changes so that the user can manually merge in the changes. The advantage is that this works even if it's the same user making two simultaneous edits. If this feature ends up being frequently used you could add client-side merging similar to what a diff tool uses (but you might need to keep the old revisions in that case).
You're probably better off going for a "merge" solution. Using this approach you only need to check for changes when the user posts their document to the server.
The basic approach would be:
1. User A gets the document for editing, document is at version 1
2. User B gets the document for editing, document is at version 1
3. User B posts some changes, including the base version number of 1
4. Server updates document, document now at version 2
5. User B posts some changes, including the base version number of 1
6. Server responds saying document has changed since the user starts editing, and sends user the new document, and their version - user will then need to perform any merging of their changes into document version 2, and post back to the server. User is essentially now editing document version 2
7. User A posts some changes, including the version number of 2
8. Server updates the document, which is now at version 3
You can still do a "ping" every minute, to get the current version number - you already know what version they're editing, so if a new version is available you can let them know and let them download the latest version to make their changes into.
The main benefit of this approach is that users never lock files, so you don't need any arbitrary "time-outs".
I would say you are on the right track. I would probably implement a hybrid solution:
Have a single table called "active_edits" or something like that with a column for the document_id, the user, and the last_update_time. Lets say your ping time is 1 minute and your timeout is 5 minutes. So a use-case would look like this:
Bob opens a document. It checks the last_update_time. If it is over 5 minutes ago, update the table with Bob and the current time. If it is not, someone else is working on the document, so give an error message. Assuming it is not being edited, Bob works on the document for a while and the client pings an update time every minute.
I would say do include a "finish editing" button and a onunload handler. Onunload, from what I understand can be flaky, but might as well add it. Both of these would send a single send-only post to the server saying that Bob is done. Even if Bob doesn't hit "finish editing" and onunload flakes out, the worst case is that another user would have to wait 5 more minutes to edit. The advantage is that if these normally work (a fair assumption) then the system works a bit better.
In the case you described where a Bob is on a bad wireless connection or takes a break: I would say this isn't a big deal. Your ping function should make sure that the document hasn't been taken over by someone else since Bob's last ping. If it has, just give Bob a message saying "someone else has started working on the document" and give them the option to reload.
EDIT: Also, I would be looking into window.onbeforeunload, not onunload. I believe it executes earlier. I believe this is the function website (slashdot included) use to allow you to confirm that you actually want to leave the page. I think it works in the major browsers except Opera.
As with this SO question How do you manage concurrent access to forms?, I would not try to implement pessimistic locking. It is simply too difficult to get working reliably in a stateless environment. Instead, I would use optimistic locking. However, in this case I used something like a SHA hash of the file to determine if the file had changed since the user last read from the file. For each request to change the file, you would run a SHA hash of the file bytes and compare it with the version you pulled when you first read the data. If had changed, you reject the change and either force the user to do their edits again (pulling a fresh copy of the file contents) or you provide a fancier conflict resolution.
In my web app, when a user logs in, I add his Id to a vector of valid Ids in the servlet, when he logs out, I remove his Id from the vector, so I can see how many current users are active, if a user forgets to log out, my servelt generated html has :
<meta http-equiv="Refresh" content="30; url=My_Servlet?User_Action=logout&User_Id=1111">
in the tag to automatically log him out.
But I've noticed many users are there for ever, never logged out. I found out why, by closing their browsers, they never manually or automatically logged out, so their user Ids will never be removed from the valid user Ids vector.
So, my question is : how do I detect users closing their browsers, so my servlet can remove their Ids from the vector ?
I see some light at the end of the tunnel, but there is still a problem, my program has something like this :
Active User List :
User_1 : Machine_1 [ IP_1 address ]
User_2 : Machine_2 [ IP_2 address ]
User_3 : Machine_3 [ IP_3 address ]
...
How do I know, from the session listener, which user's session has ended and therefore remove him from my list?
I was hoping when the session ends, the HttpServlet's destroy() method would be called and I can remove the user Id in there, but it never gets called when user closes his browser, why? And is there any other method in the HttpServlet that gets called when a session closes?
There is no way to know on the server-side (unless you are using some JavaScript to send a message to the server) that the browser has closed. How could there be? Think of how HTTP works - everything is request and response.
However, the application server will track when Sessions are active and will even tell you when a Session has been destroyed (such as due to time-out). Take a look at this page to see how to configure a HttpSessionListener to receive these events. Then you can simply keep track of the number of active sessions.
The number of active sessions will lag behind the actual number of current users, since some period of (configurable) time has to elapse before a session is timed out; however, this should be somewhat close (you can lower the session-timeout to increase the accuracy) and it is a lot cleaner and easier than 1) tracking Sessions yourself or 2) sending some asynchronous JavaScript to the server when a browser is closed (which is not guaranteed to be sent).
I suggest you remove the ID when the Servlet engine destroys the session. Register a HttpSessionListener that removes the user's ID when sessionDestroyed() is called.
Diodeus's idea will only help you detect that the session is over more immediately.
in JavaScript you can use the onbeforeclose event to pass a call back to the server when the user closes the browser.
I typically use a synchronous Ajax call to do this.
I had to do that recently, and after some searches, I found some solutions on the Net... all of them non working universally!
onbeforeclose and onclose events are used for this task. But there are two catches: they are fired when the user reload the page or even just change the current page. There are tricks to see if the event is actually a window/page/tab closing (looking at some Dom properties going haywire on closing event), but:
They are browser dependent
The tricks are undocumented, thus brittle
And actually they vary along the browser version/update...
And worst of all, these events are now ignored by most modern browsers, because they have been abused by rogue ads popping out windows when browser was closing. They are not fired in Safari, Opera, IE7, etc.
As pointed out, most Web applications with login destroy the user session after a while, eg. half an hour. I was asked to logout on browser closing to free faster a precious resource: licenses. Because users often forget to log out...
The solution I gave was to ping with an Ajax request (sending the user ID) the server on regular intervals (say 1 minute). If the server receives no ping for, say, 3 minutes, it disconnect the user.
There is no foolproof way to do what you're trying to do, but both sblundy and Diodeus have plans that will cover most circumstances. There is nothing you can do about someone who turns off Javascript in their browser, or their internet connection goes down, or their power goes out. You should just cull sessions after a certain period of inactivity (which I think is what sblundy's suggestion of listening for session destruction will do).