Define types for WebSocket messages and migrate WebSocket actions to TS (#34603)

* Add TS definitions for every WebSocket event

* Remove unused WebSocket events

* Add a few extra fields to POSTED events

* Stop reusing WS event types as Redux actions

* Remove now-unused WS event types from mattermost-redux

* Rename some types to be clearer

* Use new WebSocketEvents and WebSocketMessage type everywhere

* Reorganize and export named types for WS messages

* Use new types in websocket_actions.jsx the best we can

* Rename websocket_actions.jsx to websocket_actions.tsx

* Migrate websocket_actions.tsx to TypeScript

* Break up websocket_messages.ts and group together WebSocketMessages types

* Rename websocket_actions.tsx to websocket_actions.ts
This commit is contained in:
Harrison Healey
2026-01-23 14:29:40 -05:00
committed by GitHub
parent 09c4a61fed
commit 777867dc36
35 changed files with 1058 additions and 577 deletions
-2
View File
@@ -50,7 +50,6 @@ const (
WebsocketEventReactionRemoved WebsocketEventType = "reaction_removed"
WebsocketEventResponse WebsocketEventType = "response"
WebsocketEventEmojiAdded WebsocketEventType = "emoji_added"
WebsocketEventChannelViewed WebsocketEventType = "channel_viewed"
WebsocketEventMultipleChannelsViewed WebsocketEventType = "multiple_channels_viewed"
WebsocketEventPluginStatusesChanged WebsocketEventType = "plugin_statuses_changed"
WebsocketEventPluginEnabled WebsocketEventType = "plugin_enabled"
@@ -72,7 +71,6 @@ const (
WebsocketEventSidebarCategoryUpdated WebsocketEventType = "sidebar_category_updated"
WebsocketEventSidebarCategoryDeleted WebsocketEventType = "sidebar_category_deleted"
WebsocketEventSidebarCategoryOrderUpdated WebsocketEventType = "sidebar_category_order_updated"
WebsocketEventCloudPaymentStatusUpdated WebsocketEventType = "cloud_payment_status_updated"
WebsocketEventCloudSubscriptionChanged WebsocketEventType = "cloud_subscription_changed"
WebsocketEventThreadUpdated WebsocketEventType = "thread_updated"
WebsocketEventThreadFollowChanged WebsocketEventType = "thread_follow_changed"
@@ -1,25 +1,20 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Post} from '@mattermost/types/posts';
import type {WebSocketMessages} from '@mattermost/client';
import {PostTypes} from 'mattermost-redux/action_types';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import type {DispatchFunc, GetStateFunc} from 'types/store';
export interface PostRevealedData {
post?: string | Post;
recipients?: string[];
}
/**
* Handles the post_revealed websocket event for burn-on-read posts.
* Two scenarios:
* 1. Post author: Updates recipients list for real-time recipient count tracking
* 2. Revealing user: Updates post with revealed content for multi-device sync
*/
export function handleBurnOnReadPostRevealed(data: PostRevealedData) {
export function handleBurnOnReadPostRevealed(data: WebSocketMessages.BurnOnReadPostRevealed['data']) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const state = getState();
const currentUserId = getCurrentUserId(state);
@@ -74,16 +69,11 @@ export function handleBurnOnReadPostRevealed(data: PostRevealedData) {
};
}
export interface AllRevealedData {
post_id: string;
sender_expire_at: number;
}
/**
* Handles the burn_on_read_all_revealed websocket event.
* Sent to the post author when all recipients have revealed the message.
*/
export function handleBurnOnReadAllRevealed(data: AllRevealedData) {
export function handleBurnOnReadAllRevealed(data: WebSocketMessages.BurnOnReadPostAllRevealed['data']) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const state = getState();
const {post_id: postId, sender_expire_at: senderExpireAt} = data;
@@ -32,7 +32,7 @@ import {loadProfilesForSidebar} from 'actions/user_actions';
import {clearUserCookie} from 'actions/views/cookie';
import {close as closeLhs} from 'actions/views/lhs';
import {closeRightHandSide, closeMenu as closeRhsMenu, updateRhsState} from 'actions/views/rhs';
import * as WebsocketActions from 'actions/websocket_actions.jsx';
import * as WebsocketActions from 'actions/websocket_actions';
import {getCurrentLocale} from 'selectors/i18n';
import {getIsRhsOpen, getPreviousRhsState, getRhsState} from 'selectors/rhs';
import BrowserStore from 'stores/browser_store';
+3 -16
View File
@@ -4,7 +4,7 @@
import type {AnyAction} from 'redux';
import {batchActions} from 'redux-batched-actions';
import type {ChannelType} from '@mattermost/types/channels';
import type {WebSocketMessages} from '@mattermost/client';
import type {Post} from '@mattermost/types/posts';
import {
@@ -33,20 +33,7 @@ import {ActionTypes} from 'utils/constants';
import type {DispatchFunc, GetStateFunc, ActionFunc, ActionFuncAsync} from 'types/store';
export type NewPostMessageProps = {
channel_type: ChannelType;
channel_display_name: string;
channel_name: string;
sender_name: string;
set_online: boolean;
mentions?: string;
followers?: string;
team_id: string;
should_ack: boolean;
otherFile?: 'true';
image?: 'true';
post: string;
}
export type NewPostMessageProps = Partial<WebSocketMessages.Posted['data']>;
export function completePostReceive(post: Post, websocketMessageProps: NewPostMessageProps, fetchedChannelMember?: boolean): ActionFuncAsync<boolean> {
return async (dispatch, getState) => {
@@ -146,7 +133,7 @@ export function setChannelReadAndViewed(dispatch: DispatchFunc, getState: GetSta
return actionsToMarkChannelAsRead(getState, post.channel_id);
}
return actionsToMarkChannelAsUnread(getState, websocketMessageProps.team_id, post.channel_id, websocketMessageProps.mentions || '', fetchedChannelMember, post.root_id === '', post?.metadata?.priority?.priority);
return actionsToMarkChannelAsUnread(getState, websocketMessageProps.team_id || '', post.channel_id, websocketMessageProps.mentions || '', fetchedChannelMember, post.root_id === '', post?.metadata?.priority?.priority);
}
export function setThreadRead(post: Post): ActionFunc<boolean> {
@@ -106,14 +106,14 @@ export function sendDesktopNotification(post: Post, msgProps: NewPostMessageProp
return async (dispatch, getState) => {
const state = getState();
const teamId = msgProps.team_id;
const teamId = msgProps.team_id || '';
const channel = makeGetChannel()(state, post.channel_id) || {
id: post.channel_id,
name: msgProps.channel_name,
display_name: msgProps.channel_display_name,
type: msgProps.channel_type,
};
} as Channel;
const user = getCurrentUser(state);
const member = getMyChannelMember(state, post.channel_id);
const isCrtReply = isCollapsedThreadsEnabled(state) && post.root_id !== '';
@@ -185,7 +185,7 @@ const getNotificationTitle = (channel: Pick<Channel, 'type' | 'display_name'>, m
if (msgProps.channel_type === Constants.DM_CHANNEL) {
title = Utils.localizeMessage({id: 'notification.dm', defaultMessage: 'Direct Message'});
} else {
title = msgProps.channel_display_name;
title = msgProps.channel_display_name || '';
}
}
@@ -218,8 +218,7 @@ const getNotificationBody = (state: GlobalState, post: Post, msgProps: NewPostMe
let notifyText = post.message;
const msgPropsPost: Post = JSON.parse(msgProps.post);
const attachments = isMessageAttachmentArray(msgPropsPost?.props?.attachments) ? msgPropsPost.props.attachments : [];
const attachments = isMessageAttachmentArray(post.props?.attachments) ? post.props.attachments : [];
let image = false;
attachments.forEach((attachment) => {
if (notifyText.length === 0) {
@@ -244,7 +244,7 @@ describe('Actions.Posts', () => {
channelId: 'other_channel_id',
fetchedChannelMember: false,
onlyMentions: undefined,
teamId: undefined,
teamId: '',
},
},
{
@@ -263,7 +263,7 @@ describe('Actions.Posts', () => {
amountUrgent: 0,
channelId: 'other_channel_id',
fetchedChannelMember: false,
teamId: undefined,
teamId: '',
},
},
],
+4 -5
View File
@@ -4,8 +4,8 @@
import type {AnyAction} from 'redux';
import {batchActions} from 'redux-batched-actions';
import type {WebSocketMessages} from '@mattermost/client';
import type {FileInfo} from '@mattermost/types/files';
import type {GroupChannel} from '@mattermost/types/groups';
import type {Post} from '@mattermost/types/posts';
import type {ScheduledPost} from '@mattermost/types/schedule_post';
@@ -58,7 +58,6 @@ import type {
import type {PostDraft} from 'types/store/draft';
import type {StorageItem} from 'types/store/storage';
import type {NewPostMessageProps} from './new_post';
import {completePostReceive} from './new_post';
import type {OnSubmitOptions, SubmitPostReturnType} from './views/create_comment';
@@ -67,7 +66,7 @@ export type CreatePostOptions = {
ignorePostError?: boolean;
}
export function handleNewPost(post: Post, msg?: {data?: NewPostMessageProps & GroupChannel}): ActionFuncAsync<boolean> {
export function handleNewPost(post: Post, msg?: WebSocketMessages.Posted | WebSocketMessages.EphemeralPost): ActionFuncAsync<boolean> {
return async (dispatch, getState) => {
let websocketMessageProps = {};
const state = getState();
@@ -82,9 +81,9 @@ export function handleNewPost(post: Post, msg?: {data?: NewPostMessageProps & Gr
await dispatch(getMyChannelMember(post.channel_id));
}
dispatch(completePostReceive(post, websocketMessageProps as NewPostMessageProps, myChannelMemberDoesntExist));
dispatch(completePostReceive(post, websocketMessageProps, myChannelMemberDoesntExist));
if (msg && msg.data) {
if (msg && msg.data && 'channel_type' in msg.data) {
if (msg.data.channel_type === Constants.DM_CHANNEL) {
dispatch(loadNewDMIfNeeded(post.channel_id));
} else if (msg.data.channel_type === Constants.GM_CHANNEL) {
@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {WebSocketEvents} from '@mattermost/client';
import {CloudTypes} from 'mattermost-redux/action_types';
import {fetchMyCategories} from 'mattermost-redux/actions/channel_categories';
import {fetchAllMyTeamsChannels} from 'mattermost-redux/actions/channels';
@@ -24,7 +26,7 @@ import store from 'stores/redux_store';
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
import configureStore from 'tests/test_store';
import {getHistory} from 'utils/browser_history';
import Constants, {SocketEvents, ActionTypes, UserStatuses} from 'utils/constants';
import Constants, {ActionTypes, UserStatuses} from 'utils/constants';
import {
handleChannelUpdatedEvent,
@@ -230,7 +232,7 @@ jest.mock('actions/views/rhs', () => ({
describe('handleEvent', () => {
test('should dispatch channel updated event properly', () => {
const msg = {event: SocketEvents.CHANNEL_UPDATED};
const msg = {event: WebSocketEvents.ChannelUpdated};
handleEvent(msg);
@@ -861,7 +863,7 @@ describe('handleCloudSubscriptionChanged', () => {
id: 'newsub',
};
const msg = {
event: SocketEvents.CLOUD_PRODUCT_LIMITS_CHANGED,
event: WebSocketEvents.CloudSubscriptionChanged,
data: {
limits: newLimits,
subscription: newSubscription,
@@ -900,7 +902,7 @@ describe('handleCloudSubscriptionChanged', () => {
},
};
const msg = {
event: SocketEvents.CLOUD_PRODUCT_LIMITS_CHANGED,
event: WebSocketEvents.CloudSubscriptionChanged,
data: {
limits: newLimits,
},
@@ -934,7 +936,7 @@ describe('handleCloudSubscriptionChanged', () => {
};
const msg = {
event: SocketEvents.CLOUD_PRODUCT_LIMITS_CHANGED,
event: WebSocketEvents.CloudSubscriptionChanged,
data: {
subscription: newSubscription,
},
@@ -1294,7 +1296,7 @@ describe('handleStatusChangedEvent', () => {
expect(getStatusForUserId(testStore.getState(), currentUserId)).toBe(UserStatuses.ONLINE);
testStore.dispatch(handleStatusChangedEvent({
event: SocketEvents.STATUS_CHANGED,
event: WebSocketEvents.StatusChange,
data: {
user_id: currentUserId,
status: UserStatuses.AWAY,
@@ -1304,7 +1306,7 @@ describe('handleStatusChangedEvent', () => {
expect(getStatusForUserId(testStore.getState(), currentUserId)).toBe(UserStatuses.AWAY);
testStore.dispatch(handleStatusChangedEvent({
event: SocketEvents.STATUS_CHANGED,
event: WebSocketEvents.StatusChange,
data: {
user_id: currentUserId,
status: UserStatuses.ONLINE,
@@ -1314,7 +1316,7 @@ describe('handleStatusChangedEvent', () => {
expect(getStatusForUserId(testStore.getState(), currentUserId)).toBe(UserStatuses.ONLINE);
testStore.dispatch(handleStatusChangedEvent({
event: SocketEvents.STATUS_CHANGED,
event: WebSocketEvents.StatusChange,
data: {
user_id: currentUserId,
status: UserStatuses.OFFLINE,
@@ -1346,7 +1348,7 @@ describe('handleCustomAttributeValuesUpdated', () => {
expect(stateUser(testStore.getState(), currentUserId)).toEqual({id: currentUserId});
testStore.dispatch(handleCustomAttributeValuesUpdated({
event: SocketEvents.CPA_VALUES_UPDATED,
event: WebSocketEvents.CPAValuesUpdated,
data: {
user_id: currentUserId,
values: {field1: 'value1', field2: 'value2'},
@@ -1359,7 +1361,7 @@ describe('handleCustomAttributeValuesUpdated', () => {
// update one field, add new field
testStore.dispatch(handleCustomAttributeValuesUpdated({
event: SocketEvents.CPA_VALUES_UPDATED,
event: WebSocketEvents.CPAValuesUpdated,
data: {
user_id: currentUserId,
values: {field1: 'valueChanged', field3: 'new field'},
@@ -1378,7 +1380,7 @@ describe('handleCustomAttributeValuesUpdated', () => {
expect(stateUser(testStore.getState(), currentUserId)).toEqual({id: currentUserId});
testStore.dispatch(handleCustomAttributeValuesUpdated({
event: SocketEvents.CPA_VALUES_UPDATED,
event: WebSocketEvents.CPAValuesUpdated,
data: {
user_id: 'nonExistantUser',
values: {field1: 'value1', field2: 'value2'},
@@ -1408,7 +1410,7 @@ describe('handleCustomAttributeCRUD', () => {
const testStore = realConfigureStore(makeInitialState());
testStore.dispatch(handleCustomAttributesCreated({
event: SocketEvents.CPA_FIELD_CREATED,
event: WebSocketEvents.CPAFieldCreated,
data: {
field: field1,
},
@@ -1422,7 +1424,7 @@ describe('handleCustomAttributeCRUD', () => {
// create second field
testStore.dispatch(handleCustomAttributesCreated({
event: SocketEvents.CPA_FIELD_CREATED,
event: WebSocketEvents.CPAFieldCreated,
data: {
field: field2,
},
@@ -1436,7 +1438,7 @@ describe('handleCustomAttributeCRUD', () => {
// update field
testStore.dispatch(handleCustomAttributesUpdated({
event: SocketEvents.CPA_FIELD_UPDATED,
event: WebSocketEvents.CPAFieldUpdated,
data: {
field: {...field1, name: 'Updated Name'},
},
@@ -1450,7 +1452,7 @@ describe('handleCustomAttributeCRUD', () => {
// delete field
testStore.dispatch(handleCustomAttributesDeleted({
event: SocketEvents.CPA_FIELD_DELETED,
event: WebSocketEvents.CPAFieldDeleted,
data: {
field_id: field1.id,
},
@@ -1468,7 +1470,7 @@ describe('handleCustomAttributeCRUD', () => {
// First create a field
testStore.dispatch(handleCustomAttributesCreated({
event: SocketEvents.CPA_FIELD_CREATED,
event: WebSocketEvents.CPAFieldCreated,
data: {
field: field1,
},
@@ -1482,7 +1484,7 @@ describe('handleCustomAttributeCRUD', () => {
// Update the field
const updatedField = {...field1, name: 'Updated Field Name'};
testStore.dispatch(handleCustomAttributesUpdated({
event: SocketEvents.CPA_FIELD_UPDATED,
event: WebSocketEvents.CPAFieldUpdated,
data: {
field: updatedField,
},
@@ -1514,7 +1516,7 @@ describe('handleCustomAttributeCRUD', () => {
// First create a field
testStore.dispatch(handleCustomAttributesCreated({
event: SocketEvents.CPA_FIELD_CREATED,
event: WebSocketEvents.CPAFieldCreated,
data: {
field: field1,
},
@@ -1523,7 +1525,7 @@ describe('handleCustomAttributeCRUD', () => {
// Update the field with delete_values flag
const updatedField = {...field1, type: 'select'};
testStore.dispatch(handleCustomAttributesUpdated({
event: SocketEvents.CPA_FIELD_UPDATED,
event: WebSocketEvents.CPAFieldUpdated,
data: {
field: updatedField,
delete_values: true,
@@ -1561,7 +1563,7 @@ describe('handleCustomAttributeCRUD', () => {
// First create a field
testStore.dispatch(handleCustomAttributesCreated({
event: SocketEvents.CPA_FIELD_CREATED,
event: WebSocketEvents.CPAFieldCreated,
data: {
field: field1,
},
@@ -1570,7 +1572,7 @@ describe('handleCustomAttributeCRUD', () => {
// Update the field but with delete_values flag set to false
const updatedField = {...field1, name: 'Updated Field Name', type: 'text'};
testStore.dispatch(handleCustomAttributesUpdated({
event: SocketEvents.CPA_FIELD_UPDATED,
event: WebSocketEvents.CPAFieldUpdated,
data: {
field: updatedField,
delete_values: false,
@@ -1609,7 +1611,7 @@ describe('handleCustomAttributeCRUD', () => {
// First create a field
testStore.dispatch(handleCustomAttributesCreated({
event: SocketEvents.CPA_FIELD_CREATED,
event: WebSocketEvents.CPAFieldCreated,
data: {
field: field1,
},
@@ -1618,7 +1620,7 @@ describe('handleCustomAttributeCRUD', () => {
// Update the field without specifying delete_values
const updatedField = {...field1, type: 'number'};
testStore.dispatch(handleCustomAttributesUpdated({
event: SocketEvents.CPA_FIELD_UPDATED,
event: WebSocketEvents.CPAFieldUpdated,
data: {
field: updatedField,
@@ -5,11 +5,11 @@ import {useCallback, useEffect, useRef} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import type {WebSocketMessage} from '@mattermost/client';
import {WebSocketEvents} from '@mattermost/client';
import {getAgents as getAgentsAction} from 'mattermost-redux/actions/agents';
import {getAgents} from 'mattermost-redux/selectors/entities/agents';
import {SocketEvents} from 'utils/constants';
import {useWebSocket} from 'utils/use_websocket/hooks';
const AI_PLUGIN_ID = 'mattermost-ai';
@@ -63,10 +63,10 @@ export default function useGetAgentsBridgeEnabled(): boolean {
// Note: When a plugin is enabled/disabled, the backend fires CONFIG_CHANGED first,
// then PLUGIN_ENABLED/PLUGIN_DISABLED. We debounce to avoid duplicate fetches.
const isPluginEvent =
(msg.event === SocketEvents.PLUGIN_ENABLED || msg.event === SocketEvents.PLUGIN_DISABLED) &&
(msg.event === WebSocketEvents.PluginEnabled || msg.event === WebSocketEvents.PluginDisabled) &&
msg.data?.manifest?.id === AI_PLUGIN_ID;
const isConfigChange = msg.event === SocketEvents.CONFIG_CHANGED;
const isConfigChange = msg.event === WebSocketEvents.ConfigChanged;
if (isPluginEvent || isConfigChange) {
debouncedRefetch();
@@ -14,7 +14,7 @@ import type {Props} from 'components/logged_in/logged_in';
import {fireEvent, renderWithContext, screen} from 'tests/react_testing_utils';
jest.mock('actions/websocket_actions.jsx', () => ({
jest.mock('actions/websocket_actions', () => ({
initialize: jest.fn(),
close: jest.fn(),
}));
@@ -7,7 +7,7 @@ import {Redirect} from 'react-router-dom';
import type {UserProfile} from '@mattermost/types/users';
import * as GlobalActions from 'actions/global_actions';
import * as WebSocketActions from 'actions/websocket_actions.jsx';
import * as WebSocketActions from 'actions/websocket_actions';
import BrowserStore from 'stores/browser_store';
import LoadingScreen from 'components/loading_screen';
@@ -1,8 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {WebSocketTypes} from 'mattermost-redux/action_types';
import {getMissingProfilesByIds, getStatusesByIds} from 'mattermost-redux/actions/users';
import {General, WebsocketEvents} from 'mattermost-redux/constants';
import {General} from 'mattermost-redux/constants';
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
import configureStore from 'tests/test_store';
@@ -39,8 +40,8 @@ describe('handleUserTypingEvent', () => {
store.dispatch(userStartedTyping(userId, channelId, rootId, Date.now()));
expect(store.getActions().find((action) => action.type === WebsocketEvents.TYPING)).toMatchObject({
type: WebsocketEvents.TYPING,
expect(store.getActions().find((action) => action.type === WebSocketTypes.TYPING)).toMatchObject({
type: WebSocketTypes.TYPING,
data: {
id: channelId + rootId,
userId,
@@ -3,8 +3,9 @@
import type {GlobalState} from '@mattermost/types/store';
import {WebSocketTypes} from 'mattermost-redux/action_types';
import {getMissingProfilesByIds, getStatusesByIds} from 'mattermost-redux/actions/users';
import {General, Preferences, WebsocketEvents} from 'mattermost-redux/constants';
import {General, Preferences} from 'mattermost-redux/constants';
import {getIsUserStatusesConfigEnabled} from 'mattermost-redux/selectors/entities/common';
import {getConfig, isPerformanceDebuggingEnabled} from 'mattermost-redux/selectors/entities/general';
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
@@ -30,7 +31,7 @@ export function userStartedTyping(userId: string, channelId: string, rootId: str
}
dispatch({
type: WebsocketEvents.TYPING,
type: WebSocketTypes.TYPING,
data: {
id: channelId + rootId,
userId,
@@ -72,7 +73,7 @@ function fillInMissingInfo(userId: string): ActionFuncAsync {
export function userStoppedTyping(userId: string, channelId: string, rootId: string, now: number) {
return {
type: WebsocketEvents.STOP_TYPING,
type: WebSocketTypes.STOPPED_TYPING,
data: {
id: channelId + rootId,
userId,
@@ -4,9 +4,8 @@
import React, {useCallback} from 'react';
import {FormattedMessage} from 'react-intl';
import type {WebSocketMessage} from '@mattermost/client';
import {WebSocketEvents, type WebSocketMessage} from '@mattermost/client';
import {SocketEvents} from 'utils/constants';
import {useWebSocket} from 'utils/use_websocket';
type Props = {
@@ -21,7 +20,7 @@ export default function MsgTyping(props: Props) {
const {userStartedTyping, userStoppedTyping} = props;
useWebSocket({
handler: useCallback((msg: WebSocketMessage) => {
if (msg.event === SocketEvents.TYPING) {
if (msg.event === WebSocketEvents.Typing) {
const channelId = msg.broadcast.channel_id;
const rootId = msg.data.parent_id;
const userId = msg.data.user_id;
@@ -29,7 +28,7 @@ export default function MsgTyping(props: Props) {
if (props.channelId === channelId && props.rootId === rootId) {
userStartedTyping(userId, channelId, rootId, Date.now());
}
} else if (msg.event === SocketEvents.POSTED) {
} else if (msg.event === WebSocketEvents.Posted) {
const post = JSON.parse(msg.data.post);
const channelId = post.channel_id;
@@ -15,7 +15,7 @@ import {
} from 'mattermost-redux/selectors/entities/content_flagging';
import type {ActionResult} from 'mattermost-redux/types/actions';
import {reconnect} from 'actions/websocket_actions.jsx';
import {reconnect} from 'actions/websocket_actions';
import LocalStorageStore from 'stores/local_storage_store';
import {makeAsyncComponent, makeAsyncPluggableComponent} from 'components/async_load';
@@ -35,6 +35,7 @@ import SharedChannelTypes from './shared_channels';
import TeamTypes from './teams';
import ThreadTypes from './threads';
import UserTypes from './users';
import WebSocketTypes from './websocket';
export {
ErrorTypes,
@@ -69,6 +70,7 @@ export {
SharedChannelTypes,
ContentFlaggingTypes,
AgentTypes,
WebSocketTypes,
};
/**
@@ -22,7 +22,6 @@ export default keyMirror({
RECEIVED_ROLES: null,
RECEIVED_ROLE: null,
ROLE_DELETED: null,
SET_PENDING_ROLES: null,
});
@@ -0,0 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import keyMirror from 'mattermost-redux/utils/key_mirror';
export default keyMirror({
TYPING: null,
STOPPED_TYPING: null,
});
@@ -31,7 +31,7 @@ export function createSchedulePost(schedulePost: ScheduledPost, teamId: string,
};
}
export function fetchTeamScheduledPosts(teamId: string, includeDirectChannels: boolean, prune?: false) {
export function fetchTeamScheduledPosts(teamId: string, includeDirectChannels: boolean, prune?: boolean) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let scheduledPosts;
@@ -14,6 +14,5 @@ import Stats from './stats';
import Teams from './teams';
import Threads from './threads';
import Users from './users';
import WebsocketEvents from './websocket';
export {General, Preferences, Posts, Files, RequestStatus, WebsocketEvents, Teams, Stats, Permissions, Emoji, Plugins, Users, Roles, Threads};
export {General, Preferences, Posts, Files, RequestStatus, Teams, Stats, Permissions, Emoji, Plugins, Users, Roles, Threads};
@@ -1,59 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const WebsocketEvents = {
POSTED: 'posted',
POST_EDITED: 'post_edited',
POST_DELETED: 'post_deleted',
POST_UNREAD: 'post_unread',
CHANNEL_CONVERTED: 'channel_converted',
CHANNEL_CREATED: 'channel_created',
CHANNEL_DELETED: 'channel_deleted',
CHANNEL_UNARCHIVED: 'channel_restored',
CHANNEL_UPDATED: 'channel_updated',
MULTIPLE_CHANNELS_VIEWED: 'multiple_channels_viewed',
CHANNEL_MEMBER_UPDATED: 'channel_member_updated',
CHANNEL_SCHEME_UPDATED: 'channel_scheme_updated',
DIRECT_ADDED: 'direct_added',
ADDED_TO_TEAM: 'added_to_team',
LEAVE_TEAM: 'leave_team',
UPDATE_TEAM: 'update_team',
USER_ADDED: 'user_added',
USER_REMOVED: 'user_removed',
USER_UPDATED: 'user_updated',
USER_ROLE_UPDATED: 'user_role_updated',
ROLE_ADDED: 'role_added',
ROLE_REMOVED: 'role_removed',
ROLE_UPDATED: 'role_updated',
TYPING: 'typing',
STOP_TYPING: 'stop_typing',
PREFERENCE_CHANGED: 'preference_changed',
PREFERENCES_CHANGED: 'preferences_changed',
PREFERENCES_DELETED: 'preferences_deleted',
EPHEMERAL_MESSAGE: 'ephemeral_message',
STATUS_CHANGED: 'status_change',
HELLO: 'hello',
WEBRTC: 'webrtc',
REACTION_ADDED: 'reaction_added',
REACTION_REMOVED: 'reaction_removed',
EMOJI_ADDED: 'emoji_added',
LICENSE_CHANGED: 'license_changed',
CONFIG_CHANGED: 'config_changed',
PLUGIN_STATUSES_CHANGED: 'plugin_statuses_changed',
OPEN_DIALOG: 'open_dialog',
INCREASE_POST_VISIBILITY_BY_ONE: 'increase_post_visibility_by_one',
RECEIVED_GROUP: 'received_group',
RECEIVED_GROUP_ASSOCIATED_TO_TEAM: 'group_associated_to_team',
RECEIVED_GROUP_NOT_ASSOCIATED_TO_TEAM: 'group_not_associated_to_team',
RECEIVED_GROUP_ASSOCIATED_TO_CHANNEL: 'group_associated_to_channel',
RECEIVED_GROUP_NOT_ASSOCIATED_TO_CHANNEL: 'group_not_associated_to_channel',
THREAD_UPDATED: 'thread_updated',
THREAD_FOLLOW_CHANGED: 'thread_follow_changed',
THREAD_READ_CHANGED: 'thread_read_changed',
FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED: 'first_admin_visit_marketplace_status_received',
GROUP_MEMBER_DELETED: 'group_member_deleted',
BURN_ON_READ_POST_REVEALED: 'post_revealed',
BURN_ON_READ_POST_BURNED: 'post_burned',
BURN_ON_READ_ALL_REVEALED: 'burn_on_read_all_revealed',
};
export default WebsocketEvents;
@@ -32,15 +32,6 @@ function roles(state: Record<string, Role> = {}, action: MMReduxAction) {
return state;
}
case RoleTypes.ROLE_DELETED: {
if (action.data) {
const nextState = {...state};
Reflect.deleteProperty(nextState, action.data.name);
return nextState;
}
return state;
}
case RoleTypes.RECEIVED_ROLE: {
if (action.data) {
const nextState = {...state};
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {WebsocketEvents} from 'mattermost-redux/constants';
import {WebSocketTypes} from 'mattermost-redux/action_types';
import typingReducer from 'mattermost-redux/reducers/entities/typing';
import TestHelper from '../../../test/test_helper';
@@ -17,7 +17,7 @@ describe('Reducers.Typing', () => {
expect(state).toEqual({});
});
it('WebsocketEvents.TYPING', async () => {
it('WebSocketTypes.TYPING', async () => {
let state = {};
const id1 = TestHelper.generateId();
@@ -27,7 +27,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.TYPING,
type: WebSocketTypes.TYPING,
data: {
id: id1,
userId: userId1,
@@ -49,7 +49,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.TYPING,
type: WebSocketTypes.TYPING,
data: {
id: id2,
userId: userId1,
@@ -74,7 +74,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.TYPING,
type: WebSocketTypes.TYPING,
data: {
id: id1,
userId: userId2,
@@ -99,7 +99,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.TYPING,
type: WebSocketTypes.TYPING,
data: {
id: id2,
userId: userId2,
@@ -121,7 +121,7 @@ describe('Reducers.Typing', () => {
});
});
it('WebsocketEvents.STOP_TYPING', async () => {
it('WebSocketTypes.STOPPED_TYPING', async () => {
const id1 = TestHelper.generateId();
const id2 = TestHelper.generateId();
@@ -147,7 +147,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.STOP_TYPING,
type: WebSocketTypes.STOPPED_TYPING,
data: {
id: id1,
userId: userId1,
@@ -170,7 +170,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.STOP_TYPING,
type: WebSocketTypes.STOPPED_TYPING,
data: {
id: id2,
userId: userId1,
@@ -192,7 +192,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.STOP_TYPING,
type: WebSocketTypes.STOPPED_TYPING,
data: {
id: id1,
userId: userId2,
@@ -212,7 +212,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.STOP_TYPING,
type: WebSocketTypes.STOPPED_TYPING,
data: {
id: id2,
userId: userId2,
@@ -232,7 +232,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.STOP_TYPING,
type: WebSocketTypes.STOPPED_TYPING,
data: {
id: id1,
userId: userId1,
@@ -251,7 +251,7 @@ describe('Reducers.Typing', () => {
state = typingReducer(
state,
{
type: WebsocketEvents.STOP_TYPING,
type: WebSocketTypes.STOPPED_TYPING,
data: {
id: id1,
userId: userId1,
@@ -5,7 +5,7 @@ import type {AnyAction} from 'redux';
import type {Typing} from '@mattermost/types/typing';
import {WebsocketEvents} from 'mattermost-redux/constants';
import {WebSocketTypes} from 'mattermost-redux/action_types';
export default function typing(state: Typing = {}, action: AnyAction): Typing {
const {
@@ -14,7 +14,7 @@ export default function typing(state: Typing = {}, action: AnyAction): Typing {
} = action;
switch (type) {
case WebsocketEvents.TYPING: {
case WebSocketTypes.TYPING: {
const {
id,
userId,
@@ -33,7 +33,7 @@ export default function typing(state: Typing = {}, action: AnyAction): Typing {
return state;
}
case WebsocketEvents.STOP_TYPING: {
case WebSocketTypes.STOPPED_TYPING: {
const {
id,
userId,
+15 -4
View File
@@ -5,6 +5,8 @@ import React from 'react';
import {isValidElementType} from 'react-is';
import type {Reducer} from 'redux';
import type {WebSocketMessages} from '@mattermost/client';
import reducerRegistry from 'mattermost-redux/store/reducer_registry';
import {
@@ -24,7 +26,7 @@ import {
unregisterPluginWebSocketEvent,
registerPluginReconnectHandler,
unregisterPluginReconnectHandler,
} from 'actions/websocket_actions.jsx';
} from 'actions/websocket_actions';
import store from 'stores/redux_store';
import {ActionTypes} from 'utils/constants';
@@ -818,7 +820,16 @@ export default class PluginRegistry {
* - handler - a function to handle the event, receives the event message as an argument
* Returns undefined.
*/
registerWebSocketEventHandler = reArg(['event', 'handler'], ({event, handler}) => {
registerWebSocketEventHandler = reArg([
'event',
'handler',
], ({
event,
handler,
}: {
event: string;
handler: (msg: WebSocketMessages.Unknown) => void;
}) => {
registerPluginWebSocketEvent(this.id, event, handler);
});
@@ -827,7 +838,7 @@ export default class PluginRegistry {
* Accepts a string event type.
* Returns undefined.
*/
unregisterWebSocketEventHandler = reArg(['event'], ({event}) => {
unregisterWebSocketEventHandler = reArg(['event'], ({event}: { event: string }) => {
unregisterPluginWebSocketEvent(this.id, event);
});
@@ -836,7 +847,7 @@ export default class PluginRegistry {
* internet after previously disconnecting.
* Accepts a function to handle the event. Returns undefined.
*/
registerReconnectHandler = reArg(['handler'], ({handler}) => {
registerReconnectHandler = reArg(['handler'], ({handler}: {handler: () => void}) => {
registerPluginReconnectHandler(this.id, handler);
});
+3 -87
View File
@@ -612,97 +612,13 @@ export const AppEvents = {
FOCUS_EDIT_TEXTBOX: 'focus_edit_textbox',
};
/**
* @deprecated Use WebSocketEvents from @mattermost/client instead.
*/
export const SocketEvents = {
POSTED: 'posted',
POST_EDITED: 'post_edited',
POST_DELETED: 'post_deleted',
POST_UPDATED: 'post_updated',
POST_UNREAD: 'post_unread',
BURN_ON_READ_POST_REVEALED: 'post_revealed',
BURN_ON_READ_POST_BURNED: 'post_burned',
BURN_ON_READ_ALL_REVEALED: 'burn_on_read_all_revealed',
CHANNEL_CONVERTED: 'channel_converted',
CHANNEL_CREATED: 'channel_created',
CHANNEL_DELETED: 'channel_deleted',
CHANNEL_UNARCHIVED: 'channel_restored',
CHANNEL_UPDATED: 'channel_updated',
CHANNEL_BOOKMARK_CREATED: 'channel_bookmark_created',
CHANNEL_BOOKMARK_DELETED: 'channel_bookmark_deleted',
CHANNEL_BOOKMARK_UPDATED: 'channel_bookmark_updated',
CHANNEL_BOOKMARK_SORTED: 'channel_bookmark_sorted',
MULTIPLE_CHANNELS_VIEWED: 'multiple_channels_viewed',
CHANNEL_MEMBER_UPDATED: 'channel_member_updated',
CHANNEL_SCHEME_UPDATED: 'channel_scheme_updated',
DIRECT_ADDED: 'direct_added',
GROUP_ADDED: 'group_added',
NEW_USER: 'new_user',
ADDED_TO_TEAM: 'added_to_team',
JOIN_TEAM: 'join_team',
LEAVE_TEAM: 'leave_team',
UPDATE_TEAM: 'update_team',
DELETE_TEAM: 'delete_team',
UPDATE_TEAM_SCHEME: 'update_team_scheme',
USER_ADDED: 'user_added',
USER_REMOVED: 'user_removed',
USER_UPDATED: 'user_updated',
USER_ROLE_UPDATED: 'user_role_updated',
MEMBERROLE_UPDATED: 'memberrole_updated',
ROLE_ADDED: 'role_added',
ROLE_REMOVED: 'role_removed',
ROLE_UPDATED: 'role_updated',
TYPING: 'typing',
PREFERENCE_CHANGED: 'preference_changed',
PREFERENCES_CHANGED: 'preferences_changed',
PREFERENCES_DELETED: 'preferences_deleted',
EPHEMERAL_MESSAGE: 'ephemeral_message',
STATUS_CHANGED: 'status_change',
HELLO: 'hello',
REACTION_ADDED: 'reaction_added',
REACTION_REMOVED: 'reaction_removed',
EMOJI_ADDED: 'emoji_added',
PLUGIN_ENABLED: 'plugin_enabled',
PLUGIN_DISABLED: 'plugin_disabled',
LICENSE_CHANGED: 'license_changed',
CONFIG_CHANGED: 'config_changed',
PLUGIN_STATUSES_CHANGED: 'plugin_statuses_changed',
OPEN_DIALOG: 'open_dialog',
RECEIVED_GROUP: 'received_group',
GROUP_MEMBER_ADD: 'group_member_add',
GROUP_MEMBER_DELETED: 'group_member_deleted',
RECEIVED_GROUP_ASSOCIATED_TO_TEAM: 'received_group_associated_to_team',
RECEIVED_GROUP_NOT_ASSOCIATED_TO_TEAM: 'received_group_not_associated_to_team',
RECEIVED_GROUP_ASSOCIATED_TO_CHANNEL: 'received_group_associated_to_channel',
RECEIVED_GROUP_NOT_ASSOCIATED_TO_CHANNEL: 'received_group_not_associated_to_channel',
SIDEBAR_CATEGORY_CREATED: 'sidebar_category_created',
SIDEBAR_CATEGORY_UPDATED: 'sidebar_category_updated',
SIDEBAR_CATEGORY_DELETED: 'sidebar_category_deleted',
SIDEBAR_CATEGORY_ORDER_UPDATED: 'sidebar_category_order_updated',
USER_ACTIVATION_STATUS_CHANGED: 'user_activation_status_change',
CLOUD_PAYMENT_STATUS_UPDATED: 'cloud_payment_status_updated',
CLOUD_SUBSCRIPTION_CHANGED: 'cloud_subscription_changed',
APPS_FRAMEWORK_REFRESH_BINDINGS: 'custom_com.mattermost.apps_refresh_bindings',
APPS_FRAMEWORK_PLUGIN_ENABLED: 'custom_com.mattermost.apps_plugin_enabled',
APPS_FRAMEWORK_PLUGIN_DISABLED: 'custom_com.mattermost.apps_plugin_disabled',
FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED: 'first_admin_visit_marketplace_status_received',
THREAD_UPDATED: 'thread_updated',
THREAD_FOLLOW_CHANGED: 'thread_follow_changed',
THREAD_READ_CHANGED: 'thread_read_changed',
POST_ACKNOWLEDGEMENT_ADDED: 'post_acknowledgement_added',
POST_ACKNOWLEDGEMENT_REMOVED: 'post_acknowledgement_removed',
DRAFT_CREATED: 'draft_created',
DRAFT_UPDATED: 'draft_updated',
DRAFT_DELETED: 'draft_deleted',
SCHEDULED_POST_CREATED: 'scheduled_post_created',
SCHEDULED_POST_UPDATED: 'scheduled_post_updated',
SCHEDULED_POST_DELETED: 'scheduled_post_deleted',
PERSISTENT_NOTIFICATION_TRIGGERED: 'persistent_notification_triggered',
HOSTED_CUSTOMER_SIGNUP_PROGRESS_UPDATED: 'hosted_customer_signup_progress_updated',
CPA_FIELD_CREATED: 'custom_profile_attributes_field_created',
CPA_FIELD_UPDATED: 'custom_profile_attributes_field_updated',
CPA_FIELD_DELETED: 'custom_profile_attributes_field_deleted',
CPA_VALUES_UPDATED: 'custom_profile_attributes_values_updated',
CONTENT_FLAGGING_REPORT_VALUE_CHANGED: 'content_flagging_report_value_updated',
RECAP_UPDATED: 'recap_updated',
};
export const TutorialSteps = {
+2 -2
View File
@@ -10,7 +10,7 @@ import type {AppBinding, AppCallRequest, AppCallResponse} from '@mattermost/type
import type {Audit} from '@mattermost/types/audits';
import type {UserAutocomplete, AutocompleteSuggestion} from '@mattermost/types/autocomplete';
import type {Bot, BotPatch} from '@mattermost/types/bots';
import type {ChannelBookmark, ChannelBookmarkCreate, ChannelBookmarkPatch} from '@mattermost/types/channel_bookmarks';
import type {ChannelBookmark, ChannelBookmarkCreate, ChannelBookmarkPatch, UpdateChannelBookmarkResponse} from '@mattermost/types/channel_bookmarks';
import type {ChannelCategory, OrderedChannelCategories} from '@mattermost/types/channel_categories';
import type {
Channel,
@@ -2040,7 +2040,7 @@ export default class Client4 {
};
updateChannelBookmark = (channelId: string, channelBookmarkId: string, patch: ChannelBookmarkPatch, connectionId: string) => {
return this.doFetch<{updated: ChannelBookmark; deleted: ChannelBookmark}>(
return this.doFetch<UpdateChannelBookmarkResponse>(
`${this.getChannelBookmarkRoute(channelId, channelBookmarkId)}`,
{method: 'PATCH', body: JSON.stringify(patch), headers: {'Connection-Id': connectionId}},
);
+5 -1
View File
@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type * as WebSocketMessages from './websocket_messages';
export {
default as Client4,
ClientError,
@@ -8,5 +10,7 @@ export {
DEFAULT_LIMIT_BEFORE,
} from './client4';
export type {WebSocketMessage} from './websocket';
export {default as WebSocketClient} from './websocket';
export {WebSocketEvents} from './websocket_events';
export type {BaseWebSocketMessage, JsonEncodedValue, WebSocketBroadcast, WebSocketMessage} from './websocket_message';
export {WebSocketMessages};
+3 -16
View File
@@ -1,7 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const WEBSOCKET_HELLO = 'hello';
import {WebSocketEvents} from './websocket_events';
import type {WebSocketMessage} from './websocket_message';
export type MessageListener = (msg: WebSocketMessage) => void;
export type FirstConnectListener = () => void;
@@ -362,7 +363,7 @@ export default class WebSocketClient {
}
} else if (this.eventCallback || this.messageListeners.size > 0) {
// We check the hello packet, which is always the first packet in a stream.
if (msg.event === WEBSOCKET_HELLO && (this.missedEventCallback || this.missedMessageListeners.size > 0)) {
if (msg.event === WebSocketEvents.Hello && (this.missedEventCallback || this.missedMessageListeners.size > 0)) {
console.log('got connection id ', msg.data.connection_id); //eslint-disable-line no-console
// If we already have a connectionId present, and server sends a different one,
// that means it's either a long timeout, or server restart, or sequence number is not found.
@@ -675,17 +676,3 @@ export default class WebSocketClient {
this.sendMessage('get_statuses_by_ids', data, callback);
}
}
export type WebSocketBroadcast = {
omit_users: Record<string, boolean>;
user_id: string;
channel_id: string;
team_id: string;
}
export type WebSocketMessage<T = any> = {
event: string;
data: T;
broadcast: WebSocketBroadcast;
seq: number;
}
@@ -0,0 +1,93 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const enum WebSocketEvents {
Typing = 'typing',
Posted = 'posted',
PostEdited = 'post_edited',
PostDeleted = 'post_deleted',
PostUnread = 'post_unread',
BurnOnReadPostRevealed = 'post_revealed',
BurnOnReadPostBurned = 'post_burned',
BurnOnReadPostAllRevealed = 'burn_on_read_all_revealed',
ChannelConverted = 'channel_converted',
ChannelCreated = 'channel_created',
ChannelDeleted = 'channel_deleted',
ChannelRestored = 'channel_restored',
ChannelUpdated = 'channel_updated',
ChannelMemberUpdated = 'channel_member_updated',
ChannelSchemeUpdated = 'channel_scheme_updated',
DirectAdded = 'direct_added',
GroupAdded = 'group_added',
NewUser = 'new_user',
AddedToTeam = 'added_to_team',
LeaveTeam = 'leave_team',
UpdateTeam = 'update_team',
DeleteTeam = 'delete_team',
RestoreTeam = 'restore_team', // This isn't currently used by the web app
UpdateTeamScheme = 'update_team_scheme',
UserAdded = 'user_added',
UserUpdated = 'user_updated',
UserRoleUpdated = 'user_role_updated',
MemberRoleUpdated = 'memberrole_updated',
UserRemoved = 'user_removed',
PreferenceChanged = 'preference_changed',
PreferencesChanged = 'preferences_changed',
PreferencesDeleted = 'preferences_deleted',
EphemeralMessage = 'ephemeral_message',
StatusChange = 'status_change',
Hello = 'hello',
AuthenticationChallenge = 'authentication_challenge', // This isn't currently used by the web app, and it's a message that would be sent from a client to the server
ReactionAdded = 'reaction_added',
ReactionRemoved = 'reaction_removed',
Response = 'response',
EmojiAdded = 'emoji_added',
MultipleChannelsViewed = 'multiple_channels_viewed',
PluginStatusesChanged = 'plugin_statuses_changed',
PluginEnabled = 'plugin_enabled',
PluginDisabled = 'plugin_disabled',
RoleUpdated = 'role_updated',
LicenseChanged = 'license_changed',
ConfigChanged = 'config_changed',
OpenDialog = 'open_dialog',
GuestsDeactivated = 'guests_deactivated', // This isn't currently used by the web app
UserActivationStatusChange = 'user_activation_status_change',
ReceivedGroup = 'received_group',
ReceivedGroupAssociatedToTeam = 'received_group_associated_to_team',
ReceivedGroupNotAssociatedToTeam = 'received_group_not_associated_to_team',
ReceivedGroupAssociatedToChannel = 'received_group_associated_to_channel',
ReceivedGroupNotAssociatedToChannel = 'received_group_not_associated_to_channel',
GroupMemberDeleted = 'group_member_deleted',
GroupMemberAdded = 'group_member_add',
SidebarCategoryCreated = 'sidebar_category_created',
SidebarCategoryUpdated = 'sidebar_category_updated',
SidebarCategoryDeleted = 'sidebar_category_deleted',
SidebarCategoryOrderUpdated = 'sidebar_category_order_updated',
CloudSubscriptionChanged = 'cloud_subscription_changed',
ThreadUpdated = 'thread_updated',
ThreadFollowChanged = 'thread_follow_changed',
ThreadReadChanged = 'thread_read_changed',
FirstAdminVisitMarketplaceStatusReceived = 'first_admin_visit_marketplace_status_received',
DraftCreated = 'draft_created',
DraftUpdated = 'draft_updated',
DraftDeleted = 'draft_deleted',
PostAcknowledgementAdded = 'post_acknowledgement_added',
PostAcknowledgementRemoved = 'post_acknowledgement_removed',
PersistentNotificationTriggered = 'persistent_notification_triggered',
HostedCustomerSignupProgressUpdated = 'hosted_customer_signup_progress_updated',
ChannelBookmarkCreated = 'channel_bookmark_created',
ChannelBookmarkUpdated = 'channel_bookmark_updated',
ChannelBookmarkDeleted = 'channel_bookmark_deleted',
ChannelBookmarkSorted = 'channel_bookmark_sorted',
PresenceIndicator = 'presence',
PostedNotifyAck = 'posted_notify_ack', // This isn't currently used by the web app
ScheduledPostCreated = 'scheduled_post_created',
ScheduledPostUpdated = 'scheduled_post_updated',
ScheduledPostDeleted = 'scheduled_post_deleted',
CPAFieldCreated = 'custom_profile_attributes_field_created',
CPAFieldUpdated = 'custom_profile_attributes_field_updated',
CPAFieldDeleted = 'custom_profile_attributes_field_deleted',
CPAValuesUpdated = 'custom_profile_attributes_values_updated',
ContentFlaggingReportValueUpdated = 'content_flagging_report_value_updated',
RecapUpdated = 'recap_updated',
}
@@ -0,0 +1,117 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {WebSocketEvents} from './websocket_events';
import type * as Messages from './websocket_messages';
export type WebSocketMessage = (
Messages.Hello |
Messages.AuthenticationChallenge |
Messages.Response |
Messages.Posted |
Messages.PostEdited |
Messages.PostDeleted |
Messages.PostUnread |
Messages.BurnOnReadPostRevealed |
Messages.BurnOnReadPostBurned |
Messages.BurnOnReadPostAllRevealed |
Messages.EphemeralPost |
Messages.PostReaction |
Messages.PostAcknowledgement |
Messages.PostDraft |
Messages.PersistentNotificationTriggered |
Messages.ScheduledPost |
Messages.ThreadUpdated |
Messages.ThreadFollowedChanged |
Messages.ThreadReadChanged |
Messages.ChannelCreated |
Messages.ChannelUpdated |
Messages.ChannelConverted |
Messages.ChannelSchemeUpdated |
Messages.ChannelDeleted |
Messages.ChannelRestored |
Messages.DirectChannelCreated |
Messages.GroupChannelCreated |
Messages.UserAddedToChannel |
Messages.UserRemovedFromChannel |
Messages.ChannelMemberUpdated |
Messages.MultipleChannelsViewed |
Messages.ChannelBookmarkCreated |
Messages.ChannelBookmarkUpdated |
Messages.ChannelBookmarkDeleted |
Messages.ChannelBookmarkSorted |
Messages.Team |
Messages.UpdateTeamScheme |
Messages.UserAddedToTeam |
Messages.UserRemovedFromTeam |
Messages.TeamMemberRoleUpdated |
Messages.NewUser |
Messages.UserUpdated |
Messages.UserActivationStatusChanged |
Messages.UserRoleUpdated |
Messages.StatusChanged |
Messages.Typing |
Messages.ReceivedGroup |
Messages.GroupAssociatedToTeam |
Messages.GroupAssociatedToChannel |
Messages.GroupMember |
Messages.PreferenceChanged |
Messages.PreferencesChanged |
Messages.SidebarCategoryCreated |
Messages.SidebarCategoryUpdated |
Messages.SidebarCategoryDeleted |
Messages.SidebarCategoryOrderUpdated |
Messages.EmojiAdded |
Messages.RoleUpdated |
Messages.ConfigChanged |
Messages.GuestsDeactivated |
Messages.LicenseChanged |
Messages.CloudSubscriptionChanged |
Messages.FirstAdminVisitMarketplaceStatusReceived |
Messages.HostedCustomerSignupProgressUpdated |
Messages.CPAFieldCreated |
Messages.CPAFieldUpdated |
Messages.CPAFieldDeleted |
Messages.CPAValuesUpdated |
Messages.ContentFlaggingReportValueUpdated |
Messages.RecapUpdated |
Messages.Plugin |
Messages.PluginStatusesChanged |
Messages.OpenDialog |
BaseWebSocketMessage<WebSocketEvents.PresenceIndicator, unknown> |
BaseWebSocketMessage<WebSocketEvents.PostedNotifyAck, unknown>
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type JsonEncodedValue<T> = string;
export type BaseWebSocketMessage<Event, T = Record<string, never>> = {
event: Event;
data: T;
broadcast: WebSocketBroadcast;
seq: number;
}
export type WebSocketBroadcast = {
omit_users: Record<string, boolean>;
user_id: string;
channel_id: string;
team_id: string;
}
@@ -0,0 +1,441 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ChannelBookmarkWithFileInfo, UpdateChannelBookmarkResponse} from '@mattermost/types/channel_bookmarks';
import type {ChannelCategory} from '@mattermost/types/channel_categories';
import type {Channel, ChannelMembership, ChannelType} from '@mattermost/types/channels';
import type {Limits, Subscription} from '@mattermost/types/cloud';
import type {ClientConfig, ClientLicense} from '@mattermost/types/config';
import type {Draft} from '@mattermost/types/drafts';
import type {CustomEmoji} from '@mattermost/types/emojis';
import type {Group, GroupMember as GroupMemberType} from '@mattermost/types/groups';
import type {OpenDialogRequest} from '@mattermost/types/integrations';
import type {PluginManifest, PluginStatus} from '@mattermost/types/plugins';
import type {Post, PostAcknowledgement as PostAcknowledgementType} from '@mattermost/types/posts';
import type {PreferenceType} from '@mattermost/types/preferences';
import type {PropertyField, PropertyValue} from '@mattermost/types/properties';
import type {Reaction} from '@mattermost/types/reactions';
import type {Role} from '@mattermost/types/roles';
import type {ScheduledPost as ScheduledPostType} from '@mattermost/types/schedule_post';
import type {Team as TeamType, TeamMembership} from '@mattermost/types/teams';
import type {UserThread} from '@mattermost/types/threads';
import type {UserProfile, UserStatus} from '@mattermost/types/users';
import type {WebSocketEvents} from './websocket_events';
import type {BaseWebSocketMessage, JsonEncodedValue} from './websocket_message';
// WebSocket-related messages
export type Hello = BaseWebSocketMessage<WebSocketEvents.Hello, {
server_version: string;
connection_id: string;
server_hostname?: string;
}>;
export type AuthenticationChallenge = BaseWebSocketMessage<WebSocketEvents.AuthenticationChallenge, unknown>;
export type Response = BaseWebSocketMessage<WebSocketEvents.Response>;
// Post, reactions, and acknowledgement messages
export type Posted = BaseWebSocketMessage<WebSocketEvents.Posted, {
channel_type: ChannelType;
channel_display_name: string;
channel_name: string;
sender_name: string;
team_id: string;
set_online: boolean;
otherFile?: boolean;
image?: boolean;
post: JsonEncodedValue<Post>;
/**
* If the current user is mentioned by this post, this field will contain the ID of that user. Otherwise,
* it will be empty.
*/
mentions?: JsonEncodedValue<string[]>;
/**
* If the current user is following this post, this field will contain the ID of that user. Otherwise,
* it will be empty.
*/
followers?: JsonEncodedValue<string[]>;
should_ack?: boolean;
}>;
export type PostEdited = BaseWebSocketMessage<WebSocketEvents.PostEdited, {
post: JsonEncodedValue<Post>;
}>;
export type PostDeleted = BaseWebSocketMessage<WebSocketEvents.PostDeleted, {
post: JsonEncodedValue<Post>;
/** The user ID of the user who deleted the post, only sent to admin users. */
delete_by?: string;
}>;
export type PostUnread = BaseWebSocketMessage<WebSocketEvents.PostUnread, {
msg_count: number;
msg_count_root: number;
mention_count: number;
mention_count_root: number;
urgent_mention_count: number;
last_viewed_at: number;
post_id: string;
}>;
export type BurnOnReadPostRevealed = BaseWebSocketMessage<WebSocketEvents.BurnOnReadPostRevealed, {
post?: string | Post;
recipients?: string[];
}>;
export type BurnOnReadPostBurned = BaseWebSocketMessage<WebSocketEvents.BurnOnReadPostBurned, {
post_id: string;
}>;
export type BurnOnReadPostAllRevealed = BaseWebSocketMessage<WebSocketEvents.BurnOnReadPostAllRevealed, {
post_id: string;
sender_expire_at: number;
}>;
export type EphemeralPost = BaseWebSocketMessage<WebSocketEvents.EphemeralMessage, {
post: JsonEncodedValue<Post>;
}>;
export type PostReaction =
BaseWebSocketMessage<WebSocketEvents.ReactionAdded | WebSocketEvents.ReactionRemoved, {
reaction: JsonEncodedValue<Reaction>;
}>;
export type PostAcknowledgement =
BaseWebSocketMessage<WebSocketEvents.PostAcknowledgementAdded | WebSocketEvents.PostAcknowledgementRemoved, {
acknowledgement: JsonEncodedValue<PostAcknowledgementType>;
}>;
export type PostDraft =
BaseWebSocketMessage<WebSocketEvents.DraftCreated | WebSocketEvents.DraftUpdated | WebSocketEvents.DraftDeleted, {
draft: JsonEncodedValue<Draft>;
}>;
export type PersistentNotificationTriggered =
BaseWebSocketMessage<WebSocketEvents.PersistentNotificationTriggered, {
post: JsonEncodedValue<Post>;
channel_type: ChannelType;
channel_display_name: string;
channel_name: string;
sender_name: string;
team_id: string;
otherFile?: boolean;
image?: boolean;
mentions?: JsonEncodedValue<string[]>;
}>;
export type ScheduledPost =
BaseWebSocketMessage<WebSocketEvents.ScheduledPostCreated | WebSocketEvents.ScheduledPostUpdated | WebSocketEvents.ScheduledPostDeleted, {
scheduledPost: JsonEncodedValue<ScheduledPostType>;
}>;
// Thread messages
export type ThreadUpdated = BaseWebSocketMessage<WebSocketEvents.ThreadUpdated, {
thread: JsonEncodedValue<UserThread>;
previous_unread_mentions?: number;
previous_unread_replies?: number;
}>;
export type ThreadFollowedChanged = BaseWebSocketMessage<WebSocketEvents.ThreadFollowChanged, {
thread_id: string;
state: boolean;
reply_count: number;
}>;
export type ThreadReadChanged = BaseWebSocketMessage<WebSocketEvents.ThreadReadChanged, {
thread_id?: string;
timestamp: number;
unread_mentions?: number;
unread_replies?: number;
previous_unread_mentions?: number;
previous_unread_replies?: number;
channel_id?: string;
}>;
// Channel and channel member messages
export type ChannelCreated = BaseWebSocketMessage<WebSocketEvents.ChannelCreated, {
channel_id: string;
team_id: string;
}>;
export type ChannelUpdated = BaseWebSocketMessage<WebSocketEvents.ChannelUpdated, {
// Normally, the channel field is sent, except in some cases where a shared channel is updated in which case
// the channel_id field is used.
channel?: JsonEncodedValue<Channel>;
channel_id?: string;
}>;
export type ChannelConverted = BaseWebSocketMessage<WebSocketEvents.ChannelConverted, {
channel_id: string;
}>;
export type ChannelSchemeUpdated = BaseWebSocketMessage<WebSocketEvents.ChannelSchemeUpdated>;
export type ChannelDeleted = BaseWebSocketMessage<WebSocketEvents.ChannelDeleted, {
channel_id: string;
delete_at: number;
}>;
export type ChannelRestored = BaseWebSocketMessage<WebSocketEvents.ChannelRestored, {
channel_id: string;
}>;
export type DirectChannelCreated = BaseWebSocketMessage<WebSocketEvents.DirectAdded, {
creator_id: string;
teammate_id: string;
}>;
export type GroupChannelCreated = BaseWebSocketMessage<WebSocketEvents.GroupAdded, {
teammate_ids: JsonEncodedValue<string[]>;
}>;
export type UserAddedToChannel = BaseWebSocketMessage<WebSocketEvents.UserAdded, {
user_id: string;
team_id: string;
}>;
export type UserRemovedFromChannel = BaseWebSocketMessage<WebSocketEvents.UserRemoved, {
/** The user ID of the user that was removed from the channel. It isn't sent to the user who was removed. */
user_id?: string;
/** The ID of the channel that the user was removed from. It's only sent to the user who was removed. */
channel_id?: string;
remover_id: string;
}>;
export type ChannelMemberUpdated = BaseWebSocketMessage<WebSocketEvents.ChannelMemberUpdated, {
channelMember: JsonEncodedValue<ChannelMembership>;
}>;
export type MultipleChannelsViewed = BaseWebSocketMessage<WebSocketEvents.MultipleChannelsViewed, {
channel_times: Record<string, number>;
}>;
// Channel bookmark messages
export type ChannelBookmarkCreated = BaseWebSocketMessage<WebSocketEvents.ChannelBookmarkCreated, {
bookmark: JsonEncodedValue<ChannelBookmarkWithFileInfo>;
}>;
export type ChannelBookmarkUpdated = BaseWebSocketMessage<WebSocketEvents.ChannelBookmarkUpdated, {
bookmarks: JsonEncodedValue<UpdateChannelBookmarkResponse>;
}>;
export type ChannelBookmarkDeleted = BaseWebSocketMessage<WebSocketEvents.ChannelBookmarkDeleted, {
bookmark: JsonEncodedValue<ChannelBookmarkWithFileInfo>;
}>;
export type ChannelBookmarkSorted = BaseWebSocketMessage<WebSocketEvents.ChannelBookmarkSorted, {
bookmarks: JsonEncodedValue<ChannelBookmarkWithFileInfo[]>;
}>;
// Team and team member messages
export type Team =
BaseWebSocketMessage<WebSocketEvents.UpdateTeam | WebSocketEvents.DeleteTeam | WebSocketEvents.RestoreTeam, {
team: JsonEncodedValue<TeamType>;
}>;
export type UserAddedToTeam = BaseWebSocketMessage<WebSocketEvents.AddedToTeam, {
team_id: string;
user_id: string;
}>;
export type UserRemovedFromTeam = BaseWebSocketMessage<WebSocketEvents.LeaveTeam, {
user_id: string;
team_id: string;
}>;
export type UpdateTeamScheme = BaseWebSocketMessage<WebSocketEvents.UpdateTeamScheme, {
team: JsonEncodedValue<Team>;
}>;
export type TeamMemberRoleUpdated = BaseWebSocketMessage<WebSocketEvents.MemberRoleUpdated, {
member: JsonEncodedValue<TeamMembership>;
}>;
// User and status messages
export type NewUser = BaseWebSocketMessage<WebSocketEvents.NewUser, {
user_id: string;
}>;
export type UserUpdated = BaseWebSocketMessage<WebSocketEvents.UserUpdated, {
/** This user may be missing sensitive data based on if the recipient is that user or an admin. */
user: UserProfile;
}>;
export type UserActivationStatusChanged = BaseWebSocketMessage<WebSocketEvents.UserActivationStatusChange>;
export type UserRoleUpdated = BaseWebSocketMessage<WebSocketEvents.UserRoleUpdated, {
user_id: string;
roles: string;
}>;
export type StatusChanged = BaseWebSocketMessage<WebSocketEvents.StatusChange, {
status: UserStatus['status'];
user_id: string;
}>;
export type Typing = BaseWebSocketMessage<WebSocketEvents.Typing, {
parent_id: string;
user_id: string;
}>;
// Group-related messages
export type ReceivedGroup = BaseWebSocketMessage<WebSocketEvents.ReceivedGroup, {
group: JsonEncodedValue<Group>;
}>;
export type GroupAssociatedToTeam =
BaseWebSocketMessage<WebSocketEvents.ReceivedGroupAssociatedToTeam | WebSocketEvents.ReceivedGroupNotAssociatedToTeam, {
group_id: string;
}>;
export type GroupAssociatedToChannel =
BaseWebSocketMessage<WebSocketEvents.ReceivedGroupAssociatedToChannel | WebSocketEvents.ReceivedGroupNotAssociatedToChannel, {
group_id: string;
}>;
export type GroupMember =
BaseWebSocketMessage<WebSocketEvents.GroupMemberAdded | WebSocketEvents.GroupMemberDeleted, {
group_member: JsonEncodedValue<GroupMemberType>;
}>;
// Preference messages
export type PreferenceChanged = BaseWebSocketMessage<WebSocketEvents.PreferenceChanged, {
preference: JsonEncodedValue<PreferenceType>;
}>;
export type PreferencesChanged =
BaseWebSocketMessage<WebSocketEvents.PreferencesChanged | WebSocketEvents.PreferencesDeleted, {
preferences: JsonEncodedValue<PreferenceType[]>;
}>;
// Channel sidebar messages
export type SidebarCategoryCreated = BaseWebSocketMessage<WebSocketEvents.SidebarCategoryCreated, {
category_id: string;
}>;
export type SidebarCategoryUpdated = BaseWebSocketMessage<WebSocketEvents.SidebarCategoryUpdated, {
updatedCategories: JsonEncodedValue<ChannelCategory[]>;
}>;
export type SidebarCategoryDeleted = BaseWebSocketMessage<WebSocketEvents.SidebarCategoryDeleted, {
category_id: string;
}>;
export type SidebarCategoryOrderUpdated = BaseWebSocketMessage<WebSocketEvents.SidebarCategoryOrderUpdated, {
order: string[];
}>;
// Emoji messages
export type EmojiAdded = BaseWebSocketMessage<WebSocketEvents.EmojiAdded, {
emoji: JsonEncodedValue<Omit<CustomEmoji, 'category'>>;
}>;
// Role messages
export type RoleUpdated = BaseWebSocketMessage<WebSocketEvents.RoleUpdated, {
role: JsonEncodedValue<Role>;
}>;
// Configuration and license messages
export type ConfigChanged = BaseWebSocketMessage<WebSocketEvents.ConfigChanged, {
config: ClientConfig;
}>;
export type GuestsDeactivated = BaseWebSocketMessage<WebSocketEvents.GuestsDeactivated>;
export type LicenseChanged = BaseWebSocketMessage<WebSocketEvents.LicenseChanged, {
license: ClientLicense;
}>;
export type CloudSubscriptionChanged = BaseWebSocketMessage<WebSocketEvents.CloudSubscriptionChanged, {
limits?: Limits;
subscription: Subscription;
}>;
export type FirstAdminVisitMarketplaceStatusReceived =
BaseWebSocketMessage<WebSocketEvents.FirstAdminVisitMarketplaceStatusReceived, {
firstAdminVisitMarketplaceStatus: JsonEncodedValue<boolean>;
}>;
export type HostedCustomerSignupProgressUpdated =
BaseWebSocketMessage<WebSocketEvents.HostedCustomerSignupProgressUpdated, {
progress: string;
}>
// Custom properties messages
export type CPAFieldCreated = BaseWebSocketMessage<WebSocketEvents.CPAFieldCreated, {
field: PropertyField;
}>;
export type CPAFieldUpdated = BaseWebSocketMessage<WebSocketEvents.CPAFieldUpdated, {
field: PropertyField;
delete_values: boolean;
}>;
export type CPAFieldDeleted = BaseWebSocketMessage<WebSocketEvents.CPAFieldDeleted, {
field_id: string;
}>;
export type CPAValuesUpdated = BaseWebSocketMessage<WebSocketEvents.CPAValuesUpdated, {
user_id: string;
values: Array<PropertyValue<unknown>>;
}>;
// Content flagging messages
export type ContentFlaggingReportValueUpdated =
BaseWebSocketMessage<WebSocketEvents.ContentFlaggingReportValueUpdated, {
property_values: JsonEncodedValue<Array<PropertyValue<unknown>>>;
target_id: string;
}>;
// Recap messages
export type RecapUpdated = BaseWebSocketMessage<WebSocketEvents.RecapUpdated, {
recap_id: string;
}>;
// Plugin and integration messages
export type Plugin = BaseWebSocketMessage<WebSocketEvents.PluginEnabled | WebSocketEvents.PluginDisabled, {
manifest: PluginManifest;
}>;
export type PluginStatusesChanged = BaseWebSocketMessage<WebSocketEvents.PluginStatusesChanged, {
plugin_statuses: PluginStatus[];
}>;
export type OpenDialog = BaseWebSocketMessage<WebSocketEvents.OpenDialog, {
dialog: JsonEncodedValue<OpenDialogRequest>;
}>;
/**
* Unknown is used for WebSocket messages which don't come from Mattermost itself. It's primarily intended for use
* by plugins.
*/
export type Unknown = BaseWebSocketMessage<string, unknown>;
@@ -48,6 +48,11 @@ export type ChannelBookmarkPatch = {
emoji?: string;
}
export type UpdateChannelBookmarkResponse = {
updated: ChannelBookmark;
deleted: ChannelBookmark;
};
export type ChannelBookmarkWithFileInfo = ChannelBookmark & {
file: FileInfo;
}
+8 -6
View File
@@ -142,14 +142,16 @@ export type IntegrationsState = {
commands: IDMappedObjects<Command>;
dialogArguments?: DialogArgs;
dialogTriggerId: string;
dialog?: {
url: string;
dialog: Dialog;
trigger_id: string;
};
dialog?: OpenDialogRequest;
};
type Dialog = {
export type OpenDialogRequest = {
trigger_id: string;
url: string;
dialog: Dialog;
}
export type Dialog = {
callback_id?: string;
elements?: DialogElement[];
title: string;