mattermost-redux
Version:
Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client
594 lines (593 loc) • 22.7 kB
JavaScript
"use strict";
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const redux_1 = require("redux");
const action_types_1 = require("mattermost-redux/action_types");
function profilesToSet(state, action) {
const id = action.id;
const users = Object.values(action.data);
return users.reduce((nextState, user) => addProfileToSet(nextState, id, user.id), state);
}
function removeProfilesFromSet(state, action) {
const id = action.id;
const users = Object.values(action.data);
return users.reduce((nextState, user) => removeProfileFromSet(nextState, { type: '', data: { id, user_id: user.id } }), state);
}
function profileListToSet(state, action, replace = false) {
const id = action.id;
const users = action.data || [];
if (replace) {
return {
...state,
[id]: new Set(users.map((user) => user.id)),
};
}
return users.reduce((nextState, user) => addProfileToSet(nextState, id, user.id), state);
}
function removeProfileListFromSet(state, action) {
const id = action.id;
const nextSet = new Set(state[id]);
if (action.data) {
action.data.forEach((profile) => {
nextSet.delete(profile.id);
});
return {
...state,
[id]: nextSet,
};
}
return state;
}
function addProfileToSet(state, id, userId) {
const nextSet = new Set(state[id]);
nextSet.add(userId);
return {
...state,
[id]: nextSet,
};
}
function removeProfileFromSets(state, action) {
const newState = { ...state };
let removed = false;
Object.keys(state).forEach((key) => {
if (newState[key].has(action.data.user_id)) {
newState[key] = new Set(newState[key]);
newState[key].delete(action.data.user_id);
removed = true;
}
});
return removed ? newState : state;
}
function removeProfileFromSet(state, action) {
const { id, user_id: userId } = action.data;
const nextSet = new Set(state[id]);
nextSet.delete(userId);
return {
...state,
[id]: nextSet,
};
}
function currentUserId(state = '', action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_ME: {
const data = action.data;
return data.id;
}
case action_types_1.UserTypes.LOGIN: { // Used by the mobile app
const { user } = action.data;
return user ? user.id : state;
}
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return '';
}
return state;
}
function mySessions(state = [], action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_SESSIONS:
return [...action.data];
case action_types_1.UserTypes.RECEIVED_REVOKED_SESSION: {
let index = -1;
const length = state.length;
for (let i = 0; i < length; i++) {
if (state[i].id === action.sessionId) {
index = i;
break;
}
}
if (index > -1) {
return state.slice(0, index).concat(state.slice(index + 1));
}
return state;
}
case action_types_1.UserTypes.REVOKE_ALL_USER_SESSIONS_SUCCESS:
if (action.data.isCurrentUser === true) {
return [];
}
return state;
case action_types_1.UserTypes.REVOKE_SESSIONS_FOR_ALL_USERS_SUCCESS:
return [];
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return [];
default:
return state;
}
}
function myAudits(state = [], action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_AUDITS:
return [...action.data];
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return [];
default:
return state;
}
}
function receiveUserProfile(state, received) {
const existing = state[received.id];
if (!existing) {
// No existing data to merge with
return {
...state,
[received.id]: received,
};
}
const merged = {
...existing,
...received,
};
// If there was a remote_id but not anymore, remove it
if (existing.remote_id && !received.remote_id) {
delete merged.remote_id;
}
// MM-53377:
// For non-admin users, certain API responses don't return details for the current user that would be sanitized
// out for others. This currently includes:
// - email (if PrivacySettings.ShowEmailAddress is false)
// - first_name/last_name (if PrivacySettings.ShowFullName is false)
// - last_password_update
// - auth_service
// - notify_props
//
// Because email, first_name, last_name, and auth_service can all be empty strings regularly, we can't just
// merge the received user and the existing one together like we normally would. Instead, we can use the
// existence of existing.notify_props or existing.last_password_update to determine which object has that extra
// data so that it can take precedence. Those fields are:
// 1. Never empty or zero by Go standards
// 2. Only ever sent to the current user, not even to admins, so we know that the object contains privileged data
//
// Note that admins may have the email/name/auth_service of other users loaded as well. This does not prevent that
// data from being replaced when merging sanitized user objects. There doesn't seem to be a way for us to detect
// whether the object is sanitized for admins.
if (existing.notify_props && (!received.notify_props || Object.keys(received.notify_props).length === 0)) {
merged.email = existing.email;
merged.first_name = existing.first_name;
merged.last_name = existing.last_name;
merged.last_password_update = existing.last_password_update;
merged.auth_service = existing.auth_service;
merged.notify_props = existing.notify_props;
}
if ((0, isEqual_1.default)(existing, merged)) {
return state;
}
return {
...state,
[merged.id]: merged,
};
}
function profiles(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_ME:
case action_types_1.UserTypes.RECEIVED_PROFILE: {
const user = action.data;
return receiveUserProfile(state, user);
}
case action_types_1.UserTypes.RECEIVED_CPA_VALUES: {
const { userID, customAttributeValues } = action.data;
const existingProfile = state[userID];
if (!existingProfile) {
return state;
}
const profileAttributes = { ...existingProfile.custom_profile_attributes, ...customAttributeValues };
return receiveUserProfile(state, { ...existingProfile, custom_profile_attributes: profileAttributes });
}
case action_types_1.UserTypes.CLEAR_CPA_VALUES: {
const { fieldId } = action.data;
return Object.values(state).reduce((nextState, profile) => {
// Only modify profiles that have this field value
if (profile.custom_profile_attributes && profile.custom_profile_attributes[fieldId] !== undefined) {
const newAttributes = { ...profile.custom_profile_attributes };
delete newAttributes[fieldId];
nextState[profile.id] = {
...profile,
custom_profile_attributes: newAttributes,
};
}
else {
nextState[profile.id] = profile;
}
return nextState;
}, {});
}
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST: {
const users = action.data || [];
return users.reduce(receiveUserProfile, state);
}
case action_types_1.UserTypes.RECEIVED_PROFILES: {
const users = Object.values(action.data);
return users.reduce(receiveUserProfile, state);
}
case action_types_1.UserTypes.RECEIVED_TERMS_OF_SERVICE_STATUS: {
const data = action.data;
return {
...state,
[data.user_id]: {
...state[data.user_id],
terms_of_service_id: data.terms_of_service_id,
terms_of_service_create_at: data.terms_of_service_create_at,
},
};
}
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE: {
if (state[action.data.user_id]) {
const newState = { ...state };
delete newState[action.data.user_id];
return newState;
}
return state;
}
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function profilesInTeam(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_PROFILE_IN_TEAM:
return addProfileToSet(state, action.data.id, action.data.user_id);
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM:
return profileListToSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILES_IN_TEAM:
return profilesToSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM:
return removeProfileFromSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM:
return removeProfileListFromSet(state, action);
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromSets(state, action);
default:
return state;
}
}
function profilesNotInTeam(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM:
return addProfileToSet(state, action.data.id, action.data.user_id);
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM:
return profileListToSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM_AND_REPLACE:
return profileListToSet(state, action, true);
case action_types_1.UserTypes.RECEIVED_PROFILE_IN_TEAM:
return removeProfileFromSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM:
return removeProfileListFromSet(state, action);
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromSets(state, action);
default:
return state;
}
}
function profilesInChannel(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_PROFILE_IN_CHANNEL:
return addProfileToSet(state, action.data.id, action.data.user_id);
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL:
return profileListToSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILES_IN_CHANNEL:
return profilesToSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL:
return removeProfileFromSet(state, action);
case action_types_1.ChannelTypes.CHANNEL_MEMBER_REMOVED:
return removeProfileFromSet(state, {
type: '',
data: {
id: action.data.channel_id,
user_id: action.data.user_id,
}
});
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromSets(state, action);
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function profilesNotInChannel(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL:
return addProfileToSet(state, action.data.id, action.data.user_id);
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL:
return profileListToSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL_AND_REPLACE:
return profileListToSet(state, action, true);
case action_types_1.UserTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL:
return profilesToSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILES_IN_CHANNEL:
return removeProfilesFromSet(state, action);
case action_types_1.UserTypes.RECEIVED_PROFILE_IN_CHANNEL:
return removeProfileFromSet(state, action);
case action_types_1.ChannelTypes.CHANNEL_MEMBER_ADDED:
return removeProfileFromSet(state, {
type: '',
data: {
id: action.data.channel_id,
user_id: action.data.user_id,
}
});
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromSets(state, action);
default:
return state;
}
}
function profilesInGroup(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_IN_GROUP: {
return profileListToSet(state, action);
}
case action_types_1.UserTypes.RECEIVED_PROFILES_FOR_GROUP: {
const id = action.id;
const nextSet = new Set(state[id]);
if (action.data) {
action.data.forEach((profile) => {
nextSet.add(profile.user_id);
});
return {
...state,
[id]: nextSet,
};
}
return state;
}
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_TO_REMOVE_FROM_GROUP: {
const id = action.id;
const nextSet = new Set(state[id]);
if (action.data) {
action.data.forEach((profile) => {
nextSet.delete(profile.user_id);
});
return {
...state,
[id]: nextSet,
};
}
return state;
}
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromSets(state, action);
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function profilesNotInGroup(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_PROFILES_FOR_GROUP: {
const id = action.id;
const nextSet = new Set(state[id]);
if (action.data) {
action.data.forEach((profile) => {
nextSet.delete(profile.user_id);
});
return {
...state,
[id]: nextSet,
};
}
return state;
}
case action_types_1.UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_GROUP: {
return profileListToSet(state, action);
}
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromSets(state, action);
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function dndEndTimes(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_DND_END_TIMES: {
return { ...state, ...action.data };
}
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE: {
if (state[action.data.user_id]) {
const newState = { ...state };
delete newState[action.data.user_id];
return newState;
}
return state;
}
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function statuses(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_STATUSES: {
return { ...state, ...action.data };
}
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE: {
if (state[action.data.user_id]) {
const newState = { ...state };
delete newState[action.data.user_id];
return newState;
}
return state;
}
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function isManualStatus(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_STATUSES_IS_MANUAL: {
return { ...state, ...action.data };
}
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE: {
if (state[action.data.user_id]) {
const newState = { ...state };
delete newState[action.data.user_id];
return newState;
}
return state;
}
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function myUserAccessTokens(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_MY_USER_ACCESS_TOKEN: {
const nextState = { ...state };
nextState[action.data.id] = action.data;
return nextState;
}
case action_types_1.UserTypes.RECEIVED_MY_USER_ACCESS_TOKENS: {
const nextState = { ...state };
for (const uat of action.data) {
nextState[uat.id] = uat;
}
return nextState;
}
case action_types_1.UserTypes.REVOKED_USER_ACCESS_TOKEN: {
const nextState = { ...state };
Reflect.deleteProperty(nextState, action.data);
return nextState;
}
case action_types_1.UserTypes.ENABLED_USER_ACCESS_TOKEN: {
if (state[action.data]) {
const nextState = { ...state };
nextState[action.data] = { ...nextState[action.data], is_active: true };
return nextState;
}
return state;
}
case action_types_1.UserTypes.DISABLED_USER_ACCESS_TOKEN: {
if (state[action.data]) {
const nextState = { ...state };
nextState[action.data] = { ...nextState[action.data], is_active: false };
return nextState;
}
return state;
}
case action_types_1.UserTypes.CLEAR_MY_USER_ACCESS_TOKENS:
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function stats(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_USER_STATS: {
const stat = action.data;
return {
...state,
...stat,
};
}
default:
return state;
}
}
function filteredStats(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_FILTERED_USER_STATS: {
const stat = action.data;
return {
...state,
...stat,
};
}
default:
return state;
}
}
function lastActivity(state = {}, action) {
switch (action.type) {
case action_types_1.UserTypes.RECEIVED_LAST_ACTIVITIES: {
return { ...state, ...action.data };
}
case action_types_1.UserTypes.LOGOUT_SUCCESS:
return {};
case action_types_1.UserTypes.PROFILE_NO_LONGER_VISIBLE: {
if (state[action.data.user_id]) {
const newState = { ...state };
delete newState[action.data.user_id];
return newState;
}
return state;
}
default:
return state;
}
}
exports.default = (0, redux_1.combineReducers)({
// the current selected user
currentUserId,
// array with the user's sessions
mySessions,
// array with the user's audits
myAudits,
// object where every key is the token id and has a user access token as a value
myUserAccessTokens,
// object where every key is a user id and has an object with the users details
profiles,
// object where every key is a team id and has a Set with the users id that are members of the team
profilesInTeam,
// object where every key is a team id and has a Set with the users id that are not members of the team
profilesNotInTeam,
// object where every key is a channel id and has a Set with the users id that are members of the channel
profilesInChannel,
// object where every key is a channel id and has a Set with the users id that are not members of the channel
profilesNotInChannel,
// object where every key is a group id and has a Set with the users id that are members of the group
profilesInGroup,
// object where every key is a group id and has a Set with the users id that are members of the group
profilesNotInGroup,
// object where every key is the user id and has a value with the dnd end time of each user
dndEndTimes,
// object where every key is the user id and has a value with the current status of each user
statuses,
// object where every key is the user id and has a value with a flag determining if their status was set manually
isManualStatus,
// Total user stats
stats,
// Total user stats after filters have been applied
filteredStats,
// object where every key is the user id and has a value with the last activity timestamp
lastActivity,
});