diff --git a/lib/selectors/socket-selectors.js b/lib/selectors/socket-selectors.js index 96d8a7ca0..ad6e35414 100644 --- a/lib/selectors/socket-selectors.js +++ b/lib/selectors/socket-selectors.js @@ -1,226 +1,257 @@ // @flow import _memoize from 'lodash/memoize.js'; import { createSelector } from 'reselect'; import { updatesCurrentAsOfSelector, currentAsOfSelector, + urlPrefixSelector, + cookieSelector, } from './keyserver-selectors.js'; import { currentCalendarQuery } from './nav-selectors.js'; +import { createOpenSocketFunction } from '../shared/socket-utils.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 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 { getConfig } from '../utils/config.js'; import { minimumOneTimeKeysRequired } from '../utils/crypto-utils.js'; import { values } from '../utils/objects.js'; +const baseOpenSocketSelector: ( + keyserverID: string, +) => (state: AppState) => ?() => WebSocket = keyserverID => + createSelector( + urlPrefixSelector(keyserverID), + // 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. + cookieSelector(keyserverID), + (urlPrefix: ?string) => { + if (!urlPrefix) { + return null; + } + return createOpenSocketFunction(urlPrefix); + }, + ); + +const openSocketSelector: ( + keyserverID: string, +) => (state: AppState) => ?() => WebSocket = _memoize(baseOpenSocketSelector); + const queuedReports: ( state: AppState, ) => $ReadOnlyArray = createSelector( (state: AppState) => state.reportStore.queuedReports, ( mainQueuedReports: $ReadOnlyArray, ): $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. type BoundStateSyncSpecs = { +specsPerHashKey: { +[string]: BoundStateSyncSpec }, +specPerInnerHashKey: { +[string]: BoundStateSyncSpec }, }; const stateSyncSpecSelectors = values(stateSyncSpecs).map( spec => spec.selector, ); const boundStateSyncSpecsSelector: AppState => BoundStateSyncSpecs = // 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, ) => ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, getSignedIdentityKeysBlob: () => Promise, getInitialNotificationsEncryptedMessage: ?() => Promise, serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray> = createSelector( boundStateSyncSpecsSelector, currentCalendarQuery, ( boundStateSyncSpecs: BoundStateSyncSpecs, 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, ); const { specsPerHashKey, specPerInnerHashKey } = boundStateSyncSpecs; 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 query = calendarQuery(calendarActive); const hashResults: { [string]: boolean } = {}; for (const key in serverRequest.hashesToCheck) { const expectedHashValue = serverRequest.hashesToCheck[key]; let hashValue; const [specKey, id] = key.split('|'); if (id) { hashValue = specPerInnerHashKey[specKey]?.getInfoHash(id); } else { 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; } } const { failUnmentioned } = serverRequest; for (const spec of values(specPerInnerHashKey)) { const innerHashKey = spec.innerHashSpec?.hashKey; if (!failUnmentioned?.[spec.hashKey] || !innerHashKey) { continue; } 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) { 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 ) { 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 baseSessionStateFuncSelector: ( keyserverID: string, ) => ( state: AppState, ) => (calendarActive: boolean) => SessionState = keyserverID => createSelector( currentAsOfSelector(keyserverID), updatesCurrentAsOfSelector(keyserverID), currentCalendarQuery, ( messagesCurrentAsOf: number, updatesCurrentAsOf: number, calendarQuery: (calendarActive: boolean) => CalendarQuery, ) => (calendarActive: boolean): SessionState => ({ calendarQuery: calendarQuery(calendarActive), messagesCurrentAsOf, updatesCurrentAsOf, watchedIDs: threadWatcher.getWatchedIDs(), }), ); const sessionStateFuncSelector: ( keyserverID: string, ) => (state: AppState) => (calendarActive: boolean) => SessionState = _memoize( baseSessionStateFuncSelector, ); -export { queuedReports, getClientResponsesSelector, sessionStateFuncSelector }; +export { + openSocketSelector, + queuedReports, + getClientResponsesSelector, + sessionStateFuncSelector, +}; diff --git a/native/selectors/socket-selectors.js b/native/selectors/socket-selectors.js index 660048f34..eee94e09f 100644 --- a/native/selectors/socket-selectors.js +++ b/native/selectors/socket-selectors.js @@ -1,151 +1,123 @@ // @flow import _memoize from 'lodash/memoize.js'; import { createSelector } from 'reselect'; -import { - cookieSelector, - urlPrefixSelector, -} from 'lib/selectors/keyserver-selectors.js'; +import { cookieSelector } from 'lib/selectors/keyserver-selectors.js'; 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 { commCoreModule } from '../native-modules.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 baseOpenSocketSelector: ( - keyserverID: string, -) => (state: AppState) => ?() => WebSocket = keyserverID => - createSelector( - urlPrefixSelector(keyserverID), - // 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. - cookieSelector(keyserverID), - (urlPrefix: ?string) => { - if (!urlPrefix) { - return null; - } - return createOpenSocketFunction(urlPrefix); - }, - ); - -const openSocketSelector: ( - keyserverID: string, -) => (state: AppState) => ?() => WebSocket = _memoize(baseOpenSocketSelector); - const baseSessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = keyserverID => createSelector( cookieSelector(keyserverID), (cookie: ?string): SessionIdentification => ({ cookie }), ); const sessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = _memoize( baseSessionIdentificationSelector, ); 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; } async function getSignedIdentityKeysBlob(): Promise { await commCoreModule.initializeCryptoAccount(); const { blobPayload, signature } = await commCoreModule.getUserPublicKey(); const signedIdentityKeysBlob: SignedIdentityKeysBlob = { payload: blobPayload, signature, }; return signedIdentityKeysBlob; } type NativeGetClientResponsesSelectorInputType = { ...NavPlusRedux, getInitialNotificationsEncryptedMessage: () => Promise, }; const nativeGetClientResponsesSelector: ( input: NativeGetClientResponsesSelectorInputType, ) => ( serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray> = createSelector( (input: NativeGetClientResponsesSelectorInputType) => getClientResponsesSelector(input.redux), (input: NativeGetClientResponsesSelectorInputType) => calendarActiveSelector(input.navContext), (input: NativeGetClientResponsesSelectorInputType) => input.getInitialNotificationsEncryptedMessage, ( getClientResponsesFunc: ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, getSignedIdentityKeysBlob: () => Promise, getInitialNotificationsEncryptedMessage: ?() => Promise, serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray>, calendarActive: boolean, getInitialNotificationsEncryptedMessage: () => Promise, ) => (serverRequests: $ReadOnlyArray) => getClientResponsesFunc( calendarActive, oneTimeKeyGenerator, getSignedIdentityKeysBlob, getInitialNotificationsEncryptedMessage, serverRequests, ), ); const baseNativeSessionStateFuncSelector: ( keyserverID: string, ) => (input: NavPlusRedux) => () => SessionState = keyserverID => createSelector( (input: NavPlusRedux) => sessionStateFuncSelector(keyserverID)(input.redux), (input: NavPlusRedux) => calendarActiveSelector(input.navContext), ( sessionStateFunc: (calendarActive: boolean) => SessionState, calendarActive: boolean, ) => () => sessionStateFunc(calendarActive), ); const nativeSessionStateFuncSelector: ( keyserverID: string, ) => (input: NavPlusRedux) => () => SessionState = _memoize( baseNativeSessionStateFuncSelector, ); export { - openSocketSelector, sessionIdentificationSelector, nativeGetClientResponsesSelector, nativeSessionStateFuncSelector, }; diff --git a/native/socket.react.js b/native/socket.react.js index ce1707f97..9123d47e4 100644 --- a/native/socket.react.js +++ b/native/socket.react.js @@ -1,178 +1,178 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { resolveKeyserverSessionInvalidation } from 'lib/keyserver-conn/recovery-utils.js'; import { preRequestUserStateForSingleKeyserverSelector } from 'lib/selectors/account-selectors.js'; import { cookieSelector, urlPrefixSelector, connectionSelector, lastCommunicatedPlatformDetailsSelector, } from 'lib/selectors/keyserver-selectors.js'; +import { openSocketSelector } from 'lib/selectors/socket-selectors.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { accountHasPassword } from 'lib/shared/account-utils.js'; import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js'; import Socket, { type BaseSocketProps } from 'lib/socket/socket.react.js'; import { logInActionSources } from 'lib/types/account-types.js'; import { setConnectionIssueActionType } from 'lib/types/socket-types.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; import { InputStateContext } from './input/input-state.js'; import { activeMessageListSelector, nativeCalendarQuery, } from './navigation/nav-selectors.js'; import { NavContext } from './navigation/navigation-context.js'; import { useSelector } from './redux/redux-utils.js'; import { noDataAfterPolicyAcknowledgmentSelector } from './selectors/account-selectors.js'; import { - openSocketSelector, sessionIdentificationSelector, nativeGetClientResponsesSelector, nativeSessionStateFuncSelector, } from './selectors/socket-selectors.js'; import Alert from './utils/alert.js'; import { nativeNotificationsSessionCreator } from './utils/crypto-utils.js'; import { decompressMessage } from './utils/decompress.js'; const NativeSocket: React.ComponentType = React.memo(function NativeSocket(props: BaseSocketProps) { const inputState = React.useContext(InputStateContext); const navContext = React.useContext(NavContext); const cookie = useSelector(cookieSelector(ashoatKeyserverID)); const urlPrefix = useSelector(urlPrefixSelector(ashoatKeyserverID)); invariant(urlPrefix, 'missing urlPrefix for given keyserver id'); const connection = useSelector(connectionSelector(ashoatKeyserverID)); invariant(connection, 'keyserver missing from keyserverStore'); const frozen = useSelector(state => state.frozen); const active = useSelector( state => isLoggedIn(state) && state.lifecycleState !== 'background', ); const noDataAfterPolicyAcknowledgment = useSelector( noDataAfterPolicyAcknowledgmentSelector(ashoatKeyserverID), ); const currentUserInfo = useSelector(state => state.currentUserInfo); const openSocket = useSelector(openSocketSelector(ashoatKeyserverID)); invariant(openSocket, 'openSocket failed to be created'); const sessionIdentification = useSelector( sessionIdentificationSelector(ashoatKeyserverID), ); const preRequestUserState = useSelector( preRequestUserStateForSingleKeyserverSelector(ashoatKeyserverID), ); const getInitialNotificationsEncryptedMessage = useInitialNotificationsEncryptedMessage( nativeNotificationsSessionCreator, ); const getClientResponses = useSelector(state => nativeGetClientResponsesSelector({ redux: state, navContext, getInitialNotificationsEncryptedMessage, }), ); const sessionStateFunc = useSelector(state => nativeSessionStateFuncSelector(ashoatKeyserverID)({ redux: state, navContext, }), ); const currentCalendarQuery = useSelector(state => nativeCalendarQuery({ redux: state, navContext, }), ); const canSendReports = useSelector( state => !state.frozen && state.connectivity.hasWiFi && (!inputState || !inputState.uploadInProgress()), ); const activeThread = React.useMemo(() => { if (!active) { return null; } return activeMessageListSelector(navContext); }, [active, navContext]); const lastCommunicatedPlatformDetails = useSelector( lastCommunicatedPlatformDetailsSelector(ashoatKeyserverID), ); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const socketCrashLoopRecovery = React.useCallback(async () => { if (!accountHasPassword(currentUserInfo)) { void dispatch({ type: setConnectionIssueActionType, payload: { keyserverID: ashoatKeyserverID, connectionIssue: 'policy_acknowledgement_socket_crash_loop', }, }); Alert.alert( 'Log in needed', 'After acknowledging the policies, we need you to log in to your account again', [{ text: 'OK' }], ); return; } await resolveKeyserverSessionInvalidation( dispatch, cookie, urlPrefix, logInActionSources.refetchUserDataAfterAcknowledgment, ashoatKeyserverID, getInitialNotificationsEncryptedMessage, ); }, [ cookie, currentUserInfo, dispatch, urlPrefix, getInitialNotificationsEncryptedMessage, ]); return ( ); }); export default NativeSocket; diff --git a/web/selectors/socket-selectors.js b/web/selectors/socket-selectors.js index 916cec01c..e415844a0 100644 --- a/web/selectors/socket-selectors.js +++ b/web/selectors/socket-selectors.js @@ -1,127 +1,110 @@ // @flow import _memoize from 'lodash/memoize.js'; import { createSelector } from 'reselect'; import { sessionIDSelector, - urlPrefixSelector, cookieSelector, } from 'lib/selectors/keyserver-selectors.js'; 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 baseOpenSocketSelector: ( - keyserverID: string, -) => (state: AppState) => ?() => WebSocket = keyserverID => - createSelector(urlPrefixSelector(keyserverID), (urlPrefix: ?string) => { - if (!urlPrefix) { - return null; - } - return createOpenSocketFunction(urlPrefix); - }); - -const openSocketSelector: ( - keyserverID: string, -) => (state: AppState) => ?() => WebSocket = _memoize(baseOpenSocketSelector); - const baseSessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = keyserverID => createSelector( cookieSelector(keyserverID), sessionIDSelector(keyserverID), (cookie: ?string, sessionID: ?string): SessionIdentification => ({ cookie, sessionID, }), ); const sessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = _memoize( baseSessionIdentificationSelector, ); type WebGetClientResponsesSelectorInputType = { +state: AppState, +getSignedIdentityKeysBlob: () => Promise, +getInitialNotificationsEncryptedMessage: () => Promise, }; const webGetClientResponsesSelector: ( input: WebGetClientResponsesSelectorInputType, ) => ( serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray> = createSelector( (input: WebGetClientResponsesSelectorInputType) => getClientResponsesSelector(input.state), (input: WebGetClientResponsesSelectorInputType) => input.getSignedIdentityKeysBlob, (input: WebGetClientResponsesSelectorInputType) => input.state.navInfo.tab === 'calendar', (input: WebGetClientResponsesSelectorInputType) => input.getInitialNotificationsEncryptedMessage, ( getClientResponsesFunc: ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, getSignedIdentityKeysBlob: () => Promise, getInitialNotificationsEncryptedMessage: () => Promise, serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray>, getSignedIdentityKeysBlob: () => Promise, calendarActive: boolean, getInitialNotificationsEncryptedMessage: () => Promise, ) => (serverRequests: $ReadOnlyArray) => getClientResponsesFunc( calendarActive, null, getSignedIdentityKeysBlob, getInitialNotificationsEncryptedMessage, serverRequests, ), ); const baseWebSessionStateFuncSelector: ( keyserverID: string, ) => (state: AppState) => () => SessionState = keyserverID => createSelector( sessionStateFuncSelector(keyserverID), (state: AppState) => state.navInfo.tab === 'calendar', ( sessionStateFunc: (calendarActive: boolean) => SessionState, calendarActive: boolean, ) => () => sessionStateFunc(calendarActive), ); const webSessionStateFuncSelector: ( keyserverID: string, ) => (state: AppState) => () => SessionState = _memoize( baseWebSessionStateFuncSelector, ); export { - openSocketSelector, sessionIdentificationSelector, webGetClientResponsesSelector, webSessionStateFuncSelector, }; diff --git a/web/socket.react.js b/web/socket.react.js index 02463f56f..31a0ad198 100644 --- a/web/socket.react.js +++ b/web/socket.react.js @@ -1,128 +1,128 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { preRequestUserStateForSingleKeyserverSelector } from 'lib/selectors/account-selectors.js'; import { cookieSelector, urlPrefixSelector, connectionSelector, lastCommunicatedPlatformDetailsSelector, } from 'lib/selectors/keyserver-selectors.js'; +import { openSocketSelector } from 'lib/selectors/socket-selectors.js'; import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js'; import Socket, { type BaseSocketProps } from 'lib/socket/socket.react.js'; import type { OLMIdentityKeys } from 'lib/types/crypto-types.js'; import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; import { useGetSignedIdentityKeysBlob, useWebNotificationsSessionCreator, } from './account/account-hooks.js'; import { useSelector } from './redux/redux-utils.js'; import { activeThreadSelector, webCalendarQuery, } from './selectors/nav-selectors.js'; import { - openSocketSelector, sessionIdentificationSelector, webGetClientResponsesSelector, webSessionStateFuncSelector, } from './selectors/socket-selectors.js'; import { decompressMessage } from './utils/decompress.js'; const WebSocket: React.ComponentType = React.memo(function WebSocket(props) { const cookie = useSelector(cookieSelector(ashoatKeyserverID)); const urlPrefix = useSelector(urlPrefixSelector(ashoatKeyserverID)); invariant(urlPrefix, 'missing urlPrefix for given keyserver id'); const connection = useSelector(connectionSelector(ashoatKeyserverID)); invariant(connection, 'keyserver missing from keyserverStore'); const active = useSelector( state => !!state.currentUserInfo && !state.currentUserInfo.anonymous && state.lifecycleState !== 'background', ); const openSocket = useSelector(openSocketSelector(ashoatKeyserverID)); invariant(openSocket, 'openSocket failed to be created'); const sessionIdentification = useSelector( sessionIdentificationSelector(ashoatKeyserverID), ); const preRequestUserState = useSelector( preRequestUserStateForSingleKeyserverSelector(ashoatKeyserverID), ); const getSignedIdentityKeysBlob = useGetSignedIdentityKeysBlob(); const webNotificationsSessionCreator = useWebNotificationsSessionCreator(); const webNotifsSessionCreatorForCookie = React.useCallback( async ( notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, ) => webNotificationsSessionCreator( cookie, notificationsIdentityKeys, notificationsInitializationInfo, ), [webNotificationsSessionCreator, cookie], ); const getInitialNotificationsEncryptedMessage = useInitialNotificationsEncryptedMessage(webNotifsSessionCreatorForCookie); const getClientResponses = useSelector(state => webGetClientResponsesSelector({ state, getSignedIdentityKeysBlob, getInitialNotificationsEncryptedMessage, }), ); const sessionStateFunc = useSelector( webSessionStateFuncSelector(ashoatKeyserverID), ); const currentCalendarQuery = useSelector(webCalendarQuery); const reduxActiveThread = useSelector(activeThreadSelector); const windowActive = useSelector(state => state.windowActive); const activeThread = React.useMemo(() => { if (!active || !windowActive) { return null; } return reduxActiveThread; }, [active, windowActive, reduxActiveThread]); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const lastCommunicatedPlatformDetails = useSelector( lastCommunicatedPlatformDetailsSelector(ashoatKeyserverID), ); return ( ); }); export default WebSocket;