diff --git a/lib/keyserver-conn/recovery-utils.js b/lib/keyserver-conn/recovery-utils.js
new file mode 100644
--- /dev/null
+++ b/lib/keyserver-conn/recovery-utils.js
@@ -0,0 +1,153 @@
+// @flow
+
+import { type ActionTypes, setNewSession } from './keyserver-conn-types.js';
+import type {
+  LogInActionSource,
+  LogInStartingPayload,
+  LogInResult,
+} 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 callServerEndpoint from '../utils/call-server-endpoint.js';
+import type { CallServerEndpointOptions } from '../utils/call-server-endpoint.js';
+import { getConfig } from '../utils/config.js';
+import { promiseAll } from '../utils/promises.js';
+import { wrapActionPromise } from '../utils/redux-promise-utils.js';
+import { usingCommServicesAccessToken } from '../utils/services-utils.js';
+
+// This function is a shortcut that tells us whether it's worth even trying to
+// call resolveKeyserverSessionInvalidation
+function canResolveKeyserverSessionInvalidation(): boolean {
+  if (usingCommServicesAccessToken) {
+    // We can always try to resolve a keyserver session invalidation
+    // automatically using the Olm auth responder
+    return true;
+  }
+  const { resolveKeyserverSessionInvalidationUsingNativeCredentials } =
+    getConfig();
+  // If we can't use the Olm auth responder, then we can only resolve a
+  // keyserver session invalidation on native, where we have access to the
+  // user's native credentials. Note that we can't do this for ETH users, but we
+  // don't know if the user is an ETH user from this function
+  return !!resolveKeyserverSessionInvalidationUsingNativeCredentials;
+}
+
+// This function attempts to resolve an invalid keyserver session. A session can
+// become invalid when a keyserver invalidates it, or due to inconsistent client
+// state. If the client is usingCommServicesAccessToken, then the invalidation
+// recovery will try to go through the keyserver's Olm auth responder.
+// Otherwise, it will attempt to use the user's credentials to log in with the
+// legacy auth responder, which won't work on web and won't work for ETH users.
+async function resolveKeyserverSessionInvalidation(
+  dispatch: Dispatch,
+  cookie: ?string,
+  urlPrefix: string,
+  logInActionSource: LogInActionSource,
+  keyserverID: string,
+  getInitialNotificationsEncryptedMessage?: () => Promise<string>,
+): Promise<?ClientSessionChange> {
+  const { resolveKeyserverSessionInvalidationUsingNativeCredentials } =
+    getConfig();
+  if (!resolveKeyserverSessionInvalidationUsingNativeCredentials) {
+    return null;
+  }
+  let newSessionChange = null;
+  let callServerEndpointCallback = null;
+  const boundCallServerEndpoint = async (
+    endpoint: Endpoint,
+    data: { +[key: string]: mixed },
+    options?: ?CallServerEndpointOptions,
+  ) => {
+    const innerBoundSetNewSession = (
+      sessionChange: ClientSessionChange,
+      error: ?string,
+    ) => {
+      newSessionChange = sessionChange;
+      setNewSession(
+        dispatch,
+        sessionChange,
+        null,
+        error,
+        logInActionSource,
+        keyserverID,
+      );
+    };
+    try {
+      const result = await callServerEndpoint(
+        cookie,
+        innerBoundSetNewSession,
+        () => new Promise(r => r(null)),
+        () => new Promise(r => r(null)),
+        urlPrefix,
+        null,
+        false,
+        null,
+        null,
+        endpoint,
+        data,
+        dispatch,
+        options,
+        false,
+        keyserverID,
+      );
+      if (callServerEndpointCallback) {
+        callServerEndpointCallback(!!newSessionChange);
+      }
+      return result;
+    } catch (e) {
+      if (callServerEndpointCallback) {
+        callServerEndpointCallback(!!newSessionChange);
+      }
+      throw e;
+    }
+  };
+
+  const boundCallKeyserverEndpoint = (
+    endpoint: Endpoint,
+    requests: { +[keyserverID: string]: ?{ +[string]: mixed } },
+    options?: ?CallServerEndpointOptions,
+  ) => {
+    if (requests[keyserverID]) {
+      const promises = {
+        [keyserverID]: boundCallServerEndpoint(
+          endpoint,
+          requests[keyserverID],
+          options,
+        ),
+      };
+      return promiseAll(promises);
+    }
+    return Promise.resolve({});
+  };
+
+  const dispatchRecoveryAttempt = (
+    actionTypes: ActionTypes<
+      'LOG_IN_STARTED',
+      'LOG_IN_SUCCESS',
+      'LOG_IN_FAILED',
+    >,
+    promise: Promise<LogInResult>,
+    inputStartingPayload: LogInStartingPayload,
+  ) => {
+    const startingPayload = { ...inputStartingPayload, logInActionSource };
+    void dispatch(
+      wrapActionPromise(actionTypes, promise, null, startingPayload),
+    );
+    return new Promise<boolean>(r => (callServerEndpointCallback = r));
+  };
+  await resolveKeyserverSessionInvalidationUsingNativeCredentials(
+    boundCallServerEndpoint,
+    boundCallKeyserverEndpoint,
+    dispatchRecoveryAttempt,
+    logInActionSource,
+    keyserverID,
+    getInitialNotificationsEncryptedMessage,
+  );
+  return newSessionChange;
+}
+
+export {
+  canResolveKeyserverSessionInvalidation,
+  resolveKeyserverSessionInvalidation,
+};
diff --git a/lib/socket/socket.react.js b/lib/socket/socket.react.js
--- a/lib/socket/socket.react.js
+++ b/lib/socket/socket.react.js
@@ -16,6 +16,7 @@
 import { updateActivityActionTypes } from '../actions/activity-actions.js';
 import { updateLastCommunicatedPlatformDetailsActionType } from '../actions/device-actions.js';
 import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js';
+import { resolveKeyserverSessionInvalidation } from '../keyserver-conn/recovery-utils.js';
 import { unsupervisedBackgroundActionType } from '../reducers/lifecycle-state-reducer.js';
 import {
   pingFrequency,
@@ -65,7 +66,6 @@
   type PongServerSocketMessage,
 } from '../types/socket-types.js';
 import { actionLogger } from '../utils/action-logger.js';
-import { resolveKeyserverSessionInvalidation } from '../utils/action-utils.js';
 import { getConfig } from '../utils/config.js';
 import { ServerError, SocketTimeout, SocketOffline } from '../utils/errors.js';
 import { promiseAll } from '../utils/promises.js';
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
@@ -10,23 +10,15 @@
   CallServerEndpoint,
   CallServerEndpointOptions,
 } from './call-server-endpoint.js';
-import { getConfig } from './config.js';
-import { promiseAll } from './promises.js';
-import { wrapActionPromise } from './redux-promise-utils.js';
 import { useSelector, useDispatch } from './redux-utils.js';
-import { usingCommServicesAccessToken } from './services-utils.js';
 import { ashoatKeyserverID } from './validation-utils.js';
+import { setNewSession } from '../keyserver-conn/keyserver-conn-types.js';
 import {
-  type ActionTypes,
-  setNewSession,
-} from '../keyserver-conn/keyserver-conn-types.js';
+  canResolveKeyserverSessionInvalidation,
+  resolveKeyserverSessionInvalidation,
+} from '../keyserver-conn/recovery-utils.js';
 import { serverCallStateSelector } from '../selectors/server-calls.js';
-import {
-  logInActionSources,
-  type LogInActionSource,
-  type LogInStartingPayload,
-  type LogInResult,
-} from '../types/account-types.js';
+import { logInActionSources } from '../types/account-types.js';
 import type { PlatformDetails } from '../types/device-types.js';
 import type { Endpoint, SocketAPIHandler } from '../types/endpoints.js';
 import type { Dispatch } from '../types/redux-types.js';
@@ -38,137 +30,6 @@
   callServerEndpoint: ?CallServerEndpoint,
 ) => void)[] = [];
 
-// This function is a shortcut that tells us whether it's worth even trying to
-// call resolveKeyserverSessionInvalidation
-function canResolveKeyserverSessionInvalidation() {
-  if (usingCommServicesAccessToken) {
-    // We can always try to resolve a keyserver session invalidation
-    // automatically using the Olm auth responder
-    return true;
-  }
-  const { resolveKeyserverSessionInvalidationUsingNativeCredentials } =
-    getConfig();
-  // If we can't use the Olm auth responder, then we can only resolve a
-  // keyserver session invalidation on native, where we have access to the
-  // user's native credentials. Note that we can't do this for ETH users, but we
-  // don't know if the user is an ETH user from this function
-  return !!resolveKeyserverSessionInvalidationUsingNativeCredentials;
-}
-
-// This function attempts to resolve an invalid keyserver session. A session can
-// become invalid when a keyserver invalidates it, or due to inconsistent client
-// state. If the client is usingCommServicesAccessToken, then the invalidation
-// recovery will try to go through the keyserver's Olm auth responder.
-// Otherwise, it will attempt to use the user's credentials to log in with the
-// legacy auth responder, which won't work on web and won't work for ETH users.
-async function resolveKeyserverSessionInvalidation(
-  dispatch: Dispatch,
-  cookie: ?string,
-  urlPrefix: string,
-  logInActionSource: LogInActionSource,
-  keyserverID: string,
-  getInitialNotificationsEncryptedMessage?: () => Promise<string>,
-): Promise<?ClientSessionChange> {
-  const { resolveKeyserverSessionInvalidationUsingNativeCredentials } =
-    getConfig();
-  if (!resolveKeyserverSessionInvalidationUsingNativeCredentials) {
-    return null;
-  }
-  let newSessionChange = null;
-  let callServerEndpointCallback = null;
-  const boundCallServerEndpoint = async (
-    endpoint: Endpoint,
-    data: { +[key: string]: mixed },
-    options?: ?CallServerEndpointOptions,
-  ) => {
-    const innerBoundSetNewSession = (
-      sessionChange: ClientSessionChange,
-      error: ?string,
-    ) => {
-      newSessionChange = sessionChange;
-      setNewSession(
-        dispatch,
-        sessionChange,
-        null,
-        error,
-        logInActionSource,
-        keyserverID,
-      );
-    };
-    try {
-      const result = await callServerEndpoint(
-        cookie,
-        innerBoundSetNewSession,
-        () => new Promise(r => r(null)),
-        () => new Promise(r => r(null)),
-        urlPrefix,
-        null,
-        false,
-        null,
-        null,
-        endpoint,
-        data,
-        dispatch,
-        options,
-        false,
-        keyserverID,
-      );
-      if (callServerEndpointCallback) {
-        callServerEndpointCallback(!!newSessionChange);
-      }
-      return result;
-    } catch (e) {
-      if (callServerEndpointCallback) {
-        callServerEndpointCallback(!!newSessionChange);
-      }
-      throw e;
-    }
-  };
-
-  const boundCallKeyserverEndpoint = (
-    endpoint: Endpoint,
-    requests: { +[keyserverID: string]: ?{ +[string]: mixed } },
-    options?: ?CallServerEndpointOptions,
-  ) => {
-    if (requests[keyserverID]) {
-      const promises = {
-        [keyserverID]: boundCallServerEndpoint(
-          endpoint,
-          requests[keyserverID],
-          options,
-        ),
-      };
-      return promiseAll(promises);
-    }
-    return Promise.resolve({});
-  };
-
-  const dispatchRecoveryAttempt = (
-    actionTypes: ActionTypes<
-      'LOG_IN_STARTED',
-      'LOG_IN_SUCCESS',
-      'LOG_IN_FAILED',
-    >,
-    promise: Promise<LogInResult>,
-    inputStartingPayload: LogInStartingPayload,
-  ) => {
-    const startingPayload = { ...inputStartingPayload, logInActionSource };
-    void dispatch(
-      wrapActionPromise(actionTypes, promise, null, startingPayload),
-    );
-    return new Promise<boolean>(r => (callServerEndpointCallback = r));
-  };
-  await resolveKeyserverSessionInvalidationUsingNativeCredentials(
-    boundCallServerEndpoint,
-    boundCallKeyserverEndpoint,
-    dispatchRecoveryAttempt,
-    logInActionSource,
-    keyserverID,
-    getInitialNotificationsEncryptedMessage,
-  );
-  return newSessionChange;
-}
-
 // Third param is optional and gets called with newCookie if we get a new cookie
 // Necessary to propagate cookie in cookieInvalidationRecovery below
 function bindCookieAndUtilsIntoCallServerEndpoint(
@@ -394,7 +255,6 @@
 }
 
 export {
-  resolveKeyserverSessionInvalidation,
   createBoundServerCallsSelector,
   registerActiveSocket,
   useServerCall,
diff --git a/native/account/logged-out-modal.react.js b/native/account/logged-out-modal.react.js
--- a/native/account/logged-out-modal.react.js
+++ b/native/account/logged-out-modal.react.js
@@ -18,6 +18,7 @@
 import { SafeAreaView } from 'react-native-safe-area-context';
 
 import { resetUserStateActionType } from 'lib/actions/user-actions.js';
+import { resolveKeyserverSessionInvalidation } from 'lib/keyserver-conn/recovery-utils.js';
 import {
   cookieSelector,
   urlPrefixSelector,
@@ -26,7 +27,6 @@
 import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js';
 import { logInActionSources } from 'lib/types/account-types.js';
 import type { Dispatch } from 'lib/types/redux-types.js';
-import { resolveKeyserverSessionInvalidation } from 'lib/utils/action-utils.js';
 import { useDispatch } from 'lib/utils/redux-utils.js';
 import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js';
 import { ashoatKeyserverID } from 'lib/utils/validation-utils.js';
diff --git a/native/data/sqlite-data-handler.js b/native/data/sqlite-data-handler.js
--- a/native/data/sqlite-data-handler.js
+++ b/native/data/sqlite-data-handler.js
@@ -5,6 +5,7 @@
 
 import { setClientDBStoreActionType } from 'lib/actions/client-db-store-actions.js';
 import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js';
+import { resolveKeyserverSessionInvalidation } from 'lib/keyserver-conn/recovery-utils.js';
 import { reportStoreOpsHandlers } from 'lib/ops/report-store-ops.js';
 import { threadStoreOpsHandlers } from 'lib/ops/thread-store-ops.js';
 import { userStoreOpsHandlers } from 'lib/ops/user-store-ops.js';
@@ -18,7 +19,6 @@
   logInActionSources,
   type LogInActionSource,
 } from 'lib/types/account-types.js';
-import { resolveKeyserverSessionInvalidation } from 'lib/utils/action-utils.js';
 import { getMessageForException } from 'lib/utils/errors.js';
 import { useDispatch } from 'lib/utils/redux-utils.js';
 import { ashoatKeyserverID } from 'lib/utils/validation-utils.js';
diff --git a/native/socket.react.js b/native/socket.react.js
--- a/native/socket.react.js
+++ b/native/socket.react.js
@@ -3,6 +3,7 @@
 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,
@@ -16,7 +17,6 @@
 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 { resolveKeyserverSessionInvalidation } from 'lib/utils/action-utils.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';