Persistent client-side data storage with specified expiration - javascript

I am searching for a client-side persistent data storage method that is common across a large number of browsers and allows the specification of an expiration date, like that of document.cookies. I have looked at windows.localStorage (cannot limit expiration) and windows.sessionStorage (expiration occurs too soon) and, for the reasons stated, have decided not to use them. This leaves me with document.cookies. The major problem appears to be that cookies can be blocked. The EU cookie policy appears not to place restrictions on session cookies that are used internally within the site. In the current design, a visitor who blocks cookies will see only an insignificant loss of functionality.
So is there another method of client-side persistent data storage, other than document.cookies, that might be recommended?

You can just use localStorage and include a timestamp with each value you store. Then, when you're reading the value, check the timestamp and see if it's too old to be considered.
function storeInLocalStorage(key, val, msLifespan){
window.localStorage.setItem(key, (new Date().getTime() + msLifespan) + "|" + val);
}
function getFromLocalStorage(key){
var item = window.localStorage.getItem(key);
if(item === null) return null;
var expiration = Number(item.substring(0, item.indexOf("|")));
if(new Date().getTime() < expiration) return item.substring(item.indexOf("|") + 1);
return null;
}

Related

On reload of the application, localStorage data will clear or not?

I want to use some data in my entire application. I am using localStorage to store the data .I am using it in my application but here is my issue , on Reload my entire localStorage values are removed from browser. Can anyone please tell me, localStoage values are removed when user reloads the application? If yes suggest me any other solution to use the data in entire application.
Localstorage is stored in the system, reload application would not erase it.
There are two variables localStorage and sessionStorage .
The difference between two is that data in localStorage has no expiration while sessionStorage clears your data when the page session ends.It depends whatever variable you want to use as per your requirement.
So localStorage will not clear your data when your page is reloaded.
You can use Cookies for store data until the session clears. That way your data will not be cleared when the app reload every time.
Use below code to store data in cookies,
document.cookie = "sampleData=www.samplesite.com";
document.cookie = "sampleUser=user001";
use below code to retrieve data from cookies,
function getCookieData(name) {
var nameEQ = name + "=";
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, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
function getSampleSite() {
var _st = getCookieData("sampleData");
return (_st != null) ? _st : "";
}
function getUser() {
var _st = getCookieData("sampleUser");
return (_st != null) ? _st : "";
}
Check for window.localStorage.clear() in your file and see if it is getting called on reload, as thats the only way to clear our localStorage and you might be hitting on it accidentally.
The read-only localStorage property allows you to access a Storage object for the Document's origin; the stored data is saved across browser sessions. localStorage is similar to sessionStorage, except that while data stored in localStorage has no expiration time, data stored in sessionStorage gets cleared when the page session ends — that is, when the page is closed.
so Ideally it should not clear the values of localStorage unlee you have manually triggered something to clear that (check for any method which is clearing it and triggered on page reload) if all looks good then there are next cases like exceptions:
Exceptions
SecurityError
The request violates a policy decision, or the origin is not a valid scheme/host/port tuple (this can happen if the origin uses the file: or data: scheme, for example). For example, the user may have their browser configured to deny permission to persist data for the specified origin.
Reference : https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage

How can I remove local storage after one hour?

My data is object
I save it use local storage javascript like this :
localStorage.setItem('storedData', JSON.stringify(data))
I just want to keep that data for 1 hour. So if it's been more than 1 hour then the data will be removed
I know to remove data like this :
localStorage.removeItem("storedData")
But how can I set it to auto delete after 1 hour?
You can't.
When do items in HTML5 local storage expire?
The only thing you can do is set the delete statement in a timeout of 1 hour. This requires the user to stay on your page or the timeout won't be executed.
You can also set an expiration field. When the user revisits your site, check the expiration and delete the storage on next visit as the first thing you do.
Add the following code in your App.js file, then the localStorage will be cleared for every 1 hour
var hours = 1; // to clear the localStorage after 1 hour
// (if someone want to clear after 8hrs simply change hours=8)
var now = new Date().getTime();
var setupTime = localStorage.getItem('setupTime');
if (setupTime == null) {
localStorage.setItem('setupTime', now)
} else {
if(now-setupTime > hours*60*60*1000) {
localStorage.clear()
localStorage.setItem('setupTime', now);
}
}
use setInterval, with setting/pushing expiration key in your local data,
check code below.
var myHour = new Date();
myHour.setHours(myDate.getHours() + 1); //one hour from now
data.push(myHour);
localStorage.setItem('storedData', JSON.stringify(data))
function checkExpiration (){
//check if past expiration date
var values = JSON.parse(localStorage.getItem('storedData'));
//check "my hour" index here
if (values[1] < new Date()) {
localStorage.removeItem("storedData")
}
}
function myFunction() {
var myinterval = 15*60*1000; // 15 min interval
setInterval(function(){ checkExpiration(); }, myinterval );
}
myFunction();
You can't use setTimeout().Since you can't guarantee your code is going to be running on the browser in 1 hours. But you can set a timestamp and while returning back just check with Timestamp and clear the storage out based on expiry condition.
Use cookies instead
Cookies.set('foo_bar', 1, { expires: 1/24 })
Alternatively you can use IndexedDB in WebWorkers. What it basically means is you can create a parallel to your main thread JavaScript program, like this
var worker = new Worker('worker.js');
The advantage is when it finishes its work, and you don't have a reference to it, it gets cleaned up automatically.
However, since I'm not fully sure about the above, here is The worker's lifetime from Living Standard
Workers communicate with other workers and with browsing contexts
through message channels and their MessagePort objects.
Each WorkerGlobalScope object worker global scope has a list of the
worker's ports, which consists of all the MessagePort objects that are
entangled with another port and that have one (but only one) port
owned by worker global scope. This list includes the implicit
MessagePort in the case of dedicated workers.
Given an environment settings object o when creating or obtaining a
worker, the relevant owner to add depends on the type of global object
specified by o. If o specifies a global object that is a
WorkerGlobalScope object (i.e., if we are creating a nested dedicated
worker), then the relevant owner is that global object. Otherwise, o
specifies a global object that is a Window object, and the relevant
owner is the responsible document specified by o.
A worker is said to be a permissible worker if its WorkerGlobalScope's
owner set is not empty or:
its owner set has been empty for no more than a short implementation-defined timeout value,
its WorkerGlobalScope object is a SharedWorkerGlobalScope object (i.e., the worker is a shared worker), and
the user agent has a browsing context whose Document object is not completely loaded.
Another advantage is you can terminate a worker, like this
worker.terminate();
Also, it is worth mentioning
IndexedDB has built-in support for schema versions and upgrading via
its IDBOpenDBRequest.onupgradeneeded() method; however, you still
need to write your upgrade code in such a way that it can handle the
user coming from a previous version (including a version with a bug).
This works for a SPA where the browser window doesn't reload on page renders:
Set an expiresIn field in your local storage.
On window reload check to see if current time >= expiresIn
if true clear localStorage items else carry on
You can also do this check whenever your business logic requires it.
Just add the check in your app to check if the variable exists and then do a check on it. Put a unix timestamp on the variable, and compare that the next time the user visits your site.
I think setTimeout() method will do the trick.
UPDATED:
While setting the value to localStorage also set the time of recording the data.
// Store the data with time
const EXPIRE_TIME = 1000*60*60;
localStorage.setItem('storedData', JSON.stringify({
time: new Date(),
data: "your some data"
}));
// start the time out
setTimeout(function() {
localStorage.removeItem('storedData');
}, EXPIRE_TIME); // after an hour it will delete the data
Now the problem is if user leave the site. And setTimeout will not work. So when user next time visit the site you have to bootstrap the setTimeout again.
// On page load
$(document).ready(function() {
let userData = JSON.parse(localStorage.getItem('storedData')) || {};
let diff = new Date() - new Date(userData.time || new Date());
let timeout = Math.max(EXPIRE_TIME - diff, 0);
setTimeout(function() {
localStorage.removeItem('storedData');
}, timeout);
})

Browser keeps multiple values for a single cookie name-value pair

We have a webshop. We use a cookie that stores the order ID of every single order/user. All of the items in the basket and the user's address info are related to that ID. The cookie is only meant to be changed when an order is complete or if its value is empty. We check the cookie with the server on each page load and only change it when conditions above are met.
A few months ago, we discovered that in some cases, the browser can keep multiple versions of that cookie value, and "switch" between those values randomly on page load. Moreover, the value is not overwritten - if the browser switches from value A to value B, a few page loads later it can load value A again. The browser can hold up to 5 (possibly more) values for a single cookie, and it keeps changing them randomly as the user navigates our webshop. It is very problematic since once the cookie value is changed - the basket contents changes with it. We experienced this problem primarily in Google Chrome and Internet Explorer. Trying to check the cookie value in the console shows only the value that is being used for the current page load.
We use the following function to set cookies:
function SetCookie(c_name, value, exdays){
var expires = "";
if(exdays)
{
var date = new Date();
date.setTime(date.getTime() + (exdays*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = c_name + "=" + escape(value) + expires + "; path=/";
}
Whenever I read about cookies, everyone says that overwriting a cookie with the same name and path is supposed to destroy the previous value. So I tried to do the following when setting the order ID cookie (delete cookie before setting it):
SetCookie(name , "", -1);
SetCookie(name , val, 14);
However, the problem still persists and the browser keeps randomly choosing the value on page load. What could be causing such behaviour? Is there any way to check the (shadow) values of the cookie that the browser is currently NOT using? Is there any way to check how many values for a specific cookie name and path the browser has stored?
EDIT: Our javascript order ID cookie setter function runs on page load. It is the only place we ever change the order ID cookie.
Recently, we tested this behaviour with dev tools open and it showed interesting results. A simple page reload can change the cookie header of the request to a request containing a different cookie value, before our cookie setter function ever had a chance to run. We thought it could be a caching issue (request being cached and used later), but it seems this behaviour persists when we set up the server to return a no-cache and no-store response header.
Look at the Nate answer to this question How to handle multiple cookies with the same name?
Hope it helps !!

Are cookies stored in browser cache?

I'm running into a problem where Javascript I've written is getting an outdated cookie value. Each request made to the server should be updating the cookie with a the date the session will expire. But occasionally the date is in the past (which doesn't make sense because the page just loaded which renews the session).
So I'm wondering if the outdated value could be coming from the browser (Chrome) cache. Do cookie values get stored in the cache and then placed in document.cookie when the cached resource is utilized?
I'm reading the cookie via this code:
readCookie : function(name) {
var match = document.cookie.match(new RegExp("(^|;\\s*)(" + name + ")=([^;]*)"));
return (match ? decodeURIComponent(match[3]) : null);
},

Stop people having my website loaded on multiple tabs

I want users to browse my site from only one tab in their browser. How can this be done? Would I use javascript and cookies?
For example, I have a website: www.example.com - and I want my clients to only be able to visit the site from one single tab in one browser. If they open another tab and load the site (or a subpage of the site) - I want an alert "Can't open multiple instances", and then redirect them to an error page.
Once thing to note - if the user changes the address from www.example.com/action/door/mine.aspx to www.example.com - that should work fine, because the user is in the same (original) tab.
Any help will be appreciated. Thanks in advance.
I've created a simple solution for this. The master page layout creates a tab GUID and stores it in sessionStorage area of the tab. The using an event listener on the storage area I write the tab GUID to the sites localStorage area. The listener then compares the tabs GUID to the one written to site storage and if they differ then it knows more than one tab is open.
So if I have three tabs A,B,C then click something in tab C, tab A and B detect another tab is open and warn user of this. I haven't yet got to fixing it so the last tab used get's notification, work in progress.
Here's the JS I have in master page, plus in the login page I have a localStorage.Clear to clear last tab from previous session.
// multi tab detection
function register_tab_GUID() {
// detect local storage available
if (typeof (Storage) !== "undefined") {
// get (set if not) tab GUID and store in tab session
if (sessionStorage["tabGUID"] == null) sessionStorage["tabGUID"] = tab_GUID();
var guid = sessionStorage["tabGUID"];
// add eventlistener to local storage
window.addEventListener("storage", storage_Handler, false);
// set tab GUID in local storage
localStorage["tabGUID"] = guid;
}
}
function storage_Handler(e) {
// if tabGUID does not match then more than one tab and GUID
if (e.key == 'tabGUID') {
if (e.oldValue != e.newValue) tab_Warning();
}
}
function tab_GUID() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
function tab_Warning() {
alert("Another tab is open!");
}
Note: It's IE9+
Hope this helps.
UPDATE - 2020
Client side implementation:
We can make use of Broadcast Channel API which allows communication across browsing contexts (windows, tabs, frames or iframes) provided both contexts are from same origin.
A simple implementation to detect 2nd tab loading the website from the 1st tab:
//in entry point of your app (index.js)
const channel = new BroadcastChannel('tab');
channel.postMessage('another-tab');
// note that listener is added after posting the message
channel.addEventListener('message', (msg) => {
if (msg.data === 'another-tab') {
// message received from 2nd tab
alert('Cannot open multiple instances');
}
});
This doesn't use localStorage or cookies and it even works if 1st tab is offline and 2nd tab is being loaded.
Note: This is not supported in Safari & IE11 yet :(
UPDATE - 2022
From March 2022, it is now officially supported on Safari 🥳
Take a note on its browser compatibility.
However, there's a polyfill available that does the job.
EDIT2:
It's the exact thing which is mentioned at this answer, You need 2 IDs:
One random one
One consistent one (this will be our SSID actually, since you limit tabs of a single browser, it's better to get generated form browser's unique parameters)
You can generate consistent one from browser's user-agent or get it from server-side. store both of them server-side.
Store the random one in window.name property which is tab-specific.
Send a heartbeat every 1~2 seconds to your server containing both consistent ID and random one. if server fails to receive the heartbeat, it cleans up database and de-register dead clients.
on every browser's request, check window.name for the value. if it were missing, check with the server-side whether if the previous tab is closed or not (cleaned from database).
If yes, generate a new pair for client if no, reject them.
Two suggestions on top of my mind:
Server-side (better): provide all your clients, a user name and password. request them on their first visit of your site to enter with their credentials. then on every other request, check for whether user with said credentials is already logged in or not.
Client *
|
|
Server ---> Check whether
Already logged
or not?
______________
| |
yes no
| |
permit reject
them them
Client-side: If you really need a strong check of this, use evercookie to store an already-logged-in cookie on client's machine.
Side-note: Do know that every attempt in client side is not secure at all! client-side should help server-side, it shouldn't be used as the one and only source of security. even evercookies can be deleted so, give my first suggestion a go.
**EDIT:**
Evercookie is really doing a good job at storing most secure zombie cookies ever but since the library itself is a little bit heavy for browsers (storing a cookie takes more than 100ms each time) it's not really recommended for using in real-world web app.
use these instead if you went with server-side solution:
Way around ASP.NET session being shared across multiple tab windows
Kiranvj's answer
Extending rehman_00001's answer to handle the case where you want the alert on the new tabs instead.
const channel = new BroadcastChannel('tab');
let isOriginal = true;
channel.postMessage('another-tab');
// note that listener is added after posting the message
channel.addEventListener('message', (msg) => {
if (msg.data === 'another-tab' && isOriginal) {
// message received from 2nd tab
// reply to all new tabs that the website is already open
channel.postMessage('already-open');
}
if (msg.data === 'already-open') {
isOriginal = false;
// message received from original tab
// replace this with whatever logic you need
alert('Cannot open multiple instances');
}
});
I know this post is pretty old, but in case it helps anybody, I recently looked into basically doing the same thing using localStorage and sessionStorage.
Similar Anthony's answer, it sets an interval to make sure the originating tab keeps the entry fresh, so that if the browser crashes or somehow closes without calling the unload event (included in the comments but not part of the code for testing purposes), then there would just be a short delay before the application would run properly in a new browser window.
Obviously, you would change the "tab is good", "tab is bad" conditions to do whatever logic you want.
Oh, and also, the createGUID method is just a utility to make the session identifier unique... it is from this answer to a previous question (wanted to make sure I wasn't taking credit for that).
https://jsfiddle.net/yex8k2ts/30/
let localStorageTimeout = 15 * 1000; // 15,000 milliseconds = 15 seconds.
let localStorageResetInterval = 10 * 1000; // 10,000 milliseconds = 10 seconds.
let localStorageTabKey = 'test-application-browser-tab';
let sessionStorageGuidKey = 'browser-tab-guid';
function createGUID() {
let guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
/*eslint-disable*/
let r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
/*eslint-enable*/
return v.toString(16);
});
return guid;
}
/**
* Compare our tab identifier associated with this session (particular tab)
* with that of one that is in localStorage (the active one for this browser).
* This browser tab is good if any of the following are true:
* 1. There is no localStorage Guid yet (first browser tab).
* 2. The localStorage Guid matches the session Guid. Same tab, refreshed.
* 3. The localStorage timeout period has ended.
*
* If our current session is the correct active one, an interval will continue
* to re-insert the localStorage value with an updated timestamp.
*
* Another thing, that should be done (so you can open a tab within 15 seconds of closing it) would be to do the following (or hook onto an existing onunload method):
* window.onunload = () => {
localStorage.removeItem(localStorageTabKey);
};
*/
function testTab() {
let sessionGuid = sessionStorage.getItem(sessionStorageGuidKey) || createGUID();
let tabObj = JSON.parse(localStorage.getItem(localStorageTabKey)) || null;
sessionStorage.setItem(sessionStorageGuidKey, sessionGuid);
// If no or stale tab object, our session is the winner. If the guid matches, ours is still the winner
if (tabObj === null || (tabObj.timestamp < new Date().getTime() - localStorageTimeout) || tabObj.guid === sessionGuid) {
function setTabObj() {
let newTabObj = {
guid: sessionGuid,
timestamp: new Date().getTime()
};
localStorage.setItem(localStorageTabKey, JSON.stringify(newTabObj));
}
setTabObj();
setInterval(setTabObj, localStorageResetInterval);
return true;
} else {
// An active tab is already open that does not match our session guid.
return false;
}
}
if (testTab()) {
document.getElementById('result').innerHTML = 'tab is good';
} else {
document.getElementById('result').innerHTML = 'tab is bad';
}
window.addEventListener('load', function () {
if (localStorage.getItem('web_browser') == null) {
// new tab
localStorage.setItem('web_browser', 'true');
window.addEventListener('unload', function() {
localStorage.removeItem('web_browser');
})
} else {
// duplicate tab
return;
}
})
Put this script at the beginning of html pages, where you don't want users to duplicate current page or tab.
The same problem (and solution) : https://sites.google.com/site/sarittechworld/track-client-windows
Similar :
http://www.codeproject.com/Articles/35859/Detect-and-prevent-multiple-windows-or-tab-usage-i
The best way to solve this is to have one-time session IDs.
Eg, each page contain a session ID, that is valid for one visit, is unique, and random.
When clicking any one link, it will use & invalidate the session ID, and the new page will have a new session ID.
This will force the user to always browse in the newest window or tab, and also prevents session stealing over the wire.
Any attempt to reuse a old session ID should immediately kill also the active session IDs for that user.
Its also important to store, in the session management system, which pages is accessible from page X. So if page X (with session ID abc) contains links to page 1, 2 and 3, any attempt to visit page 4 with session ID abc, will fail and also kill the session.
This will force the user to always have one single session track, and always follow the logic on the site. Any attempt to go forward, back, using history or log entires, or opening multiple windows or tabs, will fail and logout the user in all windows, tabs and devices.
All this can be completely implemented on server-side, without any client-side logic.
Why do you want to do this?
Could try to do some ugly hacking, but the result would be: There is no way you could completely suppress this behaviour.
This could not be solved by JavaScript, because there is always the possibility that the user has disabled JavaScript in his browser, or allows only a certain subset.
The user could open a new browser, use a different computer, etc. to visit multiple pages at once.
But more important:
Also, your site would be the only site that has this behaviour and for this reason this will confuse everybody which uses your site, because it doesn't work like a web site should work. Everybody who tries to open a second tab will think: "This is odd. This website sucks because it different then websites should be. I will not come again!" ;-)
I wrote this to stop a call center page from being accessed in multiple tabs. It works well and is purely client-side. Just update the else if part to do what you want if it detects a new tab.
// helper function to set cookies
function setCookie(cname, cvalue, seconds) {
var d = new Date();
d.setTime(d.getTime() + (seconds * 1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
// helper function to get a cookie
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.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 "";
}
// Do not allow multiple call center tabs
if (~window.location.hash.indexOf('#admin/callcenter')) {
$(window).on('beforeunload onbeforeunload', function(){
document.cookie = 'ic_window_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
});
function validateCallCenterTab() {
var win_id_cookie_duration = 10; // in seconds
if (!window.name) {
window.name = Math.random().toString();
}
if (!getCookie('ic_window_id') || window.name === getCookie('ic_window_id')) {
// This means they are using just one tab. Set/clobber the cookie to prolong the tab's validity.
setCookie('ic_window_id', window.name, win_id_cookie_duration);
} else if (getCookie('ic_window_id') !== window.name) {
// this means another browser tab is open, alert them to close the tabs until there is only one remaining
var message = 'You cannot have this website open in multiple tabs. ' +
'Please close them until there is only one remaining. Thanks!';
$('html').html(message);
clearInterval(callCenterInterval);
throw 'Multiple call center tabs error. Program terminating.';
}
}
callCenterInterval = setInterval(validateCallCenterTab, 3000);
}

Categories

Resources