entre Desarrolladores

Recibe ayuda de expertos

Registrate y pregunta

Es gratis y fácil

Recibe respuestas

Respuestas, votos y comentarios

Vota y selecciona respuestas

Recibe puntos, vota y da la solución

Pregunta

2votos

Gestionar timeout en múltiples pestañas

Buenas tardes ante todo; feliz año que desde el año pasado no escribo por aquí.

Os voy a exponer la situación a ver qué solución me podéis proponer si tenéis conocimiento de este tema;

Tenemos un sistema donde un usuario hace Login y empieza a trabajar, ese sistema tiene un sistema de cuenta atrás que se activa si el usuario está inactivo (es decir, se resetea cuando escribe, mueve el ratón o hace scroll) y desconecta al usuario del sistema cuando la cuenta atrás llega a cero. En base a esto nos estamos encontrando con el siguiente problema; los usuarios abren múltiples pestañas del sistema entonces cuando cualquiera de esas pestañas llega a cero dropea al usuario del sistema. Lo que queremos es que sólo dropee al usuario si la ventana activa llegara a cero, no cualquiera.

Se os ocurre algo?

Os pego el código actual para el timeout para si queréis lo tengáis como referencia:

<script type="text/javascript">

var interval;

var start_minutes = 29;
var minutes = 29;
var start_seconds = 59;

$( document ).ready(function() {

    console.log( "ready!" );

    //countdown('countdown');
    interval = setInterval('setTimer()', 1000);

    $('*').keypress(function() {

      //console.log( "Handler for .keypress() called." );
      ResetCountdown('countdown');
      //$.get('resettimer.php');

    });

    $('*').mousemove(function() {

      console.log( "Handler for .mousemove() called." );
      ResetCountdown('countdown');

    });
    $('*').onscroll(function() {

      console.log( "Handler for .onscroll() called." );
      ResetCountdown('countdown');

    });

});
</script>

Y aquí el código .js con las funciones:

function setTimer()
{
    console.log('timer set');
        var el = document.getElementById('countdown');             
        if(seconds == 0) 
        {                 
            if(minutes == 0)
            {
                el.innerHTML = "EXPIRED";                                        
                clearInterval(interval); 
                //alert('Your Session has expired, Please relogin again');
                window.location = '../control.php?logout=true';
                return;                 
            } 
            else 
            {                     
                minutes--;                     
                seconds = 60;                 
            }             
        }             
        if(minutes > 0) 
        {                
            var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute');
        } 
        else 
        {                
            var minute_text = '';             
        }             
        var second_text = seconds > 1 ? 'seconds' : 'second';
        el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + '';            
        seconds--;         

}

function ResetCountdown() 
{   
    clearInterval(interval);

    seconds = start_seconds;
    minutes = start_minutes;

    interval = setInterval('setTimer()', 1000);
    console.log('timer stopped');

}

Un saludo

2 Respuestas

3votos

Leonardo-Tadei Puntos227320

Hola @ankeorum,

tu problema es insalvable con ese código (o tal vez sea salvable, pero no vale la pena) porque estás delegando al cliente la desición de si se cierra o no la sesión. Esto, además del problema de las múltiples pestañas, tiene el problema de que basta que alguien con un navegador moderno altere la línea window.location = '../control.php?logout=true'; para que el cliente jamás se desconecte...

Lo que deberías hacer es mantener en el servidor el tiempo de vida de la sesión y que el cliente mande una señal de "stay alive" en los eventos de usuario. Podés manejar esta información en una sesión de PHP, ya que múltiples pestañas comparten la sesión.

Al recibir el "stay alive" el servidor analiza si la sesión debería seguir activa o no. Si debe seguir activa, respondés un Ok; si debe expirar, respondés al cliente con un error y hacés el proceso de logout.

De esta manera, si el cliente recibe un Ok no hace nada, y si recibe un error va a la pantalla de login directamente, porque la sesión ya está muerta del lado del servidor.

Fijate que así si alguien modifica el JavaScript, se pierde ir al inicio de sesión, pero su siguinente click lo lleva a una página que lo rechazará porque ya está deslogueado.

Se entiende la idea?

Para implementarla en tu código, basta que en los eventos keypress, mousemove y onscroll llames al servidor vía Ajax para que se refresque la sesión. Si la respuesta no es Ok, hacés un window.location = 'index.php''; para mandarlo al login, pero ya la sesión está cerrada.

Esto te evita además todo el código JavaScript del control del tiempo...

Salduos cordiales!

0voto

ankeorum comentado

Entiendo tu respuesta pero no tengo ni idea de como se implementa, me echas un cable?

0voto

Leonardo-Tadei comentado

Claro que sí! Pasá el código de control.php?logout=true (solo la parte que se ejecuta al recibir logout=true) y vemos de adaptarlo.

En el peor de los casos, si la cosa se pone larga, pasamos a una neuva pregunta.

0voto

ankeorum comentado

if (isset($_REQUEST['logout']))
    {
        unset($_SESSION['username']);
        setcookie ("demo_email", "", time() - 3600);
        header ("location: index.php");
    }

Eso es el código escrito para cuando se llama a control.php?logout=true.

0voto

Leonardo-Tadei comentado

Podrías hacer que vía AJAX se llame en los eventos a '../control.php?alive

el código en control.php para procesar esto sería:

if (isset($_REQUEST['alive']))  {
   $max_timeout = 60; // 60 segundos
   $ahora = date("U"); // la hora en segundos
   $valor = $_SESSION['ahora']; // la hora en segundos del último llamado
   if($valor > $ahora - $max_timeout){
     // Si está expirado, lo echa
     unset($_SESSION['username']);
     unset($_SESSION['ahora']);
     setcookie ("demo_email", "", time() - 3600);
     print('Loguot');
   } else {
      // Si está vigente, le renueva la hora
     $_SESSION['ahora'] = $ahora;
     print('Ok');
  }
}

Por claridad del código, estoy usando variables demás...

Tenés que agregar el el proceso de inicio de sesión el valor del tiempo actual de la sesión para la priemra vez:

...
$_SESSION['ahora'] = date('U');
...

Por último, en el código Ajax que llama al control con el mensaje 'alive', tenés que analizar la respuesta. Si responde Ok no hacés nada (o lo usás para reinicializar el tiempo de la cuenta regresiva) y si la respuesta es Logout le hacés el window.location = 'index.php';

Con todo esto conseguís que sea el servidor el que controle el tiempo de la sesión, en vez de poner eso en el cliente, en dónde puede ser fácilmente burlado o desactivado.

Saludos cordiales!

0voto

ankeorum comentado

No sé si lo entiendo bien @Leonardo-Tadei; ese código es obvio que hay que meterlo en control.php pero desde donde haces las request de alive? Desde php o desde javascript? Supongo que javascript porque obviamente del lado cliente php no tiene sentido...

Aparte de eso, $_SESSION['ahora'] debe ser definida cuando se defina $_SESSION['username'] no?

0voto

Leonardo-Tadei comentado

Como te digo al principio, el request con el "alive" lo tenés que hacer en JavaScript y dispararlo en los eventos keypress, mousemove y onscroll.

De esta forma, cada vea que el usurio interacciona con la página, está enviando un "alive"

0voto

ankeorum comentado

Relacionado con esto, como haría para mostrar el contador de tiempo antes de la expiración en todas las páginas?

De la forma anterior, hay un archivo .js que se incluye en todas las páginas que es lo que va haciendo la cuenta regresiva, pero habiendo desechado la forma anterior por sus problemas... con esta nueva forma como lo haría?

0voto

Leonardo-Tadei comentado

Si lo querés mostrar como antes, es decir que cada contador tenga su ritmo, podés poner una cuenta atrás en 60 cada vez que se recibe el Ok al mensaje alive.

Casi no se notaría la desincronía porque no se verían dos pestañas a la vez.

Si querés que se vea sincronizado, tendrías que implementar websocket o alguna tecnología push para que el servidor le avise a todas las instancias del nuevo tiempo. En este caso no parece que valga la pena respecto a la solución que ya usabas, porque no se ven varias pestañas a la vez...

2votos

white Puntos75880

Que tal si usas localStorage? de esta forma una "variable" podria estar en todas las pestañas.

<script type="text/javascript">

    var interval, tab_actived = false, expired = false;

    if(localStorage.getItem('time') === null)
        localStorage.setItem("time", Math.floor(Date.now() / 1000));

    if(localStorage.getItem('activate') === null)
        localStorage.setItem("activate", 1);

    $(window).focus(function() {
        tab_actived = true;
    });

    $(window).blur(function() {
        tab_actived = false
    });

    $( document ).ready(function() {

        console.log( "ready!" );

        //countdown('countdown');
        interval = setInterval('setTimer()', 1000);

        $('*').keypress(function() {

          //console.log( "Handler for .keypress() called." );
          ResetCountdown('countdown');
          //$.get('resettimer.php');

        });

        $('*').mousemove(function() {

          //console.log( "Handler for .mousemove() called." );
          ResetCountdown('countdown');

        });

        $('*').onscroll(function() {

          //console.log( "Handler for .onscroll() called." );
          ResetCountdown('countdown');

        });

    });

    function setTimer()
    {
        if(expired)
            return false;

        var now = Math.floor(Date.now() / 1000),
            time = parseInt(localStorage.getItem('time')),
            elapsed = (now - time);

        if(elapsed >= (60 * 30)) // mayor o igual a 30 minutos
        {
            if(tab_actived){
                console.log('tiempo expirado!', 'logout!');
                expired = true;
            }
            else{
                console.log('usuario no presente');
            }
        }

        var minutes = 30 - Math.floor((elapsed % (60 * 60)) / 60),
            seconds = 60 - Math.floor((elapsed % 60) / 1);

        var min_tag = $('<span />').html(minutes).addClass('minutes'),
            sec_tag = $('<span />').html(seconds).addClass('seconds');

        if($('#countdown').children().length == 0)
            $('#countdown').append(min_tag).append('<span />').append(sec_tag);

        $('#countdown span').each(function(){
            $(this).html( 
                $(this).hasClass('minutes') ? minutes : ($(this).hasClass('seconds') ? seconds : ':')
            );
        });
    }

    function ResetCountdown() 
    {   
        clearInterval(interval);

        localStorage.setItem("time", Math.floor(Date.now() / 1000));

        interval = setInterval('setTimer()', 1000);

        console.log('timer stopped');

    }
</script>

Edit: tambien existen algunas librerias para este fin, como ejemplo esta:
http://jillelaine.github.io/jquery-idleTimeout/

Por favor, accede o regístrate para responder a esta pregunta.

Otras Preguntas y Respuestas


...

Bienvenido a entre Desarrolladores, donde puedes realizar preguntas y recibir respuestas de otros miembros de la comunidad.

Conecta