// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {switchToChannelById} from '@actions/remote/channel'; import {fetchAndSwitchToThread} from '@actions/remote/thread'; import {Preferences, Screens} from '@constants'; import {getDefaultThemeByAppearance} from '@context/theme'; import DatabaseManager from '@database/manager'; import {getMyChannel} from '@queries/servers/channel'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {getConfig, getCurrentTeamId, getLicense, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system'; import {getMyTeamById} from '@queries/servers/team'; import {getIsCRTEnabled} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; import EphemeralStore from '@store/ephemeral_store'; import NavigationStore from '@store/navigation_store'; import {setTeamLoading} from '@store/team_load_store'; import {isTablet} from '@utils/helpers'; import {emitNotificationError} from '@utils/notification'; import {setThemeDefaults, updateThemeIfNeeded} from '@utils/theme'; import {syncOtherServers} from './common'; import {deferredAppEntryActions, entry} from './gql_common'; export async function pushNotificationEntry(serverUrl: string, notification: NotificationWithData) { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { return {error: `${serverUrl} database not found`}; } // We only reach this point if we have a channel Id in the notification payload const channelId = notification.payload!.channel_id!; const rootId = notification.payload!.root_id!; const {database} = operator; const currentTeamId = await getCurrentTeamId(database); const currentServerUrl = await DatabaseManager.getActiveServerUrl(); const lastDisconnectedAt = await getWebSocketLastDisconnected(database); let isDirectChannel = false; let teamId = notification.payload?.team_id; if (!teamId) { // If the notification payload does not have a teamId we assume is a DM/GM isDirectChannel = true; teamId = currentTeamId; } if (currentServerUrl !== serverUrl) { await DatabaseManager.setActiveServerDatabase(serverUrl); } if (!EphemeralStore.theme) { // When opening the app from a push notification the theme may not be set in the EphemeralStore // causing the goToScreen to use the Appearance theme instead and that causes the screen background color to potentially // not match the theme const themes = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_THEME, teamId).fetch(); let theme = getDefaultThemeByAppearance(); if (themes.length) { theme = setThemeDefaults(JSON.parse(themes[0].value) as Theme); } updateThemeIfNeeded(theme, true); } await NavigationStore.waitUntilScreenHasLoaded(Screens.HOME); // To make the switch faster we determine if we already have the team & channel const myChannel = await getMyChannel(database, channelId); const myTeam = await getMyTeamById(database, teamId); const isCRTEnabled = await getIsCRTEnabled(database); const isThreadNotification = isCRTEnabled && Boolean(rootId); let switchedToScreen = false; let switchedToChannel = false; if (myChannel && myTeam) { if (isThreadNotification) { await fetchAndSwitchToThread(serverUrl, rootId, true); } else { switchedToChannel = true; await switchToChannelById(serverUrl, channelId, teamId); } switchedToScreen = true; } setTeamLoading(serverUrl, true); const entryData = await entry(serverUrl, teamId, channelId); if ('error' in entryData) { setTeamLoading(serverUrl, false); return {error: entryData.error}; } const {models, initialTeamId, initialChannelId, prefData, teamData, chData} = entryData; // There is a chance that after the above request returns // the user is no longer part of the team or channel // that triggered the notification (rare but possible) let selectedTeamId = teamId; let selectedChannelId = channelId; if (initialTeamId !== teamId) { // We are no longer a part of the team that the notification belongs to // Immediately set the new team as the current team in the database so that the UI // renders the correct team. selectedTeamId = initialTeamId; if (!isDirectChannel) { selectedChannelId = initialChannelId; } } if (!switchedToScreen) { const isTabletDevice = await isTablet(); if (isTabletDevice || (channelId === selectedChannelId)) { // Make switch again to get the missing data and make sure the team is the correct one switchedToScreen = true; if (isThreadNotification) { await fetchAndSwitchToThread(serverUrl, rootId, true); } else { switchedToChannel = true; await switchToChannelById(serverUrl, channelId, teamId); } } else if (teamId !== selectedTeamId || channelId !== selectedChannelId) { // If in the end the selected team or channel is different than the one from the notification // we switch again await setCurrentTeamAndChannelId(operator, selectedTeamId, selectedChannelId); } } if (teamId !== selectedTeamId) { emitNotificationError('Team'); } else if (channelId !== selectedChannelId) { emitNotificationError('Channel'); } // Waiting for the screen to display fixes a race condition when fetching and storing data if (switchedToChannel) { await NavigationStore.waitUntilScreenHasLoaded(Screens.CHANNEL); } else if (switchedToScreen && isThreadNotification) { await NavigationStore.waitUntilScreenHasLoaded(Screens.THREAD); } await operator.batchRecords(models); setTeamLoading(serverUrl, false); const {id: currentUserId, locale: currentUserLocale} = (await getCurrentUser(operator.database))!; const config = await getConfig(database); const license = await getLicense(database); await deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, selectedTeamId, selectedChannelId); syncOtherServers(serverUrl); return {userId: currentUserId}; }