diff --git a/.versions b/.versions index e073d67..738d3b8 100644 --- a/.versions +++ b/.versions @@ -1,5 +1,5 @@ allow-deny@1.1.0 -babel-compiler@7.2.4 +babel-compiler@7.3.4 babel-runtime@1.3.0 base64@1.0.11 binary-heap@1.0.11 @@ -9,7 +9,7 @@ check@1.3.1 ddp@1.4.0 ddp-client@2.3.3 ddp-common@1.4.0 -ddp-server@2.2.0 +ddp-server@2.3.0 diff-sequence@1.1.1 dynamic-import@0.5.1 ecmascript@0.12.4 @@ -17,32 +17,32 @@ ecmascript-runtime@0.7.0 ecmascript-runtime-client@0.8.0 ecmascript-runtime-server@0.7.1 ejson@1.1.0 -fetch@0.1.0 +fetch@0.1.1 geojson-utils@1.0.10 id-map@1.1.0 inter-process-messaging@0.1.0 -local-test:ostrio:cstorage@2.2.2 +local-test:ostrio:cstorage@3.0.0 logging@1.1.20 -meteor@1.9.2 +meteor@1.9.3 minimongo@1.4.5 -modern-browsers@0.1.3 +modern-browsers@0.1.4 modules@0.13.0 modules-runtime@0.10.3 -mongo@1.6.0 -mongo-decimal@0.1.0 +mongo@1.6.2 +mongo-decimal@0.1.1 mongo-dev-server@1.1.0 mongo-id@1.0.7 -npm-mongo@3.1.1 +npm-mongo@3.1.2 ordered-dict@1.1.0 -ostrio:cstorage@2.2.2 +ostrio:cstorage@3.0.0 promise@0.11.2 random@1.1.0 -reload@1.2.0 +reload@1.3.0 retry@1.1.0 routepolicy@1.1.0 socket-stream-client@0.2.2 tinytest@1.1.0 tracker@1.2.0 underscore@1.0.10 -webapp@1.7.1 +webapp@1.7.3 webapp-hashing@1.0.9 diff --git a/client-storage.js b/client-storage.js index 0a00167..d63cff3 100644 --- a/client-storage.js +++ b/client-storage.js @@ -1,23 +1,38 @@ 'use strict'; + +var DEFAULT_TTL = 3.154e+8; // 10 years +var TTL_SUFFIX = '.___exp'; +var TTL_SUFFIX_LENGTH = TTL_SUFFIX.length; + /* -@locus Client -@class __Cookies -@param _cookies {Object|String} - Current cookies as String or Object -@summary Internal Class + * @locus Client + * @class __Cookies + * @param _cookies {String} - Current cookies as String + * @summary Internal Class */ function __Cookies(_cookies) { this.cookies = {}; - if (!_cookies || typeof _cookies !== 'string') { - _cookies = ''; + if (_cookies && typeof _cookies === 'string') { + this.init(_cookies); } +} +/* + * @locus Client + * @memberOf __Cookies + * @name init + * @param _cookies {String} - Current cookies as String + * @summary parse document.cookie string + * @returns {void 0} + */ +__Cookies.prototype.init = function (_cookies) { if (_cookies && _cookies.length) { var self = this; var i; var key; var val; - _cookies.split(/;\ */).forEach(function (pair) { + _cookies.split(/; */).forEach(function (pair) { i = pair.indexOf('='); if (i < 0) { return; @@ -39,85 +54,102 @@ function __Cookies(_cookies) { } }); } -} - +}; /* -@locus Client -@memberOf __Cookies -@name get -@param {String} key - The name of the cookie to read -@summary Read a cookie. If the cookie doesn't exist a null value will be returned. -@returns {String|null} + * @locus Client + * @memberOf __Cookies + * @name get + * @param {String} key - The name of the cookie to read + * @summary Read a cookie. If the cookie doesn't exist a void 0 (undefined) value will be returned. + * @returns {String|void 0} */ -__Cookies.prototype.get = function(key) { +__Cookies.prototype.get = function (key) { if (!key) { return void 0; } if (this.cookies.hasOwnProperty(key)) { + if (this.cookies.hasOwnProperty(key + TTL_SUFFIX)) { + var expireAt = this.cookies[key + TTL_SUFFIX]; + console.log({expireAt}); + if (expireAt && expireAt <= Date.now()) { + this.remove(key); + return void 0; + } + } return this.cookies[key]; } return void 0; }; - /* -@locus Client -@memberOf __Cookies -@name set -@param {String} key - The name of the cookie to create/overwrite -@param {String} value - The value of the cookie -@summary Create/overwrite a cookie. -@returns {Boolean} + * @locus Client + * @memberOf __Cookies + * @name set + * @param {String} key - The name of the cookie to create/overwrite + * @param {String} value - The value of the cookie + * @param {Number} ttl - Cookies TTL (e.g. max-age) in seconds + * @summary Create/overwrite a cookie. + * @returns {Boolean} */ -__Cookies.prototype.set = function(key, value) { +__Cookies.prototype.set = function (key, value, ttl) { + if (!ttl) { + ttl = DEFAULT_TTL; + } if (key) { this.cookies[key] = value; - document.cookie = escape(key) + '=' + escape(value) + '; Expires=Fri, 31 Dec 9999 23:59:59 GMT; Path=/'; + document.cookie = escape(key) + '=' + escape(value) + '; Max-Age=' + ttl + '; Path=/'; + if (ttl !== DEFAULT_TTL) { + var expireAt = +(new Date(Date.now() + (ttl * 1000))); + this.cookies[key + TTL_SUFFIX] = expireAt; + document.cookie = escape(key + TTL_SUFFIX) + '=' + expireAt + '; Max-Age=' + ttl + '; Path=/'; + } return true; } return false; }; - /* -@locus Client -@memberOf __Cookies -@name remove -@param {String} key - The name of the cookie to create/overwrite -@summary Remove a cookie(s). -@returns {Boolean} + * @locus Client + * @memberOf __Cookies + * @name remove + * @param {String} key - The name of the cookie to remove + * @summary Remove a cookie(s). + * @returns {Boolean} */ -__Cookies.prototype.remove = function(key) { - var keys = Object.keys(this.cookies); +__Cookies.prototype.remove = function (key) { if (key) { if (!this.cookies.hasOwnProperty(key)) { return false; } delete this.cookies[key]; + delete this.cookies[key + TTL_SUFFIX]; document.cookie = escape(key) + '=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/'; + document.cookie = escape(key + TTL_SUFFIX) + '=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/'; return true; - } else if (keys.length > 0 && keys[0] !== '') { - for (var i = 0; i < keys.length; i++) { - this.remove(keys[i]); + } else if (key === void 0) { + var keys = Object.keys(this.cookies); + if (keys.length > 0 && keys[0] !== '') { + for (var i = 0; i < keys.length; i++) { + this.remove(keys[i]); + } + return true; } - return true; } return false; }; - /* -@locus Client -@memberOf __Cookies -@name has -@param {String} key - The name of the cookie to check -@summary Check whether a cookie key is exists -@returns {Boolean} + * @locus Client + * @memberOf __Cookies + * @name has + * @param {String} key - The name of the cookie to check + * @summary Check whether a cookie key is exists + * @returns {Boolean} */ -__Cookies.prototype.has = function(key) { +__Cookies.prototype.has = function (key) { if (!key) { return false; } @@ -125,23 +157,22 @@ __Cookies.prototype.has = function(key) { return this.cookies.hasOwnProperty(key); }; - /* -@locus Client -@memberOf __Cookies -@name keys -@summary Returns an array of all readable cookies from this location. -@returns {[String]} + * @locus Client + * @memberOf __Cookies + * @name keys + * @summary Returns an array of Strings with all readable cookies from this location. + * @returns {[String]} */ -__Cookies.prototype.keys = function() { +__Cookies.prototype.keys = function () { return Object.keys(this.cookies); }; /* -@locus Client -@class ClientStorage -@param driver {Sting} - Preferable driver `localStorage` or `cookies` -@summary Implement boilerplate Client storage functions, localStorage with fall-back to Cookies + * @locus Client + * @class ClientStorage + * @param driver {Sting} - Preferable driver `localStorage` or `cookies` + * @summary Implement boilerplate Client storage functions, localStorage with fall-back to Cookies */ function ClientStorage(driver) { this._data = {}; @@ -155,6 +186,16 @@ function ClientStorage(driver) { case 'localStorage': if (this.LSSupport) { this.ls = window.localStorage || localStorage; + + var i = this.ls.length; + var key; + while (i--) { + key = this.ls.key(i); + if ((key.indexOf(TTL_SUFFIX, key.length - TTL_SUFFIX_LENGTH) !== -1) && this.ls.getItem(key) <= Date.now()) { + this.ls.removeItem(key); + this.ls.removeItem(key.replace(TTL_SUFFIX, '')); + } + } } else { console.warn('ClientStorage is set to "localStorage", but it is not supported on this browser'); } @@ -182,81 +223,102 @@ function ClientStorage(driver) { } } - /* -@function -@memberOf ClientStorage -@name get -@param {String} key - The name of the stored record to read -@summary Read a record. If the record doesn't exist a null value will be returned. -@returns {mixed} + * @function + * @memberOf ClientStorage + * @name get + * @param {String} key - The name of the stored record to read + * @summary Read a record. If the record doesn't exist a null value will be returned. + * @returns {mixed} */ -ClientStorage.prototype.get = function(key) { +ClientStorage.prototype.get = function (key) { if (!this.has(key)) { return void 0; } + + var expireAt = 0; + if (this.LSSupport) { + expireAt = this.__unescape(this.ls.getItem(key + TTL_SUFFIX)); + if (expireAt && expireAt <= Date.now()) { + this.ls.removeItem(key); + this.ls.removeItem(key + TTL_SUFFIX); + return void 0; + } return this.__unescape(this.ls.getItem(key)); } else if (this.cookies) { return this.__unescape(this.cookies.get(key)); } + + expireAt = this._data[key + TTL_SUFFIX]; + if (expireAt && expireAt <= Date.now()) { + delete this._data[key]; + delete this._data[key + TTL_SUFFIX]; + return void 0; + } return this._data[key]; }; - /* -@function -@memberOf ClientStorage -@name set -@param {String} key - The name of the key to create/overwrite -@param {mixed} value - The value -@summary Create/overwrite a value in storage. -@returns {Boolean} + * @function + * @memberOf ClientStorage + * @name set + * @param {String} key - The name of the key to create/overwrite + * @param {mixed} value - The value + * @param {Number} ttl - [OPTIONAL] Record TTL in seconds + * @summary Create/overwrite a value in storage. + * @returns {Boolean} */ -ClientStorage.prototype.set = function(key, value) { +ClientStorage.prototype.set = function (key, value, ttl) { if (this.LSSupport) { this.ls.setItem(key, this.__escape(value)); + if (ttl) { + this.ls.setItem(key + TTL_SUFFIX, +(new Date(Date.now() + (ttl * 1000)))); + } } else if (this.cookies) { - this.cookies.set(key, this.__escape(value)); + this.cookies.set(key, this.__escape(value), ttl); } else { this._data[key] = value; + if (ttl) { + this._data[key + TTL_SUFFIX] = +(new Date(Date.now() + (ttl * 1000))); + } } return true; }; - /* -@function -@memberOf ClientStorage -@name remove -@param {String} key - The name of the record to create/overwrite -@summary Remove a record. -@returns {Boolean} + * @function + * @memberOf ClientStorage + * @name remove + * @param {String} key - The name of the record to create/overwrite + * @summary Remove a record. + * @returns {Boolean} */ -ClientStorage.prototype.remove = function(key) { +ClientStorage.prototype.remove = function (key) { if (key && this.has(key)) { if (this.LSSupport) { this.ls.removeItem(key); + this.ls.removeItem(key + TTL_SUFFIX); return true; } else if (this.cookies) { - return this.cookies.remove(key, null, window.location.host); + return this.cookies.remove(key); } delete this._data[key]; + delete this._data[key + TTL_SUFFIX]; return true; } return false; }; - /* -@function -@memberOf ClientStorage -@name has -@param {String} key - The name of the record to check -@summary Check if record exists -@returns {Boolean} + * @function + * @memberOf ClientStorage + * @name has + * @param {String} key - The name of the record to check + * @summary Check if record exists + * @returns {Boolean} */ -ClientStorage.prototype.has = function(key) { +ClientStorage.prototype.has = function (key) { if (this.LSSupport) { return !!this.ls.getItem(key); } else if (this.cookies) { @@ -265,40 +327,44 @@ ClientStorage.prototype.has = function(key) { return this._data.hasOwnProperty(key); }; - /* -@function -@memberOf ClientStorage -@name keys -@summary Returns all storage keys -@returns {[String]]} + * @function + * @memberOf ClientStorage + * @name keys + * @summary Returns all storage keys + * @returns {[String]]} */ -ClientStorage.prototype.keys = function() { +ClientStorage.prototype.keys = function () { if (this.LSSupport) { var i = this.ls.length; var results = []; + var key; while (i--) { - results.push(this.ls.key(i)); + key = this.ls.key(i); + if (key.indexOf(TTL_SUFFIX, key.length - TTL_SUFFIX_LENGTH) === -1) { + results.push(this.ls.key(i)); + } } return results; } else if (this.cookies) { return this.cookies.keys(); } - return Object.keys(this._data); + return Object.keys(this._data).filter(function (_key) { + return _key.indexOf(TTL_SUFFIX, _key.length - TTL_SUFFIX_LENGTH) === -1; + }); }; - /* -@function -@memberOf ClientStorage -@name empty -@summary Empty storage (remove all key/value pairs) -@returns {Boolean} + * @function + * @memberOf ClientStorage + * @name empty + * @summary Empty storage (remove all key/value pairs) + * @returns {Boolean} */ -ClientStorage.prototype.empty = function() { +ClientStorage.prototype.empty = function () { if (this.LSSupport && this.ls.length > 0) { var self = this; - this.keys().forEach(function(key) { + this.keys().forEach(function (key) { return self.remove(key); }); return true; @@ -311,13 +377,12 @@ ClientStorage.prototype.empty = function() { return false; }; - /* -@function -@memberOf ClientStorage -@name __escape + * @function + * @memberOf ClientStorage + * @name __escape */ -ClientStorage.prototype.__escape = function(value) { +ClientStorage.prototype.__escape = function (value) { try { return JSON.stringify(value); } catch (e) { @@ -330,11 +395,11 @@ ClientStorage.prototype.__escape = function(value) { }; /* -@function -@memberOf ClientStorage -@name __unescape + * @function + * @memberOf ClientStorage + * @name __unescape */ -ClientStorage.prototype.__unescape = function(value) { +ClientStorage.prototype.__unescape = function (value) { try { return JSON.parse(value); } catch (e) { @@ -342,13 +407,12 @@ ClientStorage.prototype.__unescape = function(value) { } }; - /* -@memberOf ClientStorage -@name LSSupport -@summary Test browser for localStorage support + * @memberOf ClientStorage + * @name LSSupport + * @summary Test browser for localStorage support */ -ClientStorage.prototype.LSSupport = (function() { +ClientStorage.prototype.LSSupport = (function () { try { var support = 'localStorage' in window && window.localStorage !== null; if (support) { diff --git a/package.js b/package.js index fadf4a1..680d8dc 100755 --- a/package.js +++ b/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'ostrio:cstorage', - version: '2.2.2', + version: '3.0.0', summary: 'Bulletproof persistent Client (Browser) storage, works with disabled Cookies and/or localStorage', git: 'https://github.com/VeliovGroup/Client-Storage', documentation: 'README.md' @@ -9,7 +9,7 @@ Package.describe({ Package.onUse((api) => { api.versionsFrom('1.4'); api.use('ecmascript', 'client'); - api.mainModule('cstorage.js', 'client'); + api.mainModule('client-storage.js', 'client'); }); Package.onTest((api) => { @@ -17,7 +17,3 @@ Package.onTest((api) => { api.use(['ecmascript', 'ostrio:cstorage'], 'client'); api.addFiles('client-storage-tests.js', 'client'); }); - -Npm.depends({ - ClientStorage: '2.1.4' -}); diff --git a/package.json b/package.json index 1234c7b..c07ef4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ClientStorage", - "version": "2.1.4", + "version": "3.0.0", "description": "Bulletproof persistent browser storage, works with disabled Cookies and/or localStorage", "main": "./client-storage.js", "scripts": {