Ignorant till Proven Guilty

Ramblings of a computer programmer, musician and martial artist.
Apr
12

Client Side Javascript Session Timeout.

by Scott Kelley | Tags:

Recently, I was tasked with implementing a client side javascript based timeout routine.  By the time I was done, it worked beautifully until we started up multiple browsers on the same site.  If one browser was in the background, it was oblivious to the activity in the other browser window.  This can be accomplished by the famed Ctrl-N.  Browser copy 1 would count down, display the warning message and then log everyone out which would be quite a shock to the user who was in the other browser at the time.  This is because the timeout hinged on a javascript variable, which is not shared between browser sessions.  So, in order to fix this, I went the cookie route.  This post is to detail how I solved this problem.  The only code presented here is the code to implement and check the cookie and some basic UI.

First, I will deal with the cookies.  I created (ok, borrowed) some code for the cookies.

        // Generic function to set a cookie with an expiration date.
        function setCookieWithExpire(cname, cvalue, exdays)
        {
            var d = new Date();
            d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
            var expires = "expires=" + d.toUTCString();
            document.cookie = cname + "=" + cvalue + "; " + expires + "; path=/";
        }

        // Generic function to set a session cookie
        function setCookie(cname, cvalue)
        {
            document.cookie = cname + "=" + cvalue;
        }

        // Generic funtion to get a cookie with a particular name.
        function getCookie(cname)
        {
            var name = cname + "=";
            var ca = document.cookie.split(';');
            for (var i = 0; i < ca.length; i++)
            {
                var c = ca[i];
                while (c.charAt(0) == ' ') c = c.substring(1);
                if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
            }
            return "";
        }

This code helps me work with the cookies.  For the sake of the article, I use a JQuery UI Dialog to popup my timeout warning message.  Here is the basics of the UI.


<input id="txtSecondsLeft" type="text" value="Here Here" style="width: 300px;" />
    <br />
    <input id="reset" type="button" value="Press to reset timer" />

    <div id="dialog-confirm" title="About to time out?">
        <p>
            <span class='ui-icon ui-icon-alert' 
style='float: left; margin: 0 7px 20px 0;'></span> <span id='TimeoutMessage' style="font-size: small;">
Press OK to not be logged out.</span> </p> </div>

The text box "secondsLeft" is used for debugging to see the status of our timer at any given point.  The button reset again, helps us test our code.  The idea behind the reset is that we want to reset the timer any time the user interacts with the page.  Again, I am using JQuery here so let's look at how the page is setup.

        // When the page loads
        $(document).ready(function ()
        {
            // Here are all the activities that can reset the timer.
            $("input").keydown(RenewSession);
            $("textarea").keydown(RenewSession);
            $("a").mousemove(RenewSession);
            $("input").mousemove(RenewSession);
            $("textarea").mousemove(RenewSession);

            // Create the confirmation dialog window.
            renewalVars.TimeoutDialog =
            $("#dialog-confirm").dialog({
                autoOpen: false,
                resizable: false,
                height: 250,
                width: 350,
                modal: true,
                buttons: {
                    "Ok": function ()
                    {
                        RenewSession();
                        $(this).dialog("close");
                    }
                }
            });
            // Setup the timeout session to begin with.
            RenewSession();
            // Set the timer in motion to do the checking.
            renewalVars.intervalID = setInterval('CheckSession()', 1000);
        });

When the document loads, we will setup each of the input devices to renew the session whenever they receive input or even a mouse move event.  This let's our routine know that the user is alive and well.  You can tailor this to suit your needs.  The next thing we do is create the JQuery UI dialog.  Note the close button on the dialog also Renews the session.  Finally we call renew session to set the point from which the ticker can operate and then start up the ticker to 'CheckSession' every second or so.  Again, you can tailor this for your needs.  Now, let's look at the CheckSession and RenewSession functions.

// This function sets the renewal cookie to the current time, 
//as a result, any window basing their
// timeout on this cookie should also get reset.
function RenewSession()
{
    // This will record the getTime value of the date when the 
    // function is invoked.  Draws a line in the sand.
    setCookieWithExpire(renewalVars.secondsLeftCookieName,
        (new Date()).getTime() / 1000, 1);
}

// This function is what fires every so often to check the status of the timeout,
// display the window if necessary and if no action is taken, transfer the user to
// the logout screen.
function CheckSession()
{
    // This will get the elapsed seconds since we "drew the line in the sand" 
    // and then subtract it from the timeout value to get the seconds remaining.
    var savedSeconds = getCookie(renewalVars.secondsLeftCookieName);
    var elapsedSeconds = ((new Date()).getTime() / 1000) - savedSeconds;
    var secondsLeft = renewalVars.timeoutValue - elapsedSeconds;

    $("#TimeoutMessage").html('You Currently Have ' + secondsLeft.toFixed(0)
        + ' second(s) left.<br/>Press OK to not be logged out.');
    $("#txtSecondsLeft").val(secondsLeft.toFixed(0));

    // This is the case where we are less than the warning but haven't shown the box.
    if (secondsLeft <= renewalVars.warningValue && !renewalVars.confirmDialogShowing)
        ConfirmSessionTimeout();

    // This is the case where we are showing the box, but our timeout 
    // is reset, perhaps by another browser instance. (edge case)
    if (secondsLeft > renewalVars.warningValue && renewalVars.confirmDialogShowing)
    {
        RenewSession();
        renewalVars.confirmDialogShowing = false;
        renewalVars.TimeoutDialog.dialog("close");
    }

    // Now, if nothing was done and the seconds left go to 0, then we 
    // log the user out (which effectively logs all sessions out)
    if (secondsLeft <= 0)
    {
        // This is to prevent the countdown from continuing if the 
        // browser is a bit slow to respond.
        clearInterval(renewalVars.intervalID); 
        window.location.href = "http://www.google.com";
    }
}

The RenewSession simply takes the current date and stores it in the cookie.  Whenever the timer fires, the CheckSession will do the following.

  1. go get the last cookie saved.  This can be by this browser or another browser session.
  2. Determine how many seconds have elapsed.
  3. Determine how many seconds we have left.
  4. Update message
  5. Next bit will check to see if we are below our threshold and the dialog has not been shown, then show the dialog.
  6. If we are above our threshold and the dialog is shown, hide the dialog.  This is the case when another browser session renews.
  7. Finally, if our timer has fallen to zero or below, we can stop the timer and then invoke whatever function you wish.  In this case, I have simply transferred to Google.  You can invoke your logout controller/action or whatever you wish to do.

We only have a little bit more to wrap up so let's show that code.

// This structure will hold some of the data needed to manage the timeout.
var renewalVars =
{
    // This is the cookie that will store the exact time a refresh happened.
    secondsLeftCookieName: "Timeout.ResetSnapshot",

    // Amount of time in seconds till the timeout happens.
    timeoutValue: 12,

    // Amount in seconds when the dialog will appear.  This is 
    //   X seconds before the timer runs out.
    warningValue: 10,

    // Flag that indicates whether or not the Confirmation Dialog is showing.
    confirmDialogShowing: false,

    // Stores the interval ID so the timer can be shut off before exiting.
    intervalID: null,

    // Dialog Variable itself.
    TimeoutDialog: null
}
// This function shows a dialog that will display to the user that their 
// session is about to time out.
function ConfirmSessionTimeout()
{
    // Show the window.
    renewalVars.TimeoutDialog.dialog("open");

    // Indicate to the rest of the program that the dialog is shown.
    renewalVars.confirmDialogShowing = true;
}

The renewalVars structure is just a handy place to keep the variables that I need.  Confirm session timeout is responsible for invoking and showing the dialog and setting the confirmDialogShowing variable to true. 

If you would like to see the entire page working, you can go get it here.

Click here for working example.

In finishing, if you try and get this to work on your local development machine, it will likely not work.  The reason is, in order to share cookies, there must be at least one "." in the domain name.  For example, www.IgnorantTillProvenGuilty.com has dots in the name, localhost does not.  In order to get around this, add the following to your hosts file.

127.0.0.1 dev.localhost.com

Once you do that, then you can refer to your site with this alias and it should work.  Of course, once deployed to real domain, no worries.  This is a good Stack Overflow article with more details on this.



Mar
29

Getting around the silliness that is DateDiff in TSql

by Scott Kelley | Tags:

This post is to highlight a fairly useful script I came up with to break down differences between dates.  I was originally trying to use DATEDIFF to do my work but there is something a little quirky about DATEDIFF that made my goal tougher to reach.  In short, what I was trying to do was to get the "Quantity" of time between two dates.  For example, say I have the following two dates:

March 1, 2015 and April 3, 2015

If I were to ask SQL what the difference between these two dates were in Years, I would get a zero.

Print DateDiff(yy, 'March 1, 2015','April 3, 2015')

Results in 0.  This is totally expected.  If I were to ask for the months in between, then I get the following.

Print DateDiff(mm, 'March 1, 2015','April 3, 2015')

Results in 1.  Eureka, he cried, I have found my functions.  I can just do a little math in the background and presto, I can build a string that is much more readable.  However, during this development, I found the an interesting quirk.  Suppose I were to run this.

Print DateDiff(yy, 'December 1, 2014','April 3, 2015')

Results in 1.  Wait a friggin second.  I know that there is not 1 full year between 1/1/2014 and 4/3/2015.  That's only about 4 months.  So I did a little digging and found this.

https://msdn.microsoft.com/en-us/library/ms189794.aspx

It basically states the following.  "datepart:  Is the part of startdate and enddate that specifies the type of boundary crossed...." What the....  So according to TSQL if I ask for the difference between 12/30/2014 at 11:59 PM and 1/1/2015 at 12:01, it is going to tell me 1 Year because we happened to cross a boundary.  Correct me if I am wrong, but this is  bit misleading.  I want the Difference in the date (Hence the name DateDiff).  So back to my problem, I want to know how many years, months, days, hourse, minutes, etc...  have passed between the two dates.  If I can't use DateDiff to get the actual difference in the dates, then how do I do it?  What I came up with was to use the DateAdd function.  Here is how it works...

Suppose I have two dates and I want to know if there is a year between them.  I can Add a year to date one and check to see if has surpassed date 2.  If it has (or it is equal), then I know there is at least one year between them.  If not, then I know there is less than a year between them.  So I can create a User Function that takes two dates, and then using While loops, I can move the original date forward until the "Next" advance would move past the second date.  Once I do that, I switch to the next unit of time.  I keep going until I have reached the precision I want.  I can then format the output any way I like and there you have it, a more readable listing of the time passed between two dates, regardless of boundaries.  So without further delay, here is the function I wrote.

ALTER Function [dbo].[udfExactDateDiffInString](
    @Date1 datetime,
    @Date2 datetime
)
Returns varchar(255)
As
Begin
Declare @return varchar(255)
/*
    The nature of this function is to return a descriptive string 
    around the difference of two dates.  We can always do a date
    diff to get one measurement of time (days, years, minutes, seconds, etc...)
    What this will do is to give us a string that looks like this.
    
    2 Years 3 Months 5 Days

    This is accomplished by breaking down the time involved using the
    normal date Add functions.
*/

Set @Return = '' -- Avoid conflict with null concat

Declare @Years int
Declare @Months int
Declare @Days int
Declare @Hours int
Declare @minutes int
Declare @Seconds int

-- Intitialize all the values.
Select @Years = 0, @Months = 0, @Days = 0, 
@Hours = 0, @Minutes = 0, @Seconds = 0 While DateAdd(yy, 1, @Date1) < @Date2
Select @Years = @Years + 1, @Date1 = DateAdd(yy, 1, @Date1) While DateAdd(mm, 1, @Date1) < @Date2
   Select @Months = @Months + 1, @Date1 = DateAdd(mm, 1, @Date1) While DateAdd(dd, 1, @Date1) < @Date2
   Select @Days = @Days + 1, @Date1 = DateAdd(dd, 1, @Date1) While DateAdd(hh, 1, @Date1) < @Date2
   Select @Hours = @Hours + 1, @Date1 = DateAdd(hh, 1, @Date1) While DateAdd(mi, 1, @Date1) < @Date2
   Select @Minutes = @Minutes + 1, @Date1 = DateAdd(mi, 1, @Date1) While DateAdd(ss, 1, @Date1) < @Date2
   Select @Seconds = @Seconds + 1, @Date1 = DateAdd(ss, 1, @Date1) -- Now that we have our numbers, let's format them. To make it readable -- we will need to take into account plural forms. Set @return = @return + Case When @Years = 1 Then Convert(varchar,@Years) + ' year ' When @Years > 1 Then Convert(varchar,@Years) + ' years ' When @Years = 0 Then '' End Set @return = @return + Case When @Months = 1 Then Convert(varchar,@Months) + ' month ' When @Months > 1 Then Convert(varchar,@Months) + ' months ' When @Months = 0 Then '' End Set @return = @return + Case When @Days = 1 Then Convert(varchar,@Days) + ' day ' When @Days > 1 Then Convert(varchar,@Days) + ' days ' When @Days = 0 Then '' End Set @return = @return + Case When @Hours = 1 Then Convert(varchar,@Hours) + ' hour ' When @Hours > 1 Then Convert(varchar,@Hours) + ' hours ' When @Hours = 0 Then '' End Set @return = @return + Case When @Minutes = 1 Then Convert(varchar,@Minutes) + ' minute ' When @Minutes > 1 Then Convert(varchar,@Minutes) + ' minutes ' When @Minutes = 0 Then '' End Set @return = @return + Case When @Seconds = 1 Then Convert(varchar,@Seconds) + ' second ' When @Seconds > 1 Then Convert(varchar,@Seconds) + ' seconds ' When @Seconds = 0 Then '' End Return @return End




Mar
27

Simple timing class for .net

by Scott Kelley | Tags:

Have you ever wanted to know how much time a process takes in your code?  I was recently working on a project where I wanted to compare different techniques of file access and wast trying to remove bottlenecks in the process.  Now of course you have to be very careful with any type of observer in your code as the observer can have adverse effects and wind up reporting back inaccurate information.  As I was just trying to get a relative feel for how long things were taking, I didn't worry too much.  Now on with the class.

What I decided to do was to create a timing class which would initialize on creation and record the system time.  The idea is that I want to profile a particular portion of code, then log the results.  Here is the class and we can talk about each part and why.

public class ProcessTimer : IDisposable
{
    public string Name { get; set; }
    public Guid Id { get; set; }

    private DateTime _startTime;
    private static readonly ILog log = LogManager.GetLogger(typeof(ProcessTimer));

    public ProcessTimer(string name)
    {
        Name = name;
        Id = Guid.NewGuid();
        _startTime = DateTime.Now;
    }

    public void Dispose()
    {
        // Here, we are done, let's get the total time string.
        var _ts = DateTime.Now - _startTime;

        double leftoverms = _ts.TotalMilliseconds;
        int hours = (int)(leftoverms/(360000));
        leftoverms -= hours * 360000;
        int mins = (int)(leftoverms / (60000));
        leftoverms -= mins * 60000;
        int secs = (int)(leftoverms / (1000));
        leftoverms -= secs * 1000;
        int msecs = (int)leftoverms;
        log.Info(Name + " : " + hours + " Hrs, " + mins + " Mins, " + secs + " Secs, " + msecs + " Msecs.");
    }
}

At the top of the class, you have two methods for identifying the instance.  You can set the name of the instance so that when the results are logged, you will know what it is you are profiling.  I have also included an optional Guid for further identification but haven't used it in this instance.  Moving on, you have two private fields called _startTime and log.  _startTime records when the class is instantiated and the log is the instance of log4net we are using.  Of course, you can change the code to use your own logging mechanism.  Taking a look at the constructor, we see that it sets the name of the instance, generates a new Guid and then captures the system date and time through DateTime.Now.  So far, we have setup the class, now let's get into the meat of it which happens in the Dispose method.  You may have noticed that the ProcessTimer class implements the IDisposable interface.  This allows us to participate in a using block and have the Dispose method called upon leaving the using block.  I will demonstrate this later.  The first thing we do in the Dispose method is to capture a timespan between the current datetime and the datetime that we captured earlier.  I could use the TimeSpan.TotalMilliseconds method, log that and be done but I wanted to get a little more fancy.  The math that follows breaks the millisecond count up into Hours, Minutes, Seconds and Milliseconds.  For Example, if the result is 23,568 milliseconds, wouldn't it be nicer if it read 23 secs, 568 MSecs.  That is not that big of an issue but what happens when we get into longer processes that take perhaps minutes or even hours.  For example, how long is 4,567,213 milliseconds.  It is not obvious right at first but when you break it down, you get   12 Hours, 4 Minutes, 7 seconds and 213 milliseconds.  That to me seems more useful.  So the way that this is calculated is to make a determination as to first how many hours are represented by the milliseconds by doing integer division by 360,000.  Take the number of hours, * 360,000 and subtract that from the original milliseconds.  YOu can then do the same with minutes (using 60000) and then seconds (using 1000) and you should get a nice breakdown.  Step through the code to see this in action.  Now, on to how you would use this in code.  It's very simple.

using (var m = new ProcessTimer("Testing my Functionality"))
{
    while (!done)
    {
	done = myObject.Do();
    }
}

As you can see, anything that needs to be timed, can simply be wrapped with a using statement.  The constructor will get called at the top and the Dispose is called automatically when you leave the block.  Anything you want to time, just wrap it in the process timer and it will be called and logged.  Finally, let's see what the log looks like.  This is the sample line from the log4net file.

 

INFO 9 ConsoleApplication2.ProcessTimer - Testing my Functionality: 0 Hrs, 0 Mins, 18 Secs, 957 Msecs.

Please take this base and move it around to suit your needs.

 

 



About the Author

My name is Scott Kelley.  I am a software engineer in the Atlanta area.  I am also into martial arts and music.  I have been practicing karate since 1984 and aikido since 1991.  This blog is my ramblings on life, liberty and the pursuit of happyness.  Anything I say here should be verified by outside sources.  Always consider this informatoin ignorant until proven guilty.

Good Stuff

Month List

Sign in