UNPKG

13.2 kBJavaScriptView Raw
1import idbDriver from './drivers/indexeddb';
2import websqlDriver from './drivers/websql';
3import localstorageDriver from './drivers/localstorage';
4import serializer from './utils/serializer';
5import Promise from './utils/promise';
6import executeCallback from './utils/executeCallback';
7import executeTwoCallbacks from './utils/executeTwoCallbacks';
8import isArray from './utils/isArray';
9
10// Drivers are stored here when `defineDriver()` is called.
11// They are shared across all instances of localForage.
12const DefinedDrivers = {};
13
14const DriverSupport = {};
15
16const DefaultDrivers = {
17 INDEXEDDB: idbDriver,
18 WEBSQL: websqlDriver,
19 LOCALSTORAGE: localstorageDriver
20};
21
22const DefaultDriverOrder = [
23 DefaultDrivers.INDEXEDDB._driver,
24 DefaultDrivers.WEBSQL._driver,
25 DefaultDrivers.LOCALSTORAGE._driver
26];
27
28const OptionalDriverMethods = [
29 'dropInstance'
30];
31
32const LibraryMethods = [
33 'clear',
34 'getItem',
35 'iterate',
36 'key',
37 'keys',
38 'length',
39 'removeItem',
40 'setItem'
41].concat(OptionalDriverMethods);
42
43const DefaultConfig = {
44 description: '',
45 driver: DefaultDriverOrder.slice(),
46 name: 'localforage',
47 // Default DB size is _JUST UNDER_ 5MB, as it's the highest size
48 // we can use without a prompt.
49 size: 4980736,
50 storeName: 'keyvaluepairs',
51 version: 1.0
52};
53
54function callWhenReady(localForageInstance, libraryMethod) {
55 localForageInstance[libraryMethod] = function() {
56 const _args = arguments;
57 return localForageInstance.ready().then(function() {
58 return localForageInstance[libraryMethod].apply(localForageInstance, _args);
59 });
60 };
61}
62
63function extend() {
64 for (let i = 1; i < arguments.length; i++) {
65 const arg = arguments[i];
66
67 if (arg) {
68 for (let key in arg) {
69 if (arg.hasOwnProperty(key)) {
70 if (isArray(arg[key])) {
71 arguments[0][key] = arg[key].slice();
72 } else {
73 arguments[0][key] = arg[key];
74 }
75 }
76 }
77 }
78 }
79
80 return arguments[0];
81}
82
83class LocalForage {
84 constructor(options) {
85 for (let driverTypeKey in DefaultDrivers) {
86 if (DefaultDrivers.hasOwnProperty(driverTypeKey)) {
87 const driver = DefaultDrivers[driverTypeKey];
88 const driverName = driver._driver;
89 this[driverTypeKey] = driverName;
90
91 if (!DefinedDrivers[driverName]) {
92 // we don't need to wait for the promise,
93 // since the default drivers can be defined
94 // in a blocking manner
95 this.defineDriver(driver);
96 }
97 }
98 }
99
100 this._defaultConfig = extend({}, DefaultConfig);
101 this._config = extend({}, this._defaultConfig, options);
102 this._driverSet = null;
103 this._initDriver = null;
104 this._ready = false;
105 this._dbInfo = null;
106
107 this._wrapLibraryMethodsWithReady();
108 this.setDriver(this._config.driver).catch(() => {});
109 }
110
111 // Set any config values for localForage; can be called anytime before
112 // the first API call (e.g. `getItem`, `setItem`).
113 // We loop through options so we don't overwrite existing config
114 // values.
115 config(options) {
116 // If the options argument is an object, we use it to set values.
117 // Otherwise, we return either a specified config value or all
118 // config values.
119 if (typeof(options) === 'object') {
120 // If localforage is ready and fully initialized, we can't set
121 // any new configuration values. Instead, we return an error.
122 if (this._ready) {
123 return new Error('Can\'t call config() after localforage ' +
124 'has been used.');
125 }
126
127 for (let i in options) {
128 if (i === 'storeName') {
129 options[i] = options[i].replace(/\W/g, '_');
130 }
131
132 if (i === 'version' && typeof options[i] !== 'number') {
133 return new Error('Database version must be a number.');
134 }
135
136 this._config[i] = options[i];
137 }
138
139 // after all config options are set and
140 // the driver option is used, try setting it
141 if ('driver' in options && options.driver) {
142 return this.setDriver(this._config.driver);
143 }
144
145 return true;
146 } else if (typeof(options) === 'string') {
147 return this._config[options];
148 } else {
149 return this._config;
150 }
151 }
152
153 // Used to define a custom driver, shared across all instances of
154 // localForage.
155 defineDriver(driverObject, callback, errorCallback) {
156 const promise = new Promise(function(resolve, reject) {
157 try {
158 const driverName = driverObject._driver;
159 const complianceError = new Error(
160 'Custom driver not compliant; see ' +
161 'https://mozilla.github.io/localForage/#definedriver'
162 );
163
164 // A driver name should be defined and not overlap with the
165 // library-defined, default drivers.
166 if (!driverObject._driver) {
167 reject(complianceError);
168 return;
169 }
170
171 const driverMethods = LibraryMethods.concat('_initStorage');
172 for (let i = 0, len = driverMethods.length; i < len; i++) {
173 const driverMethodName = driverMethods[i];
174
175 // when the property is there,
176 // it should be a method even when optional
177 const isRequired = OptionalDriverMethods.indexOf(driverMethodName) < 0;
178 if ((isRequired || driverObject[driverMethodName]) &&
179 typeof driverObject[driverMethodName] !== 'function') {
180 reject(complianceError);
181 return;
182 }
183 }
184
185 const configureMissingMethods = function() {
186 const methodNotImplementedFactory = function(methodName) {
187 return function() {
188 const error = new Error(`Method ${methodName} is not implemented by the current driver`);
189 const promise = Promise.reject(error);
190 executeCallback(promise, arguments[arguments.length - 1]);
191 return promise;
192 };
193 };
194
195 for (let i = 0, len = OptionalDriverMethods.length; i < len; i++) {
196 const optionalDriverMethod = OptionalDriverMethods[i];
197 if (!driverObject[optionalDriverMethod]) {
198 driverObject[optionalDriverMethod] = methodNotImplementedFactory(optionalDriverMethod);
199 }
200 }
201 };
202
203 configureMissingMethods();
204
205 const setDriverSupport = function(support) {
206 if (DefinedDrivers[driverName]) {
207 console.info(`Redefining LocalForage driver: ${driverName}`);
208 }
209 DefinedDrivers[driverName] = driverObject;
210 DriverSupport[driverName] = support;
211 // don't use a then, so that we can define
212 // drivers that have simple _support methods
213 // in a blocking manner
214 resolve();
215 };
216
217 if ('_support' in driverObject) {
218 if (driverObject._support && typeof driverObject._support === 'function') {
219 driverObject._support().then(setDriverSupport, reject);
220 } else {
221 setDriverSupport(!!driverObject._support);
222 }
223 } else {
224 setDriverSupport(true);
225 }
226 } catch (e) {
227 reject(e);
228 }
229 });
230
231 executeTwoCallbacks(promise, callback, errorCallback);
232 return promise;
233 }
234
235 driver() {
236 return this._driver || null;
237 }
238
239 getDriver(driverName, callback, errorCallback) {
240 const getDriverPromise = DefinedDrivers[driverName] ?
241 Promise.resolve(DefinedDrivers[driverName]) :
242 Promise.reject(new Error('Driver not found.'));
243
244 executeTwoCallbacks(getDriverPromise, callback, errorCallback);
245 return getDriverPromise;
246 }
247
248 getSerializer(callback) {
249 const serializerPromise = Promise.resolve(serializer);
250 executeTwoCallbacks(serializerPromise, callback);
251 return serializerPromise;
252 }
253
254 ready(callback) {
255 const self = this;
256
257 const promise = self._driverSet.then(() => {
258 if (self._ready === null) {
259 self._ready = self._initDriver();
260 }
261
262 return self._ready;
263 });
264
265 executeTwoCallbacks(promise, callback, callback);
266 return promise;
267 }
268
269 setDriver(drivers, callback, errorCallback) {
270 const self = this;
271
272 if (!isArray(drivers)) {
273 drivers = [drivers];
274 }
275
276 const supportedDrivers = this._getSupportedDrivers(drivers);
277
278 function setDriverToConfig() {
279 self._config.driver = self.driver();
280 }
281
282 function extendSelfWithDriver(driver) {
283 self._extend(driver);
284 setDriverToConfig();
285
286 self._ready = self._initStorage(self._config);
287 return self._ready;
288 }
289
290 function initDriver(supportedDrivers) {
291 return function() {
292 let currentDriverIndex = 0;
293
294 function driverPromiseLoop() {
295 while (currentDriverIndex < supportedDrivers.length) {
296 let driverName = supportedDrivers[currentDriverIndex];
297 currentDriverIndex++;
298
299 self._dbInfo = null;
300 self._ready = null;
301
302 return self.getDriver(driverName)
303 .then(extendSelfWithDriver)
304 .catch(driverPromiseLoop);
305 }
306
307 setDriverToConfig();
308 const error = new Error('No available storage method found.');
309 self._driverSet = Promise.reject(error);
310 return self._driverSet;
311 }
312
313 return driverPromiseLoop();
314 };
315 }
316
317 // There might be a driver initialization in progress
318 // so wait for it to finish in order to avoid a possible
319 // race condition to set _dbInfo
320 const oldDriverSetDone = this._driverSet !== null ?
321 this._driverSet.catch(() => Promise.resolve()) :
322 Promise.resolve();
323
324 this._driverSet = oldDriverSetDone.then(() => {
325 const driverName = supportedDrivers[0];
326 self._dbInfo = null;
327 self._ready = null;
328
329 return self.getDriver(driverName)
330 .then(driver => {
331 self._driver = driver._driver;
332 setDriverToConfig();
333 self._wrapLibraryMethodsWithReady();
334 self._initDriver = initDriver(supportedDrivers);
335 });
336 }).catch(() => {
337 setDriverToConfig();
338 const error = new Error('No available storage method found.');
339 self._driverSet = Promise.reject(error);
340 return self._driverSet;
341 });
342
343 executeTwoCallbacks(this._driverSet, callback, errorCallback);
344 return this._driverSet;
345 }
346
347 supports(driverName) {
348 return !!DriverSupport[driverName];
349 }
350
351 _extend(libraryMethodsAndProperties) {
352 extend(this, libraryMethodsAndProperties);
353 }
354
355 _getSupportedDrivers(drivers) {
356 const supportedDrivers = [];
357 for (let i = 0, len = drivers.length; i < len; i++) {
358 const driverName = drivers[i];
359 if (this.supports(driverName)) {
360 supportedDrivers.push(driverName);
361 }
362 }
363 return supportedDrivers;
364 }
365
366 _wrapLibraryMethodsWithReady() {
367 // Add a stub for each driver API method that delays the call to the
368 // corresponding driver method until localForage is ready. These stubs
369 // will be replaced by the driver methods as soon as the driver is
370 // loaded, so there is no performance impact.
371 for (let i = 0, len = LibraryMethods.length; i < len; i++) {
372 callWhenReady(this, LibraryMethods[i]);
373 }
374 }
375
376 createInstance(options) {
377 return new LocalForage(options);
378 }
379}
380
381// The actual localForage object that we expose as a module or via a
382// global. It's extended by pulling in one of our other libraries.
383export default new LocalForage();