'Automatically perform action in client side when the session expires

I want to display in a <p:growl> that the session has expired.

I found many methods to handle session expiration like:

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?



Solution 1:[1]

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

Solution 2:[2]

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?

Solution 3:[3]

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!

Solution 4:[4]

I just wanted to post here for future visitors, I was able to find a slightly different but working approach with idlemonitor and timer components. Basically, if the session timeout is set to 30 mins in web.xml, this code will open a dialog after 28 min of user being idle, with the following components: Message - You are about to be logged off in mins. Please click the ok button to keep the session alive. Timer - Primefaces extensions timer which will have a 2 min countdown. Ok button - keeps session alive Log Out button - Logs the user out. Here is the code for this change:

sessionTimeOut.xhtml

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:p="http://primefaces.org/ui"
       xmlns:pu="http://primefaces.org/ultima"
       xmlns:pe="http://primefaces.org/ui/extensions">


       <h:form>
           <p:idleMonitor timeout="#{session.maxInactiveInterval * 1000 - 130000}" onidle="PF('idleDialog').show();PF('timeoutTimer').start();" />

           <p:dialog id="timeoutDialog" header="Are you there?" widgetVar="idleDialog" modal="true" closable="false" draggable="false" resizable="false" >
                <p:panelGrid columns="1" styleClass="ui-noborder">

                    <p:panel>
                        <h:outputText value="You are about to be logged off in " />
                        <p:spacer width="10"/>
                        <pe:timer id="timeoutTimer" widgetVar="timeoutTimer" singleRun="true" timeout="120" format="mm:ss" autoStart="false" listener="#{userSessionBean.logout()}"/>
                        <p:spacer width="10"/>
                        <h:outputText value=" mins." />

                    </p:panel>

                    <p:panel>
                        <h:outputText value="Please click 'Ok' to keep the session alive" />
                    </p:panel>

                    <p:panel style="text-align: center;">
                        <p:commandButton id="confirm" value="Ok" actionListener="#{userSessionBean.keepAlive()}" onclick="PF('timeoutTimer').stop(true);" oncomplete="PF('idleDialog').hide();" process="@this"/>
                        <p:spacer width="10"/>
                        <p:commandButton id="timeoutLogout" value="Log Out" widgetVar="timeoutLogoutWV"
                               actionListener="#{userSessionBean.logout()}" oncomplete="PF('timeoutTimer').stop(true); PF('idleDialog').hide(); location.reload(true);"/>
                    </p:panel>

                </p:panelGrid>
           </p:dialog>
       </h:form>

</ui:composition>

insert this into your base template as this

<ui:include src="./sessionTimeOut.xhtml" />

Here is the java side code

public void keepAlive() {
    logger.info("User " + loggedInUser.getUserLogin() + " requested the Session " + getCurrentHttpSessionId() + "  to be kept alive at " + new Date().toString());

    /**
     * Do nothing
     */
}


public String logout() throws IOException {
    FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();
    externalContext.getContextName();

    logger.info(FacesContext.getCurrentInstance().getExternalContext().getContextName());

    facesContext.getExternalContext().redirect("/Project/views/login.xhtml");

    logger.info("Logout");

}

I hope this helps. Thanks.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Divyesh Kanzariya
Solution 2 BalusC
Solution 3
Solution 4 AmeyK