From c00b29d0baa6b3b4ccb38b56a959968d990fc367 Mon Sep 17 00:00:00 2001 From: Mathieu Ghaleb Date: Thu, 16 Oct 2014 18:39:50 +0200 Subject: [PATCH 1/9] - 0.4.0 first implementation - Basil.utils.each - Basil.utils.tryEach --- README.md | 13 ++-- build/basil.js | 176 +++++++++++++++++++++++---------------------- build/basil.min.js | 2 +- src/basil.js | 176 +++++++++++++++++++++++---------------------- src/basil.set.js | 21 +++--- test/index.html | 19 +++-- test/test.js | 21 +++--- 7 files changed, 220 insertions(+), 208 deletions(-) diff --git a/README.md b/README.md index c2867ed..0df2028 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ basil.remove('foo'); // remove 'foo' value // advanced methods basil.check('local'); // boolean. Test if localStorage is available -basil.reset(); // reset all stored values under namespace for current storage +basil.reset(); // reset all stored values under namespace ``` @@ -34,8 +34,9 @@ basil = new window.Basil(options); // set 'bar' value under 'foo' key in localStorage basil.set('foo', 'bar', { 'storages': ['local'] }); -// set 'bar' value under 'foo' key in localStorage AND cookie -basil.set('foo', 'bar', { 'storages': ['local', 'cookie'] }); +// set 'bar' value under 'foo' key. +// try first to store it into cookies and if not possible into localStorage +basil.set('foo', 'quux', { 'storages': ['cookie', 'local'] }); // set 'xyz' value under 'abc' key in memory basil.set('abc', 'xyz', { 'storages': ['memory'] }); @@ -44,7 +45,7 @@ basil.set('abc', 'xyz', { 'storages': ['memory'] }); basil.keys(); // returns ['foo', 'abc'] basic.keys({ 'storages': ['memory'] }); // returns ['abc'] -// retrive keys map +// retrieve keys map basil.keysMap(); // returns { 'foo': ['local', 'cookie'], 'abc': ['memory'] } basic.keysMap({ 'storages': ['memory'] }); // returns { 'abc': ['memory'] } @@ -111,10 +112,6 @@ options = { // default: `['local', 'cookie', 'session', 'memory']` storages: ['cookie', 'local'] - // storage. Specify the default storage to use - // default: detect best available storage among the supported ones - storage: 'cookie' - // expireDays. Default number of days before cookies expiration // default: 365 expireDays: 31 diff --git a/build/basil.js b/build/basil.js index bad9879..0d1d3b3 100644 --- a/build/basil.js +++ b/build/basil.js @@ -5,7 +5,7 @@ }; // Version - Basil.version = '0.3.4'; + Basil.version = '0.4.0'; // Utils Basil.utils = { @@ -18,20 +18,45 @@ } return destination; }, - isArray: function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; + each: function (obj, fnIterator, context) { + if (this.isArray(obj)) { + for (var i = 0; i < obj.length; i++) + if (fnIterator.call(context, obj[i], i) === false) return; + } else if (obj) { + for (var key in obj) + if (fnIterator.call(context, obj[key], key) === false) return; + } + }, + tryEach: function (obj, fnIterator, fnError, context) { + this.each(obj, function (value, key) { + try { + return fnIterator.call(context, value, key); + } catch (error) { + if (this.isFunction(fnError)) + fnError.call(context, value, key, error); + } + }, this); }, registerPlugin: function (methods) { Basil.plugins = this.extend(methods, Basil.plugins); } }; + // Add some isType methods: isArguments, isBoolean, isFunction, isString, isArray, isNumber, isDate, isRegExp. + var types = ['Arguments', 'Boolean', 'Function', 'String', 'Array', 'Number', 'Date', 'RegExp'] + for (var i = 0; i < types.length; i++) { + Basil.utils['is' + types[i]] = (function (type) { + return function (obj) { + return Object.prototype.toString.call(obj) === '[object ' + type + ']'; + }; + })(types[i]); + } + // Plugins Basil.plugins = {}; // Options Basil.options = Basil.utils.extend({ namespace: 'b45i1', - storage: null, storages: ['local', 'cookie', 'session', 'memory'], expireDays: 365 }, window.Basil ? window.Basil.options : {}); @@ -43,21 +68,17 @@ .substring(7), _storages = {}, _toStoragesArray = function (storages) { - if (!storages) - return null; - return Basil.utils.isArray(storages) ? storages : [storages]; + if (Basil.utils.isArray(storages)) + return storages; + return Basil.utils.isString(storages) ? [storages] : []; }, - _toStoredKey = function (namespace, name) { + _toStoredKey = function (namespace, path) { var key = ''; - if (typeof name === 'string') - key = namespace + ':' + name; - else if (Basil.utils.isArray(name)) { - key = namespace; - for (var i = 0; i < name.length; i++) - if (name[i]) - key += ':' + name[i]; - } - return key; + if (Basil.utils.isString(path) && path.length) + path = [path]; + if (Basil.utils.isArray(path) && path.length) + key = path.join(':'); + return key && namespace ? namespace + ':' + key : key; }, _toKeyName = function (namespace, name) { if (!namespace) @@ -157,10 +178,12 @@ return navigator.cookieEnabled; }, set: function (name, value, options) { + options = options || {}; if (!name) return; - options = options || {}; + var cookie = name + '=' + value; + if (options.expireDays) { var date = new Date(); date.setTime(date.getTime() + (options.expireDays * 24 * 60 * 60 * 1000)); @@ -184,6 +207,7 @@ return; // remove cookie from main domain this.set(name, '', { expireDays: -1 }); + // remove cookie from upper domains var domainParts = document.domain.split('.'); for (var i = domainParts.length - 1; i > 0; i--) { @@ -214,80 +238,63 @@ return { init: function (options) { - this.options = Basil.utils.extend({}, Basil.options, options); - this.supportedStorages = {}; - for (var i = 0, storage; i < this.options.storages.length; i++) { - storage = this.options.storages[i]; - if (_storages.hasOwnProperty(storage)) - this.supportedStorages[storage] = _storages[storage]; - } - this.defaultStorage = this.check(this.options.storage) ? this.options.storage : this.detect(); + this.setOptions(options); return this; }, - detect: function () { - for (var storage in this.supportedStorages) - if (this.check(storage)) - return storage; - return null; + setOptions: function (options) { + this.options = Basil.utils.extend({}, this.options || Basil.options, options); + }, + support: function (storage) { + return _storages.hasOwnProperty(storage); }, check: function (storage) { - storage = storage || this.defaultStorage; - if (this.supportedStorages.hasOwnProperty(storage)) - return this.supportedStorages[storage].check(); + if (this.support(storage)) + return _storages[storage].check(); return false; }, set: function (name, value, options) { - options = options || {}; - if (!(name = _toStoredKey(options.namespace || this.options.namespace, name))) + options = Basil.utils.extend({}, this.options, options); + if (!(name = _toStoredKey(options.namespace, name))) return; value = _toStoredValue(value); - options = Basil.utils.extend({ - expireDays: this.options.expireDays - }, options); - var storages = _toStoragesArray(options.storages) || [this.defaultStorage]; - for (var i = 0, storage; i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { _storages[storage].set(name, value, options); - } + return false; // break; + }, function (storage, index, error) { + if (this.support(storage)) + _storages[storage].remove(name); + }, this); }, get: function (name, options) { - options = options || {}; - if (!(name = _toStoredKey(options.namespace || this.options.namespace, name))) + options = Basil.utils.extend({}, this.options, options); + if (!(name = _toStoredKey(options.namespace, name))) return null; - var value = null, - storages = _toStoragesArray(options.storages) || [this.defaultStorage]; - for (var i = 0, storage; value === null && i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; - value = _fromStoredValue(_storages[storage].get(name)); - } + var value = null; + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { + if (value !== null) + return false; // break + if (this.support(storage)) + value = _fromStoredValue(_storages[storage].get(name, options)); + }, function (storage, index, error) { + value = _storages[storage].get(name, options) || null; + }, this); return value; }, remove: function (name, options) { - options = options || {}; - if (!(name = _toStoredKey(options.namespace || this.options.namespace, name))) + options = Basil.utils.extend({}, this.options, options); + if (!(name = _toStoredKey(options.namespace, name))) return null; - var storages = _toStoragesArray(options.storages) || [this.defaultStorage]; - for (var i = 0, storage; i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; - _storages[storage].remove(name); - } + Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + if (this.support(storage)) + _storages[storage].remove(name); + }, this); }, reset: function (options) { - options = options || {}; - var storages = _toStoragesArray(options.storages) || [this.defaultStorage], - namespace = options.namespace || this.options.namespace; - for (var i = 0, storage; i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; - _storages[storage].reset(namespace); - } + options = Basil.utils.extend({}, this.options, options); + Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + if (this.support(storage)) + _storages[storage].reset(options.namespace); + }, this); }, keys: function (options) { options = options || {}; @@ -297,21 +304,16 @@ return keys; }, keysMap: function (options) { - options = options || {}; - var map = {}, - storages = _toStoragesArray(options.storages) || this.options.storages, - namespace = options.namespace || this.options.namespace; - for (var i = 0, storage, storageKeys; i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; - storageKeys = _storages[storage].keys(namespace); - for (var j = 0, key; j < storageKeys.length; j++) { - key = storageKeys[j]; - map[key] = map[key] instanceof Array ? map[key] : []; + options = Basil.utils.extend({}, this.options, options); + var map = {}; + Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + if (!this.support(storage)) + return true; // continue + Basil.utils.each(_storages[storage].keys(options.namespace), function (key) { + map[key] = Basil.utils.isArray(map[key]) ? map[key] : []; map[key].push(storage); - } - } + }, this); + }, this); return map; }, // Access to native storages, without namespace or basil value decoration diff --git a/build/basil.min.js b/build/basil.min.js index e3ac8d1..2c66780 100644 --- a/build/basil.min.js +++ b/build/basil.min.js @@ -1 +1 @@ -!function(){var e=function(t){return e.utils.extend(e.plugins,(new e.Storage).init(t))};e.version="0.3.4",e.utils={extend:function(){for(var e="object"==typeof arguments[0]?arguments[0]:{},t=1;t0;n--)this.set(e,"",{expireDays:-1,domain:"."+t.slice(-n).join(".")})}},reset:function(e){for(var t,n,i=document.cookie.split(";"),s=0;s0;n--)this.set(t,"",{expireDays:-1,domain:"."+e.slice(-n).join(".")})}},reset:function(t){for(var e,n,i=document.cookie.split(";"),s=0;s 0; i--) { @@ -214,80 +238,63 @@ return { init: function (options) { - this.options = Basil.utils.extend({}, Basil.options, options); - this.supportedStorages = {}; - for (var i = 0, storage; i < this.options.storages.length; i++) { - storage = this.options.storages[i]; - if (_storages.hasOwnProperty(storage)) - this.supportedStorages[storage] = _storages[storage]; - } - this.defaultStorage = this.check(this.options.storage) ? this.options.storage : this.detect(); + this.setOptions(options); return this; }, - detect: function () { - for (var storage in this.supportedStorages) - if (this.check(storage)) - return storage; - return null; + setOptions: function (options) { + this.options = Basil.utils.extend({}, this.options || Basil.options, options); + }, + support: function (storage) { + return _storages.hasOwnProperty(storage); }, check: function (storage) { - storage = storage || this.defaultStorage; - if (this.supportedStorages.hasOwnProperty(storage)) - return this.supportedStorages[storage].check(); + if (this.support(storage)) + return _storages[storage].check(); return false; }, set: function (name, value, options) { - options = options || {}; - if (!(name = _toStoredKey(options.namespace || this.options.namespace, name))) + options = Basil.utils.extend({}, this.options, options); + if (!(name = _toStoredKey(options.namespace, name))) return; value = _toStoredValue(value); - options = Basil.utils.extend({ - expireDays: this.options.expireDays - }, options); - var storages = _toStoragesArray(options.storages) || [this.defaultStorage]; - for (var i = 0, storage; i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { _storages[storage].set(name, value, options); - } + return false; // break; + }, function (storage, index, error) { + if (this.support(storage)) + _storages[storage].remove(name); + }, this); }, get: function (name, options) { - options = options || {}; - if (!(name = _toStoredKey(options.namespace || this.options.namespace, name))) + options = Basil.utils.extend({}, this.options, options); + if (!(name = _toStoredKey(options.namespace, name))) return null; - var value = null, - storages = _toStoragesArray(options.storages) || [this.defaultStorage]; - for (var i = 0, storage; value === null && i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; - value = _fromStoredValue(_storages[storage].get(name)); - } + var value = null; + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { + if (value !== null) + return false; // break + if (this.support(storage)) + value = _fromStoredValue(_storages[storage].get(name, options)); + }, function (storage, index, error) { + value = _storages[storage].get(name, options) || null; + }, this); return value; }, remove: function (name, options) { - options = options || {}; - if (!(name = _toStoredKey(options.namespace || this.options.namespace, name))) + options = Basil.utils.extend({}, this.options, options); + if (!(name = _toStoredKey(options.namespace, name))) return null; - var storages = _toStoragesArray(options.storages) || [this.defaultStorage]; - for (var i = 0, storage; i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; - _storages[storage].remove(name); - } + Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + if (this.support(storage)) + _storages[storage].remove(name); + }, this); }, reset: function (options) { - options = options || {}; - var storages = _toStoragesArray(options.storages) || [this.defaultStorage], - namespace = options.namespace || this.options.namespace; - for (var i = 0, storage; i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; - _storages[storage].reset(namespace); - } + options = Basil.utils.extend({}, this.options, options); + Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + if (this.support(storage)) + _storages[storage].reset(options.namespace); + }, this); }, keys: function (options) { options = options || {}; @@ -297,21 +304,16 @@ return keys; }, keysMap: function (options) { - options = options || {}; - var map = {}, - storages = _toStoragesArray(options.storages) || this.options.storages, - namespace = options.namespace || this.options.namespace; - for (var i = 0, storage, storageKeys; i < storages.length; i++) { - storage = storages[i]; - if (!this.check(storage)) - continue; - storageKeys = _storages[storage].keys(namespace); - for (var j = 0, key; j < storageKeys.length; j++) { - key = storageKeys[j]; - map[key] = map[key] instanceof Array ? map[key] : []; + options = Basil.utils.extend({}, this.options, options); + var map = {}; + Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + if (!this.support(storage)) + return true; // continue + Basil.utils.each(_storages[storage].keys(options.namespace), function (key) { + map[key] = Basil.utils.isArray(map[key]) ? map[key] : []; map[key].push(storage); - } - } + }, this); + }, this); return map; }, // Access to native storages, without namespace or basil value decoration diff --git a/src/basil.set.js b/src/basil.set.js index 446b994..824c827 100644 --- a/src/basil.set.js +++ b/src/basil.set.js @@ -9,7 +9,7 @@ }(function (Basil) { var BasilSet = function () { - var _indexOf = function(array, item) { + var _indexOf = function (array, item) { for (var i = 0, length = array.length; i < length; i++) { if (array[i] === item) @@ -21,15 +21,12 @@ _contains = function (array, item) { return _indexOf(array, item) >= 0; }, - _isArray = function(arg) { - return Object.prototype.toString.call(arg) === '[object Array]'; - }, //http://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript _shuffle = function (o){ - for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); + for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); return o; }, - _union = function() { + _union = function () { var args = arguments[0], allElements = [], uniqElements = []; @@ -43,7 +40,7 @@ return uniqElements; }, - _difference = function() { + _difference = function () { arguments = arguments[0]; var firstArray = arguments[0], otherArrays = [], diffArray = []; for (var i = 1, length = arguments.length; i < length; i++) { @@ -58,7 +55,7 @@ return diffArray; }, - _intersection = function() { + _intersection = function () { arguments = arguments[0]; var result = [], firstArray = arguments[0], argsLength = arguments.length; @@ -86,7 +83,7 @@ sadd: function (key, members) { var set = this.get(key) || [], addedItems = 0; - if (!_isArray(members)) { + if (!Basil.utils.isArray(members)) { members = [members]; } //Accept array or single. @@ -242,7 +239,7 @@ return 0; } - if (!_isArray(members)) { + if (!Basil.utils.isArray(members)) { members = [members]; } //Accept array or single. @@ -273,8 +270,8 @@ args.push(this.smembers(arguments[i])); } var union = _union(args); - this.set(key, union); - return union.length; + this.set(key, union); + return union.length; }, sscan: function () { throw new Error('not implemented yet'); diff --git a/test/index.html b/test/index.html index a56385c..ac9e8a7 100644 --- a/test/index.html +++ b/test/index.html @@ -5,12 +5,21 @@ - - +

Welcome to Basil

+ - + \ No newline at end of file diff --git a/test/test.js b/test/test.js index eddec3e..a0d5dad 100644 --- a/test/test.js +++ b/test/test.js @@ -10,15 +10,20 @@ }); it('should have a valid unified API', function () { var basil = new window.Basil(); - expect(basil.detect).to.be.a('function'); + expect(basil.init).to.be.a('function'); + expect(basil.setOptions).to.be.a('function'); + expect(basil.support).to.be.a('function'); expect(basil.check).to.be.a('function'); expect(basil.get).to.be.a('function'); expect(basil.set).to.be.a('function'); expect(basil.remove).to.be.a('function'); expect(basil.reset).to.be.a('function'); + expect(basil.keys).to.be.a('function'); + expect(basil.keysMap).to.be.a('function'); }); it('should allow access to native storages', function () { var basil = new window.Basil(); + expect(basil.memory).to.be.an('object'); expect(basil.cookie).to.be.an('object'); expect(basil.localStorage).to.be.an('object'); expect(basil.sessionStorage).to.be.an('object'); @@ -27,12 +32,12 @@ describe('Options handling', function () { it('should handle storages option', function () { - var basil = new window.Basil({storages: ['cookie']}); - expect(basil.supportedStorages).to.have.key(['cookie']); + var basil = new window.Basil({ storages: ['cookie'] }); + expect(basil.options.storages).to.eql(['cookie']); }); - it('should handle storage option', function () { - var basil = new window.Basil({storages: ['cookie']}); - expect(basil.defaultStorage).to.be('cookie'); + it('should handle namespace option', function () { + var basil = new window.Basil({ namespace: 'foo' }); + expect(basil.options.namespace).to.be('foo'); }); }); @@ -110,7 +115,7 @@ expect(basil.keys()).to.eql(['foo', 'bar', 'baz']); expect(basil.keysMap()).to.eql({ 'foo': ['local', 'session'], - 'bar': ['cookie', 'session'], + 'bar': ['cookie'], 'baz': ['session'] }); basil.reset(); @@ -130,7 +135,7 @@ }); expect(basil.keys({ namespace: 'second' })).to.eql(['bar', 'baz']); expect(basil.keysMap({ namespace: 'second' })).to.eql({ - 'bar': ['cookie', 'session'], + 'bar': ['cookie'], 'baz': ['session'] }); expect(basil.keys({ namespace: 'third' })).to.eql([]); From f65b4fff0b45c36ef34feb395598ee1df4e81d48 Mon Sep 17 00:00:00 2001 From: Mathieu Ghaleb Date: Thu, 16 Oct 2014 18:47:45 +0200 Subject: [PATCH 2/9] - update bower and package version number --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index caa72f8..6938755 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "basil.js", - "version": "0.3.4", + "version": "0.4.0", "homepage": "https://github.com/Wisembly/basil.js", "authors": [ "Mathieu Ghaleb " diff --git a/package.json b/package.json index 6762ace..40b5edd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "basil.js", - "version": "0.3.4", + "version": "0.4.0", "scripts": { "test": "node node_modules/karma/bin/karma start test/karma.config.js", "test-plugins": "node node_modules/karma/bin/karma start test/karma.plugins.config.js", From 127e002a95e8aef8c2bb08a3b185051eb6deebc6 Mon Sep 17 00:00:00 2001 From: Mathieu Ghaleb Date: Fri, 17 Oct 2014 12:41:18 +0200 Subject: [PATCH 3/9] - fix SecurityError with disabled cookies --- src/basil.js | 69 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/src/basil.js b/src/basil.js index 0d1d3b3..a2d85b2 100644 --- a/src/basil.js +++ b/src/basil.js @@ -32,8 +32,11 @@ try { return fnIterator.call(context, value, key); } catch (error) { - if (this.isFunction(fnError)) - fnError.call(context, value, key, error); + if (this.isFunction(fnError)) { + try { + fnError.call(context, value, key, error); + } catch (error) {} + } } }, this); }, @@ -92,13 +95,13 @@ return JSON.parse(value); }; - // local storage - _storages.local = { - engine: window.localStorage, + // HTML5 web storage interface + var webStorageInterface = { + engine: null, check: function () { try { - this.engine.setItem(_salt, true); - this.engine.removeItem(_salt); + window[this.engine].setItem(_salt, true); + window[this.engine].removeItem(_salt); } catch (e) { return false; } @@ -107,17 +110,17 @@ set: function (name, value, options) { if (!name) return; - this.engine.setItem(name, value); + window[this.engine].setItem(name, value); }, get: function (name) { - return this.engine.getItem(name); + return window[this.engine].getItem(name); }, remove: function (name) { - this.engine.removeItem(name); + window[this.engine].removeItem(name); }, reset: function (namespace) { - for (var i = 0, key; i < this.engine.length; i++) { - key = this.engine.key(i); + for (var i = 0, key; i < window[this.engine].length; i++) { + key = window[this.engine].key(i); if (!namespace || key.indexOf(namespace) === 0) { this.remove(key); i--; @@ -126,8 +129,8 @@ }, keys: function (namespace) { var keys = []; - for (var i = 0, key; i < this.engine.length; i++) { - key = this.engine.key(i); + for (var i = 0, key; i < window[this.engine].length; i++) { + key = window[this.engine].key(i); if (!namespace || key.indexOf(namespace) === 0) keys.push(_toKeyName(namespace, key)); } @@ -135,9 +138,13 @@ } }; + // local storage + _storages.local = Basil.utils.extend({}, webStorageInterface, { + engine: 'localStorage' + }); // session storage - _storages.session = Basil.utils.extend({}, _storages.local, { - engine: window.sessionStorage + _storages.session = Basil.utils.extend({}, webStorageInterface, { + engine: 'sessionStorage' }); // memory storage @@ -178,12 +185,12 @@ return navigator.cookieEnabled; }, set: function (name, value, options) { + if (!this.check()) + throw 'SecurityError: cookies are disabled'; options = options || {}; if (!name) return; - var cookie = name + '=' + value; - if (options.expireDays) { var date = new Date(); date.setTime(date.getTime() + (options.expireDays * 24 * 60 * 60 * 1000)); @@ -194,7 +201,9 @@ document.cookie = cookie + '; path=/'; }, get: function (name) { - var cookies = document.cookie.split(';'); + if (!this.check()) + throw 'SecurityError: cookies are disabled'; + var cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); if (cookie.indexOf(name + '=') === 0) @@ -203,6 +212,8 @@ return null; }, remove: function (name) { + if (!this.check()) + throw 'SecurityError: cookies are disabled'; if (!name) return; // remove cookie from main domain @@ -215,7 +226,9 @@ } }, reset: function (namespace) { - var cookies = document.cookie.split(';'); + if (!this.check()) + throw 'SecurityError: cookies are disabled'; + var cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie, key; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); key = cookie.substr(0, cookie.indexOf('=')); @@ -224,8 +237,10 @@ } }, keys: function (namespace) { + if (!this.check()) + throw 'SecurityError: cookies are disabled'; var keys = [], - cookies = document.cookie.split(';'); + cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie, key; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); key = cookie.substr(0, cookie.indexOf('=')); @@ -284,17 +299,17 @@ options = Basil.utils.extend({}, this.options, options); if (!(name = _toStoredKey(options.namespace, name))) return null; - Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { if (this.support(storage)) _storages[storage].remove(name); - }, this); + }, null, this); }, reset: function (options) { options = Basil.utils.extend({}, this.options, options); - Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { if (this.support(storage)) _storages[storage].reset(options.namespace); - }, this); + }, null, this); }, keys: function (options) { options = options || {}; @@ -306,14 +321,14 @@ keysMap: function (options) { options = Basil.utils.extend({}, this.options, options); var map = {}; - Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { if (!this.support(storage)) return true; // continue Basil.utils.each(_storages[storage].keys(options.namespace), function (key) { map[key] = Basil.utils.isArray(map[key]) ? map[key] : []; map[key].push(storage); }, this); - }, this); + }, null, this); return map; }, // Access to native storages, without namespace or basil value decoration From 75888ca4f06a0eb09d4cf10bf2e61f38dd553805 Mon Sep 17 00:00:00 2001 From: Mathieu Ghaleb Date: Fri, 17 Oct 2014 14:52:35 +0200 Subject: [PATCH 4/9] - build --- build/basil.js | 69 ++++++++++++++++++++++++++++------------------ build/basil.min.js | 2 +- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/build/basil.js b/build/basil.js index 0d1d3b3..a2d85b2 100644 --- a/build/basil.js +++ b/build/basil.js @@ -32,8 +32,11 @@ try { return fnIterator.call(context, value, key); } catch (error) { - if (this.isFunction(fnError)) - fnError.call(context, value, key, error); + if (this.isFunction(fnError)) { + try { + fnError.call(context, value, key, error); + } catch (error) {} + } } }, this); }, @@ -92,13 +95,13 @@ return JSON.parse(value); }; - // local storage - _storages.local = { - engine: window.localStorage, + // HTML5 web storage interface + var webStorageInterface = { + engine: null, check: function () { try { - this.engine.setItem(_salt, true); - this.engine.removeItem(_salt); + window[this.engine].setItem(_salt, true); + window[this.engine].removeItem(_salt); } catch (e) { return false; } @@ -107,17 +110,17 @@ set: function (name, value, options) { if (!name) return; - this.engine.setItem(name, value); + window[this.engine].setItem(name, value); }, get: function (name) { - return this.engine.getItem(name); + return window[this.engine].getItem(name); }, remove: function (name) { - this.engine.removeItem(name); + window[this.engine].removeItem(name); }, reset: function (namespace) { - for (var i = 0, key; i < this.engine.length; i++) { - key = this.engine.key(i); + for (var i = 0, key; i < window[this.engine].length; i++) { + key = window[this.engine].key(i); if (!namespace || key.indexOf(namespace) === 0) { this.remove(key); i--; @@ -126,8 +129,8 @@ }, keys: function (namespace) { var keys = []; - for (var i = 0, key; i < this.engine.length; i++) { - key = this.engine.key(i); + for (var i = 0, key; i < window[this.engine].length; i++) { + key = window[this.engine].key(i); if (!namespace || key.indexOf(namespace) === 0) keys.push(_toKeyName(namespace, key)); } @@ -135,9 +138,13 @@ } }; + // local storage + _storages.local = Basil.utils.extend({}, webStorageInterface, { + engine: 'localStorage' + }); // session storage - _storages.session = Basil.utils.extend({}, _storages.local, { - engine: window.sessionStorage + _storages.session = Basil.utils.extend({}, webStorageInterface, { + engine: 'sessionStorage' }); // memory storage @@ -178,12 +185,12 @@ return navigator.cookieEnabled; }, set: function (name, value, options) { + if (!this.check()) + throw 'SecurityError: cookies are disabled'; options = options || {}; if (!name) return; - var cookie = name + '=' + value; - if (options.expireDays) { var date = new Date(); date.setTime(date.getTime() + (options.expireDays * 24 * 60 * 60 * 1000)); @@ -194,7 +201,9 @@ document.cookie = cookie + '; path=/'; }, get: function (name) { - var cookies = document.cookie.split(';'); + if (!this.check()) + throw 'SecurityError: cookies are disabled'; + var cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); if (cookie.indexOf(name + '=') === 0) @@ -203,6 +212,8 @@ return null; }, remove: function (name) { + if (!this.check()) + throw 'SecurityError: cookies are disabled'; if (!name) return; // remove cookie from main domain @@ -215,7 +226,9 @@ } }, reset: function (namespace) { - var cookies = document.cookie.split(';'); + if (!this.check()) + throw 'SecurityError: cookies are disabled'; + var cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie, key; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); key = cookie.substr(0, cookie.indexOf('=')); @@ -224,8 +237,10 @@ } }, keys: function (namespace) { + if (!this.check()) + throw 'SecurityError: cookies are disabled'; var keys = [], - cookies = document.cookie.split(';'); + cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie, key; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); key = cookie.substr(0, cookie.indexOf('=')); @@ -284,17 +299,17 @@ options = Basil.utils.extend({}, this.options, options); if (!(name = _toStoredKey(options.namespace, name))) return null; - Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { if (this.support(storage)) _storages[storage].remove(name); - }, this); + }, null, this); }, reset: function (options) { options = Basil.utils.extend({}, this.options, options); - Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { if (this.support(storage)) _storages[storage].reset(options.namespace); - }, this); + }, null, this); }, keys: function (options) { options = options || {}; @@ -306,14 +321,14 @@ keysMap: function (options) { options = Basil.utils.extend({}, this.options, options); var map = {}; - Basil.utils.each(_toStoragesArray(options.storages), function (storage) { + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { if (!this.support(storage)) return true; // continue Basil.utils.each(_storages[storage].keys(options.namespace), function (key) { map[key] = Basil.utils.isArray(map[key]) ? map[key] : []; map[key].push(storage); }, this); - }, this); + }, null, this); return map; }, // Access to native storages, without namespace or basil value decoration diff --git a/build/basil.min.js b/build/basil.min.js index 2c66780..f537aa0 100644 --- a/build/basil.min.js +++ b/build/basil.min.js @@ -1 +1 @@ -!function(){var t=function(e){return t.utils.extend(t.plugins,(new t.Storage).init(e))};t.version="0.4.0",t.utils={extend:function(){for(var t="object"==typeof arguments[0]?arguments[0]:{},e=1;e0;n--)this.set(t,"",{expireDays:-1,domain:"."+e.slice(-n).join(".")})}},reset:function(t){for(var e,n,i=document.cookie.split(";"),s=0;s0;n--)this.set(e,"",{expireDays:-1,domain:"."+t.slice(-n).join(".")})}},reset:function(e){if(!this.check())throw"SecurityError: cookies are disabled";for(var t,n,i=document.cookie?document.cookie.split(";"):[],r=0;r Date: Mon, 20 Oct 2014 11:56:17 +0200 Subject: [PATCH 5/9] - native storages are now instantiating window.Basil --- src/basil.js | 57 +++++++++++++++++++++---------------------------- test/index.html | 7 +++++- test/test.js | 34 +++++++++++++---------------- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/src/basil.js b/src/basil.js index a2d85b2..2033c63 100644 --- a/src/basil.js +++ b/src/basil.js @@ -1,7 +1,7 @@ (function () { // Basil var Basil = function (options) { - return Basil.utils.extend(Basil.plugins, new Basil.Storage().init(options)); + return Basil.utils.extend({}, Basil.plugins, new Basil.Storage().init(options)); }; // Version @@ -186,7 +186,7 @@ }, set: function (name, value, options) { if (!this.check()) - throw 'SecurityError: cookies are disabled'; + throw Error('cookies are disabled'); options = options || {}; if (!name) return; @@ -202,7 +202,7 @@ }, get: function (name) { if (!this.check()) - throw 'SecurityError: cookies are disabled'; + throw Error('cookies are disabled'); var cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); @@ -212,10 +212,6 @@ return null; }, remove: function (name) { - if (!this.check()) - throw 'SecurityError: cookies are disabled'; - if (!name) - return; // remove cookie from main domain this.set(name, '', { expireDays: -1 }); @@ -226,8 +222,6 @@ } }, reset: function (namespace) { - if (!this.check()) - throw 'SecurityError: cookies are disabled'; var cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie, key; i < cookies.length; i++) { cookie = cookies[i].replace(/^\s*/, ''); @@ -238,7 +232,7 @@ }, keys: function (namespace) { if (!this.check()) - throw 'SecurityError: cookies are disabled'; + throw Error('cookies are disabled'); var keys = [], cookies = document.cookie ? document.cookie.split(';') : []; for (var i = 0, cookie, key; i < cookies.length; i++) { @@ -271,44 +265,42 @@ options = Basil.utils.extend({}, this.options, options); if (!(name = _toStoredKey(options.namespace, name))) return; - value = _toStoredValue(value); - Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { + value = options.raw === true ? value : _toStoredValue(value); + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage, index) { _storages[storage].set(name, value, options); - return false; // break; + return false; // break }, function (storage, index, error) { - if (this.support(storage)) - _storages[storage].remove(name); + _storages[storage].remove(name); }, this); + return; }, get: function (name, options) { options = Basil.utils.extend({}, this.options, options); if (!(name = _toStoredKey(options.namespace, name))) return null; var value = null; - Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage, index) { if (value !== null) - return false; // break - if (this.support(storage)) - value = _fromStoredValue(_storages[storage].get(name, options)); - }, function (storage, index, error) { + return false; // break if a value has already been found. value = _storages[storage].get(name, options) || null; + value = options.raw === true ? value : _fromStoredValue(value); + }, function (storage, index, error) { + value = null; }, this); return value; }, remove: function (name, options) { options = Basil.utils.extend({}, this.options, options); if (!(name = _toStoredKey(options.namespace, name))) - return null; + return; Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { - if (this.support(storage)) - _storages[storage].remove(name); + _storages[storage].remove(name); }, null, this); }, reset: function (options) { options = Basil.utils.extend({}, this.options, options); Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { - if (this.support(storage)) - _storages[storage].reset(options.namespace); + _storages[storage].reset(options.namespace); }, null, this); }, keys: function (options) { @@ -322,23 +314,22 @@ options = Basil.utils.extend({}, this.options, options); var map = {}; Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { - if (!this.support(storage)) - return true; // continue Basil.utils.each(_storages[storage].keys(options.namespace), function (key) { map[key] = Basil.utils.isArray(map[key]) ? map[key] : []; map[key].push(storage); }, this); }, null, this); return map; - }, - // Access to native storages, without namespace or basil value decoration - memory: _storages.memory, - cookie: _storages.cookie, - localStorage: _storages.local, - sessionStorage: _storages.session + } }; }; + // Access to native storages, without namespace or basil value decoration + Basil.memory = new Basil.Storage().init({ storages: 'memory', namespace: null, raw: true }); + Basil.cookie = new Basil.Storage().init({ storages: 'cookie', namespace: null, raw: true }); + Basil.localStorage = new Basil.Storage().init({ storages: 'local', namespace: null, raw: true }); + Basil.sessionStorage = new Basil.Storage().init({ storages: 'session', namespace: null, raw: true }); + // browser export window.Basil = Basil; diff --git a/test/index.html b/test/index.html index ac9e8a7..26526de 100644 --- a/test/index.html +++ b/test/index.html @@ -16,7 +16,12 @@

Welcome to Basil

document.write('' + key + '
'); Basil.utils.each(storages, function (storage) { var value = window.basil.get(key, { namespace: null, storages: [storage] }); - document.write(storage + ': [' + typeof value + ']: ' + JSON.stringify(value) + '
'); + if (value) { + document.write(storage + ': [' + typeof value + ']: ' + JSON.stringify(value) + '
'); + } else { + value = window.basil.get(key, { namespace: null, storages: [storage], raw: true }); + document.write(storage + ': [Invalid raw data]: ' + JSON.stringify(value) + '
'); + } }, this); document.write('-----------------
'); }, this); diff --git a/test/test.js b/test/test.js index a0d5dad..c5c8044 100644 --- a/test/test.js +++ b/test/test.js @@ -22,11 +22,10 @@ expect(basil.keysMap).to.be.a('function'); }); it('should allow access to native storages', function () { - var basil = new window.Basil(); - expect(basil.memory).to.be.an('object'); - expect(basil.cookie).to.be.an('object'); - expect(basil.localStorage).to.be.an('object'); - expect(basil.sessionStorage).to.be.an('object'); + expect(Basil.memory).to.be.an('object'); + expect(Basil.cookie).to.be.an('object'); + expect(Basil.localStorage).to.be.an('object'); + expect(Basil.sessionStorage).to.be.an('object'); }); }); @@ -146,40 +145,37 @@ }); }); - if (new window.Basil().localStorage.check()) { + if (window.Basil.localStorage.check()) { describe('localStorage', function () { var _engine = { setItem: function () {}, removeItem: function () {} }; it('should have check() method returning true if localStorage available', function () { - expect(new window.Basil().localStorage.check()).to.be(true); + expect(window.Basil.localStorage.check()).to.be(true); }); it('should have check() method returning false if localStorage not available 1/2', function () { - var basil = new window.Basil(), - stub = sinon.stub(_engine, 'setItem'); + var stub = sinon.stub(_engine, 'setItem'); stub.throws(); - basil.localStorage.engine = _engine; - expect(basil.localStorage.check()).to.be(false); + Basil.localStorage.engine = _engine; + expect(Basil.localStorage.check()).to.be(false); stub.restore(); }); it('should have check() method returning false if localStorage not available 2/2', function () { - var basil = new window.Basil(), - stub = sinon.stub(_engine, 'removeItem'); + var stub = sinon.stub(_engine, 'removeItem'); stub.throws(); - basil.localStorage.engine = _engine; - expect(basil.localStorage.check()).to.be(false); + Basil.localStorage.engine = _engine; + expect(Basil.localStorage.check()).to.be(false); stub.restore(); }); it('should have set() method storing properly items and values 1/2', function () { - var basil = new window.Basil(), - stub = sinon.stub(_engine, 'setItem'); - basil.localStorage.set('foo', 'bar'); + var stub = sinon.stub(_engine, 'setItem'); + Basil.localStorage.set('foo', 'bar'); expect(stub.calledWith('foo', 'bar')); stub.restore(); }); it('should have set() method storing properly items and values 2/2', function () { - expect(new Basil().localStorage.set()).to.be(undefined); + expect(Basil.localStorage.set()).to.be(undefined); }); }); } else { From df507112e7ed9f965f084949be13e374da833b1d Mon Sep 17 00:00:00 2001 From: Mathieu Ghaleb Date: Mon, 20 Oct 2014 14:27:46 +0200 Subject: [PATCH 6/9] - update readme.md --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0df2028..eb200bc 100644 --- a/README.md +++ b/README.md @@ -48,21 +48,24 @@ basic.keys({ 'storages': ['memory'] }); // returns ['abc'] // retrieve keys map basil.keysMap(); // returns { 'foo': ['local', 'cookie'], 'abc': ['memory'] } basic.keysMap({ 'storages': ['memory'] }); // returns { 'abc': ['memory'] } +``` +### Native storages +``` // Access native storages // With basil API, but without namespace nor JSON parsing for values // cookies -basil.cookie.get(key); -basil.cookie.set(key, value, { 'expireDays': days, 'domain': 'mydomain.com' }); +Basil.cookie.get(key); +Basil.cookie.set(key, value, { 'expireDays': days, 'domain': 'mydomain.com' }); // localStorage -basil.localStorage.get(key); -basil.localStorage.set(key, value); +Basil.localStorage.get(key); +Basil.localStorage.set(key, value); // sessionStorage -basil.sessionStorage.get(key); -basil.sessionStorage.set(key, value); +Basil.sessionStorage.get(key); +Basil.sessionStorage.set(key, value); ``` ### Namespaces From f1291508f298bf145ebdeec288bc4012e37f28c3 Mon Sep 17 00:00:00 2001 From: Guillaume Potier Date: Mon, 20 Oct 2014 19:05:10 +0200 Subject: [PATCH 7/9] - added changelog + updated Readme --- CHANGELOG.md | 10 ++++++++++ README.md | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..534f776 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Basil changelog + +## 4.0.0 + + - Updated README + - Added `keys()` and `keysMap()` methods (#24) + - fixed cookie/localStorage security error if cookies disabled + - [BC Break] Basil raw storage engines do not need now a basil instance + to be accessed. Use `Basil.cookie.set('foo', 'bar')` now instead of + `new Basil().cookie.set('foo', 'bar')` diff --git a/README.md b/README.md index eb200bc..0628b08 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,17 @@ The missing Javascript smart persistence layer. Unified localstorage, cookie and session storage JavaScript API. +## Philosophy + +Basil aims to ease the frontend storage management for developpers. It strive +to be bulletproof and handle for you disabled cookies, full localStorage, +unwanted native storage exceptions.. + +When you'll try to store something, basil will automaticall seek through all +the available storages and find the best suited one to store your value. It +also handle for you the storage of complex javascript objects using json. + + ## Basic Usage ```javascript @@ -22,7 +33,6 @@ basil.check('local'); // boolean. Test if localStorage is available basil.reset(); // reset all stored values under namespace ``` - ## Advanced Usage ### Storages @@ -41,6 +51,9 @@ basil.set('foo', 'quux', { 'storages': ['cookie', 'local'] }); // set 'xyz' value under 'abc' key in memory basil.set('abc', 'xyz', { 'storages': ['memory'] }); +// set value without JSON encoding +basil.set('foo', '{ "bar": "baz" }', { raw: true }); // will save { "bar": "baz" } as string + // retrieve keys basil.keys(); // returns ['foo', 'abc'] basic.keys({ 'storages': ['memory'] }); // returns ['abc'] From 9d82238eb926298921060c20dcd47f6f8107570a Mon Sep 17 00:00:00 2001 From: Mathieu Ghaleb Date: Thu, 30 Oct 2014 11:28:43 +0100 Subject: [PATCH 8/9] - prevent 'Illegal access' error on some android devices --- src/basil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basil.js b/src/basil.js index 2033c63..68849af 100644 --- a/src/basil.js +++ b/src/basil.js @@ -92,7 +92,7 @@ return JSON.stringify(value); }, _fromStoredValue = function (value) { - return JSON.parse(value); + return value ? JSON.parse(value) : null; }; // HTML5 web storage interface From 11c92abad99fc0302ca2b558b3dc5812f95fa584 Mon Sep 17 00:00:00 2001 From: Mathieu Ghaleb Date: Thu, 13 Nov 2014 15:16:04 +0100 Subject: [PATCH 9/9] - storing `key/value` will remove `key` from all unused storages - get latest cookie first --- src/basil.js | 112 +++++++++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/src/basil.js b/src/basil.js index 68849af..b588f3c 100644 --- a/src/basil.js +++ b/src/basil.js @@ -83,10 +83,10 @@ key = path.join(':'); return key && namespace ? namespace + ':' + key : key; }, - _toKeyName = function (namespace, name) { + _toKeyName = function (namespace, key) { if (!namespace) - return name; - return name.replace(new RegExp('^' + namespace + ':'), ''); + return key; + return key.replace(new RegExp('^' + namespace + ':'), ''); }, _toStoredValue = function (value) { return JSON.stringify(value); @@ -107,16 +107,16 @@ } return true; }, - set: function (name, value, options) { - if (!name) - return; - window[this.engine].setItem(name, value); + set: function (key, value, options) { + if (!key) + throw Error('invalid key'); + window[this.engine].setItem(key, value); }, - get: function (name) { - return window[this.engine].getItem(name); + get: function (key) { + return window[this.engine].getItem(key); }, - remove: function (name) { - window[this.engine].removeItem(name); + remove: function (key) { + window[this.engine].removeItem(key); }, reset: function (namespace) { for (var i = 0, key; i < window[this.engine].length; i++) { @@ -153,16 +153,16 @@ check: function () { return true; }, - set: function (name, value, options) { - if (!name) - return; - this._hash[name] = value; + set: function (key, value, options) { + if (!key) + throw Error('invalid key'); + this._hash[key] = value; }, - get: function (name) { - return this._hash[name] || null; + get: function (key) { + return this._hash[key] || null; }, - remove: function (name) { - delete this._hash[name]; + remove: function (key) { + delete this._hash[key]; }, reset: function (namespace) { for (var key in this._hash) { @@ -184,41 +184,47 @@ check: function () { return navigator.cookieEnabled; }, - set: function (name, value, options) { + set: function (key, value, options) { if (!this.check()) throw Error('cookies are disabled'); options = options || {}; - if (!name) - return; - var cookie = name + '=' + value; + if (!key) + throw Error('invalid key'); + var cookie = key + '=' + value; + // handle expiration days if (options.expireDays) { var date = new Date(); date.setTime(date.getTime() + (options.expireDays * 24 * 60 * 60 * 1000)); cookie += '; expires=' + date.toGMTString(); } - if (options.domain) + // handle domain + if (options.domain && options.domain !== document.domain) { + var _domain = options.domain.replace(/^\./, ''); + if (document.domain.indexOf(_domain) === -1 || _domain.split('.').length <= 1) + throw Error('invalid domain'); cookie += '; domain=' + options.domain; + } document.cookie = cookie + '; path=/'; }, - get: function (name) { + get: function (key) { if (!this.check()) throw Error('cookies are disabled'); var cookies = document.cookie ? document.cookie.split(';') : []; - for (var i = 0, cookie; i < cookies.length; i++) { + // retrieve last updated cookie first + for (var i = cookies.length - 1, cookie; i >= 0; i--) { cookie = cookies[i].replace(/^\s*/, ''); - if (cookie.indexOf(name + '=') === 0) - return cookie.substring(name.length + 1, cookie.length); + if (cookie.indexOf(key + '=') === 0) + return cookie.substring(key.length + 1, cookie.length); } return null; }, - remove: function (name) { + remove: function (key) { // remove cookie from main domain - this.set(name, '', { expireDays: -1 }); - + this.set(key, '', { expireDays: -1 }); // remove cookie from upper domains var domainParts = document.domain.split('.'); - for (var i = domainParts.length - 1; i > 0; i--) { - this.set(name, '', { expireDays: -1, domain: '.' + domainParts.slice(- i).join('.') }); + for (var i = domainParts.length; i >= 0; i--) { + this.set(key, '', { expireDays: -1, domain: '.' + domainParts.slice(- i).join('.') }); } }, reset: function (namespace) { @@ -261,40 +267,50 @@ return _storages[storage].check(); return false; }, - set: function (name, value, options) { + set: function (key, value, options) { options = Basil.utils.extend({}, this.options, options); - if (!(name = _toStoredKey(options.namespace, name))) - return; + if (!(key = _toStoredKey(options.namespace, key))) + return false; value = options.raw === true ? value : _toStoredValue(value); + var where = null; + // try to set key/value in first available storage Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage, index) { - _storages[storage].set(name, value, options); - return false; // break - }, function (storage, index, error) { - _storages[storage].remove(name); - }, this); - return; + _storages[storage].set(key, value, options); + where = storage; + return false; // break; + }, null, this); + if (!where) { + // key has not been set anywhere + return false; + } + // remove key from all other storages + Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage, index) { + if (storage !== where) + _storages[storage].remove(key); + }, null, this); + return true; }, - get: function (name, options) { + get: function (key, options) { options = Basil.utils.extend({}, this.options, options); - if (!(name = _toStoredKey(options.namespace, name))) + if (!(key = _toStoredKey(options.namespace, key))) return null; var value = null; Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage, index) { if (value !== null) return false; // break if a value has already been found. - value = _storages[storage].get(name, options) || null; + value = _storages[storage].get(key, options) || null; value = options.raw === true ? value : _fromStoredValue(value); }, function (storage, index, error) { value = null; }, this); return value; }, - remove: function (name, options) { + remove: function (key, options) { options = Basil.utils.extend({}, this.options, options); - if (!(name = _toStoredKey(options.namespace, name))) + if (!(key = _toStoredKey(options.namespace, key))) return; Basil.utils.tryEach(_toStoragesArray(options.storages), function (storage) { - _storages[storage].remove(name); + _storages[storage].remove(key); }, null, this); }, reset: function (options) {