mattermost-mobile-upstream/app/actions/remote/entry/notification.ts
Carlos Garcia a5ff8a0d95
Mm 68767 zero persistence (#9803)
* implements auto cache cleanup when zero persistence mode is enabled (0 value)

* uses memory flag with sqlite instead of loki when running in zero persistence mode

* renames ephemeral mode manager and improves readability

* zero persistence refactors and restore values during transitions

* fixes ephemeral mode tests

* use initializeClient instead of just initialize to wait for doReconnect to finish

that way teams loading is awaited while WS reconnects during ZPM transitions

* adds inital sync as a mount hook to prevent undesired load error states

during app cold start in ZPM mode, we need a way to load team and channels
without showing loading error during the transition. The nre is initial sync
hook allows for it

* reset started sync subject on wipe

* removes extra log lines

* resets tracked server during ephemeral mode transitions

* improvements to apply persistence mode changes when something fails

* address coderabbit PR suggestions

* add batching time logging

* throws ephemeral mode change error as is

* make deleteServerDatabaseFiles private

* inline derive persistence flag function

* return current user id when refetching current user

* support zero persistence mode server for push notifications

* adds support for zero persistence mode when sharing to mattermost

* adds clarifying comment

* catch and log error deleting files

* reorder translations to correct position

* adds some new tests to improve coverage

* ios push notification native fix

* logout when fetchMe fails with 401 status code during reconnection

* properly surface errors

* return proper false value when checking for zero persistence flag

* change comments and properly await on some operations

* prevent not set persistence mode to report as an error

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* include servers with no persistence flag set in share list

* disable button for accessibility

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* improves when to store and remove signing key from global area

* review push notification reception running in zero persistence mode

* add server to ephemeral mode manager right after login

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-16 13:55:45 +02:00

145 lines
6.0 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {fetchMyChannel, switchToChannelById} from '@actions/remote/channel';
import {fetchPostById} from '@actions/remote/post';
import {fetchMyTeam} from '@actions/remote/team';
import {fetchAndSwitchToThread} from '@actions/remote/thread';
import {refetchCurrentUser} from '@actions/remote/user';
import {getDefaultThemeByAppearance} from '@context/theme';
import DatabaseManager from '@database/manager';
import PerformanceMetricsManager from '@managers/performance_metrics_manager';
import WebsocketManager from '@managers/websocket_manager';
import {getServer} from '@queries/app/servers';
import {getMyChannel} from '@queries/servers/channel';
import {getPostById} from '@queries/servers/post';
import {queryThemePreferences} from '@queries/servers/preference';
import {getCurrentTeamId, getCurrentUserId} from '@queries/servers/system';
import {getMyTeamById} from '@queries/servers/team';
import {getIsCRTEnabled} from '@queries/servers/thread';
import EphemeralStore from '@store/ephemeral_store';
import {isErrorWithStatusCode} from '@utils/errors';
import {dismissKeyboard} from '@utils/keyboard';
import {emitNotificationError} from '@utils/notification';
import {setThemeDefaults, updateThemeIfNeeded} from '@utils/theme';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type MyTeamModel from '@typings/database/models/servers/my_team';
import type PostModel from '@typings/database/models/servers/post';
export async function pushNotificationEntry(serverUrl: string, notification: NotificationData, groupLabel?: BaseRequestGroupLabel) {
// We only reach this point if we have a channel Id in the notification payload
const channelId = notification.channel_id!;
const rootId = notification.root_id!;
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
// Cold start from push: ensure keyboard is not stuck over the channel after navigation
await dismissKeyboard();
PerformanceMetricsManager.startTimeToInteraction();
const {database} = operator;
const currentTeamId = await getCurrentTeamId(database);
const currentServerUrl = await DatabaseManager.getActiveServerUrl();
let teamId = notification.team_id;
if (!teamId) {
// If the notification payload does not have a teamId we assume is a DM/GM
teamId = currentTeamId;
}
if (currentServerUrl !== serverUrl) {
await DatabaseManager.setActiveServerDatabase(serverUrl);
}
if (!EphemeralStore.getTheme()) {
// 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 queryThemePreferences(database, teamId).fetch();
let theme = getDefaultThemeByAppearance();
if (themes.length) {
theme = setThemeDefaults(JSON.parse(themes[0].value) as Theme);
}
updateThemeIfNeeded(theme, true);
}
const server = await getServer(serverUrl);
const isZeroPersistence = server?.persistenceFlag === 'zero-persistence';
// To make the switch faster we determine if we already have the team & channel
let myChannel: MyChannelModel | ChannelMembership | undefined = await getMyChannel(database, channelId);
let myTeam: MyTeamModel | TeamMembership | undefined = await getMyTeamById(database, teamId);
if (isZeroPersistence && !myChannel && !myTeam) {
// no values in db and zero persistence mode means we probably need to refetch current user id
const existingUserId = await getCurrentUserId(database);
if (!existingUserId) {
await refetchCurrentUser(serverUrl, undefined);
}
}
if (!myTeam) {
const resp = await fetchMyTeam(serverUrl, teamId, false, groupLabel);
if (resp.error) {
if (isErrorWithStatusCode(resp.error) && resp.error.status_code === 403) {
emitNotificationError('Team');
} else {
emitNotificationError('Connection');
}
} else {
myTeam = resp.memberships?.[0];
}
}
if (!myChannel) {
const resp = await fetchMyChannel(serverUrl, teamId, channelId, false, groupLabel);
if (resp.error) {
if (isErrorWithStatusCode(resp.error) && resp.error.status_code === 403) {
emitNotificationError('Channel');
} else {
emitNotificationError('Connection');
}
} else {
myChannel = resp.memberships?.[0];
}
}
const isCRTEnabled = await getIsCRTEnabled(database);
const isThreadNotification = isCRTEnabled && Boolean(rootId);
if (myChannel && myTeam) {
if (isThreadNotification) {
let post: PostModel | Post | undefined = await getPostById(database, rootId);
if (!post) {
const resp = await fetchPostById(serverUrl, rootId, false, groupLabel);
post = resp.post;
}
const actualRootId = post && ('root_id' in post ? post.root_id : post.rootId);
if (actualRootId) {
PerformanceMetricsManager.setLoadTarget('THREAD');
await fetchAndSwitchToThread(serverUrl, actualRootId, true, groupLabel);
} else if (post) {
PerformanceMetricsManager.setLoadTarget('THREAD');
await fetchAndSwitchToThread(serverUrl, rootId, true, groupLabel);
} else {
emitNotificationError('Post');
}
} else {
PerformanceMetricsManager.setLoadTarget('CHANNEL');
await switchToChannelById(serverUrl, channelId, teamId, false, groupLabel);
}
}
WebsocketManager.openAll(groupLabel);
return {};
}