I want to display in a <p:growl>
that the session has expired.
I found many methods to handle session expiration like Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request, but I couldn't push a faces message to <p:growl>
.
To the point, how can I automatically run some (JavaScript) code in client side when the HTTP session has automatically expired in server side?
You can use PrimeFaces idle monitor for this. User is redirected to logout action after timeout to invalidate the session. 2 minutes before a countdown dialog is shown to warn user. After moving the mouse again session is extended.
PrimeFaces idle monitor and dialog is placed in a template you can add to every page which is involved:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<ui:composition>
<h:form prependId="false">
<p:idleMonitor
timeout="#{session.maxInactiveInterval * 1000 - 125000}"
onidle="startIdleMonitor()"
onactive="timeoutDialog.hide()" />
<p:dialog id="timeoutSession"
header="#{msg['session.expire']}"
widgetVar="timeoutDialog"
showEffect="fade" hideEffect="fade"
modal="true"
width="400"
height="110"
closable="false"
draggable="false"
resizable="false"
appendToBody="true"
onHide="stopCount()"
onShow="doTimer()">
<br />
<p>
<span class="ui-icon ui-icon-alert" style="float: left; margin: 8px 8px 0;"/>
<p:panel>
#{msg['logoff.soon.1']}
<span id="dialog-countdown" style="font-weight: bold"></span>
#{msg['logoff.soon.2']}
</p:panel>
</p>
<br />
<p style="font-weight: bold;">#{msg['move.cursor']}</p>
</p:dialog>
<p:remoteCommand name="keepAlive" actionListener="#{auth.keepSessionAlive}" />
</h:form>
<script type="text/javascript">
var TIME = 120; // in seconds
var countTimer = TIME;
var processTimer;
var timer_is_on = 0;
var redirectPage = "#{request.contextPath}/auth/j_verinice_timeout";
var countDownDiv = "dialog-countdown";
var txtCountDown = null;
if (!txtCountDown)
txtCountDown = document.getElementById(countDownDiv);
function startIdleMonitor() {
countTimer = TIME;
txtCountDown.innerHTML = countTimer;
timeoutDialog.show();
}
function timedCount() {
txtCountDown.innerHTML = countTimer;
if (countTimer == 0) {
stopCount();
window.location.href = redirectPage;
return;
}
countTimer = countTimer - 1;
processTimer = setTimeout("timedCount()", 1000);
}
function doTimer() {
if (!timer_is_on) {
timer_is_on = 1;
timedCount();
}
}
function stopCount() {
clearTimeout(processTimer);
timer_is_on = 0;
keepAlive();
}
</script>
</ui:composition>
</html>
- Line 11: The timeout of the idle monitor is set by system var session.maxInactiveInterval. The value you set in your web.xml or server configuration.
- Line 12/13: Javascript method startIdleMonitor() is called after timeout without any user interaction. This method opens the dialog. timeoutDialog.hide() is called when user is busy again: Dialog is closed
- Line 26/27: Two more Javascript methods are called when dialog is shown or hide: doTimer() starts and stopCount() stops the countdown.
- Line 40: PrimeFaces remote command to keep session alive. By calling an arbitrary method on server the session is extended. Command is called by Javascript method keepAlive() in line 78.
- Line 59-68: Javascript method timedCount() is called every second to execute the countdown. After timeout redirect is done in line 63.
To activate timeout handling in multiple pages include the timeout template in your layout template:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xml:lang="de-DE">
<h:head>
...
</h:head>
<body>
<ui:include src="/template/sessionTimeOut.xhtml" />
<ui:include src="/nav.xhtml"/>>
<ui:insert name="content">Default content</ui:insert>
<ui:include src="/footer.xhtml"/>>
</body>
</html>
A specific time out for your web application you can set in your web.xml:
<!--?xml version="1.0" encoding="UTF-8"?-->
<web-app>
...
<session-config>
<!-- Session idle timeout in min. -->
<session-timeout>30</session-timeout>
</session-config>
</web-app>
You can read more about this solution in this blog post: JSF and PrimeFaces: Session Timeout Handling
If you're on JSF 2.3, then you can use a session scoped <f:websocket>
for this. When the session expires in server side, then the <f:websocket>
is automatically closed with close code 1000 ("normal closure").
In other words, just do:
<f:websocket ... scope="session" onclose="sessionScopedSocketCloseListener" />
function sessionScopedSocketCloseListener(code) {
if (code == 1000) {
alert("Session has expired!");
}
}
You can if necessary combine this with JavaScript API of the <p:growl>
.
<p:growl widgetVar="growl" ... />
PF("growl").renderMessage({severity: "warn", summary: "Session has expired!" });
If you're not on JSF 2.3 yet, then you can always use <o:socket>
instead which offers exactly the same onclose
feature as <f:websocket>
. See also How can server push asynchronous changes to a HTML page created by JSF?
The code posted by @uı6ʎɹnɯ lǝıuɐp i's very useful, but i will add some observations:
@Jonathan L sugests replace processTimer = setTimeout("timedCount()", 1000)
to setInterval("timedCount()", 1000)
. I think makes sense, but need some
modifications:
function doTimer() {
if (!timer_is_on) {
timer_is_on = 1;
processTimer = setInterval("timedCount()", 1000);
}
}
function stopCount() {
clearInterval(processTimer);
timer_is_on = 0;
keepAlive();
}
The method timedCount() can be changed to:
function timedCount() {
txtCountDown.innerHTML = countTimer;
if (countTimer > 0) {
countTimer = countTimer - 1;
} else {
clearInterval(processTimer);
doLogout();
}
}
And you need add under the keepAlive
remoteCommand something like this:
<p:remoteCommand name="doLogout" action="#{loginMB.logout()}"
process="@this" partialSubmit="true" immediate="true" />
Inside Managed Bean:
public void logout() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
validUser = false;
loggedUser = false;
redirectToPage("/login.xhtml");
}
private void redirectToPage(String page) {
ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
context.redirect(context.getRequestContextPath() + page);
}
Remember the atribute name="doLogout"
of p:remoteCommand
is converted in a javascript function that you can call anywhere. It's perfect to iterate the view with managed bean.
This approach will prevent your system stacks the ViewExpiredException.
Some people doesn't understand the keepAlive
concept, but it's very easy, just add a simple function inside your managed bean. Example keepSessionAlive
action:
public void keepSessionAlive () {
System.out.println(">>> Session is alive... ");
}
Remember the p:remoteCommand
named keepAlive
is a javascript function called keepAlive()
and that call an action keepSessionAlive
inside your managed bean.
This action signs you are not idle.
The session.maxInactiveInterval
of idleMonitor references <session-timeout>
of web.xml
<p:idleMonitor timeout="#{session.maxInactiveInterval * 1000 - 125000}"
onidle="startIdleMonitor()" onactive="timeoutDialog.hide()" />
I recommend put session monitor to execute 5 seconds before finish the session to prevent run doLogout()
after session end and get the ViewExpiredException.
The first line of javascript code is var TIME = 120; // in seconds
, this represents the time to finalize the session. The original code # {session.maxInactiveInterval * 1000 - 125000}
will use session-timeout mutiplicated by 1.000 (because it's miliseconds) and subtract 125.000 that represents 125 seconds, 5 seconds less than counter, therefore doesn't need changes.
I hope this helps... Good Luck!