/** * An Angular module that gives you access to the browsers local storage * @version v0.6.0 - 2017-05-18 * @link https://github.com/grevory/angular-local-storage * @author grevory * @license MIT License, http://www.opensource.org/licenses/MIT */ (function (window, angular) { var isDefined = angular.isDefined, isUndefined = angular.isUndefined, isNumber = angular.isNumber, isObject = angular.isObject, isArray = angular.isArray, isString = angular.isString, extend = angular.extend, toJson = angular.toJson; angular .module('LocalStorageModule', []) .provider('localStorageService', function() { // You should set a prefix to avoid overwriting any local storage variables from the rest of your app // e.g. localStorageServiceProvider.setPrefix('yourAppName'); // With provider you can use config as this: // myApp.config(function (localStorageServiceProvider) { // localStorageServiceProvider.prefix = 'yourAppName'; // }); this.prefix = 'ls'; // You could change web storage type localstorage or sessionStorage this.storageType = 'localStorage'; // Cookie options (usually in case of fallback) // expiry = Number of days before cookies expire // 0 = Does not expire // path = The web path the cookie represents // secure = Wether the cookies should be secure (i.e only sent on HTTPS requests) this.cookie = { expiry: 30, path: '/', secure: false }; // Decides wether we should default to cookies if localstorage is not supported. this.defaultToCookie = true; // Send signals for each of the following actions? this.notify = { setItem: true, removeItem: false }; // Setter for the prefix this.setPrefix = function(prefix) { this.prefix = prefix; return this; }; // Setter for the storageType this.setStorageType = function(storageType) { this.storageType = storageType; return this; }; // Setter for defaultToCookie value, default is true. this.setDefaultToCookie = function (shouldDefault) { this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value. return this; }; // Setter for cookie config this.setStorageCookie = function(exp, path, secure) { this.cookie.expiry = exp; this.cookie.path = path; this.cookie.secure = secure; return this; }; // Setter for cookie domain this.setStorageCookieDomain = function(domain) { this.cookie.domain = domain; return this; }; // Setter for notification config // itemSet & itemRemove should be booleans this.setNotify = function(itemSet, itemRemove) { this.notify = { setItem: itemSet, removeItem: itemRemove }; return this; }; this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) { var self = this; var prefix = self.prefix; var cookie = self.cookie; var notify = self.notify; var storageType = self.storageType; var webStorage; // When Angular's $document is not available if (!$document) { $document = document; } else if ($document[0]) { $document = $document[0]; } // If there is a prefix set in the config lets use that with an appended period for readability if (prefix.substr(-1) !== '.') { prefix = !!prefix ? prefix + '.' : ''; } var deriveQualifiedKey = function(key) { return prefix + key; }; // Removes prefix from the key. var underiveQualifiedKey = function (key) { return key.replace(new RegExp('^' + prefix, 'g'), ''); }; // Check if the key is within our prefix namespace. var isKeyPrefixOurs = function (key) { return key.indexOf(prefix) === 0; }; // Checks the browser to see if local storage is supported var checkSupport = function () { try { var supported = (storageType in $window && $window[storageType] !== null); // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage // is available, but trying to call .setItem throws an exception. // // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage // that exceeded the quota." var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7)); if (supported) { webStorage = $window[storageType]; webStorage.setItem(key, ''); webStorage.removeItem(key); } return supported; } catch (e) { // Only change storageType to cookies if defaulting is enabled. if (self.defaultToCookie) storageType = 'cookie'; $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); return false; } }; var browserSupportsLocalStorage = checkSupport(); // 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, type) { var previousType = getStorageType(); try { setStorageType(type); // Let's convert undefined values to null to get the value consistent if (isUndefined(value)) { value = null; } else { value = toJson(value); } // If this browser does not support local storage use cookies if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { if (!browserSupportsLocalStorage) { $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); } if (notify.setItem) { $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'}); } return addToCookies(key, value); } try { if (webStorage) { webStorage.setItem(deriveQualifiedKey(key), value); } if (notify.setItem) { $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType}); } } catch (e) { $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); return addToCookies(key, value); } return true; } finally { setStorageType(previousType); } }; // Directly get a value from local storage // Example use: localStorageService.get('library'); // returns 'angular' var getFromLocalStorage = function (key, type) { var previousType = getStorageType(); try { setStorageType(type); if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { if (!browserSupportsLocalStorage) { $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); } 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 if (!item || item === 'null') { return null; } try { return JSON.parse(item); } catch (e) { return item; } } finally { setStorageType(previousType); } }; // Remove an item from local storage // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular' // // This is var-arg removal, check the last argument to see if it is a storageType // and set type accordingly before removing. // var removeFromLocalStorage = function () { var previousType = getStorageType(); try { // can't pop on arguments, so we do this var consumed = 0; if (arguments.length >= 1 && (arguments[arguments.length - 1] === 'localStorage' || arguments[arguments.length - 1] === 'sessionStorage')) { consumed = 1; setStorageType(arguments[arguments.length - 1]); } var i, key; for (i = 0; i < arguments.length - consumed; i++) { key = arguments[i]; if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { if (!browserSupportsLocalStorage) { $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); } if (notify.removeItem) { $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'}); } removeFromCookies(key); } else { try { webStorage.removeItem(deriveQualifiedKey(key)); if (notify.removeItem) { $rootScope.$broadcast('LocalStorageModule.notification.removeitem', { key: key, storageType: self.storageType }); } } catch (e) { $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); removeFromCookies(key); } } } } finally { setStorageType(previousType); } }; // Return array of keys for local storage // Example use: var keys = localStorageService.keys() var getKeysForLocalStorage = function (type) { var previousType = getStorageType(); try { setStorageType(type); if (!browserSupportsLocalStorage) { $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); return []; } var prefixLength = prefix.length; var keys = []; for (var key in webStorage) { // Only return keys that are for this app if (key.substr(0, prefixLength) === prefix) { try { keys.push(key.substr(prefixLength)); } catch (e) { $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description); return []; } } } return keys; } finally { setStorageType(previousType); } }; // Remove all data for this app from local storage // Also optionally takes a regular expression string and removes the matching key-value pairs // Example use: localStorageService.clearAll(); // Should be used mostly for development purposes var clearAllFromLocalStorage = function (regularExpression, type) { var previousType = getStorageType(); try { setStorageType(type); // Setting both regular expressions independently // Empty strings result in catchall RegExp var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp(); var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp(); if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { if (!browserSupportsLocalStorage) { $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); } return clearAllFromCookies(); } if (!browserSupportsLocalStorage && !self.defaultToCookie) return false; var prefixLength = prefix.length; for (var key in webStorage) { // Only remove items that are for this app and match the regular expression if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) { try { removeFromLocalStorage(key.substr(prefixLength)); } catch (e) { $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); return clearAllFromCookies(); } } } return true; } finally { setStorageType(previousType); } }; // Checks the browser to see if cookies are supported var browserSupportsCookies = (function() { try { return $window.navigator.cookieEnabled || ("cookie" in $document && ($document.cookie.length > 0 || ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1)); } catch (e) { $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); return false; } }()); // Directly adds a value to cookies // Typically used as a fallback if local storage is not available in the browser // Example use: localStorageService.cookie.add('library','angular'); var addToCookies = function (key, value, daysToExpiry, secure) { if (isUndefined(value)) { return false; } else if(isArray(value) || isObject(value)) { value = toJson(value); } if (!browserSupportsCookies) { $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); return false; } try { var expiry = '', expiryDate = new Date(), cookieDomain = ''; if (value === null) { // Mark that the cookie has expired one day ago expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000)); expiry = "; expires=" + expiryDate.toGMTString(); value = ''; } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) { expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000)); expiry = "; expires=" + expiryDate.toGMTString(); } else if (cookie.expiry !== 0) { expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000)); expiry = "; expires=" + expiryDate.toGMTString(); } if (!!key) { var cookiePath = "; path=" + cookie.path; if (cookie.domain) { cookieDomain = "; domain=" + cookie.domain; } /* Providing the secure parameter always takes precedence over config * (allows developer to mix and match secure + non-secure) */ if (typeof secure === 'boolean') { if (secure === true) { /* We've explicitly specified secure, * add the secure attribute to the cookie (after domain) */ cookieDomain += "; secure"; } // else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says } else if (cookie.secure === true) { // secure parameter wasn't specified, get default from config cookieDomain += "; secure"; } $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain; } } catch (e) { $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); return false; } return true; }; // Directly get a value from a cookie // Example use: localStorageService.cookie.get('library'); // returns 'angular' var getFromCookies = function (key) { if (!browserSupportsCookies) { $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); return false; } var cookies = $document.cookie && $document.cookie.split(';') || []; for(var i=0; i < cookies.length; i++) { var thisCookie = cookies[i]; while (thisCookie.charAt(0) === ' ') { thisCookie = thisCookie.substring(1,thisCookie.length); } if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) { var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length)); try { var parsedValue = JSON.parse(storedValues); return typeof(parsedValue) === 'number' ? storedValues : parsedValue; } catch(e) { return storedValues; } } } return null; }; var removeFromCookies = function (key) { addToCookies(key,null); }; var clearAllFromCookies = function () { var thisCookie = null; var prefixLength = prefix.length; var cookies = $document.cookie.split(';'); for(var i = 0; i < cookies.length; i++) { thisCookie = cookies[i]; while (thisCookie.charAt(0) === ' ') { thisCookie = thisCookie.substring(1, thisCookie.length); } var key = thisCookie.substring(prefixLength, thisCookie.indexOf('=')); removeFromCookies(key); } }; var getStorageType = function() { return storageType; }; var setStorageType = function(type) { if (type && storageType !== type) { storageType = type; browserSupportsLocalStorage = checkSupport(); } return browserSupportsLocalStorage; }; // Add a listener on scope variable to save its changes to local storage // Return a function which when called cancels binding var bindToScope = function(scope, key, def, lsKey, type) { lsKey = lsKey || key; var value = getFromLocalStorage(lsKey, type); if (value === null && isDefined(def)) { value = def; } else if (isObject(value) && isObject(def)) { value = extend(value, def); } $parse(key).assign(scope, value); return scope.$watch(key, function(newVal) { addToLocalStorage(lsKey, newVal, type); }, isObject(scope[key])); }; // Add listener to local storage, for update callbacks. if (browserSupportsLocalStorage) { if ($window.addEventListener) { $window.addEventListener("storage", handleStorageChangeCallback, false); $rootScope.$on('$destroy', function() { $window.removeEventListener("storage", handleStorageChangeCallback); }); } else if($window.attachEvent){ // attachEvent and detachEvent are proprietary to IE v6-10 $window.attachEvent("onstorage", handleStorageChangeCallback); $rootScope.$on('$destroy', function() { $window.detachEvent("onstorage", handleStorageChangeCallback); }); } } // Callback handler for storage changed. function handleStorageChangeCallback(e) { if (!e) { e = $window.event; } if (notify.setItem) { if (isString(e.key) && isKeyPrefixOurs(e.key)) { var key = underiveQualifiedKey(e.key); // Use timeout, to avoid using $rootScope.$apply. $timeout(function () { $rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType }); }); } } } // Return localStorageService.length // ignore keys that not owned var lengthOfLocalStorage = function(type) { var previousType = getStorageType(); try { setStorageType(type); var count = 0; var storage = $window[storageType]; for(var i = 0; i < storage.length; i++) { if(storage.key(i).indexOf(prefix) === 0 ) { count++; } } return count; } finally { setStorageType(previousType); } }; return { isSupported: browserSupportsLocalStorage, getStorageType: getStorageType, setStorageType: setStorageType, set: addToLocalStorage, add: addToLocalStorage, //DEPRECATED get: getFromLocalStorage, keys: getKeysForLocalStorage, remove: removeFromLocalStorage, clearAll: clearAllFromLocalStorage, bind: bindToScope, deriveKey: deriveQualifiedKey, underiveKey: underiveQualifiedKey, length: lengthOfLocalStorage, defaultToCookie: this.defaultToCookie, cookie: { isSupported: browserSupportsCookies, set: addToCookies, add: addToCookies, //DEPRECATED get: getFromCookies, remove: removeFromCookies, clearAll: clearAllFromCookies } }; }]; }); })(window, window.angular);