From 58fc869d70fd10ba825fecb6baa70931e31fbab2 Mon Sep 17 00:00:00 2001 From: harshil Sharma Date: Thu, 23 Nov 2023 10:40:49 +0530 Subject: [PATCH 1/4] Added onReconnect handling for converted GM --- app/actions/remote/entry/common.ts | 61 ++++++++++++++++++++++++++---- app/actions/websocket/index.ts | 4 +- ios/Podfile.lock | 8 ++-- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index 34cd697b1..ef4a65272 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -19,7 +19,7 @@ import {selectDefaultTeam} from '@helpers/api/team'; import {DEFAULT_LOCALE} from '@i18n'; import NetworkManager from '@managers/network_manager'; import {getDeviceToken} from '@queries/app/global'; -import {queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel'; +import {getChannelById, queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel'; import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry'; import {getHasCRTChanged} from '@queries/servers/preference'; import {getConfig, getCurrentChannelId, getCurrentTeamId, getIsDataRetentionEnabled, getPushVerificationStatus, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system'; @@ -42,6 +42,8 @@ export type AppEntryData = { removeTeamIds?: string[]; removeChannelIds?: string[]; isCRTEnabled: boolean; + initialChannelId?: string; + gmConverted?: boolean; } export type AppEntryError = { @@ -56,6 +58,7 @@ export type EntryResponse = { teamData: MyTeamsRequest; chData?: MyChannelsRequest; meData?: MyUserRequest; + gmConverted?: boolean; } | { error: unknown; } @@ -123,12 +126,12 @@ const entryRest = async (serverUrl: string, teamId?: string, channelId?: string, const lastDisconnectedAt = since || await getWebSocketLastDisconnected(database); - const fetchedData = await fetchAppEntryData(serverUrl, lastDisconnectedAt, teamId); + const fetchedData = await fetchAppEntryData(serverUrl, lastDisconnectedAt, teamId, channelId); if ('error' in fetchedData) { return {error: fetchedData.error}; } - const {initialTeamId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds, isCRTEnabled} = fetchedData; + const {initialTeamId, initialChannelId: fetchedChannelId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds, isCRTEnabled, gmConverted} = fetchedData; const chError = chData?.error; if (isErrorWithStatusCode(chError) && chError.status_code === 403) { // if the user does not have appropriate permissions, which means the user those not belong to the team, @@ -142,7 +145,7 @@ const entryRest = async (serverUrl: string, teamId?: string, channelId?: string, const rolesData = await fetchRoles(serverUrl, teamData.memberships, chData?.memberships, meData.user, true); - const initialChannelId = await entryInitialChannelId(database, channelId, teamId, initialTeamId, meData?.user?.locale || '', chData?.channels, chData?.memberships); + const initialChannelId = await entryInitialChannelId(database, fetchedChannelId, teamId, initialTeamId, meData?.user?.locale || '', chData?.channels, chData?.memberships); const removeTeams = await teamsToRemove(serverUrl, removeTeamIds); @@ -158,10 +161,10 @@ const entryRest = async (serverUrl: string, teamId?: string, channelId?: string, const models = await Promise.all(modelPromises); - return {models: models.flat(), initialChannelId, initialTeamId, prefData, teamData, chData, meData}; + return {models: models.flat(), initialChannelId, initialTeamId, prefData, teamData, chData, meData, gmConverted}; }; -const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTeamId = ''): Promise => { +const fetchAppEntryData = async (serverUrl: string, sinceArg: number, onLoadTeamId = '', channelId?: string): Promise => { const database = DatabaseManager.serverDatabases[serverUrl]?.database; if (!database) { return {error: `${serverUrl} database not found`}; @@ -191,7 +194,7 @@ const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTea // Fetch in parallel teams / team membership / channels for current team / user preferences / user const promises: [Promise, Promise, Promise] = [ fetchMyTeams(serverUrl, fetchOnly), - initialTeamId ? fetchMyChannelsForTeam(serverUrl, initialTeamId, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled) : Promise.resolve(undefined), + onLoadTeamId ? fetchMyChannelsForTeam(serverUrl, onLoadTeamId, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled) : Promise.resolve(undefined), fetchMe(serverUrl, fetchOnly), ]; @@ -199,6 +202,41 @@ const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTea const [teamData, , meData] = resolution; let [, chData] = resolution; + let initialTeamId = onLoadTeamId; + let initialChannelId = channelId; + let gmConverted = false; + + if (channelId && chData?.channels) { + // check if channelId is in list of team's channels returned by server + const channelInServerData = chData.channels.find((channel) => channel.id === channelId); + + // if channel is not found in server data, we need to check if it + // was a GM that is converted to a private or public channel in a different team than + // the team the mobile app was last closed in. + // Although yon can convert GM only to a pirvate channel, a private channel can furthur be converted to a public channel. + // So between the mobile app being on the GM and reconnecting, + // it may have become either a public or a private channel. So we need to check for both. + if (!channelInServerData) { + const existingChannel = await getChannelById(database, channelId); + if (existingChannel && existingChannel.type === General.GM_CHANNEL) { + // Okay, so now we know the channel existsin in mobile app's database as a GM. + // We now need to also check if channel on server is actually a private channel, + // and if so, which team does it belong to now. That team will become the + // active team on mobile app after this point. + + const client = NetworkManager.getClient(serverUrl); + const serverChannel = await client.getChannel(channelId); + if (serverChannel.type === General.PRIVATE_CHANNEL || serverChannel.type === General.OPEN_CHANNEL) { + initialTeamId = serverChannel.team_id; + initialChannelId = channelId; + gmConverted = true; + + chData = await fetchMyChannelsForTeam(serverUrl, serverChannel.team_id, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled); + } + } + } + } + if (!initialTeamId && teamData.teams?.length && teamData.memberships?.length) { // If no initial team was set in the database but got teams in the response const config = await getConfig(database); @@ -221,6 +259,8 @@ const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTea meData, removeTeamIds, isCRTEnabled, + initialChannelId, + gmConverted, }; if (teamData.teams?.length === 0 && !teamData.error) { @@ -438,6 +478,7 @@ export async function handleEntryAfterLoadNavigation( currentChannelId: string, initialTeamId: string, initialChannelId: string, + gmConverted: boolean, ) { try { const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); @@ -462,7 +503,11 @@ export async function handleEntryAfterLoadNavigation( await handleKickFromTeam(serverUrl, currentTeamIdAfterLoad); } } else if (currentTeamIdAfterLoad !== initialTeamId) { - await handleKickFromTeam(serverUrl, currentTeamIdAfterLoad); + if (gmConverted) { + await setCurrentTeamAndChannelId(operator, initialTeamId, currentChannelId); + } else { + await handleKickFromTeam(serverUrl, currentTeamIdAfterLoad); + } } else if (currentChannelIdAfterLoad !== currentChannelId) { // Switched channels while loading if (!channelMembers.find((m) => m.channel_id === currentChannelIdAfterLoad)) { diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index 35c72dbbf..98e1bded2 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -159,9 +159,9 @@ async function doReconnect(serverUrl: string) { setTeamLoading(serverUrl, false); return entryData.error; } - const {models, initialTeamId, initialChannelId, prefData, teamData, chData} = entryData; + const {models, initialTeamId, initialChannelId, prefData, teamData, chData, gmConverted} = entryData; - await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeamId || '', currentChannelId || '', initialTeamId, initialChannelId); + await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeamId || '', currentChannelId || '', initialTeamId, initialChannelId, Boolean(gmConverted)); const dt = Date.now(); if (models?.length) { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 69a4e3a57..c3f6f0a8b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -570,9 +570,9 @@ PODS: - React-Core - RNVectorIcons (10.0.0): - React-Core - - SDWebImage (5.18.2): - - SDWebImage/Core (= 5.18.2) - - SDWebImage/Core (5.18.2) + - SDWebImage (5.18.3): + - SDWebImage/Core (= 5.18.3) + - SDWebImage/Core (5.18.3) - SDWebImageWebPCoder (0.13.0): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.17) @@ -986,7 +986,7 @@ SPEC CHECKSUMS: RNShare: da6d90b6dc332f51f86498041d6e34211f96b630 RNSVG: 03e4d258ca355d7836a0a5dd4d4dc63c1eb49cbb RNVectorIcons: 8b5bb0fa61d54cd2020af4f24a51841ce365c7e9 - SDWebImage: c0de394d7cf7f9838aed1fd6bb6037654a4572e4 + SDWebImage: 96e0c18ef14010b7485210e92fac888587ebb958 SDWebImageWebPCoder: af09429398d99d524cae2fe00f6f0f6e491ed102 Sentry: 56c76eed917f7dffd46db50906afbf5c9aa2673a SentryPrivate: f3be34b5deb9fe676fdfb1f1ad5cdb1b740c5688 From d49dfa50fbc82865e129d4476831bca4fbcaa567 Mon Sep 17 00:00:00 2001 From: harshil Sharma Date: Thu, 23 Nov 2023 11:19:02 +0530 Subject: [PATCH 2/4] Restored podfile --- ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c3f6f0a8b..69a4e3a57 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -570,9 +570,9 @@ PODS: - React-Core - RNVectorIcons (10.0.0): - React-Core - - SDWebImage (5.18.3): - - SDWebImage/Core (= 5.18.3) - - SDWebImage/Core (5.18.3) + - SDWebImage (5.18.2): + - SDWebImage/Core (= 5.18.2) + - SDWebImage/Core (5.18.2) - SDWebImageWebPCoder (0.13.0): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.17) @@ -986,7 +986,7 @@ SPEC CHECKSUMS: RNShare: da6d90b6dc332f51f86498041d6e34211f96b630 RNSVG: 03e4d258ca355d7836a0a5dd4d4dc63c1eb49cbb RNVectorIcons: 8b5bb0fa61d54cd2020af4f24a51841ce365c7e9 - SDWebImage: 96e0c18ef14010b7485210e92fac888587ebb958 + SDWebImage: c0de394d7cf7f9838aed1fd6bb6037654a4572e4 SDWebImageWebPCoder: af09429398d99d524cae2fe00f6f0f6e491ed102 Sentry: 56c76eed917f7dffd46db50906afbf5c9aa2673a SentryPrivate: f3be34b5deb9fe676fdfb1f1ad5cdb1b740c5688 From 878ed5673c0ed6721d9797745933c95f27e486a8 Mon Sep 17 00:00:00 2001 From: harshil Sharma Date: Fri, 24 Nov 2023 11:17:12 +0530 Subject: [PATCH 3/4] nit fix --- app/actions/remote/entry/common.ts | 4 ++-- app/actions/websocket/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index ef4a65272..87c16c50e 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -43,7 +43,7 @@ export type AppEntryData = { removeChannelIds?: string[]; isCRTEnabled: boolean; initialChannelId?: string; - gmConverted?: boolean; + gmConverted: boolean; } export type AppEntryError = { @@ -58,7 +58,7 @@ export type EntryResponse = { teamData: MyTeamsRequest; chData?: MyChannelsRequest; meData?: MyUserRequest; - gmConverted?: boolean; + gmConverted: boolean; } | { error: unknown; } diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index 98e1bded2..95e1084a6 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -161,7 +161,7 @@ async function doReconnect(serverUrl: string) { } const {models, initialTeamId, initialChannelId, prefData, teamData, chData, gmConverted} = entryData; - await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeamId || '', currentChannelId || '', initialTeamId, initialChannelId, Boolean(gmConverted)); + await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeamId || '', currentChannelId || '', initialTeamId, initialChannelId, gmConverted); const dt = Date.now(); if (models?.length) { From 3c6638f47495e763d5ec19911bc7e9d5e7ffa19c Mon Sep 17 00:00:00 2001 From: harshil Sharma Date: Mon, 27 Nov 2023 15:22:50 +0530 Subject: [PATCH 4/4] Optimized the number of calls to fetchMyChannelsForTeam --- app/actions/remote/entry/common.ts | 54 +++++++++++++----------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index 87c16c50e..9e09b1058 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -191,52 +191,46 @@ const fetchAppEntryData = async (serverUrl: string, sinceArg: number, onLoadTeam } } - // Fetch in parallel teams / team membership / channels for current team / user preferences / user - const promises: [Promise, Promise, Promise] = [ + // Fetch in parallel teams / team membership / user preferences / user + const promises: [Promise, Promise] = [ fetchMyTeams(serverUrl, fetchOnly), - onLoadTeamId ? fetchMyChannelsForTeam(serverUrl, onLoadTeamId, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled) : Promise.resolve(undefined), fetchMe(serverUrl, fetchOnly), ]; const resolution = await Promise.all(promises); - const [teamData, , meData] = resolution; - let [, chData] = resolution; + const [teamData, meData] = resolution; + let chData; let initialTeamId = onLoadTeamId; let initialChannelId = channelId; let gmConverted = false; - if (channelId && chData?.channels) { - // check if channelId is in list of team's channels returned by server - const channelInServerData = chData.channels.find((channel) => channel.id === channelId); + if (channelId) { + const existingChannel = await getChannelById(database, channelId); + if (existingChannel && existingChannel.type === General.GM_CHANNEL) { + // Okay, so now we know the channel existsin in mobile app's database as a GM. + // We now need to also check if channel on server is actually a private channel, + // and if so, which team does it belong to now. That team will become the + // active team on mobile app after this point. - // if channel is not found in server data, we need to check if it - // was a GM that is converted to a private or public channel in a different team than - // the team the mobile app was last closed in. - // Although yon can convert GM only to a pirvate channel, a private channel can furthur be converted to a public channel. - // So between the mobile app being on the GM and reconnecting, - // it may have become either a public or a private channel. So we need to check for both. - if (!channelInServerData) { - const existingChannel = await getChannelById(database, channelId); - if (existingChannel && existingChannel.type === General.GM_CHANNEL) { - // Okay, so now we know the channel existsin in mobile app's database as a GM. - // We now need to also check if channel on server is actually a private channel, - // and if so, which team does it belong to now. That team will become the - // active team on mobile app after this point. + const client = NetworkManager.getClient(serverUrl); + const serverChannel = await client.getChannel(channelId); - const client = NetworkManager.getClient(serverUrl); - const serverChannel = await client.getChannel(channelId); - if (serverChannel.type === General.PRIVATE_CHANNEL || serverChannel.type === General.OPEN_CHANNEL) { - initialTeamId = serverChannel.team_id; - initialChannelId = channelId; - gmConverted = true; - - chData = await fetchMyChannelsForTeam(serverUrl, serverChannel.team_id, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled); - } + // Although yon can convert GM only to a pirvate channel, a private channel can furthur be converted to a public channel. + // So between the mobile app being on the GM and reconnecting, + // it may have become either a public or a private channel. So we need to check for both. + if (serverChannel.type === General.PRIVATE_CHANNEL || serverChannel.type === General.OPEN_CHANNEL) { + initialTeamId = serverChannel.team_id; + initialChannelId = channelId; + gmConverted = true; } } } + if (initialTeamId) { + chData = await fetchMyChannelsForTeam(serverUrl, initialTeamId, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled); + } + if (!initialTeamId && teamData.teams?.length && teamData.memberships?.length) { // If no initial team was set in the database but got teams in the response const config = await getConfig(database);