diff --git a/keyserver/src/shared/state-sync/state-sync-spec.js b/keyserver/src/shared/state-sync/state-sync-spec.js index 041a9cb7f..78991ba1a 100644 --- a/keyserver/src/shared/state-sync/state-sync-spec.js +++ b/keyserver/src/shared/state-sync/state-sync-spec.js @@ -1,15 +1,15 @@ // @flow import type { StateSyncSpec } from 'lib/shared/state-sync/state-sync-spec.js'; import type { CalendarQuery } from 'lib/types/entry-types.js'; import type { Viewer } from '../../session/viewer.js'; export type ServerStateSyncSpec = { +fetch: ( viewer: Viewer, calendarQuery: $ReadOnlyArray, ids?: $ReadOnlySet, ) => Promise, - ...StateSyncSpec, + ...StateSyncSpec, }; diff --git a/lib/selectors/socket-selectors.js b/lib/selectors/socket-selectors.js index 059019adb..f3789a8d5 100644 --- a/lib/selectors/socket-selectors.js +++ b/lib/selectors/socket-selectors.js @@ -1,255 +1,235 @@ // @flow import { createSelector } from 'reselect'; -import t from 'tcomb'; import { updatesCurrentAsOfSelector, currentAsOfSelector, } from './keyserver-selectors.js'; import { currentCalendarQuery } from './nav-selectors.js'; -import { - serverEntryInfo, - serverEntryInfosObject, - filterRawEntryInfosByCalendarQuery, -} from '../shared/entry-utils.js'; +import { serverEntryInfo } from '../shared/entry-utils.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, - rawEntryInfoValidator, type CalendarQuery, } from '../types/entry-types.js'; import type { AppState } from '../types/redux-types.js'; import type { ClientReportCreationRequest } from '../types/report-types.js'; import { serverRequestTypes, type ClientServerRequest, type ClientClientResponse, } from '../types/request-types.js'; import type { SessionState } from '../types/session-types.js'; import type { OneTimeKeyGenerator } from '../types/socket-types.js'; -import { - type RawThreadInfos, - rawThreadInfoValidator, -} from '../types/thread-types.js'; -import { - type CurrentUserInfo, - currentUserInfoValidator, - type UserInfos, - userInfosValidator, -} from '../types/user-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 { convertClientIDsToServerIDs } from '../utils/conversion-utils.js'; import { minimumOneTimeKeysRequired } from '../utils/crypto-utils.js'; -import { values, hash } from '../utils/objects.js'; -import { tID, ashoatKeyserverID } from '../utils/validation-utils.js'; +import { hash } from '../utils/objects.js'; const queuedReports: ( state: AppState, ) => $ReadOnlyArray = createSelector( (state: AppState) => state.reportStore.queuedReports, ( mainQueuedReports: $ReadOnlyArray, ): $ReadOnlyArray => mainQueuedReports, ); const getClientResponsesSelector: ( state: AppState, ) => ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, getSignedIdentityKeysBlob: ?() => Promise, 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, currentCalendarQuery, ( threadInfos: RawThreadInfos, entryInfos: RawEntryInfos, userInfos: UserInfos, currentUserInfo: ?CurrentUserInfo, calendarQuery: (calendarActive: boolean) => CalendarQuery, ) => { return async ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, getSignedIdentityKeysBlob: ?() => Promise, getInitialNotificationsEncryptedMessage: ?() => Promise, serverRequests: $ReadOnlyArray, ): Promise<$ReadOnlyArray> => { const clientResponses = []; const serverRequestedPlatformDetails = serverRequests.some( request => request.type === serverRequestTypes.PLATFORM_DETAILS, ); for (const serverRequest of serverRequests) { if ( serverRequest.type === serverRequestTypes.PLATFORM && !serverRequestedPlatformDetails ) { clientResponses.push({ type: serverRequestTypes.PLATFORM, platform: getConfig().platformDetails.platform, }); } else if (serverRequest.type === serverRequestTypes.PLATFORM_DETAILS) { clientResponses.push({ type: serverRequestTypes.PLATFORM_DETAILS, platformDetails: getConfig().platformDetails, }); } else if (serverRequest.type === serverRequestTypes.CHECK_STATE) { - const filteredEntryInfos = filterRawEntryInfosByCalendarQuery( - serverEntryInfosObject(values(entryInfos)), - calendarQuery(calendarActive), - ); + const query = calendarQuery(calendarActive); - const convertedEntryInfos = convertClientIDsToServerIDs( - ashoatKeyserverID, - t.dict(tID, rawEntryInfoValidator), - filteredEntryInfos, - ); + const convertedEntryInfos = + stateSyncSpecs.entries.convertClientToServerInfos( + entryInfos, + query, + ); - const convertedThreadInfos = convertClientIDsToServerIDs( - ashoatKeyserverID, - t.dict(tID, rawThreadInfoValidator), - threadInfos, - ); + const convertedThreadInfos = + stateSyncSpecs.threads.convertClientToServerInfos( + threadInfos, + query, + ); - const convertedUserInfos = convertClientIDsToServerIDs( - ashoatKeyserverID, - userInfosValidator, - userInfos, - ); + const convertedUserInfos = + stateSyncSpecs.users.convertClientToServerInfos(userInfos, query); - const convertedCurrentUserInfo = convertClientIDsToServerIDs( - ashoatKeyserverID, - t.maybe(currentUserInfoValidator), - currentUserInfo, - ); + const convertedCurrentUserInfo = currentUserInfo + ? stateSyncSpecs.currentUser.convertClientToServerInfos( + currentUserInfo, + query, + ) + : currentUserInfo; const hashResults = {}; for (const key in serverRequest.hashesToCheck) { const expectedHashValue = serverRequest.hashesToCheck[key]; let hashValue; if (key === 'threadInfos') { hashValue = hash(convertedThreadInfos); } else if (key === 'entryInfos') { hashValue = hash(convertedEntryInfos); } else if (key === 'userInfos') { hashValue = hash(convertedUserInfos); } else if (key === 'currentUserInfo') { hashValue = hash(convertedCurrentUserInfo); } else if (key.startsWith('threadInfo|')) { const [, threadID] = key.split('|'); hashValue = hash(convertedThreadInfos[threadID]); } else if (key.startsWith('entryInfo|')) { const [, entryID] = key.split('|'); let rawEntryInfo = convertedEntryInfos[entryID]; if (rawEntryInfo) { rawEntryInfo = serverEntryInfo(rawEntryInfo); } hashValue = hash(rawEntryInfo); } else if (key.startsWith('userInfo|')) { const [, userID] = key.split('|'); hashValue = hash(convertedUserInfos[userID]); } else { continue; } hashResults[key] = expectedHashValue === hashValue; } const { failUnmentioned } = serverRequest; if (failUnmentioned && failUnmentioned.threadInfos) { for (const threadID in convertedThreadInfos) { const key = `threadInfo|${threadID}`; const hashResult = hashResults[key]; if (hashResult === null || hashResult === undefined) { hashResults[key] = false; } } } if (failUnmentioned && failUnmentioned.entryInfos) { for (const entryID in convertedEntryInfos) { const key = `entryInfo|${entryID}`; const hashResult = hashResults[key]; if (hashResult === null || hashResult === undefined) { hashResults[key] = false; } } } if (failUnmentioned && failUnmentioned.userInfos) { for (const userID in convertedUserInfos) { const key = `userInfo|${userID}`; const hashResult = hashResults[key]; if (hashResult === null || hashResult === undefined) { hashResults[key] = false; } } } clientResponses.push({ type: serverRequestTypes.CHECK_STATE, hashResults, }); } else if ( serverRequest.type === serverRequestTypes.MORE_ONE_TIME_KEYS && oneTimeKeyGenerator ) { const keys: string[] = []; for (let i = 0; i < minimumOneTimeKeysRequired; ++i) { keys.push(oneTimeKeyGenerator(i)); } clientResponses.push({ type: serverRequestTypes.MORE_ONE_TIME_KEYS, keys, }); } else if ( serverRequest.type === serverRequestTypes.SIGNED_IDENTITY_KEYS_BLOB && getSignedIdentityKeysBlob ) { const signedIdentityKeysBlob = await getSignedIdentityKeysBlob(); clientResponses.push({ type: serverRequestTypes.SIGNED_IDENTITY_KEYS_BLOB, signedIdentityKeysBlob, }); } else if ( serverRequest.type === serverRequestTypes.INITIAL_NOTIFICATIONS_ENCRYPTED_MESSAGE && getInitialNotificationsEncryptedMessage ) { const initialNotificationsEncryptedMessage = await getInitialNotificationsEncryptedMessage(); clientResponses.push({ type: serverRequestTypes.INITIAL_NOTIFICATIONS_ENCRYPTED_MESSAGE, initialNotificationsEncryptedMessage, }); } } return clientResponses; }; }, ); const sessionStateFuncSelector: ( state: AppState, ) => (calendarActive: boolean) => SessionState = createSelector( currentAsOfSelector, updatesCurrentAsOfSelector, currentCalendarQuery, ( messagesCurrentAsOf: number, updatesCurrentAsOf: number, calendarQuery: (calendarActive: boolean) => CalendarQuery, ) => (calendarActive: boolean): SessionState => ({ calendarQuery: calendarQuery(calendarActive), messagesCurrentAsOf, updatesCurrentAsOf, watchedIDs: threadWatcher.getWatchedIDs(), }), ); export { queuedReports, getClientResponsesSelector, sessionStateFuncSelector }; diff --git a/lib/shared/state-sync/current-user-state-sync-spec.js b/lib/shared/state-sync/current-user-state-sync-spec.js index b6a5a3e05..78b1e2114 100644 --- a/lib/shared/state-sync/current-user-state-sync-spec.js +++ b/lib/shared/state-sync/current-user-state-sync-spec.js @@ -1,7 +1,23 @@ // @flow import type { StateSyncSpec } from './state-sync-spec.js'; +import { + type CurrentUserInfo, + type OldCurrentUserInfo, + currentUserInfoValidator, +} from '../../types/user-types.js'; +import { convertClientIDsToServerIDs } from '../../utils/conversion-utils.js'; +import { ashoatKeyserverID } from '../../utils/validation-utils.js'; -export const currentUserStateSyncSpec: StateSyncSpec<> = Object.freeze({ +export const currentUserStateSyncSpec: StateSyncSpec< + OldCurrentUserInfo | CurrentUserInfo, +> = Object.freeze({ hashKey: 'currentUserInfo', + convertClientToServerInfos(info: OldCurrentUserInfo | CurrentUserInfo) { + return convertClientIDsToServerIDs( + ashoatKeyserverID, + currentUserInfoValidator, + info, + ); + }, }); diff --git a/lib/shared/state-sync/entries-state-sync-spec.js b/lib/shared/state-sync/entries-state-sync-spec.js index 435b780ee..4023e242c 100644 --- a/lib/shared/state-sync/entries-state-sync-spec.js +++ b/lib/shared/state-sync/entries-state-sync-spec.js @@ -1,12 +1,43 @@ // @flow +import t from 'tcomb'; + import type { StateSyncSpec } from './state-sync-spec.js'; +import { + type CalendarQuery, + type RawEntryInfos, + rawEntryInfoValidator, +} from '../../types/entry-types.js'; +import { convertClientIDsToServerIDs } from '../../utils/conversion-utils.js'; +import { values } from '../../utils/objects.js'; +import { ashoatKeyserverID, tID } from '../../utils/validation-utils.js'; +import { + filterRawEntryInfosByCalendarQuery, + serverEntryInfosObject, +} from '../entry-utils.js'; + +export const entriesStateSyncSpec: StateSyncSpec = Object.freeze( + { + hashKey: 'entryInfos', + innerHashSpec: { + hashKey: 'entryInfo', + deleteKey: 'deleteEntryIDs', + rawInfosKey: 'rawEntryInfos', + }, + convertClientToServerInfos( + infos: RawEntryInfos, + calendarQuery: CalendarQuery, + ) { + const filteredEntryInfos = filterRawEntryInfosByCalendarQuery( + serverEntryInfosObject(values(infos)), + calendarQuery, + ); -export const entriesStateSyncSpec: StateSyncSpec<> = Object.freeze({ - hashKey: 'entryInfos', - innerHashSpec: { - hashKey: 'entryInfo', - deleteKey: 'deleteEntryIDs', - rawInfosKey: 'rawEntryInfos', + return convertClientIDsToServerIDs( + ashoatKeyserverID, + t.dict(tID, rawEntryInfoValidator), + filteredEntryInfos, + ); + }, }, -}); +); diff --git a/lib/shared/state-sync/state-sync-spec.js b/lib/shared/state-sync/state-sync-spec.js index 0ad0d90bf..a40eaa6f1 100644 --- a/lib/shared/state-sync/state-sync-spec.js +++ b/lib/shared/state-sync/state-sync-spec.js @@ -1,11 +1,17 @@ // @flow -export type StateSyncSpec = { +import type { CalendarQuery } from '../../types/entry-types.js'; + +export type StateSyncSpec = { +hashKey: string, +innerHashSpec?: { +hashKey: string, +deleteKey: string, +rawInfosKey: string, +additionalDeleteCondition?: Info => boolean, }, + +convertClientToServerInfos: ( + infos: Infos, + calendarQuery: CalendarQuery, + ) => Infos, }; diff --git a/lib/shared/state-sync/threads-state-sync-spec.js b/lib/shared/state-sync/threads-state-sync-spec.js index 04bf77f2d..aace2dbc1 100644 --- a/lib/shared/state-sync/threads-state-sync-spec.js +++ b/lib/shared/state-sync/threads-state-sync-spec.js @@ -1,12 +1,28 @@ // @flow +import t from 'tcomb'; + import type { StateSyncSpec } from './state-sync-spec.js'; +import { + type RawThreadInfos, + rawThreadInfoValidator, +} from '../../types/thread-types.js'; +import { convertClientIDsToServerIDs } from '../../utils/conversion-utils.js'; +import { ashoatKeyserverID, tID } from '../../utils/validation-utils.js'; -export const threadsStateSyncSpec: StateSyncSpec<> = Object.freeze({ - hashKey: 'threadInfos', - innerHashSpec: { - hashKey: 'threadInfo', - deleteKey: 'deleteThreadIDs', - rawInfosKey: 'rawThreadInfos', - }, -}); +export const threadsStateSyncSpec: StateSyncSpec = + Object.freeze({ + hashKey: 'threadInfos', + innerHashSpec: { + hashKey: 'threadInfo', + deleteKey: 'deleteThreadIDs', + rawInfosKey: 'rawThreadInfos', + }, + convertClientToServerInfos(infos: RawThreadInfos) { + return convertClientIDsToServerIDs( + ashoatKeyserverID, + t.dict(tID, rawThreadInfoValidator), + infos, + ); + }, + }); diff --git a/lib/shared/state-sync/users-state-sync-spec.js b/lib/shared/state-sync/users-state-sync-spec.js index 0e54064cc..4907c1d5b 100644 --- a/lib/shared/state-sync/users-state-sync-spec.js +++ b/lib/shared/state-sync/users-state-sync-spec.js @@ -1,16 +1,30 @@ // @flow import type { StateSyncSpec } from './state-sync-spec.js'; -import type { UserInfo } from '../../types/user-types.js'; +import { + type UserInfo, + type UserInfos, + userInfosValidator, +} from '../../types/user-types.js'; +import { convertClientIDsToServerIDs } from '../../utils/conversion-utils.js'; +import { ashoatKeyserverID } from '../../utils/validation-utils.js'; -export const usersStateSyncSpec: StateSyncSpec = Object.freeze({ - hashKey: 'userInfos', - innerHashSpec: { - hashKey: 'userInfo', - deleteKey: 'deleteUserInfoIDs', - rawInfosKey: 'userInfos', - additionalDeleteCondition(user: UserInfo) { - return !user.username; +export const usersStateSyncSpec: StateSyncSpec = + Object.freeze({ + hashKey: 'userInfos', + innerHashSpec: { + hashKey: 'userInfo', + deleteKey: 'deleteUserInfoIDs', + rawInfosKey: 'userInfos', + additionalDeleteCondition(user: UserInfo) { + return !user.username; + }, }, - }, -}); + convertClientToServerInfos(infos: UserInfos) { + return convertClientIDsToServerIDs( + ashoatKeyserverID, + userInfosValidator, + infos, + ); + }, + });