run idb operations in a web worker (#517)

This commit is contained in:
Nolan Lawson 2018-08-29 19:03:12 -07:00 committed by GitHub
parent 2449a27767
commit d599f2f308
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 316 additions and 124 deletions

39
package-lock.json generated
View file

@ -2243,13 +2243,11 @@
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "1.0.0", "balanced-match": "1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -2262,18 +2260,15 @@
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -2376,8 +2371,7 @@
}, },
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -2387,7 +2381,6 @@
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "1.0.1" "number-is-nan": "1.0.1"
} }
@ -2400,15 +2393,13 @@
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "1.1.11" "brace-expansion": "1.1.11"
} }
}, },
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
@ -2429,7 +2420,6 @@
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -2502,8 +2492,7 @@
}, },
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -2618,7 +2607,6 @@
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "1.1.0", "code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0", "is-fullwidth-code-point": "1.0.0",
@ -6697,6 +6685,11 @@
"postcss": "^6.0.1" "postcss": "^6.0.1"
} }
}, },
"idb-keyval": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.1.0.tgz",
"integrity": "sha512-iFwFN5n00KNNnVxlOOK280SJJfXWY7pbMUOQXdIXehvvc/mGCV/6T2Ae+Pk2KwAkkATDTwfMavOiDH5lrJKWXQ=="
},
"ieee754": { "ieee754": {
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
@ -12594,6 +12587,14 @@
"errno": "~0.1.7" "errno": "~0.1.7"
} }
}, },
"workerize-loader": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/workerize-loader/-/workerize-loader-1.0.4.tgz",
"integrity": "sha512-HMTr/zpuZhm8dbhcK52cMYmn57uf7IJeMZJil+5lL/vC5+AO9wzxZ0FISkGVj78No7HcpaINwAWHGCYx3dnsTw==",
"requires": {
"loader-utils": "^1.1.0"
}
},
"wrap-ansi": { "wrap-ansi": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",

View file

@ -66,6 +66,7 @@
"form-data": "^2.3.2", "form-data": "^2.3.2",
"glob": "^7.1.2", "glob": "^7.1.2",
"helmet": "^3.13.0", "helmet": "^3.13.0",
"idb-keyval": "^3.1.0",
"indexeddb-getall-shim": "^1.3.5", "indexeddb-getall-shim": "^1.3.5",
"intersection-observer": "^0.5.0", "intersection-observer": "^0.5.0",
"lodash-es": "^4.17.10", "lodash-es": "^4.17.10",
@ -98,6 +99,7 @@
"web-animations-js": "^2.3.1", "web-animations-js": "^2.3.1",
"webpack": "^4.17.1", "webpack": "^4.17.1",
"webpack-bundle-analyzer": "^2.13.1", "webpack-bundle-analyzer": "^2.13.1",
"workerize-loader": "^1.0.4",
"yargs": "^12.0.1" "yargs": "^12.0.1"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,18 +1,12 @@
import { getAccount } from '../_api/user' import { getAccount } from '../_api/user'
import { getRelationship } from '../_api/relationships' import { getRelationship } from '../_api/relationships'
import { import { database } from '../_database/database'
getAccount as getAccountFromDatabase,
setAccount as setAccountInDatabase} from '../_database/accounts'
import {
getRelationship as getRelationshipFromDatabase,
setRelationship as setRelationshipInDatabase
} from '../_database/relationships'
import { store } from '../_store/store' import { store } from '../_store/store'
async function _updateAccount (accountId, instanceName, accessToken) { async function _updateAccount (accountId, instanceName, accessToken) {
let localPromise = getAccountFromDatabase(instanceName, accountId) let localPromise = database.getAccount(instanceName, accountId)
let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => { let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => {
/* no await */ setAccountInDatabase(instanceName, account) /* no await */ database.setAccount(instanceName, account)
return account return account
}) })
@ -29,9 +23,9 @@ async function _updateAccount (accountId, instanceName, accessToken) {
} }
async function _updateRelationship (accountId, instanceName, accessToken) { async function _updateRelationship (accountId, instanceName, accessToken) {
let localPromise = getRelationshipFromDatabase(instanceName, accountId) let localPromise = database.getRelationship(instanceName, accountId)
let remotePromise = getRelationship(instanceName, accessToken, accountId).then(relationship => { let remotePromise = getRelationship(instanceName, accessToken, accountId).then(relationship => {
/* no await */ setRelationshipInDatabase(instanceName, relationship) /* no await */ database.setRelationship(instanceName, relationship)
return relationship return relationship
}) })
try { try {
@ -47,7 +41,7 @@ async function _updateRelationship (accountId, instanceName, accessToken) {
} }
export async function updateLocalRelationship (instanceName, accountId, relationship) { export async function updateLocalRelationship (instanceName, accountId, relationship) {
await setRelationshipInDatabase(instanceName, relationship) await database.setRelationship(instanceName, relationship)
try { try {
store.set({currentAccountRelationship: relationship}) store.set({currentAccountRelationship: relationship})
} catch (e) { } catch (e) {

View file

@ -5,7 +5,7 @@ import { switchToTheme } from '../_utils/themeEngine'
import { store } from '../_store/store' import { store } from '../_store/store'
import { updateVerifyCredentialsForInstance } from './instances' import { updateVerifyCredentialsForInstance } from './instances'
import { updateCustomEmojiForInstance } from './emoji' import { updateCustomEmojiForInstance } from './emoji'
import { setInstanceInfo as setInstanceInfoInDatabase } from '../_database/meta' import { database } from '../_database/database'
const REDIRECT_URI = (typeof location !== 'undefined' const REDIRECT_URI = (typeof location !== 'undefined'
? location.origin : 'https://pinafore.social') + '/settings/instances/add' ? location.origin : 'https://pinafore.social') + '/settings/instances/add'
@ -19,7 +19,7 @@ async function redirectToOauth () {
} }
let registrationPromise = registerApplication(instanceNameInSearch, REDIRECT_URI) let registrationPromise = registerApplication(instanceNameInSearch, REDIRECT_URI)
let instanceInfo = await getInstanceInfo(instanceNameInSearch) let instanceInfo = await getInstanceInfo(instanceNameInSearch)
await setInstanceInfoInDatabase(instanceNameInSearch, instanceInfo) // cache for later await database.setInstanceInfo(instanceNameInSearch, instanceInfo) // cache for later
let instanceData = await registrationPromise let instanceData = await registrationPromise
store.set({ store.set({
currentRegisteredInstanceName: instanceNameInSearch, currentRegisteredInstanceName: instanceNameInSearch,

View file

@ -4,9 +4,7 @@ import { store } from '../_store/store'
import uniqBy from 'lodash-es/uniqBy' import uniqBy from 'lodash-es/uniqBy'
import uniq from 'lodash-es/uniq' import uniq from 'lodash-es/uniq'
import isEqual from 'lodash-es/isEqual' import isEqual from 'lodash-es/isEqual'
import { import { database } from '../_database/database'
insertTimelineItems as insertTimelineItemsInDatabase
} from '../_database/timelines/insertion'
import { runMediumPriorityTask } from '../_utils/runMediumPriorityTask' import { runMediumPriorityTask } from '../_utils/runMediumPriorityTask'
const STREAMING_THROTTLE_DELAY = 3000 const STREAMING_THROTTLE_DELAY = 3000
@ -29,7 +27,7 @@ async function insertUpdatesIntoTimeline (instanceName, timelineName, updates) {
return return
} }
await insertTimelineItemsInDatabase(instanceName, timelineName, updates) await database.insertTimelineItems(instanceName, timelineName, updates)
let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') || [] let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') || []
let newItemIdsToAdd = uniq([].concat(itemIdsToAdd).concat(updates.map(_ => _.id))) let newItemIdsToAdd = uniq([].concat(itemIdsToAdd).concat(updates.map(_ => _.id)))

View file

@ -2,13 +2,13 @@ import { store } from '../_store/store'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { postStatus as postStatusToServer } from '../_api/statuses' import { postStatus as postStatusToServer } from '../_api/statuses'
import { addStatusOrNotification } from './addStatusOrNotification' import { addStatusOrNotification } from './addStatusOrNotification'
import { getStatus as getStatusFromDatabase } from '../_database/timelines/getStatusOrNotification' import { database } from '../_database/database'
import { emit } from '../_utils/eventBus' import { emit } from '../_utils/eventBus'
import { putMediaDescription } from '../_api/media' import { putMediaDescription } from '../_api/media'
export async function insertHandleForReply (statusId) { export async function insertHandleForReply (statusId) {
let { currentInstance } = store.get() let { currentInstance } = store.get()
let status = await getStatusFromDatabase(currentInstance, statusId) let status = await database.getStatus(currentInstance, statusId)
let { currentVerifyCredentials } = store.get() let { currentVerifyCredentials } = store.get()
let originalStatus = status.reblog || status let originalStatus = status.reblog || status
let accounts = [originalStatus.account].concat(originalStatus.mentions || []) let accounts = [originalStatus.account].concat(originalStatus.mentions || [])

View file

@ -2,9 +2,7 @@ import { getIdsThatRebloggedThisStatus, getNotificationIdsForStatuses } from './
import { store } from '../_store/store' import { store } from '../_store/store'
import { scheduleIdleTask } from '../_utils/scheduleIdleTask' import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
import isEqual from 'lodash-es/isEqual' import isEqual from 'lodash-es/isEqual'
import { import { database } from '../_database/database'
deleteStatusesAndNotifications as deleteStatusesAndNotificationsFromDatabase
} from '../_database/timelines/deletion'
function filterItemIdsFromTimelines (instanceName, timelineFilter, idFilter) { function filterItemIdsFromTimelines (instanceName, timelineFilter, idFilter) {
let keys = ['timelineItemIds', 'itemIdsToAdd'] let keys = ['timelineItemIds', 'itemIdsToAdd']
@ -45,7 +43,7 @@ function deleteNotificationIdsFromStore (instanceName, idsToDelete) {
async function deleteStatusesAndNotifications (instanceName, statusIdsToDelete, notificationIdsToDelete) { async function deleteStatusesAndNotifications (instanceName, statusIdsToDelete, notificationIdsToDelete) {
deleteStatusIdsFromStore(instanceName, statusIdsToDelete) deleteStatusIdsFromStore(instanceName, statusIdsToDelete)
deleteNotificationIdsFromStore(instanceName, notificationIdsToDelete) deleteNotificationIdsFromStore(instanceName, notificationIdsToDelete)
await deleteStatusesAndNotificationsFromDatabase(instanceName, statusIdsToDelete, notificationIdsToDelete) await database.deleteStatusesAndNotifications(instanceName, statusIdsToDelete, notificationIdsToDelete)
} }
async function doDeleteStatus (instanceName, statusId) { async function doDeleteStatus (instanceName, statusId) {

View file

@ -1,16 +1,13 @@
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
import { import { database } from '../_database/database'
getCustomEmoji as getCustomEmojiFromDatabase,
setCustomEmoji as setCustomEmojiInDatabase
} from '../_database/meta'
import { getCustomEmoji } from '../_api/emoji' import { getCustomEmoji } from '../_api/emoji'
import { store } from '../_store/store' import { store } from '../_store/store'
export async function updateCustomEmojiForInstance (instanceName) { export async function updateCustomEmojiForInstance (instanceName) {
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getCustomEmoji(instanceName), () => getCustomEmoji(instanceName),
() => getCustomEmojiFromDatabase(instanceName), () => database.getCustomEmoji(instanceName),
emoji => setCustomEmojiInDatabase(instanceName, emoji), emoji => database.setCustomEmoji(instanceName, emoji),
emoji => { emoji => {
let { customEmoji } = store.get() let { customEmoji } = store.get()
customEmoji[instanceName] = emoji customEmoji[instanceName] = emoji

View file

@ -1,9 +1,7 @@
import { favoriteStatus, unfavoriteStatus } from '../_api/favorite' import { favoriteStatus, unfavoriteStatus } from '../_api/favorite'
import { store } from '../_store/store' import { store } from '../_store/store'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { import { database } from '../_database/database'
setStatusFavorited as setStatusFavoritedInDatabase
} from '../_database/timelines/updateStatus'
export async function setFavorited (statusId, favorited) { export async function setFavorited (statusId, favorited) {
let { online } = store.get() let { online } = store.get()
@ -18,7 +16,7 @@ export async function setFavorited (statusId, favorited) {
store.setStatusFavorited(currentInstance, statusId, favorited) // optimistic update store.setStatusFavorited(currentInstance, statusId, favorited) // optimistic update
try { try {
await networkPromise await networkPromise
await setStatusFavoritedInDatabase(currentInstance, statusId, favorited) await database.setStatusFavorited(currentInstance, statusId, favorited)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
toast.say(`Failed to ${favorited ? 'favorite' : 'unfavorite'}. ` + (e.message || '')) toast.say(`Failed to ${favorited ? 'favorite' : 'unfavorite'}. ` + (e.message || ''))

View file

@ -5,13 +5,7 @@ import { toast } from '../_utils/toast'
import { goto } from 'sapper/runtime.js' import { goto } from 'sapper/runtime.js'
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
import { getInstanceInfo } from '../_api/instance' import { getInstanceInfo } from '../_api/instance'
import { clearDatabaseForInstance } from '../_database/clear' import { database } from '../_database/database'
import {
getInstanceVerifyCredentials as getInstanceVerifyCredentialsFromDatabase,
setInstanceVerifyCredentials as setInstanceVerifyCredentialsInDatabase,
getInstanceInfo as getInstanceInfoFromDatabase,
setInstanceInfo as setInstanceInfoInDatabase
} from '../_database/meta'
export function changeTheme (instanceName, newTheme) { export function changeTheme (instanceName, newTheme) {
let { instanceThemes } = store.get() let { instanceThemes } = store.get()
@ -62,7 +56,7 @@ export async function logOutOfInstance (instanceName) {
store.save() store.save()
toast.say(`Logged out of ${instanceName}`) toast.say(`Logged out of ${instanceName}`)
switchToTheme(instanceThemes[newInstance] || 'default') switchToTheme(instanceThemes[newInstance] || 'default')
await clearDatabaseForInstance(instanceName) await database.clearDatabaseForInstance(instanceName)
goto('/settings/instances') goto('/settings/instances')
} }
@ -77,8 +71,8 @@ export async function updateVerifyCredentialsForInstance (instanceName) {
let accessToken = loggedInInstances[instanceName].access_token let accessToken = loggedInInstances[instanceName].access_token
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getVerifyCredentials(instanceName, accessToken), () => getVerifyCredentials(instanceName, accessToken),
() => getInstanceVerifyCredentialsFromDatabase(instanceName), () => database.getInstanceVerifyCredentials(instanceName),
verifyCredentials => setInstanceVerifyCredentialsInDatabase(instanceName, verifyCredentials), verifyCredentials => database.setInstanceVerifyCredentials(instanceName, verifyCredentials),
verifyCredentials => setStoreVerifyCredentials(instanceName, verifyCredentials) verifyCredentials => setStoreVerifyCredentials(instanceName, verifyCredentials)
) )
} }
@ -91,8 +85,8 @@ export async function updateVerifyCredentialsForCurrentInstance () {
export async function updateInstanceInfo (instanceName) { export async function updateInstanceInfo (instanceName) {
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getInstanceInfo(instanceName), () => getInstanceInfo(instanceName),
() => getInstanceInfoFromDatabase(instanceName), () => database.getInstanceInfo(instanceName),
info => setInstanceInfoInDatabase(instanceName, info), info => database.setInstanceInfo(instanceName, info),
info => { info => {
let { instanceInfos } = store.get() let { instanceInfos } = store.get()
instanceInfos[instanceName] = info instanceInfos[instanceName] = info

View file

@ -1,18 +1,15 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { getLists } from '../_api/lists' import { getLists } from '../_api/lists'
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
import { import { database } from '../_database/database'
getLists as getListsFromDatabase,
setLists as setListsInDatabase
} from '../_database/meta'
export async function updateLists () { export async function updateLists () {
let { currentInstance, accessToken } = store.get() let { currentInstance, accessToken } = store.get()
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getLists(currentInstance, accessToken), () => getLists(currentInstance, accessToken),
() => getListsFromDatabase(currentInstance), () => database.getLists(currentInstance),
lists => setListsInDatabase(currentInstance, lists), lists => database.setLists(currentInstance, lists),
lists => { lists => {
let { instanceLists } = store.get() let { instanceLists } = store.get()
instanceLists[currentInstance] = lists instanceLists[currentInstance] = lists

View file

@ -1,7 +1,7 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { muteConversation, unmuteConversation } from '../_api/muteConversation' import { muteConversation, unmuteConversation } from '../_api/muteConversation'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { setStatusMuted as setStatusMutedInDatabase } from '../_database/timelines/updateStatus' import { database } from '../_database/database'
export async function setConversationMuted (statusId, mute, toastOnSuccess) { export async function setConversationMuted (statusId, mute, toastOnSuccess) {
let { currentInstance, accessToken } = store.get() let { currentInstance, accessToken } = store.get()
@ -11,7 +11,7 @@ export async function setConversationMuted (statusId, mute, toastOnSuccess) {
} else { } else {
await unmuteConversation(currentInstance, accessToken, statusId) await unmuteConversation(currentInstance, accessToken, statusId)
} }
await setStatusMutedInDatabase(currentInstance, statusId, mute) await database.setStatusMuted(currentInstance, statusId, mute)
if (toastOnSuccess) { if (toastOnSuccess) {
if (mute) { if (mute) {
toast.say('Muted conversation') toast.say('Muted conversation')

View file

@ -1,7 +1,7 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { pinStatus, unpinStatus } from '../_api/pin' import { pinStatus, unpinStatus } from '../_api/pin'
import { setStatusPinned as setStatusPinnedInDatabase } from '../_database/timelines/updateStatus' import { database } from '../_database/database'
import { emit } from '../_utils/eventBus' import { emit } from '../_utils/eventBus'
export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSuccess) { export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSuccess) {
@ -20,7 +20,7 @@ export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSucces
} }
} }
store.setStatusPinned(currentInstance, statusId, pinned) store.setStatusPinned(currentInstance, statusId, pinned)
await setStatusPinnedInDatabase(currentInstance, statusId, pinned) await database.setStatusPinned(currentInstance, statusId, pinned)
emit('updatePinnedStatuses') emit('updatePinnedStatuses')
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View file

@ -1,9 +1,6 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
import { import { database } from '../_database/database'
getPinnedStatuses as getPinnedStatusesFromDatabase,
insertPinnedStatuses as insertPinnedStatusesInDatabase
} from '../_database/timelines/pinnedStatuses'
import { import {
getPinnedStatuses getPinnedStatuses
} from '../_api/pinnedStatuses' } from '../_api/pinnedStatuses'
@ -13,8 +10,8 @@ export async function updatePinnedStatusesForAccount (accountId) {
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
() => getPinnedStatuses(currentInstance, accessToken, accountId), () => getPinnedStatuses(currentInstance, accessToken, accountId),
() => getPinnedStatusesFromDatabase(currentInstance, accountId), () => database.getPinnedStatuses(currentInstance, accountId),
statuses => insertPinnedStatusesInDatabase(currentInstance, accountId, statuses), statuses => database.insertPinnedStatuses(currentInstance, accountId, statuses),
statuses => { statuses => {
let { pinnedStatuses } = store.get() let { pinnedStatuses } = store.get()
pinnedStatuses[currentInstance] = pinnedStatuses[currentInstance] || {} pinnedStatuses[currentInstance] = pinnedStatuses[currentInstance] || {}

View file

@ -1,7 +1,7 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { reblogStatus, unreblogStatus } from '../_api/reblog' import { reblogStatus, unreblogStatus } from '../_api/reblog'
import { setStatusReblogged as setStatusRebloggedInDatabase } from '../_database/timelines/updateStatus' import { database } from '../_database/database'
export async function setReblogged (statusId, reblogged) { export async function setReblogged (statusId, reblogged) {
let online = store.get() let online = store.get()
@ -16,7 +16,7 @@ export async function setReblogged (statusId, reblogged) {
store.setStatusReblogged(currentInstance, statusId, reblogged) // optimistic update store.setStatusReblogged(currentInstance, statusId, reblogged) // optimistic update
try { try {
await networkPromise await networkPromise
await setStatusRebloggedInDatabase(currentInstance, statusId, reblogged) await database.setStatusReblogged(currentInstance, statusId, reblogged)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
toast.say(`Failed to ${reblogged ? 'boost' : 'unboost'}. ` + (e.message || '')) toast.say(`Failed to ${reblogged ? 'boost' : 'unboost'}. ` + (e.message || ''))

View file

@ -1,13 +1,7 @@
import { import { database } from '../_database/database'
getNotificationIdsForStatuses as getNotificationIdsForStatusesFromDatabase,
getReblogsForStatus as getReblogsForStatusFromDatabase
} from '../_database/timelines/lookup'
import {
getStatus as getStatusFromDatabase
} from '../_database/timelines/getStatusOrNotification'
export async function getIdThatThisStatusReblogged (instanceName, statusId) { export async function getIdThatThisStatusReblogged (instanceName, statusId) {
let status = await getStatusFromDatabase(instanceName, statusId) let status = await database.getStatus(instanceName, statusId)
return status.reblog && status.reblog.id return status.reblog && status.reblog.id
} }
@ -19,9 +13,9 @@ export async function getIdsThatTheseStatusesReblogged (instanceName, statusIds)
} }
export async function getIdsThatRebloggedThisStatus (instanceName, statusId) { export async function getIdsThatRebloggedThisStatus (instanceName, statusId) {
return getReblogsForStatusFromDatabase(instanceName, statusId) return database.getReblogsForStatus(instanceName, statusId)
} }
export async function getNotificationIdsForStatuses (instanceName, statusIds) { export async function getNotificationIdsForStatuses (instanceName, statusIds) {
return getNotificationIdsForStatusesFromDatabase(instanceName, statusIds) return database.getNotificationIdsForStatuses(instanceName, statusIds)
} }

View file

@ -5,19 +5,14 @@ import { mark, stop } from '../_utils/marks'
import { concat, mergeArrays } from '../_utils/arrays' import { concat, mergeArrays } from '../_utils/arrays'
import { byItemIds } from '../_utils/sorting' import { byItemIds } from '../_utils/sorting'
import isEqual from 'lodash-es/isEqual' import isEqual from 'lodash-es/isEqual'
import { import { database } from '../_database/database'
insertTimelineItems as insertTimelineItemsInDatabase
} from '../_database/timelines/insertion'
import {
getTimeline as getTimelineFromDatabase
} from '../_database/timelines/pagination'
import { getStatus, getStatusContext } from '../_api/statuses' import { getStatus, getStatusContext } from '../_api/statuses'
import { emit } from '../_utils/eventBus' import { emit } from '../_utils/eventBus'
const FETCH_LIMIT = 20 const FETCH_LIMIT = 20
async function storeFreshTimelineItemsInDatabase (instanceName, timelineName, items) { async function storeFreshTimelineItemsInDatabase (instanceName, timelineName, items) {
await insertTimelineItemsInDatabase(instanceName, timelineName, items) await database.insertTimelineItems(instanceName, timelineName, items)
if (timelineName.startsWith('status/')) { if (timelineName.startsWith('status/')) {
// For status threads, we want to be sure to update the favorite/reblog counts even if // For status threads, we want to be sure to update the favorite/reblog counts even if
// this is a stale "view" of the status. See 119-status-counts-update.js for // this is a stale "view" of the status. See 119-status-counts-update.js for
@ -45,7 +40,7 @@ async function fetchTimelineItems (instanceName, accessToken, timelineName, last
let items let items
let stale = false let stale = false
if (!online) { if (!online) {
items = await getTimelineFromDatabase(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT) items = await database.getTimeline(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT)
stale = true stale = true
} else { } else {
try { try {
@ -54,7 +49,7 @@ async function fetchTimelineItems (instanceName, accessToken, timelineName, last
} catch (e) { } catch (e) {
console.error(e) console.error(e)
toast.say('Internet request failed. Showing offline content.') toast.say('Internet request failed. Showing offline content.')
items = await getTimelineFromDatabase(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT) items = await database.getTimeline(instanceName, timelineName, lastTimelineItemId, FETCH_LIMIT)
stale = true stale = true
} }
} }

View file

@ -41,10 +41,7 @@
importNotificationVirtualListItem importNotificationVirtualListItem
} from '../../_utils/asyncModules' } from '../../_utils/asyncModules'
import { timelines } from '../../_static/timelines' import { timelines } from '../../_static/timelines'
import { import { database } from '../../_database/database'
getStatus as getStatusFromDatabase,
getNotification as getNotificationFromDatabase
} from '../../_database/timelines/getStatusOrNotification'
import { import {
fetchTimelineItemsOnScrollToBottom, fetchTimelineItemsOnScrollToBottom,
setupTimeline, setupTimeline,
@ -101,9 +98,9 @@
timelineValue timelineValue
} }
if (timelineType === 'notifications') { if (timelineType === 'notifications') {
res.notification = await getNotificationFromDatabase($currentInstance, itemId) res.notification = await database.getNotification($currentInstance, itemId)
} else { } else {
res.status = await getStatusFromDatabase($currentInstance, itemId) res.status = await database.getStatus($currentInstance, itemId)
} }
return res return res
}, },

View file

@ -12,7 +12,8 @@ export async function setAccount (instanceName, account) {
return setGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, cloneForStorage(account)) return setGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, cloneForStorage(account))
} }
export async function searchAccountsByUsername (instanceName, usernamePrefix, limit = 20) { export async function searchAccountsByUsername (instanceName, usernamePrefix, limit) {
limit = limit || 20
const db = await getDatabase(instanceName) const db = await getDatabase(instanceName)
return dbPromise(db, ACCOUNTS_STORE, 'readonly', (accountsStore, callback) => { return dbPromise(db, ACCOUNTS_STORE, 'readonly', (accountsStore, callback) => {
let keyRange = createAccountUsernamePrefixKeyRange(usernamePrefix.toLowerCase()) let keyRange = createAccountUsernamePrefixKeyRange(usernamePrefix.toLowerCase())

View file

@ -22,7 +22,7 @@ export const notificationsCache = {
} }
if (process.browser && process.env.NODE_ENV !== 'production') { if (process.browser && process.env.NODE_ENV !== 'production') {
window.cacheStats = { (typeof self !== 'undefined' ? self : window).cacheStats = {
statuses: statusesCache, statuses: statusesCache,
accounts: accountsCache, accounts: accountsCache,
relationships: relationshipsCache, relationships: relationshipsCache,

View file

@ -1,5 +1,4 @@
import { dbPromise, getDatabase } from './databaseLifecycle' import { dbPromise, getDatabase } from './databaseLifecycle'
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
import { import {
ACCOUNTS_STORE, ACCOUNTS_STORE,
NOTIFICATION_TIMELINES_STORE, NOTIFICATION_TIMELINES_STORE,
@ -12,10 +11,10 @@ import {
TIMESTAMP TIMESTAMP
} from './constants' } from './constants'
import debounce from 'lodash-es/debounce' import debounce from 'lodash-es/debounce'
import { store } from '../_store/store'
import { mark, stop } from '../_utils/marks' import { mark, stop } from '../_utils/marks'
import { deleteAll } from './utils' import { deleteAll } from './utils'
import { createPinnedStatusKeyRange, createThreadKeyRange } from './keys' import { createPinnedStatusKeyRange, createThreadKeyRange } from './keys'
import { getKnownInstances } from './knownInstances'
const BATCH_SIZE = 20 const BATCH_SIZE = 20
const TIME_AGO = 7 * 24 * 60 * 60 * 1000 // one week ago const TIME_AGO = 7 * 24 * 60 * 60 * 1000 // one week ago
@ -135,15 +134,16 @@ async function cleanup (instanceName) {
} }
function doCleanup (instanceName) { function doCleanup (instanceName) {
scheduleIdleTask(() => cleanup(instanceName)) // run in setTimeout because we're in a worker and there's no requestIdleCallback
setTimeout(() => cleanup(instanceName))
} }
function scheduledCleanup () { async function scheduledCleanup () {
console.log('scheduledCleanup') console.log('scheduledCleanup')
let { loggedInInstancesInOrder } = store.get() let knownInstances = await getKnownInstances()
for (let instance of loggedInInstancesInOrder) { for (let instance of knownInstances) {
doCleanup(instance) doCleanup(instance)
} }
} }
export const scheduleCleanup = debounce(() => scheduleIdleTask(scheduledCleanup), DELAY) export const scheduleCleanup = debounce(scheduledCleanup, DELAY)

View file

@ -0,0 +1,3 @@
// vanilla version
import * as database from './databaseWorker'
export { database }

View file

@ -0,0 +1,7 @@
// workerize version
let database
if (process.browser) {
const worker = require('./databaseWorker')
database = worker()
}
export { database }

View file

@ -13,6 +13,7 @@ import {
STATUS_ID, STATUS_ID,
USERNAME_LOWERCASE USERNAME_LOWERCASE
} from './constants' } from './constants'
import { addKnownInstance, deleteKnownInstance } from './knownInstances'
const openReqs = {} const openReqs = {}
const databaseCache = {} const databaseCache = {}
@ -86,6 +87,8 @@ export function getDatabase (instanceName) {
} }
} }
req.onsuccess = () => resolve(req.result) req.onsuccess = () => resolve(req.result)
}).then(res => {
return addKnownInstance(instanceName).then(() => res)
}) })
return databaseCache[instanceName] return databaseCache[instanceName]
} }
@ -118,5 +121,5 @@ export function deleteDatabase (instanceName) {
let req = indexedDB.deleteDatabase(instanceName) let req = indexedDB.deleteDatabase(instanceName)
req.onsuccess = () => resolve() req.onsuccess = () => resolve()
req.onerror = () => reject(req.error) req.onerror = () => reject(req.error)
}) }).then(() => deleteKnownInstance(instanceName))
} }

View file

@ -0,0 +1,187 @@
import {
getAccount as _getAccount,
searchAccountsByUsername as _searchAccountsByUsername,
setAccount as _setAccount
} from './accounts'
import {
clearDatabaseForInstance as _clearDatabaseForInstance
} from './clear'
import {
getNotificationIdsForStatuses as _getNotificationIdsForStatuses,
getReblogsForStatus as _getReblogsForStatus
} from './timelines/lookup'
import {
getPinnedStatuses as _getPinnedStatuses,
insertPinnedStatuses as _insertPinnedStatuses
} from './timelines/pinnedStatuses'
import {
getNotificationTimeline as _getNotificationTimeline,
getStatusThread as _getStatusThread,
getStatusTimeline as _getStatusTimeline,
getTimeline as _getTimeline
} from './timelines/pagination'
import {
getNotification as _getNotification,
getStatus as _getStatus
} from './timelines/getStatusOrNotification'
import {
setStatusFavorited as _setStatusFavorited,
setStatusMuted as _setStatusMuted,
setStatusPinned as _setStatusPinned,
setStatusReblogged as _setStatusReblogged
} from './timelines/updateStatus'
import {
deleteStatusesAndNotifications as _deleteStatusesAndNotifications
} from './timelines/deletion'
import {
insertStatusThread as _insertStatusThread,
insertTimelineItems as _insertTimelineItems,
insertTimelineNotifications as _insertTimelineNotifications,
insertTimelineStatuses as _insertTimelineStatuses
} from './timelines/insertion'
import {
getCustomEmoji as _getCustomEmoji,
getInstanceInfo as _getInstanceInfo,
getInstanceVerifyCredentials as _getInstanceVerifyCredentials,
getLists as _getLists,
setCustomEmoji as _setCustomEmoji,
setInstanceInfo as _setInstanceInfo,
setInstanceVerifyCredentials as _setInstanceVerifyCredentials,
setLists as _setLists
} from './meta'
import {
getRelationship as _getRelationship,
setRelationship as _setRelationship
} from './relationships'
export async function getAccount (instanceName, accountId) {
return _getAccount(instanceName, accountId)
}
export async function setAccount (instanceName, account) {
return _setAccount(instanceName, account)
}
export async function searchAccountsByUsername (instanceName, usernamePrefix, limit) {
return _searchAccountsByUsername(instanceName, usernamePrefix, limit)
}
export async function clearDatabaseForInstance (instanceName) {
return _clearDatabaseForInstance(instanceName)
}
export async function getReblogsForStatus (instanceName, id) {
return _getReblogsForStatus(instanceName, id)
}
export async function getNotificationIdsForStatuses (instanceName, statusIds) {
return _getNotificationIdsForStatuses(instanceName, statusIds)
}
export async function insertPinnedStatuses (instanceName, accountId, statuses) {
return _insertPinnedStatuses(instanceName, accountId, statuses)
}
export async function getPinnedStatuses (instanceName, accountId) {
return _getPinnedStatuses(instanceName, accountId)
}
export async function getNotificationTimeline (instanceName, timeline, maxId, limit) {
return _getNotificationTimeline(instanceName, timeline, maxId, limit)
}
export async function getStatusTimeline (instanceName, timeline, maxId, limit) {
return _getStatusTimeline(instanceName, timeline, maxId, limit)
}
export async function getStatusThread (instanceName, statusId) {
return _getStatusThread(instanceName, statusId)
}
export async function getTimeline (instanceName, timeline, maxId, limit) {
return _getTimeline(instanceName, timeline, maxId, limit)
}
export async function getStatus (instanceName, id) {
return _getStatus(instanceName, id)
}
export async function getNotification (instanceName, id) {
return _getNotification(instanceName, id)
}
export async function setStatusFavorited (instanceName, statusId, favorited) {
return _setStatusFavorited(instanceName, statusId, favorited)
}
export async function setStatusReblogged (instanceName, statusId, reblogged) {
return _setStatusReblogged(instanceName, statusId, reblogged)
}
export async function setStatusPinned (instanceName, statusId, pinned) {
return _setStatusPinned(instanceName, statusId, pinned)
}
export async function setStatusMuted (instanceName, statusId, muted) {
return _setStatusMuted(instanceName, statusId, muted)
}
export async function deleteStatusesAndNotifications (instanceName, statusIds, notificationIds) {
return _deleteStatusesAndNotifications(instanceName, statusIds, notificationIds)
}
export async function insertTimelineNotifications (instanceName, timeline, notifications) {
return _insertTimelineNotifications(instanceName, timeline, notifications)
}
export async function insertTimelineStatuses (instanceName, timeline, statuses) {
return _insertTimelineStatuses(instanceName, timeline, statuses)
}
export async function insertStatusThread (instanceName, statusId, statuses) {
return _insertStatusThread(instanceName, statusId, statuses)
}
export async function insertTimelineItems (instanceName, timeline, timelineItems) {
return _insertTimelineItems(instanceName, timeline, timelineItems)
}
export async function getInstanceVerifyCredentials (instanceName) {
return _getInstanceVerifyCredentials(instanceName)
}
export async function setInstanceVerifyCredentials (instanceName, value) {
return _setInstanceVerifyCredentials(instanceName, value)
}
export async function getInstanceInfo (instanceName) {
return _getInstanceInfo(instanceName)
}
export async function setInstanceInfo (instanceName, value) {
return _setInstanceInfo(instanceName, value)
}
export async function getLists (instanceName) {
return _getLists(instanceName)
}
export async function setLists (instanceName, value) {
return _setLists(instanceName, value)
}
export async function getCustomEmoji (instanceName) {
return _getCustomEmoji(instanceName)
}
export async function setCustomEmoji (instanceName, value) {
return _setCustomEmoji(instanceName, value)
}
export async function getRelationship (instanceName, accountId) {
return _getRelationship(instanceName, accountId)
}
export async function setRelationship (instanceName, relationship) {
return _setRelationship(instanceName, relationship)
}

View file

@ -0,0 +1,17 @@
import { set, keys, del } from 'idb-keyval'
const PREFIX = 'known-instance-'
export async function getKnownInstances () {
return (await keys())
.filter(_ => _.startsWith(PREFIX))
.map(_ => _.substring(PREFIX.length))
}
export async function addKnownInstance (instanceName) {
return set(PREFIX + instanceName, true)
}
export async function deleteKnownInstance (instanceName) {
return del(PREFIX + instanceName)
}

View file

@ -80,7 +80,9 @@ export async function getStatusThread (instanceName, statusId) {
}) })
} }
export async function getTimeline (instanceName, timeline, maxId = null, limit = 20) { export async function getTimeline (instanceName, timeline, maxId, limit) {
maxId = maxId || null
limit = limit || 20
if (timeline === 'notifications') { if (timeline === 'notifications') {
return getNotificationTimeline(instanceName, timeline, maxId, limit) return getNotificationTimeline(instanceName, timeline, maxId, limit)
} else if (timeline.startsWith('status/')) { } else if (timeline.startsWith('status/')) {

View file

@ -1,6 +1,4 @@
import { import { database } from '../../_database/database'
searchAccountsByUsername as searchAccountsByUsernameInDatabase
} from '../../_database/accounts'
const SEARCH_RESULTS_LIMIT = 4 const SEARCH_RESULTS_LIMIT = 4
const DATABASE_SEARCH_RESULTS_LIMIT = 30 const DATABASE_SEARCH_RESULTS_LIMIT = 30
@ -8,7 +6,7 @@ const DATABASE_SEARCH_RESULTS_LIMIT = 30
async function searchAccounts (store, searchText) { async function searchAccounts (store, searchText) {
searchText = searchText.substring(1) searchText = searchText.substring(1)
let { currentInstance } = store.get() let { currentInstance } = store.get()
let results = await searchAccountsByUsernameInDatabase( let results = await database.searchAccountsByUsername(
currentInstance, searchText, DATABASE_SEARCH_RESULTS_LIMIT) currentInstance, searchText, DATABASE_SEARCH_RESULTS_LIMIT)
return results.slice(0, SEARCH_RESULTS_LIMIT) return results.slice(0, SEARCH_RESULTS_LIMIT)
} }

View file

@ -1,6 +1,6 @@
const enabled = process.browser && performance.mark && ( const enabled = process.browser && performance.mark && (
process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'production' ||
location.search.includes('marks=true') (typeof location !== 'undefined' && location.search.includes('marks=true'))
) )
const perf = process.browser && performance const perf = process.browser && performance

View file

@ -10,6 +10,8 @@ const isDev = config.dev
module.exports = { module.exports = {
entry: config.client.entry(), entry: config.client.entry(),
// uncomment to enable HMR within workers
// output: Object.assign(config.client.output(), { globalObject: 'this' }),
output: config.client.output(), output: config.client.output(),
resolve: { resolve: {
extensions: ['.js', '.json', '.html'] extensions: ['.js', '.json', '.html']
@ -45,6 +47,12 @@ module.exports = {
MiniCssExtractPlugin.loader, MiniCssExtractPlugin.loader,
'css-loader' 'css-loader'
] ]
},
!isDev && { // workerize-loader makes dev mode hard (e.g. HMR)
test: /\/_database\/databaseWorker\.js$/,
use: [
'workerize-loader'
]
} }
].filter(Boolean) ].filter(Boolean)
}, },
@ -80,6 +88,10 @@ module.exports = {
paths: true paths: true
}) })
].concat(isDev ? [ ].concat(isDev ? [
new webpack.NormalModuleReplacementPlugin(
/\/_database\/database\.js$/,
'./database.dev.js'
),
new webpack.HotModuleReplacementPlugin({ new webpack.HotModuleReplacementPlugin({
requestTimeout: 120000 requestTimeout: 120000
}) })