367 lines
11 KiB
JavaScript
367 lines
11 KiB
JavaScript
import WxStorage from '../helpers/WxStorage'
|
|
|
|
|
|
|
|
var factory = function () {
|
|
"use strict";
|
|
|
|
var _maxExpireDate = new Date('Fri, 31 Dec 9999 23:59:59 UTC');
|
|
var _defaultExpire = _maxExpireDate;
|
|
|
|
// https://github.com/jeromegn/Backbone.localStorage/blob/master/backbone.localStorage.js#L63
|
|
var defaultSerializer = {
|
|
serialize: function (item) {
|
|
return JSON.stringify(item);
|
|
},
|
|
// fix for "illegal access" error on Android when JSON.parse is
|
|
// passed null
|
|
deserialize: function (data) {
|
|
return data && JSON.parse(data);
|
|
}
|
|
};
|
|
|
|
function _extend (obj, props) {
|
|
for (var key in props) obj[key] = props[key];
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* https://github.com/gsklee/ngStorage/blob/master/ngStorage.js#L52
|
|
*
|
|
* 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 below: "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was
|
|
* made to add something to storage that exceeded the quota."
|
|
*/
|
|
function _isStorageSupported (storage) {
|
|
return true;
|
|
|
|
var supported = false;
|
|
if (storage && storage.setItem ) {
|
|
supported = true;
|
|
var key = '__' + Math.round(Math.random() * 1e7);
|
|
try {
|
|
storage.setItem(key, key);
|
|
storage.removeItem(key);
|
|
} catch (err) {
|
|
supported = false;
|
|
}
|
|
}
|
|
return supported;
|
|
}
|
|
|
|
// get storage instance
|
|
function _getStorageInstance (storage) {
|
|
//调用封装
|
|
var wxStorage = new WxStorage;
|
|
|
|
return wxStorage;
|
|
/*
|
|
var type = typeof storage;
|
|
|
|
if (type === 'string' && window[storage] instanceof Storage) {
|
|
return window[storage];
|
|
}
|
|
return storage;
|
|
*/
|
|
}
|
|
|
|
function _isValidDate (date) {
|
|
return Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
|
|
}
|
|
|
|
function _getExpiresDate (expires, now) {
|
|
now = now || new Date();
|
|
|
|
if (typeof expires === 'number') {
|
|
expires = expires === Infinity ?
|
|
_maxExpireDate : new Date(now.getTime() + expires * 1000);
|
|
} else if (typeof expires === 'string') {
|
|
expires = new Date(expires.replace(/-/g, "/"));
|
|
}
|
|
|
|
if (expires && !_isValidDate(expires)) {
|
|
throw new Error('`expires` parameter cannot be converted to a valid Date instance');
|
|
}
|
|
|
|
return expires;
|
|
}
|
|
|
|
// http://crocodillon.com/blog/always-catch-localstorage-security-and-quota-exceeded-errors
|
|
function _isQuotaExceeded(e) {
|
|
var quotaExceeded = false;
|
|
if (e) {
|
|
if (e.code) {
|
|
switch (e.code) {
|
|
case 22:
|
|
quotaExceeded = true;
|
|
break;
|
|
case 1014:
|
|
// Firefox
|
|
if (e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
|
|
quotaExceeded = true;
|
|
}
|
|
break;
|
|
}
|
|
} else if (e.number === -2147024882) {
|
|
// Internet Explorer 8
|
|
quotaExceeded = true;
|
|
}
|
|
}
|
|
return quotaExceeded;
|
|
}
|
|
|
|
// cache item constructor
|
|
function CacheItemConstructor (value, exp) {
|
|
// createTime
|
|
this.c = (new Date()).getTime();
|
|
exp = exp || _defaultExpire;
|
|
var expires = _getExpiresDate(exp);
|
|
// expiresTime
|
|
this.e = expires.getTime();
|
|
this.v = value;
|
|
}
|
|
|
|
function _isCacheItem(item) {
|
|
if (typeof item !== 'object') {
|
|
return false;
|
|
}
|
|
if(item) {
|
|
if('c' in item && 'e' in item && 'v' in item) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// check cacheItem If effective
|
|
function _checkCacheItemIfEffective(cacheItem) {
|
|
var timeNow = (new Date()).getTime();
|
|
return timeNow < cacheItem.e;
|
|
}
|
|
|
|
function _checkAndWrapKeyAsString(key) {
|
|
if (typeof key !== 'string') {
|
|
console.warn(key + ' used as a key, but it is not a string.');
|
|
key = String(key);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
// cache api
|
|
var CacheAPI = {
|
|
|
|
set: function (key, value, options) {},
|
|
|
|
get: function (key) {},
|
|
|
|
delete: function (key) {},
|
|
// Try the best to clean All expires CacheItem.
|
|
deleteAllExpires: function() {},
|
|
// Clear all keys
|
|
clear: function () {},
|
|
// Add key-value item to memcached, success only when the key is not exists in memcached.
|
|
add: function (key, options) {},
|
|
// Replace the key's data item in cache, success only when the key's data item is exists in cache.
|
|
replace: function (key, value, options) {},
|
|
// Set a new options for an existing key.
|
|
touch: function (key, exp) {}
|
|
};
|
|
|
|
// cache api
|
|
var CacheAPIImpl = {
|
|
|
|
set: function(key, val, options) {
|
|
|
|
key = _checkAndWrapKeyAsString(key);
|
|
|
|
options = _extend({force: true}, options);
|
|
|
|
if (val === undefined) {
|
|
return this.delete(key);
|
|
}
|
|
|
|
var value = defaultSerializer.serialize(val);
|
|
|
|
var cacheItem = new CacheItemConstructor(value, options.exp);
|
|
try {
|
|
this.storage.setItem(key, defaultSerializer.serialize(cacheItem));
|
|
} catch (e) {
|
|
if (_isQuotaExceeded(e)) { //data wasn't successfully saved due to quota exceed so throw an error
|
|
this.quotaExceedHandler(key, value, options, e);
|
|
} else {
|
|
console.error(e);
|
|
}
|
|
}
|
|
|
|
return val;
|
|
},
|
|
get: function (key) {
|
|
key = _checkAndWrapKeyAsString(key);
|
|
var cacheItem = null;
|
|
try{
|
|
cacheItem = defaultSerializer.deserialize(this.storage.getItem(key));
|
|
}catch(e){
|
|
return null;
|
|
}
|
|
if(_isCacheItem(cacheItem)){
|
|
if(_checkCacheItemIfEffective(cacheItem)) {
|
|
var value = cacheItem.v;
|
|
return defaultSerializer.deserialize(value);
|
|
} else {
|
|
this.delete(key);
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
delete: function (key) {
|
|
key = _checkAndWrapKeyAsString(key);
|
|
this.storage.removeItem(key);
|
|
return key;
|
|
},
|
|
|
|
deleteAllExpires: function() {
|
|
var length = this.storage.length;
|
|
var deleteKeys = [];
|
|
var _this = this;
|
|
for (var i = 0; i < length; i++) {
|
|
var key = this.storage.key(i);
|
|
var cacheItem = null;
|
|
try {
|
|
cacheItem = defaultSerializer.deserialize(this.storage.getItem(key));
|
|
} catch (e) {}
|
|
|
|
if(cacheItem !== null && cacheItem.e !== undefined) {
|
|
var timeNow = (new Date()).getTime();
|
|
if(timeNow >= cacheItem.e) {
|
|
deleteKeys.push(key);
|
|
}
|
|
}
|
|
}
|
|
deleteKeys.forEach(function(key) {
|
|
_this.delete(key);
|
|
});
|
|
return deleteKeys;
|
|
},
|
|
|
|
clear: function () {
|
|
this.storage.clear();
|
|
},
|
|
|
|
add: function (key, value, options) {
|
|
key = _checkAndWrapKeyAsString(key);
|
|
options = _extend({force: true}, options);
|
|
try {
|
|
var cacheItem = defaultSerializer.deserialize(this.storage.getItem(key));
|
|
if (!_isCacheItem(cacheItem) || !_checkCacheItemIfEffective(cacheItem)) {
|
|
this.set(key, value, options);
|
|
return true;
|
|
}
|
|
} catch (e) {
|
|
this.set(key, value, options);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
replace: function (key, value, options) {
|
|
key = _checkAndWrapKeyAsString(key);
|
|
var cacheItem = null;
|
|
try{
|
|
cacheItem = defaultSerializer.deserialize(this.storage.getItem(key));
|
|
}catch(e){
|
|
return false;
|
|
}
|
|
if(_isCacheItem(cacheItem)){
|
|
if(_checkCacheItemIfEffective(cacheItem)) {
|
|
this.set(key, value, options);
|
|
return true;
|
|
} else {
|
|
this.delete(key);
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
touch: function (key, exp) {
|
|
key = _checkAndWrapKeyAsString(key);
|
|
var cacheItem = null;
|
|
try{
|
|
cacheItem = defaultSerializer.deserialize(this.storage.getItem(key));
|
|
}catch(e){
|
|
return false;
|
|
}
|
|
if(_isCacheItem(cacheItem)){
|
|
if(_checkCacheItemIfEffective(cacheItem)) {
|
|
this.set(key, this.get(key), {exp: exp});
|
|
return true;
|
|
} else {
|
|
this.delete(key);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Cache Constructor
|
|
*/
|
|
function CacheConstructor (options) {
|
|
|
|
// default options
|
|
var defaults = {
|
|
storage: 'localStorage',
|
|
exp: Infinity //An expiration time, in seconds. default never .
|
|
};
|
|
|
|
var opt = _extend(defaults, options);
|
|
|
|
var expires = opt.exp;
|
|
|
|
if (expires && typeof expires !== 'number' && !_isValidDate(expires)) {
|
|
throw new Error('Constructor `exp` parameter cannot be converted to a valid Date instance');
|
|
} else {
|
|
_defaultExpire = expires;
|
|
}
|
|
|
|
var storage = _getStorageInstance(opt.storage);
|
|
|
|
var isSupported = _isStorageSupported(storage);
|
|
|
|
this.isSupported = function () {
|
|
return isSupported;
|
|
};
|
|
|
|
if (isSupported) {
|
|
|
|
this.storage = storage;
|
|
|
|
this.quotaExceedHandler = function (key, val, options, e) {
|
|
console.warn('Quota exceeded!');
|
|
if (options && options.force === true) {
|
|
var deleteKeys = this.deleteAllExpires();
|
|
console.warn('delete all expires CacheItem : [' + deleteKeys + '] and try execute `set` method again!');
|
|
try {
|
|
options.force = false;
|
|
this.set(key, val, options);
|
|
} catch (err) {
|
|
console.warn(err);
|
|
}
|
|
}
|
|
};
|
|
|
|
} else { // if not support, rewrite all functions without doing anything
|
|
_extend(this, CacheAPI);
|
|
}
|
|
|
|
}
|
|
|
|
CacheConstructor.prototype = CacheAPIImpl;
|
|
|
|
return CacheConstructor;
|
|
|
|
}
|
|
|
|
|
|
export default factory(); |