diff --git a/lib/components/keyserver-connection-handler.js b/lib/components/keyserver-connection-handler.js index ef30108e0..ea0890927 100644 --- a/lib/components/keyserver-connection-handler.js +++ b/lib/components/keyserver-connection-handler.js @@ -1,90 +1,161 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; -import { logOutActionTypes, useLogOut } from '../actions/user-actions.js'; +import { + keyserverAuthActionTypes, + logOutActionTypes, + useKeyserverAuth, + useLogOut, +} from '../actions/user-actions.js'; +import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; +import { filterThreadIDsInFilterList } from '../reducers/calendar-filters-reducer.js'; import { connectionSelector, cookieSelector, + deviceTokenSelector, } from '../selectors/keyserver-selectors.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { OlmSessionCreatorContext } from '../shared/olm-session-creator-context.js'; import type { BaseSocketProps } from '../socket/socket.react.js'; +import { logInActionSources } from '../types/account-types.js'; import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; import { useSelector } from '../utils/redux-utils.js'; import { usingCommServicesAccessToken } from '../utils/services-utils.js'; import { ashoatKeyserverID } from '../utils/validation-utils.js'; type Props = { ...BaseSocketProps, +keyserverID: string, +socketComponent: React.ComponentType, }; function KeyserverConnectionHandler(props: Props) { const { socketComponent: Socket, keyserverID, ...rest } = props; const dispatchActionPromise = useDispatchActionPromise(); const callLogOut = useLogOut(); + const keyserverAuth = useKeyserverAuth(); const hasConnectionIssue = useSelector( state => !!connectionSelector(keyserverID)(state)?.connectionIssue, ); const cookie = useSelector(cookieSelector(keyserverID)); + const keyserverDeviceToken = useSelector(deviceTokenSelector(keyserverID)); + // We have an assumption that we should be always connected to Ashoat's + // keyserver. It is possible that a token which it has is correct, so we can + // try to use it. In worst case it is invalid and our push-handler will try + // to fix it. + const ashoatKeyserverDeviceToken = useSelector( + deviceTokenSelector(ashoatKeyserverID), + ); + const deviceToken = keyserverDeviceToken ?? ashoatKeyserverDeviceToken; + + const navInfo = useSelector(state => state.navInfo); + const calendarFilters = useSelector(state => state.calendarFilters); + const calendarQuery = React.useMemo(() => { + const filters = filterThreadIDsInFilterList( + calendarFilters, + (threadID: string) => extractKeyserverIDFromID(threadID) === keyserverID, + ); + return { + startDate: navInfo.startDate, + endDate: navInfo.endDate, + filters, + }; + }, [calendarFilters, keyserverID, navInfo.endDate, navInfo.startDate]); + React.useEffect(() => { if (hasConnectionIssue) { void dispatchActionPromise(logOutActionTypes, callLogOut()); } }, [callLogOut, hasConnectionIssue, dispatchActionPromise]); const identityContext = React.useContext(IdentityClientContext); invariant(identityContext, 'Identity context should be set'); - const { identityClient } = identityContext; + const { identityClient, getAuthMetadata } = identityContext; const olmSessionCreator = React.useContext(OlmSessionCreatorContext); invariant(olmSessionCreator, 'Olm session creator should be set'); React.useEffect(() => { if (!usingCommServicesAccessToken) { return; } void (async () => { try { const keyserverKeys = await identityClient.getKeyserverKeys(keyserverID); - // eslint-disable-next-line no-unused-vars const [notifsSession, contentSession] = await Promise.all([ olmSessionCreator.notificationsSessionCreator( cookie, keyserverKeys.identityKeysBlob.notificationIdentityPublicKeys, keyserverKeys.notifInitializationInfo, keyserverID, ), olmSessionCreator.contentSessionCreator( keyserverKeys.identityKeysBlob.primaryIdentityPublicKeys, keyserverKeys.contentInitializationInfo, ), ]); + + const { userID, deviceID } = await getAuthMetadata(); + invariant(userID, 'userID should be set'); + invariant(deviceID, 'deviceID should be set'); + + const deviceTokenUpdateInput = deviceToken + ? { [keyserverID]: { deviceToken } } + : {}; + + await dispatchActionPromise( + keyserverAuthActionTypes, + keyserverAuth({ + userID, + deviceID, + doNotRegister: false, + calendarQuery, + deviceTokenUpdateInput, + logInActionSource: process.env.BROWSER + ? logInActionSources.keyserverAuthFromWeb + : logInActionSources.keyserverAuthFromNative, + keyserverData: { + [keyserverID]: { + initialContentEncryptedMessage: contentSession, + initialNotificationsEncryptedMessage: notifsSession, + }, + }, + }), + ); } catch (e) { console.log( - `Error getting keys for keyserver with id ${keyserverID}`, + `Error while authenticating to keyserver with id ${keyserverID}`, e, ); } })(); - }, [keyserverID, identityClient, olmSessionCreator, cookie]); + }, [ + keyserverID, + identityClient, + olmSessionCreator, + cookie, + getAuthMetadata, + dispatchActionPromise, + keyserverAuth, + deviceToken, + calendarQuery, + ]); if (keyserverID !== ashoatKeyserverID) { return null; } return ; } const Handler: React.ComponentType = React.memo( KeyserverConnectionHandler, ); export default Handler; diff --git a/lib/reducers/calendar-filters-reducer.js b/lib/reducers/calendar-filters-reducer.js index fab3e5e48..6ae2a597d 100644 --- a/lib/reducers/calendar-filters-reducer.js +++ b/lib/reducers/calendar-filters-reducer.js @@ -1,209 +1,210 @@ // @flow import { updateCalendarCommunityFilter, clearCalendarCommunityFilter, } from '../actions/community-actions.js'; import { siweAuthActionTypes } from '../actions/siwe-actions.js'; import { newThreadActionTypes, joinThreadActionTypes, leaveThreadActionTypes, deleteThreadActionTypes, } from '../actions/thread-actions.js'; import { keyserverAuthActionTypes, logOutActionTypes, deleteKeyserverAccountActionTypes, logInActionTypes, tempIdentityLoginActionTypes, keyserverRegisterActionTypes, } from '../actions/user-actions.js'; import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import { filteredThreadIDs, nonThreadCalendarFilters, nonExcludeDeletedCalendarFilters, } from '../selectors/calendar-filter-selectors.js'; import { threadInFilterList } from '../shared/thread-utils.js'; import { updateSpecs } from '../shared/updates/update-specs.js'; import { type CalendarFilter, defaultCalendarFilters, updateCalendarThreadFilter, clearCalendarThreadFilter, setCalendarDeletedFilter, calendarThreadFilterTypes, } from '../types/filter-types.js'; import type { BaseAction } from '../types/redux-types.js'; import { fullStateSyncActionType, incrementalStateSyncActionType, } from '../types/socket-types.js'; import type { RawThreadInfos, ThreadStore } from '../types/thread-types.js'; import { type ClientUpdateInfo, processUpdatesActionType, } from '../types/update-types.js'; import { filterThreadIDsBelongingToCommunity } from '../utils/drawer-utils.react.js'; export default function reduceCalendarFilters( state: $ReadOnlyArray, action: BaseAction, threadStore: ThreadStore, ): $ReadOnlyArray { if ( action.type === tempIdentityLoginActionTypes.success || action.type === logOutActionTypes.success || action.type === deleteKeyserverAccountActionTypes.success || action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === keyserverRegisterActionTypes.success || (action.type === setNewSessionActionType && action.payload.sessionChange.cookieInvalidated) ) { return defaultCalendarFilters; } else if (action.type === keyserverAuthActionTypes.success) { const keyserverIDs = Object.keys(action.payload.updatesCurrentAsOf); return removeKeyserverThreadIDsFromFilterList(state, keyserverIDs); } else if (action.type === updateCalendarThreadFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); return [ ...nonThreadFilters, { type: calendarThreadFilterTypes.THREAD_LIST, threadIDs: action.payload.threadIDs, }, ]; } else if (action.type === clearCalendarThreadFilter) { return nonThreadCalendarFilters(state); } else if (action.type === setCalendarDeletedFilter) { const otherFilters = nonExcludeDeletedCalendarFilters(state); if (action.payload.includeDeleted && otherFilters.length === state.length) { // Attempting to remove NOT_DELETED filter, but it doesn't exist return state; } else if (action.payload.includeDeleted) { // Removing NOT_DELETED filter return otherFilters; } else if (otherFilters.length < state.length) { // Attempting to add NOT_DELETED filter, but it already exists return state; } else { // Adding NOT_DELETED filter return [...state, { type: calendarThreadFilterTypes.NOT_DELETED }]; } } else if ( action.type === newThreadActionTypes.success || action.type === joinThreadActionTypes.success || action.type === leaveThreadActionTypes.success || action.type === deleteThreadActionTypes.success || action.type === processUpdatesActionType ) { return updateFilterListFromUpdateInfos( state, action.payload.updatesResult.newUpdates, ); } else if (action.type === incrementalStateSyncActionType) { return updateFilterListFromUpdateInfos( state, action.payload.updatesResult.newUpdates, ); } else if (action.type === fullStateSyncActionType) { return removeDeletedThreadIDsFromFilterList( state, action.payload.threadInfos, ); } else if (action.type === updateCalendarCommunityFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); const threadIDs = Array.from( filterThreadIDsBelongingToCommunity( action.payload, threadStore.threadInfos, ), ); return [ ...nonThreadFilters, { type: calendarThreadFilterTypes.THREAD_LIST, threadIDs, }, ]; } else if (action.type === clearCalendarCommunityFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); return nonThreadFilters; } return state; } function updateFilterListFromUpdateInfos( state: $ReadOnlyArray, updateInfos: $ReadOnlyArray, ): $ReadOnlyArray { const currentlyFilteredIDs: ?$ReadOnlySet = filteredThreadIDs(state); if (!currentlyFilteredIDs) { return state; } const newFilteredThreadIDs = updateInfos.reduce( (reducedFilteredThreadIDs, update) => { const { reduceCalendarThreadFilters } = updateSpecs[update.type]; return reduceCalendarThreadFilters ? reduceCalendarThreadFilters(reducedFilteredThreadIDs, update) : reducedFilteredThreadIDs; }, currentlyFilteredIDs, ); if (currentlyFilteredIDs !== newFilteredThreadIDs) { return [ ...nonThreadCalendarFilters(state), { type: 'threads', threadIDs: [...newFilteredThreadIDs] }, ]; } return state; } function filterThreadIDsInFilterList( state: $ReadOnlyArray, filterCondition: (threadID: string) => boolean, ): $ReadOnlyArray { const currentlyFilteredIDs = filteredThreadIDs(state); if (!currentlyFilteredIDs) { return state; } const filtered = [...currentlyFilteredIDs].filter(filterCondition); if (filtered.length < currentlyFilteredIDs.size) { return [ ...nonThreadCalendarFilters(state), { type: 'threads', threadIDs: filtered }, ]; } return state; } function removeDeletedThreadIDsFromFilterList( state: $ReadOnlyArray, threadInfos: RawThreadInfos, ): $ReadOnlyArray { const filterCondition = (threadID: string) => threadInFilterList(threadInfos[threadID]); return filterThreadIDsInFilterList(state, filterCondition); } function removeKeyserverThreadIDsFromFilterList( state: $ReadOnlyArray, keyserverIDs: $ReadOnlyArray, ): $ReadOnlyArray { const keyserverIDsSet = new Set(keyserverIDs); const filterCondition = (threadID: string) => !keyserverIDsSet.has(extractKeyserverIDFromID(threadID)); return filterThreadIDsInFilterList(state, filterCondition); } export { + filterThreadIDsInFilterList, removeDeletedThreadIDsFromFilterList, removeKeyserverThreadIDsFromFilterList, };