1 | import idbDriver from './drivers/indexeddb';
|
2 | import websqlDriver from './drivers/websql';
|
3 | import localstorageDriver from './drivers/localstorage';
|
4 | import serializer from './utils/serializer';
|
5 | import Promise from './utils/promise';
|
6 | import executeCallback from './utils/executeCallback';
|
7 | import executeTwoCallbacks from './utils/executeTwoCallbacks';
|
8 | import isArray from './utils/isArray';
|
9 |
|
10 |
|
11 |
|
12 | const DefinedDrivers = {};
|
13 |
|
14 | const DriverSupport = {};
|
15 |
|
16 | const DefaultDrivers = {
|
17 | INDEXEDDB: idbDriver,
|
18 | WEBSQL: websqlDriver,
|
19 | LOCALSTORAGE: localstorageDriver
|
20 | };
|
21 |
|
22 | const DefaultDriverOrder = [
|
23 | DefaultDrivers.INDEXEDDB._driver,
|
24 | DefaultDrivers.WEBSQL._driver,
|
25 | DefaultDrivers.LOCALSTORAGE._driver
|
26 | ];
|
27 |
|
28 | const OptionalDriverMethods = [
|
29 | 'dropInstance'
|
30 | ];
|
31 |
|
32 | const LibraryMethods = [
|
33 | 'clear',
|
34 | 'getItem',
|
35 | 'iterate',
|
36 | 'key',
|
37 | 'keys',
|
38 | 'length',
|
39 | 'removeItem',
|
40 | 'setItem'
|
41 | ].concat(OptionalDriverMethods);
|
42 |
|
43 | const DefaultConfig = {
|
44 | description: '',
|
45 | driver: DefaultDriverOrder.slice(),
|
46 | name: 'localforage',
|
47 |
|
48 |
|
49 | size: 4980736,
|
50 | storeName: 'keyvaluepairs',
|
51 | version: 1.0
|
52 | };
|
53 |
|
54 | function 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 |
|
63 | function 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 |
|
83 | class 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 |
|
93 |
|
94 |
|
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 |
|
112 |
|
113 |
|
114 |
|
115 | config(options) {
|
116 |
|
117 |
|
118 |
|
119 | if (typeof(options) === 'object') {
|
120 |
|
121 |
|
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 |
|
140 |
|
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 |
|
154 |
|
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 |
|
165 |
|
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 |
|
176 |
|
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 |
|
212 |
|
213 |
|
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 |
|
318 |
|
319 |
|
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 |
|
368 |
|
369 |
|
370 |
|
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 |
|
382 |
|
383 | export default new LocalForage();
|