From e4601a89f3b18e00d08e8d7c4f79a1d1f3a729e5 Mon Sep 17 00:00:00 2001 From: Benjamin Elo Date: Sun, 28 Jul 2019 12:46:10 -0700 Subject: [PATCH] Added IndexedDB utility functions for reading and writing bookmarks Summary: These are utility function for reading from the IndexedDB and writing to it. This will be used for persistant storage of bookmarks. The reason I chose IndexedDB over local storage is due to the poor efficiency of stringifying and parsing a long list of bookmarks everytime we read and write to local storage. With IndexedDB we can modify a single value at a time. Bookmarks are passed around as a Map, with the key being the uri's and the common names being the values. This allows me to check if a specified uri is in the Map in O(1) time, so that I can highlight the star icon in the UI with gold. Reviewed By: jknoxville Differential Revision: D16498744 fbshipit-source-id: 7c7af28bf4eb3fcc985a71dfd61ffbdb8481b6a6 --- flow-typed/indexedDB.js | 31 ++++++++ src/plugins/navigation/util/indexedDB.js | 94 ++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 flow-typed/indexedDB.js create mode 100644 src/plugins/navigation/util/indexedDB.js diff --git a/flow-typed/indexedDB.js b/flow-typed/indexedDB.js new file mode 100644 index 000000000..0b0c94f1e --- /dev/null +++ b/flow-typed/indexedDB.js @@ -0,0 +1,31 @@ +declare class DOMStringList { + +[key: number]: string; + +length: number; + item: number => string | null; + contains: string => boolean; +} + +declare interface IDBDatabase extends EventTarget { + close(): void; + createObjectStore( + name: string, + options?: { + keyPath?: ?(string | string[]), + autoIncrement?: boolean, + ... + }, + ): IDBObjectStore; + deleteObjectStore(name: string): void; + transaction( + storeNames: string | string[], + mode?: 'readonly' | 'readwrite' | 'versionchange', + ): IDBTransaction; + name: string; + version: number; + objectStoreNames: string[]; + objectStoreNames: DOMStringList; + onabort: (e: any) => mixed; + onclose: (e: any) => mixed; + onerror: (e: any) => mixed; + onversionchange: (e: any) => mixed; +} diff --git a/src/plugins/navigation/util/indexedDB.js b/src/plugins/navigation/util/indexedDB.js new file mode 100644 index 000000000..b0fe6910a --- /dev/null +++ b/src/plugins/navigation/util/indexedDB.js @@ -0,0 +1,94 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + * @flow strict-local + */ + +import type {Bookmark} from '../'; + +const FLIPPER_NAVIGATION_PLUGIN_DB = 'flipper_navigation_plugin_db'; +const FLIPPER_NAVIGATION_PLUGIN_DB_VERSION = 1; + +const BOOKMARKS_KEY = 'bookmarks'; + +const createBookmarksObjectStore: IDBDatabase => Promise = ( + db: IDBDatabase, +) => { + return new Promise((resolve, reject) => { + if (!db.objectStoreNames.contains(BOOKMARKS_KEY)) { + const bookmarksObjectStore = db.createObjectStore(BOOKMARKS_KEY, { + keyPath: 'uri', + }); + bookmarksObjectStore.transaction.oncomplete = () => resolve(); + bookmarksObjectStore.transaction.onerror = event => + reject(event.target.error); + } else { + resolve(); + } + }); +}; + +const initializeNavigationPluginDB: IDBDatabase => Promise> = ( + db: IDBDatabase, +) => { + return Promise.all([createBookmarksObjectStore(db)]); +}; + +const openNavigationPluginDB: () => Promise = () => { + return new Promise((resolve, reject) => { + const openRequest = window.indexedDB.open( + FLIPPER_NAVIGATION_PLUGIN_DB, + FLIPPER_NAVIGATION_PLUGIN_DB_VERSION, + ); + openRequest.onupgradeneeded = () => { + const db = openRequest.result; + initializeNavigationPluginDB(db).then(() => resolve(db)); + }; + openRequest.onerror = event => reject(event.target.error); + openRequest.onsuccess = () => resolve(openRequest.result); + }); +}; + +export const writeBookmarkToDB: Bookmark => Promise = ( + bookmark: Bookmark, +) => { + return new Promise((resolve, reject) => { + openNavigationPluginDB() + .then((db: IDBDatabase) => { + const bookmarksObjectStore = db + .transaction(BOOKMARKS_KEY, 'readwrite') + .objectStore(BOOKMARKS_KEY); + const request = bookmarksObjectStore.put(bookmark); + request.onsuccess = () => resolve(); + request.onerror = event => reject(event.target.error); + }) + .catch(reject); + }); +}; + +export const readBookmarksFromDB: () => Promise> = () => { + return new Promise((resolve, reject) => { + const bookmarks = new Map(); + openNavigationPluginDB() + .then((db: IDBDatabase) => { + const bookmarksObjectStore = db + .transaction(BOOKMARKS_KEY) + .objectStore(BOOKMARKS_KEY); + bookmarksObjectStore.openCursor().onsuccess = event => { + const cursor = event.target.result; + if (cursor) { + const bookmark = cursor.value; + bookmarks.set(bookmark.uri, bookmark); + cursor.continue(); + } else { + resolve(bookmarks); + } + }; + bookmarksObjectStore.openCursor().onerror = event => + reject(event.target.error); + }) + .catch(reject); + }); +};