diff --git a/lib/reducers/data-loaded-reducer.js b/lib/reducers/data-loaded-reducer.js index eba7308c1..ee839be44 100644 --- a/lib/reducers/data-loaded-reducer.js +++ b/lib/reducers/data-loaded-reducer.js @@ -1,40 +1,42 @@ // @flow import { setDataLoadedActionType } from '../actions/client-db-store-actions.js'; import { keyserverAuthActionTypes, logOutActionTypes, deleteAccountActionTypes, logInActionTypes, } from '../actions/user-actions.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import type { BaseAction } from '../types/redux-types.js'; +import { usingCommServicesAccessToken } from '../utils/services-utils.js'; import { ashoatKeyserverID } from '../utils/validation-utils.js'; export default function reduceDataLoaded( state: boolean, action: BaseAction, ): boolean { if (action.type === setDataLoadedActionType) { return action.payload.dataLoaded; } else if (action.type === logInActionTypes.success) { return true; } else if ( action.type === setNewSessionActionType && action.payload.sessionChange.currentUserInfo && - action.payload.sessionChange.currentUserInfo.anonymous + action.payload.sessionChange.currentUserInfo.anonymous && + !usingCommServicesAccessToken ) { return false; } else if ( action.type === logOutActionTypes.started || action.type === logOutActionTypes.success || action.type === deleteAccountActionTypes.success ) { return false; } else if (action.type === keyserverAuthActionTypes.success) { if (ashoatKeyserverID in action.payload.updatesCurrentAsOf) { return true; } } return state; } diff --git a/lib/reducers/services-access-token-reducer.js b/lib/reducers/services-access-token-reducer.js index 0b9a95032..e472f6da5 100644 --- a/lib/reducers/services-access-token-reducer.js +++ b/lib/reducers/services-access-token-reducer.js @@ -1,31 +1,33 @@ // @flow import { logOutActionTypes, deleteAccountActionTypes, setAccessTokenActionType, } from '../actions/user-actions.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import type { BaseAction } from '../types/redux-types.js'; +import { usingCommServicesAccessToken } from '../utils/services-utils.js'; export default function reduceServicesAccessToken( state: ?string, action: BaseAction, ): ?string { if (action.type === setAccessTokenActionType) { return action.payload; } else if ( action.type === setNewSessionActionType && action.payload.sessionChange.currentUserInfo && - action.payload.sessionChange.currentUserInfo.anonymous + action.payload.sessionChange.currentUserInfo.anonymous && + !usingCommServicesAccessToken ) { return null; } else if ( action.type === logOutActionTypes.started || action.type === logOutActionTypes.success || action.type === deleteAccountActionTypes.success ) { return null; } return state; } diff --git a/lib/reducers/theme-reducer.js b/lib/reducers/theme-reducer.js index 1b9678eb7..f4192643d 100644 --- a/lib/reducers/theme-reducer.js +++ b/lib/reducers/theme-reducer.js @@ -1,49 +1,51 @@ // @flow import { siweAuthActionTypes } from '../actions/siwe-actions.js'; import { updateThemeInfoActionType } from '../actions/theme-actions.js'; import { logOutActionTypes, deleteAccountActionTypes, logInActionTypes, tempIdentityLoginActionTypes, keyserverRegisterActionTypes, } from '../actions/user-actions.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import type { BaseAction } from '../types/redux-types.js'; import { defaultGlobalThemeInfo, type GlobalThemeInfo, } from '../types/theme-types.js'; +import { usingCommServicesAccessToken } from '../utils/services-utils.js'; export default function reduceGlobalThemeInfo( state: GlobalThemeInfo, action: BaseAction, ): GlobalThemeInfo { if ( action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === tempIdentityLoginActionTypes.success || action.type === keyserverRegisterActionTypes.success ) { return defaultGlobalThemeInfo; } else if ( action.type === setNewSessionActionType && action.payload.sessionChange.currentUserInfo && - action.payload.sessionChange.currentUserInfo.anonymous + action.payload.sessionChange.currentUserInfo.anonymous && + !usingCommServicesAccessToken ) { return defaultGlobalThemeInfo; } else if ( action.type === logOutActionTypes.started || action.type === logOutActionTypes.success || action.type === deleteAccountActionTypes.success ) { return defaultGlobalThemeInfo; } else if (action.type === updateThemeInfoActionType) { return { ...state, ...action.payload, }; } return state; } diff --git a/lib/reducers/user-reducer.js b/lib/reducers/user-reducer.js index ba2ab1a91..0374bcf59 100644 --- a/lib/reducers/user-reducer.js +++ b/lib/reducers/user-reducer.js @@ -1,474 +1,476 @@ // @flow import _isEqual from 'lodash/fp/isEqual.js'; import _keyBy from 'lodash/fp/keyBy.js'; import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js'; import { siweAuthActionTypes } from '../actions/siwe-actions.js'; import { joinThreadActionTypes, newThreadActionTypes, } from '../actions/thread-actions.js'; import { deleteAccountActionTypes, keyserverAuthActionTypes, logOutActionTypes, logInActionTypes, keyserverRegisterActionTypes, setUserSettingsActionTypes, updateUserAvatarActionTypes, resetUserStateActionType, } from '../actions/user-actions.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import { convertUserInfosToReplaceUserOps, type UserStoreOperation, userStoreOpsHandlers, } from '../ops/user-store-ops.js'; import { stateSyncSpecs } from '../shared/state-sync/state-sync-specs.js'; import { updateSpecs } from '../shared/updates/update-specs.js'; import type { BaseAction } from '../types/redux-types.js'; import type { ClientUserInconsistencyReportCreationRequest } from '../types/report-types.js'; import { serverRequestTypes, processServerRequestsActionType, } from '../types/request-types.js'; import { fullStateSyncActionType, incrementalStateSyncActionType, } from '../types/socket-types.js'; import { updateTypes } from '../types/update-types-enum.js'; import { processUpdatesActionType } from '../types/update-types.js'; import type { ClientUpdateInfo } from '../types/update-types.js'; import type { CurrentUserInfo, UserInfos, UserStore, } from '../types/user-types.js'; import { getMessageForException } from '../utils/errors.js'; import { assertObjectsAreEqual } from '../utils/objects.js'; import { usingCommServicesAccessToken } from '../utils/services-utils.js'; +import { ashoatKeyserverID } from '../utils/validation-utils.js'; function reduceCurrentUserInfo( state: ?CurrentUserInfo, action: BaseAction, ): ?CurrentUserInfo { if ( action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === keyserverRegisterActionTypes.success || action.type === logOutActionTypes.success || action.type === deleteAccountActionTypes.success ) { if (!_isEqual(action.payload.currentUserInfo)(state)) { return action.payload.currentUserInfo; } } else if ( action.type === setNewSessionActionType && - action.payload.sessionChange.currentUserInfo + action.payload.sessionChange.currentUserInfo && + action.payload.keyserverID === ashoatKeyserverID ) { const { sessionChange } = action.payload; if (!_isEqual(sessionChange.currentUserInfo)(state)) { return sessionChange.currentUserInfo; } } else if (action.type === fullStateSyncActionType) { const { currentUserInfo } = action.payload; if (!_isEqual(currentUserInfo)(state)) { return currentUserInfo; } } else if ( action.type === incrementalStateSyncActionType || action.type === processUpdatesActionType ) { return action.payload.updatesResult.newUpdates.reduce( (reducedState, update) => { const { reduceCurrentUser } = updateSpecs[update.type]; return reduceCurrentUser ? reduceCurrentUser(reducedState, update) : reducedState; }, state, ); } else if (action.type === processServerRequestsActionType) { const checkStateRequest = action.payload.serverRequests.find( candidate => candidate.type === serverRequestTypes.CHECK_STATE, ); const newCurrentUserInfo = checkStateRequest?.stateChanges?.currentUserInfo; if (newCurrentUserInfo && !_isEqual(newCurrentUserInfo)(state)) { return newCurrentUserInfo; } } else if ( action.type === updateUserAvatarActionTypes.success && state && !state.anonymous ) { const { viewerUpdates } = action.payload.updates; for (const update of viewerUpdates) { if ( update.type === updateTypes.UPDATE_CURRENT_USER && !_isEqual(update.currentUserInfo.avatar)(state.avatar) ) { return { ...state, avatar: update.currentUserInfo.avatar, }; } } return state; } else if (action.type === setUserSettingsActionTypes.success) { if (state?.settings) { return { ...state, settings: { ...state.settings, ...action.payload, }, }; } } else if (action.type === resetUserStateActionType) { state = state && state.anonymous ? state : null; } return state; } function assertUserStoresAreEqual( processedUserStore: UserInfos, expectedUserStore: UserInfos, location: string, onStateDifference: (message: string) => mixed, ) { try { assertObjectsAreEqual( processedUserStore, expectedUserStore, `UserInfos - ${location}`, ); } catch (e) { console.log( 'Error processing UserStore ops', processedUserStore, expectedUserStore, ); const message = `Error processing UserStore ops ${ getMessageForException(e) ?? '{no exception message}' }`; onStateDifference(message); } } const { processStoreOperations: processUserStoreOps } = userStoreOpsHandlers; function generateOpsForUserUpdates(payload: { +updatesResult: { +newUpdates: $ReadOnlyArray, ... }, ... }): $ReadOnlyArray { return payload.updatesResult.newUpdates .map(update => updateSpecs[update.type].generateOpsForUserInfoUpdates?.(update), ) .filter(Boolean) .flat(); } function reduceUserInfos( state: UserStore, action: BaseAction, onStateDifference: (message: string) => mixed, ): [ UserStore, $ReadOnlyArray, $ReadOnlyArray, ] { if ( action.type === joinThreadActionTypes.success || action.type === newThreadActionTypes.success ) { const newUserInfos: UserInfos = _keyBy(userInfo => userInfo.id)( action.payload.userInfos, ); const userStoreOps: $ReadOnlyArray = convertUserInfosToReplaceUserOps(newUserInfos); const processedUserInfos: UserInfos = processUserStoreOps( state.userInfos, userStoreOps, ); const updated: UserInfos = { ...state.userInfos, ...newUserInfos }; assertUserStoresAreEqual( processedUserInfos, updated, action.type, onStateDifference, ); if (!_isEqual(state.userInfos)(updated)) { return [ { ...state, userInfos: updated, }, [], userStoreOps, ]; } } else if ( action.type === logOutActionTypes.success || action.type === deleteAccountActionTypes.success ) { const userStoreOps: $ReadOnlyArray = [ { type: 'remove_all_users' }, ]; const processedUserInfos: UserInfos = processUserStoreOps( state.userInfos, userStoreOps, ); assertUserStoresAreEqual( processedUserInfos, {}, action.type, onStateDifference, ); if (Object.keys(state.userInfos).length === 0) { return [state, [], []]; } return [ { userInfos: {}, }, [], userStoreOps, ]; } else if ( action.type === setNewSessionActionType && action.payload.sessionChange.cookieInvalidated && !usingCommServicesAccessToken ) { const userStoreOps: $ReadOnlyArray = [ { type: 'remove_all_users' }, ]; const processedUserInfos: UserInfos = processUserStoreOps( state.userInfos, userStoreOps, ); assertUserStoresAreEqual( processedUserInfos, {}, action.type, onStateDifference, ); if (Object.keys(state.userInfos).length === 0) { return [state, [], []]; } return [ { userInfos: {}, }, [], userStoreOps, ]; } else if ( action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === keyserverRegisterActionTypes.success || action.type === fullStateSyncActionType ) { const newUserInfos = _keyBy(userInfo => userInfo.id)( action.payload.userInfos, ); const userStoreOps: $ReadOnlyArray = [ { type: 'remove_all_users' }, ...convertUserInfosToReplaceUserOps(newUserInfos), ]; const processedUserInfos: UserInfos = processUserStoreOps( state.userInfos, userStoreOps, ); assertUserStoresAreEqual( processedUserInfos, newUserInfos, action.type, onStateDifference, ); if ( action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === keyserverRegisterActionTypes.success || !_isEqual(state.userInfos)(newUserInfos) ) { return [ { userInfos: newUserInfos, }, [], userStoreOps, ]; } } else if (action.type === keyserverAuthActionTypes.success) { const newUserInfos = _keyBy(userInfo => userInfo.id)( action.payload.userInfos, ); const mergedUserInfos: UserInfos = { ...state.userInfos, ...newUserInfos }; const userStoreOps: $ReadOnlyArray = convertUserInfosToReplaceUserOps(newUserInfos); const processedUserInfos: UserInfos = processUserStoreOps( state.userInfos, userStoreOps, ); assertUserStoresAreEqual( processedUserInfos, mergedUserInfos, action.type, onStateDifference, ); if (!_isEqual(state.userInfos)(mergedUserInfos)) { return [ { ...state, userInfos: mergedUserInfos, }, [], userStoreOps, ]; } } else if ( action.type === incrementalStateSyncActionType || action.type === processUpdatesActionType ) { const newUserInfos = _keyBy(userInfo => userInfo.id)( action.payload.userInfos, ); const userStoreOps: $ReadOnlyArray = [ ...convertUserInfosToReplaceUserOps(newUserInfos), ...generateOpsForUserUpdates(action.payload), ]; const updated = action.payload.updatesResult.newUpdates.reduce( (reducedState, update) => { const { reduceUserInfos: reduceUserInfosUpdate } = updateSpecs[update.type]; return reduceUserInfosUpdate ? reduceUserInfosUpdate(reducedState, update) : reducedState; }, { ...state.userInfos, ...newUserInfos }, ); const processedUserInfos: UserInfos = processUserStoreOps( state.userInfos, userStoreOps, ); assertUserStoresAreEqual( processedUserInfos, updated, action.type, onStateDifference, ); if (!_isEqual(state.userInfos)(updated)) { return [ { ...state, userInfos: updated, }, [], userStoreOps, ]; } } else if (action.type === processServerRequestsActionType) { const checkStateRequest = action.payload.serverRequests.find( candidate => candidate.type === serverRequestTypes.CHECK_STATE, ); if (!checkStateRequest || !checkStateRequest.stateChanges) { return [state, [], []]; } const { userInfos, deleteUserInfoIDs } = checkStateRequest.stateChanges; if (!userInfos && !deleteUserInfoIDs) { return [state, [], []]; } const userStoreOps: UserStoreOperation[] = []; const newUserInfos = { ...state.userInfos }; if (userInfos) { for (const userInfo of userInfos) { newUserInfos[userInfo.id] = userInfo; userStoreOps.push({ type: 'replace_user', payload: { ...userInfo } }); } } if (deleteUserInfoIDs) { for (const deleteUserInfoID of deleteUserInfoIDs) { delete newUserInfos[deleteUserInfoID]; } userStoreOps.push({ type: 'remove_users', payload: { ids: deleteUserInfoIDs }, }); } const processedUserInfos: UserInfos = processUserStoreOps( state.userInfos, userStoreOps, ); assertUserStoresAreEqual( processedUserInfos, newUserInfos, action.type, onStateDifference, ); const newInconsistencies = stateSyncSpecs.users.findStoreInconsistencies( action, state.userInfos, newUserInfos, ); return [ { userInfos: newUserInfos, }, newInconsistencies, userStoreOps, ]; } else if (action.type === updateUserAvatarActionTypes.success) { const newUserInfos = _keyBy(userInfo => userInfo.id)( action.payload.updates.userInfos, ); const userStoreOps: $ReadOnlyArray = convertUserInfosToReplaceUserOps(newUserInfos); const processedUserInfos: UserInfos = processUserStoreOps( state.userInfos, userStoreOps, ); const updated: UserInfos = { ...state.userInfos, ...newUserInfos }; assertUserStoresAreEqual( processedUserInfos, updated, action.type, onStateDifference, ); const newState = !_isEqual(state.userInfos)(updated) ? { ...state, userInfos: updated, } : state; return [newState, [], userStoreOps]; } else if (action.type === setClientDBStoreActionType) { if (!action.payload.users) { return [state, [], []]; } // Once the functionality is confirmed to work correctly, // we will proceed with returning the users from the payload. assertUserStoresAreEqual( action.payload.users ?? {}, state.userInfos, action.type, onStateDifference, ); return [state, [], []]; } return [state, [], []]; } export { reduceCurrentUserInfo, reduceUserInfos };