diff --git a/lib/hooks/disconnected-bar.js b/lib/hooks/disconnected-bar.js --- a/lib/hooks/disconnected-bar.js +++ b/lib/hooks/disconnected-bar.js @@ -1,16 +1,19 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { useDispatch } from 'react-redux'; +import { connectionSelector } from '../selectors/keyserver-selectors.js'; import { updateDisconnectedBarActionType } from '../types/socket-types.js'; import { useSelector } from '../utils/redux-utils.js'; function useDisconnectedBarVisibilityHandler(networkConnected: boolean): void { const dispatch = useDispatch(); - const disconnected = useSelector( - state => state.connection.showDisconnectedBar, - ); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + + const disconnected = connection.showDisconnectedBar; const setDisconnected = React.useCallback( (newDisconnected: boolean) => { if (newDisconnected === disconnected) { @@ -33,10 +36,8 @@ }, [setDisconnected, networkConnected]); const prevConnectionStatusRef = React.useRef(); - const connectionStatus = useSelector(state => state.connection.status); - const someRequestIsLate = useSelector( - state => state.connection.lateResponses.length !== 0, - ); + const connectionStatus = connection.status; + const someRequestIsLate = connection.lateResponses.length !== 0; React.useEffect(() => { const prevConnectionStatus = prevConnectionStatusRef.current; prevConnectionStatusRef.current = connectionStatus; @@ -66,12 +67,10 @@ +disconnected: boolean, +shouldShowDisconnectedBar: boolean, } { - const disconnected = useSelector( - state => state.connection.showDisconnectedBar, - ); - const socketConnected = useSelector( - state => state.connection.status === 'connected', - ); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + const disconnected = connection.showDisconnectedBar; + const socketConnected = connection.status === 'connected'; const shouldShowDisconnectedBar = disconnected || !socketConnected; return { disconnected, shouldShowDisconnectedBar }; diff --git a/lib/reducers/connection-reducer.js b/lib/reducers/connection-reducer.js --- a/lib/reducers/connection-reducer.js +++ b/lib/reducers/connection-reducer.js @@ -10,6 +10,7 @@ logInActionTypes, registerActionTypes, } from '../actions/user-actions.js'; +import { connectionSelector } from '../selectors/keyserver-selectors.js'; import { queueActivityUpdatesActionType } from '../types/activity-types.js'; import { defaultCalendarQuery } from '../types/entry-types.js'; import { type BaseAction, rehydrateActionType } from '../types/redux-types.js'; @@ -93,11 +94,15 @@ actualizedCalendarQuery: action.payload.calendarQuery, }; } else if (action.type === rehydrateActionType) { - if (!action.payload || !action.payload.connection) { + if (!action.payload) { + return state; + } + const connection = connectionSelector(action.payload); + if (!connection) { return state; } return { - ...action.payload.connection, + ...connection, status: 'connecting', queuedActivityUpdates: [], lateResponses: [], diff --git a/lib/reducers/master-reducer.js b/lib/reducers/master-reducer.js --- a/lib/reducers/master-reducer.js +++ b/lib/reducers/master-reducer.js @@ -64,7 +64,8 @@ let keyserverStore = reduceKeyserverStore(state.keyserverStore, action); if ( - connection.status !== 'connected' && + keyserverStore.keyserverInfos[ashoatKeyserverID].connection.status !== + 'connected' && action.type !== incrementalStateSyncActionType && action.type !== fullStateSyncActionType && action.type !== registerActionTypes.success && diff --git a/lib/selectors/keyserver-selectors.js b/lib/selectors/keyserver-selectors.js --- a/lib/selectors/keyserver-selectors.js +++ b/lib/selectors/keyserver-selectors.js @@ -4,6 +4,7 @@ import type { KeyserverInfo } from '../types/keyserver-types'; import type { AppState } from '../types/redux-types.js'; +import type { ConnectionInfo } from '../types/socket-types.js'; import { ashoatKeyserverID } from '../utils/validation-utils.js'; const cookieSelector: (state: AppState) => ?string = (state: AppState) => @@ -37,6 +38,10 @@ const urlPrefixSelector: (state: AppState) => ?string = (state: AppState) => state.keyserverStore.keyserverInfos[ashoatKeyserverID]?.urlPrefix; +const connectionSelector: (state: AppState) => ?ConnectionInfo = ( + state: AppState, +) => state.keyserverStore.keyserverInfos[ashoatKeyserverID]?.connection; + export { cookieSelector, cookiesSelector, @@ -44,4 +49,5 @@ updatesCurrentAsOfSelector, currentAsOfSelector, urlPrefixSelector, + connectionSelector, }; diff --git a/lib/selectors/server-calls.js b/lib/selectors/server-calls.js --- a/lib/selectors/server-calls.js +++ b/lib/selectors/server-calls.js @@ -6,10 +6,14 @@ cookieSelector, sessionIDSelector, urlPrefixSelector, + connectionSelector, } from './keyserver-selectors.js'; import type { LastCommunicatedPlatformDetails } from '../types/device-types.js'; import type { AppState } from '../types/redux-types.js'; -import { type ConnectionStatus } from '../types/socket-types.js'; +import type { + ConnectionInfo, + ConnectionStatus, +} from '../types/socket-types.js'; import { type CurrentUserInfo } from '../types/user-types.js'; export type ServerCallState = { @@ -17,7 +21,7 @@ +urlPrefix: ?string, +sessionID: ?string, +currentUserInfo: ?CurrentUserInfo, - +connectionStatus: ConnectionStatus, + +connectionStatus: ?ConnectionStatus, +lastCommunicatedPlatformDetails: LastCommunicatedPlatformDetails, }; @@ -27,21 +31,21 @@ urlPrefixSelector, sessionIDSelector, (state: AppState) => state.currentUserInfo, - (state: AppState) => state.connection.status, + connectionSelector, (state: AppState) => state.lastCommunicatedPlatformDetails, ( cookie: ?string, urlPrefix: ?string, sessionID: ?string, currentUserInfo: ?CurrentUserInfo, - connectionStatus: ConnectionStatus, + connectionInfo: ?ConnectionInfo, lastCommunicatedPlatformDetails: LastCommunicatedPlatformDetails, ) => ({ cookie, urlPrefix, sessionID, currentUserInfo, - connectionStatus, + connectionStatus: connectionInfo?.status, lastCommunicatedPlatformDetails, }), ); diff --git a/lib/socket/activity-handler.react.js b/lib/socket/activity-handler.react.js --- a/lib/socket/activity-handler.react.js +++ b/lib/socket/activity-handler.react.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { useDispatch } from 'react-redux'; @@ -7,6 +8,7 @@ updateActivityActionTypes, updateActivity, } from '../actions/activity-actions.js'; +import { connectionSelector } from '../selectors/keyserver-selectors.js'; import { getMostRecentNonLocalMessageID } from '../shared/message-utils.js'; import { threadIsPending } from '../shared/thread-utils.js'; import { queueActivityUpdatesActionType } from '../types/activity-types.js'; @@ -29,7 +31,8 @@ }, [activeThread]); const prevActiveThread = prevActiveThreadRef.current; - const connection = useSelector(state => state.connection); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); const connectionStatus = connection.status; const prevConnectionStatusRef = React.useRef(); React.useEffect(() => { diff --git a/lib/socket/api-request-handler.react.js b/lib/socket/api-request-handler.react.js --- a/lib/socket/api-request-handler.react.js +++ b/lib/socket/api-request-handler.react.js @@ -4,6 +4,7 @@ import * as React from 'react'; import { InflightRequests } from './inflight-requests.js'; +import { connectionSelector } from '../selectors/keyserver-selectors.js'; import type { APIRequest } from '../types/endpoints.js'; import { clientSocketMessageTypes, @@ -35,7 +36,7 @@ // will wait for the response before shutting down. But if Socket starts // shutting down first, we'll have a problem. Note that this approach only // stops the race in fetchResponse below, and not in action-utils (which - // happens earlier via the registerActiveSocket call below), but empircally + // happens earlier via the registerActiveSocket call below), but empirically // that hasn't been an issue. // The reason I didn't rewrite this to happen in a single component is // because I want to maintain separation of concerns. Upcoming React Hooks @@ -92,7 +93,8 @@ const ConnectedAPIRequestHandler: React.ComponentType = React.memo(function ConnectedAPIRequestHandler(props) { - const connection = useSelector(state => state.connection); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); return ; }); diff --git a/lib/socket/calendar-query-handler.react.js b/lib/socket/calendar-query-handler.react.js --- a/lib/socket/calendar-query-handler.react.js +++ b/lib/socket/calendar-query-handler.react.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import _isEqual from 'lodash/fp/isEqual.js'; import * as React from 'react'; @@ -7,6 +8,7 @@ updateCalendarQueryActionTypes, updateCalendarQuery, } from '../actions/entry-actions.js'; +import { connectionSelector } from '../selectors/keyserver-selectors.js'; import { timeUntilCalendarRangeExpiration } from '../selectors/nav-selectors.js'; import { useIsAppForegrounded } from '../shared/lifecycle-utils.js'; import type { @@ -122,7 +124,8 @@ const ConnectedCalendarQueryHandler: React.ComponentType = React.memo(function ConnectedCalendarQueryHandler(props) { - const connection = useSelector(state => state.connection); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); const lastUserInteractionCalendar = useSelector( state => state.entryStore.lastUserInteractionCalendar, ); diff --git a/lib/socket/request-response-handler.react.js b/lib/socket/request-response-handler.react.js --- a/lib/socket/request-response-handler.react.js +++ b/lib/socket/request-response-handler.react.js @@ -5,6 +5,7 @@ import { useDispatch } from 'react-redux'; import { InflightRequests } from './inflight-requests.js'; +import { connectionSelector } from '../selectors/keyserver-selectors.js'; import type { CalendarQuery } from '../types/entry-types.js'; import type { Dispatch } from '../types/redux-types.js'; import { @@ -136,7 +137,9 @@ const ConnectedRequestResponseHandler: React.ComponentType = React.memo(function ConnectedRequestResponseHandler(props) { - const connection = useSelector(state => state.connection); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + const dispatch = useDispatch(); return ( diff --git a/lib/socket/update-handler.react.js b/lib/socket/update-handler.react.js --- a/lib/socket/update-handler.react.js +++ b/lib/socket/update-handler.react.js @@ -1,9 +1,11 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; +import { connectionSelector } from '../selectors/keyserver-selectors.js'; import { type ClientSocketMessageWithoutID, type SocketListener, @@ -23,7 +25,9 @@ const { addListener, removeListener, sendMessage } = props; const dispatch = useDispatch(); - const connectionStatus = useSelector(state => state.connection.status); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + const connectionStatus = connection.status; const onMessage = React.useCallback( (message: ClientServerSocketMessage) => { if (message.type !== serverSocketMessageTypes.UPDATES) { diff --git a/lib/utils/action-utils.js b/lib/utils/action-utils.js --- a/lib/utils/action-utils.js +++ b/lib/utils/action-utils.js @@ -431,12 +431,16 @@ const dispatch = useDispatch(); const serverCallState = useSelector(serverCallStateSelector); return React.useMemo(() => { - const { urlPrefix } = serverCallState; - invariant(urlPrefix, 'missing urlPrefix for given keyserver id'); + const { urlPrefix, connectionStatus } = serverCallState; + invariant( + !!urlPrefix && !!connectionStatus, + 'keyserver missing from keyserverStore', + ); return createBoundServerCallsSelector(serverCall)({ ...serverCallState, urlPrefix, + connectionStatus, dispatch, ...paramOverride, }); diff --git a/native/calendar/calendar.react.js b/native/calendar/calendar.react.js --- a/native/calendar/calendar.react.js +++ b/native/calendar/calendar.react.js @@ -24,6 +24,7 @@ updateCalendarQueryActionTypes, updateCalendarQuery, } from 'lib/actions/entry-actions.js'; +import { connectionSelector } from 'lib/selectors/keyserver-selectors.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { entryKey } from 'lib/shared/entry-utils.js'; import type { @@ -1068,7 +1069,9 @@ const calendarFilters = useSelector(state => state.calendarFilters); const dimensions = useSelector(derivedDimensionsInfoSelector); const loadingStatus = useSelector(loadingStatusSelector); - const connectionStatus = useSelector(state => state.connection.status); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + const connectionStatus = connection.status; const colors = useColors(); const styles = useStyles(unboundStyles); const indicatorStyle = useIndicatorStyle(); diff --git a/native/calendar/entry.react.js b/native/calendar/entry.react.js --- a/native/calendar/entry.react.js +++ b/native/calendar/entry.react.js @@ -28,6 +28,7 @@ concurrentModificationResetActionType, } from 'lib/actions/entry-actions.js'; import { registerFetchKey } from 'lib/reducers/loading-reducer.js'; +import { connectionSelector } from 'lib/selectors/keyserver-selectors.js'; import { colorIsDark } from 'lib/shared/color-utils.js'; import { entryKey } from 'lib/shared/entry-utils.js'; import { threadHasPermission } from 'lib/shared/thread-utils.js'; @@ -783,9 +784,9 @@ navContext, }), ); - const online = useSelector( - state => state.connection.status === 'connected', - ); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + const online = connection.status === 'connected'; const styles = useStyles(unboundStyles); const navigateToThread = useNavigateToThread(); diff --git a/native/media/encrypted-image.react.js b/native/media/encrypted-image.react.js --- a/native/media/encrypted-image.react.js +++ b/native/media/encrypted-image.react.js @@ -1,8 +1,10 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js'; +import { connectionSelector } from 'lib/selectors/keyserver-selectors.js'; import { decryptBase64, decryptMedia } from './encryption-utils.js'; import LoadableImage from './loadable-image.react.js'; @@ -33,7 +35,9 @@ const mediaCache = React.useContext(MediaCacheContext); const [source, setSource] = React.useState(null); - const connectionStatus = useSelector(state => state.connection.status); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + const connectionStatus = connection.status; const prevConnectionStatusRef = React.useRef(connectionStatus); const [attempt, setAttempt] = React.useState(0); const [errorOccured, setErrorOccured] = React.useState(false); diff --git a/native/media/remote-image.react.js b/native/media/remote-image.react.js --- a/native/media/remote-image.react.js +++ b/native/media/remote-image.react.js @@ -1,8 +1,10 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import type { ImageSource } from 'react-native/Libraries/Image/ImageSource'; +import { connectionSelector } from 'lib/selectors/keyserver-selectors.js'; import { type ConnectionStatus } from 'lib/types/socket-types.js'; import LoadableImage from './loadable-image.react.js'; @@ -67,7 +69,9 @@ const ConnectedRemoteImage: React.ComponentType = React.memo(function ConnectedRemoteImage(props: BaseProps) { - const connectionStatus = useSelector(state => state.connection.status); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + const connectionStatus = connection.status; return ; }); diff --git a/native/push/push-handler.react.js b/native/push/push-handler.react.js --- a/native/push/push-handler.react.js +++ b/native/push/push-handler.react.js @@ -1,6 +1,7 @@ // @flow import * as Haptics from 'expo-haptics'; +import invariant from 'invariant'; import * as React from 'react'; import { Platform, LogBox } from 'react-native'; import { Notification as InAppNotification } from 'react-native-in-app-message'; @@ -11,7 +12,10 @@ setDeviceToken, } from 'lib/actions/device-actions.js'; import { saveMessagesActionType } from 'lib/actions/message-actions.js'; -import { updatesCurrentAsOfSelector } from 'lib/selectors/keyserver-selectors.js'; +import { + updatesCurrentAsOfSelector, + connectionSelector, +} from 'lib/selectors/keyserver-selectors.js'; import { unreadCount, threadInfoSelector, @@ -641,7 +645,8 @@ const notifPermissionAlertInfo = useSelector( state => state.notifPermissionAlertInfo, ); - const connection = useSelector(state => state.connection); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); const updatesCurrentAsOf = useSelector(updatesCurrentAsOfSelector); const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme); const loggedIn = useSelector(isLoggedIn); diff --git a/native/socket.react.js b/native/socket.react.js --- a/native/socket.react.js +++ b/native/socket.react.js @@ -9,6 +9,7 @@ import { cookieSelector, urlPrefixSelector, + connectionSelector, } from 'lib/selectors/keyserver-selectors.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { accountHasPassword } from 'lib/shared/account-utils.js'; @@ -45,7 +46,8 @@ const cookie = useSelector(cookieSelector); const urlPrefix = useSelector(urlPrefixSelector); invariant(urlPrefix, 'missing urlPrefix for given keyserver id'); - const connection = useSelector(state => state.connection); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); const frozen = useSelector(state => state.frozen); const active = useSelector( state => isLoggedIn(state) && state.lifecycleState !== 'background', diff --git a/web/calendar/entry.react.js b/web/calendar/entry.react.js --- a/web/calendar/entry.react.js +++ b/web/calendar/entry.react.js @@ -18,6 +18,7 @@ useModalContext, type PushModal, } from 'lib/components/modal-provider.react.js'; +import { connectionSelector } from 'lib/selectors/keyserver-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import { colorIsDark } from 'lib/shared/color-utils.js'; import { entryKey } from 'lib/shared/entry-utils.js'; @@ -472,9 +473,9 @@ !!(state.currentUserInfo && !state.currentUserInfo.anonymous && true), ); const calendarQuery = useSelector(nonThreadCalendarQuery); - const online = useSelector( - state => state.connection.status === 'connected', - ); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); + const online = connection.status === 'connected'; const callCreateEntry = useServerCall(createEntry); const callSaveEntry = useServerCall(saveEntry); const callDeleteEntry = useServerCall(deleteEntry); diff --git a/web/socket.react.js b/web/socket.react.js --- a/web/socket.react.js +++ b/web/socket.react.js @@ -9,6 +9,7 @@ import { cookieSelector, urlPrefixSelector, + connectionSelector, } from 'lib/selectors/keyserver-selectors.js'; import Socket, { type BaseSocketProps } from 'lib/socket/socket.react.js'; import { @@ -33,7 +34,8 @@ const cookie = useSelector(cookieSelector); const urlPrefix = useSelector(urlPrefixSelector); invariant(urlPrefix, 'missing urlPrefix for given keyserver id'); - const connection = useSelector(state => state.connection); + const connection = useSelector(connectionSelector); + invariant(connection, 'keyserver missing from keyserverStore'); const active = useSelector( state => !!state.currentUserInfo &&