diff --git a/lib/selectors/socket-selectors.js b/lib/selectors/socket-selectors.js --- a/lib/selectors/socket-selectors.js +++ b/lib/selectors/socket-selectors.js @@ -7,13 +7,11 @@ currentAsOfSelector, } from './keyserver-selectors.js'; import { currentCalendarQuery } from './nav-selectors.js'; +import type { BoundStateSyncSpec } from '../shared/state-sync/state-sync-spec.js'; import { stateSyncSpecs } from '../shared/state-sync/state-sync-specs.js'; import threadWatcher from '../shared/thread-watcher.js'; import type { SignedIdentityKeysBlob } from '../types/crypto-types.js'; -import { - type RawEntryInfos, - type CalendarQuery, -} from '../types/entry-types.js'; +import { type CalendarQuery } from '../types/entry-types.js'; import type { AppState } from '../types/redux-types.js'; import type { ClientReportCreationRequest } from '../types/report-types.js'; import { @@ -23,11 +21,9 @@ } from '../types/request-types.js'; import type { SessionState } from '../types/session-types.js'; import type { OneTimeKeyGenerator } from '../types/socket-types.js'; -import { type RawThreadInfos } from '../types/thread-types.js'; -import { type CurrentUserInfo, type UserInfos } from '../types/user-types.js'; import { getConfig } from '../utils/config.js'; import { minimumOneTimeKeysRequired } from '../utils/crypto-utils.js'; -import { hash, values } from '../utils/objects.js'; +import { values } from '../utils/objects.js'; const queuedReports: ( state: AppState, @@ -38,6 +34,36 @@ ): $ReadOnlyArray => mainQueuedReports, ); +// We pass all selectors specified in stateSyncSpecs and get the resulting +// BoundStateSyncSpecs in the specs array. We do it so we don't have to +// modify the selector when we add a new spec. +const stateSyncSpecSelectors = values(stateSyncSpecs).map( + spec => spec.selector, +); +const boundStateSyncSpecsSelector: AppState => { + specsPerHashKey: { +[string]: BoundStateSyncSpec }, + specPerInnerHashKey: { +[string]: BoundStateSyncSpec }, +} = + // The FlowFixMe is needed because createSelector types require flow + // to know the number of subselectors at compile time. + // $FlowFixMe + createSelector(stateSyncSpecSelectors, (...specs) => { + const boundSpecs = (specs: BoundStateSyncSpec[]); + // We create a map from `hashKey` to a given spec for easier lookup later + const specsPerHashKey = Object.fromEntries( + boundSpecs.map(spec => [spec.hashKey, spec]), + ); + + // We do the same for innerHashKey + const specPerInnerHashKey = Object.fromEntries( + boundSpecs + .filter(spec => spec.innerHashSpec?.hashKey) + .map(spec => [spec.innerHashSpec?.hashKey, spec]), + ); + + return { specsPerHashKey, specPerInnerHashKey }; + }); + const getClientResponsesSelector: ( state: AppState, ) => ( @@ -47,16 +73,10 @@ getInitialNotificationsEncryptedMessage: ?() => Promise, serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray> = createSelector( - (state: AppState) => state.threadStore.threadInfos, - (state: AppState) => state.entryStore.entryInfos, - (state: AppState) => state.userStore.userInfos, - (state: AppState) => state.currentUserInfo, + boundStateSyncSpecsSelector, currentCalendarQuery, ( - threadInfos: RawThreadInfos, - entryInfos: RawEntryInfos, - userInfos: UserInfos, - currentUserInfo: ?CurrentUserInfo, + { specsPerHashKey, specPerInnerHashKey }, calendarQuery: (calendarActive: boolean) => CalendarQuery, ) => { return async ( @@ -87,47 +107,26 @@ } else if (serverRequest.type === serverRequestTypes.CHECK_STATE) { const query = calendarQuery(calendarActive); - const convertedInfos = { - [stateSyncSpecs.entries.hashKey]: - stateSyncSpecs.entries.convertClientToServerInfos( - entryInfos, - query, - ), - [stateSyncSpecs.threads.hashKey]: - stateSyncSpecs.threads.convertClientToServerInfos( - threadInfos, - query, - ), - [stateSyncSpecs.users.hashKey]: - stateSyncSpecs.users.convertClientToServerInfos(userInfos, query), - [stateSyncSpecs.currentUser.hashKey]: currentUserInfo - ? stateSyncSpecs.currentUser.convertClientToServerInfos( - currentUserInfo, - query, - ) - : currentUserInfo, - }; - - const specPerInnerHashKey = Object.fromEntries( - values(stateSyncSpecs) - .filter(spec => spec.innerHashSpec?.hashKey) - .map(spec => [spec.innerHashSpec?.hashKey, spec]), - ); const hashResults = {}; for (const key in serverRequest.hashesToCheck) { const expectedHashValue = serverRequest.hashesToCheck[key]; let hashValue; - if (convertedInfos[key]) { - hashValue = hash(convertedInfos[key]); + const [specKey, id] = key.split('|'); + if (id) { + hashValue = specPerInnerHashKey[specKey]?.getInfoHash(id); } else { - const [keyPrefix, id] = key.split('|'); - const innerSpec = specPerInnerHashKey[keyPrefix]; - if (!innerSpec || !id) { - continue; - } - hashValue = hash(convertedInfos[innerSpec.hashKey][id]); + hashValue = specsPerHashKey[specKey]?.getAllInfosHash(query); + } + + // If hashValue values is null then we are still calculating + // the hashes in the background. In this case we return true + // to skip this state check. Future state checks (after the hash + // calculation complete) will be handled normally. + if (!hashValue) { + hashResults[key] = true; + } else { + hashResults[key] = expectedHashValue === hashValue; } - hashResults[key] = expectedHashValue === hashValue; } const { failUnmentioned } = serverRequest; @@ -136,7 +135,13 @@ if (!failUnmentioned?.[spec.hashKey] || !innerHashKey) { continue; } - for (const id in convertedInfos[spec.hashKey]) { + + const ids = spec.getIDs(query); + if (!ids) { + continue; + } + + for (const id of ids) { const key = `${innerHashKey}|${id}`; const hashResult = hashResults[key]; if (hashResult === null || hashResult === undefined) {