localforage
Version:
Offline storage, improved.
318 lines (266 loc) • 10.5 kB
JavaScript
(function() {
'use strict';
// Promises!
var Promise = (typeof module !== 'undefined' && module.exports) ?
require('promise') : this.Promise;
var DriverType = {
INDEXEDDB: 'asyncStorage',
LOCALSTORAGE: 'localStorageWrapper',
WEBSQL: 'webSQLStorage'
};
var DefaultDriverOrder = [
DriverType.INDEXEDDB,
DriverType.WEBSQL,
DriverType.LOCALSTORAGE
];
var LibraryMethods = [
'clear',
'getItem',
'key',
'keys',
'length',
'removeItem',
'setItem'
];
var ModuleType = {
DEFINE: 1,
EXPORT: 2,
WINDOW: 3
};
var DefaultConfig = {
description: '',
name: 'localforage',
// Default DB size is _JUST UNDER_ 5MB, as it's the highest size
// we can use without a prompt.
size: 4980736,
storeName: 'keyvaluepairs',
version: 1.0
};
// Attaching to window (i.e. no module loader) is the assumed,
// simple default.
var moduleType = ModuleType.WINDOW;
// Find out what kind of module setup we have; if none, we'll just attach
// localForage to the main window.
if (typeof define === 'function' && define.amd) {
moduleType = ModuleType.DEFINE;
} else if (typeof module !== 'undefined' && module.exports) {
moduleType = ModuleType.EXPORT;
}
// Check to see if IndexedDB is available and if it is the latest
// implementation; it's our preferred backend library. We use "_spec_test"
// as the name of the database because it's not the one we'll operate on,
// but it's useful to make sure its using the right spec.
// See: https://github.com/mozilla/localForage/issues/128
var driverSupport = (function(self) {
// Initialize IndexedDB; fall back to vendor-prefixed versions
// if needed.
var indexedDB = indexedDB || self.indexedDB || self.webkitIndexedDB ||
self.mozIndexedDB || self.OIndexedDB ||
self.msIndexedDB;
var result = {};
result[DriverType.WEBSQL] = !!self.openDatabase;
result[DriverType.INDEXEDDB] = !!(function() {
// We mimic PouchDB here; just UA test for Safari (which, as of
// iOS 8/Yosemite, doesn't properly support IndexedDB).
// IndexedDB support is broken and different from Blink's.
// This is faster than the test case (and it's sync), so we just
// do this. *SIGH*
// http://bl.ocks.org/nolanlawson/raw/c83e9039edf2278047e9/
//
// We test for openDatabase because IE Mobile identifies itself
// as Safari. Oh the lulz...
if (typeof self.openDatabase !== 'undefined' && self.navigator &&
self.navigator.userAgent &&
/Safari/.test(navigator.userAgent) &&
!/Chrome/.test(navigator.userAgent)) {
return false;
}
try {
return (indexedDB &&
typeof indexedDB.open === 'function' &&
indexedDB.open('_localforage_spec_test', 1)
.onupgradeneeded === null);
} catch (e) {
return false;
}
})();
result[DriverType.LOCALSTORAGE] = !!(function() {
try {
return (localStorage &&
('setItem' in localStorage) &&
(localStorage.setItem));
} catch (e) {
return false;
}
})();
return result;
})(this);
function extend(/*...*/) {
for (var i = 1; i < arguments.length; i++) {
var arg = arguments[i];
if (arg) {
for (var key in arg) {
if (arg.hasOwnProperty(key)) {
arguments[0][key] = arg[key];
}
}
}
}
return arguments[0];
}
function callWhenReady(localForageInstance, libraryMethod) {
localForageInstance[libraryMethod] = function() {
var _args = arguments;
return localForageInstance.ready().then(function() {
return localForageInstance[libraryMethod].apply(localForageInstance, _args);
});
};
}
var globalObject = this;
function LocalForage(options) {
this._config = extend({}, DefaultConfig, options);
this._driverSet = null;
this._ready = false;
this._dbInfo = null;
// Add a stub for each driver API method that delays the call to the
// corresponding driver method until localForage is ready. These stubs
// will be replaced by the driver methods as soon as the driver is
// loaded, so there is no performance impact.
for (var i = 0; i < LibraryMethods.length; i++) {
callWhenReady(this, LibraryMethods[i]);
}
this.setDriver(DefaultDriverOrder);
}
LocalForage.prototype.INDEXEDDB = DriverType.INDEXEDDB;
LocalForage.prototype.LOCALSTORAGE = DriverType.LOCALSTORAGE;
LocalForage.prototype.WEBSQL = DriverType.WEBSQL;
// Set any config values for localForage; can be called anytime before
// the first API call (e.g. `getItem`, `setItem`).
// We loop through options so we don't overwrite existing config
// values.
LocalForage.prototype.config = function(options) {
// If the options argument is an object, we use it to set values.
// Otherwise, we return either a specified config value or all
// config values.
if (typeof(options) === 'object') {
// If localforage is ready and fully initialized, we can't set
// any new configuration values. Instead, we return an error.
if (this._ready) {
return new Error("Can't call config() after localforage " +
'has been used.');
}
for (var i in options) {
if (i === 'storeName') {
options[i] = options[i].replace(/\W/g, '_');
}
this._config[i] = options[i];
}
return true;
} else if (typeof(options) === 'string') {
return this._config[options];
} else {
return this._config;
}
};
LocalForage.prototype.driver = function() {
return this._driver || null;
};
LocalForage.prototype.ready = function(callback) {
var self = this;
var ready = new Promise(function(resolve, reject) {
self._driverSet.then(function() {
if (self._ready === null) {
self._ready = self._initStorage(self._config);
}
self._ready.then(resolve, reject);
}).catch(reject);
});
ready.then(callback, callback);
return ready;
};
LocalForage.prototype.setDriver = function(drivers, callback,
errorCallback) {
var self = this;
if (typeof drivers === 'string') {
drivers = [drivers];
}
this._driverSet = new Promise(function(resolve, reject) {
var driverName = self._getFirstSupportedDriver(drivers);
if (!driverName) {
var error = new Error('No available storage method found.');
self._driverSet = Promise.reject(error);
reject(error);
return;
}
self._dbInfo = null;
self._ready = null;
// We allow localForage to be declared as a module or as a
// library available without AMD/require.js.
if (moduleType === ModuleType.DEFINE) {
require([driverName], function(lib) {
self._extend(lib);
resolve();
});
return;
} else if (moduleType === ModuleType.EXPORT) {
// Making it browserify friendly
var driver;
switch (driverName) {
case self.INDEXEDDB:
driver = require('./drivers/indexeddb');
break;
case self.LOCALSTORAGE:
driver = require('./drivers/localstorage');
break;
case self.WEBSQL:
driver = require('./drivers/websql');
}
self._extend(driver);
} else {
self._extend(globalObject[driverName]);
}
resolve();
});
this._driverSet.then(callback, errorCallback);
return this._driverSet;
};
LocalForage.prototype.supports = function(driverName) {
return !!driverSupport[driverName];
};
LocalForage.prototype._extend = function(libraryMethodsAndProperties) {
extend(this, libraryMethodsAndProperties);
};
// Used to determine which driver we should use as the backend for this
// instance of localForage.
LocalForage.prototype._getFirstSupportedDriver = function(drivers) {
var isArray = Array.isArray || function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
if (drivers && isArray(drivers)) {
for (var i = 0; i < drivers.length; i++) {
var driver = drivers[i];
if (this.supports(driver)) {
return driver;
}
}
}
return null;
};
LocalForage.prototype.createInstance = function(options) {
return new LocalForage(options);
};
// The actual localForage object that we expose as a module or via a
// global. It's extended by pulling in one of our other libraries.
var localForage = new LocalForage();
// We allow localForage to be declared as a module or as a library
// available without AMD/require.js.
if (moduleType === ModuleType.DEFINE) {
define('localforage', function() {
return localForage;
});
} else if (moduleType === ModuleType.EXPORT) {
module.exports = localForage;
} else {
this.localforage = localForage;
}
}).call(window);