forked from Ivasoft/mattermost-mobile
[Gekidou] Fix & small refactor to the app entry logic and WS reconnection (#5937)
* Fix & small refactor to the app entry logic and WS reconnection * Remove not needed log in MainActivity * Replace async forEach with for await * extract getClient to its own block * replace double filter with reduce * fix select channel on WS reconnect and user no longer belongs to current team * feedback review on WS users actions * Add windowFocusChanged for Android only * on WS reconnection set team & channel if not member of current team * reduce suggestion * feedback review
This commit is contained in:
@@ -2,9 +2,14 @@ package com.mattermost.rnbeta;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.reactnativenavigation.NavigationActivity;
|
||||
import com.github.emilioicai.hwkeyboardevent.HWKeyboardEventModule;
|
||||
|
||||
@@ -29,6 +34,18 @@ public class MainActivity extends NavigationActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
// This can be removed once https://github.com/facebook/react-native/issues/32628 is solved
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
MattermostManagedModule instance = MattermostManagedModule.getInstance();
|
||||
if (instance != null) {
|
||||
WritableMap data = Arguments.createMap();
|
||||
data.putString("appState", hasFocus ? "active" : "background");
|
||||
instance.sendEvent("windowFocusChanged", data);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
https://mattermost.atlassian.net/browse/MM-10601
|
||||
Required by react-native-hw-keyboard-event
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.mattermost.rnbeta;
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -10,19 +11,24 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
public class MattermostManagedModule extends ReactContextBaseJavaModule {
|
||||
private static MattermostManagedModule instance;
|
||||
private ReactApplicationContext reactContext;
|
||||
|
||||
private static final String TAG = MattermostManagedModule.class.getSimpleName();
|
||||
|
||||
private MattermostManagedModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
this.reactContext = reactContext;
|
||||
}
|
||||
|
||||
public static MattermostManagedModule getInstance(ReactApplicationContext reactContext) {
|
||||
if (instance == null) {
|
||||
instance = new MattermostManagedModule(reactContext);
|
||||
} else {
|
||||
instance.reactContext = reactContext;
|
||||
}
|
||||
|
||||
return instance;
|
||||
@@ -32,6 +38,13 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void sendEvent(String eventName,
|
||||
@Nullable WritableMap params) {
|
||||
this.reactContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(eventName, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String getName() {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {addChannelToTeamHistory, addTeamToTeamHistory, removeChannelFromTeamHist
|
||||
import {queryCurrentUser} from '@queries/servers/user';
|
||||
import {dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen} from '@screens/navigation';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {displayGroupMessageName, displayUsername} from '@utils/user';
|
||||
import {displayGroupMessageName, displayUsername, getUserIdFromChannelName} from '@utils/user';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
@@ -298,29 +298,38 @@ export const updateLastPostAt = async (serverUrl: string, channelId: string, las
|
||||
return {models};
|
||||
};
|
||||
|
||||
export async function updateChannelsDisplayName(serverUrl: string, channels: ChannelModel[], user: UserProfile, prepareRecordsOnly = false) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
export async function updateChannelsDisplayName(serverUrl: string, channels: ChannelModel[], users: UserProfile[], prepareRecordsOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {};
|
||||
}
|
||||
const currentUser = await queryCurrentUser(database.database);
|
||||
|
||||
const {database} = operator;
|
||||
const currentUser = await queryCurrentUser(database);
|
||||
if (!currentUser) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const {config, license} = await queryCommonSystemValues(database.database);
|
||||
const preferences = await queryPreferencesByCategoryAndName(database.database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT);
|
||||
const {config, license} = await queryCommonSystemValues(database);
|
||||
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT);
|
||||
const displaySettings = getTeammateNameDisplaySetting(preferences, config, license);
|
||||
const models: Model[] = [];
|
||||
channels?.forEach(async (channel) => {
|
||||
for await (const channel of channels) {
|
||||
let newDisplayName = '';
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
const otherUserId = getUserIdFromChannelName(currentUser.id, channel.name);
|
||||
const user = users.find((u) => u.id === otherUserId);
|
||||
newDisplayName = displayUsername(user, currentUser.locale, displaySettings);
|
||||
} else {
|
||||
const dbProfiles = await database.database.get<UserModel>(USER).query(Q.on(CHANNEL_MEMBERSHIP, Q.where('channel_id', channel.id))).fetch();
|
||||
const newProfiles: Array<UserModel|UserProfile> = dbProfiles.filter((u) => u.id !== user.id);
|
||||
newProfiles.push(user);
|
||||
newDisplayName = displayGroupMessageName(newProfiles, currentUser.locale, displaySettings, currentUser.id);
|
||||
const dbProfiles = await database.get<UserModel>(USER).query(Q.on(CHANNEL_MEMBERSHIP, Q.where('channel_id', channel.id))).fetch();
|
||||
const profileIds = dbProfiles.map((p) => p.id);
|
||||
const gmUsers = users.filter((u) => profileIds.includes(u.id));
|
||||
if (gmUsers.length) {
|
||||
const uIds = gmUsers.map((u) => u.id);
|
||||
const newProfiles: Array<UserModel|UserProfile> = dbProfiles.filter((u) => !uIds.includes(u.id));
|
||||
newProfiles.push(...gmUsers);
|
||||
newDisplayName = displayGroupMessageName(newProfiles, currentUser.locale, displaySettings, currentUser.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (channel.displayName !== newDisplayName) {
|
||||
@@ -329,10 +338,10 @@ export async function updateChannelsDisplayName(serverUrl: string, channels: Cha
|
||||
});
|
||||
models.push(channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (models.length && !prepareRecordsOnly) {
|
||||
database.operator.batchRecords(models);
|
||||
await operator.batchRecords(models);
|
||||
}
|
||||
|
||||
return {models};
|
||||
|
||||
@@ -7,7 +7,7 @@ import {fetchConfigAndLicense} from '@actions/remote/systems';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {queryChannelsById, queryDefaultChannelForTeam} from '@queries/servers/channel';
|
||||
import {prepareModels} from '@queries/servers/entry';
|
||||
import {prepareCommonSystemValues, queryCommonSystemValues, queryCurrentChannelId, queryCurrentTeamId, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {prepareCommonSystemValues, queryCommonSystemValues, queryCurrentChannelId, queryCurrentTeamId, queryWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {deleteMyTeams, queryTeamsById} from '@queries/servers/team';
|
||||
import {queryCurrentUser} from '@queries/servers/user';
|
||||
import {deleteV1Data} from '@utils/file';
|
||||
@@ -24,7 +24,8 @@ export const appEntry = async (serverUrl: string) => {
|
||||
|
||||
const tabletDevice = await isTablet();
|
||||
const currentTeamId = await queryCurrentTeamId(database);
|
||||
const fetchedData = await fetchAppEntryData(serverUrl, currentTeamId);
|
||||
const lastDisconnectedAt = await queryWebSocketLastDisconnected(database);
|
||||
const fetchedData = await fetchAppEntryData(serverUrl, lastDisconnectedAt, currentTeamId);
|
||||
const fetchedError = (fetchedData as AppEntryError).error;
|
||||
|
||||
if (fetchedError) {
|
||||
@@ -32,6 +33,7 @@ export const appEntry = async (serverUrl: string) => {
|
||||
}
|
||||
|
||||
const {initialTeamId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds} = fetchedData as AppEntryData;
|
||||
const rolesData = await fetchRoles(serverUrl, teamData?.memberships, chData?.memberships, meData?.user, true);
|
||||
|
||||
if (initialTeamId === currentTeamId) {
|
||||
let cId = await queryCurrentChannelId(database);
|
||||
@@ -67,14 +69,15 @@ export const appEntry = async (serverUrl: string) => {
|
||||
await deleteMyTeams(operator, removeTeams!);
|
||||
}
|
||||
|
||||
fetchRoles(serverUrl, teamData?.memberships, chData?.memberships, meData?.user);
|
||||
|
||||
let removeChannels;
|
||||
if (removeChannelIds?.length) {
|
||||
removeChannels = await queryChannelsById(database, removeChannelIds);
|
||||
}
|
||||
|
||||
const modelPromises = await prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData});
|
||||
if (rolesData.roles?.length) {
|
||||
modelPromises.push(operator.handleRole({roles: rolesData.roles, prepareRecordsOnly: true}));
|
||||
}
|
||||
const models = await Promise.all(modelPromises);
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models.flat());
|
||||
@@ -82,7 +85,7 @@ export const appEntry = async (serverUrl: string) => {
|
||||
|
||||
const {id: currentUserId, locale: currentUserLocale} = meData.user || (await queryCurrentUser(database))!;
|
||||
const {config, license} = await queryCommonSystemValues(database);
|
||||
deferredAppEntryActions(serverUrl, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId);
|
||||
deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId);
|
||||
|
||||
const error = teamData.error || chData?.error || prefData.error || meData.error;
|
||||
return {error, userId: meData?.user?.id};
|
||||
|
||||
@@ -5,15 +5,16 @@ import {fetchMissingSidebarInfo, fetchMyChannelsForTeam, MyChannelsRequest} from
|
||||
import {fetchGroupsForTeam} from '@actions/remote/group';
|
||||
import {fetchPostsForChannel, fetchPostsForUnreadChannels} from '@actions/remote/post';
|
||||
import {MyPreferencesRequest, fetchMyPreferences} from '@actions/remote/preference';
|
||||
import {fetchConfigAndLicense} from '@actions/remote/systems';
|
||||
import {fetchMyTeams, fetchTeamsChannelsAndUnreadPosts, MyTeamsRequest} from '@actions/remote/team';
|
||||
import {fetchMe, MyUserRequest} from '@actions/remote/user';
|
||||
import {fetchMe, MyUserRequest, updateAllUsersSince} from '@actions/remote/user';
|
||||
import {General, Preferences} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getPreferenceValue, getTeammateNameDisplaySetting} from '@helpers/api/preference';
|
||||
import {selectDefaultTeam} from '@helpers/api/team';
|
||||
import {DEFAULT_LOCALE} from '@i18n';
|
||||
import {queryAllChannelsForTeam} from '@queries/servers/channel';
|
||||
import {queryConfig, queryWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
import {queryConfig} from '@queries/servers/system';
|
||||
import {queryAvailableTeamIds, queryMyTeams} from '@queries/servers/team';
|
||||
|
||||
import type ClientError from '@client/rest/error';
|
||||
@@ -32,20 +33,21 @@ export type AppEntryError = {
|
||||
error?: Error | ClientError | string;
|
||||
}
|
||||
|
||||
export const fetchAppEntryData = async (serverUrl: string, initialTeamId: string): Promise<AppEntryData | AppEntryError> => {
|
||||
export const fetchAppEntryData = async (serverUrl: string, since: number, initialTeamId: string): Promise<AppEntryData | AppEntryError> => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const lastDisconnected = await queryWebSocketLastDisconnected(database);
|
||||
const includeDeletedChannels = true;
|
||||
const fetchOnly = true;
|
||||
|
||||
await fetchConfigAndLicense(serverUrl);
|
||||
|
||||
// Fetch in parallel teams / team membership / channels for current team / user preferences / user
|
||||
const promises: [Promise<MyTeamsRequest>, Promise<MyChannelsRequest | undefined>, Promise<MyPreferencesRequest>, Promise<MyUserRequest>] = [
|
||||
fetchMyTeams(serverUrl, fetchOnly),
|
||||
initialTeamId ? fetchMyChannelsForTeam(serverUrl, initialTeamId, includeDeletedChannels, lastDisconnected, fetchOnly) : Promise.resolve(undefined),
|
||||
initialTeamId ? fetchMyChannelsForTeam(serverUrl, initialTeamId, includeDeletedChannels, since, fetchOnly) : Promise.resolve(undefined),
|
||||
fetchMyPreferences(serverUrl, fetchOnly),
|
||||
fetchMe(serverUrl, fetchOnly),
|
||||
];
|
||||
@@ -63,7 +65,7 @@ export const fetchAppEntryData = async (serverUrl: string, initialTeamId: string
|
||||
const myTeams = teamData.teams!.filter((t) => teamMembers?.includes(t.id));
|
||||
const defaultTeam = selectDefaultTeam(myTeams, meData.user?.locale || DEFAULT_LOCALE, teamOrderPreference, config.ExperimentalPrimaryTeam);
|
||||
if (defaultTeam?.id) {
|
||||
chData = await fetchMyChannelsForTeam(serverUrl, defaultTeam.id, includeDeletedChannels, lastDisconnected, fetchOnly);
|
||||
chData = await fetchMyChannelsForTeam(serverUrl, defaultTeam.id, includeDeletedChannels, since, fetchOnly);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +104,7 @@ export const fetchAppEntryData = async (serverUrl: string, initialTeamId: string
|
||||
}
|
||||
|
||||
const availableTeamIds = await queryAvailableTeamIds(database, initialTeamId, teamData.teams, prefData.preferences, meData.user?.locale);
|
||||
const alternateTeamData = await fetchAlternateTeamData(serverUrl, availableTeamIds, removeTeamIds, includeDeletedChannels, lastDisconnected, fetchOnly);
|
||||
const alternateTeamData = await fetchAlternateTeamData(serverUrl, availableTeamIds, removeTeamIds, includeDeletedChannels, since, fetchOnly);
|
||||
|
||||
data = {
|
||||
...data,
|
||||
@@ -156,7 +158,7 @@ export const fetchAlternateTeamData = async (
|
||||
};
|
||||
|
||||
export const deferredAppEntryActions = async (
|
||||
serverUrl: string, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined,
|
||||
serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined,
|
||||
config: ClientConfig, license: ClientLicense, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined,
|
||||
initialTeamId?: string, initialChannelId?: string) => {
|
||||
// defer fetching posts for initial channel
|
||||
@@ -179,11 +181,13 @@ export const deferredAppEntryActions = async (
|
||||
|
||||
// defer groups for team
|
||||
if (initialTeamId) {
|
||||
await fetchGroupsForTeam(serverUrl, initialTeamId);
|
||||
fetchGroupsForTeam(serverUrl, initialTeamId, since);
|
||||
}
|
||||
|
||||
// defer fetch channels and unread posts for other teams
|
||||
if (teamData.teams?.length && teamData.memberships?.length) {
|
||||
fetchTeamsChannelsAndUnreadPosts(serverUrl, teamData.teams, teamData.memberships, initialTeamId);
|
||||
await fetchTeamsChannelsAndUnreadPosts(serverUrl, since, teamData.teams, teamData.memberships, initialTeamId);
|
||||
}
|
||||
|
||||
updateAllUsersSince(serverUrl, since);
|
||||
};
|
||||
|
||||
@@ -158,7 +158,7 @@ export const loginEntry = async ({serverUrl, user, deviceToken}: AfterLoginArgs)
|
||||
|
||||
const config = clData.config || {} as ClientConfig;
|
||||
const license = clData.license || {} as ClientLicense;
|
||||
deferredAppEntryActions(serverUrl, user.id, user.locale, prefData.preferences, config, license, teamData, chData, initialTeam?.id, initialChannel?.id);
|
||||
deferredAppEntryActions(serverUrl, 0, user.id, user.locale, prefData.preferences, config, license, teamData, chData, initialTeam?.id, initialChannel?.id);
|
||||
|
||||
const error = clData.error || prefData.error || teamData.error || chData?.error;
|
||||
return {error, time: Date.now() - dt, hasTeams: Boolean((myTeams?.length || 0) > 0 && !teamData.error)};
|
||||
|
||||
@@ -10,7 +10,7 @@ import {Screens} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {queryChannelsById, queryDefaultChannelForTeam, queryMyChannel} from '@queries/servers/channel';
|
||||
import {prepareModels} from '@queries/servers/entry';
|
||||
import {queryCommonSystemValues, queryCurrentTeamId, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {queryCommonSystemValues, queryCurrentTeamId, queryWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {deleteMyTeams, queryMyTeamById, queryTeamsById} from '@queries/servers/team';
|
||||
import {queryCurrentUser} from '@queries/servers/user';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
@@ -30,6 +30,7 @@ export const pushNotificationEntry = async (serverUrl: string, notification: Not
|
||||
const isTabletDevice = await isTablet();
|
||||
const {database} = operator;
|
||||
const currentTeamId = await queryCurrentTeamId(database);
|
||||
const lastDisconnectedAt = await queryWebSocketLastDisconnected(database);
|
||||
const currentServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
let isDirectChannel = false;
|
||||
|
||||
@@ -55,7 +56,7 @@ export const pushNotificationEntry = async (serverUrl: string, notification: Not
|
||||
await switchToChannel(serverUrl, channelId, teamId);
|
||||
}
|
||||
|
||||
const fetchedData = await fetchAppEntryData(serverUrl, teamId);
|
||||
const fetchedData = await fetchAppEntryData(serverUrl, lastDisconnectedAt, teamId);
|
||||
const fetchedError = (fetchedData as AppEntryError).error;
|
||||
|
||||
if (fetchedError) {
|
||||
@@ -63,6 +64,7 @@ export const pushNotificationEntry = async (serverUrl: string, notification: Not
|
||||
}
|
||||
|
||||
const {initialTeamId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds} = fetchedData as AppEntryData;
|
||||
const rolesData = await fetchRoles(serverUrl, teamData?.memberships, chData?.memberships, meData?.user, true);
|
||||
|
||||
// There is a chance that after the above request returns
|
||||
// the user is no longer part of the team or channel
|
||||
@@ -122,14 +124,16 @@ export const pushNotificationEntry = async (serverUrl: string, notification: Not
|
||||
await deleteMyTeams(operator, removeTeams!);
|
||||
}
|
||||
|
||||
fetchRoles(serverUrl, teamData?.memberships, chData?.memberships, meData?.user);
|
||||
|
||||
let removeChannels;
|
||||
if (removeChannelIds?.length) {
|
||||
removeChannels = await queryChannelsById(operator.database, removeChannelIds);
|
||||
}
|
||||
|
||||
const modelPromises = await prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData});
|
||||
if (rolesData.roles?.length) {
|
||||
modelPromises.push(operator.handleRole({roles: rolesData.roles, prepareRecordsOnly: true}));
|
||||
}
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models.flat() as Model[]);
|
||||
@@ -138,7 +142,7 @@ export const pushNotificationEntry = async (serverUrl: string, notification: Not
|
||||
const {id: currentUserId, locale: currentUserLocale} = meData.user || (await queryCurrentUser(operator.database))!;
|
||||
const {config, license} = await queryCommonSystemValues(operator.database);
|
||||
|
||||
deferredAppEntryActions(serverUrl, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, selectedTeamId, selectedChannelId);
|
||||
deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, selectedTeamId, selectedChannelId);
|
||||
const error = teamData.error || chData?.error || prefData.error || meData.error;
|
||||
return {error, userId: meData?.user?.id};
|
||||
};
|
||||
|
||||
@@ -5,12 +5,12 @@ import {Model} from '@nozbe/watermelondb';
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@init/network_manager';
|
||||
import {queryCommonSystemValues, queryWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
import {queryCommonSystemValues} from '@queries/servers/system';
|
||||
import {queryTeamById} from '@queries/servers/team';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
export const fetchGroupsForTeam = async (serverUrl: string, teamId: string) => {
|
||||
export const fetchGroupsForTeam = async (serverUrl: string, teamId: string, since: number) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
@@ -59,7 +59,6 @@ export const fetchGroupsForTeam = async (serverUrl: string, teamId: string) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const since = await queryWebSocketLastDisconnected(database);
|
||||
const [groupsAssociatedToChannelsInTeam, allGroups]: [{groups: Record<string, Group[]>}, Group[]] = await Promise.all([
|
||||
client.getAllGroupsAssociatedToChannelsInTeam(teamId, true),
|
||||
client.getGroups(true, 0, 0, since),
|
||||
|
||||
@@ -56,7 +56,7 @@ export const fetchRolesIfNeeded = async (serverUrl: string, updatedRoles: string
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchRoles = async (serverUrl: string, teamMembership?: TeamMembership[], channelMembership?: ChannelMembership[], user?: UserProfile) => {
|
||||
export const fetchRoles = async (serverUrl: string, teamMembership?: TeamMembership[], channelMembership?: ChannelMembership[], user?: UserProfile, fetchOnly = false) => {
|
||||
const rolesToFetch = new Set<string>(user?.roles.split(' ') || []);
|
||||
|
||||
if (teamMembership?.length) {
|
||||
@@ -79,6 +79,8 @@ export const fetchRoles = async (serverUrl: string, teamMembership?: TeamMembers
|
||||
}
|
||||
|
||||
if (rolesToFetch.size > 0) {
|
||||
fetchRolesIfNeeded(serverUrl, Array.from(rolesToFetch));
|
||||
return fetchRolesIfNeeded(serverUrl, Array.from(rolesToFetch), fetchOnly);
|
||||
}
|
||||
|
||||
return {roles: []};
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import {Events} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@init/network_manager';
|
||||
import {prepareMyChannelsForTeam, queryDefaultChannelForTeam} from '@queries/servers/channel';
|
||||
import {prepareCommonSystemValues, queryCurrentTeamId, queryWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
import {prepareCommonSystemValues, queryCurrentTeamId} from '@queries/servers/system';
|
||||
import {addTeamToTeamHistory, prepareDeleteTeam, prepareMyTeams, queryNthLastChannelFromTeam, queryTeamsById, syncTeamTable} from '@queries/servers/team';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
|
||||
@@ -191,14 +191,13 @@ export const fetchAllTeams = async (serverUrl: string, fetchOnly = false): Promi
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchTeamsChannelsAndUnreadPosts = async (serverUrl: string, teams: Team[], memberships: TeamMembership[], excludeTeamId?: string) => {
|
||||
export const fetchTeamsChannelsAndUnreadPosts = async (serverUrl: string, since: number, teams: Team[], memberships: TeamMembership[], excludeTeamId?: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const myTeams = teams.filter((t) => memberships.find((m) => m.team_id === t.id && t.id !== excludeTeamId));
|
||||
const since = await queryWebSocketLastDisconnected(database);
|
||||
|
||||
for await (const team of myTeams) {
|
||||
const {channels, memberships: members} = await fetchMyChannelsForTeam(serverUrl, team.id, since > 0, since, false, true);
|
||||
|
||||
@@ -3,19 +3,22 @@
|
||||
|
||||
import {Model, Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {updateChannelsDisplayName} from '@actions/local/channel';
|
||||
import {updateRecentCustomStatuses, updateLocalUser} from '@actions/local/user';
|
||||
import {fetchRolesIfNeeded} from '@actions/remote/role';
|
||||
import {Database, General} from '@constants';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {debounce} from '@helpers/api/general';
|
||||
import NetworkManager from '@init/network_manager';
|
||||
import {queryCurrentUserId, queryWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
import {queryCurrentUserId} from '@queries/servers/system';
|
||||
import {prepareUsers, queryAllUsers, queryCurrentUser, queryUsersById, queryUsersByUsername} from '@queries/servers/user';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
export type MyUserRequest = {
|
||||
@@ -34,6 +37,8 @@ export type ProfilesInChannelRequest = {
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
const {SERVER: {CHANNEL}} = MM_TABLES;
|
||||
|
||||
export const fetchMe = async (serverUrl: string, fetchOnly = false): Promise<MyUserRequest> => {
|
||||
let client;
|
||||
try {
|
||||
@@ -332,31 +337,49 @@ export const fetchMissingProfilesByUsernames = async (serverUrl: string, usernam
|
||||
}
|
||||
};
|
||||
|
||||
export const updateAllUsersSinceLastDisconnect = async (serverUrl: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
export const updateAllUsersSince = async (serverUrl: string, since: number, fetchOnly = false) => {
|
||||
if (!since) {
|
||||
return {users: []};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const lastDisconnectedAt = await queryWebSocketLastDisconnected(database.database);
|
||||
|
||||
if (!lastDisconnectedAt) {
|
||||
return {users: []};
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
const currentUserId = await queryCurrentUserId(database.database);
|
||||
const users = await queryAllUsers(database.database);
|
||||
|
||||
const currentUserId = await queryCurrentUserId(operator.database);
|
||||
const users = await queryAllUsers(operator.database);
|
||||
const userIds = users.map((u) => u.id).filter((id) => id !== currentUserId);
|
||||
let userUpdates: UserProfile[] = [];
|
||||
try {
|
||||
userUpdates = await NetworkManager.getClient(serverUrl).getProfilesByIds(userIds, {since: lastDisconnectedAt});
|
||||
userUpdates = await client.getProfilesByIds(userIds, {since});
|
||||
if (userUpdates.length && !fetchOnly) {
|
||||
const modelsToBatch: Model[] = [];
|
||||
const userModels = await operator.handleUsers({users: userUpdates, prepareRecordsOnly: true});
|
||||
modelsToBatch.push(...userModels);
|
||||
const directChannels = await operator.database.get<ChannelModel>(CHANNEL).
|
||||
query(Q.where('type', Q.oneOf([General.DM_CHANNEL, General.GM_CHANNEL]))).
|
||||
fetch();
|
||||
const {models} = await updateChannelsDisplayName(serverUrl, directChannels, userUpdates, true);
|
||||
if (models?.length) {
|
||||
modelsToBatch.push(...models);
|
||||
}
|
||||
|
||||
if (modelsToBatch.length) {
|
||||
await operator.batchRecords(modelsToBatch);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
if (userUpdates.length) {
|
||||
database.operator.handleUsers({users: userUpdates, prepareRecordsOnly: false});
|
||||
}
|
||||
|
||||
return {users: userUpdates};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {fetchMissingSidebarInfo, fetchMyChannelsForTeam} from '@actions/remote/channel';
|
||||
import {fetchPostsSince} from '@actions/remote/post';
|
||||
import {fetchMyPreferences} from '@actions/remote/preference';
|
||||
import {fetchMissingSidebarInfo, switchToChannelById} from '@actions/remote/channel';
|
||||
import {AppEntryData, AppEntryError, fetchAppEntryData} from '@actions/remote/entry/common';
|
||||
import {fetchGroupsForTeam} from '@actions/remote/group';
|
||||
import {fetchPostsForUnreadChannels, fetchPostsSince} from '@actions/remote/post';
|
||||
import {fetchRoles} from '@actions/remote/role';
|
||||
import {fetchConfigAndLicense} from '@actions/remote/systems';
|
||||
import {fetchAllTeams, fetchMyTeams} from '@actions/remote/team';
|
||||
import {fetchMe, updateAllUsersSinceLastDisconnect} from '@actions/remote/user';
|
||||
import {fetchAllTeams, fetchTeamsChannelsAndUnreadPosts} from '@actions/remote/team';
|
||||
import {updateAllUsersSince} from '@actions/remote/user';
|
||||
import {General, WebsocketEvents} from '@constants';
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import Events from '@constants/events';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
|
||||
import {prepareMyChannelsForTeam} from '@queries/servers/channel';
|
||||
import {queryCommonSystemValues, queryConfig, queryWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
import {queryCurrentUser} from '@queries/servers/user';
|
||||
import {queryChannelsById, queryDefaultChannelForTeam} from '@queries/servers/channel';
|
||||
import {prepareModels} from '@queries/servers/entry';
|
||||
import {queryCommonSystemValues, queryConfig, queryCurrentChannelId, queryWebSocketLastDisconnected, resetWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {deleteMyTeams, queryTeamsById} from '@queries/servers/team';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
|
||||
import {handleChannelDeletedEvent, handleUserAddedToChannelEvent, handleUserRemovedFromChannelEvent} from './channel';
|
||||
import {handleNewPostEvent, handlePostDeleted, handlePostEdited, handlePostUnread} from './posts';
|
||||
@@ -27,21 +27,25 @@ import {handleUserRoleUpdatedEvent, handleTeamMemberRoleUpdatedEvent, handleRole
|
||||
import {handleLeaveTeamEvent, handleUserAddedToTeamEvent, handleUpdateTeamEvent} from './teams';
|
||||
import {handleUserUpdatedEvent, handleUserTypingEvent} from './users';
|
||||
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
// ESR: 5.37
|
||||
const alreadyConnected = new Set<string>();
|
||||
|
||||
export async function handleFirstConnect(serverUrl: string) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
const config = await queryConfig(database);
|
||||
const lastDisconnect = await queryWebSocketLastDisconnected(database);
|
||||
if (lastDisconnect && config.EnableReliableWebSockets !== 'true') {
|
||||
doReconnect(serverUrl);
|
||||
const config = await queryConfig(operator.database);
|
||||
const lastDisconnect = await queryWebSocketLastDisconnected(operator.database);
|
||||
|
||||
// ESR: 5.37
|
||||
if (lastDisconnect && config.EnableReliableWebSockets !== 'true' && alreadyConnected.has(serverUrl)) {
|
||||
handleReconnect(serverUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
doFirstConnect(serverUrl);
|
||||
alreadyConnected.add(serverUrl);
|
||||
resetWebSocketLastDisconnected(operator);
|
||||
}
|
||||
|
||||
export function handleReconnect(serverUrl: string) {
|
||||
@@ -64,90 +68,126 @@ export async function handleClose(serverUrl: string, lastDisconnect: number) {
|
||||
});
|
||||
}
|
||||
|
||||
function doFirstConnect(serverUrl: string) {
|
||||
updateAllUsersSinceLastDisconnect(serverUrl);
|
||||
}
|
||||
|
||||
async function doReconnect(serverUrl: string) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const system = await queryCommonSystemValues(database.database);
|
||||
const lastDisconnectedAt = await queryWebSocketLastDisconnected(database.database);
|
||||
const {database} = operator;
|
||||
const tabletDevice = await isTablet();
|
||||
const system = await queryCommonSystemValues(database);
|
||||
const lastDisconnectedAt = await queryWebSocketLastDisconnected(database);
|
||||
|
||||
// TODO consider fetch only and batch all the results.
|
||||
await fetchMe(serverUrl);
|
||||
await fetchMyPreferences(serverUrl);
|
||||
const {config} = await fetchConfigAndLicense(serverUrl);
|
||||
const {memberships: teamMemberships, error: teamMembershipsError} = await fetchMyTeams(serverUrl);
|
||||
const {currentChannelId, currentUserId, currentTeamId, license} = system;
|
||||
const currentTeamMembership = teamMemberships?.find((tm) => tm.team_id === currentTeamId && tm.delete_at === 0);
|
||||
|
||||
let channelMemberships: ChannelMembership[] | undefined;
|
||||
if (currentTeamMembership) {
|
||||
const {memberships, channels, error} = await fetchMyChannelsForTeam(serverUrl, currentTeamMembership.team_id, true, lastDisconnectedAt);
|
||||
if (error) {
|
||||
DeviceEventEmitter.emit(Events.TEAM_LOAD_ERROR, serverUrl, error);
|
||||
return;
|
||||
}
|
||||
const currentUser = await queryCurrentUser(database.database);
|
||||
const preferences = currentUser ? (await currentUser.preferences.fetch()) : [];
|
||||
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], system.config, license);
|
||||
const directChannels = channels?.filter((c) => c.type === General.DM_CHANNEL || c.type === General.GM_CHANNEL);
|
||||
if (directChannels?.length) {
|
||||
await fetchMissingSidebarInfo(serverUrl, directChannels, currentUser?.locale, teammateDisplayNameSetting, currentUserId);
|
||||
}
|
||||
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
const prepare = await prepareMyChannelsForTeam(database.operator, currentTeamMembership.team_id, channels!, memberships!);
|
||||
if (prepare) {
|
||||
modelPromises.push(...prepare);
|
||||
}
|
||||
if (modelPromises.length) {
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat();
|
||||
if (flattenedModels?.length > 0) {
|
||||
try {
|
||||
await database.operator.batchRecords(flattenedModels);
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FAILED TO BATCH CHANNELS');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
channelMemberships = memberships;
|
||||
|
||||
if (currentChannelId) {
|
||||
const stillMemberOfCurrentChannel = memberships?.find((cm) => cm.channel_id === currentChannelId);
|
||||
const channelStillExist = channels?.find((c) => c.id === currentChannelId);
|
||||
const viewArchivedChannels = config?.ExperimentalViewArchivedChannels === 'true';
|
||||
|
||||
if (!stillMemberOfCurrentChannel) {
|
||||
handleUserRemovedFromChannelEvent(serverUrl, {data: {user_id: currentUserId, channel_id: currentChannelId}});
|
||||
} else if (!channelStillExist ||
|
||||
(!viewArchivedChannels && channelStillExist.delete_at !== 0)
|
||||
) {
|
||||
handleChannelDeletedEvent(serverUrl, {data: {user_id: currentUserId, channel_id: currentChannelId}} as WebSocketMessage);
|
||||
} else {
|
||||
// TODO Differentiate between post and thread, to fetch the thread posts
|
||||
fetchPostsSince(serverUrl, currentChannelId, lastDisconnectedAt);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Consider global thread screen to update global threads
|
||||
} else if (!teamMembershipsError) {
|
||||
handleLeaveTeamEvent(serverUrl, {data: {user_id: currentUserId, team_id: currentTeamId}} as WebSocketMessage);
|
||||
resetWebSocketLastDisconnected(operator);
|
||||
let {config, license} = await fetchConfigAndLicense(serverUrl);
|
||||
if (!config) {
|
||||
config = system.config;
|
||||
}
|
||||
|
||||
if (!license) {
|
||||
license = system.license;
|
||||
}
|
||||
|
||||
const fetchedData = await fetchAppEntryData(serverUrl, lastDisconnectedAt, system.currentTeamId);
|
||||
const fetchedError = (fetchedData as AppEntryError).error;
|
||||
|
||||
if (fetchedError) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {initialTeamId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds} = fetchedData as AppEntryData;
|
||||
const rolesData = await fetchRoles(serverUrl, teamData.memberships, chData?.memberships, meData.user, true);
|
||||
|
||||
if (chData?.channels?.length) {
|
||||
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(prefData.preferences || [], config, license);
|
||||
let directChannels: Channel[];
|
||||
[chData.channels, directChannels] = chData.channels.reduce(([others, direct], c: Channel) => {
|
||||
if (c.type === General.DM_CHANNEL || c.type === General.GM_CHANNEL) {
|
||||
direct.push(c);
|
||||
} else {
|
||||
others.push(c);
|
||||
}
|
||||
|
||||
return [others, direct];
|
||||
}, [[], []] as Channel[][]);
|
||||
|
||||
if (directChannels.length) {
|
||||
await fetchMissingSidebarInfo(serverUrl, directChannels, meData.user?.locale, teammateDisplayNameSetting, system.currentUserId, true);
|
||||
chData.channels.push(...directChannels);
|
||||
}
|
||||
}
|
||||
|
||||
// if no longer a member of the current team
|
||||
if (initialTeamId !== system.currentTeamId) {
|
||||
let cId = '';
|
||||
if (tabletDevice) {
|
||||
if (!cId) {
|
||||
const channel = await queryDefaultChannelForTeam(database, initialTeamId);
|
||||
if (channel) {
|
||||
cId = channel.id;
|
||||
}
|
||||
}
|
||||
switchToChannelById(serverUrl, cId, initialTeamId);
|
||||
} else {
|
||||
setCurrentTeamAndChannelId(operator, initialTeamId, cId);
|
||||
}
|
||||
}
|
||||
|
||||
let removeTeams;
|
||||
if (removeTeamIds?.length) {
|
||||
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
|
||||
removeTeams = await queryTeamsById(database, removeTeamIds);
|
||||
await deleteMyTeams(operator, removeTeams!);
|
||||
}
|
||||
|
||||
let removeChannels;
|
||||
if (removeChannelIds?.length) {
|
||||
removeChannels = await queryChannelsById(database, removeChannelIds);
|
||||
}
|
||||
|
||||
const modelPromises = await prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData});
|
||||
if (rolesData.roles?.length) {
|
||||
modelPromises.push(operator.handleRole({roles: rolesData.roles, prepareRecordsOnly: true}));
|
||||
}
|
||||
|
||||
if (modelPromises.length) {
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat();
|
||||
if (flattenedModels?.length > 0) {
|
||||
try {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FAILED TO BATCH WS reconnection');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const currentChannelId = await queryCurrentChannelId(database);
|
||||
if (currentChannelId) {
|
||||
// https://mattermost.atlassian.net/browse/MM-40098
|
||||
fetchPostsSince(serverUrl, currentChannelId, lastDisconnectedAt);
|
||||
|
||||
// defer fetching posts for unread channels on initial team
|
||||
if (chData?.channels && chData.memberships) {
|
||||
fetchPostsForUnreadChannels(serverUrl, chData.channels, chData.memberships, currentChannelId);
|
||||
}
|
||||
}
|
||||
|
||||
if (initialTeamId) {
|
||||
fetchGroupsForTeam(serverUrl, initialTeamId, lastDisconnectedAt);
|
||||
}
|
||||
|
||||
// defer fetch channels and unread posts for other teams
|
||||
if (teamData.teams?.length && teamData.memberships?.length) {
|
||||
await fetchTeamsChannelsAndUnreadPosts(serverUrl, lastDisconnectedAt, teamData.teams, teamData.memberships, initialTeamId);
|
||||
}
|
||||
|
||||
fetchRoles(serverUrl, teamMemberships, channelMemberships);
|
||||
fetchAllTeams(serverUrl);
|
||||
updateAllUsersSince(serverUrl, lastDisconnectedAt);
|
||||
|
||||
// TODO Fetch App bindings?
|
||||
|
||||
updateAllUsersSinceLastDisconnect(serverUrl);
|
||||
// https://mattermost.atlassian.net/browse/MM-41520
|
||||
}
|
||||
|
||||
export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
import {Model, Q} from '@nozbe/watermelondb';
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {updateChannelsDisplayName} from '@actions/local/channel';
|
||||
@@ -21,71 +21,87 @@ import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const {SERVER: {CHANNEL, CHANNEL_MEMBERSHIP}} = MM_TABLES;
|
||||
|
||||
export async function handleUserUpdatedEvent(serverUrl: string, msg: any) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
export async function handleUserUpdatedEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
const currentUser = await queryCurrentUser(database.database);
|
||||
|
||||
const {database} = operator;
|
||||
const currentUser = await queryCurrentUser(database);
|
||||
if (!currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const user: UserProfile = msg.data.user;
|
||||
const modelsToBatch: Model[] = [];
|
||||
let userToSave = user;
|
||||
|
||||
if (user.id === currentUser.id) {
|
||||
if (user.update_at > (currentUser?.updateAt || 0)) {
|
||||
// Need to request me to make sure we don't override with sanitized fields from the
|
||||
// websocket event
|
||||
// TODO Potential improvement https://mattermost.atlassian.net/browse/MM-40582
|
||||
fetchMe(serverUrl, false);
|
||||
// ESR: 6.5
|
||||
if (!user.notify_props || !Object.keys(user.notify_props).length) {
|
||||
// Current user is sanitized so we fetch it from the server
|
||||
// Need to request me to make sure we don't override with sanitized fields from the
|
||||
// websocket event
|
||||
const me = await fetchMe(serverUrl, true);
|
||||
if (me.user) {
|
||||
userToSave = me.user;
|
||||
}
|
||||
}
|
||||
|
||||
// Update GMs display name if locale has changed
|
||||
if (user.locale !== currentUser.locale) {
|
||||
const channels = await database.database.get<ChannelModel>(CHANNEL).query(
|
||||
const channels = await database.get<ChannelModel>(CHANNEL).query(
|
||||
Q.where('type', Q.eq(General.GM_CHANNEL))).fetch();
|
||||
const {models} = await updateChannelsDisplayName(serverUrl, channels, user, true);
|
||||
if (models?.length) {
|
||||
database.operator.batchRecords(models);
|
||||
if (channels.length) {
|
||||
const {models} = await updateChannelsDisplayName(serverUrl, channels, [user], true);
|
||||
if (models?.length) {
|
||||
modelsToBatch.push(...models);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
database.operator.handleUsers({users: [user], prepareRecordsOnly: false});
|
||||
|
||||
const channels = await database.database.get<ChannelModel>(CHANNEL).query(
|
||||
const channels = await database.get<ChannelModel>(CHANNEL).query(
|
||||
Q.where('type', Q.oneOf([General.DM_CHANNEL, General.GM_CHANNEL])),
|
||||
Q.on(CHANNEL_MEMBERSHIP, Q.where('user_id', user.id))).fetch();
|
||||
if (!channels?.length) {
|
||||
return;
|
||||
if (channels.length) {
|
||||
const {models} = await updateChannelsDisplayName(serverUrl, channels, [user], true);
|
||||
if (models?.length) {
|
||||
modelsToBatch.push(...models);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const {models} = await updateChannelsDisplayName(serverUrl, channels, user, true);
|
||||
const userModel = await operator.handleUsers({users: [userToSave], prepareRecordsOnly: true});
|
||||
modelsToBatch.push(...userModel);
|
||||
|
||||
if (models?.length) {
|
||||
database.operator.batchRecords(models);
|
||||
}
|
||||
try {
|
||||
await operator.batchRecords(modelsToBatch);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUserTypingEvent(serverUrl: string, msg: any) {
|
||||
export async function handleUserTypingEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
const currentServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
if (currentServerUrl === serverUrl) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {config, license} = await queryCommonSystemValues(database.database);
|
||||
const {config, license} = await queryCommonSystemValues(database);
|
||||
|
||||
let user: UserModel | UserProfile | undefined = await queryUserById(database.database, msg.data.user_id);
|
||||
let user: UserModel | UserProfile | undefined = await queryUserById(database, msg.data.user_id);
|
||||
if (!user) {
|
||||
const {users} = await fetchUsersByIds(serverUrl, [msg.data.user_id]);
|
||||
user = users?.[0];
|
||||
}
|
||||
const namePreference = await queryPreferencesByCategoryAndName(database.database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT);
|
||||
const teammateDisplayNameSetting = await getTeammateNameDisplaySetting(namePreference, config, license);
|
||||
const currentUser = await queryCurrentUser(database.database);
|
||||
const namePreference = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT);
|
||||
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(namePreference, config, license);
|
||||
const currentUser = await queryCurrentUser(database);
|
||||
const username = displayUsername(user, currentUser?.locale, teammateDisplayNameSetting);
|
||||
const data = {
|
||||
channelId: msg.broadcast.channel_id,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {Events} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
import {Analytics, create} from '@init/analytics';
|
||||
import {setServerCredentials} from '@init/credentials';
|
||||
import {semverFromServerVersion} from '@utils/supported_server';
|
||||
|
||||
import * as ClientConstants from './constants';
|
||||
import ClientError from './error';
|
||||
@@ -234,7 +235,7 @@ export default class ClientBase {
|
||||
}
|
||||
|
||||
const headers: ClientHeaders = response.headers || {};
|
||||
const serverVersion = headers[ClientConstants.HEADER_X_VERSION_ID] || headers[ClientConstants.HEADER_X_VERSION_ID.toLowerCase()];
|
||||
const serverVersion = semverFromServerVersion(headers[ClientConstants.HEADER_X_VERSION_ID] || headers[ClientConstants.HEADER_X_VERSION_ID.toLowerCase()]);
|
||||
const hasCacheControl = Boolean(headers[ClientConstants.HEADER_CACHE_CONTROL] || headers[ClientConstants.HEADER_CACHE_CONTROL.toLowerCase()]);
|
||||
if (serverVersion && !hasCacheControl && this.serverVersion !== serverVersion) {
|
||||
this.serverVersion = serverVersion;
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('Client', () => {
|
||||
|
||||
await client.getMe();
|
||||
|
||||
assert.equal(client.serverVersion, '5.0.0.5.0.0.abc123');
|
||||
assert.equal(client.serverVersion, '5.0.0');
|
||||
assert.equal(isMinimumServerVersion(client.serverVersion, 5, 0, 0), true);
|
||||
assert.equal(isMinimumServerVersion(client.serverVersion, 5, 1, 0), false);
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('Client', () => {
|
||||
|
||||
await client.getMe();
|
||||
|
||||
assert.equal(client.serverVersion, '5.3.0.5.3.0.abc123');
|
||||
assert.equal(client.serverVersion, '5.3.0');
|
||||
assert.equal(isMinimumServerVersion(client.serverVersion, 5, 0, 0), true);
|
||||
assert.equal(isMinimumServerVersion(client.serverVersion, 5, 1, 0), true);
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import {Alert, DeviceEventEmitter, Linking, Platform} from 'react-native';
|
||||
import semver from 'semver';
|
||||
|
||||
import {selectAllMyChannelIds} from '@actions/local/channel';
|
||||
import {fetchConfigAndLicense} from '@actions/remote/systems';
|
||||
import LocalConfig from '@assets/config.json';
|
||||
import {Events, Sso} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
@@ -132,12 +131,6 @@ class GlobalEventHandler {
|
||||
{cancelable: false},
|
||||
);
|
||||
}
|
||||
|
||||
const fetchTimeout = setTimeout(() => {
|
||||
// Defer the call to avoid collision with other request writting to the db
|
||||
fetchConfigAndLicense(serverUrl);
|
||||
clearTimeout(fetchTimeout);
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import NetInfo, {NetInfoState} from '@react-native-community/netinfo';
|
||||
import {AppState, AppStateStatus} from 'react-native';
|
||||
import {AppState, AppStateStatus, DeviceEventEmitter, Platform} from 'react-native';
|
||||
|
||||
import {setCurrentUserStatusOffline} from '@actions/local/user';
|
||||
import {fetchStatusByIds} from '@actions/remote/user';
|
||||
@@ -10,7 +10,7 @@ import {handleClose, handleEvent, handleFirstConnect, handleReconnect} from '@ac
|
||||
import WebSocketClient from '@client/websocket';
|
||||
import {General} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {queryCurrentUserId, resetWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
import {queryCurrentUserId} from '@queries/servers/system';
|
||||
import {queryAllUsers} from '@queries/servers/user';
|
||||
|
||||
import type {ServerCredential} from '@typings/credentials';
|
||||
@@ -34,7 +34,7 @@ class WebsocketManager {
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
await resetWebSocketLastDisconnected(operator);
|
||||
|
||||
try {
|
||||
this.createClient(serverUrl, token, 0);
|
||||
} catch (error) {
|
||||
@@ -46,6 +46,12 @@ class WebsocketManager {
|
||||
|
||||
AppState.addEventListener('change', this.onAppStateChange);
|
||||
NetInfo.addEventListener(this.onNetStateChange);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
DeviceEventEmitter.addListener('windowFocusChanged', ({appState}: {appState: AppStateStatus}) => {
|
||||
this.onAppStateChange(appState);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public invalidateClient = (serverUrl: string) => {
|
||||
|
||||
@@ -118,12 +118,18 @@ export const queryWebSocketLastDisconnected = async (serverDatabase: Database) =
|
||||
}
|
||||
};
|
||||
|
||||
export const resetWebSocketLastDisconnected = (operator: ServerDataOperator) => {
|
||||
return operator.handleSystem({systems: [{
|
||||
id: SYSTEM_IDENTIFIERS.WEBSOCKET,
|
||||
value: 0,
|
||||
}],
|
||||
prepareRecordsOnly: false});
|
||||
export const resetWebSocketLastDisconnected = async (operator: ServerDataOperator, prepareRecordsOnly = false) => {
|
||||
const lastDisconnectedAt = await queryWebSocketLastDisconnected(operator.database);
|
||||
|
||||
if (lastDisconnectedAt) {
|
||||
return operator.handleSystem({systems: [{
|
||||
id: SYSTEM_IDENTIFIERS.WEBSOCKET,
|
||||
value: 0,
|
||||
}],
|
||||
prepareRecordsOnly});
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const queryTeamHistory = async (serverDatabase: Database) => {
|
||||
|
||||
@@ -14,6 +14,20 @@ export function unsupportedServer(isSystemAdmin: boolean, intl: IntlShape) {
|
||||
return unsupportedServerAlert(intl);
|
||||
}
|
||||
|
||||
export function semverFromServerVersion(value: string) {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const split = value.split('.');
|
||||
|
||||
const major = parseInt(split[0], 10);
|
||||
const minor = parseInt(split[1] || '0', 10);
|
||||
const patch = parseInt(split[2] || '0', 10);
|
||||
|
||||
return `${major}.${minor}.${patch}`;
|
||||
}
|
||||
|
||||
function unsupportedServerAdminAlert(intl: IntlShape) {
|
||||
const title = intl.formatMessage({id: 'mobile.server_upgrade.title', defaultMessage: 'Server upgrade required'});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user