diff --git a/lib/reducers/community-reducer.js b/lib/reducers/community-reducer.js index cd7c26fa4..725ce251a 100644 --- a/lib/reducers/community-reducer.js +++ b/lib/reducers/community-reducer.js @@ -1,42 +1,62 @@ // @flow +import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js'; import { addCommunityActionType } from '../actions/community-actions.js'; import { communityStoreOpsHandlers, type CommunityStoreOperation, type ReplaceCommunityOperation, } from '../ops/community-store-ops.js'; import type { CommunityStore } from '../types/community-types.js'; import type { BaseAction } from '../types/redux-types'; const { processStoreOperations: processStoreOps } = communityStoreOpsHandlers; function reduceCommunityStore( state: CommunityStore, action: BaseAction, ): { +communityStore: CommunityStore, +communityStoreOperations: $ReadOnlyArray, } { if (action.type === addCommunityActionType) { const replaceOperation: ReplaceCommunityOperation = { type: 'replace_community', payload: { id: action.payload.id, communityInfo: action.payload.newCommunityInfo, }, }; return { communityStore: processStoreOps(state, [replaceOperation]), communityStoreOperations: [replaceOperation], }; + } else if (action.type === setClientDBStoreActionType) { + const newCommunityInfo = action.payload.communityInfos; + + if (!newCommunityInfo) { + return { + communityStore: state, + communityStoreOperations: [], + }; + } + + const newCommunityStore: CommunityStore = { + ...state, + communityInfos: newCommunityInfo, + }; + + return { + communityStore: newCommunityStore, + communityStoreOperations: [], + }; } return { communityStore: state, communityStoreOperations: [], }; } export { reduceCommunityStore }; diff --git a/lib/reducers/message-reducer.test.js b/lib/reducers/message-reducer.test.js index bf9a8ea98..ef5abee42 100644 --- a/lib/reducers/message-reducer.test.js +++ b/lib/reducers/message-reducer.test.js @@ -1,318 +1,319 @@ // @flow import invariant from 'invariant'; import { reduceMessageStore } from './message-reducer.js'; import { createPendingThread } from '../shared/thread-utils.js'; import { messageTypes } from '../types/message-types-enum.js'; import type { MessageStore } from '../types/message-types.js'; import { threadTypes } from '../types/thread-types-enum.js'; import { authoritativeKeyserverID } from '../utils/authoritative-keyserver.js'; const messageStoreBeforeMediaUpdate: MessageStore = { messages: { local1: { type: 14, threadID: '91140', creatorID: '91097', time: 1639522317443, media: [ { id: 'localUpload2', uri: 'assets-library://asset/asset.HEIC?id=CC95F08C-88C3-4012-9D6D-64A413D254B3&ext=HEIC', type: 'photo', dimensions: { height: 3024, width: 4032 }, thumbHash: 'some_thumb_hash', localMediaSelection: { step: 'photo_library', dimensions: { height: 3024, width: 4032 }, uri: 'assets-library://asset/asset.HEIC?id=CC95F08C-88C3-4012-9D6D-64A413D254B3&ext=HEIC', filename: 'IMG_0006.HEIC', mediaNativeID: 'CC95F08C-88C3-4012-9D6D-64A413D254B3/L0/001', selectTime: 1639522317349, sendTime: 1639522317349, retries: 0, }, }, ], localID: 'local1', }, }, threads: { '91140': { messageIDs: ['local1'], startReached: true, }, }, local: {}, currentAsOf: { [authoritativeKeyserverID()]: 1639522292174 }, }; describe('UPDATE_MULTIMEDIA_MESSAGE_MEDIA', () => { const updateMultiMediaMessageMediaAction = { type: 'UPDATE_MULTIMEDIA_MESSAGE_MEDIA', payload: { messageID: 'local1', currentMediaID: 'localUpload2', mediaUpdate: { id: '91172', type: 'photo', uri: 'http://localhost/comm/upload/91172/dfa9b9fe7eb03fde', dimensions: { height: 1440, width: 1920 }, localMediaSelection: undefined, }, }, }; const { messageStore: updatedMessageStore } = reduceMessageStore( messageStoreBeforeMediaUpdate, updateMultiMediaMessageMediaAction, {}, ); test('replace local media with uploaded media', () => { expect( updatedMessageStore.messages[ updateMultiMediaMessageMediaAction.payload.messageID ], ).toStrictEqual({ type: 14, threadID: '91140', creatorID: '91097', time: 1639522317443, media: [ { id: '91172', type: 'photo', uri: 'http://localhost/comm/upload/91172/dfa9b9fe7eb03fde', dimensions: { height: 1440, width: 1920 }, thumbHash: 'some_thumb_hash', }, ], localID: 'local1', }); }); test('localMediaSelection is unset when undefined in update', () => { const msg = updatedMessageStore.messages[ updateMultiMediaMessageMediaAction.payload.messageID ]; expect(msg.type).toEqual(messageTypes.IMAGES); invariant(msg.type === messageTypes.IMAGES, 'message is of type IMAGES'); expect(msg.media[0]).not.toHaveProperty('localMediaSelection'); }); test('localMediaSelection is unchanged when missing in update', () => { const actionWithoutLocalMediaSelectionUpdate = { type: 'UPDATE_MULTIMEDIA_MESSAGE_MEDIA', payload: { messageID: 'local1', currentMediaID: 'localUpload2', mediaUpdate: { id: '91172', type: 'photo', uri: 'http://localhost/comm/upload/91172/dfa9b9fe7eb03fde', dimensions: { height: 1440, width: 1920 }, }, }, }; const { messageStore: storeWithoutLocalMediaSelectionUpdate } = reduceMessageStore( messageStoreBeforeMediaUpdate, actionWithoutLocalMediaSelectionUpdate, {}, ); const prevMsg = messageStoreBeforeMediaUpdate.messages[ actionWithoutLocalMediaSelectionUpdate.payload.messageID ]; const updatedMsg = storeWithoutLocalMediaSelectionUpdate.messages[ actionWithoutLocalMediaSelectionUpdate.payload.messageID ]; expect(updatedMsg.type).toEqual(messageTypes.IMAGES); expect(prevMsg.type).toEqual(messageTypes.IMAGES); invariant( updatedMsg.type === messageTypes.IMAGES && prevMsg.type === messageTypes.IMAGES, 'message is of type IMAGES', ); expect(updatedMsg.media[0].localMediaSelection).toStrictEqual( prevMsg.media[0].localMediaSelection, ); }); test('localMediaSelection is updated when included in update', () => { const updateMultiMediaMessageMediaActionWithReplacement = { type: 'UPDATE_MULTIMEDIA_MESSAGE_MEDIA', payload: { messageID: 'local1', currentMediaID: 'localUpload2', mediaUpdate: { id: '91172', type: 'photo', uri: 'http://localhost/comm/upload/91172/dfa9b9fe7eb03fde', dimensions: { height: 1440, width: 1920 }, localMediaSelection: { step: 'photo_library', dimensions: { height: 10, width: 10 }, uri: 'assets-library://asset/new/path', filename: 'NEWNAME.PNG', mediaNativeID: 'CC95F08C-88C3-4012-9D6D-64A413D254B3/L0/001', selectTime: 1639522317349, sendTime: 1639522317349, retries: 1, }, }, }, }; const { messageStore: updatedMessageStoreWithReplacement } = reduceMessageStore( messageStoreBeforeMediaUpdate, updateMultiMediaMessageMediaActionWithReplacement, {}, ); const updatedMsg = updatedMessageStoreWithReplacement.messages[ updateMultiMediaMessageMediaActionWithReplacement.payload.messageID ]; expect(updatedMsg.type).toEqual(messageTypes.IMAGES); invariant( updatedMsg.type === messageTypes.IMAGES, 'message is of type IMAGES', ); expect(updatedMsg.media[0].localMediaSelection).toStrictEqual({ step: 'photo_library', dimensions: { height: 10, width: 10 }, uri: 'assets-library://asset/new/path', filename: 'NEWNAME.PNG', mediaNativeID: 'CC95F08C-88C3-4012-9D6D-64A413D254B3/L0/001', selectTime: 1639522317349, sendTime: 1639522317349, retries: 1, }); }); }); describe('SET_MESSAGE_STORE_MESSAGES', () => { const clientDBMessages = [ { id: '103502', local_id: null, thread: '88471', user: '83809', type: '14', future_type: null, content: '[103501]', time: '1658168455316', media_infos: [ { id: '103501', uri: 'http://localhost/comm/upload/103501/425db25471f3acd5', type: 'photo', extras: '{"dimensions":{"width":1920,"height":1440},"loop":false}', }, ], }, { id: 'local10', local_id: 'local10', thread: '88471', user: '83809', type: '14', future_type: null, content: '[null]', time: '1658172650495', media_infos: [ { id: 'localUpload0', uri: 'assets-library://asset/asset.heic?id=CC95F08C-88C3-4012-9D6D-64A413D254B3&ext=heic', type: 'photo', extras: '{"dimensions":{"height":3024,"width":4032},"loop":false,"local_media_selection":{"step":"photo_library","dimensions":{"height":3024,"width":4032},"uri":"assets-library://asset/asset.heic?id=CC95F08C-88C3-4012-9D6D-64A413D254B3&ext=heic","filename":"IMG_0006.HEIC","mediaNativeID":"CC95F08C-88C3-4012-9D6D-64A413D254B3/L0/001","selectTime":1658172650370,"sendTime":1658172650370,"retries":0}}', }, ], }, { id: 'local11', local_id: 'local11', thread: '88471', user: '83809', type: '14', future_type: null, content: '[null,null]', time: '1658172656976', media_infos: [ { id: 'localUpload2', uri: 'assets-library://asset/asset.heic?id=CC95F08C-88C3-4012-9D6D-64A413D254B3&ext=heic', type: 'photo', extras: '{"dimensions":{"height":3024,"width":4032},"loop":false,"local_media_selection":{"step":"photo_library","dimensions":{"height":3024,"width":4032},"uri":"assets-library://asset/asset.heic?id=CC95F08C-88C3-4012-9D6D-64A413D254B3&ext=heic","filename":"IMG_0006.HEIC","mediaNativeID":"CC95F08C-88C3-4012-9D6D-64A413D254B3/L0/001","selectTime":1658172656826,"sendTime":1658172656826,"retries":0}}', }, { id: 'localUpload4', uri: 'assets-library://asset/asset.jpg?id=ED7AC36B-A150-4C38-BB8C-B6D696F4F2ED&ext=jpg', type: 'photo', extras: '{"dimensions":{"height":2002,"width":3000},"loop":false,"local_media_selection":{"step":"photo_library","dimensions":{"height":2002,"width":3000},"uri":"assets-library://asset/asset.jpg?id=ED7AC36B-A150-4C38-BB8C-B6D696F4F2ED&ext=jpg","filename":"IMG_0005.JPG","mediaNativeID":"ED7AC36B-A150-4C38-BB8C-B6D696F4F2ED/L0/001","selectTime":1658172656826,"sendTime":1658172656826,"retries":0}}', }, ], }, ]; const clientDBThreads = [ { id: '88471', start_reached: '0', }, ]; const { messageStore: updatedMessageStore } = reduceMessageStore( { messages: {}, threads: {}, local: {}, currentAsOf: { [authoritativeKeyserverID()]: 1234567890123 }, }, { type: 'SET_CLIENT_DB_STORE', payload: { currentUserID: '', drafts: [], threadStore: { threadInfos: {}, }, messageStoreThreads: clientDBThreads, messages: clientDBMessages, reports: [], users: {}, keyserverInfos: {}, + communityInfos: {}, }, }, { [88471]: createPendingThread({ viewerID: '', threadType: threadTypes.LOCAL, members: [{ id: '', username: '' }], }), }, ); test('removes local media when constructing messageStore.messages', () => { expect(updatedMessageStore.messages).toHaveProperty('103502'); expect(updatedMessageStore.messages).not.toHaveProperty('local10'); expect(updatedMessageStore.messages).not.toHaveProperty('local11'); }); test('removes local media when constructing messageStore.threads', () => { expect(updatedMessageStore).toBeDefined(); expect(updatedMessageStore.threads).toBeDefined(); expect(updatedMessageStore.threads['88471']).toBeDefined(); expect(updatedMessageStore.threads['88471'].messageIDs).toBeDefined(); expect(updatedMessageStore.threads['88471'].messageIDs).toEqual(['103502']); }); }); diff --git a/lib/types/store-ops-types.js b/lib/types/store-ops-types.js index f4f5ffb95..92032bade 100644 --- a/lib/types/store-ops-types.js +++ b/lib/types/store-ops-types.js @@ -1,83 +1,85 @@ // @flow +import type { CommunityInfos } from './community-types.js'; import type { DraftStoreOperation, ClientDBDraftStoreOperation, ClientDBDraftInfo, } from './draft-types.js'; import type { KeyserverInfos } from './keyserver-types.js'; import type { ClientDBMessageInfo, ClientDBThreadMessageInfo, } from './message-types.js'; import type { ClientReportCreationRequest } from './report-types.js'; import type { ClientDBThreadInfo, ThreadStore } from './thread-types.js'; import type { UserInfos } from './user-types.js'; import type { ClientDBCommunityInfo, ClientDBCommunityStoreOperation, CommunityStoreOperation, } from '../ops/community-store-ops.js'; import type { ClientDBKeyserverInfo, ClientDBKeyserverStoreOperation, KeyserverStoreOperation, } from '../ops/keyserver-store-ops.js'; import type { ClientDBMessageStoreOperation, MessageStoreOperation, } from '../ops/message-store-ops.js'; import type { ReportStoreOperation, ClientDBReport, ClientDBReportStoreOperation, } from '../ops/report-store-ops.js'; import type { ClientDBThreadStoreOperation, ThreadStoreOperation, } from '../ops/thread-store-ops.js'; import type { UserStoreOperation, ClientDBUserInfo, } from '../ops/user-store-ops.js'; export type StoreOperations = { +draftStoreOperations: $ReadOnlyArray, +threadStoreOperations: $ReadOnlyArray, +messageStoreOperations: $ReadOnlyArray, +reportStoreOperations: $ReadOnlyArray, +userStoreOperations: $ReadOnlyArray, +keyserverStoreOperations: $ReadOnlyArray, +communityStoreOperations: $ReadOnlyArray, }; export type ClientDBStoreOperations = { +draftStoreOperations?: $ReadOnlyArray, +threadStoreOperations?: $ReadOnlyArray, +messageStoreOperations?: $ReadOnlyArray, +reportStoreOperations?: $ReadOnlyArray, +keyserverStoreOperations?: $ReadOnlyArray, +communityStoreOperations?: $ReadOnlyArray, }; export type ClientDBStore = { +messages: $ReadOnlyArray, +drafts: $ReadOnlyArray, +threads: $ReadOnlyArray, +messageStoreThreads: $ReadOnlyArray, +reports: $ReadOnlyArray, +users: $ReadOnlyArray, +keyservers: $ReadOnlyArray, +communities: $ReadOnlyArray, }; export type ClientStore = { +currentUserID: ?string, +drafts: $ReadOnlyArray, +messages: ?$ReadOnlyArray, +threadStore: ?ThreadStore, +messageStoreThreads: ?$ReadOnlyArray, +reports: ?$ReadOnlyArray, +users: ?UserInfos, +keyserverInfos: ?KeyserverInfos, + +communityInfos: ?CommunityInfos, }; diff --git a/native/data/sqlite-data-handler.js b/native/data/sqlite-data-handler.js index 5694be2eb..25b67d405 100644 --- a/native/data/sqlite-data-handler.js +++ b/native/data/sqlite-data-handler.js @@ -1,240 +1,245 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { setClientDBStoreActionType } from 'lib/actions/client-db-store-actions.js'; import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js'; import { useStaffContext } from 'lib/components/staff-provider.react.js'; import { resolveKeyserverSessionInvalidation } from 'lib/keyserver-conn/recovery-utils.js'; +import { communityStoreOpsHandlers } from 'lib/ops/community-store-ops.js'; import { keyserverStoreOpsHandlers } from 'lib/ops/keyserver-store-ops.js'; import { reportStoreOpsHandlers } from 'lib/ops/report-store-ops.js'; import { threadStoreOpsHandlers } from 'lib/ops/thread-store-ops.js'; import { userStoreOpsHandlers } from 'lib/ops/user-store-ops.js'; import { cookieSelector, urlPrefixSelector, } from 'lib/selectors/keyserver-selectors.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js'; import { logInActionSources, type LogInActionSource, } from 'lib/types/account-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { authoritativeKeyserverID } from '../authoritative-keyserver.js'; import { filesystemMediaCache } from '../media/media-cache.js'; import { commCoreModule } from '../native-modules.js'; import { setStoreLoadedActionType } from '../redux/action-types.js'; import { useSelector } from '../redux/redux-utils.js'; import Alert from '../utils/alert.js'; import { nativeNotificationsSessionCreator } from '../utils/crypto-utils.js'; import { isTaskCancelledError } from '../utils/error-handling.js'; import { useStaffCanSee } from '../utils/staff-utils.js'; async function clearSensitiveData() { await commCoreModule.clearSensitiveData(); try { await filesystemMediaCache.clearCache(); } catch { throw new Error('clear_media_cache_failed'); } } function SQLiteDataHandler(): React.Node { const storeLoaded = useSelector(state => state.storeLoaded); const dispatch = useDispatch(); const rehydrateConcluded = useSelector( state => !!(state._persist && state._persist.rehydrated), ); const cookie = useSelector(cookieSelector(authoritativeKeyserverID)); const urlPrefix = useSelector(urlPrefixSelector(authoritativeKeyserverID)); invariant(urlPrefix, "missing urlPrefix for ashoat's keyserver"); const staffCanSee = useStaffCanSee(); const { staffUserHasBeenLoggedIn } = useStaffContext(); const loggedIn = useSelector(isLoggedIn); const currentLoggedInUserID = useSelector(state => state.currentUserInfo?.anonymous ? undefined : state.currentUserInfo?.id, ); const mediaCacheContext = React.useContext(MediaCacheContext); const getInitialNotificationsEncryptedMessage = useInitialNotificationsEncryptedMessage(nativeNotificationsSessionCreator); const callFetchNewCookieFromNativeCredentials = React.useCallback( async (source: LogInActionSource) => { try { await resolveKeyserverSessionInvalidation( dispatch, cookie, urlPrefix, source, authoritativeKeyserverID, getInitialNotificationsEncryptedMessage, ); dispatch({ type: setStoreLoadedActionType }); } catch (fetchCookieException) { if (staffCanSee) { Alert.alert( `Error fetching new cookie from native credentials: ${ getMessageForException(fetchCookieException) ?? '{no exception message}' }. Please kill the app.`, ); } else { commCoreModule.terminate(); } } }, [ cookie, dispatch, staffCanSee, urlPrefix, getInitialNotificationsEncryptedMessage, ], ); const callClearSensitiveData = React.useCallback( async (triggeredBy: string) => { await clearSensitiveData(); console.log(`SQLite database deletion was triggered by ${triggeredBy}`); }, [], ); const handleSensitiveData = React.useCallback(async () => { try { const databaseCurrentUserInfoID = await commCoreModule.getCurrentUserID(); if ( databaseCurrentUserInfoID && databaseCurrentUserInfoID !== currentLoggedInUserID ) { await callClearSensitiveData('change in logged-in user credentials'); } if (currentLoggedInUserID) { await commCoreModule.setCurrentUserID(currentLoggedInUserID); } } catch (e) { if (isTaskCancelledError(e)) { return; } if (__DEV__) { throw e; } console.log(e); if (e.message !== 'clear_media_cache_failed') { commCoreModule.terminate(); } } }, [callClearSensitiveData, currentLoggedInUserID]); React.useEffect(() => { if (!rehydrateConcluded) { return; } const databaseNeedsDeletion = commCoreModule.checkIfDatabaseNeedsDeletion(); if (databaseNeedsDeletion) { void (async () => { try { await callClearSensitiveData('detecting corrupted database'); } catch (e) { if (__DEV__) { throw e; } console.log(e); if (e.message !== 'clear_media_cache_failed') { commCoreModule.terminate(); } } await callFetchNewCookieFromNativeCredentials( logInActionSources.corruptedDatabaseDeletion, ); })(); return; } const sensitiveDataHandled = handleSensitiveData(); if (storeLoaded) { return; } if (!loggedIn) { dispatch({ type: setStoreLoadedActionType }); return; } void (async () => { await Promise.all([ sensitiveDataHandled, mediaCacheContext?.evictCache(), ]); try { const { threads, messages, drafts, messageStoreThreads, reports, users, keyservers, + communities, } = await commCoreModule.getClientDBStore(); const threadInfosFromDB = threadStoreOpsHandlers.translateClientDBData(threads); const reportsFromDB = reportStoreOpsHandlers.translateClientDBData(reports); const usersFromDB = userStoreOpsHandlers.translateClientDBData(users); const keyserverInfosFromDB = keyserverStoreOpsHandlers.translateClientDBData(keyservers); + const communityInfosFromDB = + communityStoreOpsHandlers.translateClientDBData(communities); dispatch({ type: setClientDBStoreActionType, payload: { drafts, messages, threadStore: { threadInfos: threadInfosFromDB }, currentUserID: currentLoggedInUserID, messageStoreThreads, reports: reportsFromDB, users: usersFromDB, keyserverInfos: keyserverInfosFromDB, + communities: communityInfosFromDB, }, }); } catch (setStoreException) { if (isTaskCancelledError(setStoreException)) { dispatch({ type: setStoreLoadedActionType }); return; } if (staffCanSee) { Alert.alert( 'Error setting threadStore or messageStore', getMessageForException(setStoreException) ?? '{no exception message}', ); } await callFetchNewCookieFromNativeCredentials( logInActionSources.sqliteLoadFailure, ); } })(); }, [ currentLoggedInUserID, handleSensitiveData, loggedIn, cookie, dispatch, rehydrateConcluded, staffCanSee, storeLoaded, urlPrefix, staffUserHasBeenLoggedIn, callFetchNewCookieFromNativeCredentials, callClearSensitiveData, mediaCacheContext, ]); return null; } export { SQLiteDataHandler, clearSensitiveData }; diff --git a/web/database/utils/store.js b/web/database/utils/store.js index 0fe898ec0..e31483ec3 100644 --- a/web/database/utils/store.js +++ b/web/database/utils/store.js @@ -1,127 +1,136 @@ // @flow import { communityStoreOpsHandlers } from 'lib/ops/community-store-ops.js'; import { keyserverStoreOpsHandlers } from 'lib/ops/keyserver-store-ops.js'; import { reportStoreOpsHandlers } from 'lib/ops/report-store-ops.js'; import { threadStoreOpsHandlers } from 'lib/ops/thread-store-ops.js'; import { canUseDatabaseOnWeb } from 'lib/shared/web-database.js'; import type { ClientStore, StoreOperations, } from 'lib/types/store-ops-types.js'; import { defaultWebState } from '../../redux/default-state.js'; import { workerRequestMessageTypes } from '../../types/worker-types.js'; import { getDatabaseModule } from '../database-module-provider.js'; async function getClientDBStore(): Promise { const databaseModule = await getDatabaseModule(); let result: ClientStore = { currentUserID: null, drafts: [], messages: null, threadStore: null, messageStoreThreads: null, reports: null, users: null, keyserverInfos: defaultWebState.keyserverStore.keyserverInfos, + communityInfos: null, }; const data = await databaseModule.schedule({ type: workerRequestMessageTypes.GET_CLIENT_STORE, }); if (data?.store?.drafts) { result = { ...result, drafts: data.store.drafts, }; } if (data?.store?.reports) { result = { ...result, reports: reportStoreOpsHandlers.translateClientDBData(data.store.reports), }; } if (data?.store?.threads && data.store.threads.length > 0) { result = { ...result, threadStore: { threadInfos: threadStoreOpsHandlers.translateClientDBData( data.store.threads, ), }, }; } if (data?.store?.keyservers?.length) { result = { ...result, keyserverInfos: keyserverStoreOpsHandlers.translateClientDBData( data.store.keyservers, ), }; } + if (data?.store?.communities) { + result = { + ...result, + communityInfos: communityStoreOpsHandlers.translateClientDBData( + data.store.communities, + ), + }; + } return result; } async function processDBStoreOperations( storeOperations: StoreOperations, userID: null | string, ): Promise { const { draftStoreOperations, threadStoreOperations, reportStoreOperations, keyserverStoreOperations, communityStoreOperations, } = storeOperations; const canUseDatabase = canUseDatabaseOnWeb(userID); const convertedThreadStoreOperations = canUseDatabase ? threadStoreOpsHandlers.convertOpsToClientDBOps(threadStoreOperations) : []; const convertedReportStoreOperations = reportStoreOpsHandlers.convertOpsToClientDBOps(reportStoreOperations); const convertedKeyserverStoreOperations = keyserverStoreOpsHandlers.convertOpsToClientDBOps(keyserverStoreOperations); const convertedCommunityStoreOperations = communityStoreOpsHandlers.convertOpsToClientDBOps(communityStoreOperations); if ( convertedThreadStoreOperations.length === 0 && convertedReportStoreOperations.length === 0 && draftStoreOperations.length === 0 && convertedKeyserverStoreOperations.length === 0 && convertedCommunityStoreOperations.length === 0 ) { return; } const databaseModule = await getDatabaseModule(); const isSupported = await databaseModule.isDatabaseSupported(); if (!isSupported) { return; } try { await databaseModule.schedule({ type: workerRequestMessageTypes.PROCESS_STORE_OPERATIONS, storeOperations: { draftStoreOperations, reportStoreOperations: convertedReportStoreOperations, threadStoreOperations: convertedThreadStoreOperations, keyserverStoreOperations: convertedKeyserverStoreOperations, communityStoreOperations: convertedCommunityStoreOperations, }, }); } catch (e) { console.log(e); if (canUseDatabase) { window.alert(e.message); if (threadStoreOperations.length > 0) { await databaseModule.init({ clearDatabase: true }); location.reload(); } } } } export { getClientDBStore, processDBStoreOperations };