From b9b3b067a8432d51aefceb9dc8b7217d559a60d5 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Tue, 23 Jun 2026 11:55:51 -0400 Subject: [PATCH] store uid along with current user state --- shared/chat/conversation/thread-context.tsx | 5 +++++ shared/constants/init/index.tsx | 7 ++++++- shared/router-v2/linking.tsx | 8 ++++++++ shared/stores/config.tsx | 3 +++ shared/util/storeless-actions.tsx | 7 ++++++- 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/shared/chat/conversation/thread-context.tsx b/shared/chat/conversation/thread-context.tsx index 3088ae38747a..631e3f95bdd2 100644 --- a/shared/chat/conversation/thread-context.tsx +++ b/shared/chat/conversation/thread-context.tsx @@ -20,6 +20,7 @@ import {clearChatTimeCache} from '@/util/timestamp' import {findLast} from '@/util/arrays' import {ignorePromise} from '@/constants/utils' import {RPCError} from '@/util/errors' +import {persistRoute} from '@/util/storeless-actions' import {uint8ArrayToString} from '@/util/uint8array' import {useEngineActionListener} from '@/engine/action-listener' import {useCurrentUserState} from '@/stores/current-user' @@ -868,6 +869,10 @@ const loadConversationThreadMessages = ( if (error instanceof RPCError) { logger.warn(`loadMoreMessages: error: ${error.desc}`) if (error.code === T.RPCGen.StatusCode.scchatnotinteam) { + // We're no longer in this conv's team. Clear the persisted last-route + // (ui.routeState2) so app startup doesn't keep restoring and reloading + // this conv, which would re-trigger this error on every launch. + persistRoute(true, true, () => useConfigState.getState().startup.loaded) navigateToInbox(true, 'maybeKickedFromTeam') } if (error.code !== T.RPCGen.StatusCode.scteamreaderror) { diff --git a/shared/constants/init/index.tsx b/shared/constants/init/index.tsx index c28fb12cf7ef..a41fb47aa5f9 100644 --- a/shared/constants/init/index.tsx +++ b/shared/constants/init/index.tsx @@ -215,6 +215,7 @@ const loadStartupDetails = async () => { ] as const) let conversation: T.Chat.ConversationIDKey | undefined + let conversationUid = '' let followUser = '' let link = '' let tab = '' @@ -232,11 +233,14 @@ const loadStartupDetails = async () => { try { const item = JSON.parse(routeState) as | undefined - | {param?: {selectedConversationIDKey?: unknown}; routeName?: string} + | {param?: {selectedConversationIDKey?: unknown}; routeName?: string; uid?: unknown} if (item) { const _convo = item.param?.selectedConversationIDKey || undefined if (typeof _convo === 'string') { conversation = _convo + if (typeof item.uid === 'string') { + conversationUid = item.uid + } logger.info('initialState: routeState', conversation) } const _rn = item.routeName || undefined @@ -256,6 +260,7 @@ const loadStartupDetails = async () => { useConfigState.getState().dispatch.setStartupDetails({ conversation: conversation ?? noConversationIDKey, + conversationUid, followUser, link, tab: tab as Tabs.Tab, diff --git a/shared/router-v2/linking.tsx b/shared/router-v2/linking.tsx index 6d955b3a4428..d6d4d6bfb5a5 100644 --- a/shared/router-v2/linking.tsx +++ b/shared/router-v2/linking.tsx @@ -2,6 +2,7 @@ import * as Tabs from '@/constants/tabs' import {isSplit} from '@/constants/chat/layout' import {isValidConversationIDKey} from '@/constants/types/chat/common' import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' import type * as UsePushStateType from '@/stores/push' import type {LinkingOptions} from '@react-navigation/native' import type {RootParamList} from './route-params' @@ -261,6 +262,13 @@ export const createLinkingConfig = ( startupConversation = '' } + // Only restore a conv that belongs to the logged-in account; see persistRoute + // (ui.routeState2 is device-global, so it can hold another account's conv). + const {uid: currentUid} = useCurrentUserState.getState() + if (startupConversation && startup.conversationUid && startup.conversationUid !== currentUid) { + startupConversation = '' + } + // Lazy-require to break require cycle: linking.tsx → push.native.tsx → linking.tsx const {usePushState} = require('@/stores/push') as typeof UsePushStateType const pushState = usePushState.getState() diff --git a/shared/stores/config.tsx b/shared/stores/config.tsx index 80b6644b76da..357027bc6302 100644 --- a/shared/stores/config.tsx +++ b/shared/stores/config.tsx @@ -51,6 +51,9 @@ type Store = T.Immutable<{ startup: { loaded: boolean conversation: T.Chat.ConversationIDKey + // uid of the account that persisted `conversation` (from ui.routeState2). + // Used to avoid replaying a conversation under a different account. + conversationUid?: string followUser: string link: string tab?: Tab diff --git a/shared/util/storeless-actions.tsx b/shared/util/storeless-actions.tsx index a02c35af0efb..64d45a375d15 100644 --- a/shared/util/storeless-actions.tsx +++ b/shared/util/storeless-actions.tsx @@ -7,6 +7,7 @@ import {Alert, Linking} from 'react-native' import {showShareActionSheet as showShareActionSheetImpl} from '@/util/platform-specific' import {getTab, getVisiblePath} from '@/constants/router' import {peopleTab} from '@/constants/tabs' +import {useCurrentUserState} from '@/stores/current-user' export const copyToClipboard = (text: string) => { if (isMobile) { @@ -100,7 +101,11 @@ export const persistRoute = (clear: boolean, immediate: boolean, isStartupLoaded } return false }) - const next = JSON.stringify({param, routeName}) + // Stamp the persisted route with the current uid. ui.routeState2 is stored + // device-globally (not per-account), so on startup we must only restore a + // conversation that belongs to the account we end up logged in as. + const {uid} = useCurrentUserState.getState() + const next = JSON.stringify({param, routeName, uid}) if (lastPersist === next) { return }