// 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 {getCommonSystemValues, getConfig, getCurrentTeamId, 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 {isTablet} from '@utils/helpers'; import {logDebug} from '@utils/log'; import {emitNotificationError} from '@utils/notification'; import {setThemeDefaults, updateThemeIfNeeded} from '@utils/theme'; import {deferredAppEntryActions, entry, syncOtherServers} from './common'; import {graphQLCommon} 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(); 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); const config = await getConfig(database); let result; if (config?.FeatureFlagGraphQL === 'true') { result = await graphQLCommon(serverUrl, true, teamId, channelId); if (result.error) { logDebug('Error using GraphQL, trying REST', result.error); result = restNotificationEntry(serverUrl, teamId, channelId, rootId, isDirectChannel); } } else { result = restNotificationEntry(serverUrl, teamId, channelId, rootId, isDirectChannel); } syncOtherServers(serverUrl); return result; } const restNotificationEntry = async (serverUrl: string, teamId: string, channelId: string, rootId: string, isDirectChannel: boolean) => { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { return {error: `${serverUrl} database not found`}; } const {database} = operator; const entryData = await entry(serverUrl, teamId, channelId); if ('error' in entryData) { 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; } } const myChannel = await getMyChannel(database, channelId); const myTeam = await getMyTeamById(database, teamId); const isCRTEnabled = await getIsCRTEnabled(database); const isThreadNotification = isCRTEnabled && Boolean(rootId); await operator.batchRecords(models); 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; } if (!switchedToScreen) { const isTabletDevice = await isTablet(); if (isTabletDevice || (selectedChannelId === channelId)) { // 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, selectedChannelId, selectedTeamId); } } else if (selectedTeamId !== teamId || selectedChannelId !== channelId) { // 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 (selectedTeamId !== teamId) { emitNotificationError('Team'); } else if (selectedChannelId !== channelId) { emitNotificationError('Channel'); } const {id: currentUserId, locale: currentUserLocale} = (await getCurrentUser(operator.database))!; const {config, license} = await getCommonSystemValues(operator.database); const lastDisconnectedAt = await getWebSocketLastDisconnected(database); // 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 deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, selectedTeamId, selectedChannelId); return {userId: currentUserId}; };