diff --git a/lib/selectors/socket-selectors.js b/lib/selectors/socket-selectors.js index 0b5c61c1c..39e490c89 100644 --- a/lib/selectors/socket-selectors.js +++ b/lib/selectors/socket-selectors.js @@ -1,185 +1,197 @@ // @flow import { createSelector } from 'reselect'; import { currentCalendarQuery } from './nav-selectors.js'; import { serverEntryInfo, serverEntryInfosObject, filterRawEntryInfosByCalendarQuery, } from '../shared/entry-utils.js'; import threadWatcher from '../shared/thread-watcher.js'; +import type { SignedIdentityKeysBlob } from '../types/crypto-types.js'; import type { RawEntryInfo, 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 { RawThreadInfo } from '../types/thread-types.js'; import type { CurrentUserInfo, UserInfos } from '../types/user-types.js'; import { getConfig } from '../utils/config.js'; import { minimumOneTimeKeysRequired } from '../utils/crypto-utils.js'; import { values, 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: ?() => SignedIdentityKeysBlob, serverRequests: $ReadOnlyArray, ) => $ReadOnlyArray = createSelector( (state: AppState) => state.threadStore.threadInfos, (state: AppState) => state.entryStore.entryInfos, (state: AppState) => state.userStore.userInfos, (state: AppState) => state.currentUserInfo, currentCalendarQuery, ( threadInfos: { +[id: string]: RawThreadInfo }, entryInfos: { +[id: string]: RawEntryInfo }, userInfos: UserInfos, currentUserInfo: ?CurrentUserInfo, calendarQuery: (calendarActive: boolean) => CalendarQuery, ) => ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, + getSignedIdentityKeysBlob: ?() => SignedIdentityKeysBlob, serverRequests: $ReadOnlyArray, ): $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 hashResults = {}; for (const key in serverRequest.hashesToCheck) { const expectedHashValue = serverRequest.hashesToCheck[key]; let hashValue; if (key === 'threadInfos') { hashValue = hash(threadInfos); } else if (key === 'entryInfos') { hashValue = hash(filteredEntryInfos); } else if (key === 'userInfos') { hashValue = hash(userInfos); } else if (key === 'currentUserInfo') { hashValue = hash(currentUserInfo); } else if (key.startsWith('threadInfo|')) { const [, threadID] = key.split('|'); hashValue = hash(threadInfos[threadID]); } else if (key.startsWith('entryInfo|')) { const [, entryID] = key.split('|'); let rawEntryInfo = filteredEntryInfos[entryID]; if (rawEntryInfo) { rawEntryInfo = serverEntryInfo(rawEntryInfo); } hashValue = hash(rawEntryInfo); } else if (key.startsWith('userInfo|')) { const [, userID] = key.split('|'); hashValue = hash(userInfos[userID]); } else { continue; } hashResults[key] = expectedHashValue === hashValue; } const { failUnmentioned } = serverRequest; if (failUnmentioned && failUnmentioned.threadInfos) { for (const threadID in threadInfos) { const key = `threadInfo|${threadID}`; const hashResult = hashResults[key]; if (hashResult === null || hashResult === undefined) { hashResults[key] = false; } } } if (failUnmentioned && failUnmentioned.entryInfos) { for (const entryID in filteredEntryInfos) { const key = `entryInfo|${entryID}`; const hashResult = hashResults[key]; if (hashResult === null || hashResult === undefined) { hashResults[key] = false; } } } if (failUnmentioned && failUnmentioned.userInfos) { for (const userID in userInfos) { 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 = getSignedIdentityKeysBlob(); + clientResponses.push({ + type: serverRequestTypes.SIGNED_IDENTITY_KEYS_BLOB, + signedIdentityKeysBlob, + }); } } return clientResponses; }, ); const sessionStateFuncSelector: ( state: AppState, ) => (calendarActive: boolean) => SessionState = createSelector( (state: AppState) => state.messageStore.currentAsOf, (state: AppState) => state.updatesCurrentAsOf, 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/native/selectors/socket-selectors.js b/native/selectors/socket-selectors.js index 50fb60cde..e8262309c 100644 --- a/native/selectors/socket-selectors.js +++ b/native/selectors/socket-selectors.js @@ -1,94 +1,97 @@ // @flow import { createSelector } from 'reselect'; import { getClientResponsesSelector, sessionStateFuncSelector, } from 'lib/selectors/socket-selectors.js'; import { createOpenSocketFunction } from 'lib/shared/socket-utils.js'; +import type { SignedIdentityKeysBlob } from 'lib/types/crypto-types.js'; import type { ClientServerRequest, ClientClientResponse, } from 'lib/types/request-types.js'; import type { SessionIdentification, SessionState, } from 'lib/types/session-types.js'; import type { OneTimeKeyGenerator } from 'lib/types/socket-types.js'; import { calendarActiveSelector } from '../navigation/nav-selectors.js'; import type { AppState } from '../redux/state-types.js'; import type { NavPlusRedux } from '../types/selector-types.js'; const openSocketSelector: (state: AppState) => () => WebSocket = createSelector( (state: AppState) => state.urlPrefix, // We don't actually use the cookie in the socket open function, but we do use // it in the initial message, and when the cookie changes the socket needs to // be reopened. By including the cookie here, whenever the cookie changes this // function will change, which tells the Socket component to restart the // connection. (state: AppState) => state.cookie, createOpenSocketFunction, ); const sessionIdentificationSelector: ( state: AppState, ) => SessionIdentification = createSelector( (state: AppState) => state.cookie, (cookie: ?string): SessionIdentification => ({ cookie }), ); function oneTimeKeyGenerator(inc: number): string { // todo replace this hard code with something like // commCoreModule.generateOneTimeKeys() let str = Date.now().toString() + '_' + inc.toString() + '_'; while (str.length < 43) { str += Math.random().toString(36).substr(2, 5); } str = str.substr(0, 43); return str; } const nativeGetClientResponsesSelector: ( input: NavPlusRedux, ) => ( serverRequests: $ReadOnlyArray, ) => $ReadOnlyArray = createSelector( (input: NavPlusRedux) => getClientResponsesSelector(input.redux), (input: NavPlusRedux) => calendarActiveSelector(input.navContext), ( getClientResponsesFunc: ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, + getSignedIdentityKeysBlob: ?() => SignedIdentityKeysBlob, serverRequests: $ReadOnlyArray, ) => $ReadOnlyArray, calendarActive: boolean, ) => (serverRequests: $ReadOnlyArray) => getClientResponsesFunc( calendarActive, oneTimeKeyGenerator, + null, serverRequests, ), ); const nativeSessionStateFuncSelector: ( input: NavPlusRedux, ) => () => SessionState = createSelector( (input: NavPlusRedux) => sessionStateFuncSelector(input.redux), (input: NavPlusRedux) => calendarActiveSelector(input.navContext), ( sessionStateFunc: (calendarActive: boolean) => SessionState, calendarActive: boolean, ) => () => sessionStateFunc(calendarActive), ); export { openSocketSelector, sessionIdentificationSelector, nativeGetClientResponsesSelector, nativeSessionStateFuncSelector, }; diff --git a/web/selectors/socket-selectors.js b/web/selectors/socket-selectors.js index 54ebfc2f4..fbc823ace 100644 --- a/web/selectors/socket-selectors.js +++ b/web/selectors/socket-selectors.js @@ -1,70 +1,72 @@ // @flow import { createSelector } from 'reselect'; import { getClientResponsesSelector, sessionStateFuncSelector, } from 'lib/selectors/socket-selectors.js'; import { createOpenSocketFunction } from 'lib/shared/socket-utils.js'; +import type { SignedIdentityKeysBlob } from 'lib/types/crypto-types.js'; import type { ClientServerRequest, ClientClientResponse, } from 'lib/types/request-types.js'; import type { SessionIdentification, SessionState, } from 'lib/types/session-types.js'; import type { OneTimeKeyGenerator } from 'lib/types/socket-types.js'; import type { AppState } from '../redux/redux-setup.js'; const openSocketSelector: (state: AppState) => () => WebSocket = createSelector( (state: AppState) => state.baseHref, createOpenSocketFunction, ); const sessionIdentificationSelector: ( state: AppState, ) => SessionIdentification = createSelector( (state: AppState) => state.sessionID, (sessionID: ?string): SessionIdentification => ({ sessionID }), ); const webGetClientResponsesSelector: ( state: AppState, ) => ( serverRequests: $ReadOnlyArray, ) => $ReadOnlyArray = createSelector( getClientResponsesSelector, (state: AppState) => state.navInfo.tab === 'calendar', ( getClientResponsesFunc: ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, + getSignedIdentityKeysBlob: ?() => SignedIdentityKeysBlob, serverRequests: $ReadOnlyArray, ) => $ReadOnlyArray, calendarActive: boolean, ) => (serverRequests: $ReadOnlyArray) => - getClientResponsesFunc(calendarActive, null, serverRequests), + getClientResponsesFunc(calendarActive, null, null, serverRequests), ); const webSessionStateFuncSelector: (state: AppState) => () => SessionState = createSelector( sessionStateFuncSelector, (state: AppState) => state.navInfo.tab === 'calendar', ( sessionStateFunc: (calendarActive: boolean) => SessionState, calendarActive: boolean, ) => () => sessionStateFunc(calendarActive), ); export { openSocketSelector, sessionIdentificationSelector, webGetClientResponsesSelector, webSessionStateFuncSelector, };