diff --git a/lib/components/keyserver-connection-handler.js b/lib/components/keyserver-connection-handler.js --- a/lib/components/keyserver-connection-handler.js +++ b/lib/components/keyserver-connection-handler.js @@ -11,18 +11,15 @@ } from '../actions/user-actions.js'; import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; import { - setNewSession, CANCELLED_ERROR, type CallKeyserverEndpoint, } from '../keyserver-conn/keyserver-conn-types.js'; -import { resolveKeyserverSessionInvalidation } from '../keyserver-conn/recovery-utils.js'; +import { useKeyserverRecoveryLogIn } from '../keyserver-conn/recovery-utils.js'; import { filterThreadIDsInFilterList } from '../reducers/calendar-filters-reducer.js'; import { connectionSelector, cookieSelector, deviceTokenSelector, - urlPrefixSelector, - sessionIDSelector, } from '../selectors/keyserver-selectors.js'; import { isLoggedInToKeyserver } from '../selectors/user-selectors.js'; import { useInitialNotificationsEncryptedMessage } from '../shared/crypto-utils.js'; @@ -33,12 +30,11 @@ logInActionSources, type RecoveryActionSource, } from '../types/account-types.js'; -import { genericCookieInvalidation } from '../types/session-types.js'; import { authoritativeKeyserverID } from '../utils/authoritative-keyserver.js'; import type { CallSingleKeyserverEndpoint } from '../utils/call-single-keyserver-endpoint.js'; import { getConfig } from '../utils/config.js'; import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; -import { useSelector, useDispatch } from '../utils/redux-utils.js'; +import { useSelector } from '../utils/redux-utils.js'; import { usingCommServicesAccessToken } from '../utils/services-utils.js'; import sleep from '../utils/sleep.js'; @@ -205,30 +201,6 @@ .activeSessionRecovery, ); - const preRequestUserInfo = useSelector(state => state.currentUserInfo); - const sessionID = useSelector(sessionIDSelector(keyserverID)); - const preRequestUserState = React.useMemo( - () => ({ - currentUserInfo: preRequestUserInfo, - cookiesAndSessions: { - [keyserverID]: { - cookie, - sessionID, - }, - }, - }), - [preRequestUserInfo, keyserverID, cookie, sessionID], - ); - - // We only need to do a "spot check" on this value below. - // - To avoid regenerating performRecovery whenever it changes, we want to - // make sure it's not in that function's dep list. - // - If we exclude it from that function's dep list, we'll end up binding in - // the value of preRequestUserState at the time performRecovery is updated. - // Instead, by assigning to a ref, we are able to use the latest value. - const preRequestUserStateRef = React.useRef(preRequestUserState); - preRequestUserStateRef.current = preRequestUserState; - // This async function asks the keyserver for its keys, whereas performAuth // above gets the keyserver's keys from the identity service const getInitialNotificationsEncryptedMessageForRecovery = @@ -266,15 +238,9 @@ ], ); - const dispatch = useDispatch(); - const urlPrefix = useSelector(urlPrefixSelector(keyserverID)); + const keyserverRecoveryLogIn = useKeyserverRecoveryLogIn(keyserverID); const performRecovery = React.useCallback( (recoveryActionSource: RecoveryActionSource) => { - invariant( - urlPrefix, - `urlPrefix for ${keyserverID} should be set during performRecovery`, - ); - setAuthInProgress(true); let cancelled = false; @@ -285,50 +251,11 @@ const hasBeenCancelled = () => cancelled; const promise = (async () => { - const userStateBeforeRecovery = preRequestUserStateRef.current; try { - const recoverySessionChange = - await resolveKeyserverSessionInvalidation( - dispatch, - cookie, - urlPrefix, - recoveryActionSource, - keyserverID, - innerPerformRecovery(recoveryActionSource, hasBeenCancelled), - ); - const sessionChange = - recoverySessionChange ?? genericCookieInvalidation; - if ( - sessionChange.cookieInvalidated || - !sessionChange.cookie || - !sessionChange.cookie.startsWith('user=') - ) { - setNewSession( - dispatch, - sessionChange, - userStateBeforeRecovery, - null, - recoveryActionSource, - keyserverID, - ); - } - } catch (e) { - if (cancelled) { - return; - } - - console.log( - `Error while recovering session with keyserver id ${keyserverID}`, - e, - ); - - setNewSession( - dispatch, - genericCookieInvalidation, - userStateBeforeRecovery, - null, + await keyserverRecoveryLogIn( recoveryActionSource, - keyserverID, + innerPerformRecovery(recoveryActionSource, hasBeenCancelled), + hasBeenCancelled, ); } finally { if (!cancelled) { @@ -336,9 +263,10 @@ } } })(); + return [promise, cancel]; }, - [dispatch, cookie, urlPrefix, keyserverID, innerPerformRecovery], + [keyserverRecoveryLogIn, innerPerformRecovery], ); const cancelPendingAuth = React.useRef void>(null); diff --git a/lib/keyserver-conn/recovery-utils.js b/lib/keyserver-conn/recovery-utils.js --- a/lib/keyserver-conn/recovery-utils.js +++ b/lib/keyserver-conn/recovery-utils.js @@ -1,13 +1,24 @@ // @flow +import invariant from 'invariant'; +import * as React from 'react'; + import { setNewSession, type CallKeyserverEndpoint, } from './keyserver-conn-types.js'; +import { + cookieSelector, + sessionIDSelector, + urlPrefixSelector, +} from '../selectors/keyserver-selectors.js'; import type { RecoveryActionSource } from '../types/account-types.js'; import type { Endpoint } from '../types/endpoints.js'; import type { Dispatch } from '../types/redux-types.js'; -import type { ClientSessionChange } from '../types/session-types.js'; +import { + type ClientSessionChange, + genericCookieInvalidation, +} from '../types/session-types.js'; import callSingleKeyServerEndpoint from '../utils/call-single-keyserver-endpoint.js'; import type { CallSingleKeyserverEndpoint, @@ -15,6 +26,7 @@ } from '../utils/call-single-keyserver-endpoint.js'; import { getConfig } from '../utils/config.js'; import { promiseAll } from '../utils/promises.js'; +import { useSelector, useDispatch } from '../utils/redux-utils.js'; import { usingCommServicesAccessToken } from '../utils/services-utils.js'; // This function is a shortcut that tells us whether it's worth even trying to @@ -115,7 +127,109 @@ return newSessionChange; } +function useKeyserverRecoveryLogIn( + keyserverID: string, +): ( + source: RecoveryActionSource, + actionFunc: ( + callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, + callKeyserverEndpoint: CallKeyserverEndpoint, + ) => Promise, + hasBeenCancelled: () => boolean, +) => Promise { + const preRequestUserInfo = useSelector(state => state.currentUserInfo); + const cookie = useSelector(cookieSelector(keyserverID)); + const sessionID = useSelector(sessionIDSelector(keyserverID)); + const preRequestUserState = React.useMemo( + () => ({ + currentUserInfo: preRequestUserInfo, + cookiesAndSessions: { + [keyserverID]: { + cookie, + sessionID, + }, + }, + }), + [preRequestUserInfo, keyserverID, cookie, sessionID], + ); + + // We only need to do a "spot check" on this value below. + // - To avoid regenerating performRecovery whenever it changes, we want to + // make sure it's not in that function's dep list. + // - If we exclude it from that function's dep list, we'll end up binding in + // the value of preRequestUserState at the time performRecovery is updated. + // Instead, by assigning to a ref, we are able to use the latest value. + const preRequestUserStateRef = React.useRef(preRequestUserState); + preRequestUserStateRef.current = preRequestUserState; + + const dispatch = useDispatch(); + const urlPrefix = useSelector(urlPrefixSelector(keyserverID)); + + return React.useCallback( + async ( + source: RecoveryActionSource, + actionFunc: ( + callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, + callKeyserverEndpoint: CallKeyserverEndpoint, + ) => Promise, + hasBeenCancelled: () => boolean, + ) => { + invariant( + urlPrefix, + `urlPrefix for ${keyserverID} should be set during recovery login`, + ); + + const userStateBeforeRecovery = preRequestUserStateRef.current; + try { + const recoverySessionChange = await resolveKeyserverSessionInvalidation( + dispatch, + cookie, + urlPrefix, + source, + keyserverID, + actionFunc, + ); + const sessionChange = + recoverySessionChange ?? genericCookieInvalidation; + if ( + sessionChange.cookieInvalidated || + !sessionChange.cookie || + !sessionChange.cookie.startsWith('user=') + ) { + setNewSession( + dispatch, + sessionChange, + userStateBeforeRecovery, + null, + source, + keyserverID, + ); + } + } catch (e) { + if (hasBeenCancelled()) { + return; + } + console.log( + `Error during recovery login with keyserver ${keyserverID}`, + e, + ); + setNewSession( + dispatch, + genericCookieInvalidation, + userStateBeforeRecovery, + null, + source, + keyserverID, + ); + throw e; + } + }, + [keyserverID, dispatch, cookie, urlPrefix], + ); +} + export { canResolveKeyserverSessionInvalidation, resolveKeyserverSessionInvalidation, + useKeyserverRecoveryLogIn, };