Create session timeout warning for durandal single page application

I have a durandal/requirejs single page application. When a user sits idle, I need to display a warning to the user indicating that the session is about to time out. I have looked at several examples on the internet for asp.net apps, but can’t find any examples for a single page application.

My application is similar to John Papa’s code camper (MVC application).

How can I get a session timeout warning to the user if their session is 2 minutes away from timing out?

–EDIT In my main.js file I have-

             app.setRoot('viewmodels/shell', 'entrance');

        router.guardRoute = function (instance, instruction) {
            var sess_pollInterval = 60000;
            //How many minutes the session is valid for
            var sess_expirationMinutes = 2;
            //How many minutes before the warning prompt
            var sess_warningMinutes = 1;

            var sess_intervalID;
            var sess_lastActivity;
            initSessionMonitor();
            function initSessionMonitor() {
                sess_lastActivity = new Date();
                sessSetInterval();
                $(document).bind('keypress.session', function (ed, e) { sessKeyPressed(ed, e); });
            }
            function sessSetInterval() {
                sess_intervalID = setInterval('sessInterval()', sess_pollInterval);
            }
            function sessClearInterval() {
                clearInterval(sess_intervalID);
            }
            function sessKeyPressed(ed, e) {
                sess_lastActivity = new Date();
            }
            function sessPingServer() {
                //Call an AJAX function to keep-alive your session.
                alert('someAJAXFunction();');
            }
            function sessLogOut() {
                alert('here');
                //window.location.href = '/Account/LogOff';
            }

            function sessInterval() {
                var now = new Date();
                var diff = now - sess_lastActivity;
                var diffMins = (diff / 1000 / 60);

                if (diffMins >= sess_warningMinutes) {
                    //wran before expiring
                    //stop the timer
                    sessClearInterval();
                    //promt for attention
                    if (confirm('Your session will expire in ' + (sess_expirationMinutes - sess_warningMinutes) +
                        ' minutes (as of ' + now.toTimeString() + '), press OK to remain logged in ' +
                        'or press Cancel to log off. nIf you are logged off any changes will be lost.')) {
                        now = new Date();
                        diff = now - sess_lastActivity;
                        diffMins = (diff / 1000 / 60);

                        if (diffMins > sess_expirationMinutes) {
                            //timed out
                            sessLogOut();
                        }
                        else {
                            //reset inactivity timer
                            sessPingServer();
                            sessSetInterval();
                            sess_lastActivity = new Date();
                        }
                    } else {
                        sessLogOut();
                    }
                } else {
                    sessPingServer();
                }
            }
            return true;
        };
    }

now getting “Uncaught ReferenceError: sessInterval is not defined.” Ideas?

Answer

The approach I used was different than my post above. I used timeout-dialog.js and altered that script to use with durandal’s router and any other service I needed within my application. I also used idle js. Here is the code-

main.js in app.start()-

                 var timeout = 100;

            $(document).bind("idle.idleTimer", function () {

                controls.timeoutDialog.setupDialogTimer();

            });

            $(document).bind("active.idleTimer", function () {
                var sess = Security.GetKeepSessionAlive();

            });

            $.idleTimer(timeout);

timeout-dialog.js code-

     String.prototype.format = function () {
    var s = this,
            i = arguments.length;

    while (i--) {
        s = s.replace(new RegExp('\{' + i + '\}', 'gm'), arguments[i]);
    }
    return s;
};
define(['durandal/system', 'plugins/router', 'services/logger', 'services/SecurityDataService'],
function (system, router, logger, Security){
    timeoutDialog = {
        settings: {
            timeout: 50,
            countdown: 15,
            title: 'Your session is about to expire!',
            message: 'You will be logged out in {0} seconds.',
            question: 'Do you want to stay signed in?',
            keep_alive_button_text: 'Yes, Keep me signed in',
            sign_out_button_text: 'No, Sign me out',
            keep_alive_url: '',
            keep_alive_function: function () {

            },
            logout_url: function () {
                router.map([
       { route: 'ErrorPage', moduleId: 'ErrorPage', title: 'ErrorPage', title: 'ErrorPage', nav: false }
                ]).activate

                router.navigate('ErrorPage');
            },
            logout_redirect_url: function () {
                router.map([
       { route: 'ErrorPage', moduleId: 'ErrorPage', title: 'ErrorPage', title: 'ErrorPage', nav: false }
                ]).activate

                router.navigate('ErrorPage');
            },
            logout_function: function () {
                amplify.store("ErrorDetails", "Session Timed Out!");
                router.map([
        { route: 'ErrorPage', moduleId: 'ErrorPage', title: 'ErrorPage', title: 'ErrorPage', nav: false }
                ]).activate

                router.navigate('ErrorPage');
            },
            restart_on_yes: true,
            dialog_width: 350
        },
        alertSetTimeoutHandle: 0,
        setupDialogTimer: function (options) {
            if (options !== undefined) {
                $.extend(this.settings, options);
            }

            var self = this;

            if (self.alertSetTimeoutHandle !== 0) {
                clearTimeout(self.alertSetTimeoutHandle);
            }

            self.alertSetTimeoutHandle = window.setTimeout(function () {
                self.setupDialog();
            }, (this.settings.timeout - this.settings.countdown) * 1000);
        },
        setupDialog: function () {

            //check for other modal forms on view
            //$.element.modal('hide');
            $('.modal').modal('hide');

            var self = this;
            self.destroyDialog();

            $('<div id="timeout-dialog">' +
                    '<p id="timeout-message">' + this.settings.message.format('<span id="timeout-countdown">' + this.settings.countdown + '</span>') + '</p>' +
                    '<p id="timeout-question">' + this.settings.question + '</p>' +
                    '</div>')
                    .appendTo('body')
                    .dialog({
                        modal: true,
                        width: this.settings.dialog_width,
                        minHeight: 'auto',
                        zIndex: 10000,
                        closeOnEscape: false,
                        draggable: false,
                        resizable: false,
                        dialogClass: 'timeout-dialog',
                        title: this.settings.title,
                        buttons: {
                            'keep-alive-button': {
                                text: this.settings.keep_alive_button_text,
                                id: "timeout-keep-signin-btn",
                                click: function () {
                                    self.keepAlive();
                                }
                            },
                            'sign-out-button': {
                                text: this.settings.sign_out_button_text,
                                id: "timeout-sign-out-button",
                                click: function () {
                                    self.signOut(true);
                                }
                            }
                        }
                    });

            self.startCountdown();
        },
        destroyDialog: function () {
            if ($("#timeout-dialog").length) {
                $("#timeout-dialog").dialog("close");
                $('#timeout-dialog').remove();
            }
        },
        startCountdown: function () {
            var self = this,
                    counter = this.settings.countdown;

            this.countdown = window.setInterval(function () {
                counter -= 1;
                $("#timeout-countdown").html(counter);

                if (counter <= 0) {
                    window.clearInterval(self.countdown);
                    self.signOut(false);
                }

            }, 1000);
        },
        keepAlive: function () {
            var self = this;
            this.destroyDialog();
            window.clearInterval(this.countdown);

            this.settings.keep_alive_function();

            if (this.settings.keep_alive_url !== '') {
                $.get(this.settings.keep_alive_url, function (data) {
                    if (data === "OK") {
                        if (this.settings.restart_on_yes) {
                            self.setupDialogTimer();
                        }
                    }
                    else {
                        self.signOut(false);
                    }
                });
            }
        },
        signOut: function (is_forced) {
            var self = this;
            this.destroyDialog();

            this.settings.logout_function(is_forced);

            if (this.settings.logout_url !== null) {
                $.post(this.settings.logout_url, function (data) {
                    self.redirectLogout(is_forced);
                });
            }
            else {
                self.redirectLogout(is_forced);
            }
        },
        redirectLogout: function (is_forced) {
            var target = this.settings.logout_redirect_url + '?next=' + encodeURIComponent(window.location.pathname + window.location.search);
            if (!is_forced)
                target += '&timeout=t';
            window.location = target;
        },
    };

         var dataservice = {
            timeoutDialog: timeoutDialog
        };
        return dataservice;
     });

I put the timeout-dialog.js in my own folder under the apps folder to bring in durandal and other services i needed. The idle-timer.js was left in the scripts folder and registered via bundle.config.

Leave a Reply

Your email address will not be published. Required fields are marked *