PDA

View Full Version : How To: Timeout (Client Side) using postMessage (HTML5) with Countdown alert



equiman
Apr 23, 2012, 2:45 PM
It's my first Englis How To... I hope can help others. Sorry my English isn't very good. Corrections are welcome!

Finally I can do TimeOut at client side... you need a browser with postMessage (HTML5) compatibility.

My first implementation was create an mouse and keyboard detection. But I'm using tab panel, and any tab panel is an iFrame then my script only works in the master page, but not in any tab. Then I'm trying to catch mouse and keyboard events in any iFrame and send this to master and run the script in the master page, but this actions was blocked by browsers to prevent hacking actions.

Surfing into the web I found in HTML the postMessage that is used to send message between windows and iFrames.
Here an example how it works: http://robertnyman.com/2010/03/18/postmessage-in-html5-to-send-messages-between-windows-and-iframes/

Then now I can catch the events in any iFrame but the action is run directly in the master page.

NOTE: I'm working with MVC

1: Create a TimeOut.js into Scripts folder file with this content:



// Session Timeout client side. Master and Detail detection.
/**
* Detecting in Master and Detail, Mouse and Keyboard events.
* Using postMessage (HTML5)
* @autor Camilo Martinez [Equiman], http://gplus.to/equiman
* @created 2012-04-11
* @updated 2012-04-22
* @licence CC BY-SA http://creativecommons.org/licenses/by-sa/3.0/
*/

// Add this into Master Page (TabPanelContain):
// <DocumentReady Handler="masterTimeOut();" />
var masterTimeOut = function () {

//Convert minutes value indicated in TimeOut del Web.Config to seconds
var min = 0;
var sec = 0;
var timer = null;
var vis = false;

doTimer = function () {
// At least 1 min, show the countdown window
if ((min - sec) < 60) {

updateTime();

if (frmTimeOut.hidden) {
vis = true;
frmTimeOut.setVisible(true);
}
}
else {
if (!frmTimeOut.hidden) {
vis = false;
frmTimeOut.setVisible(false);
}
}

// Check the countdown counter
if ((min - sec) > 0) {
sec++;
// Repeat the process each minute
timer = setTimeout('doTimer()', 1000);
}
else {
// When countdown is finish, redirect to Login page
top.location.replace(GetNewPath('/Home/Login/'));
}
};

stopCount = function () {
if (vis === true) {
//2 seconds Idle (no detect events, because showing alert window is detected as mousemove)
if ((min - sec) < 58) {
vis = false;
}
}
else {
// Convert minutes to second, minus 10 to be sure Client TimeOut occurs first than Server Timeout
min = (document.getElementById('txtMinTimeOut').value * 60) - 10;
sec = 0;
clearTimeout(timer);
doTimer();
}
};

updateTime = function () {
var message = 'Inactivity was detected. Your session will expire in';
var time = (min - sec);
var unity = 'seconds.';

Ext.getCmp('lblText').setText(message + ' ' + time + ' ' + unity);
};

// Start counter on Load
document.onload = function () {
stopCount();
return false;
};
// Star counter when mouse move
document.onmousemove = function () {
stopCount();
return false;
};
// Star counter when key is pressed
document.onkeypress = function () {
stopCount();
return false;
};

// Read and event when is send from an iFrame
function displayMessage(e) {
if ((e.origin.split(":", 2)[0] + ":" + e.origin.split(":", 2)[1]) === (GetNewPath("/").split(":", 2)[0] + ":" + GetNewPath("/").split(":", 2)[1])) {
// If the iFrame send any of this events start counter
switch (e.data) {
case "onload":
case "onmousemove":
case "onkeypress":
stopCount();
}
}
};

if (window.addEventListener) {
// For standards-compliant web browsers
window.addEventListener("message", displayMessage, false);
}
else {
window.attachEvent("onmessage", displayMessage);
};
};


// Add this into any iFrame Page:
// <DocumentReady Handler="detailTimeOut();" />
var detailTimeOut = function () {
// Start counter on Load
document.onload = function () {
top.postMessage("onload", GetNewPath("/"));
return false;
};
// Star counter when mouse move
document.onmousemove = function () {
top.postMessage("onmousemove", GetNewPath("/"));
return false;
};
// Star counter when key is pressed
document.onkeypress = function () {
top.postMessage("onkeypress", GetNewPath("/"));
return false;
};
};


// Get the Path when use VirtualPath in .Net or IIS
var GetNewPath = function (relative_path) {
var url = window.location.href;

if (url.substring(url.length - 1, url.length) == '/') {
url = url.substring(0, url.length - 1);
}

var url_parts = url.split('/');

if (relative_path.substring(0, 1) != '/') {
url_parts[url_parts.length - 2] = relative_path;
url_parts[url_parts.length - 1] = '';
}
else {
url_parts[url_parts.length - 2] = relative_path.substring(1);
url_parts[url_parts.length - 1] = '';
}

var new_page_absolute_path = url_parts.join('/');

if (new_page_absolute_path.substring(new_page_absolut e_path.length - 1, new_page_absolute_path.length) == '/') {
new_page_absolute_path = new_page_absolute_path.substring(0, new_page_absolute_path.length - 1);
}

return new_page_absolute_path;
};


2: Add the timeout to Web.Config (in minutes). Into <system.web> add:

<sessionState timeout="5"></sessionState>

3: Read into master page the TimeOutValue.

Add Namespace System.Web

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<%@ Import Namespace="System.Web" %>

Into <head> tag read the TimeOut time and send to Hidden TextField:


<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack && !X.IsAjaxRequest)
{
txtMinTimeOut.Value = Session.Timeout;
}
}
</script>

Into <body><form> tags tead the TimeOut.js, when Document Ready load the masterTimeout() and create the Hidden Text Field:


<asp:ScriptManager ID="smmaster" EnableScriptLocalization="true" runat="server">
<Scripts>
<asp:ScriptReference Path="~/Scripts/TimeOut.js" />
</Scripts>
</asp:ScriptManager>
<ext:ResourceManager ID="ResourceManagerMaster2" runat="server">
<Listeners>
<DocumentReady Handler="masterTimeOut();" />
</Listeners>
</ext:ResourceManager>

<ext:Hidden ID="txtMinTimeOut" runat="server" />


Create a Window to show the countdown alert, before </form> tag:

<ext:Window
ID="frmTimeOut"
runat="server"
Width="235"
Height="50"
Modal="false"
Resizable="False"
Hidden="true"
Closable="false"
Layout="FormLayout"
Draggable="false"
Padding="12">
<Items>
<ext:Label ID="lblText" runat="server" HideLabel="true" Text="" AutoWidth="true" />
</Items>
</ext:Window>

4: In any iFrame (page loaded into Iframe or Tab Panel) add Into <body><form> tags tead the TimeOut.js, when Document Ready load the detailTimeout():



<asp:ScriptManager ID="smDetail" EnableScriptLocalization="true" runat="server">
<Scripts>
<asp:ScriptReference Path="~/Scripts/TimeOut.js" />
</Scripts>
</asp:ScriptManager>
<ext:ResourceManager ID="ResourceManager1" runat="server">
<Listeners>
<DocumentReady Handler="detailTimeOut();" />
</Listeners>
</ext:ResourceManager>


And it's Done!!!

Andyhincapie
Apr 26, 2012, 3:00 PM
Hi... my english is not very good.... but i working in aspx and windows forms and when the redirect page for inactivity the program redirect to /Home/Login and i need to redirect to /Login.aspx

thanks for your help about this redirection...

equiman
Apr 26, 2012, 11:18 PM
OK... I think the difference is that you are working in a project without MVC.

If you use virtual path, only need to chage:


top.location.replace(GetNewPath('/Home/Login/'));

To:


top.location.replace(GetNewPath('/Login.aspx'));

If you aren't use virtual path:

use this:


top.location.replace(Login.aspx');

DP: I speak spanish too.

equiman
Apr 26, 2012, 11:24 PM
Here is a new version of TimeOt.js file:


// Session Timeout client side. Master and Detail detection.
/**
* Detecting in Master and Detail, Mouse and Keyboard events.
* Using postMessage (HTML5)
* @autor Camilo Martinez [Equiman], http://gplus.to/equiman
* @created 2012-04-11
* @updated 2012-04-26
* @licence CC BY-SA http://creativecommons.org/licenses/by-sa/3.0/
*/

// URL Redirection
var timeOutSession = function () {
top.location.replace(GetNewPath('/Home/Timeout/'));
};

// Add this into Master Page (TabPanelContain):
// <DocumentReady Handler="masterTimeOut();" />
var masterTimeOut = function () {

//Convert minutes value indicated in TimeOut del Web.Config to seconds
var min = 0;
var sec = 0;
var timer = null;
var vis = false;

doTimer = function () {
// At least 1 min, show the countdown window
if ((min - sec) < 60) {

updateTime();

if (frmTimeOut.hidden) {
vis = true;
frmTimeOut.setVisible(true);
}
}
else {
if (!frmTimeOut.hidden) {
vis = false;
frmTimeOut.setVisible(false);
}
}

// Check the countdown counter
if ((min - sec) > 0) {
sec++;
// Repeat the process each minute
timer = setTimeout('doTimer()', 1000);
}
else {
// When countdown is finish, redirect to Login page
timeOutSession();
}
};

stopCount = function () {
if (vis === true) {
//2 seconds Idle (no detect events, because showing alert window is detected as mousemove)
if ((min - sec) < 58) {
vis = false;
}
}
else {
// Convert minutes to second, minus 10 to be sure Client TimeOut occurs first than Server Timeout
min = (document.getElementById('txtMinTimeOut').value * 60) - 10;
sec = 0;
clearTimeout(timer);
doTimer();
}
};

updateTime = function () {
var message = 'Inactivity was detected. Your session will expire in';
var time = (min - sec);
var unity = 'seconds.';

Ext.getCmp('lblText').setText(message + ' ' + time + ' ' + unity);
};

// Start counter on Load
document.onload = function () {
stopCount();
return false;
};
// Star counter when mouse move
document.onmousemove = function () {
stopCount();
return false;
};
// Star counter when key is pressed
document.onkeypress = function () {
stopCount();
return false;
};

// Read and event when is send from an iFrame
function displayMessage(e) {
if ((e.origin.split(":", 2)[0] + ":" + e.origin.split(":", 2)[1]) === (GetNewPath("/").split(":", 2)[0] + ":" + GetNewPath("/").split(":", 2)[1])) {
// If the iFrame send any of this events start counter
switch (e.data) {
case "onload":
case "onmousemove":
case "onkeypress":
case "simulated":
stopCount();
}
}
};

if (window.addEventListener) {
// For standards-compliant web browsers
window.addEventListener("message", displayMessage, false);
}
else {
window.attachEvent("onmessage", displayMessage);
};
};


// Add this into any iFrame Page:
// <DocumentReady Handler="detailTimeOut();" />
var detailTimeOut = function () {
// Start counter on Load
document.onload = function () {
top.postMessage("onload", GetNewPath("/"));
return false;
};
// Star counter when mouse move
document.onmousemove = function () {
top.postMessage("onmousemove", GetNewPath("/"));
return false;
};
// Star counter when key is pressed
document.onkeypress = function () {
top.postMessage("onkeypress", GetNewPath("/"));
return false;
};
// Simulated Event
simulatedEvent = function () {
top.postMessage("simulated", GetNewPath("/"));
return false;
};
};

// Get the Path when use VirtualPath in .Net or IIS
var GetNewPath = function (relative_path) {
var url = window.location.href;

if (url.substring(url.length - 1, url.length) == '/') {
url = url.substring(0, url.length - 1);
}

var url_parts = url.split('/');

if (relative_path.substring(0, 1) != '/') {
url_parts[url_parts.length - 2] = relative_path;
url_parts[url_parts.length - 1] = '';
}
else {
url_parts[url_parts.length - 2] = relative_path.substring(1);
url_parts[url_parts.length - 1] = '';
}

var new_page_absolute_path = url_parts.join('/');

if (new_page_absolute_path.substring(new_page_absolut e_path.length - 1, new_page_absolute_path.length) == '/') {
new_page_absolute_path = new_page_absolute_path.substring(0, new_page_absolute_path.length - 1);
}

return new_page_absolute_path;
};

I create a function separated with the redirect location (now you can use it with other events), and add a simulatedEvent to reset the timer with other event, like an automatic reload or whatever you need.

equiman
May 02, 2012, 1:20 PM
I found a bug with document.onkeypress. Here is a new version of TimeOut.js


// Session Timeout client side. Master and Detail detection.
/**
* Detecting in Master and Detail, Mouse and Keyboard events.
* Using postMessage (HTML5)
* @autor Camilo Martinez [Equiman], http://gplus.to/equiman
* @created 2012-04-11
* @updated 2012-05-02
* @licence CC BY-SA http://creativecommons.org/licenses/by-sa/3.0/
*/

// URL Redirection
var timeOutSession = function () {
top.location.replace(GetNewPath('/Home/Timeout/'));
};

// Add this into Master Page (TabPanelContain):
// <DocumentReady Handler="masterTimeOut();" />
var masterTimeOut = function () {

//Convert minutes value indicated in TimeOut del Web.Config to seconds
var min = 0;
var sec = 0;
var timer = null;
var vis = false;

doTimer = function () {
// At least 1 min, show the countdown window
if ((min - sec) < 60) {

updateTime();

if (frmTimeOut.hidden) {
vis = true;
frmTimeOut.setVisible(true);
}
}
else {
if (!frmTimeOut.hidden) {
vis = false;
frmTimeOut.setVisible(false);
}
}

// Check the countdown counter
if ((min - sec) > 0) {
sec++;
// Repeat the process each minute
timer = setTimeout('doTimer()', 1000);
}
else {
// When countdown is finish, redirect to Login page
timeOutSession();
}
};

stopCount = function () {
if (vis === true) {
//2 seconds Idle (no detect events, because showing alert window is detected as mousemove)
if ((min - sec) < 58) {
vis = false;
}
}
else {
// Convert minutes to second, minus 10 to be sure Client TimeOut occurs first than Server Timeout
min = (document.getElementById('txtMinTimeOut').value * 60) - 10;
sec = 0;
clearTimeout(timer);
doTimer();
}
};

updateTime = function () {
var message = 'Inactivity was detected. Your session will expire in';
var time = (min - sec);
var unity = 'seconds.';

Ext.getCmp('lblText').setText(message + ' ' + time + ' ' + unity);
};

// Start counter on Load
document.onload = function () {
stopCount();
//return false;
};
// Star counter when mouse move
document.onmousemove = function () {
stopCount();
//return false;
};
// Star counter when key is pressed
document.onkeypress = function () {
stopCount();
//return false;
};

// Read and event when is send from an iFrame
function displayMessage(e) {
if ((e.origin.split(":", 2)[0] + ":" + e.origin.split(":", 2)[1]) === (GetNewPath("/").split(":", 2)[0] + ":" + GetNewPath("/").split(":", 2)[1])) {
// If the iFrame send any of this events start counter
switch (e.data) {
case "onload":
case "onmousemove":
case "onkeypress":
case "simulated":
stopCount();
}
}
};

if (window.addEventListener) {
// For standards-compliant web browsers
window.addEventListener("message", displayMessage, false);
}
else {
window.attachEvent("onmessage", displayMessage);
};
};


// Add this into any iFrame Page:
// <DocumentReady Handler="detailTimeOut();" />
var detailTimeOut = function () {
// Start counter on Load
document.onload = function () {
top.postMessage("onload", GetNewPath("/"));
return false;
};
// Star counter when mouse move
document.onmousemove = function () {
top.postMessage("onmousemove", GetNewPath("/"));
//return false;
};
// Star counter when key is pressed
document.onkeypress = function () {
top.postMessage("onkeypress", GetNewPath("/"));
//return false;
};
// Simulated Event
simulatedEvent = function () {
top.postMessage("simulated", GetNewPath("/"));
//return false;
};
};

// Get the Path when use VirtualPath in .Net or IIS
var GetNewPath = function (relative_path) {
var url = window.location.href;

if (url.substring(url.length - 1, url.length) == '/') {
url = url.substring(0, url.length - 1);
}

var url_parts = url.split('/');

if (relative_path.substring(0, 1) != '/') {
url_parts[url_parts.length - 2] = relative_path;
url_parts[url_parts.length - 1] = '';
}
else {
url_parts[url_parts.length - 2] = relative_path.substring(1);
url_parts[url_parts.length - 1] = '';
}

var new_page_absolute_path = url_parts.join('/');

if (new_page_absolute_path.substring(new_page_absolut e_path.length - 1, new_page_absolute_path.length) == '/') {
new_page_absolute_path = new_page_absolute_path.substring(0, new_page_absolute_path.length - 1);
}

return new_page_absolute_path;
};

Daniil
Jun 07, 2012, 1:50 PM
Hi @equiman,

Thanks for sharing! Looks very good!

By the way, you might be interested to look at the MessageBus feature which is presented in Ext.NET v2.
http://examples2.ext.net/#/MessageBus/Basic/Simple/
http://examples2.ext.net/#/MessageBus/Basic/Complex/

equiman
Jun 13, 2012, 3:48 PM
Thanks @Daniil I see MessageBus after I finish this code. basically is the same that postMessage but without exit from Ext.

By the moment I use postMessage because I cant use Ext2 (beta) in my current projects.
I'm waiting for the release version to update this code... and use all the new Graphs.


By the way... did you know how can I reset the C# timer in server side defined in webconfig?
I likely to send a reset to this timer too.

Daniil
Jun 13, 2012, 7:20 PM
By the way... did you know how can I reset the C# timer in server side defined in webconfig?
I likely to send a reset to this timer too.

Do you mean this timeout timer?

<sessionState timeout="5"></sessionState>

equiman
Nov 16, 2012, 1:46 PM
Yes, exactly this timer.

Daniil
Nov 20, 2012, 1:32 PM
Well, I think any request to a page resets this timeout.

It is a bit unclear for me why do you need to reset it on server (I doubt it is possible). Please clarify.

equiman
Nov 20, 2012, 3:02 PM
Because... maybe you are active in the page (seeing, and movin mouse... doing thing in client side) but not in server side... the client time out don't expire, but when i do an operation server side was expired (getting an error).

Then I need control time out only with client side.... but according to sesion time out (server side) and preventing expire from server side.

I'll try sending a request to any page.

Thanks!

Daniil
Nov 21, 2012, 4:07 AM
Well, sending a request to the page periodically is a common practice to keep Session alive.

equiman
Nov 21, 2012, 7:32 PM
New version of TimeOut.js ... with Server Keep Alive prevention. Thanks to @daniil's idea ;)

I'm using some code from: http://csharpdotnetfreak.blogspot.com/2012/07/avoid-prevent-session-timeout-in-aspnet-keep-alive.html

Needs creation of: KeepAlive.aspx (View) in Home (Controller)


// Session Timeout client side. Master and Detail detection.
/**
* Detecting in Master and Detail, Mouse and Keyboard events.
* Using postMessage (HTML5)
* @autor Camilo Martinez [Equiman], http://gplus.to/equiman
* @created 2012-04-11
* @updated 2012-05-02
* @licence CC BY-SA http://creativecommons.org/licenses/by-sa/3.0/
*/

// URL Redirection
var timeOutSession = function () {
top.location.replace(GetNewPath('/Home/Timeout/'));
};

// Add this into Master Page (TabPanelContain):
// <DocumentReady Handler="masterTimeOut();stopCount();" />
var masterTimeOut = function () {

//Convert minutes value indicated in TimeOut del Web.Config to seconds
var min = 0;
var sec = 0;
var timer = null;
var vis = false;
var server = new Date().getTime() / 1000;

doTimer = function () {
// At least 1 min, show the countdown window
if ((min - sec) < 60) {

updateTime();

if (frmTimeOut.hidden) {
vis = true;
frmTimeOut.setVisible(true);
}
}
else {
if (!frmTimeOut.hidden) {
vis = false;
frmTimeOut.setVisible(false);
}
}

// Check the countdown counter
if ((min - sec) > 0) {
sec++;
// Repeat the process each minute
timer = setTimeout('doTimer()', 1000);
}
else {
// When countdown is finish, redirect to Login page
timeOutSession();
}
};

stopCount = function () {
if (vis === true) {
//2 seconds Idle (no detect events, because showing alert window is detected as mousemove)
if ((min - sec) < 58) {
vis = false;
}
}
else {
// Convert minutes to second
min = (document.getElementById('txtMinTimeOut').value * 60);
sec = 0;
clearTimeout(timer);
// Get actual time
keepaliveServer((new Date().getTime() / 1000));
doTimer();
}
};

keepaliveServer = function (time) {
// Validating not saturation of the server with sending Keep Alive request
if ((time - server) > (min - 10)) {

var pageToCall = GetNewPath('/Home/KeepAlive/');

var requestParms = new Sys.Net.WebRequest();
requestParms.set_url(pageToCall);
requestParms.set_httpVerb("POST");
var message = "I'm keeping you alive!";
requestParms.set_body(message);
requestParms.get_headers()["Content-Length"] = message.length;
requestParms.add_completed(doNothing);
requestParms.invoke();

// Set new actual time
server = time;
}
};
doNothing = function (executor, eventArgs) { };

updateTime = function () {
var message = 'Inactivity was detected. Your session will expire in';
var time = (min - sec);
var unity = 'seconds.';

Ext.getCmp('lblText').setText(message + ' ' + time + ' ' + unity);
};

// Start counter on Load
document.onload = function () {
stopCount();
//return false;
};
// Star counter when mouse move
document.onmousemove = function () {
stopCount();
//return false;
};
// Star counter when key is pressed
document.onkeypress = function () {
stopCount();
//return false;
};

// Read and event when is send from an iFrame
function displayMessage(e) {
if ((e.origin.split(":", 2)[0] + ":" + e.origin.split(":", 2)[1]) === (GetNewPath("/").split(":", 2)[0] + ":" + GetNewPath("/").split(":", 2)[1])) {
// If the iFrame send any of this events start counter
switch (e.data) {
case "onload":
case "onmousemove":
case "onkeypress":
case "simulated":
stopCount();
}
}
};

if (window.addEventListener) {
// For standards-compliant web browsers
window.addEventListener("message", displayMessage, false);
}
else {
window.attachEvent("onmessage", displayMessage);
};
};


// Add this into any iFrame Page:
// <DocumentReady Handler="detailTimeOut();" />
var detailTimeOut = function () {
// Start counter on Load
document.onload = function () {
top.postMessage("onload", GetNewPath("/"));
return false;
};
// Star counter when mouse move
document.onmousemove = function () {
top.postMessage("onmousemove", GetNewPath("/"));
//return false;
};
// Star counter when key is pressed
document.onkeypress = function () {
top.postMessage("onkeypress", GetNewPath("/"));
//return false;
};
// Simulated Event
simulatedEvent = function () {
top.postMessage("simulated", GetNewPath("/"));
//return false;
};
};

// Get the Path when use VirtualPath in .Net or IIS
var GetNewPath = function (relative_path) {
var url = window.location.href;

if (url.substring(url.length - 1, url.length) == '/') {
url = url.substring(0, url.length - 1);
}

var url_parts = url.split('/');

if (relative_path.substring(0, 1) != '/') {
url_parts[url_parts.length - 2] = relative_path;
url_parts[url_parts.length - 1] = '';
}
else {
url_parts[url_parts.length - 2] = relative_path.substring(1);
url_parts[url_parts.length - 1] = '';
}

var new_page_absolute_path = url_parts.join('/');

if (new_page_absolute_path.substring(new_page_absolut e_path.length - 1, new_page_absolute_path.length) == '/') {
new_page_absolute_path = new_page_absolute_path.substring(0, new_page_absolute_path.length - 1);
}

return new_page_absolute_path;
};

Daniil
Nov 22, 2012, 12:34 PM
Could you clarify is the problem resolved?

equiman
Nov 26, 2012, 7:33 PM
Yes @daniil, thanks!

Now, the code can be used to TimeOut (Client Side) and Keeping Alive Server Side