Tuesday 8 May 2012

Extending Liferay's 6.0 session by AJAX call

Hello,
        In case if you are using portlet which has only AJAX call to serverside you have problem with session expiring. See how it can be avoided in this post.

        For extending session Liferay uses session.js script which attached to each page you are opening. This js includes to session_timeout.jspf:

 
  Liferay.Session.init(
   {
    autoExtend: <%= PropsValues.SESSION_TIMEOUT_AUTO_EXTEND %>,
    timeout: <%= sessionTimeout %>,
    timeoutWarning: <%= sessionTimeoutWarning %>,
    redirectOnExpire: <%= PropsValues.SESSION_TIMEOUT_REDIRECT_ON_EXPIRE %>
   }
  );
 
       session_timeout.jspf includes to bottom.jsp which includes to init.vm, so session object present at every page.
       Source code of it you can find in /portal/portal-web/docroot/html/js/liferay/session.js.

        How it works, each time user accessing page session oject created and initialized with method setCookie, which stores  current time value to cookie:

 setCookie: function(status) {
  var instance = this;

  var currentTime = new Date().getTime();

  var options = {
   secure: A.UA.secure
  };

  A.Cookie.set(instance._cookieKey, status || currentTime, options);
 }

      Method checkState checks curent time with stored to cookie session and expires session:
       checkState: function() {
  var instance = this;

  var currentTime = new Date().getTime();
  var sessionState = instance.getCookie();
  var newWaitTime = instance.sessionTimeoutWarning;
  var timeDiff = 0;

  clearTimeout(instance._stateCheck);

  if (sessionState == 'expired') {
   instance.expire();
  }
  else {
   timeDiff = currentTime - sessionState;

   if (!instance.autoExtend) {
    if ((timeDiff + 100) >= instance.sessionTimeoutWarning) {
      instance.warn();
    }
    else {
     newWaitTime = (instance.sessionTimeoutWarning - timeDiff) + 10000;

     instance._stateCheck = setTimeout(
      function() {
       instance.checkState();
      },
     newWaitTime);
    }
   }
   else {
    instance.extend();
   }
  }
 }

          Session can be extend with method extend(), this method also makes AJAX call to server side in order to extend server session (A.io.request(instance._sessionUrls.extend)):
 extend: function() {
  var instance = this;

  if (instance._countdownTimer) {
   clearInterval(instance._countdownTimer);
  }

  A.io.request(instance._sessionUrls.extend);

  document.title = instance._originalTitle;

  instance._currentTime = instance.sessionTimeoutWarning;

  clearTimeout(instance._sessionExpired);

  if (instance._sessionWarning) {
   clearTimeout(instance._sessionWarning);
  }

  instance._sessionWarning = setTimeout(
   function() {
    if (!instance.autoExtend) {
     instance.warn();
    }
    else {
     instance.extend();
    }
   },
   instance._timeoutDiff
  );

  instance.setCookie();
 }

          So extending session with default config can be done only if user reloads page, to extend session with AJAX calls you sholud do next:
      1) create base.js in ext-plugin and include it javascript.everything.files in portal-ext.properties
 
      2) add to base.js : 
   AUI().use('event', function(A){
    A.on('io:start', function(transactionid, arguments){
     if (Liferay.Session._cookieKey != undefined && Liferay.Session._cookieKey != null) {
      if (arguments != 'sessionExtend') {
       Liferay.Session.extendExternal();
      }
     }
    });
   });

            io:start is global AUI event which occurs each time when AJAX call started, so on this event we can call extendSession method.

      3) override session.js in ext-plugin and add/replace next methods:    
   extend: function() {
    var instance = this;

    if (instance._countdownTimer) {
     clearInterval(instance._countdownTimer);
    }
    
    // added in order to differentiate session extend ajax calls and other ajax call, to avoid infinit loop 
    A.io.request(instance._sessionUrls.extend, {
      arguments: 'sessionExtend'
    });

    document.title = instance._originalTitle;

    instance._currentTime = instance.sessionTimeoutWarning;

    clearTimeout(instance._sessionExpired);

    if (instance._sessionWarning) {
     clearTimeout(instance._sessionWarning);
    }

    instance._sessionWarning = setTimeout(
     function() {
      if (!instance.autoExtend) {
       instance.warn();
      }
      else {
       instance.extend();
      }
     },
     instance._timeoutDiff
    );

    instance.setCookie();
   },
   
   extendExternal: function() {
    var instance = this;
    
    if (instance != undefined) {
     instance.extend();
    }
   },

   setCookie: function(status) {
    var instance = this;

    var currentTime = new Date().getTime();

    var options = {
     secure: A.UA.secure,
     path: "/"
    };

    A.Cookie.set(instance._cookieKey, status || currentTime, options);
   }
  
      That's it, after this changes each ajax call will extend session.

BR,
Paul

4 comments:

  1. How to solve the problem in the case of multiple browser tabs. If you are working in one of the tabs, the tab will work in an inactive timeout and the session is closed. As a result, the active tab the same work will be stopped. Any ideas?

    ReplyDelete
  2. Hi,
    You can define is tab active or not (http://stackoverflow.com/questions/1760250/how-to-tell-if-browser-tab-is-active) and accoridng it stop expiring session, but in this case session will be expired only if user on active tab.

    Or second way add to cookies new property lastActionTime and check it in method checkState before instance.warn();

    ReplyDelete
  3. Hello
    I would love to use your solution but I have some doubts :
    1) In my project I have lots of portlets and one ext plugin. Can I use it and add base.js and session.js to it ?
    2) How would I add base.js to javascript.everything.files ? Does the path should look like : [portlet-name]/base.js ?
    3. When overriding session.sj I understand I have to create sesion.js file in my ext plugin, then just add those 3 methods (extend, extendExternal and setCookie) or do I have to copy original session.js and replace those 3 methods ?
    Thanks in advance, it's really nice of you to share with all the world your solution!

    ReplyDelete
  4. I know this site gives quality dependent articles or reviews and extra material, is there any other web page which provides these kinds of information in quality?

    ReplyDelete