websocket_actions.jsx 10.6 KB
Newer Older
1 2 3 4
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

import $ from 'jquery';
5

6
import UserStore from 'stores/user_store.jsx';
7
import TeamStore from 'stores/team_store.jsx';
8
import PostStore from 'stores/post_store.jsx';
9
import PreferenceStore from 'stores/preference_store.jsx';
10 11 12
import ChannelStore from 'stores/channel_store.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
13
import NotificationStore from 'stores/notification_store.jsx'; //eslint-disable-line no-unused-vars
14

15
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
16 17
import Client from 'client/web_client.jsx';
import WebSocketClient from 'client/web_websocket_client.jsx';
enahum's avatar
enahum committed
18
import * as WebrtcActions from './webrtc_actions.jsx';
19 20
import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
21
import {getSiteURL} from 'utils/url.jsx';
22

23
import * as GlobalActions from 'actions/global_actions.jsx';
24
import {handleNewPost, loadPosts, loadProfilesForPosts} from 'actions/post_actions.jsx';
25
import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
26
import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
27
import * as StatusActions from 'actions/status_actions.jsx';
28

29
import {ActionTypes, Constants, Preferences, SocketEvents, UserStatuses} from 'utils/constants.jsx';
30

31
import {browserHistory} from 'react-router/es6';
32

33 34 35
const MAX_WEBSOCKET_FAILS = 7;

export function initialize() {
36 37 38 39
    if (!window.WebSocket) {
        console.log('Browser does not support websocket'); //eslint-disable-line no-console
        return;
    }
40

41
    let connUrl = getSiteURL();
42 43 44 45 46 47 48

    // replace the protocol with a websocket one
    if (connUrl.startsWith('https:')) {
        connUrl = connUrl.replace(/^https:/, 'wss:');
    } else {
        connUrl = connUrl.replace(/^http:/, 'ws:');
    }
49

50 51 52 53 54 55
    // append a port number if one isn't already specified
    if (!(/:\d+$/).test(connUrl)) {
        if (connUrl.startsWith('wss:')) {
            connUrl += ':' + global.window.mm_config.WebsocketSecurePort;
        } else {
            connUrl += ':' + global.window.mm_config.WebsocketPort;
56
        }
57
    }
58

59 60
    // append the websocket api path
    connUrl += Client.getUsersRoute() + '/websocket';
61

62 63
    WebSocketClient.setEventCallback(handleEvent);
    WebSocketClient.setFirstConnectCallback(handleFirstConnect);
64 65 66 67 68 69 70
    WebSocketClient.setReconnectCallback(() => reconnect(false));
    WebSocketClient.setMissedEventCallback(() => {
        if (global.window.mm_config.EnableDeveloper === 'true') {
            Client.logClientError('missed websocket event seq=' + WebSocketClient.eventSequence);
        }
        reconnect(false);
    });
71 72
    WebSocketClient.setCloseCallback(handleClose);
    WebSocketClient.initialize(connUrl);
73 74
}

75 76 77 78
export function close() {
    WebSocketClient.close();
}

79
function reconnectWebSocket() {
80 81
    close();
    initialize();
82 83 84 85 86 87
}

export function reconnect(includeWebSocket = true) {
    if (includeWebSocket) {
        reconnectWebSocket();
    }
88

89
    if (Client.teamId) {
90
        loadChannelsForCurrentUser();
91
        loadPosts(ChannelStore.getCurrentId());
92
        StatusActions.loadStatusesForChannelAndSidebar();
93
    }
94

95 96 97 98
    ErrorStore.clearLastError();
    ErrorStore.emitChange();
}

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
let intervalId = '';
const SYNC_INTERVAL_MILLISECONDS = 1000 * 60 * 15; // 15 minutes

export function startPeriodicSync() {
    clearInterval(intervalId);

    intervalId = setInterval(
        () => {
            if (UserStore.getCurrentUser() != null) {
                reconnect(false);
            }
        },
        SYNC_INTERVAL_MILLISECONDS
    );
}

export function stopPeriodicSync() {
    clearInterval(intervalId);
}

119
function handleFirstConnect() {
120 121 122 123 124 125 126 127 128 129 130 131
    ErrorStore.clearLastError();
    ErrorStore.emitChange();
}

function handleClose(failCount) {
    if (failCount > MAX_WEBSOCKET_FAILS) {
        ErrorStore.storeLastError({message: Utils.localizeMessage('channel_loader.socketError', 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.')});
    }

    ErrorStore.setConnectionErrorCount(failCount);
    ErrorStore.emitChange();
}
132

133 134
function handleEvent(msg) {
    switch (msg.event) {
135 136 137 138 139 140 141 142 143 144 145 146 147
    case SocketEvents.POSTED:
    case SocketEvents.EPHEMERAL_MESSAGE:
        handleNewPostEvent(msg);
        break;

    case SocketEvents.POST_EDITED:
        handlePostEditEvent(msg);
        break;

    case SocketEvents.POST_DELETED:
        handlePostDeleteEvent(msg);
        break;

148 149 150 151
    case SocketEvents.LEAVE_TEAM:
        handleLeaveTeamEvent(msg);
        break;

enahum's avatar
enahum committed
152 153 154 155
    case SocketEvents.UPDATE_TEAM:
        handleUpdateTeamEvent(msg);
        break;

156 157 158 159 160 161 162 163
    case SocketEvents.USER_ADDED:
        handleUserAddedEvent(msg);
        break;

    case SocketEvents.USER_REMOVED:
        handleUserRemovedEvent(msg);
        break;

164 165 166 167
    case SocketEvents.USER_UPDATED:
        handleUserUpdatedEvent(msg);
        break;

168 169 170 171
    case SocketEvents.CHANNEL_CREATED:
        handleChannelCreatedEvent(msg);
        break;

172 173 174 175
    case SocketEvents.CHANNEL_DELETED:
        handleChannelDeletedEvent(msg);
        break;

176 177 178 179
    case SocketEvents.DIRECT_ADDED:
        handleDirectAddedEvent(msg);
        break;

180 181 182 183 184 185 186 187
    case SocketEvents.PREFERENCE_CHANGED:
        handlePreferenceChangedEvent(msg);
        break;

    case SocketEvents.TYPING:
        handleUserTypingEvent(msg);
        break;

188 189 190 191
    case SocketEvents.STATUS_CHANGED:
        handleStatusChangedEvent(msg);
        break;

192 193 194 195
    case SocketEvents.HELLO:
        handleHelloEvent(msg);
        break;

enahum's avatar
enahum committed
196 197 198 199
    case SocketEvents.WEBRTC:
        handleWebrtc(msg);
        break;

200 201 202 203 204 205 206 207
    case SocketEvents.REACTION_ADDED:
        handleReactionAddedEvent(msg);
        break;

    case SocketEvents.REACTION_REMOVED:
        handleReactionRemovedEvent(msg);
        break;

208 209 210 211 212
    default:
    }
}

function handleNewPostEvent(msg) {
213
    const post = JSON.parse(msg.data.post);
214
    handleNewPost(post, msg);
215

216 217 218 219
    const posts = {};
    posts[post.id] = post;
    loadProfilesForPosts(posts);

220 221 222
    if (UserStore.getStatus(post.user_id) !== UserStatuses.ONLINE) {
        StatusActions.loadStatusesByIds([post.user_id]);
    }
223 224 225 226
}

function handlePostEditEvent(msg) {
    // Store post
227
    const post = JSON.parse(msg.data.post);
228
    PostStore.storePost(post, false);
229 230 231
    PostStore.emitChange();

    // Update channel state
232
    if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
233
        if (window.isActive) {
234
            AsyncClient.viewChannel();
235 236 237 238 239
        }
    }
}

function handlePostDeleteEvent(msg) {
240
    const post = JSON.parse(msg.data.post);
241 242 243
    GlobalActions.emitPostDeletedEvent(post);
}

244
function handleLeaveTeamEvent(msg) {
245
    if (UserStore.getCurrentId() === msg.data.user_id) {
246
        TeamStore.removeMyTeamMember(msg.data.team_id);
247

enahum's avatar
enahum committed
248
        // if they are on the team being removed redirect them to default team
249
        if (TeamStore.getCurrentId() === msg.data.team_id) {
250 251
            TeamStore.setCurrentId('');
            Client.setTeamId('');
enahum's avatar
enahum committed
252
            BrowserStore.removeGlobalItem('team');
enahum's avatar
enahum committed
253
            BrowserStore.removeGlobalItem(msg.data.team_id);
enahum's avatar
enahum committed
254
            GlobalActions.redirectUserToDefaultTeam();
255
        }
enahum's avatar
enahum committed
256 257 258
    } else {
        UserStore.removeProfileFromTeam(msg.data.team_id, msg.data.user_id);
        TeamStore.removeMemberInTeam(msg.data.team_id, msg.data.user_id);
259 260 261
    }
}

enahum's avatar
enahum committed
262 263 264 265
function handleUpdateTeamEvent(msg) {
    TeamStore.updateTeam(msg.data.team);
}

266
function handleDirectAddedEvent(msg) {
267
    AsyncClient.getChannel(msg.broadcast.channel_id);
268
    PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, msg.data.teammate_id, 'true');
269
    loadProfilesForSidebar();
270 271
}

272
function handleUserAddedEvent(msg) {
273
    if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
274
        AsyncClient.getChannelStats();
275 276
    }

277 278
    if (TeamStore.getCurrentId() === msg.data.team_id && UserStore.getCurrentId() === msg.data.user_id) {
        AsyncClient.getChannel(msg.broadcast.channel_id);
279 280 281 282
    }
}

function handleUserRemovedEvent(msg) {
283
    if (UserStore.getCurrentId() === msg.broadcast.user_id) {
284
        loadChannelsForCurrentUser();
285

286 287
        if (msg.data.remover_id !== msg.broadcast.user_id &&
                msg.data.channel_id === ChannelStore.getCurrentId() &&
288 289 290
                $('#removed_from_channel').length > 0) {
            var sentState = {};
            sentState.channelName = ChannelStore.getCurrent().display_name;
291
            sentState.remover = UserStore.getProfile(msg.data.remover_id).username;
292 293 294 295

            BrowserStore.setItem('channel-removed-state', sentState);
            $('#removed_from_channel').modal('show');
        }
296
    } else if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
297
        AsyncClient.getChannelStats();
298 299 300
    }
}

301
function handleUserUpdatedEvent(msg) {
302 303 304 305
    const user = msg.data.user;
    if (UserStore.getCurrentId() !== user.id) {
        UserStore.saveProfile(user);
        UserStore.emitChange(user.id);
306 307 308
    }
}

309 310
function handleChannelCreatedEvent(msg) {
    const channelId = msg.data.channel_id;
enahum's avatar
enahum committed
311
    const teamId = msg.data.team_id;
312

enahum's avatar
enahum committed
313
    if (TeamStore.getCurrentId() === teamId && !ChannelStore.getChannelById(channelId)) {
314 315 316 317
        AsyncClient.getChannel(channelId);
    }
}

318
function handleChannelDeletedEvent(msg) {
319
    if (ChannelStore.getCurrentId() === msg.data.channel_id) {
320 321 322
        const teamUrl = TeamStore.getCurrentTeamRelativeUrl();
        browserHistory.push(teamUrl + '/channels/' + Constants.DEFAULT_CHANNEL);
    }
323
    loadChannelsForCurrentUser();
324 325
}

326
function handlePreferenceChangedEvent(msg) {
327
    const preference = JSON.parse(msg.data.preference);
328 329 330 331
    GlobalActions.emitPreferenceChangedEvent(preference);
}

function handleUserTypingEvent(msg) {
332
    GlobalActions.emitRemoteUserTypingEvent(msg.broadcast.channel_id, msg.data.user_id, msg.data.parent_id);
333 334 335 336

    if (UserStore.getStatus(msg.data.user_id) !== UserStatuses.ONLINE) {
        StatusActions.loadStatusesByIds([msg.data.user_id]);
    }
337
}
338 339

function handleStatusChangedEvent(msg) {
340
    UserStore.setStatus(msg.data.user_id, msg.data.status);
341
}
342 343 344 345 346

function handleHelloEvent(msg) {
    Client.serverVersion = msg.data.server_version;
    AsyncClient.checkVersion();
}
enahum's avatar
enahum committed
347 348 349 350

function handleWebrtc(msg) {
    const data = msg.data;
    return WebrtcActions.handle(data);
351
}
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371

function handleReactionAddedEvent(msg) {
    const reaction = JSON.parse(msg.data.reaction);

    AppDispatcher.handleServerAction({
        type: ActionTypes.ADDED_REACTION,
        postId: reaction.post_id,
        reaction
    });
}

function handleReactionRemovedEvent(msg) {
    const reaction = JSON.parse(msg.data.reaction);

    AppDispatcher.handleServerAction({
        type: ActionTypes.REMOVED_REACTION,
        postId: reaction.post_id,
        reaction
    });
}