Automatically perform action in client side when the session expires
Asked Answered
H

4

11

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?

Hierology answered 30/9, 2013 at 15:3 Comment(1)
@ Xtreme Biker I tried <p:poll>Hierology
M
18

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

Marcusmarcy answered 1/10, 2013 at 7:32 Comment(7)
I did not understand the method #{auth.keepSessionAlive}Hierology
keepSessionAlive() is a method in a JSF bean. Calling this method stops idle time for the user session on the application server. Just call an arbitrary getter in this method. I think it can even be empty.Headstream
Please do not only post link-only answers. If the link contains the answer, put the relevant part of the answer in your answer on StackOverflow. If the content behind the link changes, or the content is removed, your answer does not answer this question anymore.Inopportune
For those using the example with Primefaces 4.0 or higher, you may need to use Primefaces PF function to show / hide dialog, e.g. onactive="PF('timeoutDialog').hide();" PF('timeoutDialog').show();Corticosteroid
JavaScript code processTimer = setTimeout("timedCount()", 1000) can be replaced by processTimer = setInterval("timedCount()", 1000) since timer countdown is a repeat action. Related logic can be simplified a little bit as of the results.Corticosteroid
I don't understand this part: timeout="#{session.maxInactiveInterval * 1000 - 125000}" . Could you explain it in more detail please?Foreglimpse
Make sure to use multiWindowSupport="true" though.Constabulary
S
15

Web sockets can be used for this. Since JSF 2.3, the <f:websocket> tag is available for this functionality. When you set scope="session", then the <f:websocket> is automatically closed with close code 1000 ("normal closure") when the session has expired in server side.

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?

Slush answered 21/11, 2017 at 8:45 Comment(4)
I got same idea, i used web socket to detect session expiration, bellow an example <f:websocket channel="notify" scope="session" onopen="console.log('Started !')" onclose="sessionScopedSocketCloseListener"/> but got an issue, I have infinite loop of multiple session initiation from client side, is this a know issue ?Erudite
I tried this solution (with session-timeout 1) with the "definitive guide to jsf" example : github.com/Apress/definitive-guide-to-jsf-javaee8. After logging and waiting, nothing pops on the screen, neither in the log (with console.log("Session has expired!") after the destruction of the bean "ActiveUser". Tested with wildfly 25.0.1.FinalPotentate
I'm afraid that with f:websocket, the session never times out.Potentate
You can try it (wildfly 25.0.1) in this fork of the "definitive guide to jsf" : github.com/ritonglue/definitive-guide-to-jsf-javaee8/tree/…. session-timeout is set to 1 but the session never expires and you never get the alert.Potentate
N
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!

Nate answered 9/12, 2016 at 14:48 Comment(0)
D
3

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.

Disparagement answered 18/10, 2019 at 16:33 Comment(1)
4 times the same answer?Ava

© 2022 - 2024 — McMap. All rights reserved.