mirror of
https://github.com/localForage/localForage.git
synced 2026-02-01 15:32:04 +00:00
Merge pull request #231 from mozilla/revert-227-browserify
Revert "Migrate to Browserify / Gulp for Build Process"
This commit is contained in:
commit
781f012239
4
.jscsrc
4
.jscsrc
@ -18,7 +18,5 @@
|
||||
"requireSpacesInConditionalExpression": true,
|
||||
"requireSpacesInFunctionExpression": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"requireLineFeedAtFileEnd": true,
|
||||
"validateQuoteMarks": "'"
|
||||
}
|
||||
}
|
||||
|
||||
194
Gruntfile.js
Normal file
194
Gruntfile.js
Normal file
@ -0,0 +1,194 @@
|
||||
/* jshint node:true */
|
||||
var path = require('path');
|
||||
var saucelabsBrowsers = require(path.resolve('test', 'saucelabs-browsers.js'));
|
||||
|
||||
var sourceFiles = [
|
||||
'Gruntfile.js',
|
||||
'src/*.js',
|
||||
'src/**/*.js',
|
||||
'test/**/test.*.js'
|
||||
];
|
||||
|
||||
module.exports = exports = function(grunt) {
|
||||
'use strict';
|
||||
|
||||
grunt.initConfig({
|
||||
concat: {
|
||||
options: {
|
||||
separator: ''
|
||||
},
|
||||
localforage: {
|
||||
files: {
|
||||
'dist/localforage.js': [
|
||||
// https://github.com/jakearchibald/es6-promise
|
||||
'bower_components/es6-promise/promise.js',
|
||||
'src/drivers/**/*.js',
|
||||
'src/localforage.js'
|
||||
],
|
||||
'dist/localforage.nopromises.js': [
|
||||
'src/drivers/**/*.js',
|
||||
'src/localforage.js'
|
||||
]
|
||||
},
|
||||
options: {
|
||||
banner:
|
||||
'/*!\n' +
|
||||
' localForage -- Offline Storage, Improved\n' +
|
||||
' Version 0.9.2\n' +
|
||||
' http://mozilla.github.io/localForage\n' +
|
||||
' (c) 2013-2014 Mozilla, Apache License 2.0\n' +
|
||||
'*/\n'
|
||||
}
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
test: {
|
||||
options: {
|
||||
base: '.',
|
||||
hostname: '*',
|
||||
port: 9999,
|
||||
middleware: function(connect) {
|
||||
return [
|
||||
function(req, res, next) {
|
||||
res.setHeader('Access-Control-Allow-Origin',
|
||||
'*');
|
||||
res.setHeader('Access-Control-Allow-Methods',
|
||||
'*');
|
||||
|
||||
return next();
|
||||
},
|
||||
connect.static(require('path').resolve('.'))
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
es3_safe_recast: {
|
||||
dist: {
|
||||
files: [{
|
||||
src: ['dist/localforage.js'],
|
||||
dest: 'dist/localforage.js'
|
||||
}]
|
||||
},
|
||||
nopromises: {
|
||||
files: [{
|
||||
src: ['dist/localforage.nopromises.js'],
|
||||
dest: 'dist/localforage.nopromises.js'
|
||||
}]
|
||||
}
|
||||
},
|
||||
jscs: {
|
||||
source: sourceFiles
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
source: sourceFiles
|
||||
},
|
||||
mocha: {
|
||||
unit: {
|
||||
options: {
|
||||
urls: [
|
||||
'http://localhost:9999/test/test.component.html',
|
||||
'http://localhost:9999/test/test.nodriver.html',
|
||||
'http://localhost:9999/test/test.main.html',
|
||||
'http://localhost:9999/test/test.min.html',
|
||||
'http://localhost:9999/test/test.require.html',
|
||||
'http://localhost:9999/test/test.callwhenready.html'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
open: {
|
||||
site: {
|
||||
path: 'http://localhost:4567/'
|
||||
}
|
||||
},
|
||||
'saucelabs-mocha': {
|
||||
all: {
|
||||
options: {
|
||||
username: process.env.SAUCE_USERNAME,
|
||||
key: process.env.SAUCE_ACCESS_KEY,
|
||||
urls: ['http://localhost:9999/test/test.main.html'],
|
||||
tunnelTimeout: 5,
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
concurrency: 3,
|
||||
browsers: saucelabsBrowsers,
|
||||
testname: 'localForage Tests'
|
||||
}
|
||||
}
|
||||
},
|
||||
shell: {
|
||||
options: {
|
||||
stdout: true
|
||||
},
|
||||
component: {
|
||||
command: path.resolve('node_modules', 'component', 'bin',
|
||||
'component-build') +
|
||||
' --dev -o test -n localforage.component'
|
||||
},
|
||||
'publish-site': {
|
||||
command: 'rake publish ALLOW_DIRTY=true'
|
||||
},
|
||||
'serve-site': {
|
||||
command: 'bundle exec middleman server'
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
localforage: {
|
||||
files: {
|
||||
'dist/localforage.min.js': ['dist/localforage.js'],
|
||||
'dist/localforage.nopromises.min.js': [
|
||||
'dist/localforage.nopromises.js'
|
||||
],
|
||||
'site/localforage.min.js': ['dist/localforage.js']
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
build: {
|
||||
files: ['src/*.js', 'src/**/*.js'],
|
||||
tasks: ['build']
|
||||
},
|
||||
/*jshint scripturl:true */
|
||||
'mocha:unit': {
|
||||
files: [
|
||||
'dist/localforage.js',
|
||||
'test/runner.js',
|
||||
'test/test.*.*'
|
||||
],
|
||||
tasks: ['jshint', 'jscs', 'shell:component', 'mocha:unit']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
grunt.registerTask('default', ['build', 'connect', 'watch']);
|
||||
grunt.registerTask('build', ['concat', 'es3_safe_recast', 'uglify']);
|
||||
grunt.registerTask('publish', ['build', 'shell:publish-site']);
|
||||
grunt.registerTask('serve', ['build', 'connect:test', 'watch']);
|
||||
grunt.registerTask('site', ['shell:serve-site']);
|
||||
|
||||
// These are the test tasks we run regardless of Sauce Labs credentials.
|
||||
var testTasks = [
|
||||
'build',
|
||||
'jshint',
|
||||
'jscs',
|
||||
'shell:component',
|
||||
'connect:test',
|
||||
'mocha'
|
||||
];
|
||||
grunt.registerTask('test:local', testTasks.slice());
|
||||
|
||||
// Run tests using Sauce Labs if we are on Travis or have locally
|
||||
// available Sauce Labs credentials. Use `grunt test:local` to skip
|
||||
// Sauce Labs tests.
|
||||
// if (process.env.TRAVIS_JOB_ID ||
|
||||
// (process.env.SAUCE_USERNAME && process.env.SAUCE_ACCESS_KEY)) {
|
||||
// testTasks.push('saucelabs-mocha');
|
||||
// }
|
||||
|
||||
grunt.registerTask('test', testTasks);
|
||||
};
|
||||
4036
dist/localforage.js
vendored
Executable file → Normal file
4036
dist/localforage.js
vendored
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
2
dist/localforage.min.js
vendored
Executable file → Normal file
2
dist/localforage.min.js
vendored
Executable file → Normal file
File diff suppressed because one or more lines are too long
1576
dist/localforage.nopromises.js
vendored
Normal file
1576
dist/localforage.nopromises.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/localforage.nopromises.min.js
vendored
Normal file
1
dist/localforage.nopromises.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
93
gulpfile.js
93
gulpfile.js
@ -1,93 +0,0 @@
|
||||
var gulp = require('gulp');
|
||||
|
||||
var component = require('gulp-component-build');
|
||||
var jscs = require('gulp-jscs');
|
||||
var jshint = require('gulp-jshint');
|
||||
var mocha = require('gulp-mocha-phantomjs');
|
||||
var rename = require('gulp-rename');
|
||||
var uglify = require('gulp-uglify');
|
||||
|
||||
var browserify = require('browserify');
|
||||
var del = require('del');
|
||||
var es3ify = require('es3ify');
|
||||
var source = require('vinyl-source-stream');
|
||||
|
||||
// Define source target
|
||||
var src = gulp.src([
|
||||
'gulpfile.js',
|
||||
'src/*.js',
|
||||
'src/**/*.js',
|
||||
'test/**/test.*.js'
|
||||
]);
|
||||
|
||||
// ---------------------
|
||||
|
||||
// Clean build directory
|
||||
gulp.task('clean', function(callback) {
|
||||
del(['dist'], callback);
|
||||
});
|
||||
|
||||
// Browserify
|
||||
gulp.task('browserify', function() {
|
||||
return browserify({
|
||||
standalone: 'localforage'
|
||||
})
|
||||
.add('./src/localforage.js')
|
||||
.transform({}, es3ify)
|
||||
.bundle()
|
||||
.pipe(source('localforage.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
// Minify
|
||||
gulp.task('uglify', function() {
|
||||
gulp.src('./dist/localforage.js')
|
||||
.pipe(uglify())
|
||||
.pipe(rename({suffix: '.min'}))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
// ---------------------
|
||||
|
||||
// Style
|
||||
gulp.task('jscs', function() {
|
||||
return gulp.src([
|
||||
'./src/*.js'
|
||||
])
|
||||
.pipe(jscs());
|
||||
});
|
||||
|
||||
// Lint
|
||||
gulp.task('jshint', function() {
|
||||
return src
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('default'));
|
||||
});
|
||||
|
||||
// Integration
|
||||
gulp.task('mocha', function() {
|
||||
return gulp.src([
|
||||
'test/test.main.html',
|
||||
'test/test.min.html',
|
||||
'test/test.nodriver.html',
|
||||
'test/test.callwhenready.html',
|
||||
'test/test.require.html',
|
||||
'test/test.component.html'
|
||||
])
|
||||
.pipe(mocha());
|
||||
});
|
||||
|
||||
// ---------------------
|
||||
|
||||
gulp.task('component', function() {
|
||||
return gulp.src('./component.json')
|
||||
.pipe(component.scripts({ install: true }))
|
||||
.pipe(rename('component.js'))
|
||||
.pipe(gulp.dest('./test/'));
|
||||
});
|
||||
|
||||
// ---------------------
|
||||
|
||||
gulp.task('build', ['clean', 'browserify', 'uglify']);
|
||||
gulp.task('test', ['jscs', 'jshint', 'mocha']);
|
||||
gulp.task('publish', ['component']);
|
||||
43
package.json
43
package.json
@ -15,31 +15,30 @@
|
||||
"url": "git://github.com/mozilla/localForage.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "./node_modules/.bin/gulp build",
|
||||
"test": "./node_modules/.bin/gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"promise": "~5.0.0"
|
||||
"prepublish": "grunt build",
|
||||
"test": "grunt test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "~5.9.1",
|
||||
"del": "~0.1.1",
|
||||
"es3ify": "~0.1.3",
|
||||
"expect.js": "~0.3.1",
|
||||
"gulp": "~3.8.7",
|
||||
"gulp-component-build": "0.0.4",
|
||||
"gulp-jscs": "~1.1.0",
|
||||
"gulp-jshint": "~1.8.4",
|
||||
"gulp-mocha-phantomjs": "~0.3.0",
|
||||
"gulp-rename": "~1.2.0",
|
||||
"gulp-uglify": "~0.3.1",
|
||||
"mocha": "~1.21.3",
|
||||
"phantomjs": "~1.9.7-12",
|
||||
"requirejs": "~2.1.14",
|
||||
"vinyl-source-stream": "~0.1.1",
|
||||
"ym-modernizr": "~3.0.0-pre"
|
||||
"component": "^1.0.0",
|
||||
"cors": "^2.3.1",
|
||||
"grunt": "^0.4.2",
|
||||
"grunt-contrib-concat": "^0.3.0",
|
||||
"grunt-contrib-connect": "^0.8.0",
|
||||
"grunt-contrib-jshint": "^0.9.2",
|
||||
"grunt-contrib-uglify": "^0.4.0",
|
||||
"grunt-contrib-watch": "^0.5.0",
|
||||
"grunt-es3-safe-recast": "^0.1.0",
|
||||
"grunt-jscs-checker": "^0.4.4",
|
||||
"grunt-mocha": "^0.4.10",
|
||||
"grunt-saucelabs": "^5.1.2",
|
||||
"grunt-shell": "^0.6.4",
|
||||
"load-grunt-tasks": "^0.4.0",
|
||||
"mocha": "^1.18.2",
|
||||
"phantomjs": "^1.9.7-12",
|
||||
"uglify-js": "^2.3.x"
|
||||
},
|
||||
"main": "src/localforage.js",
|
||||
"browser": "dist/localforage.js",
|
||||
"main": "dist/localforage.js",
|
||||
"licence": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "http://github.com/mozilla/localForage/issues"
|
||||
|
||||
@ -1,350 +1,359 @@
|
||||
// Exclude 'redefinition of {a}' from jshint as we are declaring a local var
|
||||
// that appears to conflict with the global namespace.
|
||||
// http://jslinterrors.com/redefinition-of-a
|
||||
/*jshint -W079 */
|
||||
/*jshint latedef:false */
|
||||
|
||||
// Some code originally from async_storage.js in
|
||||
// [Gaia](https://github.com/mozilla-b2g/gaia).
|
||||
// Originally found in https://github.com/mozilla-b2g/gaia/blob/e8f624e4cc9ea945727278039b3bc9bcb9f8667a/shared/js/async_storage.js
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Promises!
|
||||
var Promise = require('promise');
|
||||
// Originally found in https://github.com/mozilla-b2g/gaia/blob/e8f624e4cc9ea945727278039b3bc9bcb9f8667a/shared/js/async_storage.js
|
||||
|
||||
var db = null;
|
||||
var dbInfo = {};
|
||||
// Promises!
|
||||
var Promise = (typeof module !== 'undefined' && module.exports) ?
|
||||
require('promise') : this.Promise;
|
||||
|
||||
// Initialize IndexedDB; fall back to vendor-prefixed versions if needed.
|
||||
var indexedDB = indexedDB || window.indexedDB || window.webkitIndexedDB ||
|
||||
window.mozIndexedDB || window.OIndexedDB ||
|
||||
window.msIndexedDB;
|
||||
var db = null;
|
||||
var dbInfo = {};
|
||||
|
||||
// If IndexedDB isn't available, we get outta here!
|
||||
if (!indexedDB) {
|
||||
indexedDB = null;
|
||||
}
|
||||
// Initialize IndexedDB; fall back to vendor-prefixed versions if needed.
|
||||
var indexedDB = indexedDB || this.indexedDB || this.webkitIndexedDB ||
|
||||
this.mozIndexedDB || this.OIndexedDB ||
|
||||
this.msIndexedDB;
|
||||
|
||||
// Open the IndexedDB database (automatically creates one if one didn't
|
||||
// previously exist), using any options set in the config.
|
||||
function _initStorage(options) {
|
||||
if (options) {
|
||||
for (var i in options) {
|
||||
dbInfo[i] = options[i];
|
||||
}
|
||||
// If IndexedDB isn't available, we get outta here!
|
||||
if (!indexedDB) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var openreq = indexedDB.open(dbInfo.name, dbInfo.version);
|
||||
openreq.onerror = function() {
|
||||
reject(openreq.error);
|
||||
};
|
||||
openreq.onupgradeneeded = function() {
|
||||
// First time setup: create an empty object store
|
||||
openreq.result.createObjectStore(dbInfo.storeName);
|
||||
};
|
||||
openreq.onsuccess = function() {
|
||||
db = openreq.result;
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
var req = store.get(key);
|
||||
|
||||
req.onsuccess = function() {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
deferCallback(callback,value);
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function setItem(key, value, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readwrite')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
// The reason we don't _save_ null is because IE 10 does
|
||||
// not support saving the `null` type in IndexedDB. How
|
||||
// ironic, given the bug below!
|
||||
// See: https://github.com/mozilla/localForage/issues/161
|
||||
if (value === null) {
|
||||
value = undefined;
|
||||
// Open the IndexedDB database (automatically creates one if one didn't
|
||||
// previously exist), using any options set in the config.
|
||||
function _initStorage(options) {
|
||||
if (options) {
|
||||
for (var i in options) {
|
||||
dbInfo[i] = options[i];
|
||||
}
|
||||
|
||||
var req = store.put(value, key);
|
||||
req.onsuccess = function() {
|
||||
// Cast to undefined so the value passed to
|
||||
// callback/promise is the same as what one would get out
|
||||
// of `getItem()` later. This leads to some weirdness
|
||||
// (setItem('foo', undefined) will return `null`), but
|
||||
// it's not my fault localStorage is our baseline and that
|
||||
// it's weird.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
deferCallback(callback, value);
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function removeItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readwrite')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
// We use a Grunt task to make this safe for IE and some
|
||||
// versions of Android (including those used by Cordova).
|
||||
// Normally IE won't like `.delete()` and will insist on
|
||||
// using `['delete']()`, but we have a build step that
|
||||
// fixes this for us now.
|
||||
var req = store.delete(key);
|
||||
req.onsuccess = function() {
|
||||
|
||||
deferCallback(callback);
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
// The request will be aborted if we've exceeded our storage
|
||||
// space. In this case, we will reject with a specific
|
||||
// "QuotaExceededError".
|
||||
req.onabort = function(event) {
|
||||
var error = event.target.error;
|
||||
if (error === 'QuotaExceededError') {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function clear(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readwrite')
|
||||
.objectStore(dbInfo.storeName);
|
||||
var req = store.clear();
|
||||
|
||||
req.onsuccess = function() {
|
||||
deferCallback(callback);
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function length(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
var req = store.count();
|
||||
|
||||
req.onsuccess = function() {
|
||||
if (callback) {
|
||||
callback(req.result);
|
||||
}
|
||||
|
||||
resolve(req.result);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function key(n, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (n < 0) {
|
||||
if (callback) {
|
||||
callback(null);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
return new Promise(function(resolve, reject) {
|
||||
var openreq = indexedDB.open(dbInfo.name, dbInfo.version);
|
||||
openreq.onerror = function() {
|
||||
reject(openreq.error);
|
||||
};
|
||||
openreq.onupgradeneeded = function() {
|
||||
// First time setup: create an empty object store
|
||||
openreq.result.createObjectStore(dbInfo.storeName);
|
||||
};
|
||||
openreq.onsuccess = function() {
|
||||
db = openreq.result;
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
var advanced = false;
|
||||
var req = store.openCursor();
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
if (callback) {
|
||||
callback(null);
|
||||
function getItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
var req = store.get(key);
|
||||
|
||||
req.onsuccess = function() {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
deferCallback(callback,value);
|
||||
|
||||
return;
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function setItem(key, value, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readwrite')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
// The reason we don't _save_ null is because IE 10 does
|
||||
// not support saving the `null` type in IndexedDB. How
|
||||
// ironic, given the bug below!
|
||||
// See: https://github.com/mozilla/localForage/issues/161
|
||||
if (value === null) {
|
||||
value = undefined;
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
// We have the first key, return it if that's what they
|
||||
// wanted.
|
||||
if (callback) {
|
||||
callback(cursor.key);
|
||||
var req = store.put(value, key);
|
||||
req.onsuccess = function() {
|
||||
// Cast to undefined so the value passed to
|
||||
// callback/promise is the same as what one would get out
|
||||
// of `getItem()` later. This leads to some weirdness
|
||||
// (setItem('foo', undefined) will return `null`), but
|
||||
// it's not my fault localStorage is our baseline and that
|
||||
// it's weird.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
resolve(cursor.key);
|
||||
} else {
|
||||
if (!advanced) {
|
||||
// Otherwise, ask the cursor to skip ahead n
|
||||
// records.
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
} else {
|
||||
// When we get here, we've got the nth key.
|
||||
deferCallback(callback, value);
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function removeItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readwrite')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
// We use a Grunt task to make this safe for IE and some
|
||||
// versions of Android (including those used by Cordova).
|
||||
// Normally IE won't like `.delete()` and will insist on
|
||||
// using `['delete']()`, but we have a build step that
|
||||
// fixes this for us now.
|
||||
var req = store.delete(key);
|
||||
req.onsuccess = function() {
|
||||
|
||||
deferCallback(callback);
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
// The request will be aborted if we've exceeded our storage
|
||||
// space. In this case, we will reject with a specific
|
||||
// "QuotaExceededError".
|
||||
req.onabort = function(event) {
|
||||
var error = event.target.error;
|
||||
if (error === 'QuotaExceededError') {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function clear(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readwrite')
|
||||
.objectStore(dbInfo.storeName);
|
||||
var req = store.clear();
|
||||
|
||||
req.onsuccess = function() {
|
||||
deferCallback(callback);
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function length(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
var req = store.count();
|
||||
|
||||
req.onsuccess = function() {
|
||||
if (callback) {
|
||||
callback(req.result);
|
||||
}
|
||||
|
||||
resolve(req.result);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function key(n, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (n < 0) {
|
||||
if (callback) {
|
||||
callback(null);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
var advanced = false;
|
||||
var req = store.openCursor();
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
if (callback) {
|
||||
callback(null);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
// We have the first key, return it if that's what they
|
||||
// wanted.
|
||||
if (callback) {
|
||||
callback(cursor.key);
|
||||
}
|
||||
|
||||
resolve(cursor.key);
|
||||
} else {
|
||||
if (!advanced) {
|
||||
// Otherwise, ask the cursor to skip ahead n
|
||||
// records.
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
} else {
|
||||
// When we get here, we've got the nth key.
|
||||
if (callback) {
|
||||
callback(cursor.key);
|
||||
}
|
||||
|
||||
resolve(cursor.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function keys(callback) {
|
||||
var _this = this;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
var req = store.openCursor();
|
||||
var keys = [];
|
||||
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
|
||||
if (!cursor) {
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(keys);
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
resolve(keys);
|
||||
return;
|
||||
}
|
||||
|
||||
keys.push(cursor.key);
|
||||
cursor.continue();
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Under Chrome the callback is called before the changes (save, clear)
|
||||
// are actually made. So we use a defer function which wait that the
|
||||
// call stack to be empty.
|
||||
// For more info : https://github.com/mozilla/localForage/issues/175
|
||||
// Pull request : https://github.com/mozilla/localForage/pull/178
|
||||
function deferCallback(callback, value) {
|
||||
if (callback) {
|
||||
return setTimeout(function() {
|
||||
return callback(value);
|
||||
}, 0);
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_driver: 'asyncStorage',
|
||||
_initStorage: _initStorage,
|
||||
getItem: getItem,
|
||||
setItem: setItem,
|
||||
removeItem: removeItem,
|
||||
clear: clear,
|
||||
length: length,
|
||||
key: key,
|
||||
keys: keys
|
||||
};
|
||||
function keys(callback) {
|
||||
var _this = this;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var store = db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
var req = store.openCursor();
|
||||
var keys = [];
|
||||
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
|
||||
if (!cursor) {
|
||||
if (callback) {
|
||||
callback(keys);
|
||||
}
|
||||
|
||||
resolve(keys);
|
||||
return;
|
||||
}
|
||||
|
||||
keys.push(cursor.key);
|
||||
cursor.continue();
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
if (callback) {
|
||||
callback(null, req.error);
|
||||
}
|
||||
|
||||
reject(req.error);
|
||||
};
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Under Chrome the callback is called before the changes (save, clear)
|
||||
// are actually made. So we use a defer function which wait that the
|
||||
// call stack to be empty.
|
||||
// For more info : https://github.com/mozilla/localForage/issues/175
|
||||
// Pull request : https://github.com/mozilla/localForage/pull/178
|
||||
function deferCallback(callback, value) {
|
||||
if (callback) {
|
||||
return setTimeout(function() {
|
||||
return callback(value);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
var asyncStorage = {
|
||||
_driver: 'asyncStorage',
|
||||
_initStorage: _initStorage,
|
||||
getItem: getItem,
|
||||
setItem: setItem,
|
||||
removeItem: removeItem,
|
||||
clear: clear,
|
||||
length: length,
|
||||
key: key,
|
||||
keys: keys
|
||||
};
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('asyncStorage', function() {
|
||||
return asyncStorage;
|
||||
});
|
||||
} else if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = asyncStorage;
|
||||
} else {
|
||||
this.asyncStorage = asyncStorage;
|
||||
}
|
||||
}).call(this);
|
||||
|
||||
@ -1,409 +1,420 @@
|
||||
// Exclude 'redefinition of {a}' from jshint as we are declaring a local var
|
||||
// that appears to conflict with the global namespace.
|
||||
// http://jslinterrors.com/redefinition-of-a
|
||||
/*jshint -W079 */
|
||||
/*jshint -W020 */
|
||||
/*jshint latedef:false */
|
||||
|
||||
// If IndexedDB isn't available, we'll fall back to localStorage.
|
||||
// Note that this will have considerable performance and storage
|
||||
// side-effects (all data will be serialized on save and only data that
|
||||
// can be converted to a string via `JSON.stringify()` will be saved).
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var Promise = require('promise');
|
||||
var keyPrefix = '';
|
||||
var dbInfo = {};
|
||||
// Promises!
|
||||
var Promise = (typeof module !== 'undefined' && module.exports) ?
|
||||
require('promise') : this.Promise;
|
||||
var localStorage = null;
|
||||
|
||||
var keyPrefix = '';
|
||||
var dbInfo = {};
|
||||
|
||||
var localStorage = null;
|
||||
|
||||
// If the app is running inside a Google Chrome packaged webapp, or some
|
||||
// other context where localStorage isn't available, we don't use
|
||||
// localStorage. This feature detection is preferred over the old
|
||||
// `if (window.chrome && window.chrome.runtime)` code.
|
||||
// See: https://github.com/mozilla/localForage/issues/68
|
||||
try {
|
||||
// If localStorage is available, initialize localStorage and create a
|
||||
// variable to use throughout the code.
|
||||
if (window.localStorage && ('setItem' in window.localStorage)) {
|
||||
localStorage = window.localStorage;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// Config the localStorage backend, using options set in the config.
|
||||
function _initStorage(options) {
|
||||
if (options) {
|
||||
for (var i in options) {
|
||||
dbInfo[i] = options[i];
|
||||
// If the app is running inside a Google Chrome packaged webapp, or some
|
||||
// other context where localStorage isn't available, we don't use
|
||||
// localStorage. This feature detection is preferred over the old
|
||||
// `if (window.chrome && window.chrome.runtime)` code.
|
||||
// See: https://github.com/mozilla/localForage/issues/68
|
||||
try {
|
||||
// If localStorage isn't available, we get outta here!
|
||||
// This should be inside a try catch
|
||||
if (!this.localStorage || !('setItem' in this.localStorage)) {
|
||||
return;
|
||||
}
|
||||
// Initialize localStorage and create a variable to use throughout
|
||||
// the code.
|
||||
localStorage = this.localStorage;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyPrefix = dbInfo.name + '/';
|
||||
|
||||
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;
|
||||
|
||||
// Remove all keys from the datastore, effectively destroying all data in
|
||||
// the app's key/value store!
|
||||
function clear(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
localStorage.clear();
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
// Config the localStorage backend, using options set in the config.
|
||||
function _initStorage(options) {
|
||||
if (options) {
|
||||
for (var i in options) {
|
||||
dbInfo[i] = options[i];
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
keyPrefix = dbInfo.name + '/';
|
||||
|
||||
// Retrieve an item from the store. Unlike the original async_storage
|
||||
// library in Gaia, we don't modify return values at all. If a key's value
|
||||
// is `undefined`, we pass that value to the callback function.
|
||||
function getItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
try {
|
||||
var result = localStorage.getItem(keyPrefix + key);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// If a result was found, parse it from the serialized
|
||||
// string into a JS object. If result isn't truthy, the key
|
||||
// is likely undefined and we'll pass it straight to the
|
||||
// callback.
|
||||
if (result) {
|
||||
result = _deserialize(result);
|
||||
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;
|
||||
|
||||
// Remove all keys from the datastore, effectively destroying all data in
|
||||
// the app's key/value store!
|
||||
function clear(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
localStorage.clear();
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
resolve();
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Retrieve an item from the store. Unlike the original async_storage
|
||||
// library in Gaia, we don't modify return values at all. If a key's value
|
||||
// is `undefined`, we pass that value to the callback function.
|
||||
function getItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
try {
|
||||
var result = localStorage.getItem(keyPrefix + key);
|
||||
|
||||
// If a result was found, parse it from the serialized
|
||||
// string into a JS object. If result isn't truthy, the key
|
||||
// is likely undefined and we'll pass it straight to the
|
||||
// callback.
|
||||
if (result) {
|
||||
result = _deserialize(result);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
if (callback) {
|
||||
callback(null, e);
|
||||
}
|
||||
|
||||
reject(e);
|
||||
}
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Same as localStorage's key() method, except takes a callback.
|
||||
function key(n, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var result;
|
||||
try {
|
||||
result = localStorage.key(n);
|
||||
} catch (error) {
|
||||
result = null;
|
||||
}
|
||||
|
||||
// Remove the prefix from the key, if a key is found.
|
||||
if (result) {
|
||||
result = result.substring(keyPrefix.length);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
resolve(result);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function keys(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var length = localStorage.length;
|
||||
var keys = [];
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
keys.push(localStorage.key(i).substring(keyPrefix.length));
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(keys);
|
||||
}
|
||||
|
||||
resolve(keys);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Supply the number of keys in the datastore to the callback function.
|
||||
function length(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var result = localStorage.length;
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove an item from the store, nice and simple.
|
||||
function removeItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
localStorage.removeItem(keyPrefix + key);
|
||||
|
||||
if (callback) {
|
||||
callback(null, e);
|
||||
callback();
|
||||
}
|
||||
|
||||
reject(e);
|
||||
}
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Same as localStorage's key() method, except takes a callback.
|
||||
function key(n, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var result;
|
||||
try {
|
||||
result = localStorage.key(n);
|
||||
} catch (error) {
|
||||
result = null;
|
||||
}
|
||||
|
||||
// Remove the prefix from the key, if a key is found.
|
||||
if (result) {
|
||||
result = result.substring(keyPrefix.length);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
resolve(result);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function keys(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var length = localStorage.length;
|
||||
var keys = [];
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
keys.push(localStorage.key(i).substring(keyPrefix.length));
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(keys);
|
||||
}
|
||||
|
||||
resolve(keys);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Supply the number of keys in the datastore to the callback function.
|
||||
function length(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
var result = localStorage.length;
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove an item from the store, nice and simple.
|
||||
function removeItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
localStorage.removeItem(keyPrefix + key);
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
resolve();
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Deserialize data we've inserted into a value column/field. We place
|
||||
// special markers into our strings to mark them as encoded; this isn't
|
||||
// as nice as a meta field, but it's the only sane thing we can do whilst
|
||||
// keeping localStorage support intact.
|
||||
//
|
||||
// Oftentimes this will just deserialize JSON content, but if we have a
|
||||
// special marker (SERIALIZED_MARKER, defined above), we will extract
|
||||
// some kind of arraybuffer/binary data/typed array out of the string.
|
||||
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);
|
||||
resolve();
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a buffer to a string to store, serialized, in the backend
|
||||
// storage library.
|
||||
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;
|
||||
}
|
||||
|
||||
// Serialize a value, afterwards executing a callback (which usually
|
||||
// instructs the `setItem()` callback/promise to be executed). This is how
|
||||
// we store binary data with 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...
|
||||
// Deserialize data we've inserted into a value column/field. We place
|
||||
// special markers into our strings to mark them as encoded; this isn't
|
||||
// as nice as a meta field, but it's the only sane thing we can do whilst
|
||||
// keeping localStorage support intact.
|
||||
//
|
||||
// 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"));
|
||||
}
|
||||
// Oftentimes this will just deserialize JSON content, but if we have a
|
||||
// special marker (SERIALIZED_MARKER, defined above), we will extract
|
||||
// some kind of arraybuffer/binary data/typed array out of the string.
|
||||
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);
|
||||
}
|
||||
|
||||
callback(marker + _bufferToString(buffer));
|
||||
} else if (valueString === "[object Blob]") {
|
||||
// Conver the blob to a binaryArray and then to a string.
|
||||
var fileReader = new FileReader();
|
||||
// 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);
|
||||
|
||||
fileReader.onload = function() {
|
||||
var str = _bufferToString(this.result);
|
||||
// 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);
|
||||
}
|
||||
|
||||
callback(SERIALIZED_MARKER + TYPE_BLOB + str);
|
||||
};
|
||||
|
||||
fileReader.readAsArrayBuffer(value);
|
||||
} else {
|
||||
try {
|
||||
callback(JSON.stringify(value));
|
||||
} catch (e) {
|
||||
if (this.console && this.console.error) {
|
||||
this.console.error("Couldn't convert value into a JSON string: ", value);
|
||||
}
|
||||
|
||||
callback(null, e);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set a key's value and run an optional callback once the value is set.
|
||||
// Unlike Gaia's implementation, the callback function is passed the value,
|
||||
// in case you want to operate on that value only after you're sure it
|
||||
// saved, or something like that.
|
||||
function setItem(key, value, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
// Convert undefined values to null.
|
||||
// https://github.com/mozilla/localForage/pull/42
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
// Converts a buffer to a string to store, serialized, in the backend
|
||||
// storage library.
|
||||
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;
|
||||
}
|
||||
|
||||
// Serialize a value, afterwards executing a callback (which usually
|
||||
// instructs the `setItem()` callback/promise to be executed). This is how
|
||||
// we store binary data with 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"));
|
||||
}
|
||||
}
|
||||
|
||||
// Save the original value to pass to the callback.
|
||||
var originalValue = value;
|
||||
callback(marker + _bufferToString(buffer));
|
||||
} else if (valueString === "[object Blob]") {
|
||||
// Conver the blob to a binaryArray and then to a string.
|
||||
var fileReader = new FileReader();
|
||||
|
||||
_serialize(value, function(value, error) {
|
||||
if (error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
fileReader.onload = function() {
|
||||
var str = _bufferToString(this.result);
|
||||
|
||||
reject(error);
|
||||
} else {
|
||||
try {
|
||||
localStorage.setItem(keyPrefix + key, value);
|
||||
} catch (e) {
|
||||
// localStorage capacity exceeded.
|
||||
// TODO: Make this a specific error/event.
|
||||
if (e.name === 'QuotaExceededError' ||
|
||||
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
|
||||
if (callback) {
|
||||
callback(null, e);
|
||||
}
|
||||
callback(SERIALIZED_MARKER + TYPE_BLOB + str);
|
||||
};
|
||||
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(originalValue);
|
||||
}
|
||||
|
||||
resolve(originalValue);
|
||||
fileReader.readAsArrayBuffer(value);
|
||||
} else {
|
||||
try {
|
||||
callback(JSON.stringify(value));
|
||||
} catch (e) {
|
||||
if (this.console && this.console.error) {
|
||||
this.console.error("Couldn't convert value into a JSON string: ", value);
|
||||
}
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_driver: 'localStorageWrapper',
|
||||
_initStorage: _initStorage,
|
||||
// Default API, from Gaia/localStorage.
|
||||
getItem: getItem,
|
||||
setItem: setItem,
|
||||
removeItem: removeItem,
|
||||
clear: clear,
|
||||
length: length,
|
||||
key: key,
|
||||
keys: keys
|
||||
};
|
||||
callback(null, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set a key's value and run an optional callback once the value is set.
|
||||
// Unlike Gaia's implementation, the callback function is passed the value,
|
||||
// in case you want to operate on that value only after you're sure it
|
||||
// saved, or something like that.
|
||||
function setItem(key, value, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.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) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
} else {
|
||||
try {
|
||||
localStorage.setItem(keyPrefix + key, value);
|
||||
} catch (e) {
|
||||
// localStorage capacity exceeded.
|
||||
// TODO: Make this a specific error/event.
|
||||
if (e.name === 'QuotaExceededError' ||
|
||||
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
|
||||
if (callback) {
|
||||
callback(null, e);
|
||||
}
|
||||
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(originalValue);
|
||||
}
|
||||
|
||||
resolve(originalValue);
|
||||
}
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
var localStorageWrapper = {
|
||||
_driver: 'localStorageWrapper',
|
||||
_initStorage: _initStorage,
|
||||
// Default API, from Gaia/localStorage.
|
||||
getItem: getItem,
|
||||
setItem: setItem,
|
||||
removeItem: removeItem,
|
||||
clear: clear,
|
||||
length: length,
|
||||
key: key,
|
||||
keys: keys
|
||||
};
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('localStorageWrapper', function() {
|
||||
return localStorageWrapper;
|
||||
});
|
||||
} else if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = localStorageWrapper;
|
||||
} else {
|
||||
this.localStorageWrapper = localStorageWrapper;
|
||||
}
|
||||
}).call(this);
|
||||
|
||||
@ -1,9 +1,3 @@
|
||||
// Exclude 'redefinition of {a}' from jshint as we are declaring a local var
|
||||
// that appears to conflict with the global namespace.
|
||||
// http://jslinterrors.com/redefinition-of-a
|
||||
/*jshint -W079 */
|
||||
/*jshint latedef:false */
|
||||
|
||||
/*
|
||||
* Includes code from:
|
||||
*
|
||||
@ -13,491 +7,506 @@
|
||||
* Copyright (c) 2012 Niklas von Hertzen
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var Promise = require('promise');
|
||||
// Sadly, the best way to save binary data in WebSQL is Base64 serializing
|
||||
// it, so this is how we store it to prevent very strange errors with less
|
||||
// verbose ways of binary <-> string data storage.
|
||||
var BASE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
// Sadly, the best way to save binary data in WebSQL is Base64 serializing
|
||||
// it, so this is how we store it to prevent very strange errors with less
|
||||
// verbose ways of binary <-> string data storage.
|
||||
var BASE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
// Promises!
|
||||
var Promise = (typeof module !== 'undefined' && module.exports) ?
|
||||
require('promise') : this.Promise;
|
||||
|
||||
var openDatabase = window.openDatabase;
|
||||
var db = null;
|
||||
var dbInfo = {};
|
||||
var openDatabase = this.openDatabase;
|
||||
var db = null;
|
||||
var dbInfo = {};
|
||||
|
||||
var SERIALIZED_MARKER = '__lfsc__:';
|
||||
var SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER.length;
|
||||
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;
|
||||
// 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;
|
||||
|
||||
// If WebSQL methods aren't available, we can stop now.
|
||||
if (!openDatabase) {
|
||||
openDatabase = null;
|
||||
}
|
||||
|
||||
// Open the WebSQL database (automatically creates one if one didn't
|
||||
// previously exist), using any options set in the config.
|
||||
function _initStorage(options) {
|
||||
var _this = this;
|
||||
|
||||
if (options) {
|
||||
for (var i in options) {
|
||||
dbInfo[i] = typeof(options[i]) !== 'string' ? options[i].toString() : options[i];
|
||||
}
|
||||
// If WebSQL methods aren't available, we can stop now.
|
||||
if (!openDatabase) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
// Open the database; the openDatabase API will automatically
|
||||
// create it for us if it doesn't exist.
|
||||
try {
|
||||
db = openDatabase(dbInfo.name, dbInfo.version,
|
||||
dbInfo.description, dbInfo.size);
|
||||
} catch (e) {
|
||||
return _this.setDriver('localStorageWrapper').then(resolve, reject);
|
||||
// Open the WebSQL database (automatically creates one if one didn't
|
||||
// previously exist), using any options set in the config.
|
||||
function _initStorage(options) {
|
||||
var _this = this;
|
||||
|
||||
if (options) {
|
||||
for (var i in options) {
|
||||
dbInfo[i] = typeof(options[i]) !== 'string' ? options[i].toString() : options[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Create our key/value table if it doesn't exist.
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('CREATE TABLE IF NOT EXISTS ' + dbInfo.storeName +
|
||||
' (id INTEGER PRIMARY KEY, key unique, value)', [], function() {
|
||||
resolve();
|
||||
}, function(t, error) {
|
||||
reject(error);
|
||||
return new Promise(function(resolve, reject) {
|
||||
// Open the database; the openDatabase API will automatically
|
||||
// create it for us if it doesn't exist.
|
||||
try {
|
||||
db = openDatabase(dbInfo.name, dbInfo.version,
|
||||
dbInfo.description, dbInfo.size);
|
||||
} catch (e) {
|
||||
return _this.setDriver('localStorageWrapper').then(resolve, reject);
|
||||
}
|
||||
|
||||
// Create our key/value table if it doesn't exist.
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('CREATE TABLE IF NOT EXISTS ' + dbInfo.storeName +
|
||||
' (id INTEGER PRIMARY KEY, key unique, value)', [], function() {
|
||||
resolve();
|
||||
}, function(t, error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('SELECT * FROM ' + dbInfo.storeName +
|
||||
' WHERE key = ? LIMIT 1', [key], function(t, results) {
|
||||
var result = results.rows.length ? results.rows.item(0).value : null;
|
||||
function getItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('SELECT * FROM ' + dbInfo.storeName +
|
||||
' WHERE key = ? LIMIT 1', [key], function(t, results) {
|
||||
var result = results.rows.length ? results.rows.item(0).value : null;
|
||||
|
||||
// Check to see if this is serialized content we need to
|
||||
// unpack.
|
||||
if (result) {
|
||||
result = _deserialize(result);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function setItem(key, value, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
// The localStorage API doesn't return undefined values in an
|
||||
// "expected" way, so undefined is always cast to null in all
|
||||
// drivers. See: 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 {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('INSERT OR REPLACE INTO ' + dbInfo.storeName +
|
||||
' (key, value) VALUES (?, ?)', [key, value], function() {
|
||||
if (callback) {
|
||||
callback(originalValue);
|
||||
}
|
||||
|
||||
resolve(originalValue);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
}, function(sqlError) { // The transaction failed; check
|
||||
// to see if it's a quota error.
|
||||
if (sqlError.code === sqlError.QUOTA_ERR) {
|
||||
// We reject the callback outright for now, but
|
||||
// it's worth trying to re-run the transaction.
|
||||
// Even if the user accepts the prompt to use
|
||||
// more storage on Safari, this error will
|
||||
// be called.
|
||||
//
|
||||
// TODO: Try to re-run the transaction.
|
||||
if (callback) {
|
||||
callback(null, sqlError);
|
||||
}
|
||||
|
||||
reject(sqlError);
|
||||
// Check to see if this is serialized content we need to
|
||||
// unpack.
|
||||
if (result) {
|
||||
result = _deserialize(result);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function setItem(key, value, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
// The localStorage API doesn't return undefined values in an
|
||||
// "expected" way, so undefined is always cast to null in all
|
||||
// drivers. See: https://github.com/mozilla/localForage/pull/42
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function removeItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('DELETE FROM ' + dbInfo.storeName +
|
||||
' WHERE key = ?', [key], function() {
|
||||
if (callback) {
|
||||
callback();
|
||||
// Save the original value to pass to the callback.
|
||||
var originalValue = value;
|
||||
|
||||
_serialize(value, function(value, error) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('INSERT OR REPLACE INTO ' + dbInfo.storeName +
|
||||
' (key, value) VALUES (?, ?)', [key, value], function() {
|
||||
if (callback) {
|
||||
callback(originalValue);
|
||||
}
|
||||
|
||||
resolve(originalValue);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
}, function(sqlError) { // The transaction failed; check
|
||||
// to see if it's a quota error.
|
||||
if (sqlError.code === sqlError.QUOTA_ERR) {
|
||||
// We reject the callback outright for now, but
|
||||
// it's worth trying to re-run the transaction.
|
||||
// Even if the user accepts the prompt to use
|
||||
// more storage on Safari, this error will
|
||||
// be called.
|
||||
//
|
||||
// TODO: Try to re-run the transaction.
|
||||
if (callback) {
|
||||
callback(null, sqlError);
|
||||
}
|
||||
|
||||
reject(sqlError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resolve();
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Deletes every item in the table.
|
||||
// TODO: Find out if this resets the AUTO_INCREMENT number.
|
||||
function clear(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('DELETE FROM ' + dbInfo.storeName, [], function() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
resolve();
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Does a simple `COUNT(key)` to get the number of items stored in
|
||||
// localForage.
|
||||
function length(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
// Ahhh, SQL makes this one soooooo easy.
|
||||
t.executeSql('SELECT COUNT(key) as c FROM ' +
|
||||
dbInfo.storeName, [], function(t, results) {
|
||||
var result = results.rows.item(0).c;
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Return the key located at key index X; essentially gets the key from a
|
||||
// `WHERE id = ?`. This is the most efficient way I can think to implement
|
||||
// this rarely-used (in my experience) part of the API, but it can seem
|
||||
// inconsistent, because we do `INSERT OR REPLACE INTO` on `setItem()`, so
|
||||
// the ID of each key will change every time it's updated. Perhaps a stored
|
||||
// procedure for the `setItem()` SQL would solve this problem?
|
||||
// TODO: Don't change ID on `setItem()`.
|
||||
function key(n, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('SELECT key FROM ' + dbInfo.storeName +
|
||||
' WHERE id = ? LIMIT 1', [n + 1], function(t, results) {
|
||||
var result = results.rows.length ? results.rows.item(0).key : null;
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function keys(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('SELECT key FROM ' + dbInfo.storeName, [],
|
||||
function(t, results) {
|
||||
var length = results.rows.length;
|
||||
var keys = [];
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
keys.push(results.rows.item(i).key);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(keys);
|
||||
}
|
||||
|
||||
resolve(keys);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Converts a buffer to a string to store, serialized, in the backend
|
||||
// storage library.
|
||||
function _bufferToString(buffer) {
|
||||
// base64-arraybuffer
|
||||
var bytes = new Uint8Array(buffer);
|
||||
var i;
|
||||
var base64String = '';
|
||||
|
||||
for (i = 0; i < bytes.length; i += 3) {
|
||||
/*jslint bitwise: true */
|
||||
base64String += BASE_CHARS[bytes[i] >> 2];
|
||||
base64String += BASE_CHARS[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
|
||||
base64String += BASE_CHARS[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
|
||||
base64String += BASE_CHARS[bytes[i + 2] & 63];
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
if ((bytes.length % 3) === 2) {
|
||||
base64String = base64String.substring(0, base64String.length - 1) + "=";
|
||||
} else if (bytes.length % 3 === 1) {
|
||||
base64String = base64String.substring(0, base64String.length - 2) + "==";
|
||||
function removeItem(key, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('DELETE FROM ' + dbInfo.storeName +
|
||||
' WHERE key = ?', [key], function() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
resolve();
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
return base64String;
|
||||
}
|
||||
// Deletes every item in the table.
|
||||
// TODO: Find out if this resets the AUTO_INCREMENT number.
|
||||
function clear(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('DELETE FROM ' + dbInfo.storeName, [], function() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
// Deserialize data we've inserted into a value column/field. We place
|
||||
// special markers into our strings to mark them as encoded; this isn't
|
||||
// as nice as a meta field, but it's the only sane thing we can do whilst
|
||||
// keeping localStorage support intact.
|
||||
//
|
||||
// Oftentimes this will just deserialize JSON content, but if we have a
|
||||
// special marker (SERIALIZED_MARKER, defined above), we will extract
|
||||
// some kind of arraybuffer/binary data/typed array out of the string.
|
||||
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);
|
||||
resolve();
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Does a simple `COUNT(key)` to get the number of items stored in
|
||||
// localForage.
|
||||
function length(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
// Ahhh, SQL makes this one soooooo easy.
|
||||
t.executeSql('SELECT COUNT(key) as c FROM ' +
|
||||
dbInfo.storeName, [], function(t, results) {
|
||||
var result = results.rows.item(0).c;
|
||||
|
||||
// Fill the string into a ArrayBuffer.
|
||||
var bufferLength = serializedString.length * 0.75;
|
||||
var len = serializedString.length;
|
||||
var i;
|
||||
var p = 0;
|
||||
var encoded1, encoded2, encoded3, encoded4;
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
if (serializedString[serializedString.length - 1] === "=") {
|
||||
bufferLength--;
|
||||
if (serializedString[serializedString.length - 2] === "=") {
|
||||
bufferLength--;
|
||||
resolve(result);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Return the key located at key index X; essentially gets the key from a
|
||||
// `WHERE id = ?`. This is the most efficient way I can think to implement
|
||||
// this rarely-used (in my experience) part of the API, but it can seem
|
||||
// inconsistent, because we do `INSERT OR REPLACE INTO` on `setItem()`, so
|
||||
// the ID of each key will change every time it's updated. Perhaps a stored
|
||||
// procedure for the `setItem()` SQL would solve this problem?
|
||||
// TODO: Don't change ID on `setItem()`.
|
||||
function key(n, callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('SELECT key FROM ' + dbInfo.storeName +
|
||||
' WHERE id = ? LIMIT 1', [n + 1], function(t, results) {
|
||||
var result = results.rows.length ? results.rows.item(0).key : null;
|
||||
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function keys(callback) {
|
||||
var _this = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
_this.ready().then(function() {
|
||||
db.transaction(function(t) {
|
||||
t.executeSql('SELECT key FROM ' + dbInfo.storeName, [],
|
||||
function(t, results) {
|
||||
var length = results.rows.length;
|
||||
var keys = [];
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
keys.push(results.rows.item(i).key);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(keys);
|
||||
}
|
||||
|
||||
resolve(keys);
|
||||
}, function(t, error) {
|
||||
if (callback) {
|
||||
callback(null, error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Converts a buffer to a string to store, serialized, in the backend
|
||||
// storage library.
|
||||
function _bufferToString(buffer) {
|
||||
// base64-arraybuffer
|
||||
var bytes = new Uint8Array(buffer);
|
||||
var i;
|
||||
var base64String = '';
|
||||
|
||||
for (i = 0; i < bytes.length; i += 3) {
|
||||
/*jslint bitwise: true */
|
||||
base64String += BASE_CHARS[bytes[i] >> 2];
|
||||
base64String += BASE_CHARS[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
|
||||
base64String += BASE_CHARS[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
|
||||
base64String += BASE_CHARS[bytes[i + 2] & 63];
|
||||
}
|
||||
|
||||
if ((bytes.length % 3) === 2) {
|
||||
base64String = base64String.substring(0, base64String.length - 1) + "=";
|
||||
} else if (bytes.length % 3 === 1) {
|
||||
base64String = base64String.substring(0, base64String.length - 2) + "==";
|
||||
}
|
||||
|
||||
return base64String;
|
||||
}
|
||||
|
||||
var buffer = new ArrayBuffer(bufferLength);
|
||||
var bytes = new Uint8Array(buffer);
|
||||
|
||||
for (i = 0; i < len; i+=4) {
|
||||
encoded1 = BASE_CHARS.indexOf(serializedString[i]);
|
||||
encoded2 = BASE_CHARS.indexOf(serializedString[i+1]);
|
||||
encoded3 = BASE_CHARS.indexOf(serializedString[i+2]);
|
||||
encoded4 = BASE_CHARS.indexOf(serializedString[i+3]);
|
||||
|
||||
/*jslint bitwise: true */
|
||||
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
|
||||
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
|
||||
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize a value, afterwards executing a callback (which usually
|
||||
// instructs the `setItem()` callback/promise to be executed). This is how
|
||||
// we store binary data with 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...
|
||||
// Deserialize data we've inserted into a value column/field. We place
|
||||
// special markers into our strings to mark them as encoded; this isn't
|
||||
// as nice as a meta field, but it's the only sane thing we can do whilst
|
||||
// keeping localStorage support intact.
|
||||
//
|
||||
// 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;
|
||||
// Oftentimes this will just deserialize JSON content, but if we have a
|
||||
// special marker (SERIALIZED_MARKER, defined above), we will extract
|
||||
// some kind of arraybuffer/binary data/typed array out of the string.
|
||||
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);
|
||||
}
|
||||
|
||||
if (value instanceof ArrayBuffer) {
|
||||
buffer = value;
|
||||
marker += TYPE_ARRAYBUFFER;
|
||||
} else {
|
||||
buffer = value.buffer;
|
||||
// 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);
|
||||
|
||||
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"));
|
||||
// Fill the string into a ArrayBuffer.
|
||||
var bufferLength = serializedString.length * 0.75;
|
||||
var len = serializedString.length;
|
||||
var i;
|
||||
var p = 0;
|
||||
var encoded1, encoded2, encoded3, encoded4;
|
||||
|
||||
if (serializedString[serializedString.length - 1] === "=") {
|
||||
bufferLength--;
|
||||
if (serializedString[serializedString.length - 2] === "=") {
|
||||
bufferLength--;
|
||||
}
|
||||
}
|
||||
|
||||
callback(marker + _bufferToString(buffer));
|
||||
} else if (valueString === "[object Blob]") {
|
||||
// Conver the blob to a binaryArray and then to a string.
|
||||
var fileReader = new FileReader();
|
||||
var buffer = new ArrayBuffer(bufferLength);
|
||||
var bytes = new Uint8Array(buffer);
|
||||
|
||||
fileReader.onload = function() {
|
||||
var str = _bufferToString(this.result);
|
||||
for (i = 0; i < len; i+=4) {
|
||||
encoded1 = BASE_CHARS.indexOf(serializedString[i]);
|
||||
encoded2 = BASE_CHARS.indexOf(serializedString[i+1]);
|
||||
encoded3 = BASE_CHARS.indexOf(serializedString[i+2]);
|
||||
encoded4 = BASE_CHARS.indexOf(serializedString[i+3]);
|
||||
|
||||
callback(SERIALIZED_MARKER + TYPE_BLOB + str);
|
||||
};
|
||||
/*jslint bitwise: true */
|
||||
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
|
||||
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
|
||||
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
|
||||
}
|
||||
|
||||
fileReader.readAsArrayBuffer(value);
|
||||
} else {
|
||||
try {
|
||||
callback(JSON.stringify(value));
|
||||
} catch (e) {
|
||||
if (this.console && this.console.error) {
|
||||
this.console.error("Couldn't convert value into a JSON string: ", value);
|
||||
}
|
||||
|
||||
callback(null, e);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_driver: 'webSQLStorage',
|
||||
_initStorage: _initStorage,
|
||||
getItem: getItem,
|
||||
setItem: setItem,
|
||||
removeItem: removeItem,
|
||||
clear: clear,
|
||||
length: length,
|
||||
key: key,
|
||||
keys: keys
|
||||
};
|
||||
// Serialize a value, afterwards executing a callback (which usually
|
||||
// instructs the `setItem()` callback/promise to be executed). This is how
|
||||
// we store binary data with 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) {
|
||||
if (this.console && this.console.error) {
|
||||
this.console.error("Couldn't convert value into a JSON string: ", value);
|
||||
}
|
||||
|
||||
callback(null, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var webSQLStorage = {
|
||||
_driver: 'webSQLStorage',
|
||||
_initStorage: _initStorage,
|
||||
getItem: getItem,
|
||||
setItem: setItem,
|
||||
removeItem: removeItem,
|
||||
clear: clear,
|
||||
length: length,
|
||||
key: key,
|
||||
keys: keys
|
||||
};
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('webSQLStorage', function() {
|
||||
return webSQLStorage;
|
||||
});
|
||||
} else if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = webSQLStorage;
|
||||
} else {
|
||||
this.webSQLStorage = webSQLStorage;
|
||||
}
|
||||
}).call(this);
|
||||
|
||||
@ -1,241 +1,279 @@
|
||||
/*jshint latedef:false */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var Promise = require('promise');
|
||||
// Promises!
|
||||
var Promise = (typeof module !== 'undefined' && module.exports) ?
|
||||
require('promise') : this.Promise;
|
||||
|
||||
/**
|
||||
* Drivers
|
||||
*/
|
||||
var indexeddb = require('./drivers/indexeddb');
|
||||
var localstorage = require('./drivers/localstorage');
|
||||
var websql = require('./drivers/websql');
|
||||
|
||||
var DriverType = {
|
||||
INDEXEDDB: 'asyncStorage',
|
||||
LOCALSTORAGE: 'localStorageWrapper',
|
||||
WEBSQL: 'webSQLStorage'
|
||||
};
|
||||
|
||||
var DEFAULT_DRIVER_ORDER = [
|
||||
DriverType.INDEXEDDB,
|
||||
DriverType.WEBSQL,
|
||||
DriverType.LOCALSTORAGE
|
||||
];
|
||||
|
||||
/**
|
||||
* Define library methods
|
||||
*/
|
||||
var LibraryMethods = [
|
||||
'clear',
|
||||
'getItem',
|
||||
'key',
|
||||
'keys',
|
||||
'length',
|
||||
'removeItem',
|
||||
'setItem'
|
||||
];
|
||||
|
||||
/**
|
||||
* Export
|
||||
*/
|
||||
var localForage = module.exports = {
|
||||
INDEXEDDB: DriverType.INDEXEDDB,
|
||||
LOCALSTORAGE: DriverType.LOCALSTORAGE,
|
||||
WEBSQL: DriverType.WEBSQL,
|
||||
|
||||
_config: {
|
||||
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
|
||||
},
|
||||
|
||||
// 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.
|
||||
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) {
|
||||
this._config[i] = options[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (typeof(options) === 'string') {
|
||||
return this._config[options];
|
||||
} else {
|
||||
return this._config;
|
||||
}
|
||||
},
|
||||
|
||||
driver: function() {
|
||||
return this._driver || null;
|
||||
},
|
||||
|
||||
_ready: false,
|
||||
|
||||
_driverSet: null,
|
||||
|
||||
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);
|
||||
|
||||
if (errorCallback) {
|
||||
errorCallback(error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self._ready = null;
|
||||
|
||||
// Extend using appropriate driver
|
||||
var driver;
|
||||
switch (driverName) {
|
||||
case self.INDEXEDDB:
|
||||
driver = indexeddb;
|
||||
break;
|
||||
case self.LOCALSTORAGE:
|
||||
driver = localstorage;
|
||||
break;
|
||||
case self.WEBSQL:
|
||||
driver = websql;
|
||||
}
|
||||
|
||||
self._extend(driver);
|
||||
|
||||
// Return
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
return this._driverSet;
|
||||
},
|
||||
|
||||
_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;
|
||||
},
|
||||
|
||||
supports: function(driverName) {
|
||||
return !!driverSupport[driverName];
|
||||
},
|
||||
|
||||
ready: function(callback) {
|
||||
var ready = new Promise(function(resolve, reject) {
|
||||
localForage._driverSet.then(function() {
|
||||
if (localForage._ready === null) {
|
||||
localForage._ready = localForage._initStorage(
|
||||
localForage._config);
|
||||
}
|
||||
|
||||
localForage._ready.then(resolve, reject);
|
||||
}, reject);
|
||||
});
|
||||
|
||||
ready.then(callback, callback);
|
||||
|
||||
return ready;
|
||||
},
|
||||
|
||||
_extend: function(libraryMethodsAndProperties) {
|
||||
for (var i in libraryMethodsAndProperties) {
|
||||
if (libraryMethodsAndProperties.hasOwnProperty(i)) {
|
||||
this[i] = libraryMethodsAndProperties[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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(_this) {
|
||||
// Initialize IndexedDB; fall back to vendor-prefixed versions
|
||||
// if needed.
|
||||
var indexedDB = indexedDB || _this.indexedDB || _this.webkitIndexedDB ||
|
||||
_this.mozIndexedDB || _this.OIndexedDB ||
|
||||
_this.msIndexedDB;
|
||||
|
||||
var result = {};
|
||||
|
||||
result[localForage.WEBSQL] = !!_this.openDatabase;
|
||||
result[localForage.INDEXEDDB] = !!(
|
||||
indexedDB &&
|
||||
typeof indexedDB.open === 'function' &&
|
||||
indexedDB.open('_localforage_spec_test', 1)
|
||||
.onupgradeneeded === null
|
||||
);
|
||||
|
||||
result[localForage.LOCALSTORAGE] = !!(function() {
|
||||
try {
|
||||
return (localStorage &&
|
||||
typeof localStorage.setItem === 'function');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
return result;
|
||||
})(window);
|
||||
|
||||
function callWhenReady(libraryMethod) {
|
||||
localForage[libraryMethod] = function() {
|
||||
var _args = arguments;
|
||||
return localForage.ready().then(function() {
|
||||
return localForage[libraryMethod].apply(localForage, _args);
|
||||
});
|
||||
var DriverType = {
|
||||
INDEXEDDB: 'asyncStorage',
|
||||
LOCALSTORAGE: 'localStorageWrapper',
|
||||
WEBSQL: 'webSQLStorage'
|
||||
};
|
||||
}
|
||||
|
||||
// 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(LibraryMethods[i]);
|
||||
}
|
||||
var DEFAULT_DRIVER_ORDER = [
|
||||
DriverType.INDEXEDDB,
|
||||
DriverType.WEBSQL,
|
||||
DriverType.LOCALSTORAGE
|
||||
];
|
||||
|
||||
localForage.setDriver(DEFAULT_DRIVER_ORDER);
|
||||
var LibraryMethods = [
|
||||
'clear',
|
||||
'getItem',
|
||||
'key',
|
||||
'keys',
|
||||
'length',
|
||||
'removeItem',
|
||||
'setItem'
|
||||
];
|
||||
|
||||
var ModuleType = {
|
||||
DEFINE: 1,
|
||||
EXPORT: 2,
|
||||
WINDOW: 3
|
||||
};
|
||||
|
||||
// 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(_this) {
|
||||
// Initialize IndexedDB; fall back to vendor-prefixed versions
|
||||
// if needed.
|
||||
var indexedDB = indexedDB || _this.indexedDB || _this.webkitIndexedDB ||
|
||||
_this.mozIndexedDB || _this.OIndexedDB ||
|
||||
_this.msIndexedDB;
|
||||
|
||||
var result = {};
|
||||
|
||||
result[DriverType.WEBSQL] = !!_this.openDatabase;
|
||||
result[DriverType.INDEXEDDB] = !!(
|
||||
indexedDB &&
|
||||
typeof indexedDB.open === 'function' &&
|
||||
indexedDB.open('_localforage_spec_test', 1)
|
||||
.onupgradeneeded === null
|
||||
);
|
||||
|
||||
result[DriverType.LOCALSTORAGE] = !!(function() {
|
||||
try {
|
||||
return (localStorage &&
|
||||
typeof localStorage.setItem === 'function');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
return result;
|
||||
})(this);
|
||||
|
||||
// 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 _this = this;
|
||||
var localForage = {
|
||||
INDEXEDDB: DriverType.INDEXEDDB,
|
||||
LOCALSTORAGE: DriverType.LOCALSTORAGE,
|
||||
WEBSQL: DriverType.WEBSQL,
|
||||
|
||||
_config: {
|
||||
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
|
||||
},
|
||||
_driverSet: null,
|
||||
_ready: false,
|
||||
|
||||
// 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.
|
||||
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) {
|
||||
this._config[i] = options[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (typeof(options) === 'string') {
|
||||
return this._config[options];
|
||||
} else {
|
||||
return this._config;
|
||||
}
|
||||
},
|
||||
|
||||
driver: function() {
|
||||
return this._driver || null;
|
||||
},
|
||||
|
||||
ready: function(callback) {
|
||||
var ready = new Promise(function(resolve, reject) {
|
||||
localForage._driverSet.then(function() {
|
||||
if (localForage._ready === null) {
|
||||
localForage._ready = localForage._initStorage(
|
||||
localForage._config);
|
||||
}
|
||||
|
||||
localForage._ready.then(resolve, reject);
|
||||
}, reject);
|
||||
});
|
||||
|
||||
ready.then(callback, callback);
|
||||
|
||||
return ready;
|
||||
},
|
||||
|
||||
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);
|
||||
|
||||
if (errorCallback) {
|
||||
errorCallback(error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
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(_this[driverName]);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
return this._driverSet;
|
||||
},
|
||||
|
||||
supports: function(driverName) {
|
||||
return !!driverSupport[driverName];
|
||||
},
|
||||
|
||||
_extend: function(libraryMethodsAndProperties) {
|
||||
for (var i in libraryMethodsAndProperties) {
|
||||
if (libraryMethodsAndProperties.hasOwnProperty(i)) {
|
||||
this[i] = libraryMethodsAndProperties[i];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_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;
|
||||
}
|
||||
};
|
||||
|
||||
function callWhenReady(libraryMethod) {
|
||||
localForage[libraryMethod] = function() {
|
||||
var _args = arguments;
|
||||
return localForage.ready().then(function() {
|
||||
return localForage[libraryMethod].apply(localForage, _args);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 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(LibraryMethods[i]);
|
||||
}
|
||||
|
||||
localForage.setDriver(DEFAULT_DRIVER_ORDER);
|
||||
|
||||
// We allow localForage to be declared as a module or as a library
|
||||
// available without AMD/require.js.
|
||||
if (moduleType === ModuleType.DEFINE) {
|
||||
define(function() {
|
||||
return localForage;
|
||||
});
|
||||
} else if (moduleType === ModuleType.EXPORT) {
|
||||
module.exports = localForage;
|
||||
} else {
|
||||
this.localforage = localForage;
|
||||
}
|
||||
}).call(this);
|
||||
|
||||
1944
test/component.js
1944
test/component.js
File diff suppressed because it is too large
Load Diff
57
test/runner.js
Normal file
57
test/runner.js
Normal file
@ -0,0 +1,57 @@
|
||||
// Run before window.onload to make sure the specs have access to describe()
|
||||
// and other mocha methods. All feels very hacky though :-/
|
||||
var mocha = this.mocha;
|
||||
|
||||
mocha.setup('bdd');
|
||||
|
||||
function runTests() {
|
||||
var runner = mocha.run();
|
||||
|
||||
var failedTests = [];
|
||||
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
function flattenTitles(test) {
|
||||
var titles = [];
|
||||
|
||||
while (test.parent.title) {
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
|
||||
return titles.reverse();
|
||||
}
|
||||
|
||||
function logFailure(test, err) {
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
}
|
||||
|
||||
var require = this.require;
|
||||
if (require) {
|
||||
require(['/dist/localforage.js'], function(localforage) {
|
||||
window.localforage = localforage;
|
||||
|
||||
require([
|
||||
'/test/test.api.js',
|
||||
'/test/test.config.js',
|
||||
'/test/test.datatypes.js',
|
||||
'/test/test.drivers.js',
|
||||
'/test/test.iframes.js',
|
||||
'/test/test.webworkers.js'
|
||||
], runTests);
|
||||
});
|
||||
} else {
|
||||
this.addEventListener('load', runTests);
|
||||
}
|
||||
@ -1,14 +1,29 @@
|
||||
/* global afterEach:true, before:true, beforeEach:true, describe:true, expect:true, it:true, Modernizr:true, Promise:true */
|
||||
/* global afterEach:true, before:true, beforeEach:true, describe:true, expect:true, it:true, Modernizr:true, Promise:true, require:true */
|
||||
var DRIVERS = [
|
||||
localforage.INDEXEDDB,
|
||||
localforage.LOCALSTORAGE,
|
||||
localforage.WEBSQL
|
||||
];
|
||||
|
||||
var componentBuild = window.require && window.require.modules &&
|
||||
window.require.modules.localforage &&
|
||||
window.require.modules.localforage.component;
|
||||
|
||||
describe('localForage API', function() {
|
||||
beforeEach(function(done) {
|
||||
localforage.clear(done);
|
||||
});
|
||||
|
||||
// If this test is failing, you are likely missing the Promises polyfill,
|
||||
// installed via bower. Read more here:
|
||||
// https://github.com/mozilla/localForage#working-on-localforage
|
||||
it('has Promises available', function() {
|
||||
if (componentBuild) {
|
||||
expect(require('promise')).to.be.a('function');
|
||||
} else {
|
||||
expect(Promise).to.be.a('function');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('localForage', function() {
|
||||
|
||||
@ -2,65 +2,41 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>localForage require.js Tests!</title>
|
||||
<link rel="stylesheet" type="text/css" href="../node_modules/mocha/mocha.css">
|
||||
<title>localForage callWhenReady Tests!</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="/bower_components/mocha/mocha.css">
|
||||
|
||||
<script src="/bower_components/assert/assert.js"></script>
|
||||
<script src="/bower_components/mocha/mocha.js"></script>
|
||||
|
||||
<script src="/bower_components/expect/index.js"></script>
|
||||
|
||||
<!-- require.js -->
|
||||
<script src="/bower_components/requirejs/require.js"></script>
|
||||
|
||||
<!-- Modernizr -->
|
||||
<script src="/bower_components/modernizr/modernizr.js"></script>
|
||||
|
||||
<!-- Apply "root" level beforeEach hook to run test suite against
|
||||
callWhenReady API method stubs -->
|
||||
<script src="/test/test.callwhenready.js"></script>
|
||||
|
||||
<!-- Test runner -->
|
||||
<script>
|
||||
// Skip irrelevant config tests by mapping them to API tests so that
|
||||
// they will not be called.
|
||||
requirejs.config({
|
||||
map: {
|
||||
'*': {
|
||||
'/test/test.config.js': '/test/test.api.js'
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="/test/runner.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- test harness -->
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/expect.js/index.js"></script>
|
||||
<script src="../node_modules/ym-modernizr/dist/modernizr-build.js"></script>
|
||||
<script src="../node_modules/requirejs/require.js"></script>
|
||||
|
||||
<!-- specs -->
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
|
||||
beforeEach(function(done) {
|
||||
var previousDriver = localforage.driver();
|
||||
|
||||
// The API method stubs inserted by callWhenReady must be tested before
|
||||
// they are replaced by the driver, which happens as soon as it loads.
|
||||
//
|
||||
// To ensure that they work when the drivers are loaded asynchronously,
|
||||
// we run the entire test suite (except for config tests), but undefine
|
||||
// the localforage module and force it to reload before each test, so that
|
||||
// it will be initialized again.
|
||||
//
|
||||
// This ensures that the synchronous parts of localforage initialization
|
||||
// and the API calls in the tests occur first in every test, such that the
|
||||
// callWhenReady API method stubs are called before RequireJS
|
||||
// asynchronously loads the drivers that replace them.
|
||||
require.undef('../dist/localforage.js');
|
||||
require(['../dist/localforage.js'], function(localforage) {
|
||||
localforage.setDriver(previousDriver);
|
||||
window.localforage = localforage;
|
||||
done();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- run test -->
|
||||
<script>
|
||||
require(['../dist/localforage.js'], function(localforage) {
|
||||
window.localforage = localforage;
|
||||
|
||||
require([
|
||||
'./test.api.js',
|
||||
'./test.datatypes.js',
|
||||
'./test.drivers.js',
|
||||
'./test.iframes.js',
|
||||
'./test.webworkers.js'
|
||||
], function() {
|
||||
if (window.mochaPhantomJS) {
|
||||
mochaPhantomJS.run();
|
||||
} else {
|
||||
mocha.run();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -3,39 +3,38 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>localForage Component Tests!</title>
|
||||
<link rel="stylesheet" type="text/css" href="../node_modules/mocha/mocha.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- test harness -->
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/expect.js/index.js"></script>
|
||||
<script src="../node_modules/ym-modernizr/dist/modernizr-build.js"></script>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="/bower_components/mocha/mocha.css">
|
||||
|
||||
<script src="/bower_components/assert/assert.js"></script>
|
||||
<script src="/bower_components/mocha/mocha.js"></script>
|
||||
|
||||
<script src="/bower_components/expect/index.js"></script>
|
||||
|
||||
<!-- Modernizr -->
|
||||
<script src="/bower_components/modernizr/modernizr.js"></script>
|
||||
|
||||
<!-- Test runner -->
|
||||
<script src="/test/runner.js"></script>
|
||||
|
||||
<!-- localForage -->
|
||||
<script src="./component.js"></script>
|
||||
<script src="/test/localforage.component.js"></script>
|
||||
|
||||
<script>
|
||||
/* jshint unused: false */
|
||||
var localforage = require('localforage');
|
||||
</script>
|
||||
|
||||
<!-- specs -->
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script src="./test.api.js"></script>
|
||||
<script src="./test.config.js"></script>
|
||||
<script src="./test.datatypes.js"></script>
|
||||
<script src="./test.drivers.js"></script>
|
||||
<script src="./test.iframes.js"></script>
|
||||
<script src="./test.webworkers.js"></script>
|
||||
|
||||
<!-- run test -->
|
||||
<script>
|
||||
if (window.mochaPhantomJS) {
|
||||
mochaPhantomJS.run();
|
||||
} else {
|
||||
mocha.run();
|
||||
}
|
||||
</script>
|
||||
<script src="/test/test.api.js"></script>
|
||||
<script src="/test/test.config.js"></script>
|
||||
<script src="/test/test.datatypes.js"></script>
|
||||
<script src="/test/test.drivers.js"></script>
|
||||
<script src="/test/test.iframes.js"></script>
|
||||
<script src="/test/test.webworkers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -246,7 +246,7 @@ DRIVERS.forEach(function(driverName) {
|
||||
it('saves binary data', function(done) {
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
request.open('GET', './photo.jpg', true);
|
||||
request.open('GET', '/test/photo.jpg', true);
|
||||
request.responseType = 'arraybuffer';
|
||||
|
||||
// When the AJAX state changes, save the photo locally.
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<body>
|
||||
<div id="my-text"></div>
|
||||
|
||||
<script src="../dist/localforage.js"></script>
|
||||
<script src="/dist/localforage.js"></script>
|
||||
<script>localforage.setItem('my cool key',
|
||||
'I have been set').then(function() {
|
||||
localforage.getItem('my cool key').then(function(value) {
|
||||
|
||||
@ -7,7 +7,8 @@ describe('Inside iFrames', function() {
|
||||
iFrame.name = 'iframe';
|
||||
iFrame.id = 'iframe';
|
||||
// TODO: Get this to be cross-origin.
|
||||
iFrame.src = './test.iframecontents.html';
|
||||
iFrame.src = 'http://' + window.location.host +
|
||||
'/test/test.iframecontents.html';
|
||||
|
||||
window.document.body.appendChild(iFrame);
|
||||
});
|
||||
|
||||
@ -3,35 +3,33 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>localForage Tests!</title>
|
||||
<link rel="stylesheet" type="text/css" href="../node_modules/mocha/mocha.css">
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="/bower_components/mocha/mocha.css">
|
||||
|
||||
<script src="/bower_components/assert/assert.js"></script>
|
||||
<script src="/bower_components/mocha/mocha.js"></script>
|
||||
|
||||
<script src="/bower_components/expect/index.js"></script>
|
||||
|
||||
<!-- Modernizr -->
|
||||
<script src="/bower_components/modernizr/modernizr.js"></script>
|
||||
|
||||
<!-- Test runner -->
|
||||
<script src="/test/runner.js"></script>
|
||||
|
||||
<!-- localForage -->
|
||||
<script src="/dist/localforage.js"></script>
|
||||
|
||||
<!-- specs -->
|
||||
<script src="/test/test.api.js"></script>
|
||||
<script src="/test/test.config.js"></script>
|
||||
<script src="/test/test.datatypes.js"></script>
|
||||
<script src="/test/test.drivers.js"></script>
|
||||
<script src="/test/test.iframes.js"></script>
|
||||
<script src="/test/test.webworkers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- test harness -->
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/expect.js/index.js"></script>
|
||||
<script src="../node_modules/ym-modernizr/dist/modernizr-build.js"></script>
|
||||
|
||||
<!-- localForage -->
|
||||
<script src="../dist/localforage.js"></script>
|
||||
|
||||
<!-- specs -->
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script src="./test.api.js"></script>
|
||||
<script src="./test.config.js"></script>
|
||||
<script src="./test.datatypes.js"></script>
|
||||
<script src="./test.drivers.js"></script>
|
||||
<script src="./test.iframes.js"></script>
|
||||
<script src="./test.webworkers.js"></script>
|
||||
|
||||
<!-- run test -->
|
||||
<script>
|
||||
if (window.mochaPhantomJS) {
|
||||
mochaPhantomJS.run();
|
||||
} else {
|
||||
mocha.run();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -3,35 +3,33 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>localForage (minified) Tests!</title>
|
||||
<link rel="stylesheet" type="text/css" href="../node_modules/mocha/mocha.css">
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="/bower_components/mocha/mocha.css">
|
||||
|
||||
<script src="/bower_components/assert/assert.js"></script>
|
||||
<script src="/bower_components/mocha/mocha.js"></script>
|
||||
|
||||
<script src="/bower_components/expect/index.js"></script>
|
||||
|
||||
<!-- Modernizr -->
|
||||
<script src="/bower_components/modernizr/modernizr.js"></script>
|
||||
|
||||
<!-- Test runner -->
|
||||
<script src="/test/runner.js"></script>
|
||||
|
||||
<!-- localForage -->
|
||||
<script src="/dist/localforage.min.js"></script>
|
||||
|
||||
<!-- specs -->
|
||||
<script src="/test/test.api.js"></script>
|
||||
<script src="/test/test.config.js"></script>
|
||||
<script src="/test/test.datatypes.js"></script>
|
||||
<script src="/test/test.drivers.js"></script>
|
||||
<script src="/test/test.iframes.js"></script>
|
||||
<script src="/test/test.webworkers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- test harness -->
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/expect.js/index.js"></script>
|
||||
<script src="../node_modules/ym-modernizr/dist/modernizr-build.js"></script>
|
||||
|
||||
<!-- localForage -->
|
||||
<script src="../dist/localforage.min.js"></script>
|
||||
|
||||
<!-- specs -->
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script src="./test.api.js"></script>
|
||||
<script src="./test.config.js"></script>
|
||||
<script src="./test.datatypes.js"></script>
|
||||
<script src="./test.drivers.js"></script>
|
||||
<script src="./test.iframes.js"></script>
|
||||
<script src="./test.webworkers.js"></script>
|
||||
|
||||
<!-- run test -->
|
||||
<script>
|
||||
if (window.mochaPhantomJS) {
|
||||
mochaPhantomJS.run();
|
||||
} else {
|
||||
mocha.run();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -3,15 +3,20 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>localForage Tests!</title>
|
||||
<link rel="stylesheet" type="text/css" href="../node_modules/mocha/mocha.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="/bower_components/mocha/mocha.css">
|
||||
|
||||
<script src="/bower_components/assert/assert.js"></script>
|
||||
<script src="/bower_components/mocha/mocha.js"></script>
|
||||
|
||||
<script src="/bower_components/expect/index.js"></script>
|
||||
|
||||
<script>
|
||||
// Virtually remove support for the drivers.
|
||||
// This has to be done before localforage
|
||||
// and Modernizr execute.
|
||||
|
||||
try {
|
||||
window.indexedDB.open = null;
|
||||
} catch (error) { }
|
||||
@ -25,25 +30,19 @@
|
||||
} catch (error) { }
|
||||
</script>
|
||||
|
||||
<!-- test harness -->
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/expect.js/index.js"></script>
|
||||
<script src="../node_modules/ym-modernizr/dist/modernizr-build.js"></script>
|
||||
<!-- Modernizr -->
|
||||
<script src="/bower_components/modernizr/modernizr.js"></script>
|
||||
|
||||
<!-- Test runner -->
|
||||
<script src="/test/runner.js"></script>
|
||||
|
||||
<!-- localForage -->
|
||||
<script src="../dist/localforage.js"></script>
|
||||
<script src="/dist/localforage.js"></script>
|
||||
|
||||
<!-- specs -->
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script src="./test.nodriver.js"></script>
|
||||
|
||||
<!-- run test -->
|
||||
<script>
|
||||
if (window.mochaPhantomJS) {
|
||||
mochaPhantomJS.run();
|
||||
} else {
|
||||
mocha.run();
|
||||
}
|
||||
</script>
|
||||
<script src="/test/test.nodriver.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -20,7 +20,7 @@ describe('When No Drivers Are Available', function() {
|
||||
|
||||
expect(localforage.supports(localforage.WEBSQL)).to.be(false);
|
||||
expect(localforage.supports(localforage.WEBSQL))
|
||||
.to.be(window.openDatabase !== null);
|
||||
.to.be(Modernizr.websqldatabase);
|
||||
});
|
||||
|
||||
it('fails to load localForage [callback]', function(done) {
|
||||
|
||||
@ -3,40 +3,25 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>localForage require.js Tests!</title>
|
||||
<link rel="stylesheet" type="text/css" href="../node_modules/mocha/mocha.css">
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="/bower_components/mocha/mocha.css">
|
||||
|
||||
<script src="/bower_components/assert/assert.js"></script>
|
||||
<script src="/bower_components/mocha/mocha.js"></script>
|
||||
|
||||
<script src="/bower_components/expect/index.js"></script>
|
||||
|
||||
<!-- require.js -->
|
||||
<script src="/bower_components/requirejs/require.js"></script>
|
||||
|
||||
<!-- Modernizr -->
|
||||
<script src="/bower_components/modernizr/modernizr.js"></script>
|
||||
|
||||
<!-- Test runner -->
|
||||
<script src="/test/runner.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- test harness -->
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/expect.js/index.js"></script>
|
||||
<script src="../node_modules/ym-modernizr/dist/modernizr-build.js"></script>
|
||||
<script src="../node_modules/requirejs/require.js"></script>
|
||||
|
||||
<!-- specs -->
|
||||
<script>mocha.setup('bdd')</script>
|
||||
|
||||
<!-- run test -->
|
||||
<script>
|
||||
require(['../dist/localforage.js'], function(localforage) {
|
||||
window.localforage = localforage;
|
||||
|
||||
require([
|
||||
'./test.api.js',
|
||||
'./test.config.js',
|
||||
'./test.datatypes.js',
|
||||
'./test.drivers.js',
|
||||
'./test.iframes.js',
|
||||
'./test.webworkers.js'
|
||||
], function() {
|
||||
if (window.mochaPhantomJS) {
|
||||
mochaPhantomJS.run();
|
||||
} else {
|
||||
mocha.run();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user