1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5ChromeUtils.defineModuleGetter( 6 this, 7 "IndexedDB", 8 "resource://gre/modules/IndexedDB.jsm" 9); 10 11this.ActivityStreamStorage = class ActivityStreamStorage { 12 /** 13 * @param storeNames Array of strings used to create all the required stores 14 */ 15 constructor({ storeNames, telemetry }) { 16 if (!storeNames) { 17 throw new Error("storeNames required"); 18 } 19 20 this.dbName = "ActivityStream"; 21 this.dbVersion = 3; 22 this.storeNames = storeNames; 23 this.telemetry = telemetry; 24 } 25 26 get db() { 27 return this._db || (this._db = this.createOrOpenDb()); 28 } 29 30 /** 31 * Public method that binds the store required by the consumer and exposes 32 * the private db getters and setters. 33 * 34 * @param storeName String name of desired store 35 */ 36 getDbTable(storeName) { 37 if (this.storeNames.includes(storeName)) { 38 return { 39 get: this._get.bind(this, storeName), 40 getAll: this._getAll.bind(this, storeName), 41 set: this._set.bind(this, storeName), 42 }; 43 } 44 45 throw new Error(`Store name ${storeName} does not exist.`); 46 } 47 48 async _getStore(storeName) { 49 return (await this.db).objectStore(storeName, "readwrite"); 50 } 51 52 _get(storeName, key) { 53 return this._requestWrapper(async () => 54 (await this._getStore(storeName)).get(key) 55 ); 56 } 57 58 _getAll(storeName) { 59 return this._requestWrapper(async () => 60 (await this._getStore(storeName)).getAll() 61 ); 62 } 63 64 _set(storeName, key, value) { 65 return this._requestWrapper(async () => 66 (await this._getStore(storeName)).put(value, key) 67 ); 68 } 69 70 _openDatabase() { 71 return IndexedDB.open(this.dbName, { version: this.dbVersion }, db => { 72 // If provided with array of objectStore names we need to create all the 73 // individual stores 74 this.storeNames.forEach(store => { 75 if (!db.objectStoreNames.contains(store)) { 76 this._requestWrapper(() => db.createObjectStore(store)); 77 } 78 }); 79 }); 80 } 81 82 /** 83 * createOrOpenDb - Open a db (with this.dbName) if it exists. 84 * If it does not exist, create it. 85 * If an error occurs, deleted the db and attempt to 86 * re-create it. 87 * @returns Promise that resolves with a db instance 88 */ 89 async createOrOpenDb() { 90 try { 91 const db = await this._openDatabase(); 92 return db; 93 } catch (e) { 94 if (this.telemetry) { 95 this.telemetry.handleUndesiredEvent({ event: "INDEXEDDB_OPEN_FAILED" }); 96 } 97 await IndexedDB.deleteDatabase(this.dbName); 98 return this._openDatabase(); 99 } 100 } 101 102 async _requestWrapper(request) { 103 let result = null; 104 try { 105 result = await request(); 106 } catch (e) { 107 if (this.telemetry) { 108 this.telemetry.handleUndesiredEvent({ event: "TRANSACTION_FAILED" }); 109 } 110 throw e; 111 } 112 113 return result; 114 } 115}; 116 117function getDefaultOptions(options) { 118 return { collapsed: !!options.collapsed }; 119} 120 121const EXPORTED_SYMBOLS = ["ActivityStreamStorage", "getDefaultOptions"]; 122