emoji-picker-element/src/database/Database.js

179 lines
5.1 KiB
JavaScript
Raw Normal View History

2020-05-13 05:25:46 +02:00
import { assertNonEmptyString } from './utils/assertNonEmptyString'
2020-05-17 02:51:37 +02:00
import { assertNumber } from './utils/assertNumber'
2020-06-12 06:12:00 +02:00
import {
DEFAULT_DATA_SOURCE,
DEFAULT_LOCALE,
KEY_PREFERRED_SKINTONE,
STORE_KEYVALUE
} from './constants'
2020-05-17 19:56:31 +02:00
import { uniqEmoji } from './utils/uniqEmoji'
import { jsonChecksum } from './utils/jsonChecksum'
2020-06-12 06:12:00 +02:00
import { closeDatabase, deleteDatabase, openDatabase } from './databaseLifecycle'
2020-06-04 03:09:40 +02:00
import {
isEmpty, hasData, loadData, getEmojiByGroup,
2020-06-12 06:12:00 +02:00
getEmojiBySearchQuery, getEmojiByShortcode, getEmojiByUnicode,
get, set, getTopFavoriteEmoji, incrementFavoriteEmojiCount
2020-06-04 03:09:40 +02:00
} from './idbInterface'
import { log } from '../shared/log'
2020-06-05 04:06:51 +02:00
import { getETag, getETagAndData } from './utils/ajax'
2020-06-14 20:30:38 +02:00
import { customEmojiIndex } from './customEmojiIndex'
2020-06-05 04:06:51 +02:00
async function checkForUpdates (db, dataSource) {
// just do a simple HEAD request first to see if the eTags match
let emojiBaseData
let eTag = await getETag(dataSource)
if (!eTag) { // work around lack of ETag/Access-Control-Expose-Headers
const eTagAndData = await getETagAndData(dataSource)
eTag = eTagAndData[0]
emojiBaseData = eTagAndData[1]
if (!eTag) {
eTag = await jsonChecksum(emojiBaseData)
}
}
if (await hasData(db, dataSource, eTag)) {
log('Database already populated')
} else {
log('Database update available')
if (!emojiBaseData) {
const eTagAndData = await getETagAndData(dataSource)
emojiBaseData = eTagAndData[1]
}
await loadData(db, emojiBaseData, dataSource, eTag)
}
}
async function loadDataForFirstTime (db, dataSource) {
let [eTag, emojiBaseData] = await getETagAndData(dataSource)
if (!eTag) {
// Handle lack of support for ETag or Access-Control-Expose-Headers
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers#Browser_compatibility
eTag = await jsonChecksum(emojiBaseData)
}
await loadData(db, emojiBaseData, dataSource, eTag)
}
2020-05-13 05:25:46 +02:00
export default class Database {
2020-06-14 20:30:38 +02:00
constructor ({ dataSource = DEFAULT_DATA_SOURCE, locale = DEFAULT_LOCALE, customEmoji = [] } = {}) {
2020-06-17 09:03:50 +02:00
this.dataSource = dataSource
this.locale = locale
this._dbName = `emoji-picker-element-${this.locale}`
2020-06-04 03:09:40 +02:00
this._db = undefined
this._lazyUpdate = undefined
2020-06-14 20:30:38 +02:00
this._custom = customEmojiIndex(customEmoji)
2020-06-04 03:11:24 +02:00
this._ready = this._init()
2020-05-13 05:25:46 +02:00
}
async _init () {
2020-06-05 04:06:51 +02:00
const db = this._db = await openDatabase(this._dbName)
2020-06-17 09:03:50 +02:00
const dataSource = this.dataSource
2020-06-05 04:06:51 +02:00
const empty = await isEmpty(db)
2020-05-17 02:20:00 +02:00
2020-06-05 04:06:51 +02:00
if (empty) {
await loadDataForFirstTime(db, dataSource)
} else { // offline-first - do an update asynchronously
this._lazyUpdate = checkForUpdates(db, dataSource)
2020-06-05 04:06:51 +02:00
}
2020-05-13 05:25:46 +02:00
}
async ready () {
if (!this._ready) {
this._ready = this._init()
}
2020-06-04 03:11:24 +02:00
return this._ready
}
2020-05-13 05:25:46 +02:00
async getEmojiByGroup (group) {
2020-05-17 02:51:37 +02:00
assertNumber(group)
await this.ready()
2020-06-04 03:09:40 +02:00
const emojis = await getEmojiByGroup(this._db, group)
2020-05-17 19:56:31 +02:00
return uniqEmoji(emojis)
2020-05-13 05:25:46 +02:00
}
2020-06-06 02:41:38 +02:00
async getEmojiBySearchQuery (query) {
assertNonEmptyString(query)
await this.ready()
2020-06-14 20:30:38 +02:00
const customs = this._custom.search(query)
const natives = uniqEmoji(await getEmojiBySearchQuery(this._db, query))
return [
...customs,
...natives
]
2020-05-13 05:25:46 +02:00
}
async getEmojiByShortcode (shortcode) {
assertNonEmptyString(shortcode)
await this.ready()
2020-06-14 20:30:38 +02:00
const custom = this._custom.byShortcode(shortcode)
if (custom) {
return custom
}
2020-06-05 17:51:03 +02:00
return getEmojiByShortcode(this._db, shortcode)
2020-05-13 05:25:46 +02:00
}
2020-06-15 08:38:43 +02:00
async getEmojiByUnicodeOrName (unicodeOrName) {
assertNonEmptyString(unicodeOrName)
await this.ready()
2020-06-15 08:38:43 +02:00
const custom = this._custom.byName(unicodeOrName)
if (custom) {
return custom
}
return getEmojiByUnicode(this._db, unicodeOrName)
2020-05-13 05:25:46 +02:00
}
2020-06-12 05:04:42 +02:00
async getPreferredSkinTone () {
await this.ready()
return (await get(this._db, STORE_KEYVALUE, KEY_PREFERRED_SKINTONE)) || 0
}
async setPreferredSkinTone (skinTone) {
assertNumber(skinTone)
await this.ready()
return set(this._db, STORE_KEYVALUE, KEY_PREFERRED_SKINTONE, skinTone)
}
2020-06-14 23:42:05 +02:00
async incrementFavoriteEmojiCount (unicodeOrName) {
assertNonEmptyString(unicodeOrName)
2020-06-12 06:12:00 +02:00
await this.ready()
2020-06-14 23:42:05 +02:00
return incrementFavoriteEmojiCount(this._db, unicodeOrName)
2020-06-12 06:12:00 +02:00
}
2020-06-14 20:30:38 +02:00
async getTopFavoriteEmoji (limit) {
assertNumber(limit)
2020-06-12 06:12:00 +02:00
await this.ready()
2020-06-14 20:30:38 +02:00
return getTopFavoriteEmoji(this._db, this._custom, limit)
}
set customEmoji (customEmojis) {
this._custom = customEmojiIndex(customEmojis)
}
get customEmoji () {
return this._custom.all
2020-06-12 06:12:00 +02:00
}
2020-06-10 06:44:53 +02:00
async _shutdown () {
await this.ready() // reopen if we've already been closed/deleted
try {
await this._lazyUpdate // allow any lazy updates to process before closing/deleting
} catch (err) { /* ignore network errors (offline-first) */ }
2020-06-10 05:07:45 +02:00
if (this._db) {
this._db = this._ready = this._lazyUpdate = undefined
2020-06-10 06:44:53 +02:00
return true // we need to actually run the close/delete logic, so we return true
}
}
async close () {
if (await this._shutdown()) {
2020-06-06 07:18:02 +02:00
await closeDatabase(this._dbName)
2020-05-13 05:25:46 +02:00
}
}
async delete () {
2020-06-10 06:44:53 +02:00
if (await this._shutdown()) {
2020-06-06 07:18:02 +02:00
await deleteDatabase(this._dbName)
2020-05-13 05:25:46 +02:00
}
}
2020-05-17 02:20:00 +02:00
}