) => {\n this.setState({checked: e.target.checked});\n }\n\n handleConfirm = () => {\n this.props.onConfirm(this.state.checked);\n }\n\n handleCancel = () => {\n this.props.onCancel(this.state.checked);\n }\n\n render() {\n let checkbox;\n if (this.props.showCheckbox) {\n checkbox = (\n \n \n
\n );\n }\n\n let cancelText;\n if (this.props.cancelButtonText) {\n cancelText = this.props.cancelButtonText;\n } else {\n cancelText = (\n \n );\n }\n\n let cancelButton;\n if (!this.props.hideCancel) {\n cancelButton = (\n \n );\n }\n\n return (\n \n \n \n {this.props.title}\n \n \n \n {this.props.message}\n {checkbox}\n \n \n {cancelButton}\n \n \n \n );\n }\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n// Allow overriding the path used by webpack to dynamically resolve assets. This is driven by\n// an environment variable in development, or by a window variable defined in root.html in\n// production. The window variable is updated by the server after configuring SiteURL and\n// restarting or by running the `mattermost config subpath` command.\nwindow.publicPath = process.env.PUBLIC_PATH || window.publicPath || '/static/'; // eslint-disable-line no-process-env\n__webpack_public_path__ = window.publicPath; // eslint-disable-line camelcase, @typescript-eslint/naming-convention, no-undef\n\n// Define the subpath at which Mattermost is running. Extract this from the publicPath above to\n// avoid depending on Redux state before it is even loaded. This actual global export is used\n// in a minimum of places, as it is preferred to leverage react-router, configured to use this\n// basename accordingly.\nwindow.basename = window.publicPath.substr(0, window.publicPath.length - '/static/'.length);\n","export default __webpack_public_path__ + \"i18n/bg.297ffae9edf37a90e53925945aab3e5b.json\";","export default __webpack_public_path__ + \"i18n/de.c87283af9cf7f279cba2d4a8698da0f8.json\";","export default __webpack_public_path__ + \"i18n/en_AU.86519f2d8bdda40adaef8aa46659a45a.json\";","export default __webpack_public_path__ + \"i18n/es.9754127192e6f5e4cd8bb40794256209.json\";","export default __webpack_public_path__ + \"i18n/fr.a10af21729a69f5c7193b4b4f62f44ef.json\";","export default __webpack_public_path__ + \"i18n/hu.c97ba522f24ebd17ec7d58f82dcec7f3.json\";","export default __webpack_public_path__ + \"i18n/it.8b4f4dd97b883a8cd8c58b824cd25bec.json\";","export default __webpack_public_path__ + \"i18n/ja.334608a86a3ffd49eb6a5939ea605bae.json\";","export default __webpack_public_path__ + \"i18n/ko.479a3d3ce97db6cdce7532b852723166.json\";","export default __webpack_public_path__ + \"i18n/nl.dc82731ad2e56413b590295ff2e4436e.json\";","export default __webpack_public_path__ + \"i18n/pl.d1a49ccdcb53f48685dbf476ecc4bc21.json\";","export default __webpack_public_path__ + \"i18n/pt-BR.d5208ba2a6117babb56462383cd5d647.json\";","export default __webpack_public_path__ + \"i18n/ro.e117e857688146a4153ad17dc21cd18d.json\";","export default __webpack_public_path__ + \"i18n/ru.88a9a039d034741e80c6feeddd6f45b9.json\";","export default __webpack_public_path__ + \"i18n/sv.bca42c022c06bef3c775b396629d3715.json\";","export default __webpack_public_path__ + \"i18n/tr.fffd84d2590413dcf171e6040a68339c.json\";","export default __webpack_public_path__ + \"i18n/uk.c7802cb0914129f0e216fa13847c8e39.json\";","export default __webpack_public_path__ + \"i18n/zh-TW.ca8f563083632f1b859db0ef705d3e32.json\";","export default __webpack_public_path__ + \"i18n/zh-CN.767eee3aeed18b9f53b2bb455252c6aa.json\";","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n/* eslint-disable import/order */\nimport bg from './bg.json';\nimport de from './de.json';\nimport enAU from './en_AU.json';\nimport es from './es.json';\nimport fr from './fr.json';\nimport hu from './hu.json';\nimport it from './it.json';\nimport ja from './ja.json';\nimport ko from './ko.json';\nimport nl from './nl.json';\nimport pl from './pl.json';\nimport ptBR from './pt-BR.json';\nimport ro from './ro.json';\nimport ru from './ru.json';\nimport sv from './sv.json';\nimport tr from './tr.json';\nimport uk from './uk.json';\nimport zhTW from './zh-TW.json';\nimport zhCN from './zh-CN.json';\n\nimport {getConfig} from 'mattermost-redux/selectors/entities/general';\n\nimport store from 'stores/redux_store.jsx';\n\n// should match the values in model/config.go\nconst languages = {\n de: {\n value: 'de',\n name: 'Deutsch',\n order: 0,\n url: de,\n },\n en: {\n value: 'en',\n name: 'English (US)',\n order: 1,\n url: '',\n },\n 'en-AU': {\n value: 'en-AU',\n name: 'English (Australia)',\n order: 2,\n url: enAU,\n },\n es: {\n value: 'es',\n name: 'Español',\n order: 3,\n url: es,\n },\n fr: {\n value: 'fr',\n name: 'Français',\n order: 4,\n url: fr,\n },\n it: {\n value: 'it',\n name: 'Italiano (Alpha)',\n order: 5,\n url: it,\n },\n hu: {\n value: 'hu',\n name: 'Magyar',\n order: 6,\n url: hu,\n },\n nl: {\n value: 'nl',\n name: 'Nederlands',\n order: 7,\n url: nl,\n },\n pl: {\n value: 'pl',\n name: 'Polski (Alpha)',\n order: 8,\n url: pl,\n },\n 'pt-BR': {\n value: 'pt-BR',\n name: 'Português (Brasil)',\n order: 9,\n url: ptBR,\n },\n ro: {\n value: 'ro',\n name: 'Română',\n order: 10,\n url: ro,\n },\n sv: {\n value: 'sv',\n name: 'Svenska',\n order: 11,\n url: sv,\n },\n tr: {\n value: 'tr',\n name: 'Türkçe',\n order: 12,\n url: tr,\n },\n bg: {\n value: 'bg',\n name: 'Български',\n order: 13,\n url: bg,\n },\n ru: {\n value: 'ru',\n name: 'Pусский',\n order: 14,\n url: ru,\n },\n uk: {\n value: 'uk',\n name: 'Yкраїнська (Alpha)',\n order: 15,\n url: uk,\n },\n ko: {\n value: 'ko',\n name: '한국어 (Alpha)',\n order: 16,\n url: ko,\n },\n 'zh-CN': {\n value: 'zh-CN',\n name: '中文 (简体)',\n order: 17,\n url: zhCN,\n },\n 'zh-TW': {\n value: 'zh-TW',\n name: '中文 (繁體)',\n order: 18,\n url: zhTW,\n },\n ja: {\n value: 'ja',\n name: '日本語',\n order: 19,\n url: ja,\n },\n};\n\nexport function getAllLanguages() {\n return languages;\n}\n\nexport function getLanguages() {\n const config = getConfig(store.getState());\n if (!config.AvailableLocales) {\n return getAllLanguages();\n }\n return config.AvailableLocales.split(',').reduce((result, l) => {\n if (languages[l]) {\n result[l] = languages[l];\n }\n return result;\n }, {});\n}\n\nexport function getLanguageInfo(locale) {\n return getAllLanguages()[locale];\n}\n\nexport function isLanguageAvailable(locale) {\n return Boolean(getLanguages()[locale]);\n}\n\nexport function doAddLocaleData() {\n if (!Intl.PluralRules) {\n // eslint-disable-next-line global-require\n require('@formatjs/intl-pluralrules/polyfill-locales');\n }\n\n if (!Intl.RelativeTimeFormat) {\n // eslint-disable-next-line global-require\n require('@formatjs/intl-relativetimeformat/polyfill-locales');\n }\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n CHANNEL_REQUEST: null,\n CHANNEL_SUCCESS: null,\n CHANNEL_FAILURE: null,\n\n CHANNELS_REQUEST: null,\n CHANNELS_SUCCESS: null,\n CHANNELS_FAILURE: null,\n\n CREATE_CHANNEL_REQUEST: null,\n CREATE_CHANNEL_SUCCESS: null,\n CREATE_CHANNEL_FAILURE: null,\n\n UPDATE_CHANNEL_REQUEST: null,\n UPDATE_CHANNEL_SUCCESS: null,\n UPDATE_CHANNEL_FAILURE: null,\n\n DELETE_CHANNEL_SUCCESS: null,\n UNARCHIVED_CHANNEL_SUCCESS: null,\n\n GET_CHANNELS_REQUEST: null,\n GET_CHANNELS_SUCCESS: null,\n GET_CHANNELS_FAILURE: null,\n\n GET_ALL_CHANNELS_REQUEST: null,\n GET_ALL_CHANNELS_SUCCESS: null,\n GET_ALL_CHANNELS_FAILURE: null,\n\n GET_CHANNELS_TIMEZONE_REQUEST: null,\n GET_CHANNELS_TIMEZONE_SUCCESS: null,\n GET_CHANNELS_TIMEZONE_FAILURE: null,\n\n CHANNEL_STATS_REQUEST: null,\n CHANNEL_STATS_SUCCESS: null,\n CHANNEL_STATS_FAILURE: null,\n\n ADD_CHANNEL_MEMBER_REQUEST: null,\n ADD_CHANNEL_MEMBER_SUCCESS: null,\n\n REMOVE_CHANNEL_MEMBER_SUCCESS: null,\n\n SELECT_CHANNEL: null,\n LEAVE_CHANNEL: null,\n REMOVE_MEMBER_FROM_CHANNEL: null,\n RECEIVED_CHANNEL: null,\n RECEIVED_CHANNELS: null,\n RECEIVED_ALL_CHANNELS: null,\n RECEIVED_CHANNELS_LIST: null,\n RECEIVED_MY_CHANNEL_MEMBERS: null,\n RECEIVED_MY_CHANNEL_MEMBER: null,\n RECEIVED_CHANNEL_MEMBERS: null,\n RECEIVED_CHANNEL_MEMBER: null,\n RECEIVED_CHANNEL_STATS: null,\n RECEIVED_CHANNEL_PROPS: null,\n RECEIVED_CHANNEL_DELETED: null,\n RECEIVED_CHANNEL_UNARCHIVED: null,\n RECEIVED_LAST_VIEWED_AT: null,\n UPDATE_CHANNEL_HEADER: null,\n UPDATE_CHANNEL_PURPOSE: null,\n CHANNEL_MEMBER_ADDED: null,\n CHANNEL_MEMBER_REMOVED: null,\n\n SET_CHANNEL_MUTED: null,\n\n INCREMENT_TOTAL_MSG_COUNT: null,\n INCREMENT_UNREAD_MSG_COUNT: null,\n DECREMENT_UNREAD_MSG_COUNT: null,\n INCREMENT_UNREAD_MENTION_COUNT: null,\n DECREMENT_UNREAD_MENTION_COUNT: null,\n\n UPDATED_CHANNEL_SCHEME: null,\n UPDATED_CHANNEL_MEMBER_SCHEME_ROLES: null,\n\n RECEIVED_CHANNEL_MEMBERS_MINUS_GROUP_MEMBERS: null,\n\n RECEIVED_CHANNEL_MODERATIONS: null,\n\n RECEIVED_CHANNEL_MEMBER_COUNTS_BY_GROUP: null,\n\n RECEIVED_TOTAL_CHANNEL_COUNT: null,\n\n POST_UNREAD_SUCCESS: null,\n\n ADD_MANUALLY_UNREAD: null,\n REMOVE_MANUALLY_UNREAD: null,\n RECEIVED_MY_CHANNELS_WITH_MEMBERS: null,\n\n INCREMENT_PINNED_POST_COUNT: null,\n DECREMENT_PINNED_POST_COUNT: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n DISMISS_ERROR: null,\n LOG_ERROR: null,\n CLEAR_ERRORS: null,\n RESTORE_ERRORS: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n RECEIVED_APP_STATE: null,\n RECEIVED_APP_CREDENTIALS: null,\n REMOVED_APP_CREDENTIALS: null,\n RECEIVED_APP_DEVICE_TOKEN: null,\n\n PING_RESET: null,\n\n RECEIVED_SERVER_VERSION: null,\n\n CLIENT_CONFIG_RECEIVED: null,\n CLIENT_CONFIG_RESET: null,\n\n CLIENT_LICENSE_RECEIVED: null,\n CLIENT_LICENSE_RESET: null,\n\n RECEIVED_DATA_RETENTION_POLICY: null,\n\n LOG_CLIENT_ERROR_REQUEST: null,\n LOG_CLIENT_ERROR_SUCCESS: null,\n LOG_CLIENT_ERROR_FAILURE: null,\n\n SUPPORTED_TIMEZONES_REQUEST: null,\n SUPPORTED_TIMEZONES_SUCCESS: null,\n SUPPORTED_TIMEZONES_FAILURE: null,\n SUPPORTED_TIMEZONES_RECEIVED: null,\n\n WEBSOCKET_REQUEST: null,\n WEBSOCKET_SUCCESS: null,\n WEBSOCKET_FAILURE: null,\n WEBSOCKET_CLOSED: null,\n\n REDIRECT_LOCATION_SUCCESS: null,\n REDIRECT_LOCATION_FAILURE: null,\n SET_CONFIG_AND_LICENSE: null,\n\n WARN_METRICS_STATUS_RECEIVED: null,\n WARN_METRIC_STATUS_RECEIVED: null,\n WARN_METRIC_STATUS_REMOVED: null,\n\n FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n CREATE_USER_REQUEST: null,\n CREATE_USER_SUCCESS: null,\n CREATE_USER_FAILURE: null,\n\n LOGIN_REQUEST: null,\n LOGIN_SUCCESS: null,\n LOGIN_FAILURE: null,\n\n LOGOUT_REQUEST: null,\n LOGOUT_SUCCESS: null,\n LOGOUT_FAILURE: null,\n\n REVOKE_ALL_USER_SESSIONS_SUCCESS: null,\n REVOKE_SESSIONS_FOR_ALL_USERS_SUCCESS: null,\n\n CHECK_MFA_REQUEST: null,\n CHECK_MFA_SUCCESS: null,\n CHECK_MFA_FAILURE: null,\n\n AUTOCOMPLETE_USERS_REQUEST: null,\n AUTOCOMPLETE_USERS_SUCCESS: null,\n AUTOCOMPLETE_USERS_FAILURE: null,\n\n UPDATE_ME_REQUEST: null,\n UPDATE_ME_SUCCESS: null,\n UPDATE_ME_FAILURE: null,\n\n RECEIVED_ME: null,\n RECEIVED_TERMS_OF_SERVICE_STATUS: null,\n RECEIVED_PROFILE: null,\n RECEIVED_PROFILES: null,\n RECEIVED_PROFILES_LIST: null,\n RECEIVED_PROFILES_IN_TEAM: null,\n RECEIVED_PROFILE_IN_TEAM: null,\n RECEIVED_PROFILES_LIST_IN_TEAM: null,\n RECEIVED_PROFILE_NOT_IN_TEAM: null,\n RECEIVED_PROFILES_LIST_NOT_IN_TEAM: null,\n RECEIVED_PROFILES_LIST_NOT_IN_TEAM_AND_REPLACE: null,\n RECEIVED_PROFILE_WITHOUT_TEAM: null,\n RECEIVED_PROFILES_LIST_WITHOUT_TEAM: null,\n RECEIVED_PROFILES_IN_CHANNEL: null,\n RECEIVED_PROFILES_LIST_IN_CHANNEL: null,\n RECEIVED_PROFILE_IN_CHANNEL: null,\n RECEIVED_PROFILES_NOT_IN_CHANNEL: null,\n RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL: null,\n RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL_AND_REPLACE: null,\n RECEIVED_PROFILE_NOT_IN_CHANNEL: null,\n RECEIVED_PROFILES_LIST_IN_GROUP: null,\n RECEIVED_SESSIONS: null,\n RECEIVED_REVOKED_SESSION: null,\n RECEIVED_AUDITS: null,\n RECEIVED_STATUS: null,\n RECEIVED_STATUSES: null,\n RECEIVED_AUTOCOMPLETE_IN_CHANNEL: null,\n RESET_LOGOUT_STATE: null,\n RECEIVED_MY_USER_ACCESS_TOKEN: null,\n RECEIVED_MY_USER_ACCESS_TOKENS: null,\n CLEAR_MY_USER_ACCESS_TOKENS: null,\n REVOKED_USER_ACCESS_TOKEN: null,\n DISABLED_USER_ACCESS_TOKEN: null,\n ENABLED_USER_ACCESS_TOKEN: null,\n RECEIVED_USER_STATS: null,\n RECEIVED_FILTERED_USER_STATS: null,\n PROFILE_NO_LONGER_VISIBLE: null,\n LOGIN: null,\n RECEIVED_BATCHED_PROFILES_IN_CHANNEL: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n GET_TEAMS_REQUEST: null,\n GET_TEAMS_SUCCESS: null,\n GET_TEAMS_FAILURE: null,\n\n MY_TEAMS_REQUEST: null,\n MY_TEAMS_SUCCESS: null,\n MY_TEAMS_FAILURE: null,\n\n CREATE_TEAM_REQUEST: null,\n CREATE_TEAM_SUCCESS: null,\n CREATE_TEAM_FAILURE: null,\n\n GET_TEAM_MEMBERS_REQUEST: null,\n GET_TEAM_MEMBERS_SUCCESS: null,\n GET_TEAM_MEMBERS_FAILURE: null,\n\n JOIN_TEAM_REQUEST: null,\n JOIN_TEAM_SUCCESS: null,\n JOIN_TEAM_FAILURE: null,\n\n TEAM_INVITE_INFO_REQUEST: null,\n TEAM_INVITE_INFO_SUCCESS: null,\n TEAM_INVITE_INFO_FAILURE: null,\n\n ADD_TO_TEAM_FROM_INVITE_REQUEST: null,\n ADD_TO_TEAM_FROM_INVITE_SUCCESS: null,\n ADD_TO_TEAM_FROM_INVITE_FAILURE: null,\n\n CREATED_TEAM: null,\n SELECT_TEAM: null,\n UPDATED_TEAM: null,\n PATCHED_TEAM: null,\n REGENERATED_TEAM_INVITE_ID: null,\n RECEIVED_TEAM: null,\n RECEIVED_TEAMS: null,\n RECEIVED_TEAM_DELETED: null,\n RECEIVED_TEAM_UNARCHIVED: null,\n RECEIVED_TEAMS_LIST: null,\n RECEIVED_MY_TEAM_MEMBERS: null,\n RECEIVED_MY_TEAM_MEMBER: null,\n RECEIVED_TEAM_MEMBERS: null,\n RECEIVED_MEMBERS_IN_TEAM: null,\n RECEIVED_MEMBER_IN_TEAM: null,\n REMOVE_MEMBER_FROM_TEAM: null,\n RECEIVED_TEAM_STATS: null,\n RECEIVED_MY_TEAM_UNREADS: null,\n LEAVE_TEAM: null,\n UPDATED_TEAM_SCHEME: null,\n UPDATED_TEAM_MEMBER_SCHEME_ROLES: null,\n\n RECEIVED_TEAM_MEMBERS_MINUS_GROUP_MEMBERS: null,\n\n RECEIVED_TOTAL_TEAM_COUNT: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n CREATE_POST_REQUEST: null,\n CREATE_POST_SUCCESS: null,\n CREATE_POST_FAILURE: null,\n CREATE_POST_RESET_REQUEST: null,\n\n EDIT_POST_REQUEST: null,\n EDIT_POST_SUCCESS: null,\n EDIT_POST_FAILURE: null,\n\n GET_POST_THREAD_REQUEST: null,\n GET_POST_THREAD_SUCCESS: null,\n GET_POST_THREAD_FAILURE: null,\n\n GET_POSTS_SUCCESS: null,\n GET_POSTS_FAILURE: null,\n GET_POSTS_SINCE_SUCCESS: null,\n\n GET_POST_THREAD_WITH_RETRY_ATTEMPT: null,\n GET_POSTS_WITH_RETRY_ATTEMPT: null,\n GET_POSTS_SINCE_WITH_RETRY_ATTEMPT: null,\n GET_POSTS_BEFORE_WITH_RETRY_ATTEMPT: null,\n GET_POSTS_AFTER_WITH_RETRY_ATTEMPT: null,\n\n RECEIVED_POST: null,\n RECEIVED_NEW_POST: null,\n\n RECEIVED_POSTS: null,\n RECEIVED_POSTS_AFTER: null,\n RECEIVED_POSTS_BEFORE: null,\n RECEIVED_POSTS_IN_CHANNEL: null,\n RECEIVED_POSTS_IN_THREAD: null,\n RECEIVED_POSTS_SINCE: null,\n\n POST_DELETED: null,\n POST_REMOVED: null,\n\n RECEIVED_FOCUSED_POST: null,\n RECEIVED_POST_SELECTED: null,\n RECEIVED_EDIT_POST: null,\n RECEIVED_REACTION: null,\n RECEIVED_REACTIONS: null,\n REACTION_DELETED: null,\n RECEIVED_OPEN_GRAPH_METADATA: null,\n\n ADD_MESSAGE_INTO_HISTORY: null,\n RESET_HISTORY_INDEX: null,\n MOVE_HISTORY_INDEX_BACK: null,\n MOVE_HISTORY_INDEX_FORWARD: null,\n\n RESET_POSTS_IN_CHANNEL: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\nexport default keyMirror({\n\n UPLOAD_FILES_REQUEST: null,\n UPLOAD_FILES_SUCCESS: null,\n UPLOAD_FILES_FAILURE: null,\n UPLOAD_FILES_CANCEL: null,\n\n RECEIVED_FILES_FOR_SEARCH: null,\n RECEIVED_FILES_FOR_POST: null,\n RECEIVED_UPLOAD_FILES: null,\n RECEIVED_FILE_PUBLIC_LINK: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n RECEIVED_PREFERENCES: null,\n RECEIVED_ALL_PREFERENCES: null,\n DELETED_PREFERENCES: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n\n RECEIVED_INCOMING_HOOK: null,\n RECEIVED_INCOMING_HOOKS: null,\n DELETED_INCOMING_HOOK: null,\n RECEIVED_OUTGOING_HOOK: null,\n RECEIVED_OUTGOING_HOOKS: null,\n DELETED_OUTGOING_HOOK: null,\n RECEIVED_CUSTOM_TEAM_COMMANDS: null,\n RECEIVED_COMMAND: null,\n RECEIVED_COMMANDS: null,\n RECEIVED_COMMAND_TOKEN: null,\n DELETED_COMMAND: null,\n RECEIVED_OAUTH_APP: null,\n RECEIVED_OAUTH_APPS: null,\n DELETED_OAUTH_APP: null,\n RECEIVED_APPS_OAUTH_APP_IDS: null,\n RECEIVED_APPS_BOT_IDS: null,\n\n RECEIVED_DIALOG_TRIGGER_ID: null,\n RECEIVED_DIALOG: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n CLEAR_CUSTOM_EMOJIS: null,\n RECEIVED_CUSTOM_EMOJI: null,\n RECEIVED_CUSTOM_EMOJIS: null,\n DELETED_CUSTOM_EMOJI: null,\n CUSTOM_EMOJI_DOES_NOT_EXIST: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n\n // General.\n\n SAVE_APP_PROPS: null,\n\n // Search.\n\n SEARCH_REQUEST: null,\n SEARCH_FAILURE: null,\n SEARCH_SUCCESS: null,\n\n SEARCH_BY_ID_REQUEST: null,\n SEARCH_BY_ID_FAILURE: null,\n SEARCH_BY_ID_SUCCESS: null,\n\n SELECT_SEARCH_TEXT: null,\n INVALIDATE_SEARCH_TEXT: null,\n\n REQUEST_SEARCH: null,\n RECEIVE_SEARCH: null,\n RECEIVE_SEARCH_END: null,\n\n RECEIVE_CATEGORY_SEARCH: null,\n\n CLEAR_SEARCH_RESULTS: null,\n\n SAVE_SEARCH_SCROLL_POSITION: null,\n SAVE_SEARCH_PRIOR_LOCATION: null,\n\n UPDATE_SEARCH_TEXT: null,\n SAVE_SEARCH_BAR_TEXT: null,\n\n // Categories.\n\n REQUEST_CATEGORIES_LIST: null,\n CATEGORIES_LIST_RECEIVED: null,\n CATEGORIES_LIST_FAILURE: null,\n\n // Cache.\n\n CACHE_GIFS: null,\n CACHE_REQUEST: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n GET_LOGS_REQUEST: null,\n GET_LOGS_SUCCESS: null,\n GET_LOGS_FAILURE: null,\n\n GET_AUDITS_REQUEST: null,\n GET_AUDITS_SUCCESS: null,\n GET_AUDITS_FAILURE: null,\n\n GET_CONFIG_REQUEST: null,\n GET_CONFIG_SUCCESS: null,\n GET_CONFIG_FAILURE: null,\n\n UPDATE_CONFIG_REQUEST: null,\n UPDATE_CONFIG_SUCCESS: null,\n UPDATE_CONFIG_FAILURE: null,\n\n PATCH_CONFIG_REQUEST: null,\n PATCH_CONFIG_SUCCESS: null,\n PATCH_CONFIG_FAILURE: null,\n\n RELOAD_CONFIG_REQUEST: null,\n RELOAD_CONFIG_SUCCESS: null,\n RELOAD_CONFIG_FAILURE: null,\n\n GET_ENVIRONMENT_CONFIG_REQUEST: null,\n GET_ENVIRONMENT_CONFIG_SUCCESS: null,\n GET_ENVIRONMENT_CONFIG_FAILURE: null,\n\n TEST_EMAIL_REQUEST: null,\n TEST_EMAIL_SUCCESS: null,\n TEST_EMAIL_FAILURE: null,\n\n TEST_SITE_URL_REQUEST: null,\n TEST_SITE_URL_SUCCESS: null,\n TEST_SITE_URL_FAILURE: null,\n\n TEST_S3_REQUEST: null,\n TEST_S3_SUCCESS: null,\n TEST_S3_FAILURE: null,\n\n INVALIDATE_CACHES_REQUEST: null,\n INVALIDATE_CACHES_SUCCESS: null,\n INVALIDATE_CACHES_FAILURE: null,\n\n RECYCLE_DATABASE_REQUEST: null,\n RECYCLE_DATABASE_SUCCESS: null,\n RECYCLE_DATABASE_FAILURE: null,\n\n CREATE_COMPLIANCE_REQUEST: null,\n CREATE_COMPLIANCE_SUCCESS: null,\n CREATE_COMPLIANCE_FAILURE: null,\n\n GET_COMPLIANCE_REQUEST: null,\n GET_COMPLIANCE_SUCCESS: null,\n GET_COMPLIANCE_FAILURE: null,\n\n UPLOAD_BRAND_IMAGE_REQUEST: null,\n UPLOAD_BRAND_IMAGE_SUCCESS: null,\n UPLOAD_BRAND_IMAGE_FAILURE: null,\n\n DELETE_BRAND_IMAGE_REQUEST: null,\n DELETE_BRAND_IMAGE_SUCCESS: null,\n DELETE_BRAND_IMAGE_FAILURE: null,\n\n GET_CLUSTER_STATUS_REQUEST: null,\n GET_CLUSTER_STATUS_SUCCESS: null,\n GET_CLUSTER_STATUS_FAILURE: null,\n\n TEST_LDAP_REQUEST: null,\n TEST_LDAP_SUCCESS: null,\n TEST_LDAP_FAILURE: null,\n\n SYNC_LDAP_REQUEST: null,\n SYNC_LDAP_SUCCESS: null,\n SYNC_LDAP_FAILURE: null,\n\n GET_LDAP_GROUPS_REQUEST: null,\n GET_LDAP_GROUPS_SUCCESS: null,\n GET_LDAP_GROUPS_FAILURE: null,\n\n LINK_LDAP_GROUP_REQUEST: null,\n LINK_LDAP_GROUP_SUCCESS: null,\n LINK_LDAP_GROUP_FAILURE: null,\n\n UNLINK_LDAP_GROUP_REQUEST: null,\n UNLINK_LDAP_GROUP_SUCCESS: null,\n UNLINK_LDAP_GROUP_FAILURE: null,\n\n SAML_CERT_STATUS_REQUEST: null,\n SAML_CERT_STATUS_SUCCESS: null,\n SAML_CERT_STATUS_FAILURE: null,\n\n UPLOAD_SAML_PUBLIC_REQUEST: null,\n UPLOAD_SAML_PUBLIC_SUCCESS: null,\n UPLOAD_SAML_PUBLIC_FAILURE: null,\n\n UPLOAD_SAML_PRIVATE_REQUEST: null,\n UPLOAD_SAML_PRIVATE_SUCCESS: null,\n UPLOAD_SAML_PRIVATE_FAILURE: null,\n\n UPLOAD_SAML_IDP_REQUEST: null,\n UPLOAD_SAML_IDP_SUCCESS: null,\n UPLOAD_SAML_IDP_FAILURE: null,\n\n DELETE_SAML_PUBLIC_REQUEST: null,\n DELETE_SAML_PUBLIC_SUCCESS: null,\n DELETE_SAML_PUBLIC_FAILURE: null,\n\n DELETE_SAML_PRIVATE_REQUEST: null,\n DELETE_SAML_PRIVATE_SUCCESS: null,\n DELETE_SAML_PRIVATE_FAILURE: null,\n\n DELETE_SAML_IDP_REQUEST: null,\n DELETE_SAML_IDP_SUCCESS: null,\n DELETE_SAML_IDP_FAILURE: null,\n\n UPLOAD_LICENSE_REQUEST: null,\n UPLOAD_LICENSE_SUCCESS: null,\n UPLOAD_LICENSE_FAILURE: null,\n\n REMOVE_LICENSE_REQUEST: null,\n REMOVE_LICENSE_SUCCESS: null,\n REMOVE_LICENSE_FAILURE: null,\n\n PREV_TRIAL_LICENSE_SUCCESS: null,\n\n GET_ANALYTICS_REQUEST: null,\n GET_ANALYTICS_SUCCESS: null,\n GET_ANALYTICS_FAILURE: null,\n\n TEST_ELASTICSEARCH_REQUEST: null,\n TEST_ELASTICSEARCH_SUCCESS: null,\n TEST_ELASTICSEARCH_FAILURE: null,\n\n PURGE_ELASTICSEARCH_INDEXES_REQUEST: null,\n PURGE_ELASTICSEARCH_INDEXES_SUCCESS: null,\n PURGE_ELASTICSEARCH_INDEXES_FAILURE: null,\n\n UPLOAD_PLUGIN_REQUEST: null,\n UPLOAD_PLUGIN_SUCCESS: null,\n UPLOAD_PLUGIN_FAILURE: null,\n\n INSTALL_PLUGIN_FROM_URL_REQUEST: null,\n INSTALL_PLUGIN_FROM_URL_SUCCESS: null,\n INSTALL_PLUGIN_FROM_URL_FAILURE: null,\n\n GET_PLUGIN_REQUEST: null,\n GET_PLUGIN_SUCCESS: null,\n GET_PLUGIN_FAILURE: null,\n\n GET_PLUGIN_STATUSES_REQUEST: null,\n GET_PLUGIN_STATUSES_SUCCESS: null,\n GET_PLUGIN_STATUSES_FAILURE: null,\n\n REMOVE_PLUGIN_REQUEST: null,\n REMOVE_PLUGIN_SUCCESS: null,\n REMOVE_PLUGIN_FAILURE: null,\n\n ENABLE_PLUGIN_REQUEST: null,\n ENABLE_PLUGIN_SUCCESS: null,\n ENABLE_PLUGIN_FAILURE: null,\n\n DISABLE_PLUGIN_REQUEST: null,\n DISABLE_PLUGIN_SUCCESS: null,\n DISABLE_PLUGIN_FAILURE: null,\n\n RECEIVED_LOGS: null,\n RECEIVED_AUDITS: null,\n RECEIVED_CONFIG: null,\n RECEIVED_ENVIRONMENT_CONFIG: null,\n RECEIVED_COMPLIANCE_REPORT: null,\n RECEIVED_COMPLIANCE_REPORTS: null,\n RECEIVED_CLUSTER_STATUS: null,\n RECEIVED_SAML_CERT_STATUS: null,\n RECEIVED_SYSTEM_ANALYTICS: null,\n RECEIVED_TEAM_ANALYTICS: null,\n RECEIVED_USER_ACCESS_TOKEN: null,\n RECEIVED_USER_ACCESS_TOKENS: null,\n RECEIVED_USER_ACCESS_TOKENS_FOR_USER: null,\n RECEIVED_PLUGINS: null,\n RECEIVED_PLUGIN_STATUSES: null,\n RECEIVED_LDAP_GROUPS: null,\n LINKED_LDAP_GROUP: null,\n UNLINKED_LDAP_GROUP: null,\n REMOVED_PLUGIN: null,\n ENABLED_PLUGIN: null,\n DISABLED_PLUGIN: null,\n\n RECEIVED_SAML_METADATA_RESPONSE: null,\n SET_SAML_IDP_SUCCESS: null,\n\n UPLOAD_LDAP_PUBLIC_SUCCESS: null,\n UPLOAD_LDAP_PRIVATE_SUCCESS: null,\n DELETE_LDAP_PUBLIC_SUCCESS: null,\n DELETE_LDAP_PRIVATE_SUCCESS: null,\n\n RECEIVED_DATA_RETENTION_CUSTOM_POLICIES: null,\n RECEIVED_DATA_RETENTION_CUSTOM_POLICY: null,\n DELETE_DATA_RETENTION_CUSTOM_POLICY_SUCCESS: null,\n DELETE_DATA_RETENTION_CUSTOM_POLICY_FAILURE: null,\n RECEIVED_DATA_RETENTION_CUSTOM_POLICY_TEAMS: null,\n RECEIVED_DATA_RETENTION_CUSTOM_POLICY_CHANNELS: null,\n RECEIVED_DATA_RETENTION_CUSTOM_POLICY_TEAMS_SEARCH: null,\n RECEIVED_DATA_RETENTION_CUSTOM_POLICY_CHANNELS_SEARCH: null,\n CREATE_DATA_RETENTION_CUSTOM_POLICY_SUCCESS: null,\n UPDATE_DATA_RETENTION_CUSTOM_POLICY_SUCCESS: null,\n ADD_DATA_RETENTION_CUSTOM_POLICY_TEAMS_SUCCESS: null,\n ADD_DATA_RETENTION_CUSTOM_POLICY_CHANNELS_SUCCESS: null,\n REMOVE_DATA_RETENTION_CUSTOM_POLICY_TEAMS_SUCCESS: null,\n REMOVE_DATA_RETENTION_CUSTOM_POLICY_TEAMS_FAILURE: null,\n REMOVE_DATA_RETENTION_CUSTOM_POLICY_CHANNELS_SUCCESS: null,\n REMOVE_DATA_RETENTION_CUSTOM_POLICY_CHANNELS_FAILURE: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n CREATE_JOB_REQUEST: null,\n CREATE_JOB_SUCCESS: null,\n CREATE_JOB_FAILURE: null,\n\n CANCEL_JOB_REQUEST: null,\n CANCEL_JOB_SUCCESS: null,\n CANCEL_JOB_FAILURE: null,\n\n GET_JOB_REQUEST: null,\n GET_JOB_SUCCESS: null,\n GET_JOB_FAILURE: null,\n\n GET_JOBS_REQUEST: null,\n GET_JOBS_SUCCESS: null,\n GET_JOBS_FAILURE: null,\n\n RECEIVED_JOB: null,\n RECEIVED_JOBS: null,\n RECEIVED_JOBS_BY_TYPE: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n SEARCH_POSTS_REQUEST: null,\n SEARCH_POSTS_SUCCESS: null,\n\n SEARCH_FILES_REQUEST: null,\n SEARCH_FILES_SUCCESS: null,\n\n SEARCH_FLAGGED_POSTS_REQUEST: null,\n SEARCH_FLAGGED_POSTS_SUCCESS: null,\n SEARCH_FLAGGED_POSTS_FAILURE: null,\n\n SEARCH_PINNED_POSTS_REQUEST: null,\n SEARCH_PINNED_POSTS_SUCCESS: null,\n SEARCH_PINNED_POSTS_FAILURE: null,\n REMOVE_SEARCH_PINNED_POSTS: null,\n RECEIVED_SEARCH_POSTS: null,\n RECEIVED_SEARCH_FILES: null,\n RECEIVED_SEARCH_FLAGGED_POSTS: null,\n RECEIVED_SEARCH_PINNED_POSTS: null,\n RECEIVED_SEARCH_TERM: null,\n REMOVE_SEARCH_POSTS: null,\n REMOVE_SEARCH_FILES: null,\n REMOVE_SEARCH_TERM: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n ROLES_BY_NAMES_REQUEST: null,\n ROLES_BY_NAMES_SUCCESS: null,\n ROLES_BY_NAMES_FAILURE: null,\n\n ROLE_BY_NAME_REQUEST: null,\n ROLE_BY_NAME_SUCCESS: null,\n ROLE_BY_NAME_FAILURE: null,\n\n ROLE_BY_ID_REQUEST: null,\n ROLE_BY_ID_SUCCESS: null,\n ROLE_BY_ID_FAILURE: null,\n\n EDIT_ROLE_REQUEST: null,\n EDIT_ROLE_SUCCESS: null,\n EDIT_ROLE_FAILURE: null,\n\n RECEIVED_ROLES: null,\n RECEIVED_ROLE: null,\n ROLE_DELETED: null,\n\n SET_PENDING_ROLES: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n RECEIVED_SCHEME: null,\n\n RECEIVED_SCHEMES: null,\n\n CREATED_SCHEME: null,\n\n DELETED_SCHEME: null,\n\n PATCHED_SCHEME: null,\n\n RECEIVED_SCHEME_TEAMS: null,\n\n RECEIVED_SCHEME_CHANNELS: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n LINKED_GROUP_TEAM: null,\n LINKED_GROUP_CHANNEL: null,\n\n UNLINKED_GROUP_TEAM: null,\n UNLINKED_GROUP_CHANNEL: null,\n\n RECEIVED_GROUP_TEAMS: null,\n RECEIVED_GROUP_CHANNELS: null,\n\n RECEIVED_GROUP_STATS: null,\n\n RECEIVED_GROUP: null,\n\n RECEIVED_GROUPS: null,\n\n RECEIVED_GROUP_ASSOCIATED_TO_TEAM: null,\n RECEIVED_GROUPS_ASSOCIATED_TO_TEAM: null,\n\n RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNELS_IN_TEAM: null,\n\n RECEIVED_GROUP_ASSOCIATED_TO_CHANNEL: null,\n RECEIVED_GROUPS_ASSOCIATED_TO_CHANNEL: null,\n\n RECEIVED_ALL_GROUPS_ASSOCIATED_TO_TEAM: null,\n\n RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNEL: null,\n\n RECEIVED_GROUP_NOT_ASSOCIATED_TO_TEAM: null,\n RECEIVED_GROUPS_NOT_ASSOCIATED_TO_TEAM: null,\n\n RECEIVED_GROUP_NOT_ASSOCIATED_TO_CHANNEL: null,\n RECEIVED_GROUPS_NOT_ASSOCIATED_TO_CHANNEL: null,\n\n PATCHED_GROUP_TEAM: null,\n PATCHED_GROUP_CHANNEL: null,\n\n RECEIVED_MY_GROUPS: null,\n\n PATCHED_GROUP: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n RECEIVED_BOT_ACCOUNTS: null,\n RECEIVED_BOT_ACCOUNT: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n RECEIVED_CATEGORY: null,\n RECEIVED_CATEGORIES: null,\n RECEIVED_CATEGORY_ORDER: null,\n\n CATEGORY_COLLAPSED: null,\n CATEGORY_EXPANDED: null,\n\n CATEGORY_DELETED: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n RECEIVED_MARKETPLACE_PLUGINS: null,\n GET_MARKETPLACE_PLUGINS_FAILURE: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n RECEIVED_CLOUD_CUSTOMER: null,\n RECEIVED_CLOUD_PRODUCTS: null,\n RECEIVED_CLOUD_SUBSCRIPTION: null,\n RECEIVED_CLOUD_INVOICES: null,\n RECEIVED_CLOUD_SUBSCRIPTION_STATS: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\n// This file's contents belong to the Apps Framework feature.\n// Apps Framework feature is experimental, and the contents of this file are\n// susceptible to breaking changes without pushing the major version of this package.\nexport default keyMirror({\n RECEIVED_APP_BINDINGS: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport keyMirror from 'mattermost-redux/utils/key_mirror';\n\nexport default keyMirror({\n RECEIVED_THREAD: null,\n RECEIVED_THREADS: null,\n FOLLOW_CHANGED_THREAD: null,\n READ_CHANGED_THREAD: null,\n ALL_TEAM_THREADS_READ: null,\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {ChannelCategoryTypes, ChannelTypes} from 'mattermost-redux/action_types';\n\nimport {Client4} from 'mattermost-redux/client';\n\nimport {logError} from 'mattermost-redux/actions/errors';\nimport {forceLogoutIfNecessary} from 'mattermost-redux/actions/helpers';\n\nimport {General} from '../constants';\nimport {CategoryTypes} from 'mattermost-redux/constants/channel_categories';\n\nimport {\n getAllCategoriesByIds,\n getCategory,\n getCategoryIdsForTeam,\n getCategoryInTeamByType,\n getCategoryInTeamWithChannel,\n} from 'mattermost-redux/selectors/entities/channel_categories';\nimport {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';\n\nimport {\n ActionFunc,\n batchActions,\n DispatchFunc,\n GetStateFunc,\n} from 'mattermost-redux/types/actions';\nimport {CategorySorting, OrderedChannelCategories, ChannelCategory} from 'mattermost-redux/types/channel_categories';\nimport {Channel} from 'mattermost-redux/types/channels';\nimport {$ID} from 'mattermost-redux/types/utilities';\n\nimport {insertMultipleWithoutDuplicates, insertWithoutDuplicates, removeItem} from 'mattermost-redux/utils/array_utils';\n\nexport function expandCategory(categoryId: string) {\n return setCategoryCollapsed(categoryId, false);\n}\n\nexport function collapseCategory(categoryId: string) {\n return setCategoryCollapsed(categoryId, true);\n}\n\nexport function setCategoryCollapsed(categoryId: string, collapsed: boolean) {\n return patchCategory(categoryId, {\n collapsed,\n });\n}\n\nexport function setCategorySorting(categoryId: string, sorting: CategorySorting) {\n return patchCategory(categoryId, {\n sorting,\n });\n}\n\nexport function patchCategory(categoryId: string, patch: Partial): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const currentUserId = getCurrentUserId(state);\n\n const category = getCategory(state, categoryId);\n const patchedCategory = {\n ...category,\n ...patch,\n };\n\n dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORY,\n data: patchedCategory,\n });\n\n try {\n await Client4.updateChannelCategory(currentUserId, category.team_id, patchedCategory);\n } catch (error) {\n dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORY,\n data: category,\n });\n\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n return {data: patchedCategory};\n };\n}\n\nexport function setCategoryMuted(categoryId: string, muted: boolean) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const category = getCategory(state, categoryId);\n\n const result = await dispatch(updateCategory({\n ...category,\n muted,\n }));\n\n if ('error' in result) {\n return result;\n }\n\n const updated = result.data as ChannelCategory;\n\n return dispatch(batchActions([\n {\n type: ChannelCategoryTypes.RECEIVED_CATEGORY,\n data: updated,\n },\n ...(updated.channel_ids.map((channelId) => ({\n type: ChannelTypes.SET_CHANNEL_MUTED,\n data: {\n channelId,\n muted,\n },\n }))),\n ]));\n };\n}\n\nfunction updateCategory(category: ChannelCategory) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const currentUserId = getCurrentUserId(state);\n\n let updatedCategory;\n try {\n updatedCategory = await Client4.updateChannelCategory(currentUserId, category.team_id, category);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n // The updated category will be added to the state after receiving the corresponding websocket event.\n\n return {data: updatedCategory};\n };\n}\n\nexport function fetchMyCategories(teamId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const currentUserId = getCurrentUserId(getState());\n\n let data: OrderedChannelCategories;\n try {\n data = await Client4.getChannelCategories(currentUserId, teamId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n return dispatch(batchActions([\n {\n type: ChannelCategoryTypes.RECEIVED_CATEGORIES,\n data: data.categories,\n },\n {\n type: ChannelCategoryTypes.RECEIVED_CATEGORY_ORDER,\n data: {\n teamId,\n order: data.order,\n },\n },\n ]));\n };\n}\n\n// addChannelToInitialCategory returns an action that can be dispatched to add a newly-joined or newly-created channel\n// to its either the Channels or Direct Messages category based on the type of channel. New DM and GM channels are\n// added to the Direct Messages category on each team.\n//\n// Unless setOnServer is true, this only affects the categories on this client. If it is set to true, this updates\n// categories on the server too.\nexport function addChannelToInitialCategory(channel: Channel, setOnServer = false): ActionFunc {\n return (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const categories = Object.values(getAllCategoriesByIds(state));\n\n if (channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL) {\n const allDmCategories = categories.filter((category) => category.type === CategoryTypes.DIRECT_MESSAGES);\n\n // Get all the categories in which channel exists\n const channelInCategories = categories.filter((category) => {\n return category.channel_ids.findIndex((channelId) => channelId === channel.id) !== -1;\n });\n\n // Skip DM categories where channel already exists in a different category\n const dmCategories = allDmCategories.filter((dmCategory) => {\n return channelInCategories.findIndex((category) => dmCategory.team_id === category.team_id) === -1;\n });\n\n return dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORIES,\n data: dmCategories.map((category) => ({\n ...category,\n channel_ids: insertWithoutDuplicates(category.channel_ids, channel.id, 0),\n })),\n });\n }\n\n // Add the new channel to the Channels category on the channel's team\n if (categories.some((category) => category.channel_ids.some((channelId) => channelId === channel.id))) {\n return {data: false};\n }\n const channelsCategory = getCategoryInTeamByType(state, channel.team_id, CategoryTypes.CHANNELS);\n\n if (!channelsCategory) {\n // No categories were found for this team, so the categories for this team haven't been loaded yet.\n // The channel will have been added to the category by the server, so we'll get it once the categories\n // are actually loaded.\n return {data: false};\n }\n\n if (setOnServer) {\n return dispatch(addChannelToCategory(channelsCategory.id, channel.id));\n }\n\n return dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORY,\n data: {\n ...channelsCategory,\n channel_ids: insertWithoutDuplicates(channelsCategory.channel_ids, channel.id, 0),\n },\n });\n };\n}\n\n// addChannelToCategory returns an action that can be dispatched to add a channel to a given category without specifying\n// its order. The channel will be removed from its previous category (if any) on the given category's team and it will be\n// placed first in its new category.\nexport function addChannelToCategory(categoryId: string, channelId: string): ActionFunc {\n return moveChannelToCategory(categoryId, channelId, 0, false);\n}\n\n// moveChannelToCategory returns an action that moves a channel into a category and puts it at the given index at the\n// category. The channel will also be removed from its previous category (if any) on that category's team. The category's\n// order will also be set to manual by default.\nexport function moveChannelToCategory(categoryId: string, channelId: string, newIndex: number, setManualSorting = true) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const targetCategory = getCategory(state, categoryId);\n const currentUserId = getCurrentUserId(state);\n\n // The default sorting needs to behave like alphabetical sorting until the point that the user rearranges their\n // channels at which point, it becomes manual. Other than that, we never change the sorting method automatically.\n let sorting = targetCategory.sorting;\n if (setManualSorting &&\n targetCategory.type !== CategoryTypes.DIRECT_MESSAGES &&\n targetCategory.sorting === CategorySorting.Default) {\n sorting = CategorySorting.Manual;\n }\n\n // Add the channel to the new category\n const categories = [{\n ...targetCategory,\n sorting,\n channel_ids: insertWithoutDuplicates(targetCategory.channel_ids, channelId, newIndex),\n }];\n\n // And remove it from the old category\n const sourceCategory = getCategoryInTeamWithChannel(getState(), targetCategory.team_id, channelId);\n if (sourceCategory && sourceCategory.id !== targetCategory.id) {\n categories.push({\n ...sourceCategory,\n channel_ids: removeItem(sourceCategory.channel_ids, channelId),\n });\n }\n\n const result = dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORIES,\n data: categories,\n });\n\n try {\n await Client4.updateChannelCategories(currentUserId, targetCategory.team_id, categories);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n\n const originalCategories = [targetCategory];\n if (sourceCategory && sourceCategory.id !== targetCategory.id) {\n originalCategories.push(sourceCategory);\n }\n\n dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORIES,\n data: originalCategories,\n });\n return {error};\n }\n\n return result;\n };\n}\n\nexport function moveChannelsToCategory(categoryId: string, channelIds: string[], newIndex: number, setManualSorting = true) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const targetCategory = getCategory(state, categoryId);\n const currentUserId = getCurrentUserId(state);\n\n // The default sorting needs to behave like alphabetical sorting until the point that the user rearranges their\n // channels at which point, it becomes manual. Other than that, we never change the sorting method automatically.\n let sorting = targetCategory.sorting;\n if (setManualSorting &&\n targetCategory.type !== CategoryTypes.DIRECT_MESSAGES &&\n targetCategory.sorting === CategorySorting.Default) {\n sorting = CategorySorting.Manual;\n }\n\n // Add the channels to the new category\n let categories = {\n [targetCategory.id]: {\n ...targetCategory,\n sorting,\n channel_ids: insertMultipleWithoutDuplicates(targetCategory.channel_ids, channelIds, newIndex),\n },\n };\n\n // Needed if we have to revert categories and for checking for favourites\n let unmodifiedCategories = {[targetCategory.id]: targetCategory};\n let sourceCategories: Record = {};\n\n // And remove it from the old categories\n channelIds.forEach((channelId) => {\n const sourceCategory = getCategoryInTeamWithChannel(getState(), targetCategory.team_id, channelId);\n if (sourceCategory && sourceCategory.id !== targetCategory.id) {\n unmodifiedCategories = {\n ...unmodifiedCategories,\n [sourceCategory.id]: sourceCategory,\n };\n sourceCategories = {...sourceCategories, [channelId]: sourceCategory.id};\n categories = {\n ...categories,\n [sourceCategory.id]: {\n ...(categories[sourceCategory.id] || sourceCategory),\n channel_ids: removeItem((categories[sourceCategory.id] || sourceCategory).channel_ids, channelId),\n },\n };\n }\n });\n\n const categoriesArray = Object.values(categories).reduce((allCategories: ChannelCategory[], category) => {\n allCategories.push(category);\n return allCategories;\n }, []);\n\n const result = dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORIES,\n data: categoriesArray,\n });\n\n try {\n await Client4.updateChannelCategories(currentUserId, targetCategory.team_id, categoriesArray);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n\n const originalCategories = Object.values(unmodifiedCategories).reduce((allCategories: ChannelCategory[], category) => {\n allCategories.push(category);\n return allCategories;\n }, []);\n\n dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORIES,\n data: originalCategories,\n });\n return {error};\n }\n\n return result;\n };\n}\n\nexport function moveCategory(teamId: string, categoryId: string, newIndex: number) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const order = getCategoryIdsForTeam(state, teamId)!;\n const currentUserId = getCurrentUserId(state);\n\n const newOrder = insertWithoutDuplicates(order, categoryId, newIndex);\n\n // Optimistically update the category order\n const result = dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORY_ORDER,\n data: {\n teamId,\n order: newOrder,\n },\n });\n\n try {\n await Client4.updateChannelCategoryOrder(currentUserId, teamId, newOrder);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n\n // Restore original order\n dispatch({\n type: ChannelCategoryTypes.RECEIVED_CATEGORY_ORDER,\n data: {\n teamId,\n order,\n },\n });\n\n return {error};\n }\n\n return result;\n };\n}\n\nexport function receivedCategoryOrder(teamId: string, order: string[]) {\n return {\n type: ChannelCategoryTypes.RECEIVED_CATEGORY_ORDER,\n data: {\n teamId,\n order,\n },\n };\n}\n\nexport function createCategory(teamId: string, displayName: string, channelIds: Array<$ID> = []): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const currentUserId = getCurrentUserId(getState());\n\n let newCategory;\n try {\n newCategory = await Client4.createChannelCategory(currentUserId, teamId, {\n team_id: teamId,\n user_id: currentUserId,\n display_name: displayName,\n channel_ids: channelIds,\n });\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n // The new category will be added to the state after receiving the corresponding websocket event.\n\n return {data: newCategory};\n };\n}\n\nexport function renameCategory(categoryId: string, displayName: string): ActionFunc {\n return patchCategory(categoryId, {\n display_name: displayName,\n });\n}\n\nexport function deleteCategory(categoryId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const category = getCategory(state, categoryId);\n const currentUserId = getCurrentUserId(state);\n\n try {\n await Client4.deleteChannelCategory(currentUserId, category.team_id, category.id);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n // The category will be deleted from the state after receiving the corresponding websocket event.\n\n return {data: true};\n };\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport * as Redux from 'redux';\n\nimport {ChannelTypes, PreferenceTypes, UserTypes} from 'mattermost-redux/action_types';\n\nimport {Client4} from 'mattermost-redux/client';\n\nimport {General, Preferences} from '../constants';\nimport {CategoryTypes} from 'mattermost-redux/constants/channel_categories';\nimport {MarkUnread} from 'mattermost-redux/constants/channels';\n\nimport {getCategoryInTeamByType} from 'mattermost-redux/selectors/entities/channel_categories';\nimport {\n getChannel as getChannelSelector,\n getChannelsNameMapInTeam,\n getMyChannelMember as getMyChannelMemberSelector,\n getRedirectChannelNameForTeam,\n isManuallyUnread,\n} from 'mattermost-redux/selectors/entities/channels';\nimport {getConfig, getServerVersion} from 'mattermost-redux/selectors/entities/general';\nimport {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';\n\nimport {Action, ActionFunc, batchActions, DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';\n\nimport {Channel, ChannelNotifyProps, ChannelMembership, ChannelModerationPatch, ChannelsWithTotalCount, ChannelSearchOpts} from 'mattermost-redux/types/channels';\n\nimport {PreferenceType} from 'mattermost-redux/types/preferences';\n\nimport {getChannelsIdForTeam, getChannelByName} from 'mattermost-redux/utils/channel_utils';\nimport {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';\n\nimport {addChannelToInitialCategory, addChannelToCategory} from './channel_categories';\nimport {logError} from './errors';\nimport {bindClientFunc, forceLogoutIfNecessary} from './helpers';\nimport {savePreferences} from './preferences';\nimport {loadRolesIfNeeded} from './roles';\nimport {getMissingProfilesByIds} from './users';\n\nexport function selectChannel(channelId: string) {\n return {\n type: ChannelTypes.SELECT_CHANNEL,\n data: channelId,\n };\n}\n\nexport function createChannel(channel: Channel, userId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let created;\n try {\n created = await Client4.createChannel(channel);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {\n type: ChannelTypes.CREATE_CHANNEL_FAILURE,\n error,\n },\n logError(error),\n ]));\n return {error};\n }\n\n const member = {\n channel_id: created.id,\n user_id: userId,\n roles: `${General.CHANNEL_USER_ROLE} ${General.CHANNEL_ADMIN_ROLE}`,\n last_viewed_at: 0,\n msg_count: 0,\n mention_count: 0,\n notify_props: {desktop: 'default', mark_unread: 'all'},\n last_update_at: created.create_at,\n };\n\n const actions: Action[] = [];\n const {channels, myMembers} = getState().entities.channels;\n\n if (!channels[created.id]) {\n actions.push({type: ChannelTypes.RECEIVED_CHANNEL, data: created});\n }\n\n if (!myMembers[created.id]) {\n actions.push({type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER, data: member});\n dispatch(loadRolesIfNeeded(member.roles.split(' ')));\n }\n\n dispatch(batchActions([\n ...actions,\n {\n type: ChannelTypes.CREATE_CHANNEL_SUCCESS,\n },\n ]));\n\n dispatch(addChannelToInitialCategory(created, true));\n\n return {data: created};\n };\n}\n\nexport function createDirectChannel(userId: string, otherUserId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.CREATE_CHANNEL_REQUEST, data: null});\n\n let created;\n try {\n created = await Client4.createDirectChannel([userId, otherUserId]);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.CREATE_CHANNEL_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n const member = {\n channel_id: created.id,\n user_id: userId,\n roles: `${General.CHANNEL_USER_ROLE}`,\n last_viewed_at: 0,\n msg_count: 0,\n mention_count: 0,\n notify_props: {desktop: 'default', mark_unread: 'all'},\n last_update_at: created.create_at,\n };\n\n const preferences = [\n {user_id: userId, category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, name: otherUserId, value: 'true'},\n {user_id: userId, category: Preferences.CATEGORY_CHANNEL_OPEN_TIME, name: created.id, value: new Date().getTime().toString()},\n ];\n\n savePreferences(userId, preferences)(dispatch);\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNEL,\n data: created,\n },\n {\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: member,\n },\n {\n type: PreferenceTypes.RECEIVED_PREFERENCES,\n data: preferences,\n },\n {\n type: ChannelTypes.CREATE_CHANNEL_SUCCESS,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL,\n id: created.id,\n data: [{id: userId}, {id: otherUserId}],\n },\n ]));\n\n dispatch(addChannelToInitialCategory(created));\n\n dispatch(loadRolesIfNeeded(member.roles.split(' ')));\n\n return {data: created};\n };\n}\n\nexport function markGroupChannelOpen(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n\n const preferences: PreferenceType[] = [\n {user_id: currentUserId, category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW, name: channelId, value: 'true'},\n {user_id: currentUserId, category: Preferences.CATEGORY_CHANNEL_OPEN_TIME, name: channelId, value: new Date().getTime().toString()},\n ];\n\n return dispatch(savePreferences(currentUserId, preferences));\n };\n}\n\nexport function createGroupChannel(userIds: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.CREATE_CHANNEL_REQUEST, data: null});\n\n const {currentUserId} = getState().entities.users;\n\n let created;\n try {\n created = await Client4.createGroupChannel(userIds);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.CREATE_CHANNEL_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n let member: Partial | undefined = {\n channel_id: created.id,\n user_id: currentUserId,\n roles: `${General.CHANNEL_USER_ROLE}`,\n last_viewed_at: 0,\n msg_count: 0,\n mention_count: 0,\n msg_count_root: 0,\n mention_count_root: 0,\n notify_props: {desktop: 'default', mark_unread: 'all'},\n last_update_at: created.create_at,\n };\n\n // Check the channel previous existency: if the channel already have\n // posts is because it existed before.\n if (created.total_msg_count > 0) {\n const storeMember = getMyChannelMemberSelector(getState(), created.id);\n if (storeMember === null) {\n try {\n member = await Client4.getMyChannelMember(created.id);\n } catch (error) {\n // Log the error and keep going with the generated membership.\n dispatch(logError(error));\n }\n } else {\n member = storeMember;\n }\n }\n\n dispatch(markGroupChannelOpen(created.id));\n\n const profilesInChannel = userIds.map((id) => ({id}));\n profilesInChannel.push({id: currentUserId}); // currentUserId is optionally in userIds, but the reducer will get rid of a duplicate\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNEL,\n data: created,\n },\n {\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: member,\n },\n {\n type: ChannelTypes.CREATE_CHANNEL_SUCCESS,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL,\n id: created.id,\n data: profilesInChannel,\n },\n ]));\n\n dispatch(addChannelToInitialCategory(created));\n\n dispatch(loadRolesIfNeeded((member && member.roles && member.roles.split(' ')) || []));\n\n return {data: created};\n };\n}\n\nexport function patchChannel(channelId: string, patch: Partial): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.UPDATE_CHANNEL_REQUEST, data: null});\n\n let updated;\n try {\n updated = await Client4.patchChannel(channelId, patch);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n dispatch(batchActions([\n {type: ChannelTypes.UPDATE_CHANNEL_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNEL,\n data: updated,\n },\n {\n type: ChannelTypes.UPDATE_CHANNEL_SUCCESS,\n },\n ]));\n\n return {data: updated};\n };\n}\n\nexport function updateChannel(channel: Channel): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.UPDATE_CHANNEL_REQUEST, data: null});\n\n let updated;\n try {\n updated = await Client4.updateChannel(channel);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n dispatch(batchActions([\n {type: ChannelTypes.UPDATE_CHANNEL_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNEL,\n data: updated,\n },\n {\n type: ChannelTypes.UPDATE_CHANNEL_SUCCESS,\n },\n ]));\n\n return {data: updated};\n };\n}\n\nexport function updateChannelPrivacy(channelId: string, privacy: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.UPDATE_CHANNEL_REQUEST, data: null});\n\n let updatedChannel;\n try {\n updatedChannel = await Client4.updateChannelPrivacy(channelId, privacy);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n dispatch(batchActions([\n {type: ChannelTypes.UPDATE_CHANNEL_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNEL,\n data: updatedChannel,\n },\n {\n type: ChannelTypes.UPDATE_CHANNEL_SUCCESS,\n },\n ]));\n\n return {data: updatedChannel};\n };\n}\n\nexport function updateChannelNotifyProps(userId: string, channelId: string, props: ChannelNotifyProps): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const notifyProps = {\n user_id: userId,\n channel_id: channelId,\n ...props,\n };\n\n try {\n await Client4.updateChannelNotifyProps(notifyProps);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n\n return {error};\n }\n\n const member = getState().entities.channels.myMembers[channelId] || {};\n const currentNotifyProps = member.notify_props || {};\n\n dispatch({\n type: ChannelTypes.RECEIVED_CHANNEL_PROPS,\n data: {\n channel_id: channelId,\n notifyProps: {...currentNotifyProps, ...notifyProps},\n },\n });\n\n return {data: true};\n };\n}\n\nexport function getChannelByNameAndTeamName(teamName: string, channelName: string, includeDeleted = false): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getChannelByNameAndTeamName(teamName, channelName, includeDeleted);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch({\n type: ChannelTypes.RECEIVED_CHANNEL,\n data,\n });\n\n return {data};\n };\n}\n\nexport function getChannel(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getChannel(channelId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch({\n type: ChannelTypes.RECEIVED_CHANNEL,\n data,\n });\n\n return {data};\n };\n}\n\nexport function getChannelAndMyMember(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let channel;\n let member;\n try {\n const channelRequest = Client4.getChannel(channelId);\n const memberRequest = Client4.getMyChannelMember(channelId);\n\n channel = await channelRequest;\n member = await memberRequest;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNEL,\n data: channel,\n },\n {\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: member,\n },\n ]));\n dispatch(loadRolesIfNeeded(member.roles.split(' ')));\n\n return {data: {channel, member}};\n };\n}\n\nexport function getChannelTimezones(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let channelTimezones;\n try {\n const channelTimezonesRequest = Client4.getChannelTimezones(channelId);\n\n channelTimezones = await channelTimezonesRequest;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n return {data: channelTimezones};\n };\n}\n\nexport function fetchMyChannelsAndMembers(teamId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({\n type: ChannelTypes.CHANNELS_REQUEST,\n data: null,\n });\n\n let channels;\n let channelMembers;\n const state = getState();\n const shouldFetchArchived = isMinimumServerVersion(getServerVersion(state), 5, 21);\n try {\n const channelRequest = Client4.getMyChannels(teamId, shouldFetchArchived);\n const memberRequest = Client4.getMyChannelMembers(teamId);\n channels = await channelRequest;\n channelMembers = await memberRequest;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n const {currentUserId} = state.entities.users;\n const {currentChannelId} = state.entities.channels;\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNELS,\n teamId,\n data: channels,\n currentChannelId,\n },\n {\n type: ChannelTypes.CHANNELS_SUCCESS,\n },\n {\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS,\n data: channelMembers,\n sync: !shouldFetchArchived,\n channels,\n remove: getChannelsIdForTeam(state, teamId),\n currentUserId,\n currentChannelId,\n },\n ]));\n const roles = new Set();\n for (const member of channelMembers) {\n for (const role of member.roles.split(' ')) {\n roles.add(role);\n }\n }\n if (roles.size > 0) {\n dispatch(loadRolesIfNeeded(roles));\n }\n\n return {data: {channels, members: channelMembers}};\n };\n}\n\nexport function getMyChannelMembers(teamId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let channelMembers;\n try {\n const channelMembersRequest = Client4.getMyChannelMembers(teamId);\n\n channelMembers = await channelMembersRequest;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const state = getState();\n const {currentUserId} = state.entities.users;\n const {currentChannelId} = state.entities.channels;\n\n dispatch({\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS,\n data: channelMembers,\n remove: getChannelsIdForTeam(getState(), teamId),\n currentUserId,\n currentChannelId,\n });\n const roles = new Set();\n\n for (const member of channelMembers) {\n for (const role of member.roles.split(' ')) {\n roles.add(role);\n }\n }\n if (roles.size > 0) {\n dispatch(loadRolesIfNeeded(roles));\n }\n\n return {data: channelMembers};\n };\n}\n\nexport function getChannelMembers(channelId: string, page = 0, perPage: number = General.CHANNELS_CHUNK_SIZE): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let channelMembers: ChannelMembership[];\n\n try {\n const channelMembersRequest = Client4.getChannelMembers(channelId, page, perPage);\n\n channelMembers = await channelMembersRequest;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const userIds = channelMembers.map((cm) => cm.user_id);\n getMissingProfilesByIds(userIds)(dispatch, getState);\n\n dispatch({\n type: ChannelTypes.RECEIVED_CHANNEL_MEMBERS,\n data: channelMembers,\n });\n\n return {data: channelMembers};\n };\n}\n\nexport function leaveChannel(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const {currentUserId} = state.entities.users;\n const {channels, myMembers} = state.entities.channels;\n const channel = channels[channelId];\n const member = myMembers[channelId];\n\n Client4.trackEvent('action', 'action_channels_leave', {channel_id: channelId});\n\n dispatch({\n type: ChannelTypes.LEAVE_CHANNEL,\n data: {\n id: channelId,\n user_id: currentUserId,\n team_id: channel.team_id,\n type: channel.type,\n },\n });\n\n (async function removeFromChannelWrapper() {\n try {\n await Client4.removeFromChannel(currentUserId, channelId);\n } catch {\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNEL,\n data: channel,\n },\n {\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: member,\n },\n ]));\n\n // The category here may not be the one in which the channel was originally located,\n // much less the order in which it was placed. Treating this as a transient issue\n // for the user to resolve by refreshing or leaving again.\n dispatch(addChannelToInitialCategory(channel, false));\n }\n }());\n\n return {data: true};\n };\n}\n\nexport function joinChannel(userId: string, teamId: string, channelId: string, channelName: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n if (!channelId && !channelName) {\n return {data: null};\n }\n\n let member: ChannelMembership | undefined | null;\n let channel: Channel;\n try {\n if (channelId) {\n member = await Client4.addToChannel(userId, channelId);\n channel = await Client4.getChannel(channelId);\n } else {\n channel = await Client4.getChannelByName(teamId, channelName, true);\n if ((channel.type === General.GM_CHANNEL) || (channel.type === General.DM_CHANNEL)) {\n member = await Client4.getChannelMember(channel.id, userId);\n } else {\n member = await Client4.addToChannel(userId, channel.id);\n }\n }\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n Client4.trackEvent('action', 'action_channels_join', {channel_id: channelId});\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNEL,\n data: channel,\n },\n {\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: member,\n },\n ]));\n\n dispatch(addChannelToInitialCategory(channel));\n\n if (member) {\n dispatch(loadRolesIfNeeded(member.roles.split(' ')));\n }\n\n return {data: {channel, member}};\n };\n}\n\nexport function deleteChannel(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let state = getState();\n const viewArchivedChannels = state.entities.general.config.ExperimentalViewArchivedChannels === 'true';\n\n try {\n await Client4.deleteChannel(channelId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n state = getState();\n const {currentChannelId} = state.entities.channels;\n if (channelId === currentChannelId && !viewArchivedChannels) {\n const teamId = getCurrentTeamId(state);\n const channelsInTeam = getChannelsNameMapInTeam(state, teamId);\n const channel = getChannelByName(channelsInTeam, getRedirectChannelNameForTeam(state, teamId));\n if (channel && channel.id) {\n dispatch({type: ChannelTypes.SELECT_CHANNEL, data: channel.id});\n }\n }\n\n dispatch({type: ChannelTypes.DELETE_CHANNEL_SUCCESS, data: {id: channelId, viewArchivedChannels}});\n\n return {data: true};\n };\n}\n\nexport function unarchiveChannel(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.unarchiveChannel(channelId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const state = getState();\n const config = getConfig(state);\n const viewArchivedChannels = config.ExperimentalViewArchivedChannels === 'true';\n dispatch({type: ChannelTypes.UNARCHIVED_CHANNEL_SUCCESS, data: {id: channelId, viewArchivedChannels}});\n\n return {data: true};\n };\n}\n\nexport function viewChannel(channelId: string, prevChannelId = ''): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n\n const {myPreferences} = getState().entities.preferences;\n const viewTimePref = myPreferences[`${Preferences.CATEGORY_CHANNEL_APPROXIMATE_VIEW_TIME}--${channelId}`];\n const viewTime = viewTimePref ? parseInt(viewTimePref.value!, 10) : 0;\n const prevChanManuallyUnread = isManuallyUnread(getState(), prevChannelId);\n\n if (viewTime < new Date().getTime() - (3 * 60 * 60 * 1000)) {\n const preferences = [\n {user_id: currentUserId, category: Preferences.CATEGORY_CHANNEL_APPROXIMATE_VIEW_TIME, name: channelId, value: new Date().getTime().toString()},\n ];\n savePreferences(currentUserId, preferences)(dispatch);\n }\n\n try {\n await Client4.viewMyChannel(channelId, prevChanManuallyUnread ? '' : prevChannelId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n\n return {error};\n }\n\n const actions: Action[] = [];\n\n const {myMembers} = getState().entities.channels;\n const member = myMembers[channelId];\n if (member) {\n if (isManuallyUnread(getState(), channelId)) {\n actions.push({\n type: ChannelTypes.REMOVE_MANUALLY_UNREAD,\n data: {channelId},\n });\n }\n actions.push({\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: {...member, last_viewed_at: new Date().getTime()},\n });\n dispatch(loadRolesIfNeeded(member.roles.split(' ')));\n }\n\n const prevMember = myMembers[prevChannelId];\n if (!prevChanManuallyUnread && prevMember) {\n actions.push({\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: {...prevMember, last_viewed_at: new Date().getTime()},\n });\n dispatch(loadRolesIfNeeded(prevMember.roles.split(' ')));\n }\n\n dispatch(batchActions(actions));\n\n return {data: true};\n };\n}\n\nexport function markChannelAsViewed(channelId: string, prevChannelId?: string): ActionFunc {\n return (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const actions = actionsToMarkChannelAsViewed(getState, channelId, prevChannelId);\n\n return dispatch(batchActions(actions));\n };\n}\n\nexport function actionsToMarkChannelAsViewed(getState: GetStateFunc, channelId: string, prevChannelId = '') {\n const actions: Redux.AnyAction[] = [];\n\n const state = getState();\n const {myMembers} = state.entities.channels;\n\n const member = myMembers[channelId];\n if (member) {\n actions.push({\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: {...member, last_viewed_at: Date.now()},\n });\n\n if (isManuallyUnread(state, channelId)) {\n actions.push({\n type: ChannelTypes.REMOVE_MANUALLY_UNREAD,\n data: {channelId},\n });\n }\n }\n\n const prevMember = myMembers[prevChannelId];\n if (prevMember && !isManuallyUnread(state, prevChannelId)) {\n actions.push({\n type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n data: {...prevMember, last_viewed_at: Date.now()},\n });\n }\n\n return actions;\n}\n\nexport function getChannels(teamId: string, page = 0, perPage: number = General.CHANNELS_CHUNK_SIZE): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.GET_CHANNELS_REQUEST, data: null});\n\n let channels;\n try {\n channels = await Client4.getChannels(teamId, page, perPage);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.GET_CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNELS,\n teamId,\n data: channels,\n },\n {\n type: ChannelTypes.GET_CHANNELS_SUCCESS,\n },\n ]));\n\n return {data: channels};\n };\n}\n\nexport function getArchivedChannels(teamId: string, page = 0, perPage: number = General.CHANNELS_CHUNK_SIZE): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let channels;\n try {\n channels = await Client4.getArchivedChannels(teamId, page, perPage);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n return {error};\n }\n\n dispatch({\n type: ChannelTypes.RECEIVED_CHANNELS,\n teamId,\n data: channels,\n });\n\n return {data: channels};\n };\n}\n\nexport function getAllChannelsWithCount(page = 0, perPage: number = General.CHANNELS_CHUNK_SIZE, notAssociatedToGroup = '', excludeDefaultChannels = false, includeDeleted = false, excludePolicyConstrained = false): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.GET_ALL_CHANNELS_REQUEST, data: null});\n\n let payload;\n try {\n payload = await Client4.getAllChannels(page, perPage, notAssociatedToGroup, excludeDefaultChannels, true, includeDeleted, excludePolicyConstrained) as ChannelsWithTotalCount;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.GET_ALL_CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_ALL_CHANNELS,\n data: payload.channels,\n },\n {\n type: ChannelTypes.GET_ALL_CHANNELS_SUCCESS,\n },\n {\n type: ChannelTypes.RECEIVED_TOTAL_CHANNEL_COUNT,\n data: payload.total_count,\n },\n ]));\n\n return {data: payload};\n };\n}\n\nexport function getAllChannels(page = 0, perPage: number = General.CHANNELS_CHUNK_SIZE, notAssociatedToGroup = '', excludeDefaultChannels = false, excludePolicyConstrained = false): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.GET_ALL_CHANNELS_REQUEST, data: null});\n\n let channels;\n try {\n channels = await Client4.getAllChannels(page, perPage, notAssociatedToGroup, excludeDefaultChannels, false, false, excludePolicyConstrained);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.GET_ALL_CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_ALL_CHANNELS,\n data: channels,\n },\n {\n type: ChannelTypes.GET_ALL_CHANNELS_SUCCESS,\n },\n ]));\n\n return {data: channels};\n };\n}\n\nexport function autocompleteChannels(teamId: string, term: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.GET_CHANNELS_REQUEST, data: null});\n\n let channels;\n try {\n channels = await Client4.autocompleteChannels(teamId, term);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.GET_CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNELS,\n teamId,\n data: channels,\n },\n {\n type: ChannelTypes.GET_CHANNELS_SUCCESS,\n },\n ]));\n\n return {data: channels};\n };\n}\n\nexport function autocompleteChannelsForSearch(teamId: string, term: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.GET_CHANNELS_REQUEST, data: null});\n\n let channels;\n try {\n channels = await Client4.autocompleteChannelsForSearch(teamId, term);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.GET_CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNELS,\n teamId,\n data: channels,\n },\n {\n type: ChannelTypes.GET_CHANNELS_SUCCESS,\n },\n ]));\n\n return {data: channels};\n };\n}\n\nexport function searchChannels(teamId: string, term: string, archived?: boolean): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.GET_CHANNELS_REQUEST, data: null});\n\n let channels;\n try {\n if (archived) {\n channels = await Client4.searchArchivedChannels(teamId, term);\n } else {\n channels = await Client4.searchChannels(teamId, term);\n }\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.GET_CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_CHANNELS,\n teamId,\n data: channels,\n },\n {\n type: ChannelTypes.GET_CHANNELS_SUCCESS,\n },\n ]));\n\n return {data: channels};\n };\n}\n\nexport function searchAllChannels(term: string, opts: ChannelSearchOpts = {}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: ChannelTypes.GET_ALL_CHANNELS_REQUEST, data: null});\n\n let response;\n try {\n response = await Client4.searchAllChannels(term, opts) as ChannelsWithTotalCount;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: ChannelTypes.GET_ALL_CHANNELS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n const channels = response.channels || response;\n\n dispatch(batchActions([\n {\n type: ChannelTypes.RECEIVED_ALL_CHANNELS,\n data: channels,\n },\n {\n type: ChannelTypes.GET_ALL_CHANNELS_SUCCESS,\n },\n ]));\n\n return {data: response};\n };\n}\n\nexport function searchGroupChannels(term: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.searchGroupChannels,\n params: [term],\n });\n}\n\nexport function getChannelStats(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let stat;\n try {\n stat = await Client4.getChannelStats(channelId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: ChannelTypes.RECEIVED_CHANNEL_STATS,\n data: stat,\n });\n\n return {data: stat};\n };\n}\n\nexport function addChannelMember(channelId: string, userId: string, postRootId = ''): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let member;\n try {\n member = await Client4.addToChannel(userId, channelId, postRootId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n Client4.trackEvent('action', 'action_channels_add_member', {channel_id: channelId});\n\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,\n data: {id: channelId, user_id: userId},\n },\n {\n type: ChannelTypes.RECEIVED_CHANNEL_MEMBER,\n data: member,\n },\n {\n type: ChannelTypes.ADD_CHANNEL_MEMBER_SUCCESS,\n id: channelId,\n },\n ], 'ADD_CHANNEL_MEMBER.BATCH'));\n\n return {data: member};\n };\n}\n\nexport function removeChannelMember(channelId: string, userId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.removeFromChannel(userId, channelId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n Client4.trackEvent('action', 'action_channels_remove_member', {channel_id: channelId});\n\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL,\n data: {id: channelId, user_id: userId},\n },\n {\n type: ChannelTypes.REMOVE_CHANNEL_MEMBER_SUCCESS,\n id: channelId,\n },\n ], 'REMOVE_CHANNEL_MEMBER.BATCH'));\n\n return {data: true};\n };\n}\n\nexport function updateChannelMemberRoles(channelId: string, userId: string, roles: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateChannelMemberRoles(channelId, userId, roles);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const membersInChannel = getState().entities.channels.membersInChannel[channelId];\n if (membersInChannel && membersInChannel[userId]) {\n dispatch({\n type: ChannelTypes.RECEIVED_CHANNEL_MEMBER,\n data: {...membersInChannel[userId], roles},\n });\n }\n\n return {data: true};\n };\n}\n\nexport function updateChannelHeader(channelId: string, header: string): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n Client4.trackEvent('action', 'action_channels_update_header', {channel_id: channelId});\n\n dispatch({\n type: ChannelTypes.UPDATE_CHANNEL_HEADER,\n data: {\n channelId,\n header,\n },\n });\n\n return {data: true};\n };\n}\n\nexport function updateChannelPurpose(channelId: string, purpose: string): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n Client4.trackEvent('action', 'action_channels_update_purpose', {channel_id: channelId});\n\n dispatch({\n type: ChannelTypes.UPDATE_CHANNEL_PURPOSE,\n data: {\n channelId,\n purpose,\n },\n });\n\n return {data: true};\n };\n}\n\nexport function markChannelAsRead(channelId: string, prevChannelId?: string, updateLastViewedAt = true): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const prevChanManuallyUnread = isManuallyUnread(state, prevChannelId);\n\n // Send channel last viewed at to the server\n if (updateLastViewedAt) {\n dispatch(markChannelAsReadOnServer(channelId, prevChanManuallyUnread ? '' : prevChannelId));\n }\n\n const actions = actionsToMarkChannelAsRead(getState, channelId, prevChannelId);\n\n if (actions.length > 0) {\n dispatch(batchActions(actions));\n }\n\n return {data: true};\n };\n}\n\nexport function markChannelAsReadOnServer(channelId: string, prevChannelId?: string): ActionFunc {\n return (dispatch: DispatchFunc, getState: GetStateFunc) => {\n Client4.viewMyChannel(channelId, prevChannelId).then().catch((error) => {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n });\n\n return {data: true};\n };\n}\n\nexport function actionsToMarkChannelAsRead(getState: GetStateFunc, channelId: string, prevChannelId?: string) {\n const state = getState();\n const {channels, myMembers} = state.entities.channels;\n\n const prevChanManuallyUnread = isManuallyUnread(state, prevChannelId);\n\n // Update channel member objects to set all mentions and posts as viewed\n const channel = channels[channelId];\n const prevChannel = (!prevChanManuallyUnread && prevChannelId) ? channels[prevChannelId] : null; // May be null since prevChannelId is optional\n\n // Update team member objects to set mentions and posts in channel as viewed\n const channelMember = myMembers[channelId];\n const prevChannelMember = (!prevChanManuallyUnread && prevChannelId) ? myMembers[prevChannelId] : null; // May also be null\n\n const actions: Redux.AnyAction[] = [];\n\n if (channel && channelMember) {\n actions.push({\n type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT,\n data: {\n teamId: channel.team_id,\n channelId,\n amount: channel.total_msg_count - channelMember.msg_count,\n amountRoot: channel.total_msg_count_root - channelMember.msg_count_root,\n },\n });\n\n actions.push({\n type: ChannelTypes.DECREMENT_UNREAD_MENTION_COUNT,\n data: {\n teamId: channel.team_id,\n channelId,\n amount: channelMember.mention_count,\n amountRoot: channelMember.mention_count_root,\n },\n });\n }\n\n if (channel && isManuallyUnread(getState(), channelId)) {\n actions.push({\n type: ChannelTypes.REMOVE_MANUALLY_UNREAD,\n data: {channelId},\n });\n }\n\n if (prevChannel && prevChannelMember) {\n actions.push({\n type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT,\n data: {\n teamId: prevChannel.team_id,\n channelId: prevChannelId,\n amount: prevChannel.total_msg_count - prevChannelMember.msg_count,\n amountRoot: prevChannel.total_msg_count_root - prevChannelMember.msg_count_root,\n },\n });\n\n actions.push({\n type: ChannelTypes.DECREMENT_UNREAD_MENTION_COUNT,\n data: {\n teamId: prevChannel.team_id,\n channelId: prevChannelId,\n amount: prevChannelMember.mention_count,\n amountRoot: prevChannelMember.mention_count_root,\n },\n });\n }\n\n return actions;\n}\n\n// Increments the number of posts in the channel by 1 and marks it as unread if necessary\nexport function markChannelAsUnread(teamId: string, channelId: string, mentions: string[], fetchedChannelMember = false, isRoot = false): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const {myMembers} = state.entities.channels;\n const {currentUserId} = state.entities.users;\n\n const actions: Action[] = [{\n type: ChannelTypes.INCREMENT_UNREAD_MSG_COUNT,\n data: {\n teamId,\n channelId,\n amount: 1,\n amountRoot: isRoot ? 1 : 0,\n onlyMentions: myMembers[channelId] && myMembers[channelId].notify_props &&\n myMembers[channelId].notify_props.mark_unread === MarkUnread.MENTION,\n fetchedChannelMember,\n },\n }];\n\n if (!fetchedChannelMember) {\n actions.push({\n type: ChannelTypes.INCREMENT_TOTAL_MSG_COUNT,\n data: {\n channelId,\n amountRoot: isRoot ? 1 : 0,\n amount: 1,\n },\n });\n }\n\n if (mentions && mentions.indexOf(currentUserId) !== -1) {\n actions.push({\n type: ChannelTypes.INCREMENT_UNREAD_MENTION_COUNT,\n data: {\n teamId,\n channelId,\n amountRoot: isRoot ? 1 : 0,\n amount: 1,\n fetchedChannelMember,\n },\n });\n }\n\n dispatch(batchActions(actions));\n\n return {data: true};\n };\n}\n\nexport function actionsToMarkChannelAsUnread(getState: GetStateFunc, teamId: string, channelId: string, mentions: string[], fetchedChannelMember = false, isRoot = false) {\n const state = getState();\n const {myMembers} = state.entities.channels;\n const {currentUserId} = state.entities.users;\n\n const actions: Redux.AnyAction[] = [{\n type: ChannelTypes.INCREMENT_UNREAD_MSG_COUNT,\n data: {\n teamId,\n channelId,\n amount: 1,\n amountRoot: isRoot ? 1 : 0,\n onlyMentions: myMembers[channelId] && myMembers[channelId].notify_props &&\n myMembers[channelId].notify_props.mark_unread === MarkUnread.MENTION,\n fetchedChannelMember,\n },\n }];\n\n if (!fetchedChannelMember) {\n actions.push({\n type: ChannelTypes.INCREMENT_TOTAL_MSG_COUNT,\n data: {\n channelId,\n amountRoot: isRoot ? 1 : 0,\n amount: 1,\n },\n });\n }\n\n if (mentions && mentions.indexOf(currentUserId) !== -1) {\n actions.push({\n type: ChannelTypes.INCREMENT_UNREAD_MENTION_COUNT,\n data: {\n teamId,\n channelId,\n amountRoot: isRoot ? 1 : 0,\n amount: 1,\n fetchedChannelMember,\n },\n });\n }\n\n return actions;\n}\n\nexport function getChannelMembersByIds(channelId: string, userIds: string[]) {\n return bindClientFunc({\n clientFunc: Client4.getChannelMembersByIds,\n onSuccess: ChannelTypes.RECEIVED_CHANNEL_MEMBERS,\n params: [\n channelId,\n userIds,\n ],\n });\n}\n\nexport function getChannelMember(channelId: string, userId: string) {\n return bindClientFunc({\n clientFunc: Client4.getChannelMember,\n onSuccess: ChannelTypes.RECEIVED_CHANNEL_MEMBER,\n params: [\n channelId,\n userId,\n ],\n });\n}\n\nexport function getMyChannelMember(channelId: string) {\n return bindClientFunc({\n clientFunc: Client4.getMyChannelMember,\n onSuccess: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,\n params: [\n channelId,\n ],\n });\n}\n\n// favoriteChannel moves the provided channel into the current team's Favorites category.\nexport function favoriteChannel(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const channel = getChannelSelector(state, channelId);\n const category = getCategoryInTeamByType(state, channel.team_id || getCurrentTeamId(state), CategoryTypes.FAVORITES);\n\n Client4.trackEvent('action', 'action_channels_favorite');\n\n if (!category) {\n return {data: false};\n }\n\n return dispatch(addChannelToCategory(category.id, channelId));\n };\n}\n\n// unfavoriteChannel moves the provided channel into the current team's Channels/DMs category.\nexport function unfavoriteChannel(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const channel = getChannelSelector(state, channelId);\n const category = getCategoryInTeamByType(\n state,\n channel.team_id || getCurrentTeamId(state),\n channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL ? CategoryTypes.DIRECT_MESSAGES : CategoryTypes.CHANNELS,\n );\n\n Client4.trackEvent('action', 'action_channels_unfavorite');\n\n if (!category) {\n return {data: false};\n }\n\n return dispatch(addChannelToCategory(category.id, channel.id));\n };\n}\n\nexport function updateChannelScheme(channelId: string, schemeId: string) {\n return bindClientFunc({\n clientFunc: async () => {\n await Client4.updateChannelScheme(channelId, schemeId);\n return {channelId, schemeId};\n },\n onSuccess: ChannelTypes.UPDATED_CHANNEL_SCHEME,\n });\n}\n\nexport function updateChannelMemberSchemeRoles(channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) {\n return bindClientFunc({\n clientFunc: async () => {\n await Client4.updateChannelMemberSchemeRoles(channelId, userId, isSchemeUser, isSchemeAdmin);\n return {channelId, userId, isSchemeUser, isSchemeAdmin};\n },\n onSuccess: ChannelTypes.UPDATED_CHANNEL_MEMBER_SCHEME_ROLES,\n });\n}\n\nexport function membersMinusGroupMembers(channelID: string, groupIDs: string[], page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.channelMembersMinusGroupMembers,\n onSuccess: ChannelTypes.RECEIVED_CHANNEL_MEMBERS_MINUS_GROUP_MEMBERS,\n params: [\n channelID,\n groupIDs,\n page,\n perPage,\n ],\n });\n}\n\nexport function getChannelModerations(channelId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: async () => {\n const moderations = await Client4.getChannelModerations(channelId);\n return {channelId, moderations};\n },\n onSuccess: ChannelTypes.RECEIVED_CHANNEL_MODERATIONS,\n params: [\n channelId,\n ],\n });\n}\n\nexport function patchChannelModerations(channelId: string, patch: ChannelModerationPatch[]): ActionFunc {\n return bindClientFunc({\n clientFunc: async () => {\n const moderations = await Client4.patchChannelModerations(channelId, patch);\n return {channelId, moderations};\n },\n onSuccess: ChannelTypes.RECEIVED_CHANNEL_MODERATIONS,\n params: [\n channelId,\n ],\n });\n}\n\nexport function getChannelMemberCountsByGroup(channelId: string, includeTimezones: boolean): ActionFunc {\n return bindClientFunc({\n clientFunc: async () => {\n const channelMemberCountsByGroup = await Client4.getChannelMemberCountsByGroup(channelId, includeTimezones);\n return {channelId, memberCounts: channelMemberCountsByGroup};\n },\n onSuccess: ChannelTypes.RECEIVED_CHANNEL_MEMBER_COUNTS_BY_GROUP,\n params: [\n channelId,\n ],\n });\n}\n\nexport default {\n selectChannel,\n createChannel,\n createDirectChannel,\n updateChannel,\n patchChannel,\n updateChannelNotifyProps,\n getChannel,\n fetchMyChannelsAndMembers,\n getMyChannelMembers,\n getChannelTimezones,\n getChannelMembersByIds,\n leaveChannel,\n joinChannel,\n deleteChannel,\n unarchiveChannel,\n viewChannel,\n markChannelAsViewed,\n getChannels,\n autocompleteChannels,\n autocompleteChannelsForSearch,\n searchChannels,\n searchGroupChannels,\n getChannelStats,\n addChannelMember,\n removeChannelMember,\n updateChannelHeader,\n updateChannelPurpose,\n markChannelAsRead,\n markChannelAsUnread,\n favoriteChannel,\n unfavoriteChannel,\n membersMinusGroupMembers,\n getChannelModerations,\n getChannelMemberCountsByGroup,\n};\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Client4} from 'mattermost-redux/client';\nimport {EmojiTypes} from 'mattermost-redux/action_types';\nimport {General, Emoji} from '../constants';\n\nimport {getCustomEmojisByName as selectCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';\nimport {parseNeededCustomEmojisFromText} from 'mattermost-redux/utils/emoji_utils';\n\nimport {GetStateFunc, DispatchFunc, ActionFunc, ActionResult} from 'mattermost-redux/types/actions';\n\nimport {SystemEmoji, CustomEmoji} from 'mattermost-redux/types/emojis';\n\nimport {Dictionary} from 'mattermost-redux/types/utilities';\n\nimport {logError} from './errors';\nimport {bindClientFunc, forceLogoutIfNecessary} from './helpers';\n\nimport {getProfilesByIds} from './users';\nexport let systemEmojis: Map = new Map();\nexport function setSystemEmojis(emojis: Map) {\n systemEmojis = emojis;\n}\n\nexport function createCustomEmoji(emoji: any, image: any): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.createCustomEmoji,\n onSuccess: EmojiTypes.RECEIVED_CUSTOM_EMOJI,\n params: [\n emoji,\n image,\n ],\n });\n}\n\nexport function getCustomEmoji(emojiId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getCustomEmoji,\n onSuccess: EmojiTypes.RECEIVED_CUSTOM_EMOJI,\n params: [\n emojiId,\n ],\n });\n}\n\nexport function getCustomEmojiByName(name: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n\n try {\n data = await Client4.getCustomEmojiByName(name);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n if (error.status_code === 404) {\n dispatch({type: EmojiTypes.CUSTOM_EMOJI_DOES_NOT_EXIST, data: name});\n } else {\n dispatch(logError(error));\n }\n\n return {error};\n }\n\n dispatch({\n type: EmojiTypes.RECEIVED_CUSTOM_EMOJI,\n data,\n });\n\n return {data};\n };\n}\n\nexport function getCustomEmojisByName(names: string[]): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n if (!names || names.length === 0) {\n return {data: true};\n }\n\n const promises: Array> = [];\n names.forEach((name) => promises.push(dispatch(getCustomEmojiByName(name))));\n\n await Promise.all(promises);\n return {data: true};\n };\n}\n\nexport function getCustomEmojisInText(text: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n if (!text) {\n return {data: true};\n }\n\n const state = getState();\n const nonExistentEmoji = state.entities.emojis.nonExistentEmoji;\n const customEmojisByName = selectCustomEmojisByName(state);\n\n const emojisToLoad = parseNeededCustomEmojisFromText(text, systemEmojis, customEmojisByName, nonExistentEmoji);\n\n return getCustomEmojisByName(Array.from(emojisToLoad))(dispatch, getState);\n };\n}\n\nexport function getCustomEmojis(\n page = 0,\n perPage: number = General.PAGE_SIZE_DEFAULT,\n sort: string = Emoji.SORT_BY_NAME,\n loadUsers = false,\n): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getCustomEmojis(page, perPage, sort);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n dispatch(logError(error));\n return {error};\n }\n\n if (loadUsers) {\n dispatch(loadProfilesForCustomEmojis(data));\n }\n\n dispatch({\n type: EmojiTypes.RECEIVED_CUSTOM_EMOJIS,\n data,\n });\n\n return {data};\n };\n}\n\nexport function loadProfilesForCustomEmojis(emojis: CustomEmoji[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const usersToLoad: Dictionary = {};\n emojis.forEach((emoji: CustomEmoji) => {\n if (!getState().entities.users.profiles[emoji.creator_id]) {\n usersToLoad[emoji.creator_id] = true;\n }\n });\n\n const userIds = Object.keys(usersToLoad);\n\n if (userIds.length > 0) {\n await dispatch(getProfilesByIds(userIds));\n }\n\n return {data: true};\n };\n}\n\nexport function getAllCustomEmojis(perPage: number = General.PAGE_SIZE_MAXIMUM): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({\n type: EmojiTypes.CLEAR_CUSTOM_EMOJIS,\n data: null,\n });\n\n let hasMore = true;\n let page = 0;\n const allEmojis = [];\n\n do {\n try {\n let emojis = [];\n emojis = await Client4.getCustomEmojis(page, perPage, Emoji.SORT_BY_NAME); // eslint-disable-line no-await-in-loop\n if (emojis.length < perPage) {\n hasMore = false;\n } else {\n page += 1;\n }\n allEmojis.push(...emojis);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n dispatch(logError(error));\n return {error: true};\n }\n } while (hasMore);\n\n dispatch({\n type: EmojiTypes.RECEIVED_CUSTOM_EMOJIS,\n data: allEmojis,\n });\n\n return {data: true};\n };\n}\n\nexport function deleteCustomEmoji(emojiId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.deleteCustomEmoji(emojiId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: EmojiTypes.DELETED_CUSTOM_EMOJI,\n data: {id: emojiId},\n });\n\n return {data: true};\n };\n}\n\nexport function searchCustomEmojis(term: string, options: any = {}, loadUsers = false): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.searchCustomEmoji(term, options);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n dispatch(logError(error));\n return {error};\n }\n\n if (loadUsers) {\n dispatch(loadProfilesForCustomEmojis(data));\n }\n\n dispatch({\n type: EmojiTypes.RECEIVED_CUSTOM_EMOJIS,\n data,\n });\n\n return {data};\n };\n}\n\nexport function autocompleteCustomEmojis(name: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.autocompleteCustomEmoji(name);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: EmojiTypes.RECEIVED_CUSTOM_EMOJIS,\n data,\n });\n\n return {data};\n };\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {serializeError, ErrorObject} from 'serialize-error';\n\nimport {ErrorTypes} from 'mattermost-redux/action_types';\nimport {Client4} from 'mattermost-redux/client';\nimport EventEmitter from 'mattermost-redux/utils/event_emitter';\nimport {DispatchFunc, ActionFunc} from 'mattermost-redux/types/actions';\nimport {ServerError} from 'mattermost-redux/types/errors';\n\nexport function dismissErrorObject(index: number) {\n return {\n type: ErrorTypes.DISMISS_ERROR,\n index,\n data: null,\n };\n}\n\nexport function dismissError(index: number): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n dispatch(dismissErrorObject(index));\n\n return {data: true};\n };\n}\n\nexport function getLogErrorAction(error: ErrorObject, displayable = false) {\n return {\n type: ErrorTypes.LOG_ERROR,\n displayable,\n error,\n data: null,\n };\n}\n\nexport function logError(error: ServerError, displayable = false): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n if (error.server_error_id === 'api.context.session_expired.app_error') {\n return {data: true};\n }\n\n const serializedError = serializeError(error);\n\n let sendToServer = true;\n if (error.stack && error.stack.includes('TypeError: Failed to fetch')) {\n sendToServer = false;\n }\n if (error.server_error_id) {\n sendToServer = false;\n }\n\n if (sendToServer) {\n try {\n const stringifiedSerializedError = JSON.stringify(serializedError).toString();\n await Client4.logClientError(stringifiedSerializedError);\n } catch (err) {\n // avoid crashing the app if an error sending\n // the error occurs.\n }\n }\n\n EventEmitter.emit(ErrorTypes.LOG_ERROR, error);\n dispatch(getLogErrorAction(serializedError, displayable));\n\n return {data: true};\n };\n}\n\nexport function clearErrors(): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n dispatch({type: ErrorTypes.CLEAR_ERRORS, data: null});\n\n return {data: true};\n };\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Client4} from 'mattermost-redux/client';\nimport {FileTypes} from 'mattermost-redux/action_types';\n\nimport {batchActions, DispatchFunc, GetStateFunc, ActionFunc, Action} from 'mattermost-redux/types/actions';\n\nimport {FileUploadResponse, FileSearchResultItem} from 'mattermost-redux/types/files';\n\nimport {logError} from './errors';\nimport {bindClientFunc, forceLogoutIfNecessary} from './helpers';\n\nexport function receivedFiles(files: Map): Action {\n return {\n type: FileTypes.RECEIVED_FILES_FOR_SEARCH,\n data: files,\n };\n}\n\nexport function getFilesForPost(postId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let files;\n\n try {\n files = await Client4.getFileInfosForPost(postId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: FileTypes.RECEIVED_FILES_FOR_POST,\n data: files,\n postId,\n });\n\n return {data: true};\n };\n}\n\nexport function uploadFile(channelId: string, rootId: string, clientIds: string[], fileFormData: File, formBoundary: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: FileTypes.UPLOAD_FILES_REQUEST, data: {}});\n\n let files: FileUploadResponse;\n try {\n files = await Client4.uploadFile(fileFormData, formBoundary);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n\n const failure = {\n type: FileTypes.UPLOAD_FILES_FAILURE,\n clientIds,\n channelId,\n rootId,\n error,\n };\n\n dispatch(batchActions([failure, logError(error)]));\n return {error};\n }\n\n const data = files.file_infos.map((file, index) => {\n return {\n ...file,\n clientId: files.client_ids[index],\n };\n });\n\n dispatch(batchActions([\n {\n type: FileTypes.RECEIVED_UPLOAD_FILES,\n data,\n channelId,\n rootId,\n },\n {\n type: FileTypes.UPLOAD_FILES_SUCCESS,\n },\n ]));\n\n return {data: files};\n };\n}\n\nexport function getFilePublicLink(fileId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getFilePublicLink,\n onSuccess: FileTypes.RECEIVED_FILE_PUBLIC_LINK,\n params: [\n fileId,\n ],\n });\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Client4} from 'mattermost-redux/client';\n\nimport {GeneralTypes} from 'mattermost-redux/action_types';\n\nimport {getServerVersion} from 'mattermost-redux/selectors/entities/general';\nimport {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';\nimport {GeneralState} from 'mattermost-redux/types/general';\nimport {LogLevel} from 'mattermost-redux/types/client4';\nimport {GetStateFunc, DispatchFunc, ActionFunc, batchActions} from 'mattermost-redux/types/actions';\n\nimport {logError} from './errors';\nimport {loadRolesIfNeeded} from './roles';\nimport {loadMe} from './users';\nimport {bindClientFunc, forceLogoutIfNecessary, FormattedError} from './helpers';\n\nexport function getPing(): ActionFunc {\n return async () => {\n let data;\n let pingError = new FormattedError(\n 'mobile.server_ping_failed',\n 'Cannot connect to the server. Please check your server URL and internet connection.',\n );\n try {\n data = await Client4.ping();\n if (data.status !== 'OK') {\n // successful ping but not the right return {data}\n return {error: pingError};\n }\n } catch (error) { // Client4Error\n if (error.status_code === 401) {\n // When the server requires a client certificate to connect.\n pingError = error;\n }\n return {error: pingError};\n }\n\n return {data};\n };\n}\n\nexport function resetPing(): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n dispatch({type: GeneralTypes.PING_RESET, data: {}});\n\n return {data: true};\n };\n}\n\nexport function getClientConfig(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getClientConfigOld();\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n return {error};\n }\n\n Client4.setEnableLogging(data.EnableDeveloper === 'true');\n Client4.setDiagnosticId(data.DiagnosticId);\n\n dispatch(batchActions([\n {type: GeneralTypes.CLIENT_CONFIG_RECEIVED, data},\n ]));\n\n return {data};\n };\n}\n\nexport function getDataRetentionPolicy(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getDataRetentionPolicy();\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {\n type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY,\n error,\n },\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY, data},\n ]));\n\n return {data};\n };\n}\n\nexport function getLicenseConfig(): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getClientLicenseOld,\n onSuccess: [GeneralTypes.CLIENT_LICENSE_RECEIVED],\n });\n}\n\nexport function logClientError(message: string, level: LogLevel = 'ERROR') {\n return bindClientFunc({\n clientFunc: Client4.logClientError,\n onRequest: GeneralTypes.LOG_CLIENT_ERROR_REQUEST,\n onSuccess: GeneralTypes.LOG_CLIENT_ERROR_SUCCESS,\n onFailure: GeneralTypes.LOG_CLIENT_ERROR_FAILURE,\n params: [\n message,\n level,\n ],\n });\n}\n\nexport function setAppState(state: GeneralState['appState']): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n dispatch({type: GeneralTypes.RECEIVED_APP_STATE, data: state});\n\n return {data: true};\n };\n}\n\nexport function setDeviceToken(token: GeneralState['deviceToken']): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n dispatch({type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN, data: token});\n\n return {data: true};\n };\n}\n\nexport function setServerVersion(serverVersion: string): ActionFunc {\n return async (dispatch) => {\n dispatch({type: GeneralTypes.RECEIVED_SERVER_VERSION, data: serverVersion});\n dispatch(loadRolesIfNeeded([]));\n\n return {data: true};\n };\n}\n\nexport function setStoreFromLocalData(data: { token: string; url: string }): ActionFunc {\n return async (dispatch: DispatchFunc, getState) => {\n Client4.setToken(data.token);\n Client4.setUrl(data.url);\n\n return loadMe()(dispatch, getState);\n };\n}\n\nexport function setUrl(url: string) {\n Client4.setUrl(url);\n return true;\n}\n\nexport function getRedirectLocation(url: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let pendingData: Promise;\n if (isMinimumServerVersion(getServerVersion(getState()), 5, 3)) {\n pendingData = Client4.getRedirectLocation(url);\n } else {\n pendingData = Promise.resolve({location: url});\n }\n\n let data;\n try {\n data = await pendingData;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch({type: GeneralTypes.REDIRECT_LOCATION_FAILURE, data: {error, url}});\n return {error};\n }\n\n dispatch({type: GeneralTypes.REDIRECT_LOCATION_SUCCESS, data: {...data, url}});\n return {data};\n };\n}\n\nexport function getWarnMetricsStatus(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getWarnMetricsStatus();\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n return {error};\n }\n dispatch({type: GeneralTypes.WARN_METRICS_STATUS_RECEIVED, data});\n\n return {data};\n };\n}\n\nexport function setFirstAdminVisitMarketplaceStatus(): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n try {\n await Client4.setFirstAdminVisitMarketplaceStatus();\n } catch (e) {\n dispatch(logError(e));\n return {error: e.message};\n }\n dispatch({type: GeneralTypes.FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED, data: true});\n return {data: true};\n };\n}\n\nexport function getFirstAdminVisitMarketplaceStatus(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getFirstAdminVisitMarketplaceStatus();\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n return {error};\n }\n\n data = JSON.parse(data.value);\n dispatch({type: GeneralTypes.FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED, data});\n return {data};\n };\n}\n\nexport default {\n getPing,\n getClientConfig,\n getDataRetentionPolicy,\n getLicenseConfig,\n logClientError,\n setAppState,\n setDeviceToken,\n setServerVersion,\n setStoreFromLocalData,\n setUrl,\n getRedirectLocation,\n getWarnMetricsStatus,\n getFirstAdminVisitMarketplaceStatus,\n};\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Client4} from 'mattermost-redux/client';\nimport {UserTypes} from 'mattermost-redux/action_types';\n\nimport {Client4Error} from 'mattermost-redux/types/client4';\nimport {batchActions, Action, ActionFunc, GenericAction, DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';\n\nimport {logError} from './errors';\ntype ActionType = string;\nconst HTTP_UNAUTHORIZED = 401;\nexport function forceLogoutIfNecessary(err: Client4Error, dispatch: DispatchFunc, getState: GetStateFunc) {\n const {currentUserId} = getState().entities.users;\n\n if ('status_code' in err && err.status_code === HTTP_UNAUTHORIZED && err.url && err.url.indexOf('/login') === -1 && currentUserId) {\n Client4.setToken('');\n dispatch({type: UserTypes.LOGOUT_SUCCESS, data: {}});\n }\n}\n\nfunction dispatcher(type: ActionType, data: any, dispatch: DispatchFunc) {\n if (type.indexOf('SUCCESS') === -1) { // we don't want to pass the data for the request types\n dispatch(requestSuccess(type, data));\n } else {\n dispatch(requestData(type));\n }\n}\n\nexport function requestData(type: ActionType): GenericAction {\n return {\n type,\n data: null,\n };\n}\n\nexport function requestSuccess(type: ActionType, data: any) {\n return {\n type,\n data,\n };\n}\n\nexport function requestFailure(type: ActionType, error: Client4Error): any {\n return {\n type,\n error,\n };\n}\n\n/**\n * Returns an ActionFunc which calls a specfied (client) function and\n * dispatches the specifed actions on request, success or failure.\n *\n * @export\n * @param {Object} obj an object for destructirung required properties\n * @param {() => Promise} obj.clientFunc clientFunc to execute\n * @param {ActionType} obj.onRequest ActionType to dispatch on request\n * @param {(ActionType | Array)} obj.onSuccess ActionType to dispatch on success\n * @param {ActionType} obj.onFailure ActionType to dispatch on failure\n * @param {...Array} obj.params\n * @returns {ActionFunc} ActionFunc\n */\n\nexport function bindClientFunc({\n clientFunc,\n onRequest,\n onSuccess,\n onFailure,\n params = [],\n}: {\n clientFunc: (...args: any[]) => Promise;\n onRequest?: ActionType;\n onSuccess?: ActionType | ActionType[];\n onFailure?: ActionType;\n params?: any[];\n}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n if (onRequest) {\n dispatch(requestData(onRequest));\n }\n\n let data: any = null;\n try {\n data = await clientFunc(...params);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n const actions: Action[] = [logError(error)];\n if (onFailure) {\n actions.push(requestFailure(onFailure, error));\n }\n dispatch(batchActions(actions));\n return {error};\n }\n\n if (Array.isArray(onSuccess)) {\n onSuccess.forEach((s) => {\n dispatcher(s, data, dispatch);\n });\n } else if (onSuccess) {\n dispatcher(onSuccess, data, dispatch);\n }\n\n return {data};\n };\n}\n\n// Debounce function based on underscores modified to use es6 and a cb\n\nexport function debounce(func: (...args: any) => unknown, wait: number, immediate: boolean, cb: () => unknown) {\n let timeout: NodeJS.Timeout|null;\n return function fx(...args: any[]) {\n const runLater = () => {\n timeout = null;\n if (!immediate) {\n Reflect.apply(func, null, args);\n if (cb) {\n cb();\n }\n }\n };\n const callNow = immediate && !timeout;\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(runLater, wait);\n if (callNow) {\n Reflect.apply(func, null, args);\n if (cb) {\n cb();\n }\n }\n };\n}\n\nexport class FormattedError extends Error {\n intl: {\n id: string;\n defaultMessage: string;\n values: any;\n };\n\n constructor(id: string, defaultMessage: string, values: any = {}) {\n super(defaultMessage);\n this.intl = {\n id,\n defaultMessage,\n values,\n };\n }\n}\n\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {Client4, DEFAULT_LIMIT_AFTER, DEFAULT_LIMIT_BEFORE} from 'mattermost-redux/client';\nimport {General, Preferences, Posts} from '../constants';\nimport {PostTypes, ChannelTypes, FileTypes, IntegrationTypes} from 'mattermost-redux/action_types';\n\nimport {getCurrentChannelId, getMyChannelMember as getMyChannelMemberSelector} from 'mattermost-redux/selectors/entities/channels';\nimport {getCustomEmojisByName as selectCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';\nimport * as Selectors from 'mattermost-redux/selectors/entities/posts';\nimport {getCurrentUserId, getUsersByUsername} from 'mattermost-redux/selectors/entities/users';\n\nimport {isCombinedUserActivityPost} from 'mattermost-redux/utils/post_list';\n\nimport {Action, ActionResult, batchActions, DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';\nimport {ChannelUnread} from 'mattermost-redux/types/channels';\nimport {GlobalState} from 'mattermost-redux/types/store';\nimport {Post, PostList} from 'mattermost-redux/types/posts';\nimport {Reaction} from 'mattermost-redux/types/reactions';\nimport {UserProfile} from 'mattermost-redux/types/users';\nimport {Dictionary} from 'mattermost-redux/types/utilities';\nimport {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';\n\nimport {getProfilesByIds, getProfilesByUsernames, getStatusesByIds} from './users';\nimport {\n deletePreferences,\n savePreferences,\n} from './preferences';\nimport {bindClientFunc, forceLogoutIfNecessary} from './helpers';\nimport {logError} from './errors';\nimport {systemEmojis, getCustomEmojiByName, getCustomEmojisByName} from './emojis';\nimport {selectChannel} from './channels';\n\n// receivedPost should be dispatched after a single post from the server. This typically happens when an existing post\n// is updated.\nexport function receivedPost(post: Post) {\n return {\n type: PostTypes.RECEIVED_POST,\n data: post,\n };\n}\n\n// receivedNewPost should be dispatched when receiving a newly created post or when sending a request to the server\n// to make a new post.\nexport function receivedNewPost(post: Post, crtEnabled: boolean) {\n return {\n type: PostTypes.RECEIVED_NEW_POST,\n data: post,\n features: {crtEnabled},\n };\n}\n\n// receivedPosts should be dispatched when receiving multiple posts from the server that may or may not be ordered.\n// This will typically be used alongside other actions like receivedPostsAfter which require the posts to be ordered.\nexport function receivedPosts(posts: PostList) {\n return {\n type: PostTypes.RECEIVED_POSTS,\n data: posts,\n };\n}\n\n// receivedPostsAfter should be dispatched when receiving an ordered list of posts that come before a given post.\nexport function receivedPostsAfter(posts: PostList, channelId: string, afterPostId: string, recent = false) {\n return {\n type: PostTypes.RECEIVED_POSTS_AFTER,\n channelId,\n data: posts,\n afterPostId,\n recent,\n };\n}\n\n// receivedPostsBefore should be dispatched when receiving an ordered list of posts that come after a given post.\nexport function receivedPostsBefore(posts: PostList, channelId: string, beforePostId: string, oldest = false) {\n return {\n type: PostTypes.RECEIVED_POSTS_BEFORE,\n channelId,\n data: posts,\n beforePostId,\n oldest,\n };\n}\n\n// receivedPostsSince should be dispatched when receiving a list of posts that have been updated since a certain time.\n// Due to how the API endpoint works, some of these posts will be ordered, but others will not, so this needs special\n// handling from the reducers.\nexport function receivedPostsSince(posts: PostList, channelId: string) {\n return {\n type: PostTypes.RECEIVED_POSTS_SINCE,\n channelId,\n data: posts,\n };\n}\n\n// receivedPostsInChannel should be dispatched when receiving a list of ordered posts within a channel when the\n// the adjacent posts are not known.\nexport function receivedPostsInChannel(posts: PostList, channelId: string, recent = false, oldest = false) {\n return {\n type: PostTypes.RECEIVED_POSTS_IN_CHANNEL,\n channelId,\n data: posts,\n recent,\n oldest,\n };\n}\n\n// receivedPostsInThread should be dispatched when receiving a list of unordered posts in a thread.\nexport function receivedPostsInThread(posts: PostList, rootId: string) {\n return {\n type: PostTypes.RECEIVED_POSTS_IN_THREAD,\n data: posts,\n rootId,\n };\n}\n\n// postDeleted should be dispatched when a post has been deleted and should be replaced with a \"message deleted\"\n// placeholder. This typically happens when a post is deleted by another user.\nexport function postDeleted(post: Post) {\n return {\n type: PostTypes.POST_DELETED,\n data: post,\n };\n}\n\n// postRemoved should be dispatched when a post should be immediately removed. This typically happens when a post is\n// deleted by the current user.\nexport function postRemoved(post: Post) {\n return {\n type: PostTypes.POST_REMOVED,\n data: post,\n };\n}\n\nexport function getPost(postId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let post;\n\n try {\n post = await Client4.getPost(postId);\n getProfilesAndStatusesForPosts([post], dispatch, getState);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: PostTypes.GET_POSTS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n receivedPost(post),\n {\n type: PostTypes.GET_POSTS_SUCCESS,\n },\n ]));\n\n return {data: post};\n };\n}\n\nexport function createPost(post: Post, files: any[] = []) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const currentUserId = state.entities.users.currentUserId;\n\n const timestamp = Date.now();\n const pendingPostId = post.pending_post_id || `${currentUserId}:${timestamp}`;\n let actions: Action[] = [];\n\n if (Selectors.isPostIdSending(state, pendingPostId)) {\n return {data: true};\n }\n\n let newPost = {\n ...post,\n pending_post_id: pendingPostId,\n create_at: timestamp,\n update_at: timestamp,\n reply_count: 0,\n };\n\n if (post.root_id) {\n newPost.reply_count = Selectors.getPostRepliesCount(state, post.root_id) + 1;\n }\n\n // We are retrying a pending post that had files\n if (newPost.file_ids && !files.length) {\n files = newPost.file_ids.map((id) => state.entities.files.files[id]); // eslint-disable-line\n }\n\n if (files.length) {\n const fileIds = files.map((file) => file.id);\n\n newPost = {\n ...newPost,\n file_ids: fileIds,\n };\n\n actions.push({\n type: FileTypes.RECEIVED_FILES_FOR_POST,\n postId: pendingPostId,\n data: files,\n });\n }\n\n const crtEnabled = isCollapsedThreadsEnabled(getState());\n actions.push({\n type: PostTypes.RECEIVED_NEW_POST,\n data: {\n ...newPost,\n id: pendingPostId,\n },\n features: {crtEnabled},\n });\n\n dispatch(batchActions(actions, 'BATCH_CREATE_POST_INIT'));\n\n (async function createPostWrapper() {\n try {\n const created = await Client4.createPost({...newPost, create_at: 0});\n\n actions = [\n receivedPost(created),\n {\n type: PostTypes.CREATE_POST_SUCCESS,\n },\n {\n type: ChannelTypes.INCREMENT_TOTAL_MSG_COUNT,\n data: {\n channelId: newPost.channel_id,\n amount: 1,\n amountRoot: created.root_id === '' ? 1 : 0,\n },\n },\n {\n type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT,\n data: {\n channelId: newPost.channel_id,\n amount: 1,\n amountRoot: created.root_id === '' ? 1 : 0,\n },\n },\n ];\n\n if (files) {\n actions.push({\n type: FileTypes.RECEIVED_FILES_FOR_POST,\n postId: created.id,\n data: files,\n });\n }\n\n dispatch(batchActions(actions, 'BATCH_CREATE_POST'));\n } catch (error) {\n const data = {\n ...newPost,\n id: pendingPostId,\n failed: true,\n update_at: Date.now(),\n };\n actions = [{type: PostTypes.CREATE_POST_FAILURE, error}];\n\n // If the failure was because: the root post was deleted or\n // TownSquareIsReadOnly=true then remove the post\n if (error.server_error_id === 'api.post.create_post.root_id.app_error' ||\n error.server_error_id === 'api.post.create_post.town_square_read_only' ||\n error.server_error_id === 'plugin.message_will_be_posted.dismiss_post'\n ) {\n actions.push(removePost(data) as any);\n } else {\n actions.push(receivedPost(data));\n }\n\n dispatch(batchActions(actions, 'BATCH_CREATE_POST_FAILED'));\n }\n }());\n\n return {data: true};\n };\n}\n\nexport function createPostImmediately(post: Post, files: any[] = []) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const currentUserId = state.entities.users.currentUserId;\n const timestamp = Date.now();\n const pendingPostId = `${currentUserId}:${timestamp}`;\n\n let newPost: Post = {\n ...post,\n pending_post_id: pendingPostId,\n create_at: timestamp,\n update_at: timestamp,\n reply_count: 0,\n };\n\n if (post.root_id) {\n newPost.reply_count = Selectors.getPostRepliesCount(state, post.root_id) + 1;\n }\n\n if (files.length) {\n const fileIds = files.map((file) => file.id);\n\n newPost = {\n ...newPost,\n file_ids: fileIds,\n };\n\n dispatch({\n type: FileTypes.RECEIVED_FILES_FOR_POST,\n postId: pendingPostId,\n data: files,\n });\n }\n\n dispatch(receivedNewPost({\n ...newPost,\n id: pendingPostId,\n }, isCollapsedThreadsEnabled(state)));\n\n try {\n const created = await Client4.createPost({...newPost, create_at: 0});\n newPost.id = created.id;\n newPost.reply_count = created.reply_count;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: PostTypes.CREATE_POST_FAILURE, data: newPost, error},\n removePost({\n ...newPost,\n id: pendingPostId,\n }) as any,\n logError(error),\n ]));\n return {error};\n }\n\n const actions: Action[] = [\n receivedPost(newPost),\n {\n type: PostTypes.CREATE_POST_SUCCESS,\n },\n {\n type: ChannelTypes.INCREMENT_TOTAL_MSG_COUNT,\n data: {\n channelId: newPost.channel_id,\n amount: 1,\n amountRoot: newPost.root_id === '' ? 1 : 0,\n },\n },\n {\n type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT,\n data: {\n channelId: newPost.channel_id,\n amount: 1,\n amountRoot: newPost.root_id === '' ? 1 : 0,\n },\n },\n ];\n\n if (files) {\n actions.push({\n type: FileTypes.RECEIVED_FILES_FOR_POST,\n postId: newPost.id,\n data: files,\n });\n }\n\n dispatch(batchActions(actions));\n\n return {data: newPost};\n };\n}\n\nexport function resetCreatePostRequest() {\n return {type: PostTypes.CREATE_POST_RESET_REQUEST};\n}\n\nexport function deletePost(post: ExtendedPost) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const delPost = {...post};\n if (delPost.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && delPost.system_post_ids) {\n delPost.system_post_ids.forEach((systemPostId) => {\n const systemPost = Selectors.getPost(state, systemPostId);\n if (systemPost) {\n dispatch(deletePost(systemPost));\n }\n });\n } else {\n (async function deletePostWrapper() {\n try {\n dispatch({\n type: PostTypes.POST_DELETED,\n data: delPost,\n });\n\n await Client4.deletePost(post.id);\n } catch (e) {\n // Recovering from this state doesn't actually work. The deleteAndRemovePost action\n // in the webapp needs to get an error in order to not call removePost, but then\n // the delete modal needs to handle this to show something to the user. Since none\n // of that ever worked (even with redux-offline in play), leave the behaviour here\n // unresolved.\n console.error('failed to delete post', e); // eslint-disable-line no-console\n }\n }());\n }\n\n return {data: true};\n };\n}\n\nexport function editPost(post: Post) {\n return bindClientFunc({\n clientFunc: Client4.patchPost,\n onRequest: PostTypes.EDIT_POST_REQUEST,\n onSuccess: [PostTypes.RECEIVED_POST, PostTypes.EDIT_POST_SUCCESS],\n onFailure: PostTypes.EDIT_POST_FAILURE,\n params: [\n post,\n ],\n });\n}\n\nfunction getUnreadPostData(unreadChan: ChannelUnread, state: GlobalState) {\n const member = getMyChannelMemberSelector(state, unreadChan.channel_id);\n const delta = member ? member.msg_count - unreadChan.msg_count : unreadChan.msg_count;\n const deltaRoot = member ? member.msg_count_root - unreadChan.msg_count_root : unreadChan.msg_count_root;\n\n const data = {\n teamId: unreadChan.team_id,\n channelId: unreadChan.channel_id,\n msgCount: unreadChan.msg_count,\n mentionCount: unreadChan.mention_count,\n msgCountRoot: unreadChan.msg_count_root,\n mentionCountRoot: unreadChan.mention_count_root,\n lastViewedAt: unreadChan.last_viewed_at,\n deltaMsgs: delta,\n deltaMsgsRoot: deltaRoot,\n };\n\n return data;\n}\n\nexport function setUnreadPost(userId: string, postId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let state = getState();\n const post = Selectors.getPost(state, postId);\n let unreadChan;\n\n try {\n if (isCombinedUserActivityPost(postId)) {\n return {};\n }\n unreadChan = await Client4.markPostAsUnread(userId, postId);\n dispatch({\n type: ChannelTypes.ADD_MANUALLY_UNREAD,\n data: {\n channelId: post.channel_id,\n },\n });\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n dispatch({\n type: ChannelTypes.REMOVE_MANUALLY_UNREAD,\n data: {\n channelId: post.channel_id,\n },\n });\n return {error};\n }\n\n state = getState();\n const data = getUnreadPostData(unreadChan, state);\n dispatch({\n type: ChannelTypes.POST_UNREAD_SUCCESS,\n data,\n });\n return {data};\n };\n}\n\nexport function pinPost(postId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: PostTypes.EDIT_POST_REQUEST});\n let posts;\n\n try {\n posts = await Client4.pinPost(postId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: PostTypes.EDIT_POST_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n const actions: Action[] = [\n {\n type: PostTypes.EDIT_POST_SUCCESS,\n },\n ];\n\n const post = Selectors.getPost(getState(), postId);\n if (post) {\n actions.push(\n receivedPost({\n ...post,\n is_pinned: true,\n update_at: Date.now(),\n }),\n {\n type: ChannelTypes.INCREMENT_PINNED_POST_COUNT,\n id: post.channel_id,\n },\n );\n }\n\n dispatch(batchActions(actions));\n\n return {data: posts};\n };\n}\n\nexport function unpinPost(postId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: PostTypes.EDIT_POST_REQUEST});\n let posts;\n\n try {\n posts = await Client4.unpinPost(postId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: PostTypes.EDIT_POST_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n const actions: Action[] = [\n {\n type: PostTypes.EDIT_POST_SUCCESS,\n },\n ];\n\n const post = Selectors.getPost(getState(), postId);\n if (post) {\n actions.push(\n receivedPost({\n ...post,\n is_pinned: false,\n update_at: Date.now(),\n }),\n {\n type: ChannelTypes.DECREMENT_PINNED_POST_COUNT,\n id: post.channel_id,\n },\n );\n }\n\n dispatch(batchActions(actions));\n\n return {data: posts};\n };\n}\n\nexport function addReaction(postId: string, emojiName: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const currentUserId = getState().entities.users.currentUserId;\n\n let reaction;\n try {\n reaction = await Client4.addReaction(currentUserId, postId, emojiName);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: PostTypes.RECEIVED_REACTION,\n data: reaction,\n });\n\n return {data: true};\n };\n}\n\nexport function removeReaction(postId: string, emojiName: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const currentUserId = getState().entities.users.currentUserId;\n\n try {\n await Client4.removeReaction(currentUserId, postId, emojiName);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: PostTypes.REACTION_DELETED,\n data: {user_id: currentUserId, post_id: postId, emoji_name: emojiName},\n });\n\n return {data: true};\n };\n}\n\nexport function getCustomEmojiForReaction(name: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const nonExistentEmoji = getState().entities.emojis.nonExistentEmoji;\n const customEmojisByName = selectCustomEmojisByName(getState());\n\n if (systemEmojis.has(name)) {\n return {data: true};\n }\n\n if (nonExistentEmoji.has(name)) {\n return {data: true};\n }\n\n if (customEmojisByName.has(name)) {\n return {data: true};\n }\n\n return dispatch(getCustomEmojiByName(name));\n };\n}\n\nexport function getReactionsForPost(postId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let reactions;\n try {\n reactions = await Client4.getReactionsForPost(postId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n if (reactions && reactions.length > 0) {\n const nonExistentEmoji = getState().entities.emojis.nonExistentEmoji;\n const customEmojisByName = selectCustomEmojisByName(getState());\n const emojisToLoad = new Set();\n\n reactions.forEach((r: Reaction) => {\n const name = r.emoji_name;\n\n if (systemEmojis.has(name)) {\n // It's a system emoji, go the next match\n return;\n }\n\n if (nonExistentEmoji.has(name)) {\n // We've previously confirmed this is not a custom emoji\n return;\n }\n\n if (customEmojisByName.has(name)) {\n // We have the emoji, go to the next match\n return;\n }\n\n emojisToLoad.add(name);\n });\n\n dispatch(getCustomEmojisByName(Array.from(emojisToLoad)));\n }\n\n dispatch(batchActions([\n {\n type: PostTypes.RECEIVED_REACTIONS,\n data: reactions,\n postId,\n },\n ]));\n\n return reactions;\n };\n}\n\nexport function flagPost(postId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n const preference = {\n user_id: currentUserId,\n category: Preferences.CATEGORY_FLAGGED_POST,\n name: postId,\n value: 'true',\n };\n\n Client4.trackEvent('action', 'action_posts_flag');\n\n return savePreferences(currentUserId, [preference])(dispatch);\n };\n}\n\nexport function getPostThread(rootId: string, fetchThreads = true) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: PostTypes.GET_POST_THREAD_REQUEST});\n const collapsedThreadsEnabled = isCollapsedThreadsEnabled(getState());\n let posts;\n try {\n posts = await Client4.getPostThread(rootId, fetchThreads, collapsedThreadsEnabled);\n getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: PostTypes.GET_POST_THREAD_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n receivedPosts(posts),\n receivedPostsInThread(posts, rootId),\n {\n type: PostTypes.GET_POST_THREAD_SUCCESS,\n },\n ]));\n\n return {data: posts};\n };\n}\n\nexport function getPosts(channelId: string, page = 0, perPage = Posts.POST_CHUNK_SIZE, fetchThreads = true, collapsedThreadsExtended = false) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let posts;\n const collapsedThreadsEnabled = isCollapsedThreadsEnabled(getState());\n try {\n posts = await Client4.getPosts(channelId, page, perPage, fetchThreads, collapsedThreadsEnabled, collapsedThreadsExtended);\n getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n receivedPosts(posts),\n receivedPostsInChannel(posts, channelId, page === 0, posts.prev_post_id === ''),\n ]));\n\n return {data: posts};\n };\n}\n\nexport function getPostsUnread(channelId: string, fetchThreads = true, collapsedThreadsExtended = false) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const collapsedThreadsEnabled = isCollapsedThreadsEnabled(getState());\n const userId = getCurrentUserId(getState());\n let posts;\n try {\n posts = await Client4.getPostsUnread(channelId, userId, DEFAULT_LIMIT_BEFORE, DEFAULT_LIMIT_AFTER, fetchThreads, collapsedThreadsEnabled, collapsedThreadsExtended);\n getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n receivedPosts(posts),\n receivedPostsInChannel(posts, channelId, posts.next_post_id === '', posts.prev_post_id === ''),\n ]));\n dispatch({\n type: PostTypes.RECEIVED_POSTS,\n data: posts,\n channelId,\n });\n\n return {data: posts};\n };\n}\n\nexport function getPostsSince(channelId: string, since: number, fetchThreads = true, collapsedThreadsExtended = false) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let posts;\n try {\n const collapsedThreadsEnabled = isCollapsedThreadsEnabled(getState());\n posts = await Client4.getPostsSince(channelId, since, fetchThreads, collapsedThreadsEnabled, collapsedThreadsExtended);\n getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n receivedPosts(posts),\n receivedPostsSince(posts, channelId),\n {\n type: PostTypes.GET_POSTS_SINCE_SUCCESS,\n },\n ]));\n\n return {data: posts};\n };\n}\n\nexport function getPostsBefore(channelId: string, postId: string, page = 0, perPage = Posts.POST_CHUNK_SIZE, fetchThreads = true, collapsedThreadsExtended = false) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let posts;\n try {\n const collapsedThreadsEnabled = isCollapsedThreadsEnabled(getState());\n posts = await Client4.getPostsBefore(channelId, postId, page, perPage, fetchThreads, collapsedThreadsEnabled, collapsedThreadsExtended);\n getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n receivedPosts(posts),\n receivedPostsBefore(posts, channelId, postId, posts.prev_post_id === ''),\n ]));\n\n return {data: posts};\n };\n}\n\nexport function getPostsAfter(channelId: string, postId: string, page = 0, perPage = Posts.POST_CHUNK_SIZE, fetchThreads = true, collapsedThreadsExtended = false) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let posts;\n try {\n const collapsedThreadsEnabled = isCollapsedThreadsEnabled(getState());\n posts = await Client4.getPostsAfter(channelId, postId, page, perPage, fetchThreads, collapsedThreadsEnabled, collapsedThreadsExtended);\n getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n receivedPosts(posts),\n receivedPostsAfter(posts, channelId, postId, posts.next_post_id === ''),\n ]));\n\n return {data: posts};\n };\n}\n\nexport function getPostsAround(channelId: string, postId: string, perPage = Posts.POST_CHUNK_SIZE / 2, fetchThreads = true, collapsedThreadsExtended = false) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let after;\n let thread;\n let before;\n\n try {\n const collapsedThreadsEnabled = isCollapsedThreadsEnabled(getState());\n [after, thread, before] = await Promise.all([\n Client4.getPostsAfter(channelId, postId, 0, perPage, fetchThreads, collapsedThreadsEnabled, collapsedThreadsExtended),\n Client4.getPostThread(postId, fetchThreads, collapsedThreadsEnabled, collapsedThreadsExtended),\n Client4.getPostsBefore(channelId, postId, 0, perPage, fetchThreads, collapsedThreadsEnabled, collapsedThreadsExtended),\n ]);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n // Dispatch a combined post list so that the order is correct for postsInChannel\n const posts: PostList = {\n posts: {\n ...after.posts,\n ...thread.posts,\n ...before.posts,\n },\n order: [ // Remember that the order is newest posts first\n ...after.order,\n postId,\n ...before.order,\n ],\n next_post_id: after.next_post_id,\n prev_post_id: before.prev_post_id,\n };\n\n getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n\n dispatch(batchActions([\n receivedPosts(posts),\n receivedPostsInChannel(posts, channelId, after.next_post_id === '', before.prev_post_id === ''),\n ]));\n\n return {data: posts};\n };\n}\n\n// getThreadsForPosts is intended for an array of posts that have been batched\n// (see the actions/websocket_actions/handleNewPostEvents function in the webapp)\nexport function getThreadsForPosts(posts: Post[], fetchThreads = true) {\n return (dispatch: DispatchFunc, getState: GetStateFunc) => {\n if (!Array.isArray(posts) || !posts.length) {\n return {data: true};\n }\n\n const state = getState();\n const promises: Array> = [];\n\n posts.forEach((post) => {\n if (!post.root_id) {\n return;\n }\n\n const rootPost = Selectors.getPost(state, post.root_id);\n if (!rootPost) {\n promises.push(dispatch(getPostThread(post.root_id, fetchThreads)));\n }\n });\n\n return Promise.all(promises);\n };\n}\n\n// Note that getProfilesAndStatusesForPosts can take either an array of posts or a map of ids to posts\nexport function getProfilesAndStatusesForPosts(postsArrayOrMap: Post[]|Map, dispatch: DispatchFunc, getState: GetStateFunc) {\n if (!postsArrayOrMap) {\n // Some API methods return {error} for no results\n return Promise.resolve();\n }\n\n const postsArray: Post[] = Object.values(postsArrayOrMap);\n\n if (postsArray.length === 0) {\n return Promise.resolve();\n }\n\n const postsDictionary: Dictionary = {};\n for (let i = 0; i < postsArray.length; i++) {\n postsDictionary[postsArray[i].id] = postsArray[i];\n }\n\n const state = getState();\n const {currentUserId, profiles, statuses} = state.entities.users;\n\n // Statuses and profiles of the users who made the posts\n const userIdsToLoad = new Set();\n const statusesToLoad = new Set();\n\n postsArray.forEach((post) => {\n const userId = post.user_id;\n\n if (post.metadata && post.metadata.embeds) {\n post.metadata.embeds.forEach((embed: any) => {\n if (embed.type === 'permalink' && embed.data) {\n if (embed.data.post?.user_id && !profiles[embed.data.post.user_id] && embed.data.post.user_id !== currentUserId) {\n userIdsToLoad.add(embed.data.post.user_id);\n }\n if (embed.data.post?.user_id && !statuses[embed.data.post.user_id]) {\n statusesToLoad.add(embed.data.post.user_id);\n }\n }\n });\n }\n\n if (!statuses[userId]) {\n statusesToLoad.add(userId);\n }\n\n if (userId === currentUserId) {\n return;\n }\n\n if (!profiles[userId]) {\n userIdsToLoad.add(userId);\n }\n });\n\n const promises: any[] = [];\n if (userIdsToLoad.size > 0) {\n promises.push(getProfilesByIds(Array.from(userIdsToLoad))(dispatch, getState));\n }\n\n if (statusesToLoad.size > 0) {\n promises.push(getStatusesByIds(Array.from(statusesToLoad))(dispatch, getState));\n }\n\n // Profiles of users mentioned in the posts\n const usernamesToLoad = getNeededAtMentionedUsernames(state, postsArray);\n\n if (usernamesToLoad.size > 0) {\n promises.push(getProfilesByUsernames(Array.from(usernamesToLoad))(dispatch, getState));\n }\n\n return Promise.all(promises);\n}\n\nexport function getNeededAtMentionedUsernames(state: GlobalState, posts: Post[]): Set {\n let usersByUsername: Dictionary; // Populate this lazily since it's relatively expensive\n\n const usernamesToLoad = new Set();\n\n posts.forEach((post) => {\n if (!post.message.includes('@')) {\n return;\n }\n\n if (!usersByUsername) {\n usersByUsername = getUsersByUsername(state);\n }\n\n const pattern = /\\B@(([a-z0-9_.-]*[a-z0-9_])[.-]*)/gi;\n\n let match;\n while ((match = pattern.exec(post.message)) !== null) {\n // match[1] is the matched mention including trailing punctuation\n // match[2] is the matched mention without trailing punctuation\n if (General.SPECIAL_MENTIONS.indexOf(match[2]) !== -1) {\n continue;\n }\n\n if (usersByUsername[match[1]] || usersByUsername[match[2]]) {\n // We have the user, go to the next match\n continue;\n }\n\n // If there's no trailing punctuation, this will only add 1 item to the set\n usernamesToLoad.add(match[1]);\n usernamesToLoad.add(match[2]);\n }\n });\n\n return usernamesToLoad;\n}\n\nexport type ExtendedPost = Post & { system_post_ids?: string[] };\n\nexport function removePost(post: ExtendedPost) {\n return (dispatch: DispatchFunc, getState: GetStateFunc) => {\n if (post.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && post.system_post_ids) {\n const state = getState();\n\n for (const systemPostId of post.system_post_ids) {\n const systemPost = Selectors.getPost(state, systemPostId);\n\n if (systemPost) {\n dispatch(removePost(systemPost as any) as any);\n }\n }\n } else {\n dispatch(postRemoved(post));\n if (post.is_pinned) {\n dispatch(\n {\n type: ChannelTypes.DECREMENT_PINNED_POST_COUNT,\n id: post.channel_id,\n },\n );\n }\n }\n };\n}\n\nexport function selectPost(postId: string) {\n return async (dispatch: DispatchFunc) => {\n dispatch({\n type: PostTypes.RECEIVED_POST_SELECTED,\n data: postId,\n });\n\n return {data: true};\n };\n}\n\nexport function selectFocusedPostId(postId: string) {\n return {\n type: PostTypes.RECEIVED_FOCUSED_POST,\n data: postId,\n };\n}\n\nexport function unflagPost(postId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n const preference = {\n user_id: currentUserId,\n category: Preferences.CATEGORY_FLAGGED_POST,\n name: postId,\n };\n\n Client4.trackEvent('action', 'action_posts_unflag');\n\n return deletePreferences(currentUserId, [preference])(dispatch, getState);\n };\n}\n\nexport function getOpenGraphMetadata(url: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getOpenGraphMetadata(url);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n if (data && (data.url || data.type || data.title || data.description)) {\n dispatch({\n type: PostTypes.RECEIVED_OPEN_GRAPH_METADATA,\n data,\n url,\n });\n }\n\n return {data};\n };\n}\n\nexport function doPostAction(postId: string, actionId: string, selectedOption = '') {\n return doPostActionWithCookie(postId, actionId, '', selectedOption);\n}\n\nexport function doPostActionWithCookie(postId: string, actionId: string, actionCookie: string, selectedOption = '') {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.doPostActionWithCookie(postId, actionId, actionCookie, selectedOption);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n if (data && data.trigger_id) {\n dispatch({\n type: IntegrationTypes.RECEIVED_DIALOG_TRIGGER_ID,\n data: data.trigger_id,\n });\n }\n\n return {data};\n };\n}\n\nexport function addMessageIntoHistory(message: string) {\n return async (dispatch: DispatchFunc) => {\n dispatch({\n type: PostTypes.ADD_MESSAGE_INTO_HISTORY,\n data: message,\n });\n\n return {data: true};\n };\n}\n\nexport function resetHistoryIndex(index: string) {\n return async (dispatch: DispatchFunc) => {\n dispatch({\n type: PostTypes.RESET_HISTORY_INDEX,\n data: index,\n });\n\n return {data: true};\n };\n}\n\nexport function moveHistoryIndexBack(index: string) {\n return async (dispatch: DispatchFunc) => {\n dispatch({\n type: PostTypes.MOVE_HISTORY_INDEX_BACK,\n data: index,\n });\n\n return {data: true};\n };\n}\n\nexport function moveHistoryIndexForward(index: string) {\n return async (dispatch: DispatchFunc) => {\n dispatch({\n type: PostTypes.MOVE_HISTORY_INDEX_FORWARD,\n data: index,\n });\n\n return {data: true};\n };\n}\n\n/**\n * Ensures thread-replies in channels correctly follow CRT:ON/OFF\n */\nexport function resetReloadPostsInChannel() {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({\n type: PostTypes.RESET_POSTS_IN_CHANNEL,\n });\n\n const currentChannelId = getCurrentChannelId(getState());\n if (currentChannelId) {\n // wait for channel to be fully deselected; prevent stuck loading screen\n // full state-change/reconciliation will cause prefetchChannelPosts to reload posts\n await dispatch(selectChannel('')); // do not remove await\n dispatch(selectChannel(currentChannelId));\n }\n return {data: true};\n };\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {PreferenceTypes} from 'mattermost-redux/action_types';\n\nimport {Client4} from 'mattermost-redux/client';\n\nimport {Preferences} from '../constants';\n\nimport {getMyPreferences as getMyPreferencesSelector, makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';\nimport {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';\n\nimport {GetStateFunc, DispatchFunc, ActionFunc} from 'mattermost-redux/types/actions';\nimport {PreferenceType} from 'mattermost-redux/types/preferences';\nimport {Theme} from 'mattermost-redux/types/themes';\n\nimport {getPreferenceKey} from 'mattermost-redux/utils/preference_utils';\n\nimport {getChannelAndMyMember, getMyChannelMember} from './channels';\nimport {bindClientFunc} from './helpers';\nimport {getProfilesByIds, getProfilesInChannel} from './users';\n\nexport function deletePreferences(userId: string, preferences: PreferenceType[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const myPreferences = getMyPreferencesSelector(state);\n const currentPreferences = preferences.map((pref) => myPreferences[getPreferenceKey(pref.category, pref.name)]);\n\n (async function deletePreferencesWrapper() {\n try {\n dispatch({\n type: PreferenceTypes.DELETED_PREFERENCES,\n data: preferences,\n });\n\n await Client4.deletePreferences(userId, preferences);\n } catch {\n dispatch({\n type: PreferenceTypes.RECEIVED_PREFERENCES,\n data: currentPreferences,\n });\n }\n }());\n\n return {data: true};\n };\n}\n\nexport function getMyPreferences(): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getMyPreferences,\n onSuccess: PreferenceTypes.RECEIVED_ALL_PREFERENCES,\n });\n}\n\nexport function makeDirectChannelVisibleIfNecessary(otherUserId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const myPreferences = getMyPreferencesSelector(state);\n const currentUserId = getCurrentUserId(state);\n\n let preference = myPreferences[getPreferenceKey(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, otherUserId)];\n\n if (!preference || preference.value === 'false') {\n preference = {\n user_id: currentUserId,\n category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,\n name: otherUserId,\n value: 'true',\n };\n getProfilesByIds([otherUserId])(dispatch, getState);\n savePreferences(currentUserId, [preference])(dispatch);\n }\n\n return {data: true};\n };\n}\n\nexport function makeGroupMessageVisibleIfNecessary(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const myPreferences = getMyPreferencesSelector(state);\n const currentUserId = getCurrentUserId(state);\n const {channels} = state.entities.channels;\n\n let preference = myPreferences[getPreferenceKey(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId)];\n\n if (!preference || preference.value === 'false') {\n preference = {\n user_id: currentUserId,\n category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,\n name: channelId,\n value: 'true',\n };\n\n if (channels[channelId]) {\n getMyChannelMember(channelId)(dispatch, getState);\n } else {\n getChannelAndMyMember(channelId)(dispatch, getState);\n }\n\n getProfilesInChannel(channelId, 0)(dispatch, getState);\n savePreferences(currentUserId, [preference])(dispatch);\n }\n\n return {data: true};\n };\n}\n\nexport function setCustomStatusInitialisationState(initializationState: Record) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const currentUserId = getCurrentUserId(state);\n const preference: PreferenceType = {\n user_id: currentUserId,\n category: Preferences.CATEGORY_CUSTOM_STATUS,\n name: Preferences.NAME_CUSTOM_STATUS_TUTORIAL_STATE,\n value: JSON.stringify(initializationState),\n };\n await dispatch(savePreferences(currentUserId, [preference]));\n };\n}\n\nexport function savePreferences(userId: string, preferences: PreferenceType[]) {\n return async (dispatch: DispatchFunc) => {\n (async function savePreferencesWrapper() {\n try {\n dispatch({\n type: PreferenceTypes.RECEIVED_PREFERENCES,\n data: preferences,\n });\n\n await Client4.savePreferences(userId, preferences);\n } catch {\n dispatch({\n type: PreferenceTypes.DELETED_PREFERENCES,\n data: preferences,\n });\n }\n }());\n\n return {data: true};\n };\n}\n\nexport function saveTheme(teamId: string, theme: Theme): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const currentUserId = getCurrentUserId(state);\n const preference: PreferenceType = {\n user_id: currentUserId,\n category: Preferences.CATEGORY_THEME,\n name: teamId || '',\n value: JSON.stringify(theme),\n };\n\n await savePreferences(currentUserId, [preference])(dispatch);\n return {data: true};\n };\n}\n\nexport function deleteTeamSpecificThemes(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n\n const themePreferences: PreferenceType[] = makeGetCategory()(state, Preferences.CATEGORY_THEME);\n const currentUserId = getCurrentUserId(state);\n\n const toDelete = themePreferences.filter((pref) => pref.name !== '');\n if (toDelete.length > 0) {\n await deletePreferences(currentUserId, toDelete)(dispatch, getState);\n }\n\n return {data: true};\n };\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Client4} from 'mattermost-redux/client';\nimport {RoleTypes} from 'mattermost-redux/action_types';\nimport {getRoles} from 'mattermost-redux/selectors/entities/roles_helpers';\n\nimport {DispatchFunc, GetStateFunc, ActionFunc} from 'mattermost-redux/types/actions';\nimport {Role} from 'mattermost-redux/types/roles';\n\nimport {bindClientFunc} from './helpers';\n\nexport function getRolesByNames(rolesNames: string[]) {\n return bindClientFunc({\n clientFunc: Client4.getRolesByNames,\n onRequest: RoleTypes.ROLES_BY_NAMES_REQUEST,\n onSuccess: [RoleTypes.RECEIVED_ROLES, RoleTypes.ROLES_BY_NAMES_SUCCESS],\n onFailure: RoleTypes.ROLES_BY_NAMES_FAILURE,\n params: [\n rolesNames,\n ],\n });\n}\n\nexport function getRoleByName(roleName: string) {\n return bindClientFunc({\n clientFunc: Client4.getRoleByName,\n onRequest: RoleTypes.ROLE_BY_NAME_REQUEST,\n onSuccess: [RoleTypes.RECEIVED_ROLE, RoleTypes.ROLE_BY_NAME_SUCCESS],\n onFailure: RoleTypes.ROLE_BY_NAME_FAILURE,\n params: [\n roleName,\n ],\n });\n}\n\nexport function getRole(roleId: string) {\n return bindClientFunc({\n clientFunc: Client4.getRole,\n onRequest: RoleTypes.ROLE_BY_ID_REQUEST,\n onSuccess: [RoleTypes.RECEIVED_ROLE, RoleTypes.ROLE_BY_ID_SUCCESS],\n onFailure: RoleTypes.ROLE_BY_ID_FAILURE,\n params: [\n roleId,\n ],\n });\n}\n\nexport function editRole(role: Role) {\n return bindClientFunc({\n clientFunc: Client4.patchRole,\n onRequest: RoleTypes.EDIT_ROLE_REQUEST,\n onSuccess: [RoleTypes.RECEIVED_ROLE, RoleTypes.EDIT_ROLE_SUCCESS],\n onFailure: RoleTypes.EDIT_ROLE_FAILURE,\n params: [\n role.id,\n role,\n ],\n });\n}\n\nexport function setPendingRoles(roles: string[]) {\n return async (dispatch: DispatchFunc) => {\n dispatch({type: RoleTypes.SET_PENDING_ROLES, data: roles});\n return {data: roles};\n };\n}\n\nexport function loadRolesIfNeeded(roles: Iterable): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n let pendingRoles = new Set();\n\n try {\n pendingRoles = new Set(state.entities.roles.pending);\n } catch (e) {// eslint-disable-line\n }\n\n for (const role of roles) {\n pendingRoles.add(role);\n }\n if (!state.entities.general.serverVersion) {\n dispatch(setPendingRoles(Array.from(pendingRoles)));\n setTimeout(() => dispatch(loadRolesIfNeeded([])), 500);\n return {data: []};\n }\n\n const loadedRoles = getRoles(state);\n const newRoles = new Set();\n\n for (const role of pendingRoles) {\n if (!loadedRoles[role] && role.trim() !== '') {\n newRoles.add(role);\n }\n }\n\n if (state.entities.roles.pending) {\n await dispatch(setPendingRoles([]));\n }\n if (newRoles.size > 0) {\n return getRolesByNames(Array.from(newRoles))(dispatch, getState);\n }\n return {data: state.entities.roles.roles};\n };\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Client4} from 'mattermost-redux/client';\nimport {SearchTypes} from 'mattermost-redux/action_types';\nimport {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';\nimport {getCurrentUserId, getCurrentUserMentionKeys} from 'mattermost-redux/selectors/entities/users';\n\nimport {ActionResult, batchActions, DispatchFunc, GetStateFunc, ActionFunc} from 'mattermost-redux/types/actions';\n\nimport {Post} from 'mattermost-redux/types/posts';\n\nimport {FileSearchResults, FileSearchResultItem} from 'mattermost-redux/types/files';\n\nimport {SearchParameter} from 'mattermost-redux/types/search';\n\nimport {getChannelAndMyMember, getChannelMembers} from './channels';\nimport {forceLogoutIfNecessary} from './helpers';\nimport {logError} from './errors';\nimport {getProfilesAndStatusesForPosts, receivedPosts} from './posts';\nimport {receivedFiles} from './files';\nconst WEBAPP_SEARCH_PER_PAGE = 20;\nexport function getMissingChannelsFromPosts(posts: Map): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {\n channels,\n membersInChannel,\n myMembers,\n } = getState().entities.channels;\n const promises: Array> = [];\n Object.values(posts).forEach((post) => {\n const id = post.channel_id;\n\n if (!channels[id] || !myMembers[id]) {\n promises.push(dispatch(getChannelAndMyMember(id)));\n }\n\n if (!membersInChannel[id]) {\n promises.push(dispatch(getChannelMembers(id)));\n }\n });\n return Promise.all(promises);\n };\n}\n\nexport function getMissingChannelsFromFiles(files: Map): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {\n channels,\n membersInChannel,\n myMembers,\n } = getState().entities.channels;\n const promises: Array> = [];\n Object.values(files).forEach((file) => {\n const id = file.channel_id;\n\n if (!channels[id] || !myMembers[id]) {\n promises.push(dispatch(getChannelAndMyMember(id)));\n }\n\n if (!membersInChannel[id]) {\n promises.push(dispatch(getChannelMembers(id)));\n }\n });\n return Promise.all(promises);\n };\n}\n\nexport function searchPostsWithParams(teamId: string, params: SearchParameter): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const isGettingMore = params.page > 0;\n dispatch({\n type: SearchTypes.SEARCH_POSTS_REQUEST,\n isGettingMore,\n });\n let posts;\n\n try {\n posts = await Client4.searchPostsWithParams(teamId, params);\n\n const profilesAndStatuses = getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n const missingChannels = dispatch(getMissingChannelsFromPosts(posts.posts));\n const arr: [Promise, Promise] = [profilesAndStatuses, missingChannels];\n await Promise.all(arr);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: SearchTypes.RECEIVED_SEARCH_POSTS,\n data: posts,\n isGettingMore,\n },\n receivedPosts(posts),\n {\n type: SearchTypes.RECEIVED_SEARCH_TERM,\n data: {\n teamId,\n params,\n isEnd: posts.order.length === 0,\n },\n },\n {\n type: SearchTypes.SEARCH_POSTS_SUCCESS,\n },\n ], 'SEARCH_POST_BATCH'));\n\n return {data: posts};\n };\n}\n\nexport function searchPosts(teamId: string, terms: string, isOrSearch: boolean, includeDeletedChannels: boolean) {\n return searchPostsWithParams(teamId, {terms, is_or_search: isOrSearch, include_deleted_channels: includeDeletedChannels, page: 0, per_page: WEBAPP_SEARCH_PER_PAGE});\n}\n\nexport function getMorePostsForSearch(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const teamId = getCurrentTeamId(getState());\n const {params, isEnd} = getState().entities.search.current[teamId];\n if (!isEnd) {\n const newParams = Object.assign({}, params);\n newParams.page += 1;\n return dispatch(searchPostsWithParams(teamId, newParams));\n }\n return {data: true};\n };\n}\n\nexport function clearSearch(): ActionFunc {\n return async (dispatch) => {\n dispatch({type: SearchTypes.REMOVE_SEARCH_POSTS});\n dispatch({type: SearchTypes.REMOVE_SEARCH_FILES});\n\n return {data: true};\n };\n}\n\nexport function searchFilesWithParams(teamId: string, params: SearchParameter): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const isGettingMore = params.page > 0;\n dispatch({\n type: SearchTypes.SEARCH_FILES_REQUEST,\n isGettingMore,\n });\n\n let files: FileSearchResults;\n try {\n files = await Client4.searchFilesWithParams(teamId, params);\n\n await dispatch(getMissingChannelsFromFiles(files.file_infos));\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: SearchTypes.RECEIVED_SEARCH_FILES,\n data: files,\n isGettingMore,\n },\n receivedFiles(files.file_infos),\n {\n type: SearchTypes.RECEIVED_SEARCH_TERM,\n data: {\n teamId,\n params,\n isFilesEnd: files.order.length === 0,\n },\n },\n {\n type: SearchTypes.SEARCH_FILES_SUCCESS,\n },\n ], 'SEARCH_FILE_BATCH'));\n\n return {data: files};\n };\n}\n\nexport function searchFiles(teamId: string, terms: string, isOrSearch: boolean, includeDeletedChannels: boolean) {\n return searchFilesWithParams(teamId, {terms, is_or_search: isOrSearch, include_deleted_channels: includeDeletedChannels, page: 0, per_page: WEBAPP_SEARCH_PER_PAGE});\n}\n\nexport function getMoreFilesForSearch(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const teamId = getCurrentTeamId(getState());\n const {params, isFilesEnd} = getState().entities.search.current[teamId];\n if (!isFilesEnd) {\n const newParams = Object.assign({}, params);\n newParams.page += 1;\n return dispatch(searchFilesWithParams(teamId, newParams));\n }\n return {data: true};\n };\n}\n\nexport function getFlaggedPosts(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const userId = getCurrentUserId(state);\n const teamId = getCurrentTeamId(state);\n\n dispatch({type: SearchTypes.SEARCH_FLAGGED_POSTS_REQUEST});\n\n let posts;\n try {\n posts = await Client4.getFlaggedPosts(userId, '', teamId);\n\n await Promise.all([getProfilesAndStatusesForPosts(posts.posts, dispatch, getState) as any, dispatch(getMissingChannelsFromPosts(posts.posts)) as any]);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: SearchTypes.SEARCH_FLAGGED_POSTS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: SearchTypes.RECEIVED_SEARCH_FLAGGED_POSTS,\n data: posts,\n },\n receivedPosts(posts),\n {\n type: SearchTypes.SEARCH_FLAGGED_POSTS_SUCCESS,\n },\n ], 'SEARCH_FLAGGED_POSTS_BATCH'));\n\n return {data: posts};\n };\n}\n\nexport function getPinnedPosts(channelId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: SearchTypes.SEARCH_PINNED_POSTS_REQUEST});\n\n let result;\n try {\n result = await Client4.getPinnedPosts(channelId);\n\n const profilesAndStatuses = getProfilesAndStatusesForPosts(result.posts, dispatch, getState);\n const missingChannels = dispatch(getMissingChannelsFromPosts(result.posts));\n const arr: [Promise, Promise] = [profilesAndStatuses, missingChannels];\n await Promise.all(arr);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: SearchTypes.SEARCH_PINNED_POSTS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: SearchTypes.RECEIVED_SEARCH_PINNED_POSTS,\n data: {\n pinned: result,\n channelId,\n },\n },\n receivedPosts(result),\n {\n type: SearchTypes.SEARCH_PINNED_POSTS_SUCCESS,\n },\n ], 'SEARCH_PINNED_POSTS_BATCH'));\n\n return {data: result};\n };\n}\n\nexport function clearPinnedPosts(channelId: string): ActionFunc {\n return async (dispatch) => {\n dispatch({\n type: SearchTypes.REMOVE_SEARCH_PINNED_POSTS,\n data: {\n channelId,\n },\n });\n\n return {data: true};\n };\n}\n\nexport function getRecentMentions(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const teamId = getCurrentTeamId(state);\n\n let posts;\n try {\n const termKeys = getCurrentUserMentionKeys(state).filter(({key}) => {\n return key !== '@channel' && key !== '@all' && key !== '@here';\n });\n\n const terms = termKeys.map(({key}) => key).join(' ').trim() + ' ';\n\n Client4.trackEvent('api', 'api_posts_search_mention');\n posts = await Client4.searchPosts(teamId, terms, true);\n\n const profilesAndStatuses = getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);\n const missingChannels = dispatch(getMissingChannelsFromPosts(posts.posts));\n const arr: [Promise, Promise] = [profilesAndStatuses, missingChannels];\n await Promise.all(arr);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: SearchTypes.RECEIVED_SEARCH_POSTS,\n data: posts,\n },\n receivedPosts(posts),\n ], 'SEARCH_RECENT_MENTIONS_BATCH'));\n\n return {data: posts};\n };\n}\n\nexport function removeSearchTerms(teamId: string, terms: string): ActionFunc {\n return async (dispatch) => {\n dispatch({\n type: SearchTypes.REMOVE_SEARCH_TERM,\n data: {\n teamId,\n terms,\n },\n });\n\n return {data: true};\n };\n}\n\nexport default {\n clearSearch,\n removeSearchTerms,\n searchPosts,\n};\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Client4} from 'mattermost-redux/client';\nimport {General} from '../constants';\nimport {ChannelTypes, TeamTypes, UserTypes} from 'mattermost-redux/action_types';\nimport EventEmitter from 'mattermost-redux/utils/event_emitter';\n\nimport {isCompatibleWithJoinViewTeamPermissions} from 'mattermost-redux/selectors/entities/general';\n\nimport {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';\n\nimport {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';\n\nimport {GetStateFunc, DispatchFunc, ActionFunc, ActionResult, batchActions, Action} from 'mattermost-redux/types/actions';\n\nimport {Team, TeamMembership, TeamMemberWithError, GetTeamMembersOpts, TeamsWithCount, TeamSearchOpts} from 'mattermost-redux/types/teams';\n\nimport {UserProfile} from 'mattermost-redux/types/users';\n\nimport {isCollapsedThreadsEnabled} from '../selectors/entities/preferences';\n\nimport {selectChannel} from './channels';\nimport {logError} from './errors';\nimport {bindClientFunc, forceLogoutIfNecessary} from './helpers';\nimport {getProfilesByIds, getStatusesByIds} from './users';\nimport {loadRolesIfNeeded} from './roles';\n\nasync function getProfilesAndStatusesForMembers(userIds: string[], dispatch: DispatchFunc, getState: GetStateFunc) {\n const {\n currentUserId,\n profiles,\n statuses,\n } = getState().entities.users;\n const profilesToLoad: string[] = [];\n const statusesToLoad: string[] = [];\n userIds.forEach((userId) => {\n if (!profiles[userId] && !profilesToLoad.includes(userId) && userId !== currentUserId) {\n profilesToLoad.push(userId);\n }\n\n if (!statuses[userId] && !statusesToLoad.includes(userId) && userId !== currentUserId) {\n statusesToLoad.push(userId);\n }\n });\n const requests: Array> = [];\n\n if (profilesToLoad.length) {\n requests.push(dispatch(getProfilesByIds(profilesToLoad)));\n }\n\n if (statusesToLoad.length) {\n requests.push(dispatch(getStatusesByIds(statusesToLoad)));\n }\n\n await Promise.all(requests);\n}\n\nexport function selectTeam(team: Team | string): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n const teamId = (typeof team === 'string') ? team : team.id;\n dispatch({\n type: TeamTypes.SELECT_TEAM,\n data: teamId,\n });\n\n return {data: true};\n };\n}\n\nexport function getMyTeams(): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getMyTeams,\n onRequest: TeamTypes.MY_TEAMS_REQUEST,\n onSuccess: [TeamTypes.RECEIVED_TEAMS_LIST, TeamTypes.MY_TEAMS_SUCCESS],\n onFailure: TeamTypes.MY_TEAMS_FAILURE,\n });\n}\n\n// The argument skipCurrentTeam is a (not ideal) workaround for CRT mention counts. Unread mentions are stored in the reducer per\n// team but we do not track unread mentions for DMs/GMs independently. This results in a bit of funky logic and edge case bugs\n// that need workarounds like this. In the future we should fix the root cause with better APIs and redux state.\nexport function getMyTeamUnreads(collapsedThreads: boolean, skipCurrentTeam = false): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let unreads;\n try {\n unreads = await Client4.getMyTeamUnreads(collapsedThreads);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n if (skipCurrentTeam) {\n const currentTeamId = getCurrentTeamId(getState());\n if (currentTeamId) {\n const index = unreads.findIndex((member) => member.team_id === currentTeamId);\n if (index >= 0) {\n unreads.splice(index, 1);\n }\n }\n }\n\n dispatch(\n {\n type: TeamTypes.RECEIVED_MY_TEAM_UNREADS,\n data: unreads,\n },\n );\n\n return {data: unreads};\n };\n}\n\nexport function getTeam(teamId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTeam,\n onSuccess: TeamTypes.RECEIVED_TEAM,\n params: [\n teamId,\n ],\n });\n}\n\nexport function getTeamByName(teamName: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTeamByName,\n onSuccess: TeamTypes.RECEIVED_TEAM,\n params: [\n teamName,\n ],\n });\n}\n\nexport function getTeams(page = 0, perPage: number = General.TEAMS_CHUNK_SIZE, includeTotalCount = false, excludePolicyConstrained = false): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n\n dispatch({type: TeamTypes.GET_TEAMS_REQUEST, data});\n\n try {\n data = await Client4.getTeams(page, perPage, includeTotalCount, excludePolicyConstrained) as TeamsWithCount;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch({type: TeamTypes.GET_TEAMS_FAILURE, data});\n dispatch(logError(error));\n return {error};\n }\n\n const actions: Action[] = [\n {\n type: TeamTypes.RECEIVED_TEAMS_LIST,\n data: includeTotalCount ? data.teams : data,\n },\n {\n type: TeamTypes.GET_TEAMS_SUCCESS,\n data,\n },\n ];\n\n if (includeTotalCount) {\n actions.push({\n type: TeamTypes.RECEIVED_TOTAL_TEAM_COUNT,\n data: data.total_count,\n });\n }\n\n dispatch(batchActions(actions));\n\n return {data};\n };\n}\n\nexport function searchTeams(term: string, opts: TeamSearchOpts = {}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: TeamTypes.GET_TEAMS_REQUEST, data: null});\n\n let response;\n try {\n response = await Client4.searchTeams(term, opts);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: TeamTypes.GET_TEAMS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n // The type of the response is determined by whether or not page/perPage were set\n let teams;\n if (!opts.page || !opts.per_page) {\n teams = response as Team[];\n } else {\n teams = (response as TeamsWithCount).teams;\n }\n\n dispatch(batchActions([\n {\n type: TeamTypes.RECEIVED_TEAMS_LIST,\n data: teams,\n },\n {\n type: TeamTypes.GET_TEAMS_SUCCESS,\n },\n ]));\n\n return {data: response};\n };\n}\n\nexport function createTeam(team: Team): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let created;\n try {\n created = await Client4.createTeam(team);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const member = {\n team_id: created.id,\n user_id: getState().entities.users.currentUserId,\n roles: `${General.TEAM_ADMIN_ROLE} ${General.TEAM_USER_ROLE}`,\n delete_at: 0,\n msg_count: 0,\n mention_count: 0,\n };\n\n dispatch(batchActions([\n {\n type: TeamTypes.CREATED_TEAM,\n data: created,\n },\n {\n type: TeamTypes.RECEIVED_MY_TEAM_MEMBER,\n data: member,\n },\n {\n type: TeamTypes.SELECT_TEAM,\n data: created.id,\n },\n ]));\n dispatch(loadRolesIfNeeded(member.roles.split(' ')));\n\n return {data: created};\n };\n}\n\nexport function deleteTeam(teamId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.deleteTeam(teamId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const entities = getState().entities;\n const {\n currentTeamId,\n } = entities.teams;\n const actions: Action[] = [];\n if (teamId === currentTeamId) {\n EventEmitter.emit('leave_team');\n actions.push({type: ChannelTypes.SELECT_CHANNEL, data: ''});\n }\n\n actions.push(\n {\n type: TeamTypes.RECEIVED_TEAM_DELETED,\n data: {id: teamId},\n },\n );\n\n dispatch(batchActions(actions));\n\n return {data: true};\n };\n}\n\nexport function unarchiveTeam(teamId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let team: Team;\n try {\n team = await Client4.unarchiveTeam(teamId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: TeamTypes.RECEIVED_TEAM_UNARCHIVED,\n data: team,\n });\n\n return {data: true};\n };\n}\n\nexport function updateTeam(team: Team): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.updateTeam,\n onSuccess: TeamTypes.UPDATED_TEAM,\n params: [\n team,\n ],\n });\n}\n\nexport function patchTeam(team: Team): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.patchTeam,\n onSuccess: TeamTypes.PATCHED_TEAM,\n params: [\n team,\n ],\n });\n}\n\nexport function regenerateTeamInviteId(teamId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.regenerateTeamInviteId,\n onSuccess: TeamTypes.REGENERATED_TEAM_INVITE_ID,\n params: [\n teamId,\n ],\n });\n}\n\nexport function getMyTeamMembers(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const getMyTeamMembersFunc = bindClientFunc({\n clientFunc: Client4.getMyTeamMembers,\n onSuccess: TeamTypes.RECEIVED_MY_TEAM_MEMBERS,\n });\n const teamMembers = (await getMyTeamMembersFunc(dispatch, getState)) as ActionResult;\n\n if ('data' in teamMembers && teamMembers.data) {\n const roles = new Set();\n\n for (const teamMember of teamMembers.data) {\n for (const role of teamMember.roles.split(' ')) {\n roles.add(role);\n }\n }\n if (roles.size > 0) {\n dispatch(loadRolesIfNeeded([...roles]));\n }\n }\n\n return teamMembers;\n };\n}\n\nexport function getTeamMembers(teamId: string, page = 0, perPage: number = General.TEAMS_CHUNK_SIZE, options: GetTeamMembersOpts): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTeamMembers,\n onRequest: TeamTypes.GET_TEAM_MEMBERS_REQUEST,\n onSuccess: [TeamTypes.RECEIVED_MEMBERS_IN_TEAM, TeamTypes.GET_TEAM_MEMBERS_SUCCESS],\n onFailure: TeamTypes.GET_TEAM_MEMBERS_FAILURE,\n params: [\n teamId,\n page,\n perPage,\n options,\n ],\n });\n}\n\nexport function getTeamMember(teamId: string, userId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let member;\n try {\n const memberRequest = Client4.getTeamMember(teamId, userId);\n\n getProfilesAndStatusesForMembers([userId], dispatch, getState);\n\n member = await memberRequest;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: TeamTypes.RECEIVED_MEMBERS_IN_TEAM,\n data: [member],\n });\n\n return {data: member};\n };\n}\n\nexport function getTeamMembersByIds(teamId: string, userIds: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let members;\n try {\n const membersRequest = Client4.getTeamMembersByIds(teamId, userIds);\n\n getProfilesAndStatusesForMembers(userIds, dispatch, getState);\n\n members = await membersRequest;\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: TeamTypes.RECEIVED_MEMBERS_IN_TEAM,\n data: members,\n });\n\n return {data: members};\n };\n}\n\nexport function getTeamsForUser(userId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTeamsForUser,\n onRequest: TeamTypes.GET_TEAMS_REQUEST,\n onSuccess: [TeamTypes.RECEIVED_TEAMS_LIST, TeamTypes.GET_TEAMS_SUCCESS],\n onFailure: TeamTypes.GET_TEAMS_FAILURE,\n params: [\n userId,\n ],\n });\n}\n\nexport function getTeamMembersForUser(userId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTeamMembersForUser,\n onSuccess: TeamTypes.RECEIVED_TEAM_MEMBERS,\n params: [\n userId,\n ],\n });\n}\n\nexport function getTeamStats(teamId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTeamStats,\n onSuccess: TeamTypes.RECEIVED_TEAM_STATS,\n params: [\n teamId,\n ],\n });\n}\n\nexport function addUserToTeamFromInvite(token: string, inviteId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.addToTeamFromInvite,\n onRequest: TeamTypes.ADD_TO_TEAM_FROM_INVITE_REQUEST,\n onSuccess: TeamTypes.ADD_TO_TEAM_FROM_INVITE_SUCCESS,\n onFailure: TeamTypes.ADD_TO_TEAM_FROM_INVITE_FAILURE,\n params: [\n token,\n inviteId,\n ],\n });\n}\n\nexport function addUserToTeam(teamId: string, userId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let member;\n try {\n member = await Client4.addToTeam(teamId, userId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILE_IN_TEAM,\n data: {id: teamId, user_id: userId},\n },\n {\n type: TeamTypes.RECEIVED_MEMBER_IN_TEAM,\n data: member,\n },\n ]));\n\n return {data: member};\n };\n}\n\nexport function addUsersToTeam(teamId: string, userIds: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let members;\n try {\n members = await Client4.addUsersToTeam(teamId, userIds);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const profiles: Array> = [];\n members.forEach((m: TeamMembership) => profiles.push({id: m.user_id}));\n\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM,\n data: profiles,\n id: teamId,\n },\n {\n type: TeamTypes.RECEIVED_MEMBERS_IN_TEAM,\n data: members,\n },\n ]));\n\n return {data: members};\n };\n}\n\nexport function addUsersToTeamGracefully(teamId: string, userIds: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let result: TeamMemberWithError[];\n try {\n result = await Client4.addUsersToTeamGracefully(teamId, userIds);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const addedMembers = result ? result.filter((m) => !m.error) : [];\n const profiles: Array> = addedMembers.map((m) => ({id: m.user_id}));\n const members = addedMembers.map((m) => m.member);\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM,\n data: profiles,\n id: teamId,\n },\n {\n type: TeamTypes.RECEIVED_MEMBERS_IN_TEAM,\n data: members,\n },\n ]));\n\n return {data: result};\n };\n}\n\nexport function removeUserFromTeam(teamId: string, userId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.removeFromTeam(teamId, userId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const member = {\n team_id: teamId,\n user_id: userId,\n };\n\n const actions: Action[] = [\n {\n type: UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM,\n data: {id: teamId, user_id: userId},\n },\n {\n type: TeamTypes.REMOVE_MEMBER_FROM_TEAM,\n data: member,\n },\n ];\n\n const state = getState();\n const currentUserId = getCurrentUserId(state);\n\n if (userId === currentUserId) {\n const {channels, myMembers} = state.entities.channels;\n\n for (const channelMember of Object.values(myMembers)) {\n const channel = channels[channelMember.channel_id];\n\n if (channel && channel.team_id === teamId) {\n actions.push({\n type: ChannelTypes.LEAVE_CHANNEL,\n data: channel,\n });\n }\n }\n\n if (teamId === getCurrentTeamId(state)) {\n actions.push(selectChannel(''));\n }\n }\n\n dispatch(batchActions(actions));\n\n return {data: true};\n };\n}\n\nexport function updateTeamMemberRoles(teamId: string, userId: string, roles: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateTeamMemberRoles(teamId, userId, roles);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const membersInTeam = getState().entities.teams.membersInTeam[teamId];\n if (membersInTeam && membersInTeam[userId]) {\n dispatch({\n type: TeamTypes.RECEIVED_MEMBER_IN_TEAM,\n data: {...membersInTeam[userId], roles},\n });\n }\n\n return {data: true};\n };\n}\n\nexport function sendEmailInvitesToTeam(teamId: string, emails: string[]): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.sendEmailInvitesToTeam,\n params: [\n teamId,\n emails,\n ],\n });\n}\n\nexport function sendEmailGuestInvitesToChannels(teamId: string, channelIds: string[], emails: string[], message: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.sendEmailGuestInvitesToChannels,\n params: [\n teamId,\n channelIds,\n emails,\n message,\n ],\n });\n}\nexport function sendEmailInvitesToTeamGracefully(teamId: string, emails: string[]): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.sendEmailInvitesToTeamGracefully,\n params: [\n teamId,\n emails,\n ],\n });\n}\n\nexport function sendEmailGuestInvitesToChannelsGracefully(teamId: string, channelIds: string[], emails: string[], message: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.sendEmailGuestInvitesToChannelsGracefully,\n params: [\n teamId,\n channelIds,\n emails,\n message,\n ],\n });\n}\n\nexport function getTeamInviteInfo(inviteId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTeamInviteInfo,\n onRequest: TeamTypes.TEAM_INVITE_INFO_REQUEST,\n onSuccess: TeamTypes.TEAM_INVITE_INFO_SUCCESS,\n onFailure: TeamTypes.TEAM_INVITE_INFO_FAILURE,\n params: [\n inviteId,\n ],\n });\n}\n\nexport function checkIfTeamExists(teamName: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.checkIfTeamExists(teamName);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n return {data: data.exists};\n };\n}\n\nexport function joinTeam(inviteId: string, teamId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: TeamTypes.JOIN_TEAM_REQUEST, data: null});\n\n const state = getState();\n try {\n if (isCompatibleWithJoinViewTeamPermissions(state)) {\n const currentUserId = state.entities.users.currentUserId;\n await Client4.addToTeam(teamId, currentUserId);\n } else {\n await Client4.joinTeam(inviteId);\n }\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: TeamTypes.JOIN_TEAM_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(getMyTeamUnreads(isCollapsedThreadsEnabled(state)));\n\n await Promise.all([\n getTeam(teamId)(dispatch, getState),\n getMyTeamMembers()(dispatch, getState),\n ]);\n\n dispatch({type: TeamTypes.JOIN_TEAM_SUCCESS, data: null});\n return {data: true};\n };\n}\n\nexport function setTeamIcon(teamId: string, imageData: File): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.setTeamIcon,\n params: [\n teamId,\n imageData,\n ],\n });\n}\n\nexport function removeTeamIcon(teamId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.removeTeamIcon,\n params: [\n teamId,\n ],\n });\n}\n\nexport function updateTeamScheme(teamId: string, schemeId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: async () => {\n await Client4.updateTeamScheme(teamId, schemeId);\n return {teamId, schemeId};\n },\n onSuccess: TeamTypes.UPDATED_TEAM_SCHEME,\n });\n}\n\nexport function updateTeamMemberSchemeRoles(\n teamId: string,\n userId: string,\n isSchemeUser: boolean,\n isSchemeAdmin: boolean,\n): ActionFunc {\n return bindClientFunc({\n clientFunc: async () => {\n await Client4.updateTeamMemberSchemeRoles(teamId, userId, isSchemeUser, isSchemeAdmin);\n return {teamId, userId, isSchemeUser, isSchemeAdmin};\n },\n onSuccess: TeamTypes.UPDATED_TEAM_MEMBER_SCHEME_ROLES,\n });\n}\n\nexport function invalidateAllEmailInvites(): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.invalidateAllEmailInvites,\n });\n}\n\nexport function membersMinusGroupMembers(teamID: string, groupIDs: string[], page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.teamMembersMinusGroupMembers,\n onSuccess: TeamTypes.RECEIVED_TEAM_MEMBERS_MINUS_GROUP_MEMBERS,\n params: [\n teamID,\n groupIDs,\n page,\n perPage,\n ],\n });\n}\n\nexport function getInProductNotices(teamId: string, client: string, clientVersion: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getInProductNotices,\n params: [\n teamId,\n client,\n clientVersion,\n ],\n });\n}\n\nexport function updateNoticesAsViewed(noticeIds: string[]): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.updateNoticesAsViewed,\n params: [\n noticeIds,\n ],\n });\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {uniq} from 'lodash';\n\nimport {ThreadTypes, PostTypes, UserTypes} from 'mattermost-redux/action_types';\nimport {Client4} from 'mattermost-redux/client';\n\nimport ThreadConstants from 'mattermost-redux/constants/threads';\n\nimport {DispatchFunc, GetStateFunc, batchActions} from 'mattermost-redux/types/actions';\n\nimport type {UserThread, UserThreadList} from 'mattermost-redux/types/threads';\n\nimport {getMissingProfilesByIds} from 'mattermost-redux/actions/users';\n\nimport {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';\n\nimport {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';\n\nimport {getThreadsInChannel} from 'mattermost-redux/selectors/entities/threads';\n\nimport {getChannel} from 'mattermost-redux/selectors/entities/channels';\n\nimport {logError} from './errors';\nimport {forceLogoutIfNecessary} from './helpers';\n\nexport function getThreads(userId: string, teamId: string, {before = '', after = '', perPage = ThreadConstants.THREADS_CHUNK_SIZE, unread = false} = {}) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let userThreadList: undefined | UserThreadList;\n\n try {\n userThreadList = await Client4.getUserThreads(userId, teamId, {before, after, perPage, extended: false, unread});\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n if (userThreadList?.threads?.length) {\n await dispatch(getMissingProfilesByIds(uniq(userThreadList.threads.map(({participants}) => participants.map(({id}) => id)).flat())));\n\n dispatch({\n type: PostTypes.RECEIVED_POSTS,\n data: {posts: userThreadList.threads.map(({post}) => ({...post, update_at: 0}))},\n });\n }\n\n dispatch({\n type: ThreadTypes.RECEIVED_THREADS,\n data: {\n ...userThreadList,\n threads: userThreadList?.threads?.map((thread) => ({...thread, is_following: true})) ?? [],\n team_id: teamId,\n },\n });\n\n return {data: userThreadList};\n };\n}\n\nexport function handleThreadArrived(dispatch: DispatchFunc, getState: GetStateFunc, threadData: UserThread, teamId: string) {\n const state = getState();\n const currentUserId = getCurrentUserId(state);\n const currentTeamId = getCurrentTeamId(state);\n const thread = {...threadData, is_following: true};\n\n dispatch({\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: thread.participants.filter((user) => user.id !== currentUserId),\n });\n\n dispatch({\n type: PostTypes.RECEIVED_POST,\n data: {...thread.post, update_at: 0},\n });\n\n dispatch({\n type: ThreadTypes.RECEIVED_THREAD,\n data: {\n thread,\n team_id: teamId || currentTeamId,\n },\n });\n\n const oldThreadData = state.entities.threads.threads[threadData.id];\n handleReadChanged(\n dispatch,\n thread.id,\n teamId || currentTeamId,\n thread.post.channel_id,\n {\n lastViewedAt: thread.last_viewed_at,\n prevUnreadMentions: oldThreadData?.unread_mentions ?? 0,\n newUnreadMentions: thread.unread_mentions,\n prevUnreadReplies: oldThreadData?.unread_replies ?? 0,\n newUnreadReplies: thread.unread_replies,\n },\n );\n\n return thread;\n}\n\nexport function getThread(userId: string, teamId: string, threadId: string, extended = true) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let thread;\n try {\n thread = await Client4.getUserThread(userId, teamId, threadId, extended);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n if (thread) {\n thread = handleThreadArrived(dispatch, getState, thread, teamId);\n }\n\n return {data: thread};\n };\n}\n\nexport function handleAllMarkedRead(dispatch: DispatchFunc, teamId: string) {\n dispatch({\n type: ThreadTypes.ALL_TEAM_THREADS_READ,\n data: {\n team_id: teamId,\n },\n });\n}\n\nexport function markAllThreadsInTeamRead(userId: string, teamId: string) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateThreadsReadForUser(userId, teamId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n handleAllMarkedRead(dispatch, teamId);\n\n return {};\n };\n}\n\nexport function updateThreadRead(userId: string, teamId: string, threadId: string, timestamp: number) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateThreadReadForUser(userId, teamId, threadId, timestamp);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n return {};\n };\n}\n\nexport function handleReadChanged(\n dispatch: DispatchFunc,\n threadId: string,\n teamId: string,\n channelId: string,\n {\n lastViewedAt,\n prevUnreadMentions,\n newUnreadMentions,\n prevUnreadReplies,\n newUnreadReplies,\n }: {\n lastViewedAt: number;\n prevUnreadMentions: number;\n newUnreadMentions: number;\n prevUnreadReplies: number;\n newUnreadReplies: number;\n },\n) {\n dispatch({\n type: ThreadTypes.READ_CHANGED_THREAD,\n data: {\n id: threadId,\n teamId,\n channelId,\n lastViewedAt,\n prevUnreadMentions,\n newUnreadMentions,\n prevUnreadReplies,\n newUnreadReplies,\n },\n });\n}\n\nexport function handleFollowChanged(dispatch: DispatchFunc, threadId: string, teamId: string, following: boolean) {\n dispatch({\n type: ThreadTypes.FOLLOW_CHANGED_THREAD,\n data: {\n id: threadId,\n team_id: teamId,\n following,\n },\n });\n}\n\nexport function setThreadFollow(userId: string, teamId: string, threadId: string, newState: boolean) {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n handleFollowChanged(dispatch, threadId, teamId, newState);\n\n try {\n await Client4.updateThreadFollowForUser(userId, teamId, threadId, newState);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n return {};\n };\n}\n\nexport function handleAllThreadsInChannelMarkedRead(dispatch: DispatchFunc, getState: GetStateFunc, channelId: string, lastViewedAt: number) {\n const state = getState();\n const threadsInChannel = getThreadsInChannel(state, channelId);\n const channel = getChannel(state, channelId);\n if (channel == null) {\n return;\n }\n const teamId = channel.team_id;\n const actions = [];\n\n for (const id of threadsInChannel) {\n actions.push({\n type: ThreadTypes.READ_CHANGED_THREAD,\n data: {\n id,\n channelId,\n teamId,\n lastViewedAt,\n newUnreadMentions: 0,\n newUnreadReplies: 0,\n },\n });\n }\n\n dispatch(batchActions(actions));\n}\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {Action, ActionFunc, ActionResult, batchActions, DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';\nimport {UserProfile, UserStatus, GetFilteredUsersStatsOpts, UsersStats, UserCustomStatus} from 'mattermost-redux/types/users';\nimport {TeamMembership} from 'mattermost-redux/types/teams';\nimport {Client4} from 'mattermost-redux/client';\nimport {General} from '../constants';\nimport {UserTypes, TeamTypes, AdminTypes} from 'mattermost-redux/action_types';\n\nimport {removeUserFromList} from 'mattermost-redux/utils/user_utils';\n\nimport {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';\n\nimport {getConfig, getServerVersion} from 'mattermost-redux/selectors/entities/general';\n\nimport {getCurrentUserId, getUsers} from 'mattermost-redux/selectors/entities/users';\n\nimport {Dictionary} from 'mattermost-redux/types/utilities';\n\nimport {isCollapsedThreadsEnabled} from '../selectors/entities/preferences';\n\nimport {getAllCustomEmojis} from './emojis';\nimport {getClientConfig, setServerVersion} from './general';\nimport {getMyTeams, getMyTeamMembers, getMyTeamUnreads} from './teams';\nimport {loadRolesIfNeeded} from './roles';\n\nimport {logError} from './errors';\nimport {bindClientFunc, forceLogoutIfNecessary, debounce} from './helpers';\nimport {getMyPreferences} from './preferences';\n\nexport function checkMfa(loginId: string): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n dispatch({type: UserTypes.CHECK_MFA_REQUEST, data: null});\n try {\n const data = await Client4.checkUserMfa(loginId);\n dispatch({type: UserTypes.CHECK_MFA_SUCCESS, data: null});\n return {data: data.mfa_required};\n } catch (error) {\n dispatch(batchActions([\n {type: UserTypes.CHECK_MFA_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n };\n}\n\nexport function generateMfaSecret(userId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.generateMfaSecret,\n params: [\n userId,\n ],\n });\n}\n\nexport function createUser(user: UserProfile, token: string, inviteId: string, redirect: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let created;\n\n try {\n created = await Client4.createUser(user, token, inviteId, redirect);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const profiles: {\n [userId: string]: UserProfile;\n } = {\n [created.id]: created,\n };\n dispatch({type: UserTypes.RECEIVED_PROFILES, data: profiles});\n\n return {data: created};\n };\n}\n\nexport function login(loginId: string, password: string, mfaToken = '', ldapOnly = false): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: UserTypes.LOGIN_REQUEST, data: null});\n\n const deviceId = getState().entities.general.deviceToken;\n let data;\n\n try {\n data = await Client4.login(loginId, password, mfaToken, deviceId, ldapOnly);\n } catch (error) {\n dispatch(batchActions([\n {\n type: UserTypes.LOGIN_FAILURE,\n error,\n },\n logError(error),\n ]));\n return {error};\n }\n\n return completeLogin(data)(dispatch, getState);\n };\n}\n\nexport function loginById(id: string, password: string, mfaToken = ''): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: UserTypes.LOGIN_REQUEST, data: null});\n\n const deviceId = getState().entities.general.deviceToken;\n let data;\n\n try {\n data = await Client4.loginById(id, password, mfaToken, deviceId);\n } catch (error) {\n dispatch(batchActions([\n {\n type: UserTypes.LOGIN_FAILURE,\n error,\n },\n logError(error),\n ]));\n return {error};\n }\n\n return completeLogin(data)(dispatch, getState);\n };\n}\n\nfunction completeLogin(data: UserProfile): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const collapsedThreads = isCollapsedThreadsEnabled(state);\n dispatch({\n type: UserTypes.RECEIVED_ME,\n data,\n });\n\n Client4.setUserId(data.id);\n Client4.setUserRoles(data.roles);\n let teamMembers;\n\n try {\n const membersRequest: Promise = Client4.getMyTeamMembers();\n const unreadsRequest = Client4.getMyTeamUnreads(collapsedThreads);\n\n teamMembers = await membersRequest;\n const teamUnreads = await unreadsRequest;\n\n if (teamUnreads) {\n for (const u of teamUnreads) {\n const index = teamMembers.findIndex((m) => m.team_id === u.team_id);\n const member = teamMembers[index];\n member.mention_count = u.mention_count;\n member.msg_count = u.msg_count;\n member.mention_count_root = u.mention_count_root;\n member.msg_count_root = u.msg_count_root;\n if (collapsedThreads) {\n member.thread_count = u.thread_count;\n member.thread_mention_count = u.thread_mention_count;\n }\n }\n }\n } catch (error) {\n dispatch(batchActions([\n {type: UserTypes.LOGIN_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n const promises = [\n dispatch(getMyPreferences()),\n dispatch(getMyTeams()),\n dispatch(getClientConfig()),\n ];\n\n const serverVersion = Client4.getServerVersion();\n dispatch(setServerVersion(serverVersion));\n if (!isMinimumServerVersion(serverVersion, 4, 7) && getConfig(getState()).EnableCustomEmoji === 'true') {\n dispatch(getAllCustomEmojis());\n }\n\n try {\n await Promise.all(promises);\n } catch (error) {\n dispatch(batchActions([\n {type: UserTypes.LOGIN_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: TeamTypes.RECEIVED_MY_TEAM_MEMBERS,\n data: teamMembers,\n },\n {\n type: UserTypes.LOGIN_SUCCESS,\n },\n ]));\n const roles = new Set();\n for (const role of data.roles.split(' ')) {\n roles.add(role);\n }\n for (const teamMember of teamMembers) {\n for (const role of teamMember.roles.split(' ')) {\n roles.add(role);\n }\n }\n if (roles.size > 0) {\n dispatch(loadRolesIfNeeded(roles));\n }\n\n return {data: true};\n };\n}\n\nexport function loadMe(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const config = getConfig(state);\n\n const deviceId = state.entities.general.deviceToken;\n if (deviceId) {\n Client4.attachDevice(deviceId);\n }\n\n const promises = [\n dispatch(getMe()),\n dispatch(getMyPreferences()),\n dispatch(getMyTeams()),\n dispatch(getMyTeamMembers()),\n ];\n\n // Sometimes the server version is set in one or the other\n const serverVersion = Client4.getServerVersion() || getState().entities.general.serverVersion;\n dispatch(setServerVersion(serverVersion));\n if (!isMinimumServerVersion(serverVersion, 4, 7) && config.EnableCustomEmoji === 'true') {\n dispatch(getAllCustomEmojis());\n }\n\n await Promise.all(promises);\n\n const collapsedReplies = isCollapsedThreadsEnabled(getState());\n dispatch(getMyTeamUnreads(collapsedReplies));\n\n const {currentUserId} = getState().entities.users;\n const user = getState().entities.users.profiles[currentUserId];\n if (currentUserId) {\n Client4.setUserId(currentUserId);\n }\n\n if (user) {\n Client4.setUserRoles(user.roles);\n }\n\n return {data: true};\n };\n}\n\nexport function logout(): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n dispatch({type: UserTypes.LOGOUT_REQUEST, data: null});\n\n try {\n await Client4.logout();\n } catch (error) {\n // nothing to do here\n }\n\n dispatch({type: UserTypes.LOGOUT_SUCCESS, data: null});\n\n return {data: true};\n };\n}\n\nexport function getTotalUsersStats(): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTotalUsersStats,\n onSuccess: UserTypes.RECEIVED_USER_STATS,\n });\n}\n\nexport function getFilteredUsersStats(options: GetFilteredUsersStatsOpts = {}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let stats: UsersStats;\n try {\n stats = await Client4.getFilteredUsersStats(options);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.RECEIVED_FILTERED_USER_STATS,\n data: stats,\n });\n\n return {data: stats};\n };\n}\n\nexport function getProfiles(page = 0, perPage: number = General.PROFILE_CHUNK_SIZE, options: any = {}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n let profiles: UserProfile[];\n\n try {\n profiles = await Client4.getProfiles(page, perPage, options);\n removeUserFromList(currentUserId, profiles);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: profiles,\n });\n\n return {data: profiles};\n };\n}\n\nexport function getMissingProfilesByIds(userIds: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {profiles} = getState().entities.users;\n const missingIds: string[] = [];\n userIds.forEach((id) => {\n if (!profiles[id]) {\n missingIds.push(id);\n }\n });\n\n if (missingIds.length > 0) {\n getStatusesByIds(missingIds)(dispatch, getState);\n return getProfilesByIds(missingIds)(dispatch, getState);\n }\n\n return {data: []};\n };\n}\n\nexport function getMissingProfilesByUsernames(usernames: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {profiles} = getState().entities.users;\n\n const usernameProfiles = Object.values(profiles).reduce((acc, profile: any) => {\n acc[profile.username] = profile;\n return acc;\n }, {} as Dictionary);\n const missingUsernames: string[] = [];\n usernames.forEach((username) => {\n if (!usernameProfiles[username]) {\n missingUsernames.push(username);\n }\n });\n\n if (missingUsernames.length > 0) {\n return getProfilesByUsernames(missingUsernames)(dispatch, getState);\n }\n\n return {data: []};\n };\n}\n\nexport function getProfilesByIds(userIds: string[], options?: any): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n let profiles: UserProfile[];\n\n try {\n profiles = await Client4.getProfilesByIds(userIds, options);\n removeUserFromList(currentUserId, profiles);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: profiles,\n });\n\n return {data: profiles};\n };\n}\n\nexport function getProfilesByUsernames(usernames: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n let profiles;\n\n try {\n profiles = await Client4.getProfilesByUsernames(usernames);\n removeUserFromList(currentUserId, profiles);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: profiles,\n });\n\n return {data: profiles};\n };\n}\n\nexport function getProfilesInTeam(teamId: string, page: number, perPage: number = General.PROFILE_CHUNK_SIZE, sort = '', options: any = {}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n let profiles;\n\n try {\n profiles = await Client4.getProfilesInTeam(teamId, page, perPage, sort, options);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM,\n data: profiles,\n id: teamId,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: removeUserFromList(currentUserId, [...profiles]),\n },\n ]));\n\n return {data: profiles};\n };\n}\n\nexport function getProfilesNotInTeam(teamId: string, groupConstrained: boolean, page: number, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let profiles;\n try {\n profiles = await Client4.getProfilesNotInTeam(teamId, groupConstrained, page, perPage);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const receivedProfilesListActionType = groupConstrained ? UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM_AND_REPLACE : UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM;\n\n dispatch(batchActions([\n {\n type: receivedProfilesListActionType,\n data: profiles,\n id: teamId,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: profiles,\n },\n ]));\n\n return {data: profiles};\n };\n}\n\nexport function getProfilesWithoutTeam(page: number, perPage: number = General.PROFILE_CHUNK_SIZE, options: any = {}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let profiles = null;\n try {\n profiles = await Client4.getProfilesWithoutTeam(page, perPage, options);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_WITHOUT_TEAM,\n data: profiles,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: profiles,\n },\n ]));\n\n return {data: profiles};\n };\n}\n\nexport function getProfilesInChannel(channelId: string, page: number, perPage: number = General.PROFILE_CHUNK_SIZE, sort = '', options: {active?: boolean} = {}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n let profiles;\n\n try {\n profiles = await Client4.getProfilesInChannel(channelId, page, perPage, sort, options);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL,\n data: profiles,\n id: channelId,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: removeUserFromList(currentUserId, [...profiles]),\n },\n ]));\n\n return {data: profiles};\n };\n}\n\nexport function getProfilesInGroupChannels(channelsIds: string[]): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n let channelProfiles;\n\n try {\n channelProfiles = await Client4.getProfilesInGroupChannels(channelsIds.slice(0, General.MAX_GROUP_CHANNELS_FOR_PROFILES));\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const actions: Action[] = [];\n for (const channelId in channelProfiles) {\n if (channelProfiles.hasOwnProperty(channelId)) {\n const profiles = channelProfiles[channelId];\n\n actions.push(\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL,\n data: profiles,\n id: channelId,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: removeUserFromList(currentUserId, [...profiles]),\n },\n );\n }\n }\n\n dispatch(batchActions(actions));\n\n return {data: channelProfiles};\n };\n}\n\nexport function getProfilesNotInChannel(teamId: string, channelId: string, groupConstrained: boolean, page: number, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n let profiles;\n\n try {\n profiles = await Client4.getProfilesNotInChannel(teamId, channelId, groupConstrained, page, perPage);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const receivedProfilesListActionType = groupConstrained ? UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL_AND_REPLACE : UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL;\n\n dispatch(batchActions([\n {\n type: receivedProfilesListActionType,\n data: profiles,\n id: channelId,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: removeUserFromList(currentUserId, [...profiles]),\n },\n ]));\n\n return {data: profiles};\n };\n}\n\nexport function getMe(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const getMeFunc = bindClientFunc({\n clientFunc: Client4.getMe,\n onSuccess: UserTypes.RECEIVED_ME,\n });\n const me = await getMeFunc(dispatch, getState);\n\n if ('error' in me) {\n return me;\n }\n if ('data' in me) {\n dispatch(loadRolesIfNeeded(me.data.roles.split(' ')));\n }\n return me;\n };\n}\n\nexport function updateMyTermsOfServiceStatus(termsOfServiceId: string, accepted: boolean): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const response: ActionResult = await dispatch(bindClientFunc({\n clientFunc: Client4.updateMyTermsOfServiceStatus,\n params: [\n termsOfServiceId,\n accepted,\n ],\n }));\n\n if ('data' in response) {\n if (accepted) {\n dispatch({\n type: UserTypes.RECEIVED_TERMS_OF_SERVICE_STATUS,\n data: {\n terms_of_service_create_at: new Date().getTime(),\n terms_of_service_id: accepted ? termsOfServiceId : null,\n user_id: getCurrentUserId(getState()),\n },\n });\n }\n\n return {\n data: response.data,\n };\n }\n\n return {\n error: response.error,\n };\n };\n}\n\nexport function getProfilesInGroup(groupId: string, page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n let profiles;\n\n try {\n profiles = await Client4.getProfilesInGroup(groupId, page, perPage);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch(batchActions([\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_GROUP,\n data: profiles,\n id: groupId,\n },\n {\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: removeUserFromList(currentUserId, [...profiles]),\n },\n ]));\n\n return {data: profiles};\n };\n}\n\nexport function getTermsOfService(): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getTermsOfService,\n });\n}\n\nexport function promoteGuestToUser(userId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.promoteGuestToUser,\n params: [userId],\n });\n}\n\nexport function demoteUserToGuest(userId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.demoteUserToGuest,\n params: [userId],\n });\n}\n\nexport function createTermsOfService(text: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.createTermsOfService,\n params: [\n text,\n ],\n });\n}\n\nexport function getUser(id: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getUser,\n onSuccess: UserTypes.RECEIVED_PROFILE,\n params: [\n id,\n ],\n });\n}\n\nexport function getUserByUsername(username: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getUserByUsername,\n onSuccess: UserTypes.RECEIVED_PROFILE,\n params: [\n username,\n ],\n });\n}\n\nexport function getUserByEmail(email: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getUserByEmail,\n onSuccess: UserTypes.RECEIVED_PROFILE,\n params: [\n email,\n ],\n });\n}\n\n// We create an array to hold the id's that we want to get a status for. We build our\n// debounced function that will get called after a set period of idle time in which\n// the array of id's will be passed to the getStatusesByIds with a cb that clears out\n// the array. Helps with performance because instead of making 75 different calls for\n// statuses, we are only making one call for 75 ids.\n// We could maybe clean it up somewhat by storing the array of ids in redux state possbily?\nlet ids: string[] = [];\nconst debouncedGetStatusesByIds = debounce(async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n getStatusesByIds([...new Set(ids)])(dispatch, getState);\n}, 20, false, () => {\n ids = [];\n});\nexport function getStatusesByIdsBatchedDebounced(id: string) {\n ids = [...ids, id];\n return debouncedGetStatusesByIds;\n}\n\nexport function getStatusesByIds(userIds: string[]): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getStatusesByIds,\n onSuccess: UserTypes.RECEIVED_STATUSES,\n params: [\n userIds,\n ],\n });\n}\n\nexport function getStatus(userId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getStatus,\n onSuccess: UserTypes.RECEIVED_STATUS,\n params: [\n userId,\n ],\n });\n}\n\nexport function setStatus(status: UserStatus): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateStatus(status);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.RECEIVED_STATUS,\n data: status,\n });\n\n return {data: status};\n };\n}\n\nexport function setCustomStatus(customStatus: UserCustomStatus): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.updateCustomStatus,\n params: [\n customStatus,\n ],\n });\n}\n\nexport function unsetCustomStatus(): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.unsetCustomStatus,\n });\n}\n\nexport function removeRecentCustomStatus(customStatus: UserCustomStatus): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.removeRecentCustomStatus,\n params: [\n customStatus,\n ],\n });\n}\n\nexport function getSessions(userId: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getSessions,\n onSuccess: UserTypes.RECEIVED_SESSIONS,\n params: [\n userId,\n ],\n });\n}\n\nexport function revokeSession(userId: string, sessionId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.revokeSession(userId, sessionId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.RECEIVED_REVOKED_SESSION,\n sessionId,\n data: null,\n });\n\n return {data: true};\n };\n}\n\nexport function revokeAllSessionsForUser(userId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.revokeAllSessionsForUser(userId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n const data = {isCurrentUser: userId === getCurrentUserId(getState())};\n dispatch(batchActions([\n {\n type: UserTypes.REVOKE_ALL_USER_SESSIONS_SUCCESS,\n data,\n },\n ]));\n\n return {data: true};\n };\n}\n\nexport function revokeSessionsForAllUsers(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.revokeSessionsForAllUsers();\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n dispatch({\n type: UserTypes.REVOKE_SESSIONS_FOR_ALL_USERS_SUCCESS,\n data: null,\n });\n return {data: true};\n };\n}\n\nexport function getUserAudits(userId: string, page = 0, perPage: number = General.AUDITS_CHUNK_SIZE): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getUserAudits,\n onSuccess: UserTypes.RECEIVED_AUDITS,\n params: [\n userId,\n page,\n perPage,\n ],\n });\n}\n\nexport function autocompleteUsers(term: string, teamId = '', channelId = '', options: {\n limit: number;\n} = {\n limit: General.AUTOCOMPLETE_LIMIT_DEFAULT,\n}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n dispatch({type: UserTypes.AUTOCOMPLETE_USERS_REQUEST, data: null});\n\n const {currentUserId} = getState().entities.users;\n\n let data;\n try {\n data = await Client4.autocompleteUsers(term, teamId, channelId, options);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(batchActions([\n {type: UserTypes.AUTOCOMPLETE_USERS_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n let users = [...data.users];\n if (data.out_of_channel) {\n users = [...users, ...data.out_of_channel];\n }\n removeUserFromList(currentUserId, users);\n const actions: Action[] = [{\n type: UserTypes.RECEIVED_PROFILES_LIST,\n data: users,\n }, {\n type: UserTypes.AUTOCOMPLETE_USERS_SUCCESS,\n }];\n\n if (channelId) {\n actions.push(\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL,\n data: data.users,\n id: channelId,\n },\n );\n actions.push(\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL,\n data: data.out_of_channel,\n id: channelId,\n },\n );\n }\n\n if (teamId) {\n actions.push(\n {\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM,\n data: users,\n id: teamId,\n },\n );\n }\n\n dispatch(batchActions(actions));\n\n return {data};\n };\n}\n\nexport function searchProfiles(term: string, options: any = {}): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const {currentUserId} = getState().entities.users;\n\n let profiles;\n try {\n profiles = await Client4.searchUsers(term, options);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const actions: Action[] = [{type: UserTypes.RECEIVED_PROFILES_LIST, data: removeUserFromList(currentUserId, [...profiles])}];\n\n if (options.in_channel_id) {\n actions.push({\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_CHANNEL,\n data: profiles,\n id: options.in_channel_id,\n });\n }\n\n if (options.not_in_channel_id) {\n actions.push({\n type: UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_CHANNEL,\n data: profiles,\n id: options.not_in_channel_id,\n });\n }\n\n if (options.team_id) {\n actions.push({\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM,\n data: profiles,\n id: options.team_id,\n });\n }\n\n if (options.not_in_team_id) {\n actions.push({\n type: UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM,\n data: profiles,\n id: options.not_in_team_id,\n });\n }\n\n if (options.in_group_id) {\n actions.push({\n type: UserTypes.RECEIVED_PROFILES_LIST_IN_GROUP,\n data: profiles,\n id: options.in_group_id,\n });\n }\n\n dispatch(batchActions(actions));\n\n return {data: profiles};\n };\n}\n\nlet statusIntervalId: NodeJS.Timeout|null;\nexport function startPeriodicStatusUpdates(): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n if (statusIntervalId) {\n clearInterval(statusIntervalId);\n }\n\n statusIntervalId = setInterval(\n () => {\n const {statuses} = getState().entities.users;\n\n if (!statuses) {\n return;\n }\n\n const userIds = Object.keys(statuses);\n if (!userIds.length) {\n return;\n }\n\n getStatusesByIds(userIds)(dispatch, getState);\n },\n General.STATUS_INTERVAL,\n );\n\n return {data: true};\n };\n}\n\nexport function stopPeriodicStatusUpdates(): ActionFunc {\n return async () => {\n if (statusIntervalId) {\n clearInterval(statusIntervalId);\n }\n\n return {data: true};\n };\n}\n\nexport function updateMe(user: UserProfile): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n dispatch({type: UserTypes.UPDATE_ME_REQUEST, data: null});\n\n let data;\n try {\n data = await Client4.patchMe(user);\n } catch (error) {\n dispatch(batchActions([\n {type: UserTypes.UPDATE_ME_FAILURE, error},\n logError(error),\n ]));\n return {error};\n }\n\n dispatch(batchActions([\n {type: UserTypes.RECEIVED_ME, data},\n {type: UserTypes.UPDATE_ME_SUCCESS},\n ]));\n dispatch(loadRolesIfNeeded(data.roles.split(' ')));\n\n return {data};\n };\n}\n\nexport function patchUser(user: UserProfile): ActionFunc {\n return async (dispatch: DispatchFunc) => {\n let data: UserProfile;\n try {\n data = await Client4.patchUser(user);\n } catch (error) {\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({type: UserTypes.RECEIVED_PROFILE, data});\n\n return {data};\n };\n}\n\nexport function updateUserRoles(userId: string, roles: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateUserRoles(userId, roles);\n } catch (error) {\n return {error};\n }\n\n const profile = getState().entities.users.profiles[userId];\n if (profile) {\n dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, roles}});\n }\n\n return {data: true};\n };\n}\n\nexport function updateUserMfa(userId: string, activate: boolean, code = ''): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateUserMfa(userId, activate, code);\n } catch (error) {\n dispatch(logError(error));\n return {error};\n }\n\n const profile = getState().entities.users.profiles[userId];\n if (profile) {\n dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, mfa_active: activate}});\n }\n\n return {data: true};\n };\n}\n\nexport function updateUserPassword(userId: string, currentPassword: string, newPassword: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateUserPassword(userId, currentPassword, newPassword);\n } catch (error) {\n dispatch(logError(error));\n return {error};\n }\n\n const profile = getState().entities.users.profiles[userId];\n if (profile) {\n dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, last_password_update_at: new Date().getTime()}});\n }\n\n return {data: true};\n };\n}\n\nexport function updateUserActive(userId: string, active: boolean): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.updateUserActive(userId, active);\n } catch (error) {\n dispatch(logError(error));\n return {error};\n }\n\n const profile = getState().entities.users.profiles[userId];\n if (profile) {\n const deleteAt = active ? 0 : new Date().getTime();\n dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, delete_at: deleteAt}});\n }\n\n return {data: true};\n };\n}\n\nexport function verifyUserEmail(token: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.verifyUserEmail,\n params: [\n token,\n ],\n });\n}\n\nexport function sendVerificationEmail(email: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.sendVerificationEmail,\n params: [\n email,\n ],\n });\n}\n\nexport function resetUserPassword(token: string, newPassword: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.resetUserPassword,\n params: [\n token,\n newPassword,\n ],\n });\n}\n\nexport function sendPasswordResetEmail(email: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.sendPasswordResetEmail,\n params: [\n email,\n ],\n });\n}\n\nexport function setDefaultProfileImage(userId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.setDefaultProfileImage(userId);\n } catch (error) {\n dispatch(logError(error));\n return {error};\n }\n\n const profile = getState().entities.users.profiles[userId];\n if (profile) {\n dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, last_picture_update: 0}});\n }\n\n return {data: true};\n };\n}\n\nexport function uploadProfileImage(userId: string, imageData: any): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.uploadProfileImage(userId, imageData);\n } catch (error) {\n return {error};\n }\n\n const profile = getState().entities.users.profiles[userId];\n if (profile) {\n dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, last_picture_update: new Date().getTime()}});\n }\n\n return {data: true};\n };\n}\n\nexport function switchEmailToOAuth(service: string, email: string, password: string, mfaCode = ''): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.switchEmailToOAuth,\n params: [\n service,\n email,\n password,\n mfaCode,\n ],\n });\n}\n\nexport function switchOAuthToEmail(currentService: string, email: string, password: string): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.switchOAuthToEmail,\n params: [\n currentService,\n email,\n password,\n ],\n });\n}\n\nexport function switchEmailToLdap(email: string, emailPassword: string, ldapId: string, ldapPassword: string, mfaCode = ''): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.switchEmailToLdap,\n params: [\n email,\n emailPassword,\n ldapId,\n ldapPassword,\n mfaCode,\n ],\n });\n}\n\nexport function switchLdapToEmail(ldapPassword: string, email: string, emailPassword: string, mfaCode = ''): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.switchLdapToEmail,\n params: [\n ldapPassword,\n email,\n emailPassword,\n mfaCode,\n ],\n });\n}\n\nexport function createUserAccessToken(userId: string, description: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n\n try {\n data = await Client4.createUserAccessToken(userId, description);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const actions: Action[] = [{\n type: AdminTypes.RECEIVED_USER_ACCESS_TOKEN,\n data: {...data,\n token: '',\n },\n }];\n\n const {currentUserId} = getState().entities.users;\n if (userId === currentUserId) {\n actions.push(\n {\n type: UserTypes.RECEIVED_MY_USER_ACCESS_TOKEN,\n data: {...data, token: ''},\n },\n );\n }\n\n dispatch(batchActions(actions));\n\n return {data};\n };\n}\n\nexport function getUserAccessToken(tokenId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getUserAccessToken(tokenId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const actions: Action[] = [{\n type: AdminTypes.RECEIVED_USER_ACCESS_TOKEN,\n data,\n }];\n\n const {currentUserId} = getState().entities.users;\n if (data.user_id === currentUserId) {\n actions.push(\n {\n type: UserTypes.RECEIVED_MY_USER_ACCESS_TOKEN,\n data,\n },\n );\n }\n\n dispatch(batchActions(actions));\n\n return {data};\n };\n}\n\nexport function getUserAccessTokens(page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n\n try {\n data = await Client4.getUserAccessTokens(page, perPage);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const actions = [\n {\n type: AdminTypes.RECEIVED_USER_ACCESS_TOKENS,\n data,\n },\n ];\n\n dispatch(batchActions(actions));\n\n return {data};\n };\n}\n\nexport function getUserAccessTokensForUser(userId: string, page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n let data;\n try {\n data = await Client4.getUserAccessTokensForUser(userId, page, perPage);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n const actions: Action[] = [{\n type: AdminTypes.RECEIVED_USER_ACCESS_TOKENS_FOR_USER,\n data,\n userId,\n }];\n\n const {currentUserId} = getState().entities.users;\n if (userId === currentUserId) {\n actions.push(\n {\n type: UserTypes.RECEIVED_MY_USER_ACCESS_TOKENS,\n data,\n },\n );\n }\n\n dispatch(batchActions(actions));\n\n return {data};\n };\n}\n\nexport function revokeUserAccessToken(tokenId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.revokeUserAccessToken(tokenId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.REVOKED_USER_ACCESS_TOKEN,\n data: tokenId,\n });\n\n return {data: true};\n };\n}\n\nexport function disableUserAccessToken(tokenId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.disableUserAccessToken(tokenId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.DISABLED_USER_ACCESS_TOKEN,\n data: tokenId,\n });\n\n return {data: true};\n };\n}\n\nexport function enableUserAccessToken(tokenId: string): ActionFunc {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n try {\n await Client4.enableUserAccessToken(tokenId);\n } catch (error) {\n forceLogoutIfNecessary(error, dispatch, getState);\n dispatch(logError(error));\n return {error};\n }\n\n dispatch({\n type: UserTypes.ENABLED_USER_ACCESS_TOKEN,\n data: tokenId,\n });\n\n return {data: true};\n };\n}\n\nexport function getKnownUsers(): ActionFunc {\n return bindClientFunc({\n clientFunc: Client4.getKnownUsers,\n });\n}\n\nexport function clearUserAccessTokens(): ActionFunc {\n return async (dispatch) => {\n dispatch({type: UserTypes.CLEAR_MY_USER_ACCESS_TOKENS, data: null});\n return {data: true};\n };\n}\n\nexport function checkForModifiedUsers() {\n return async (dispatch: DispatchFunc, getState: GetStateFunc) => {\n const state = getState();\n const users = getUsers(state);\n const lastDisconnectAt = state.websocket.lastDisconnectAt;\n const serverVersion = getServerVersion(state);\n\n if (!isMinimumServerVersion(serverVersion, 5, 14)) {\n return {data: true};\n }\n\n await dispatch(getProfilesByIds(Object.keys(users), {since: lastDisconnectAt}));\n return {data: true};\n };\n}\n\nexport default {\n checkMfa,\n generateMfaSecret,\n login,\n logout,\n getProfiles,\n getProfilesByIds,\n getProfilesInTeam,\n getProfilesInChannel,\n getProfilesNotInChannel,\n getUser,\n getMe,\n getUserByUsername,\n getStatus,\n getStatusesByIds,\n getSessions,\n getTotalUsersStats,\n revokeSession,\n revokeAllSessionsForUser,\n revokeSessionsForAllUsers,\n getUserAudits,\n searchProfiles,\n startPeriodicStatusUpdates,\n stopPeriodicStatusUpdates,\n updateMe,\n updateUserRoles,\n updateUserMfa,\n updateUserPassword,\n updateUserActive,\n verifyUserEmail,\n sendVerificationEmail,\n resetUserPassword,\n sendPasswordResetEmail,\n uploadProfileImage,\n switchEmailToOAuth,\n switchOAuthToEmail,\n switchEmailToLdap,\n switchLdapToEmail,\n getTermsOfService,\n createTermsOfService,\n updateMyTermsOfServiceStatus,\n createUserAccessToken,\n getUserAccessToken,\n getUserAccessTokensForUser,\n revokeUserAccessToken,\n disableUserAccessToken,\n enableUserAccessToken,\n checkForModifiedUsers,\n};\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {Options} from 'mattermost-redux/types/client4';\n\nconst data: {[x: string]: any} = {};\nconst etags: {[x: string]: string} = {};\n\nexport default ((url: string, options: Options = {headers: {}}): Promise => {\n url = url || options.url || ''; // eslint-disable-line no-param-reassign\n\n if (options.method === 'GET' || !options.method) {\n const etag = etags[url!];\n const cachedResponse = data[`${url}${etag}`]; // ensure etag is for url\n if (etag) {\n options.headers!['If-None-Match'] = etag;\n }\n\n return fetch(url!, options).\n then((response) => {\n if (response.status === 304) {\n return cachedResponse.clone();\n }\n\n if (response.status === 200) {\n const responseEtag = response.headers.get('Etag');\n\n if (responseEtag) {\n data[`${url}${responseEtag}`] = response.clone();\n etags[url!] = responseEtag;\n }\n }\n\n return response;\n });\n }\n\n // all other requests go straight to fetch\n return Reflect.apply(fetch, undefined, [url, options]);\n});\n","// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {SystemSetting} from 'mattermost-redux/types/general';\n\nimport {General} from '../constants';\n\nimport {ClusterInfo, AnalyticsRow} from 'mattermost-redux/types/admin';\nimport type {AppCallRequest, AppCallResponse, AppCallType} from 'mattermost-redux/types/apps';\nimport {Audit} from 'mattermost-redux/types/audits';\nimport {UserAutocomplete, AutocompleteSuggestion} from 'mattermost-redux/types/autocomplete';\nimport {Bot, BotPatch} from 'mattermost-redux/types/bots';\nimport {Product, Subscription, CloudCustomer, Address, CloudCustomerPatch, Invoice, SubscriptionStats} from 'mattermost-redux/types/cloud';\nimport {ChannelCategory, OrderedChannelCategories} from 'mattermost-redux/types/channel_categories';\nimport {\n Channel,\n ChannelMemberCountsByGroup,\n ChannelMembership,\n ChannelModeration,\n ChannelModerationPatch,\n ChannelStats,\n ChannelsWithTotalCount,\n ChannelUnread,\n ChannelViewResponse,\n ChannelWithTeamData,\n ChannelSearchOpts,\n} from 'mattermost-redux/types/channels';\nimport {Options, StatusOK, ClientResponse} from 'mattermost-redux/types/client4';\nimport {Compliance} from 'mattermost-redux/types/compliance';\nimport {\n ClientConfig,\n ClientLicense,\n DataRetentionPolicy,\n License,\n AdminConfig,\n EnvironmentConfig,\n} from 'mattermost-redux/types/config';\nimport {CustomEmoji} from 'mattermost-redux/types/emojis';\nimport {ServerError} from 'mattermost-redux/types/errors';\n\nimport {FileInfo, FileUploadResponse, FileSearchResults} from 'mattermost-redux/types/files';\n\nimport {\n Group,\n GroupPatch,\n GroupSyncable,\n MixedUnlinkedGroup,\n SyncablePatch,\n UsersWithGroupsAndCount,\n GroupsWithCount,\n} from 'mattermost-redux/types/groups';\nimport {PostActionResponse} from 'mattermost-redux/types/integration_actions';\nimport {\n Command,\n CommandArgs,\n CommandResponse,\n DialogSubmission,\n IncomingWebhook,\n OAuthApp,\n OutgoingWebhook,\n SubmitDialogResponse,\n} from 'mattermost-redux/types/integrations';\nimport {Job} from 'mattermost-redux/types/jobs';\nimport {MfaSecret} from 'mattermost-redux/types/mfa';\nimport {\n ClientPluginManifest,\n PluginManifest,\n PluginsResponse,\n PluginStatus,\n} from 'mattermost-redux/types/plugins';\nimport type {\n MarketplaceApp,\n MarketplacePlugin,\n} from 'mattermost-redux/types/marketplace';\nimport {Post, PostList, PostSearchResults, OpenGraphMetadata} from 'mattermost-redux/types/posts';\nimport {PreferenceType} from 'mattermost-redux/types/preferences';\nimport {Reaction} from 'mattermost-redux/types/reactions';\nimport {Role} from 'mattermost-redux/types/roles';\nimport {SamlCertificateStatus, SamlMetadataResponse} from 'mattermost-redux/types/saml';\nimport {Scheme} from 'mattermost-redux/types/schemes';\nimport {Session} from 'mattermost-redux/types/sessions';\nimport {\n GetTeamMembersOpts,\n Team,\n TeamInviteWithError,\n TeamMembership,\n TeamMemberWithError,\n TeamStats,\n TeamsWithCount,\n TeamUnread,\n TeamSearchOpts,\n} from 'mattermost-redux/types/teams';\nimport {TermsOfService} from 'mattermost-redux/types/terms_of_service';\nimport {\n AuthChangeResponse,\n UserAccessToken,\n UserProfile,\n UsersStats,\n UserStatus,\n GetFilteredUsersStatsOpts,\n UserCustomStatus,\n} from 'mattermost-redux/types/users';\nimport {$ID, RelationOneToOne} from 'mattermost-redux/types/utilities';\nimport {ProductNotices} from 'mattermost-redux/types/product_notices';\nimport {\n DataRetentionCustomPolicies,\n CreateDataRetentionCustomPolicy,\n PatchDataRetentionCustomPolicy,\n GetDataRetentionCustomPoliciesRequest,\n} from 'mattermost-redux/types/data_retention';\n\nimport {buildQueryString, isMinimumServerVersion} from 'mattermost-redux/utils/helpers';\nimport {cleanUrlForLogging} from 'mattermost-redux/utils/sentry';\nimport {isSystemAdmin} from 'mattermost-redux/utils/user_utils';\n\nimport {UserThreadList, UserThread, UserThreadWithPost} from 'mattermost-redux/types/threads';\n\nimport fetch from './fetch_etag';\nimport {TelemetryHandler} from './telemetry';\n\nconst FormData = require('form-data');\nconst HEADER_AUTH = 'Authorization';\nconst HEADER_BEARER = 'BEARER';\nconst HEADER_REQUESTED_WITH = 'X-Requested-With';\nconst HEADER_USER_AGENT = 'User-Agent';\nconst HEADER_X_CLUSTER_ID = 'X-Cluster-Id';\nconst HEADER_X_CSRF_TOKEN = 'X-CSRF-Token';\nexport const HEADER_X_VERSION_ID = 'X-Version-Id';\nconst PER_PAGE_DEFAULT = 60;\nconst LOGS_PER_PAGE_DEFAULT = 10000;\nexport const DEFAULT_LIMIT_BEFORE = 30;\nexport const DEFAULT_LIMIT_AFTER = 30;\n/* eslint-disable no-throw-literal */\n\nexport default class Client4 {\n logToConsole = false;\n serverVersion = '';\n clusterId = '';\n token = '';\n csrf = '';\n url = '';\n urlVersion = '/api/v4';\n userAgent: string|null = null;\n enableLogging = false;\n defaultHeaders: {[x: string]: string} = {};\n userId = '';\n diagnosticId = '';\n includeCookies = true;\n translations = {\n connectionError: 'There appears to be a problem with your internet connection.',\n unknownError: 'We received an unexpected status code from the server.',\n };\n userRoles?: string;\n\n telemetryHandler?: TelemetryHandler;\n\n getUrl() {\n return this.url;\n }\n\n getAbsoluteUrl(baseUrl: string) {\n if (typeof baseUrl !== 'string' || !baseUrl.startsWith('/')) {\n return baseUrl;\n }\n return this.getUrl() + baseUrl;\n }\n\n setUrl(url: string) {\n this.url = url;\n }\n\n setUserAgent(userAgent: string) {\n this.userAgent = userAgent;\n }\n\n getToken() {\n return this.token;\n }\n\n setToken(token: string) {\n this.token = token;\n }\n\n setCSRF(csrfToken: string) {\n this.csrf = csrfToken;\n }\n\n setAcceptLanguage(locale: string) {\n this.defaultHeaders['Accept-Language'] = locale;\n }\n\n setEnableLogging(enable: boolean) {\n this.enableLogging = enable;\n }\n\n setIncludeCookies(include: boolean) {\n this.includeCookies = include;\n }\n\n setUserId(userId: string) {\n this.userId = userId;\n }\n\n setUserRoles(roles: string) {\n this.userRoles = roles;\n }\n\n setDiagnosticId(diagnosticId: string) {\n this.diagnosticId = diagnosticId;\n }\n\n setTelemetryHandler(telemetryHandler?: TelemetryHandler) {\n this.telemetryHandler = telemetryHandler;\n }\n\n getServerVersion() {\n return this.serverVersion;\n }\n\n getUrlVersion() {\n return this.urlVersion;\n }\n\n getBaseRoute() {\n return `${this.url}${this.urlVersion}`;\n }\n\n // This function belongs to the Apps Framework feature.\n // Apps Framework feature is experimental, and this function is susceptible\n // to breaking changes without pushing the major version of this package.\n getAppsProxyRoute() {\n return `${this.url}/plugins/com.mattermost.apps`;\n }\n\n getUsersRoute() {\n return `${this.getBaseRoute()}/users`;\n }\n\n getUserRoute(userId: string) {\n return `${this.getUsersRoute()}/${userId}`;\n }\n\n getTeamsRoute() {\n return `${this.getBaseRoute()}/teams`;\n }\n\n getTeamRoute(teamId: string) {\n return `${this.getTeamsRoute()}/${teamId}`;\n }\n\n getTeamSchemeRoute(teamId: string) {\n return `${this.getTeamRoute(teamId)}/scheme`;\n }\n\n getTeamNameRoute(teamName: string) {\n return `${this.getTeamsRoute()}/name/${teamName}`;\n }\n\n getTeamMembersRoute(teamId: string) {\n return `${this.getTeamRoute(teamId)}/members`;\n }\n\n getTeamMemberRoute(teamId: string, userId: string) {\n return `${this.getTeamMembersRoute(teamId)}/${userId}`;\n }\n\n getChannelsRoute() {\n return `${this.getBaseRoute()}/channels`;\n }\n\n getChannelRoute(channelId: string) {\n return `${this.getChannelsRoute()}/${channelId}`;\n }\n\n getChannelMembersRoute(channelId: string) {\n return `${this.getChannelRoute(channelId)}/members`;\n }\n\n getChannelMemberRoute(channelId: string, userId: string) {\n return `${this.getChannelMembersRoute(channelId)}/${userId}`;\n }\n\n getChannelSchemeRoute(channelId: string) {\n return `${this.getChannelRoute(channelId)}/scheme`;\n }\n\n getChannelCategoriesRoute(userId: string, teamId: string) {\n return `${this.getBaseRoute()}/users/${userId}/teams/${teamId}/channels/categories`;\n }\n\n getPostsRoute() {\n return `${this.getBaseRoute()}/posts`;\n }\n\n getPostRoute(postId: string) {\n return `${this.getPostsRoute()}/${postId}`;\n }\n\n getReactionsRoute() {\n return `${this.getBaseRoute()}/reactions`;\n }\n\n getCommandsRoute() {\n return `${this.getBaseRoute()}/commands`;\n }\n\n getFilesRoute() {\n return `${this.getBaseRoute()}/files`;\n }\n\n getFileRoute(fileId: string) {\n return `${this.getFilesRoute()}/${fileId}`;\n }\n\n getPreferencesRoute(userId: string) {\n return `${this.getUserRoute(userId)}/preferences`;\n }\n\n getIncomingHooksRoute() {\n return `${this.getBaseRoute()}/hooks/incoming`;\n }\n\n getIncomingHookRoute(hookId: string) {\n return `${this.getBaseRoute()}/hooks/incoming/${hookId}`;\n }\n\n getOutgoingHooksRoute() {\n return `${this.getBaseRoute()}/hooks/outgoing`;\n }\n\n getOutgoingHookRoute(hookId: string) {\n return `${this.getBaseRoute()}/hooks/outgoing/${hookId}`;\n }\n\n getOAuthRoute() {\n return `${this.url}/oauth`;\n }\n\n getOAuthAppsRoute() {\n return `${this.getBaseRoute()}/oauth/apps`;\n }\n\n getOAuthAppRoute(appId: string) {\n return `${this.getOAuthAppsRoute()}/${appId}`;\n }\n\n getEmojisRoute() {\n return `${this.getBaseRoute()}/emoji`;\n }\n\n getEmojiRoute(emojiId: string) {\n return `${this.getEmojisRoute()}/${emojiId}`;\n }\n\n getBrandRoute() {\n return `${this.getBaseRoute()}/brand`;\n }\n\n getBrandImageUrl(timestamp: string) {\n return `${this.getBrandRoute()}/image?t=${timestamp}`;\n }\n\n getDataRetentionRoute() {\n return `${this.getBaseRoute()}/data_retention`;\n }\n\n getJobsRoute() {\n return `${this.getBaseRoute()}/jobs`;\n }\n\n getPluginsRoute() {\n return `${this.getBaseRoute()}/plugins`;\n }\n\n getPluginRoute(pluginId: string) {\n return `${this.getPluginsRoute()}/${pluginId}`;\n }\n\n getPluginsMarketplaceRoute() {\n return `${this.getPluginsRoute()}/marketplace`;\n }\n\n getRolesRoute() {\n return `${this.getBaseRoute()}/roles`;\n }\n\n getSchemesRoute() {\n return `${this.getBaseRoute()}/schemes`;\n }\n\n getRedirectLocationRoute() {\n return `${this.getBaseRoute()}/redirect_location`;\n }\n\n getBotsRoute() {\n return `${this.getBaseRoute()}/bots`;\n }\n\n getBotRoute(botUserId: string) {\n return `${this.getBotsRoute()}/${botUserId}`;\n }\n\n getGroupsRoute() {\n return `${this.getBaseRoute()}/groups`;\n }\n\n getGroupRoute(groupID: string) {\n return `${this.getGroupsRoute()}/${groupID}`;\n }\n\n getNoticesRoute() {\n return `${this.getBaseRoute()}/system/notices`;\n }\n\n getCloudRoute() {\n return `${this.getBaseRoute()}/cloud`;\n }\n\n getPermissionsRoute() {\n return `${this.getBaseRoute()}/permissions`;\n }\n\n getUserThreadsRoute(userID: string, teamID: string): string {\n return `${this.getUserRoute(userID)}/teams/${teamID}/threads`;\n }\n\n getUserThreadRoute(userId: string, teamId: string, threadId: string): string {\n return `${this.getUserThreadsRoute(userId, teamId)}/${threadId}`;\n }\n\n getCSRFFromCookie() {\n if (typeof document !== 'undefined' && typeof document.cookie !== 'undefined') {\n const cookies = document.cookie.split(';');\n for (let i = 0; i < cookies.length; i++) {\n const cookie = cookies[i].trim();\n if (cookie.startsWith('MMCSRF=')) {\n return cookie.replace('MMCSRF=', '');\n }\n }\n }\n return '';\n }\n\n getOptions(options: Options) {\n const newOptions: Options = {...options};\n\n const headers: {[x: string]: string} = {\n [HEADER_REQUESTED_WITH]: 'XMLHttpRequest',\n ...this.defaultHeaders,\n };\n\n if (this.token) {\n headers[HEADER_AUTH] = `${HEADER_BEARER} ${this.token}`;\n }\n\n const csrfToken = this.csrf || this.getCSRFFromCookie();\n if (options.method && options.method.toLowerCase() !== 'get' && csrfToken) {\n headers[HEADER_X_CSRF_TOKEN] = csrfToken;\n }\n\n if (this.includeCookies) {\n newOptions.credentials = 'include';\n }\n\n if (this.userAgent) {\n headers[HEADER_USER_AGENT] = this.userAgent;\n }\n\n if (newOptions.headers) {\n Object.assign(headers, newOptions.headers);\n }\n\n return {\n ...newOptions,\n headers,\n };\n }\n\n // User Routes\n\n createUser = (user: UserProfile, token: string, inviteId: string, redirect: string) => {\n this.trackEvent('api', 'api_users_create');\n\n const queryParams: any = {};\n\n if (token) {\n queryParams.t = token;\n }\n\n if (inviteId) {\n queryParams.iid = inviteId;\n }\n\n if (redirect) {\n queryParams.r = redirect;\n }\n\n return this.doFetch(\n `${this.getUsersRoute()}${buildQueryString(queryParams)}`,\n {method: 'post', body: JSON.stringify(user)},\n );\n }\n\n patchMe = (userPatch: Partial) => {\n return this.doFetch(\n `${this.getUserRoute('me')}/patch`,\n {method: 'put', body: JSON.stringify(userPatch)},\n );\n }\n\n patchUser = (userPatch: Partial & {id: string}) => {\n this.trackEvent('api', 'api_users_patch');\n\n return this.doFetch(\n `${this.getUserRoute(userPatch.id)}/patch`,\n {method: 'put', body: JSON.stringify(userPatch)},\n );\n }\n\n updateUser = (user: UserProfile) => {\n this.trackEvent('api', 'api_users_update');\n\n return this.doFetch(\n `${this.getUserRoute(user.id)}`,\n {method: 'put', body: JSON.stringify(user)},\n );\n }\n\n promoteGuestToUser = (userId: string) => {\n this.trackEvent('api', 'api_users_promote_guest_to_user');\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/promote`,\n {method: 'post'},\n );\n }\n\n demoteUserToGuest = (userId: string) => {\n this.trackEvent('api', 'api_users_demote_user_to_guest');\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/demote`,\n {method: 'post'},\n );\n }\n\n updateUserRoles = (userId: string, roles: string) => {\n this.trackEvent('api', 'api_users_update_roles');\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/roles`,\n {method: 'put', body: JSON.stringify({roles})},\n );\n };\n\n updateUserMfa = (userId: string, activate: boolean, code: string) => {\n const body: any = {\n activate,\n };\n\n if (activate) {\n body.code = code;\n }\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/mfa`,\n {method: 'put', body: JSON.stringify(body)},\n );\n }\n\n updateUserPassword = (userId: string, currentPassword: string, newPassword: string) => {\n this.trackEvent('api', 'api_users_newpassword');\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/password`,\n {method: 'put', body: JSON.stringify({current_password: currentPassword, new_password: newPassword})},\n );\n }\n\n resetUserPassword = (token: string, newPassword: string) => {\n this.trackEvent('api', 'api_users_reset_password');\n\n return this.doFetch(\n `${this.getUsersRoute()}/password/reset`,\n {method: 'post', body: JSON.stringify({token, new_password: newPassword})},\n );\n }\n\n getKnownUsers = () => {\n this.trackEvent('api', 'api_get_known_users');\n\n return this.doFetch>>(\n `${this.getUsersRoute()}/known`,\n {method: 'get'},\n );\n }\n\n sendPasswordResetEmail = (email: string) => {\n this.trackEvent('api', 'api_users_send_password_reset');\n\n return this.doFetch(\n `${this.getUsersRoute()}/password/reset/send`,\n {method: 'post', body: JSON.stringify({email})},\n );\n }\n\n updateUserActive = (userId: string, active: boolean) => {\n this.trackEvent('api', 'api_users_update_active');\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/active`,\n {method: 'put', body: JSON.stringify({active})},\n );\n }\n\n uploadProfileImage = (userId: string, imageData: File) => {\n this.trackEvent('api', 'api_users_update_profile_picture');\n\n const formData = new FormData();\n formData.append('image', imageData);\n const request: any = {\n method: 'post',\n body: formData,\n };\n\n if (formData.getBoundary) {\n request.headers = {\n 'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,\n };\n }\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/image`,\n request,\n );\n };\n\n setDefaultProfileImage = (userId: string) => {\n this.trackEvent('api', 'api_users_set_default_profile_picture');\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/image`,\n {method: 'delete'},\n );\n };\n\n verifyUserEmail = (token: string) => {\n return this.doFetch(\n `${this.getUsersRoute()}/email/verify`,\n {method: 'post', body: JSON.stringify({token})},\n );\n }\n\n updateMyTermsOfServiceStatus = (termsOfServiceId: string, accepted: boolean) => {\n return this.doFetch(\n `${this.getUserRoute('me')}/terms_of_service`,\n {method: 'post', body: JSON.stringify({termsOfServiceId, accepted})},\n );\n }\n\n getTermsOfService = () => {\n return this.doFetch(\n `${this.getBaseRoute()}/terms_of_service`,\n {method: 'get'},\n );\n }\n\n createTermsOfService = (text: string) => {\n return this.doFetch(\n `${this.getBaseRoute()}/terms_of_service`,\n {method: 'post', body: JSON.stringify({text})},\n );\n }\n\n sendVerificationEmail = (email: string) => {\n return this.doFetch(\n `${this.getUsersRoute()}/email/verify/send`,\n {method: 'post', body: JSON.stringify({email})},\n );\n }\n\n login = (loginId: string, password: string, token = '', deviceId = '', ldapOnly = false) => {\n this.trackEvent('api', 'api_users_login');\n\n if (ldapOnly) {\n this.trackEvent('api', 'api_users_login_ldap');\n }\n\n const body: any = {\n device_id: deviceId,\n login_id: loginId,\n password,\n token,\n };\n\n if (ldapOnly) {\n body.ldap_only = 'true';\n }\n\n return this.doFetch(\n `${this.getUsersRoute()}/login`,\n {method: 'post', body: JSON.stringify(body)},\n );\n };\n\n loginById = (id: string, password: string, token = '', deviceId = '') => {\n this.trackEvent('api', 'api_users_login');\n const body: any = {\n device_id: deviceId,\n id,\n password,\n token,\n };\n\n return this.doFetch(\n `${this.getUsersRoute()}/login`,\n {method: 'post', body: JSON.stringify(body)},\n );\n };\n\n logout = async () => {\n this.trackEvent('api', 'api_users_logout');\n\n const {response} = await this.doFetchWithResponse(\n `${this.getUsersRoute()}/logout`,\n {method: 'post'},\n );\n\n if (response.ok) {\n this.token = '';\n }\n\n this.serverVersion = '';\n\n return response;\n };\n\n getProfiles = (page = 0, perPage = PER_PAGE_DEFAULT, options = {}) => {\n this.trackEvent('api', 'api_profiles_get');\n\n return this.doFetch(\n `${this.getUsersRoute()}${buildQueryString({page, per_page: perPage, ...options})}`,\n {method: 'get'},\n );\n };\n\n getProfilesByIds = (userIds: string[], options = {}) => {\n this.trackEvent('api', 'api_profiles_get_by_ids');\n\n return this.doFetch(\n `${this.getUsersRoute()}/ids${buildQueryString(options)}`,\n {method: 'post', body: JSON.stringify(userIds)},\n );\n };\n\n getProfilesByUsernames = (usernames: string[]) => {\n this.trackEvent('api', 'api_profiles_get_by_usernames');\n\n return this.doFetch(\n `${this.getUsersRoute()}/usernames`,\n {method: 'post', body: JSON.stringify(usernames)},\n );\n };\n\n getProfilesInTeam = (teamId: string, page = 0, perPage = PER_PAGE_DEFAULT, sort = '', options = {}) => {\n this.trackEvent('api', 'api_profiles_get_in_team', {team_id: teamId, sort});\n\n return this.doFetch(\n `${this.getUsersRoute()}${buildQueryString({...options, in_team: teamId, page, per_page: perPage, sort})}`,\n {method: 'get'},\n );\n };\n\n getProfilesNotInTeam = (teamId: string, groupConstrained: boolean, page = 0, perPage = PER_PAGE_DEFAULT) => {\n this.trackEvent('api', 'api_profiles_get_not_in_team', {team_id: teamId, group_constrained: groupConstrained});\n\n const queryStringObj: any = {not_in_team: teamId, page, per_page: perPage};\n if (groupConstrained) {\n queryStringObj.group_constrained = true;\n }\n\n return this.doFetch(\n `${this.getUsersRoute()}${buildQueryString(queryStringObj)}`,\n {method: 'get'},\n );\n };\n\n getProfilesWithoutTeam = (page = 0, perPage = PER_PAGE_DEFAULT, options = {}) => {\n this.trackEvent('api', 'api_profiles_get_without_team');\n\n return this.doFetch(\n `${this.getUsersRoute()}${buildQueryString({...options, without_team: 1, page, per_page: perPage})}`,\n {method: 'get'},\n );\n };\n\n getProfilesInChannel = (channelId: string, page = 0, perPage = PER_PAGE_DEFAULT, sort = '', options: {active?: boolean} = {}) => {\n this.trackEvent('api', 'api_profiles_get_in_channel', {channel_id: channelId});\n\n const serverVersion = this.getServerVersion();\n let queryStringObj;\n if (isMinimumServerVersion(serverVersion, 4, 7)) {\n queryStringObj = {in_channel: channelId, page, per_page: perPage, sort};\n } else {\n queryStringObj = {in_channel: channelId, page, per_page: perPage};\n }\n return this.doFetch(\n `${this.getUsersRoute()}${buildQueryString({...queryStringObj, ...options})}`,\n {method: 'get'},\n );\n };\n\n getProfilesInGroupChannels = (channelsIds: string[]) => {\n this.trackEvent('api', 'api_profiles_get_in_group_channels', {channelsIds});\n\n return this.doFetch>(\n `${this.getUsersRoute()}/group_channels`,\n {method: 'post', body: JSON.stringify(channelsIds)},\n );\n };\n\n getProfilesNotInChannel = (teamId: string, channelId: string, groupConstrained: boolean, page = 0, perPage = PER_PAGE_DEFAULT) => {\n this.trackEvent('api', 'api_profiles_get_not_in_channel', {team_id: teamId, channel_id: channelId, group_constrained: groupConstrained});\n\n const queryStringObj: any = {in_team: teamId, not_in_channel: channelId, page, per_page: perPage};\n if (groupConstrained) {\n queryStringObj.group_constrained = true;\n }\n\n return this.doFetch(\n `${this.getUsersRoute()}${buildQueryString(queryStringObj)}`,\n {method: 'get'},\n );\n };\n\n getProfilesInGroup = (groupId: string, page = 0, perPage = PER_PAGE_DEFAULT) => {\n return this.doFetch(\n `${this.getUsersRoute()}${buildQueryString({in_group: groupId, page, per_page: perPage})}`,\n {method: 'get'},\n );\n };\n\n getMe = () => {\n return this.doFetch(\n `${this.getUserRoute('me')}`,\n {method: 'get'},\n );\n };\n\n getUser = (userId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}`,\n {method: 'get'},\n );\n };\n\n getUserByUsername = (username: string) => {\n return this.doFetch(\n `${this.getUsersRoute()}/username/${username}`,\n {method: 'get'},\n );\n };\n\n getUserByEmail = (email: string) => {\n return this.doFetch(\n `${this.getUsersRoute()}/email/${email}`,\n {method: 'get'},\n );\n };\n\n getProfilePictureUrl = (userId: string, lastPictureUpdate: number) => {\n const params: any = {};\n\n if (lastPictureUpdate) {\n params._ = lastPictureUpdate;\n }\n\n return `${this.getUserRoute(userId)}/image${buildQueryString(params)}`;\n };\n\n getDefaultProfilePictureUrl = (userId: string) => {\n return `${this.getUserRoute(userId)}/image/default`;\n };\n\n autocompleteUsers = (name: string, teamId: string, channelId: string, options = {\n limit: General.AUTOCOMPLETE_LIMIT_DEFAULT,\n }) => {\n return this.doFetch(`${this.getUsersRoute()}/autocomplete${buildQueryString({\n in_team: teamId,\n in_channel: channelId,\n name,\n limit: options.limit,\n })}`, {\n method: 'get',\n });\n };\n\n getSessions = (userId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/sessions`,\n {method: 'get'},\n );\n };\n\n revokeSession = (userId: string, sessionId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/sessions/revoke`,\n {method: 'post', body: JSON.stringify({session_id: sessionId})},\n );\n };\n\n revokeAllSessionsForUser = (userId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/sessions/revoke/all`,\n {method: 'post'},\n );\n };\n\n revokeSessionsForAllUsers = () => {\n return this.doFetch(\n `${this.getUsersRoute()}/sessions/revoke/all`,\n {method: 'post'},\n );\n };\n\n getUserAudits = (userId: string, page = 0, perPage = PER_PAGE_DEFAULT) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/audits${buildQueryString({page, per_page: perPage})}`,\n {method: 'get'},\n );\n };\n\n checkUserMfa = (loginId: string) => {\n return this.doFetch<{mfa_required: boolean}>(\n `${this.getUsersRoute()}/mfa`,\n {method: 'post', body: JSON.stringify({login_id: loginId})},\n );\n };\n\n generateMfaSecret = (userId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/mfa/generate`,\n {method: 'post'},\n );\n };\n\n attachDevice = (deviceId: string) => {\n return this.doFetch(\n `${this.getUsersRoute()}/sessions/device`,\n {method: 'put', body: JSON.stringify({device_id: deviceId})},\n );\n };\n\n searchUsers = (term: string, options: any) => {\n this.trackEvent('api', 'api_search_users');\n\n return this.doFetch(\n `${this.getUsersRoute()}/search`,\n {method: 'post', body: JSON.stringify({term, ...options})},\n );\n };\n\n getStatusesByIds = (userIds: string[]) => {\n return this.doFetch(\n `${this.getUsersRoute()}/status/ids`,\n {method: 'post', body: JSON.stringify(userIds)},\n );\n };\n\n getStatus = (userId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/status`,\n {method: 'get'},\n );\n };\n\n updateStatus = (status: UserStatus) => {\n return this.doFetch(\n `${this.getUserRoute(status.user_id)}/status`,\n {method: 'put', body: JSON.stringify(status)},\n );\n };\n\n updateCustomStatus = (customStatus: UserCustomStatus) => {\n return this.doFetch(\n `${this.getUserRoute('me')}/status/custom`,\n {method: 'put', body: JSON.stringify(customStatus)},\n );\n };\n\n unsetCustomStatus = () => {\n return this.doFetch(\n `${this.getUserRoute('me')}/status/custom`,\n {method: 'delete'},\n );\n }\n\n removeRecentCustomStatus = (customStatus: UserCustomStatus) => {\n return this.doFetch(\n `${this.getUserRoute('me')}/status/custom/recent/delete`,\n {method: 'post', body: JSON.stringify(customStatus)},\n );\n }\n\n switchEmailToOAuth = (service: string, email: string, password: string, mfaCode = '') => {\n this.trackEvent('api', 'api_users_email_to_oauth');\n\n return this.doFetch(\n `${this.getUsersRoute()}/login/switch`,\n {method: 'post', body: JSON.stringify({current_service: 'email', new_service: service, email, password, mfa_code: mfaCode})},\n );\n };\n\n switchOAuthToEmail = (currentService: string, email: string, password: string) => {\n this.trackEvent('api', 'api_users_oauth_to_email');\n\n return this.doFetch(\n `${this.getUsersRoute()}/login/switch`,\n {method: 'post', body: JSON.stringify({current_service: currentService, new_service: 'email', email, new_password: password})},\n );\n };\n\n switchEmailToLdap = (email: string, emailPassword: string, ldapId: string, ldapPassword: string, mfaCode = '') => {\n this.trackEvent('api', 'api_users_email_to_ldap');\n\n return this.doFetch(\n `${this.getUsersRoute()}/login/switch`,\n {method: 'post', body: JSON.stringify({current_service: 'email', new_service: 'ldap', email, password: emailPassword, ldap_id: ldapId, new_password: ldapPassword, mfa_code: mfaCode})},\n );\n };\n\n switchLdapToEmail = (ldapPassword: string, email: string, emailPassword: string, mfaCode = '') => {\n this.trackEvent('api', 'api_users_ldap_to_email');\n\n return this.doFetch(\n `${this.getUsersRoute()}/login/switch`,\n {method: 'post', body: JSON.stringify({current_service: 'ldap', new_service: 'email', email, password: ldapPassword, new_password: emailPassword, mfa_code: mfaCode})},\n );\n };\n\n getAuthorizedOAuthApps = (userId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/oauth/apps/authorized`,\n {method: 'get'},\n );\n }\n\n authorizeOAuthApp = (responseType: string, clientId: string, redirectUri: string, state: string, scope: string) => {\n return this.doFetch(\n `${this.url}/oauth/authorize`,\n {method: 'post', body: JSON.stringify({client_id: clientId, response_type: responseType, redirect_uri: redirectUri, state, scope})},\n );\n }\n\n deauthorizeOAuthApp = (clientId: string) => {\n return this.doFetch(\n `${this.url}/oauth/deauthorize`,\n {method: 'post', body: JSON.stringify({client_id: clientId})},\n );\n }\n\n createUserAccessToken = (userId: string, description: string) => {\n this.trackEvent('api', 'api_users_create_access_token');\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/tokens`,\n {method: 'post', body: JSON.stringify({description})},\n );\n }\n\n getUserAccessToken = (tokenId: string) => {\n return this.doFetch(\n `${this.getUsersRoute()}/tokens/${tokenId}`,\n {method: 'get'},\n );\n }\n\n getUserAccessTokensForUser = (userId: string, page = 0, perPage = PER_PAGE_DEFAULT) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/tokens${buildQueryString({page, per_page: perPage})}`,\n {method: 'get'},\n );\n }\n\n getUserAccessTokens = (page = 0, perPage = PER_PAGE_DEFAULT) => {\n return this.doFetch(\n `${this.getUsersRoute()}/tokens${buildQueryString({page, per_page: perPage})}`,\n {method: 'get'},\n );\n }\n\n revokeUserAccessToken = (tokenId: string) => {\n this.trackEvent('api', 'api_users_revoke_access_token');\n\n return this.doFetch(\n `${this.getUsersRoute()}/tokens/revoke`,\n {method: 'post', body: JSON.stringify({token_id: tokenId})},\n );\n }\n\n disableUserAccessToken = (tokenId: string) => {\n return this.doFetch(\n `${this.getUsersRoute()}/tokens/disable`,\n {method: 'post', body: JSON.stringify({token_id: tokenId})},\n );\n }\n\n enableUserAccessToken = (tokenId: string) => {\n return this.doFetch(\n `${this.getUsersRoute()}/tokens/enable`,\n {method: 'post', body: JSON.stringify({token_id: tokenId})},\n );\n }\n\n // Team Routes\n\n createTeam = (team: Team) => {\n this.trackEvent('api', 'api_teams_create');\n\n return this.doFetch(\n `${this.getTeamsRoute()}`,\n {method: 'post', body: JSON.stringify(team)},\n );\n };\n\n deleteTeam = (teamId: string) => {\n this.trackEvent('api', 'api_teams_delete');\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}`,\n {method: 'delete'},\n );\n };\n\n unarchiveTeam = (teamId: string) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/restore`,\n {method: 'post'},\n );\n }\n\n updateTeam = (team: Team) => {\n this.trackEvent('api', 'api_teams_update_name', {team_id: team.id});\n\n return this.doFetch(\n `${this.getTeamRoute(team.id)}`,\n {method: 'put', body: JSON.stringify(team)},\n );\n };\n\n patchTeam = (team: Partial & {id: string}) => {\n this.trackEvent('api', 'api_teams_patch_name', {team_id: team.id});\n\n return this.doFetch(\n `${this.getTeamRoute(team.id)}/patch`,\n {method: 'put', body: JSON.stringify(team)},\n );\n };\n\n regenerateTeamInviteId = (teamId: string) => {\n this.trackEvent('api', 'api_teams_regenerate_invite_id', {team_id: teamId});\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/regenerate_invite_id`,\n {method: 'post'},\n );\n };\n\n updateTeamScheme = (teamId: string, schemeId: string) => {\n const patch = {scheme_id: schemeId};\n\n this.trackEvent('api', 'api_teams_update_scheme', {team_id: teamId, ...patch});\n\n return this.doFetch(\n `${this.getTeamSchemeRoute(teamId)}`,\n {method: 'put', body: JSON.stringify(patch)},\n );\n };\n\n checkIfTeamExists = (teamName: string) => {\n return this.doFetch<{exists: boolean}>(\n `${this.getTeamNameRoute(teamName)}/exists`,\n {method: 'get'},\n );\n };\n\n getTeams = (page = 0, perPage = PER_PAGE_DEFAULT, includeTotalCount = false, excludePolicyConstrained = false) => {\n return this.doFetch(\n `${this.getTeamsRoute()}${buildQueryString({page, per_page: perPage, include_total_count: includeTotalCount, exclude_policy_constrained: excludePolicyConstrained})}`,\n {method: 'get'},\n );\n };\n\n searchTeams = (term: string, opts: TeamSearchOpts) => {\n this.trackEvent('api', 'api_search_teams');\n\n return this.doFetch(\n `${this.getTeamsRoute()}/search`,\n {method: 'post', body: JSON.stringify({term, ...opts})},\n );\n };\n\n getTeam = (teamId: string) => {\n return this.doFetch(\n this.getTeamRoute(teamId),\n {method: 'get'},\n );\n };\n\n getTeamByName = (teamName: string) => {\n this.trackEvent('api', 'api_teams_get_team_by_name');\n\n return this.doFetch(\n this.getTeamNameRoute(teamName),\n {method: 'get'},\n );\n };\n\n getMyTeams = () => {\n return this.doFetch(\n `${this.getUserRoute('me')}/teams`,\n {method: 'get'},\n );\n };\n\n getTeamsForUser = (userId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/teams`,\n {method: 'get'},\n );\n };\n\n getMyTeamMembers = () => {\n return this.doFetch(\n `${this.getUserRoute('me')}/teams/members`,\n {method: 'get'},\n );\n };\n\n getMyTeamUnreads = (includeCollapsedThreads = false) => {\n return this.doFetch(\n `${this.getUserRoute('me')}/teams/unread${buildQueryString({include_collapsed_threads: includeCollapsedThreads})}`,\n {method: 'get'},\n );\n };\n\n getTeamMembers = (teamId: string, page = 0, perPage = PER_PAGE_DEFAULT, options: GetTeamMembersOpts) => {\n return this.doFetch(\n `${this.getTeamMembersRoute(teamId)}${buildQueryString({page, per_page: perPage, ...options})}`,\n {method: 'get'},\n );\n };\n\n getTeamMembersForUser = (userId: string) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/teams/members`,\n {method: 'get'},\n );\n };\n\n getTeamMember = (teamId: string, userId: string) => {\n return this.doFetch(\n `${this.getTeamMemberRoute(teamId, userId)}`,\n {method: 'get'},\n );\n };\n\n getTeamMembersByIds = (teamId: string, userIds: string[]) => {\n return this.doFetch(\n `${this.getTeamMembersRoute(teamId)}/ids`,\n {method: 'post', body: JSON.stringify(userIds)},\n );\n };\n\n addToTeam = (teamId: string, userId: string) => {\n this.trackEvent('api', 'api_teams_invite_members', {team_id: teamId});\n\n const member = {user_id: userId, team_id: teamId};\n return this.doFetch(\n `${this.getTeamMembersRoute(teamId)}`,\n {method: 'post', body: JSON.stringify(member)},\n );\n };\n\n addToTeamFromInvite = (token = '', inviteId = '') => {\n this.trackEvent('api', 'api_teams_invite_members');\n\n const query = buildQueryString({token, invite_id: inviteId});\n return this.doFetch(\n `${this.getTeamsRoute()}/members/invite${query}`,\n {method: 'post'},\n );\n };\n\n addUsersToTeam = (teamId: string, userIds: string[]) => {\n this.trackEvent('api', 'api_teams_batch_add_members', {team_id: teamId, count: userIds.length});\n\n const members: any = [];\n userIds.forEach((id) => members.push({team_id: teamId, user_id: id}));\n return this.doFetch(\n `${this.getTeamMembersRoute(teamId)}/batch`,\n {method: 'post', body: JSON.stringify(members)},\n );\n };\n\n addUsersToTeamGracefully = (teamId: string, userIds: string[]) => {\n this.trackEvent('api', 'api_teams_batch_add_members', {team_id: teamId, count: userIds.length});\n\n const members: any = [];\n userIds.forEach((id) => members.push({team_id: teamId, user_id: id}));\n return this.doFetch(\n `${this.getTeamMembersRoute(teamId)}/batch?graceful=true`,\n {method: 'post', body: JSON.stringify(members)},\n );\n };\n\n joinTeam = (inviteId: string) => {\n const query = buildQueryString({invite_id: inviteId});\n return this.doFetch(\n `${this.getTeamsRoute()}/members/invite${query}`,\n {method: 'post'},\n );\n };\n\n removeFromTeam = (teamId: string, userId: string) => {\n this.trackEvent('api', 'api_teams_remove_members', {team_id: teamId});\n\n return this.doFetch(\n `${this.getTeamMemberRoute(teamId, userId)}`,\n {method: 'delete'},\n );\n };\n\n getTeamStats = (teamId: string) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/stats`,\n {method: 'get'},\n );\n };\n\n getTotalUsersStats = () => {\n return this.doFetch(\n `${this.getUsersRoute()}/stats`,\n {method: 'get'},\n );\n };\n\n getFilteredUsersStats = (options: GetFilteredUsersStatsOpts) => {\n return this.doFetch(\n `${this.getUsersRoute()}/stats/filtered${buildQueryString(options)}`,\n {method: 'get'},\n );\n };\n\n invalidateAllEmailInvites = () => {\n return this.doFetch(\n `${this.getTeamsRoute()}/invites/email`,\n {method: 'delete'},\n );\n };\n\n getTeamInviteInfo = (inviteId: string) => {\n return this.doFetch<{\n display_name: string;\n description: string;\n name: string;\n id: string;\n }>(\n `${this.getTeamsRoute()}/invite/${inviteId}`,\n {method: 'get'},\n );\n };\n\n updateTeamMemberRoles = (teamId: string, userId: string, roles: string[]) => {\n this.trackEvent('api', 'api_teams_update_member_roles', {team_id: teamId});\n\n return this.doFetch(\n `${this.getTeamMemberRoute(teamId, userId)}/roles`,\n {method: 'put', body: JSON.stringify({roles})},\n );\n };\n\n sendEmailInvitesToTeam = (teamId: string, emails: string[]) => {\n this.trackEvent('api', 'api_teams_invite_members', {team_id: teamId});\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/invite/email`,\n {method: 'post', body: JSON.stringify(emails)},\n );\n };\n\n sendEmailGuestInvitesToChannels = (teamId: string, channelIds: string[], emails: string[], message: string) => {\n this.trackEvent('api', 'api_teams_invite_guests', {team_id: teamId, channel_ids: channelIds});\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/invite-guests/email`,\n {method: 'post', body: JSON.stringify({emails, channels: channelIds, message})},\n );\n };\n\n sendEmailInvitesToTeamGracefully = (teamId: string, emails: string[]) => {\n this.trackEvent('api', 'api_teams_invite_members', {team_id: teamId});\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/invite/email?graceful=true`,\n {method: 'post', body: JSON.stringify(emails)},\n );\n };\n\n sendEmailGuestInvitesToChannelsGracefully = async (teamId: string, channelIds: string[], emails: string[], message: string) => {\n this.trackEvent('api', 'api_teams_invite_guests', {team_id: teamId, channel_ids: channelIds});\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/invite-guests/email?graceful=true`,\n {method: 'post', body: JSON.stringify({emails, channels: channelIds, message})},\n );\n };\n\n getTeamIconUrl = (teamId: string, lastTeamIconUpdate: number) => {\n const params: any = {};\n if (lastTeamIconUpdate) {\n params._ = lastTeamIconUpdate;\n }\n\n return `${this.getTeamRoute(teamId)}/image${buildQueryString(params)}`;\n };\n\n setTeamIcon = (teamId: string, imageData: File) => {\n this.trackEvent('api', 'api_team_set_team_icon');\n\n const formData = new FormData();\n formData.append('image', imageData);\n\n const request: any = {\n method: 'post',\n body: formData,\n };\n\n if (formData.getBoundary) {\n request.headers = {\n 'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,\n };\n }\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/image`,\n request,\n );\n };\n\n removeTeamIcon = (teamId: string) => {\n this.trackEvent('api', 'api_team_remove_team_icon');\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/image`,\n {method: 'delete'},\n );\n };\n\n updateTeamMemberSchemeRoles = (teamId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => {\n const body = {scheme_user: isSchemeUser, scheme_admin: isSchemeAdmin};\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/members/${userId}/schemeRoles`,\n {method: 'put', body: JSON.stringify(body)},\n );\n };\n\n // Channel Routes\n\n getAllChannels = (page = 0, perPage = PER_PAGE_DEFAULT, notAssociatedToGroup = '', excludeDefaultChannels = false, includeTotalCount = false, includeDeleted = false, excludePolicyConstrained = false) => {\n const queryData = {\n page,\n per_page: perPage,\n not_associated_to_group: notAssociatedToGroup,\n exclude_default_channels: excludeDefaultChannels,\n include_total_count: includeTotalCount,\n include_deleted: includeDeleted,\n exclude_policy_constrained: excludePolicyConstrained,\n };\n return this.doFetch(\n `${this.getChannelsRoute()}${buildQueryString(queryData)}`,\n {method: 'get'},\n );\n };\n\n createChannel = (channel: Channel) => {\n this.trackEvent('api', 'api_channels_create', {team_id: channel.team_id});\n\n return this.doFetch(\n `${this.getChannelsRoute()}`,\n {method: 'post', body: JSON.stringify(channel)},\n );\n };\n\n createDirectChannel = (userIds: string[]) => {\n this.trackEvent('api', 'api_channels_create_direct');\n\n return this.doFetch(\n `${this.getChannelsRoute()}/direct`,\n {method: 'post', body: JSON.stringify(userIds)},\n );\n };\n\n createGroupChannel = (userIds: string[]) => {\n this.trackEvent('api', 'api_channels_create_group');\n\n return this.doFetch(\n `${this.getChannelsRoute()}/group`,\n {method: 'post', body: JSON.stringify(userIds)},\n );\n };\n\n deleteChannel = (channelId: string) => {\n this.trackEvent('api', 'api_channels_delete', {channel_id: channelId});\n\n return this.doFetch(\n `${this.getChannelRoute(channelId)}`,\n {method: 'delete'},\n );\n };\n\n unarchiveChannel = (channelId: string) => {\n this.trackEvent('api', 'api_channels_unarchive', {channel_id: channelId});\n\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/restore`,\n {method: 'post'},\n );\n };\n\n updateChannel = (channel: Channel) => {\n this.trackEvent('api', 'api_channels_update', {channel_id: channel.id});\n\n return this.doFetch(\n `${this.getChannelRoute(channel.id)}`,\n {method: 'put', body: JSON.stringify(channel)},\n );\n };\n\n updateChannelPrivacy = (channelId: string, privacy: any) => {\n this.trackEvent('api', 'api_channels_update_privacy', {channel_id: channelId, privacy});\n\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/privacy`,\n {method: 'put', body: JSON.stringify({privacy})},\n );\n };\n\n patchChannel = (channelId: string, channelPatch: Partial) => {\n this.trackEvent('api', 'api_channels_patch', {channel_id: channelId});\n\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/patch`,\n {method: 'put', body: JSON.stringify(channelPatch)},\n );\n };\n\n updateChannelNotifyProps = (props: any) => {\n this.trackEvent('api', 'api_users_update_channel_notifications', {channel_id: props.channel_id});\n\n return this.doFetch(\n `${this.getChannelMemberRoute(props.channel_id, props.user_id)}/notify_props`,\n {method: 'put', body: JSON.stringify(props)},\n );\n };\n\n updateChannelScheme = (channelId: string, schemeId: string) => {\n const patch = {scheme_id: schemeId};\n\n this.trackEvent('api', 'api_channels_update_scheme', {channel_id: channelId, ...patch});\n\n return this.doFetch(\n `${this.getChannelSchemeRoute(channelId)}`,\n {method: 'put', body: JSON.stringify(patch)},\n );\n };\n\n getChannel = (channelId: string) => {\n this.trackEvent('api', 'api_channel_get', {channel_id: channelId});\n\n return this.doFetch(\n `${this.getChannelRoute(channelId)}`,\n {method: 'get'},\n );\n };\n\n getChannelByName = (teamId: string, channelName: string, includeDeleted = false) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/channels/name/${channelName}?include_deleted=${includeDeleted}`,\n {method: 'get'},\n );\n };\n\n getChannelByNameAndTeamName = (teamName: string, channelName: string, includeDeleted = false) => {\n this.trackEvent('api', 'api_channel_get_by_name_and_teamName', {include_deleted: includeDeleted});\n\n return this.doFetch(\n `${this.getTeamNameRoute(teamName)}/channels/name/${channelName}?include_deleted=${includeDeleted}`,\n {method: 'get'},\n );\n };\n\n getChannels = (teamId: string, page = 0, perPage = PER_PAGE_DEFAULT) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/channels${buildQueryString({page, per_page: perPage})}`,\n {method: 'get'},\n );\n };\n\n getArchivedChannels = (teamId: string, page = 0, perPage = PER_PAGE_DEFAULT) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/channels/deleted${buildQueryString({page, per_page: perPage})}`,\n {method: 'get'},\n );\n };\n\n getMyChannels = (teamId: string, includeDeleted = false) => {\n return this.doFetch(\n `${this.getUserRoute('me')}/teams/${teamId}/channels${buildQueryString({include_deleted: includeDeleted})}`,\n {method: 'get'},\n );\n };\n\n getMyChannelMember = (channelId: string) => {\n return this.doFetch(\n `${this.getChannelMemberRoute(channelId, 'me')}`,\n {method: 'get'},\n );\n };\n\n getMyChannelMembers = (teamId: string) => {\n return this.doFetch(\n `${this.getUserRoute('me')}/teams/${teamId}/channels/members`,\n {method: 'get'},\n );\n };\n\n getChannelMembers = (channelId: string, page = 0, perPage = PER_PAGE_DEFAULT) => {\n return this.doFetch(\n `${this.getChannelMembersRoute(channelId)}${buildQueryString({page, per_page: perPage})}`,\n {method: 'get'},\n );\n };\n\n getChannelTimezones = (channelId: string) => {\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/timezones`,\n {method: 'get'},\n );\n };\n\n getChannelMember = (channelId: string, userId: string) => {\n return this.doFetch(\n `${this.getChannelMemberRoute(channelId, userId)}`,\n {method: 'get'},\n );\n };\n\n getChannelMembersByIds = (channelId: string, userIds: string[]) => {\n return this.doFetch(\n `${this.getChannelMembersRoute(channelId)}/ids`,\n {method: 'post', body: JSON.stringify(userIds)},\n );\n };\n\n addToChannel = (userId: string, channelId: string, postRootId = '') => {\n this.trackEvent('api', 'api_channels_add_member', {channel_id: channelId});\n\n const member = {user_id: userId, channel_id: channelId, post_root_id: postRootId};\n return this.doFetch(\n `${this.getChannelMembersRoute(channelId)}`,\n {method: 'post', body: JSON.stringify(member)},\n );\n };\n\n removeFromChannel = (userId: string, channelId: string) => {\n this.trackEvent('api', 'api_channels_remove_member', {channel_id: channelId});\n\n return this.doFetch(\n `${this.getChannelMemberRoute(channelId, userId)}`,\n {method: 'delete'},\n );\n };\n\n updateChannelMemberRoles = (channelId: string, userId: string, roles: string) => {\n return this.doFetch(\n `${this.getChannelMemberRoute(channelId, userId)}/roles`,\n {method: 'put', body: JSON.stringify({roles})},\n );\n };\n\n getChannelStats = (channelId: string) => {\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/stats`,\n {method: 'get'},\n );\n };\n\n getChannelModerations = (channelId: string) => {\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/moderations`,\n {method: 'get'},\n );\n };\n\n patchChannelModerations = (channelId: string, channelModerationsPatch: ChannelModerationPatch[]) => {\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/moderations/patch`,\n {method: 'put', body: JSON.stringify(channelModerationsPatch)},\n );\n };\n\n getChannelMemberCountsByGroup = (channelId: string, includeTimezones: boolean) => {\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/member_counts_by_group?include_timezones=${includeTimezones}`,\n {method: 'get'},\n );\n };\n\n viewMyChannel = (channelId: string, prevChannelId?: string) => {\n const data = {channel_id: channelId, prev_channel_id: prevChannelId, collapsed_threads_supported: true};\n return this.doFetch(\n `${this.getChannelsRoute()}/members/me/view`,\n {method: 'post', body: JSON.stringify(data)},\n );\n };\n\n autocompleteChannels = (teamId: string, name: string) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/channels/autocomplete${buildQueryString({name})}`,\n {method: 'get'},\n );\n };\n\n autocompleteChannelsForSearch = (teamId: string, name: string) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/channels/search_autocomplete${buildQueryString({name})}`,\n {method: 'get'},\n );\n };\n\n searchChannels = (teamId: string, term: string) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/channels/search`,\n {method: 'post', body: JSON.stringify({term})},\n );\n };\n\n searchArchivedChannels = (teamId: string, term: string) => {\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/channels/search_archived`,\n {method: 'post', body: JSON.stringify({term})},\n );\n };\n\n searchAllChannels = (term: string, opts: ChannelSearchOpts = {}) => {\n const body = {\n term,\n ...opts,\n };\n const includeDeleted = Boolean(opts.include_deleted);\n return this.doFetch(\n `${this.getChannelsRoute()}/search?include_deleted=${includeDeleted}`,\n {method: 'post', body: JSON.stringify(body)},\n );\n };\n\n searchGroupChannels = (term: string) => {\n return this.doFetch(\n `${this.getChannelsRoute()}/group/search`,\n {method: 'post', body: JSON.stringify({term})},\n );\n };\n\n updateChannelMemberSchemeRoles = (channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => {\n const body = {scheme_user: isSchemeUser, scheme_admin: isSchemeAdmin};\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/members/${userId}/schemeRoles`,\n {method: 'put', body: JSON.stringify(body)},\n );\n };\n\n // Channel Category Routes\n\n getChannelCategories = (userId: string, teamId: string) => {\n return this.doFetch(\n `${this.getChannelCategoriesRoute(userId, teamId)}`,\n {method: 'get'},\n );\n };\n\n createChannelCategory = (userId: string, teamId: string, category: Partial) => {\n return this.doFetch(\n `${this.getChannelCategoriesRoute(userId, teamId)}`,\n {method: 'post', body: JSON.stringify(category)},\n );\n };\n\n updateChannelCategories = (userId: string, teamId: string, categories: ChannelCategory[]) => {\n return this.doFetch(\n `${this.getChannelCategoriesRoute(userId, teamId)}`,\n {method: 'put', body: JSON.stringify(categories)},\n );\n };\n\n getChannelCategoryOrder = (userId: string, teamId: string) => {\n return this.doFetch(\n `${this.getChannelCategoriesRoute(userId, teamId)}/order`,\n {method: 'get'},\n );\n };\n\n updateChannelCategoryOrder = (userId: string, teamId: string, categoryOrder: string[]) => {\n return this.doFetch(\n `${this.getChannelCategoriesRoute(userId, teamId)}/order`,\n {method: 'put', body: JSON.stringify(categoryOrder)},\n );\n };\n\n getChannelCategory = (userId: string, teamId: string, categoryId: string) => {\n return this.doFetch(\n `${this.getChannelCategoriesRoute(userId, teamId)}/${categoryId}`,\n {method: 'get'},\n );\n };\n\n updateChannelCategory = (userId: string, teamId: string, category: ChannelCategory) => {\n return this.doFetch(\n `${this.getChannelCategoriesRoute(userId, teamId)}/${category.id}`,\n {method: 'put', body: JSON.stringify(category)},\n );\n };\n\n deleteChannelCategory = (userId: string, teamId: string, categoryId: string) => {\n return this.doFetch(\n `${this.getChannelCategoriesRoute(userId, teamId)}/${categoryId}`,\n {method: 'delete'},\n );\n }\n\n // Post Routes\n\n createPost = async (post: Post) => {\n const result = await this.doFetch(\n `${this.getPostsRoute()}`,\n {method: 'post', body: JSON.stringify(post)},\n );\n const analyticsData = {channel_id: result.channel_id, post_id: result.id, user_actual_id: result.user_id, root_id: result.root_id};\n this.trackEvent('api', 'api_posts_create', analyticsData);\n\n if (result.root_id != null && result.root_id !== '') {\n this.trackEvent('api', 'api_posts_replied', analyticsData);\n }\n return result;\n };\n\n updatePost = (post: Post) => {\n this.trackEvent('api', 'api_posts_update', {channel_id: post.channel_id, post_id: post.id});\n\n return this.doFetch(\n `${this.getPostRoute(post.id)}`,\n {method: 'put', body: JSON.stringify(post)},\n );\n };\n\n getPost = (postId: string) => {\n return this.doFetch(\n `${this.getPostRoute(postId)}`,\n {method: 'get'},\n );\n };\n\n patchPost = (postPatch: Partial & {id: string}) => {\n this.trackEvent('api', 'api_posts_patch', {channel_id: postPatch.channel_id, post_id: postPatch.id});\n\n return this.doFetch(\n `${this.getPostRoute(postPatch.id)}/patch`,\n {method: 'put', body: JSON.stringify(postPatch)},\n );\n };\n\n deletePost = (postId: string) => {\n this.trackEvent('api', 'api_posts_delete');\n\n return this.doFetch(\n `${this.getPostRoute(postId)}`,\n {method: 'delete'},\n );\n };\n\n getPostThread = (postId: string, fetchThreads = true, collapsedThreads = false, collapsedThreadsExtended = false) => {\n return this.doFetch(\n `${this.getPostRoute(postId)}/thread${buildQueryString({skipFetchThreads: !fetchThreads, collapsedThreads, collapsedThreadsExtended})}`,\n {method: 'get'},\n );\n };\n\n getPosts = (channelId: string, page = 0, perPage = PER_PAGE_DEFAULT, fetchThreads = true, collapsedThreads = false, collapsedThreadsExtended = false) => {\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/posts${buildQueryString({page, per_page: perPage, skipFetchThreads: !fetchThreads, collapsedThreads, collapsedThreadsExtended})}`,\n {method: 'get'},\n );\n };\n\n getPostsUnread = (channelId: string, userId: string, limitAfter = DEFAULT_LIMIT_AFTER, limitBefore = DEFAULT_LIMIT_BEFORE, fetchThreads = true, collapsedThreads = false, collapsedThreadsExtended = false) => {\n return this.doFetch(\n `${this.getUserRoute(userId)}/channels/${channelId}/posts/unread${buildQueryString({limit_after: limitAfter, limit_before: limitBefore, skipFetchThreads: !fetchThreads, collapsedThreads, collapsedThreadsExtended})}`,\n {method: 'get'},\n );\n };\n\n getPostsSince = (channelId: string, since: number, fetchThreads = true, collapsedThreads = false, collapsedThreadsExtended = false) => {\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/posts${buildQueryString({since, skipFetchThreads: !fetchThreads, collapsedThreads, collapsedThreadsExtended})}`,\n {method: 'get'},\n );\n };\n\n getPostsBefore = (channelId: string, postId: string, page = 0, perPage = PER_PAGE_DEFAULT, fetchThreads = true, collapsedThreads = false, collapsedThreadsExtended = false) => {\n this.trackEvent('api', 'api_posts_get_before', {channel_id: channelId});\n\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/posts${buildQueryString({before: postId, page, per_page: perPage, skipFetchThreads: !fetchThreads, collapsedThreads, collapsedThreadsExtended})}`,\n {method: 'get'},\n );\n };\n\n getPostsAfter = (channelId: string, postId: string, page = 0, perPage = PER_PAGE_DEFAULT, fetchThreads = true, collapsedThreads = false, collapsedThreadsExtended = false) => {\n this.trackEvent('api', 'api_posts_get_after', {channel_id: channelId});\n\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/posts${buildQueryString({after: postId, page, per_page: perPage, skipFetchThreads: !fetchThreads, collapsedThreads, collapsedThreadsExtended})}`,\n {method: 'get'},\n );\n };\n\n getUserThreads = (\n userId: $ID = 'me',\n teamId: $ID,\n {\n before = '',\n after = '',\n perPage = PER_PAGE_DEFAULT,\n extended = false,\n deleted = false,\n unread = false,\n since = 0,\n },\n ) => {\n return this.doFetch(\n `${this.getUserThreadsRoute(userId, teamId)}${buildQueryString({before, after, per_page: perPage, extended, deleted, unread, since})}`,\n {method: 'get'},\n );\n };\n\n getUserThread = (userId: string, teamId: string, threadId: string, extended = false) => {\n const url = `${this.getUserThreadRoute(userId, teamId, threadId)}`;\n return this.doFetch(\n `${url}${buildQueryString({extended})}`,\n {method: 'get'},\n );\n };\n\n updateThreadsReadForUser = (userId: string, teamId: string) => {\n const url = `${this.getUserThreadsRoute(userId, teamId)}/read`;\n return this.doFetch(\n url,\n {method: 'put'},\n );\n };\n\n updateThreadReadForUser = (userId: string, teamId: string, threadId: string, timestamp: number) => {\n const url = `${this.getUserThreadRoute(userId, teamId, threadId)}/read/${timestamp}`;\n return this.doFetch(\n url,\n {method: 'put'},\n );\n };\n\n updateThreadFollowForUser = (userId: string, teamId: string, threadId: string, state: boolean) => {\n const url = this.getUserThreadRoute(userId, teamId, threadId) + '/following';\n return this.doFetch(\n url,\n {method: state ? 'put' : 'delete'},\n );\n };\n\n getFileInfosForPost = (postId: string) => {\n return this.doFetch(\n `${this.getPostRoute(postId)}/files/info`,\n {method: 'get'},\n );\n };\n\n getFlaggedPosts = (userId: string, channelId = '', teamId = '', page = 0, perPage = PER_PAGE_DEFAULT) => {\n this.trackEvent('api', 'api_posts_get_flagged', {team_id: teamId});\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/posts/flagged${buildQueryString({channel_id: channelId, team_id: teamId, page, per_page: perPage})}`,\n {method: 'get'},\n );\n };\n\n getPinnedPosts = (channelId: string) => {\n this.trackEvent('api', 'api_posts_get_pinned', {channel_id: channelId});\n return this.doFetch(\n `${this.getChannelRoute(channelId)}/pinned`,\n {method: 'get'},\n );\n };\n\n markPostAsUnread = (userId: string, postId: string) => {\n this.trackEvent('api', 'api_post_set_unread_post');\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/posts/${postId}/set_unread`,\n {method: 'post', body: JSON.stringify({collapsed_threads_supported: true})},\n );\n }\n\n pinPost = (postId: string) => {\n this.trackEvent('api', 'api_posts_pin');\n\n return this.doFetch(\n `${this.getPostRoute(postId)}/pin`,\n {method: 'post'},\n );\n };\n\n unpinPost = (postId: string) => {\n this.trackEvent('api', 'api_posts_unpin');\n\n return this.doFetch(\n `${this.getPostRoute(postId)}/unpin`,\n {method: 'post'},\n );\n };\n\n addReaction = (userId: string, postId: string, emojiName: string) => {\n this.trackEvent('api', 'api_reactions_save', {post_id: postId});\n\n return this.doFetch(\n `${this.getReactionsRoute()}`,\n {method: 'post', body: JSON.stringify({user_id: userId, post_id: postId, emoji_name: emojiName})},\n );\n };\n\n removeReaction = (userId: string, postId: string, emojiName: string) => {\n this.trackEvent('api', 'api_reactions_delete', {post_id: postId});\n\n return this.doFetch(\n `${this.getUserRoute(userId)}/posts/${postId}/reactions/${emojiName}`,\n {method: 'delete'},\n );\n };\n\n getReactionsForPost = (postId: string) => {\n return this.doFetch(\n `${this.getPostRoute(postId)}/reactions`,\n {method: 'get'},\n );\n };\n\n searchPostsWithParams = (teamId: string, params: any) => {\n this.trackEvent('api', 'api_posts_search', {team_id: teamId});\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/posts/search`,\n {method: 'post', body: JSON.stringify(params)},\n );\n };\n\n searchPosts = (teamId: string, terms: string, isOrSearch: boolean) => {\n return this.searchPostsWithParams(teamId, {terms, is_or_search: isOrSearch});\n };\n\n searchFilesWithParams = (teamId: string, params: any) => {\n this.trackEvent('api', 'api_files_search', {team_id: teamId});\n\n return this.doFetch(\n `${this.getTeamRoute(teamId)}/files/search`,\n {method: 'post', body: JSON.stringify(params)},\n );\n };\n\n searchFiles = (teamId: string, terms: string, isOrSearch: boolean) => {\n return this.searchFilesWithParams(teamId, {terms, is_or_search: isOrSearch});\n };\n\n getOpenGraphMetadata = (url: string) => {\n return this.doFetch(\n `${this.getBaseRoute()}/opengraph`,\n {method: 'post', body: JSON.stringify({url})},\n );\n };\n\n doPostAction = (postId: string, actionId: string, selectedOption = '') => {\n return this.doPostActionWithCookie(postId, actionId, '', selectedOption);\n };\n\n doPostActionWithCookie = (postId: string, actionId: string, actionCookie: string, selectedOption = '') => {\n if (selectedOption) {\n this.trackEvent('api', 'api_interactive_messages_menu_selected');\n } else {\n this.trackEvent('api', 'api_interactive_messages_button_clicked');\n }\n\n const msg: any = {\n selected_option: selectedOption,\n };\n if (actionCookie !== '') {\n msg.cookie = actionCookie;\n }\n return this.doFetch(\n `${this.getPostRoute(postId)}/actions/${encodeURIComponent(actionId)}`,\n {method: 'post', body: JSON.stringify(msg)},\n );\n };\n\n // Files Routes\n\n getFileUrl(fileId: string, timestamp: number) {\n let url = `${this.getFileRoute(fileId)}`;\n if (timestamp) {\n url += `?${timestamp}`;\n }\n\n return url;\n }\n\n getFileThumbnailUrl(fileId: string, timestamp: number) {\n let url = `${this.getFileRoute(fileId)}/thumbnail`;\n if (timestamp) {\n url += `?${timestamp}`;\n }\n\n return url;\n }\n\n getFilePreviewUrl(fileId: string, timestamp: number) {\n let url = `${this.getFileRoute(fileId)}/preview`;\n if (timestamp) {\n url += `?${timestamp}`;\n }\n\n return url;\n }\n\n uploadFile = (fileFormData: any, formBoundary: string) => {\n this.trackEvent('api', 'api_files_upload');\n const request: any = {\n method: 'post',\n body: fileFormData,\n };\n\n if (formBoundary) {\n request.headers = {\n 'Content-Type': `multipart/form-data; boundary=${formBoundary}`,\n };\n }\n\n return this.doFetch(\n `${this.getFilesRoute()}`,\n request,\n );\n };\n\n getFilePublicLink = (fileId: string) => {\n return this.doFetch<{\n link: string;\n }>(\n `${this.getFileRoute(fileId)}/link`,\n {method: 'get'},\n );\n }\n\n // Preference Routes\n\n savePreferences = (userId: string, preferences: PreferenceType[]) => {\n return this.doFetch(\n `${this.getPreferencesRoute(userId)}`,\n {method: 'put', body: JSON.stringify(preferences)},\n );\n };\n\n getMyPreferences = () => {\n return this.doFetch(\n `${this.getPreferencesRoute('me')}`,\n {method: 'get'},\n );\n };\n\n deletePreferences = (userId: string, preferences: PreferenceType[]) => {\n return this.doFetch(\n `${this.getPreferencesRoute(userId)}/delete`,\n {method: 'post', body: JSON.stringify(preferences)},\n );\n };\n\n // General Routes\n\n ping = () => {\n return this.doFetch<{\n status: string;\n }>(\n `${this.getBaseRoute()}/system/ping?time=${Date.now()}`,\n {method: 'get'},\n );\n };\n\n upgradeToEnterprise = async () => {\n return this.doFetch(\n `${this.getBaseRoute()}/upgrade_to_enterprise`,\n {method: 'post'},\n );\n }\n\n upgradeToEnterpriseStatus = async () => {\n return this.doFetch<{\n percentage: number;\n error: string | null;\n }>(\n `${this.getBaseRoute()}/upgrade_to_enterprise/status`,\n {method: 'get'},\n );\n }\n\n restartServer = async () => {\n return this.doFetch(\n `${this.getBaseRoute()}/restart`,\n {method: 'post'},\n );\n }\n\n logClientError = (message: string, level = 'ERROR') => {\n const url = `${this.getBaseRoute()}/logs`;\n\n if (!this.enableLogging) {\n throw new ClientError(this.getUrl(), {\n message: 'Logging disabled.',\n url,\n });\n }\n\n return this.doFetch<{\n message: string;\n }>(\n url,\n {method: 'post', body: JSON.stringify({message, level})},\n );\n };\n\n getClientConfigOld = () => {\n return this.doFetch(\n `${this.getBaseRoute()}/config/client?format=old`,\n {method: 'get'},\n );\n };\n\n getClientLicenseOld = () => {\n return this.doFetch(\n `${this.getBaseRoute()}/license/client?format=old`,\n {method: 'get'},\n );\n };\n\n getWarnMetricsStatus = async () => {\n return this.doFetch(\n `${this.getBaseRoute()}/warn_metrics/status`,\n {method: 'get'},\n );\n };\n\n sendWarnMetricAck = async (warnMetricId: string, forceAckVal: boolean) => {\n return this.doFetch(\n `${this.getBaseRoute()}/warn_metrics/ack/${encodeURI(warnMetricId)}`,\n {method: 'post', body: JSON.stringify({forceAck: forceAckVal})},\n );\n }\n\n setFirstAdminVisitMarketplaceStatus = async () => {\n return this.doFetch(\n `${this.getPluginsRoute()}/marketplace/first_admin_visit`,\n {method: 'post', body: JSON.stringify({first_admin_visit_marketplace_status: true})},\n );\n }\n\n getFirstAdminVisitMarketplaceStatus = async () => {\n return this.doFetch(\n `${this.getPluginsRoute()}/marketplace/first_admin_visit`,\n {method: 'get'},\n );\n };\n\n getTranslations = (url: string) => {\n return this.doFetch>(\n url,\n {method: 'get'},\n );\n };\n\n getWebSocketUrl = () => {\n return `${this.getBaseRoute()}/websocket`;\n }\n\n // Integration Routes\n\n createIncomingWebhook = (hook: IncomingWebhook) => {\n this.trackEvent('api', 'api_integrations_created', {team_id: hook.team_id});\n\n return this.doFetch(\n `${this.getIncomingHooksRoute()}`,\n {method: 'post', body: JSON.stringify(hook)},\n );\n };\n\n getIncomingWebhook = (hookId: string) => {\n return this.doFetch(\n `${this.getIncomingHookRoute(hookId)}`,\n {method: 'get'},\n );\n };\n\n getIncomingWebhooks = (teamId = '', page = 0, perPage = PER_PAGE_DEFAULT) => {\n const queryParams: any = {\n page,\n per_page: perPage,\n };\n\n if (teamId) {\n queryParams.team_id = teamId;\n }\n\n return this.doFetch(\n `${this.getIncomingHooksRoute()}${buildQueryString(queryParams)}`,\n {method: 'get'},\n );\n };\n\n removeIncomingWebhook = (hookId: string) => {\n this.trackEvent('api', 'api_integrations_deleted');\n\n return this.doFetch(\n `${this.getIncomingHookRoute(hookId)}`,\n {method: 'delete'},\n );\n };\n\n updateIncomingWebhook = (hook: IncomingWebhook) => {\n this.trackEvent('api', 'api_integrations_updated', {team_id: hook.team_id});\n\n return this.doFetch