From e92355a69760502a06e3d01ac7984175e17f184a Mon Sep 17 00:00:00 2001 From: Rodrigo Morais Date: Wed, 22 Oct 2014 22:05:27 -0200 Subject: [PATCH 1/2] Adding control of expiration time in localstorage. Issue #133. --- dist/angular-local-storage.js | 57 ++++++++++---- dist/angular-local-storage.min.js | 4 +- src/angular-local-storage.js | 50 +++++++++--- src/common.js | 6 ++ test/mock/localStorageMock.js | 10 ++- test/spec/localStorageSpec.js | 122 +++++++++++++++++++++--------- 6 files changed, 183 insertions(+), 66 deletions(-) diff --git a/dist/angular-local-storage.js b/dist/angular-local-storage.js index 755b378..7bfc060 100644 --- a/dist/angular-local-storage.js +++ b/dist/angular-local-storage.js @@ -1,6 +1,6 @@ /** * An Angular module that gives you access to the browsers local storage - * @version v0.1.3 - 2014-10-14 + * @version v0.1.3 - 2014-10-22 * @link https://github.com/grevory/angular-local-storage * @author grevory * @license MIT License, http://www.opensource.org/licenses/MIT @@ -14,6 +14,7 @@ var isDefined = angular.isDefined, isNumber = angular.isNumber, isObject = angular.isObject, isArray = angular.isArray, + isBoolean = isBoolean, extend = angular.extend, toJson = angular.toJson, fromJson = angular.fromJson; @@ -25,6 +26,10 @@ function isStringNumber(num) { return /^-?\d+\.?\d*$/.test(num.replace(/["']/g, '')); } + +function isBoolean(value) { + return typeof value === 'boolean'; +} var angularLocalStorage = angular.module('LocalStorageModule', []); angularLocalStorage.provider('localStorageService', function() { @@ -138,13 +143,22 @@ angularLocalStorage.provider('localStorageService', function() { // Directly adds a value to local storage // If local storage is not available in the browser use cookies // Example use: localStorageService.add('library','angular'); - var addToLocalStorage = function (key, value) { + var addToLocalStorage = function (key, value, compareDateExpiration) { + var lsValue = {}; // Let's convert undefined values to null to get the value consistent if (isUndefined(value)) { - value = null; + value = null; } else if (isObject(value) || isArray(value) || isNumber(+value || value)) { - value = toJson(value); + + value = toJson(value); + } else if(isBoolean(value)){ + value = value.toString(); } + + lsValue = { + "date": compareDateExpiration || Date.now(), + "data": value + }; // If this browser does not support local storage use cookies if (!browserSupportsLocalStorage || self.storageType === 'cookie') { @@ -160,11 +174,15 @@ angularLocalStorage.provider('localStorageService', function() { try { if (isObject(value) || isArray(value)) { - value = toJson(value); + value = toJson(value); + lsValue = { + date: compareDateExpiration || Date.now(), + data: value + }; } - if (webStorage) {webStorage.setItem(deriveQualifiedKey(key), value)}; + if (webStorage) { webStorage.setItem(deriveQualifiedKey(key), JSON.stringify(lsValue)) }; if (notify.setItem) { - $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType}); + $rootScope.$broadcast('LocalStorageModule.notification.setitem', { key: key, newvalue: JSON.stringify(lsValue), storageType: self.storageType }); } } catch (e) { $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); @@ -175,7 +193,9 @@ angularLocalStorage.provider('localStorageService', function() { // Directly get a value from local storage // Example use: localStorageService.get('library'); // returns 'angular' - var getFromLocalStorage = function (key) { + var getFromLocalStorage = function (key, expiration) { + + var data, date, saved; if (!browserSupportsLocalStorage || self.storageType === 'cookie') { if (!browserSupportsLocalStorage) { @@ -184,7 +204,7 @@ angularLocalStorage.provider('localStorageService', function() { return getFromCookies(key); } - + var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null; // angular.toJson will convert null to 'null', so a proper conversion is needed // FIXME not a perfect solution, since a valid 'null' string can't be stored @@ -192,11 +212,22 @@ angularLocalStorage.provider('localStorageService', function() { return null; } - if (item.charAt(0) === "{" || item.charAt(0) === "[" || isStringNumber(item)) { - return fromJson(item); + item = JSON.parse(item); + data = item.data; + saved = item.date; + + if (expiration) { + var dateExpiration = new Date(saved).getTime() + expiration; + if (dateExpiration < Date.now()) { + return null; + } + } + + if (data.charAt(0) === "{" || data.charAt(0) === "[" || isStringNumber(data)) { + return fromJson(data); } - return item; + return data; }; // Remove an item from local storage @@ -407,7 +438,7 @@ angularLocalStorage.provider('localStorageService', function() { $parse(key).assign(scope, value); return scope.$watch(key, function(newVal) { - addToLocalStorage(lsKey, newVal); + addToLocalStorage(lsKey, newVal); }, isObject(scope[key])); }; diff --git a/dist/angular-local-storage.min.js b/dist/angular-local-storage.min.js index 491d469..2c4029c 100644 --- a/dist/angular-local-storage.min.js +++ b/dist/angular-local-storage.min.js @@ -1,7 +1,7 @@ /** * An Angular module that gives you access to the browsers local storage - * @version v0.1.3 - 2014-10-14 + * @version v0.1.3 - 2014-10-22 * @link https://github.com/grevory/angular-local-storage * @author grevory * @license MIT License, http://www.opensource.org/licenses/MIT - */!function(a,b){"use strict";function c(a){return/^-?\d+\.?\d*$/.test(a.replace(/["']/g,""))}var d=b.isDefined,e=b.isUndefined,f=b.isNumber,g=b.isObject,h=b.isArray,i=b.extend,j=b.toJson,k=b.fromJson,l=b.module("LocalStorageModule",[]);l.provider("localStorageService",function(){this.prefix="ls",this.storageType="localStorage",this.cookie={expiry:30,path:"/"},this.notify={setItem:!0,removeItem:!1},this.setPrefix=function(a){this.prefix=a},this.setStorageType=function(a){this.storageType=a},this.setStorageCookie=function(a,b){this.cookie={expiry:a,path:b}},this.setStorageCookieDomain=function(a){this.cookie.domain=a},this.setNotify=function(a,b){this.notify={setItem:a,removeItem:b}},this.$get=["$rootScope","$window","$document","$parse",function(a,b,l,m){var n,o=this,p=o.prefix,q=o.cookie,r=o.notify,s=o.storageType;l?l[0]&&(l=l[0]):l=document,"."!==p.substr(-1)&&(p=p?p+".":"");var t=function(a){return p+a},u=function(){try{var c=s in b&&null!==b[s],d=t("__"+Math.round(1e7*Math.random()));return c&&(n=b[s],n.setItem(d,""),n.removeItem(d)),c}catch(e){return s="cookie",a.$broadcast("LocalStorageModule.notification.error",e.message),!1}}(),v=function(b,c){if(e(c)?c=null:(g(c)||h(c)||f(+c||c))&&(c=j(c)),!u||"cookie"===o.storageType)return u||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),r.setItem&&a.$broadcast("LocalStorageModule.notification.setitem",{key:b,newvalue:c,storageType:"cookie"}),B(b,c);try{(g(c)||h(c))&&(c=j(c)),n&&n.setItem(t(b),c),r.setItem&&a.$broadcast("LocalStorageModule.notification.setitem",{key:b,newvalue:c,storageType:o.storageType})}catch(d){return a.$broadcast("LocalStorageModule.notification.error",d.message),B(b,c)}return!0},w=function(b){if(!u||"cookie"===o.storageType)return u||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),C(b);var d=n?n.getItem(t(b)):null;return d&&"null"!==d?"{"===d.charAt(0)||"["===d.charAt(0)||c(d)?k(d):d:null},x=function(b){if(!u||"cookie"===o.storageType)return u||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),r.removeItem&&a.$broadcast("LocalStorageModule.notification.removeitem",{key:b,storageType:"cookie"}),D(b);try{n.removeItem(t(b)),r.removeItem&&a.$broadcast("LocalStorageModule.notification.removeitem",{key:b,storageType:o.storageType})}catch(c){return a.$broadcast("LocalStorageModule.notification.error",c.message),D(b)}return!0},y=function(){if(!u)return a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),!1;var b=p.length,c=[];for(var d in n)if(d.substr(0,b)===p)try{c.push(d.substr(b))}catch(e){return a.$broadcast("LocalStorageModule.notification.error",e.Description),[]}return c},z=function(b){b=b||"";var c=p.slice(0,-1),d=new RegExp(c+"."+b);if(!u||"cookie"===o.storageType)return u||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),E();var e=p.length;for(var f in n)if(d.test(f))try{x(f.substr(e))}catch(g){return a.$broadcast("LocalStorageModule.notification.error",g.message),E()}return!0},A=function(){try{return navigator.cookieEnabled||"cookie"in l&&(l.cookie.length>0||(l.cookie="test").indexOf.call(l.cookie,"test")>-1)}catch(b){return a.$broadcast("LocalStorageModule.notification.error",b.message),!1}},B=function(b,c){if(e(c))return!1;if((h(c)||g(c))&&(c=j(c)),!A())return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;try{var d="",f=new Date,i="";if(null===c?(f.setTime(f.getTime()+-864e5),d="; expires="+f.toGMTString(),c=""):0!==q.expiry&&(f.setTime(f.getTime()+24*q.expiry*60*60*1e3),d="; expires="+f.toGMTString()),b){var k="; path="+q.path;q.domain&&(i="; domain="+q.domain),l.cookie=t(b)+"="+encodeURIComponent(c)+d+k+i}}catch(m){return a.$broadcast("LocalStorageModule.notification.error",m.message),!1}return!0},C=function(b){if(!A())return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;for(var c=l.cookie&&l.cookie.split(";")||[],d=0;d0||(m.cookie="test").indexOf.call(m.cookie,"test")>-1)}catch(b){return a.$broadcast("LocalStorageModule.notification.error",b.message),!1}},C=function(b,c){if(f(c))return!1;if((i(c)||h(c))&&(c=k(c)),!B())return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;try{var d="",e=new Date,g="";if(null===c?(e.setTime(e.getTime()+-864e5),d="; expires="+e.toGMTString(),c=""):0!==r.expiry&&(e.setTime(e.getTime()+24*r.expiry*60*60*1e3),d="; expires="+e.toGMTString()),b){var j="; path="+r.path;r.domain&&(g="; domain="+r.domain),m.cookie=u(b)+"="+encodeURIComponent(c)+d+j+g}}catch(l){return a.$broadcast("LocalStorageModule.notification.error",l.message),!1}return!0},D=function(b){if(!B())return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;for(var c=m.cookie&&m.cookie.split(";")||[],d=0;d Date: Wed, 5 Nov 2014 22:44:14 -0200 Subject: [PATCH 2/2] Adding flag to remove key from localstorage when get have expiration time control. --- src/angular-local-storage.js | 7 ++- test/karma.conf.js | 2 +- test/spec/localStorageSpec.js | 103 ++++++++++++++++++++++------------ 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/src/angular-local-storage.js b/src/angular-local-storage.js index 90aff29..faccac3 100644 --- a/src/angular-local-storage.js +++ b/src/angular-local-storage.js @@ -161,7 +161,9 @@ angularLocalStorage.provider('localStorageService', function() { // Directly get a value from local storage // Example use: localStorageService.get('library'); // returns 'angular' - var getFromLocalStorage = function (key, expiration) { + var getFromLocalStorage = function (key, expiration, remove) { + + remove = remove === undefined ? false : remove; var data, date, saved; @@ -187,6 +189,9 @@ angularLocalStorage.provider('localStorageService', function() { if (expiration) { var dateExpiration = new Date(saved).getTime() + expiration; if (dateExpiration < Date.now()) { + if (remove) { + webStorage.removeItem(deriveQualifiedKey(key)) + } return null; } } diff --git a/test/karma.conf.js b/test/karma.conf.js index 7a6492e..ab01200 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -29,7 +29,7 @@ module.exports = function(config) { bower + 'angular/angular.js', bower + 'angular-mocks/angular-mocks.js', 'src/*.js', - 'test/mock/*.js', + 'test/mock/**/*.js', 'test/spec/**/*.js' ], diff --git a/test/spec/localStorageSpec.js b/test/spec/localStorageSpec.js index 9e77514..ab5ddfe 100644 --- a/test/spec/localStorageSpec.js +++ b/test/spec/localStorageSpec.js @@ -12,7 +12,7 @@ describe('localStorageService', function() { } function addItem(key, value, date) { - return function ($window, localStorageService) { + return function ($window, localStorageService) { elmSpy = spyOn($window.localStorage, 'setItem').andCallThrough(); localStorageService.set(key, value, date); }; @@ -33,12 +33,12 @@ describe('localStorageService', function() { } function expectAdding(key, value, date) { - return function () { - var lsValue = { - date: date || Date.now(), - data: value - }; - expect(elmSpy).toHaveBeenCalledWith(key, JSON.stringify(lsValue)); + return function () { + var lsValue = { + date: date || Date.now(), + data: value + }; + expect(elmSpy).toHaveBeenCalledWith(key, JSON.stringify(lsValue)); }; } @@ -48,9 +48,10 @@ describe('localStorageService', function() { }; } - function expectMatching(key, expected, expiration) { - return function(localStorageService) { - expect(localStorageService.get(key, expiration)).toEqual(expected); + function expectMatching(key, expected, expiration, remove) { + return function (localStorageService) { + remove = remove === undefined ? false : remove; + expect(localStorageService.get(key, expiration, remove)).toEqual(expected); }; } @@ -202,7 +203,7 @@ describe('localStorageService', function() { addItem('key', '777', now), expectAdding('ls.key', angular.toJson('777'), now), expectMatching('key', '777') - ) + ); }); it('should be able to get items', inject( @@ -212,40 +213,71 @@ describe('localStorageService', function() { /* ******************* expiration *************** */ it('should be able to set and get values within of expiration time', function () { - var aHourAgo = Date.now() + (-3600000), - twoHours = 7200000; - inject( - addItem('key', '777', aHourAgo), - expectMatching('key', '777', twoHours) - ) + var aHourAgo = Date.now() + (-3600000), + twoHours = 7200000; + inject( + addItem('key', '777', aHourAgo), + expectMatching('key', '777', twoHours) + ); }); it('should be able to set values but should get null because out of expiration time', function () { + var aHourAgo = Date.now() + (-3600000), + middleHour = 1800000; + inject( + addItem('key', '777', aHourAgo), + expectMatching('key', null, middleHour) + ); + }); + + it('should be able to set and get values within of expiration time with more 24 hours', function () { + var aHourAgo = Date.now() + (-3600000), + twentySixHours = 93600000; + inject( + addItem('key', '777', aHourAgo), + expectMatching('key', '777', twentySixHours) + ); + }); + + it('should be able to set values but should get null because out of expiration time, when expiration time is negative', function () { + var aHourAgo = Date.now() + (-3600000), + twentySixHoursNegative = -93600000; + inject( + addItem('key', '777', aHourAgo), + expectMatching('key', null, twentySixHoursNegative) + ); + }); + + it('should be able to set values but should get null in second validation because out of expiration time on first validation and remove key flag was on', function () { var aHourAgo = Date.now() + (-3600000), - middleHour = 1800000; + twentySixHoursNegative = -93600000; inject( addItem('key', '777', aHourAgo), - expectMatching('key', null, middleHour) - ) + expectMatching('key', null, twentySixHoursNegative, true), + expectMatching('key', null) + ); }); - it('should be able to set and get values within of expiration time with more 24 hours', function () { + it('should be able to set and get values in first and second validation, because first validation within of expiration time with more 24 hours', function () { var aHourAgo = Date.now() + (-3600000), twentySixHours = 93600000; inject( addItem('key', '777', aHourAgo), - expectMatching('key', '777', twentySixHours) - ) + expectMatching('key', '777', twentySixHours, true), + expectMatching('key', '777') + ); }); - it('should be able to set values but should get null because out of expiration time, when expiration time is negative', function () { + it('should be able to set values but should get null in first validation and get the value in second validation, because out of expiration time on first validation but remove key flag was off', function () { var aHourAgo = Date.now() + (-3600000), twentySixHoursNegative = -93600000; inject( addItem('key', '777', aHourAgo), - expectMatching('key', null, twentySixHoursNegative) - ) + expectMatching('key', null, twentySixHoursNegative), + expectMatching('key', '777') + ); }); + /* ******************* expiration *************** */ it('should be able to remove items', inject( @@ -360,10 +392,10 @@ describe('localStorageService', function() { var results = []; spyOn($rootScope, '$watch').andCallFake(function (key, func, eq) { - results.push(eq); + results.push(eq); }); - mocks.forEach(function (elm, i) { + mocks.forEach(function (elm, i) { localStorageService.set('mock' + i, elm); localStorageService.bind($rootScope, 'mock' + i); }); @@ -380,7 +412,8 @@ describe('localStorageService', function() { } expect(localStorageService.length()).toEqual(10); expect($window.localStorage.length).toEqual(20); - })); + } + )); it('should be able to clear all owned keys from storage',inject(function($window, localStorageService) { for(var i = 0; i < 10; i++) { @@ -423,17 +456,17 @@ describe('localStorageService', function() { inject(function($window, localStorageService) { var setSpy = spyOn($window.sessionStorage, 'setItem'), - getSpy = spyOn($window.sessionStorage, 'getItem'), - removeSpy = spyOn($window.sessionStorage, 'removeItem'), - lsValue; + getSpy = spyOn($window.sessionStorage, 'getItem'), + removeSpy = spyOn($window.sessionStorage, 'removeItem'), + lsValue; localStorageService.set('foo', 'bar', now); localStorageService.get('foo'); localStorageService.remove('foo'); lsValue = { - date: now, - data: 'bar' + date: now, + data: 'bar' }; expect(setSpy).toHaveBeenCalledWith('ls.foo', JSON.stringify(lsValue)); @@ -521,7 +554,7 @@ describe('localStorageService', function() { it('should be able to clear all owned keys from cookie', inject(function(localStorageService, $document) { localStorageService.set('ownKey1', 1); - $document.cookie = "username=John Doe"; + $document.cookie = 'username=John Doe'; localStorageService.clearAll(); expect(localStorageService.get('ownKey1')).toEqual(null); expect($document.cookie).not.toEqual('');