UNPKG

mattermost-redux

Version:

Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client

1,580 lines (1,357 loc) 48.2 kB
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {Action, ActionFunc, ActionResult, batchActions, DispatchFunc, GetStateFunc} from 'types/actions'; import {UserProfile, UserStatus, GetFilteredUsersStatsOpts, UsersStats, UserCustomStatus} from 'types/users'; import {TeamMembership} from 'types/teams'; import {Client4} from 'client'; import {General} from '../constants'; import {UserTypes, TeamTypes, AdminTypes} from 'action_types'; import {getAllCustomEmojis} from './emojis'; import {getClientConfig, setServerVersion} from './general'; import {getMyTeams, getMyTeamMembers, getMyTeamUnreads} from './teams'; import {loadRolesIfNeeded} from './roles'; import {getUserIdFromChannelName, isDirectChannel, isDirectChannelVisible, isGroupChannel, isGroupChannelVisible} from 'utils/channel_utils'; import {removeUserFromList} from 'utils/user_utils'; import {isMinimumServerVersion} from 'utils/helpers'; import {getConfig, getServerVersion} from 'selectors/entities/general'; import {getCurrentUserId, getUsers} from 'selectors/entities/users'; import {logError} from './errors'; import {bindClientFunc, forceLogoutIfNecessary, debounce} from './helpers'; import {getMyPreferences, makeDirectChannelVisibleIfNecessary, makeGroupMessageVisibleIfNecessary} from './preferences'; import {Dictionary} from 'types/utilities'; export function checkMfa(loginId: string): ActionFunc { return async (dispatch: DispatchFunc) => { dispatch({type: UserTypes.CHECK_MFA_REQUEST, data: null}); try { const data = await Client4.checkUserMfa(loginId); dispatch({type: UserTypes.CHECK_MFA_SUCCESS, data: null}); return {data: data.mfa_required}; } catch (error) { dispatch(batchActions([ {type: UserTypes.CHECK_MFA_FAILURE, error}, logError(error), ])); return {error}; } }; } export function generateMfaSecret(userId: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.generateMfaSecret, params: [ userId, ], }); } export function createUser(user: UserProfile, token: string, inviteId: string, redirect: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let created; try { created = await Client4.createUser(user, token, inviteId, redirect); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const profiles: { [userId: string]: UserProfile; } = { [created.id]: created, }; dispatch({type: UserTypes.RECEIVED_PROFILES, data: profiles}); return {data: created}; }; } export function login(loginId: string, password: string, mfaToken = '', ldapOnly = false): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { dispatch({type: UserTypes.LOGIN_REQUEST, data: null}); const deviceId = getState().entities.general.deviceToken; let data; try { data = await Client4.login(loginId, password, mfaToken, deviceId, ldapOnly); } catch (error) { dispatch(batchActions([ { type: UserTypes.LOGIN_FAILURE, error, }, logError(error), ])); return {error}; } return completeLogin(data)(dispatch, getState); }; } export function loginById(id: string, password: string, mfaToken = ''): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { dispatch({type: UserTypes.LOGIN_REQUEST, data: null}); const deviceId = getState().entities.general.deviceToken; let data; try { data = await Client4.loginById(id, password, mfaToken, deviceId); } catch (error) { dispatch(batchActions([ { type: UserTypes.LOGIN_FAILURE, error, }, logError(error), ])); return {error}; } return completeLogin(data)(dispatch, getState); }; } function completeLogin(data: UserProfile): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { dispatch({ type: UserTypes.RECEIVED_ME, data, }); Client4.setUserId(data.id); Client4.setUserRoles(data.roles); let teamMembers; try { const membersRequest: Promise<TeamMembership[]> = Client4.getMyTeamMembers(); const unreadsRequest = Client4.getMyTeamUnreads(); teamMembers = await membersRequest; const teamUnreads = await unreadsRequest; if (teamUnreads) { for (const u of teamUnreads) { const index = teamMembers.findIndex((m) => m.team_id === u.team_id); const member = teamMembers[index]; member.mention_count = u.mention_count; member.msg_count = u.msg_count; } } } catch (error) { dispatch(batchActions([ {type: UserTypes.LOGIN_FAILURE, error}, logError(error), ])); return {error}; } const promises = [ dispatch(getMyPreferences()), dispatch(getMyTeams()), dispatch(getClientConfig()), ]; const serverVersion = Client4.getServerVersion(); dispatch(setServerVersion(serverVersion)); if (!isMinimumServerVersion(serverVersion, 4, 7) && getConfig(getState()).EnableCustomEmoji === 'true') { dispatch(getAllCustomEmojis()); } try { await Promise.all(promises); } catch (error) { dispatch(batchActions([ {type: UserTypes.LOGIN_FAILURE, error}, logError(error), ])); return {error}; } dispatch(batchActions([ { type: TeamTypes.RECEIVED_MY_TEAM_MEMBERS, data: teamMembers, }, { type: UserTypes.LOGIN_SUCCESS, }, ])); const roles = new Set<string>(); for (const role of data.roles.split(' ')) { roles.add(role); } for (const teamMember of teamMembers) { for (const role of teamMember.roles.split(' ')) { roles.add(role); } } if (roles.size > 0) { dispatch(loadRolesIfNeeded(roles)); } return {data: true}; }; } export function loadMe(): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); const config = getConfig(state); const deviceId = state.entities.general.deviceToken; if (deviceId) { Client4.attachDevice(deviceId); } const promises = [ dispatch(getMe()), dispatch(getMyPreferences()), dispatch(getMyTeams()), dispatch(getMyTeamMembers()), dispatch(getMyTeamUnreads()), ]; // Sometimes the server version is set in one or the other const serverVersion = Client4.getServerVersion() || getState().entities.general.serverVersion; dispatch(setServerVersion(serverVersion)); if (!isMinimumServerVersion(serverVersion, 4, 7) && config.EnableCustomEmoji === 'true') { dispatch(getAllCustomEmojis()); } await Promise.all(promises); const {currentUserId} = getState().entities.users; const user = getState().entities.users.profiles[currentUserId]; if (currentUserId) { Client4.setUserId(currentUserId); } if (user) { Client4.setUserRoles(user.roles); } return {data: true}; }; } export function logout(): ActionFunc { return async (dispatch: DispatchFunc) => { dispatch({type: UserTypes.LOGOUT_REQUEST, data: null}); try { await Client4.logout(); } catch (error) { // nothing to do here } dispatch({type: UserTypes.LOGOUT_SUCCESS, data: null}); return {data: true}; }; } export function getTotalUsersStats(): ActionFunc { return bindClientFunc({ clientFunc: Client4.getTotalUsersStats, onSuccess: UserTypes.RECEIVED_USER_STATS, }); } export function getFilteredUsersStats(options: GetFilteredUsersStatsOpts = {}): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let stats: UsersStats; try { stats = await Client4.getFilteredUsersStats(options); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.RECEIVED_FILTERED_USER_STATS, data: stats, }); return {data: stats}; }; } export function getProfiles(page = 0, perPage: number = General.PROFILE_CHUNK_SIZE, options: any = {}): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let profiles: UserProfile[]; try { profiles = await Client4.getProfiles(page, perPage, options); removeUserFromList(currentUserId, profiles); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.RECEIVED_PROFILES_LIST, data: profiles, }); return {data: profiles}; }; } export function getMissingProfilesByIds(userIds: string[]): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {profiles} = getState().entities.users; const missingIds: string[] = []; userIds.forEach((id) => { if (!profiles[id]) { missingIds.push(id); } }); if (missingIds.length > 0) { getStatusesByIds(missingIds)(dispatch, getState); return getProfilesByIds(missingIds)(dispatch, getState); } return {data: []}; }; } export function getMissingProfilesByUsernames(usernames: string[]): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {profiles} = getState().entities.users; const usernameProfiles = Object.values(profiles).reduce((acc, profile: any) => { acc[profile.username] = profile; return acc; }, {} as Dictionary<UserProfile>); const missingUsernames: string[] = []; usernames.forEach((username) => { if (!usernameProfiles[username]) { missingUsernames.push(username); } }); if (missingUsernames.length > 0) { return getProfilesByUsernames(missingUsernames)(dispatch, getState); } return {data: []}; }; } export function getProfilesByIds(userIds: string[], options?: any): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let profiles: UserProfile[]; try { profiles = await Client4.getProfilesByIds(userIds, options); removeUserFromList(currentUserId, profiles); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.RECEIVED_PROFILES_LIST, data: profiles, }); return {data: profiles}; }; } export function getProfilesByUsernames(usernames: string[]): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let profiles; try { profiles = await Client4.getProfilesByUsernames(usernames); removeUserFromList(currentUserId, profiles); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.RECEIVED_PROFILES_LIST, data: profiles, }); return {data: profiles}; }; } export function getProfilesInTeam(teamId: string, page: number, perPage: number = General.PROFILE_CHUNK_SIZE, sort = '', options: any = {}): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let profiles; try { profiles = await Client4.getProfilesInTeam(teamId, page, perPage, sort, options); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch(batchActions([ { type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM, data: profiles, id: teamId, }, { type: UserTypes.RECEIVED_PROFILES_LIST, data: removeUserFromList(currentUserId, [...profiles]), }, ])); return {data: profiles}; }; } export function getProfilesNotInTeam(teamId: string, groupConstrained: boolean, page: number, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let profiles; try { profiles = await Client4.getProfilesNotInTeam(teamId, groupConstrained, page, perPage); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const receivedProfilesListActionType = groupConstrained ? UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM_AND_REPLACE : UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM; dispatch(batchActions([ { type: receivedProfilesListActionType, data: profiles, id: teamId, }, { type: UserTypes.RECEIVED_PROFILES_LIST, data: profiles, }, ])); return {data: profiles}; }; } export function getProfilesWithoutTeam(page: number, perPage: number = General.PROFILE_CHUNK_SIZE, options: any = {}): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let profiles = null; try { profiles = await Client4.getProfilesWithoutTeam(page, perPage, options); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch(batchActions([ { type: UserTypes.RECEIVED_PROFILES_LIST_WITHOUT_TEAM, data: profiles, }, { type: UserTypes.RECEIVED_PROFILES_LIST, data: profiles, }, ])); return {data: profiles}; }; } export function getProfilesInChannel(channelId: string, page: number, perPage: number = General.PROFILE_CHUNK_SIZE, sort = '', options: {active?: boolean} = {}): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let profiles; try { profiles = await Client4.getProfilesInChannel(channelId, page, perPage, sort, options); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch(batchActions([ { type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL, data: profiles, id: channelId, }, { type: UserTypes.RECEIVED_PROFILES_LIST, data: removeUserFromList(currentUserId, [...profiles]), }, ])); return {data: profiles}; }; } export function getProfilesInGroupChannels(channelsIds: string[]): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let channelProfiles; try { channelProfiles = await Client4.getProfilesInGroupChannels(channelsIds.slice(0, General.MAX_GROUP_CHANNELS_FOR_PROFILES)); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const actions: Action[] = []; for (const channelId in channelProfiles) { if (channelProfiles.hasOwnProperty(channelId)) { const profiles = channelProfiles[channelId]; actions.push( { type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL, data: profiles, id: channelId, }, { type: UserTypes.RECEIVED_PROFILES_LIST, data: removeUserFromList(currentUserId, [...profiles]), }, ); } } dispatch(batchActions(actions)); return {data: channelProfiles}; }; } export function getProfilesNotInChannel(teamId: string, channelId: string, groupConstrained: boolean, page: number, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let profiles; try { profiles = await Client4.getProfilesNotInChannel(teamId, channelId, groupConstrained, page, perPage); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const receivedProfilesListActionType = groupConstrained ? UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL_AND_REPLACE : UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL; dispatch(batchActions([ { type: receivedProfilesListActionType, data: profiles, id: channelId, }, { type: UserTypes.RECEIVED_PROFILES_LIST, data: removeUserFromList(currentUserId, [...profiles]), }, ])); return {data: profiles}; }; } export function getMe(): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const getMeFunc = bindClientFunc({ clientFunc: Client4.getMe, onSuccess: UserTypes.RECEIVED_ME, }); const me = await getMeFunc(dispatch, getState); if ('error' in me) { return me; } if ('data' in me) { dispatch(loadRolesIfNeeded(me.data.roles.split(' '))); } return me; }; } export function updateMyTermsOfServiceStatus(termsOfServiceId: string, accepted: boolean): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const response: ActionResult = await dispatch(bindClientFunc({ clientFunc: Client4.updateMyTermsOfServiceStatus, params: [ termsOfServiceId, accepted, ], })); if ('data' in response) { if (accepted) { dispatch({ type: UserTypes.RECEIVED_TERMS_OF_SERVICE_STATUS, data: { terms_of_service_create_at: new Date().getTime(), terms_of_service_id: accepted ? termsOfServiceId : null, user_id: getCurrentUserId(getState()), }, }); } return { data: response.data, }; } return { error: response.error, }; }; } export function getProfilesInGroup(groupId: string, page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let profiles; try { profiles = await Client4.getProfilesInGroup(groupId, page, perPage); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch(batchActions([ { type: UserTypes.RECEIVED_PROFILES_LIST_IN_GROUP, data: profiles, id: groupId, }, { type: UserTypes.RECEIVED_PROFILES_LIST, data: removeUserFromList(currentUserId, [...profiles]), }, ])); return {data: profiles}; }; } export function getTermsOfService(): ActionFunc { return bindClientFunc({ clientFunc: Client4.getTermsOfService, }); } export function promoteGuestToUser(userId: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.promoteGuestToUser, params: [userId], }); } export function demoteUserToGuest(userId: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.demoteUserToGuest, params: [userId], }); } export function createTermsOfService(text: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.createTermsOfService, params: [ text, ], }); } export function getUser(id: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.getUser, onSuccess: UserTypes.RECEIVED_PROFILE, params: [ id, ], }); } export function getUserByUsername(username: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.getUserByUsername, onSuccess: UserTypes.RECEIVED_PROFILE, params: [ username, ], }); } export function getUserByEmail(email: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.getUserByEmail, onSuccess: UserTypes.RECEIVED_PROFILE, params: [ email, ], }); } // We create an array to hold the id's that we want to get a status for. We build our // debounced function that will get called after a set period of idle time in which // the array of id's will be passed to the getStatusesByIds with a cb that clears out // the array. Helps with performance because instead of making 75 different calls for // statuses, we are only making one call for 75 ids. // We could maybe clean it up somewhat by storing the array of ids in redux state possbily? let ids: string[] = []; const debouncedGetStatusesByIds = debounce(async (dispatch: DispatchFunc, getState: GetStateFunc) => { getStatusesByIds([...new Set(ids)])(dispatch, getState); }, 20, false, () => { ids = []; }); export function getStatusesByIdsBatchedDebounced(id: string) { ids = [...ids, id]; return debouncedGetStatusesByIds; } export function getStatusesByIds(userIds: string[]): ActionFunc { return bindClientFunc({ clientFunc: Client4.getStatusesByIds, onSuccess: UserTypes.RECEIVED_STATUSES, params: [ userIds, ], }); } export function getStatus(userId: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.getStatus, onSuccess: UserTypes.RECEIVED_STATUS, params: [ userId, ], }); } export function setStatus(status: UserStatus): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.updateStatus(status); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.RECEIVED_STATUS, data: status, }); return {data: status}; }; } export function setCustomStatus(customStatus: UserCustomStatus): ActionFunc { return bindClientFunc({ clientFunc: Client4.updateCustomStatus, params: [ customStatus, ], }); } export function unsetCustomStatus(): ActionFunc { return bindClientFunc({ clientFunc: Client4.unsetCustomStatus, }); } export function removeRecentCustomStatus(customStatus: UserCustomStatus): ActionFunc { return bindClientFunc({ clientFunc: Client4.removeRecentCustomStatus, params: [ customStatus, ], }); } export function getSessions(userId: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.getSessions, onSuccess: UserTypes.RECEIVED_SESSIONS, params: [ userId, ], }); } export function revokeSession(userId: string, sessionId: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.revokeSession(userId, sessionId); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.RECEIVED_REVOKED_SESSION, sessionId, data: null, }); return {data: true}; }; } export function revokeAllSessionsForUser(userId: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.revokeAllSessionsForUser(userId); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const data = {isCurrentUser: userId === getCurrentUserId(getState())}; dispatch(batchActions([ { type: UserTypes.REVOKE_ALL_USER_SESSIONS_SUCCESS, data, }, ])); return {data: true}; }; } export function revokeSessionsForAllUsers(): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.revokeSessionsForAllUsers(); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.REVOKE_SESSIONS_FOR_ALL_USERS_SUCCESS, data: null, }); return {data: true}; }; } export function loadProfilesForDirect(): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); const config = state.entities.general.config; const {channels, myMembers} = state.entities.channels; const {myPreferences} = state.entities.preferences; const {currentUserId, profiles} = state.entities.users; const values = Object.values(channels); for (let i = 0; i < values.length; i++) { const channel: any = values[i]; const member = myMembers[channel.id]; if (!isDirectChannel(channel) && !isGroupChannel(channel)) { continue; } if (member) { if (member.mention_count > 0 && isDirectChannel(channel)) { const otherUserId = getUserIdFromChannelName(currentUserId, channel.name); if (!isDirectChannelVisible(profiles[otherUserId] || otherUserId, config, myPreferences, channel)) { makeDirectChannelVisibleIfNecessary(otherUserId)(dispatch, getState); } } else if ((member.mention_count > 0 || member.msg_count < channel.total_msg_count) && isGroupChannel(channel) && !isGroupChannelVisible(config, myPreferences, channel)) { makeGroupMessageVisibleIfNecessary(channel.id)(dispatch, getState); } } } return {data: true}; }; } export function getUserAudits(userId: string, page = 0, perPage: number = General.AUDITS_CHUNK_SIZE): ActionFunc { return bindClientFunc({ clientFunc: Client4.getUserAudits, onSuccess: UserTypes.RECEIVED_AUDITS, params: [ userId, page, perPage, ], }); } export function autocompleteUsers(term: string, teamId = '', channelId = '', options: { limit: number; } = { limit: General.AUTOCOMPLETE_LIMIT_DEFAULT, }): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { dispatch({type: UserTypes.AUTOCOMPLETE_USERS_REQUEST, data: null}); const {currentUserId} = getState().entities.users; let data; try { data = await Client4.autocompleteUsers(term, teamId, channelId, options); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(batchActions([ {type: UserTypes.AUTOCOMPLETE_USERS_FAILURE, error}, logError(error), ])); return {error}; } let users = [...data.users]; if (data.out_of_channel) { users = [...users, ...data.out_of_channel]; } removeUserFromList(currentUserId, users); const actions: Action[] = [{ type: UserTypes.RECEIVED_PROFILES_LIST, data: users, }, { type: UserTypes.AUTOCOMPLETE_USERS_SUCCESS, }]; if (channelId) { actions.push( { type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL, data: data.users, id: channelId, }, ); actions.push( { type: UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL, data: data.out_of_channel, id: channelId, }, ); } if (teamId) { actions.push( { type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM, data: users, id: teamId, }, ); } dispatch(batchActions(actions)); return {data}; }; } export function searchProfiles(term: string, options: any = {}): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const {currentUserId} = getState().entities.users; let profiles; try { profiles = await Client4.searchUsers(term, options); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const actions: Action[] = [{type: UserTypes.RECEIVED_PROFILES_LIST, data: removeUserFromList(currentUserId, [...profiles])}]; if (options.in_channel_id) { actions.push({ type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL, data: profiles, id: options.in_channel_id, }); } if (options.not_in_channel_id) { actions.push({ type: UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL, data: profiles, id: options.not_in_channel_id, }); } if (options.team_id) { actions.push({ type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM, data: profiles, id: options.team_id, }); } if (options.not_in_team_id) { actions.push({ type: UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM, data: profiles, id: options.not_in_team_id, }); } if (options.in_group_id) { actions.push({ type: UserTypes.RECEIVED_PROFILES_LIST_IN_GROUP, data: profiles, id: options.in_group_id, }); } dispatch(batchActions(actions)); return {data: profiles}; }; } let statusIntervalId: NodeJS.Timeout|null; export function startPeriodicStatusUpdates(): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { if (statusIntervalId) { clearInterval(statusIntervalId); } statusIntervalId = setInterval( () => { const {statuses} = getState().entities.users; if (!statuses) { return; } const userIds = Object.keys(statuses); if (!userIds.length) { return; } getStatusesByIds(userIds)(dispatch, getState); }, General.STATUS_INTERVAL, ); return {data: true}; }; } export function stopPeriodicStatusUpdates(): ActionFunc { return async () => { if (statusIntervalId) { clearInterval(statusIntervalId); } return {data: true}; }; } export function updateMe(user: UserProfile): ActionFunc { return async (dispatch: DispatchFunc) => { dispatch({type: UserTypes.UPDATE_ME_REQUEST, data: null}); let data; try { data = await Client4.patchMe(user); } catch (error) { dispatch(batchActions([ {type: UserTypes.UPDATE_ME_FAILURE, error}, logError(error), ])); return {error}; } dispatch(batchActions([ {type: UserTypes.RECEIVED_ME, data}, {type: UserTypes.UPDATE_ME_SUCCESS}, ])); dispatch(loadRolesIfNeeded(data.roles.split(' '))); return {data}; }; } export function patchUser(user: UserProfile): ActionFunc { return async (dispatch: DispatchFunc) => { let data: UserProfile; try { data = await Client4.patchUser(user); } catch (error) { dispatch(logError(error)); return {error}; } dispatch({type: UserTypes.RECEIVED_PROFILE, data}); return {data}; }; } export function updateUserRoles(userId: string, roles: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.updateUserRoles(userId, roles); } catch (error) { return {error}; } const profile = getState().entities.users.profiles[userId]; if (profile) { dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, roles}}); } return {data: true}; }; } export function updateUserMfa(userId: string, activate: boolean, code = ''): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.updateUserMfa(userId, activate, code); } catch (error) { dispatch(logError(error)); return {error}; } const profile = getState().entities.users.profiles[userId]; if (profile) { dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, mfa_active: activate}}); } return {data: true}; }; } export function updateUserPassword(userId: string, currentPassword: string, newPassword: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.updateUserPassword(userId, currentPassword, newPassword); } catch (error) { dispatch(logError(error)); return {error}; } const profile = getState().entities.users.profiles[userId]; if (profile) { dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, last_password_update_at: new Date().getTime()}}); } return {data: true}; }; } export function updateUserActive(userId: string, active: boolean): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.updateUserActive(userId, active); } catch (error) { dispatch(logError(error)); return {error}; } const profile = getState().entities.users.profiles[userId]; if (profile) { const deleteAt = active ? 0 : new Date().getTime(); dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, delete_at: deleteAt}}); } return {data: true}; }; } export function verifyUserEmail(token: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.verifyUserEmail, params: [ token, ], }); } export function sendVerificationEmail(email: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.sendVerificationEmail, params: [ email, ], }); } export function resetUserPassword(token: string, newPassword: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.resetUserPassword, params: [ token, newPassword, ], }); } export function sendPasswordResetEmail(email: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.sendPasswordResetEmail, params: [ email, ], }); } export function setDefaultProfileImage(userId: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.setDefaultProfileImage(userId); } catch (error) { dispatch(logError(error)); return {error}; } const profile = getState().entities.users.profiles[userId]; if (profile) { dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, last_picture_update: 0}}); } return {data: true}; }; } export function uploadProfileImage(userId: string, imageData: any): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.uploadProfileImage(userId, imageData); } catch (error) { return {error}; } const profile = getState().entities.users.profiles[userId]; if (profile) { dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, last_picture_update: new Date().getTime()}}); } return {data: true}; }; } export function switchEmailToOAuth(service: string, email: string, password: string, mfaCode = ''): ActionFunc { return bindClientFunc({ clientFunc: Client4.switchEmailToOAuth, params: [ service, email, password, mfaCode, ], }); } export function switchOAuthToEmail(currentService: string, email: string, password: string): ActionFunc { return bindClientFunc({ clientFunc: Client4.switchOAuthToEmail, params: [ currentService, email, password, ], }); } export function switchEmailToLdap(email: string, emailPassword: string, ldapId: string, ldapPassword: string, mfaCode = ''): ActionFunc { return bindClientFunc({ clientFunc: Client4.switchEmailToLdap, params: [ email, emailPassword, ldapId, ldapPassword, mfaCode, ], }); } export function switchLdapToEmail(ldapPassword: string, email: string, emailPassword: string, mfaCode = ''): ActionFunc { return bindClientFunc({ clientFunc: Client4.switchLdapToEmail, params: [ ldapPassword, email, emailPassword, mfaCode, ], }); } export function createUserAccessToken(userId: string, description: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let data; try { data = await Client4.createUserAccessToken(userId, description); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const actions: Action[] = [{ type: AdminTypes.RECEIVED_USER_ACCESS_TOKEN, data: {...data, token: '', }, }]; const {currentUserId} = getState().entities.users; if (userId === currentUserId) { actions.push( { type: UserTypes.RECEIVED_MY_USER_ACCESS_TOKEN, data: {...data, token: ''}, }, ); } dispatch(batchActions(actions)); return {data}; }; } export function getUserAccessToken(tokenId: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let data; try { data = await Client4.getUserAccessToken(tokenId); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const actions: Action[] = [{ type: AdminTypes.RECEIVED_USER_ACCESS_TOKEN, data, }]; const {currentUserId} = getState().entities.users; if (data.user_id === currentUserId) { actions.push( { type: UserTypes.RECEIVED_MY_USER_ACCESS_TOKEN, data, }, ); } dispatch(batchActions(actions)); return {data}; }; } export function getUserAccessTokens(page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let data; try { data = await Client4.getUserAccessTokens(page, perPage); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const actions = [ { type: AdminTypes.RECEIVED_USER_ACCESS_TOKENS, data, }, ]; dispatch(batchActions(actions)); return {data}; }; } export function getUserAccessTokensForUser(userId: string, page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let data; try { data = await Client4.getUserAccessTokensForUser(userId, page, perPage); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } const actions: Action[] = [{ type: AdminTypes.RECEIVED_USER_ACCESS_TOKENS_FOR_USER, data, userId, }]; const {currentUserId} = getState().entities.users; if (userId === currentUserId) { actions.push( { type: UserTypes.RECEIVED_MY_USER_ACCESS_TOKENS, data, }, ); } dispatch(batchActions(actions)); return {data}; }; } export function revokeUserAccessToken(tokenId: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.revokeUserAccessToken(tokenId); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.REVOKED_USER_ACCESS_TOKEN, data: tokenId, }); return {data: true}; }; } export function disableUserAccessToken(tokenId: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.disableUserAccessToken(tokenId); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.DISABLED_USER_ACCESS_TOKEN, data: tokenId, }); return {data: true}; }; } export function enableUserAccessToken(tokenId: string): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { try { await Client4.enableUserAccessToken(tokenId); } catch (error) { forceLogoutIfNecessary(error, dispatch, getState); dispatch(logError(error)); return {error}; } dispatch({ type: UserTypes.ENABLED_USER_ACCESS_TOKEN, data: tokenId, }); return {data: true}; }; } export function getKnownUsers(): ActionFunc { return bindClientFunc({ clientFunc: Client4.getKnownUsers, }); } export function clearUserAccessTokens(): ActionFunc { return async (dispatch) => { dispatch({type: UserTypes.CLEAR_MY_USER_ACCESS_TOKENS, data: null}); return {data: true}; }; } export function checkForModifiedUsers() { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); const users = getUsers(state); const lastDisconnectAt = state.websocket.lastDisconnectAt; const serverVersion = getServerVersion(state); if (!isMinimumServerVersion(serverVersion, 5, 14)) { return {data: true}; } await dispatch(getProfilesByIds(Object.keys(users), {since: lastDisconnectAt})); return {data: true}; }; } export default { checkMfa, generateMfaSecret, login, logout, getProfiles, getProfilesByIds, getProfilesInTeam, getProfilesInChannel, getProfilesNotInChannel, getUser, getMe, getUserByUsername, getStatus, getStatusesByIds, getSessions, getTotalUsersStats, loadProfilesForDirect, revokeSession, revokeAllSessionsForUser, revokeSessionsForAllUsers, getUserAudits, searchProfiles, startPeriodicStatusUpdates, stopPeriodicStatusUpdates, updateMe, updateUserRoles, updateUserMfa, updateUserPassword, updateUserActive, verifyUserEmail, sendVerificationEmail, resetUserPassword, sendPasswordResetEmail, uploadProfileImage, switchEmailToOAuth, switchOAuthToEmail, switchEmailToLdap, switchLdapToEmail, getTermsOfService, createTermsOfService, updateMyTermsOfServiceStatus, createUserAccessToken, getUserAccessToken, getUserAccessTokensForUser, revokeUserAccessToken, disableUserAccessToken, enableUserAccessToken, checkForModifiedUsers, };