(function() { 'use strict'; // Promises! var Promise = (typeof module !== 'undefined' && module.exports) ? require('promise') : this.Promise; var dummyStorage = {}; // Config the localStorage backend, using options set in the config. function _initStorage(options) { var self = this; var dbInfo = {}; if (options) { for (var i in options) { dbInfo[i] = options[i]; } } dummyStorage[dbInfo.name] = dbInfo.db = {}; self._dbInfo = dbInfo; return Promise.resolve(); } var SERIALIZED_MARKER = '__lfsc__:'; var SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER.length; // OMG the serializations! var TYPE_ARRAYBUFFER = 'arbf'; var TYPE_BLOB = 'blob'; var TYPE_INT8ARRAY = 'si08'; var TYPE_UINT8ARRAY = 'ui08'; var TYPE_UINT8CLAMPEDARRAY = 'uic8'; var TYPE_INT16ARRAY = 'si16'; var TYPE_INT32ARRAY = 'si32'; var TYPE_UINT16ARRAY = 'ur16'; var TYPE_UINT32ARRAY = 'ui32'; var TYPE_FLOAT32ARRAY = 'fl32'; var TYPE_FLOAT64ARRAY = 'fl64'; var TYPE_SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER_LENGTH + TYPE_ARRAYBUFFER.length; function clear(callback) { var self = this; var promise = new Promise(function(resolve, reject) { self.ready().then(function() { var db = self._dbInfo.db; for (var key in db) { if (db.hasOwnProperty(key)) { delete db[key]; // db[key] = undefined; } } resolve(); }).catch(reject); }); executeCallback(promise, callback); return promise; } function getItem(key, callback) { var self = this; // Cast the key to a string, as that's all we can set as a key. if (typeof key !== 'string') { window.console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise(function(resolve, reject) { self.ready().then(function() { try { var db = self._dbInfo.db; var result = db[key]; if (result) { result = _deserialize(result); } resolve(result); } catch (e) { reject(e); } }).catch(reject); }); executeCallback(promise, callback); return promise; } function iterate(callback) { var self = this; var promise = new Promise(function(resolve, reject) { self.ready().then(function() { try { var db = self._dbInfo.db; for (var key in db) { var result = db[key]; if (result) { result = _deserialize(result); } callback(result, key); } resolve(); } catch (e) { reject(e); } }).catch(reject); }); executeCallback(promise, callback); return promise; } function key(n, callback) { var self = this; var promise = new Promise(function(resolve, reject) { self.ready().then(function() { var db = self._dbInfo.db; var result = null; var index = 0; for (var key in db) { if (db.hasOwnProperty(key) && db[key] !== undefined) { if (n === index) { result = key; break; } index++; } } resolve(result); }).catch(reject); }); executeCallback(promise, callback); return promise; } function keys(callback) { var self = this; var promise = new Promise(function(resolve, reject) { self.ready().then(function() { var db = self._dbInfo.db; var keys = []; for (var key in db) { if (db.hasOwnProperty(key)) { keys.push(key); } } resolve(keys); }).catch(reject); }); executeCallback(promise, callback); return promise; } function length(callback) { var self = this; var promise = new Promise(function(resolve, reject) { self.keys().then(function(keys) { resolve(keys.length); }).catch(reject); }); executeCallback(promise, callback); return promise; } function removeItem(key, callback) { var self = this; // Cast the key to a string, as that's all we can set as a key. if (typeof key !== 'string') { window.console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise(function(resolve, reject) { self.ready().then(function() { var db = self._dbInfo.db; if (db.hasOwnProperty(key)) { delete db[key]; // db[key] = undefined; } resolve(); }).catch(reject); }); executeCallback(promise, callback); return promise; } function setItem(key, value, callback) { var self = this; // Cast the key to a string, as that's all we can set as a key. if (typeof key !== 'string') { window.console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise(function(resolve, reject) { self.ready().then(function() { // Convert undefined values to null. // https://github.com/mozilla/localForage/pull/42 if (value === undefined) { value = null; } // Save the original value to pass to the callback. var originalValue = value; _serialize(value, function(value, error) { if (error) { reject(error); } else { try { var db = self._dbInfo.db; db[key] = value; resolve(originalValue); } catch (e) { reject(e); } } }); }).catch(reject); }); executeCallback(promise, callback); return promise; } // _serialize just like in LocalStorage function _serialize(value, callback) { var valueString = ''; if (value) { valueString = value.toString(); } // Cannot use `value instanceof ArrayBuffer` or such here, as these // checks fail when running the tests using casper.js... // // TODO: See why those tests fail and use a better solution. if (value && (value.toString() === '[object ArrayBuffer]' || value.buffer && value.buffer.toString() === '[object ArrayBuffer]')) { // Convert binary arrays to a string and prefix the string with // a special marker. var buffer; var marker = SERIALIZED_MARKER; if (value instanceof ArrayBuffer) { buffer = value; marker += TYPE_ARRAYBUFFER; } else { buffer = value.buffer; if (valueString === '[object Int8Array]') { marker += TYPE_INT8ARRAY; } else if (valueString === '[object Uint8Array]') { marker += TYPE_UINT8ARRAY; } else if (valueString === '[object Uint8ClampedArray]') { marker += TYPE_UINT8CLAMPEDARRAY; } else if (valueString === '[object Int16Array]') { marker += TYPE_INT16ARRAY; } else if (valueString === '[object Uint16Array]') { marker += TYPE_UINT16ARRAY; } else if (valueString === '[object Int32Array]') { marker += TYPE_INT32ARRAY; } else if (valueString === '[object Uint32Array]') { marker += TYPE_UINT32ARRAY; } else if (valueString === '[object Float32Array]') { marker += TYPE_FLOAT32ARRAY; } else if (valueString === '[object Float64Array]') { marker += TYPE_FLOAT64ARRAY; } else { callback(new Error('Failed to get type for BinaryArray')); } } callback(marker + _bufferToString(buffer)); } else if (valueString === '[object Blob]') { // Conver the blob to a binaryArray and then to a string. var fileReader = new FileReader(); fileReader.onload = function() { var str = _bufferToString(this.result); callback(SERIALIZED_MARKER + TYPE_BLOB + str); }; fileReader.readAsArrayBuffer(value); } else { try { callback(JSON.stringify(value)); } catch (e) { window.console.error("Couldn't convert value into a JSON " + 'string: ', value); callback(e); } } } // _deserialize just like in LocalStorage function _deserialize(value) { // If we haven't marked this string as being specially serialized (i.e. // something other than serialized JSON), we can just return it and be // done with it. if (value.substring(0, SERIALIZED_MARKER_LENGTH) !== SERIALIZED_MARKER) { return JSON.parse(value); } // The following code deals with deserializing some kind of Blob or // TypedArray. First we separate out the type of data we're dealing // with from the data itself. var serializedString = value.substring(TYPE_SERIALIZED_MARKER_LENGTH); var type = value.substring(SERIALIZED_MARKER_LENGTH, TYPE_SERIALIZED_MARKER_LENGTH); // Fill the string into a ArrayBuffer. // 2 bytes for each char. var buffer = new ArrayBuffer(serializedString.length * 2); var bufferView = new Uint16Array(buffer); for (var i = serializedString.length - 1; i >= 0; i--) { bufferView[i] = serializedString.charCodeAt(i); } // Return the right type based on the code/type set during // serialization. switch (type) { case TYPE_ARRAYBUFFER: return buffer; case TYPE_BLOB: return new Blob([buffer]); case TYPE_INT8ARRAY: return new Int8Array(buffer); case TYPE_UINT8ARRAY: return new Uint8Array(buffer); case TYPE_UINT8CLAMPEDARRAY: return new Uint8ClampedArray(buffer); case TYPE_INT16ARRAY: return new Int16Array(buffer); case TYPE_UINT16ARRAY: return new Uint16Array(buffer); case TYPE_INT32ARRAY: return new Int32Array(buffer); case TYPE_UINT32ARRAY: return new Uint32Array(buffer); case TYPE_FLOAT32ARRAY: return new Float32Array(buffer); case TYPE_FLOAT64ARRAY: return new Float64Array(buffer); default: throw new Error('Unkown type: ' + type); } } // _bufferToString just like in LocalStorage function _bufferToString(buffer) { var str = ''; var uint16Array = new Uint16Array(buffer); try { str = String.fromCharCode.apply(null, uint16Array); } catch (e) { // This is a fallback implementation in case the first one does // not work. This is required to get the phantomjs passing... for (var i = 0; i < uint16Array.length; i++) { str += String.fromCharCode(uint16Array[i]); } } return str; } function executeCallback(promise, callback) { if (callback) { promise.then(function(result) { callback(null, result); }, function(error) { callback(error); }); } } var dummyStorageDriver = { _driver: 'dummyStorageDriver', _initStorage: _initStorage, // _supports: function() { return true; } iterate: iterate, getItem: getItem, setItem: setItem, removeItem: removeItem, clear: clear, length: length, key: key, keys: keys }; if (typeof define === 'function' && define.amd) { define('dummyStorageDriver', function() { return dummyStorageDriver; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = dummyStorageDriver; } else { this.dummyStorageDriver = dummyStorageDriver; } }).call(window);