Skip to content

Commit

Permalink
Added infrastructure for basic WebSocket API (mattermost#3432)
Browse files Browse the repository at this point in the history
  • Loading branch information
jwilander committed Jul 12, 2016
1 parent dbfdcad commit d03e47c
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 117 deletions.
4 changes: 2 additions & 2 deletions actions/global_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import SearchStore from 'stores/search_store.jsx';

import * as Websockets from 'actions/websocket_actions.jsx';
import {handleNewPost} from 'actions/post_actions.jsx';

import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;

import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import WebSocketClient from 'utils/websocket_client.jsx';
import * as Utils from 'utils/utils.jsx';

import en from 'i18n/en.json';
Expand Down Expand Up @@ -439,7 +439,7 @@ var lastTimeTypingSent = 0;
export function emitLocalUserTypingEvent(channelId, parentId) {
const t = Date.now();
if ((t - lastTimeTypingSent) > Constants.UPDATE_TYPING_MS) {
Websockets.sendMessage({channel_id: channelId, action: 'typing', props: {parent_id: parentId}, state: {}});
WebSocketClient.userTyping(channelId, parentId);
lastTimeTypingSent = t;
}
}
Expand Down
148 changes: 34 additions & 114 deletions actions/websocket_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ErrorStore from 'stores/error_store.jsx';
import NotificationStore from 'stores/notification_store.jsx'; //eslint-disable-line no-unused-vars

import Client from 'utils/web_client.jsx';
import WebSocketClient from 'utils/websocket_client.jsx';
import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
Expand All @@ -23,102 +24,45 @@ const SocketEvents = Constants.SocketEvents;
import {browserHistory} from 'react-router/es6';

const MAX_WEBSOCKET_FAILS = 7;
const MIN_WEBSOCKET_RETRY_TIME = 3000; // 3 sec
const MAX_WEBSOCKET_RETRY_TIME = 300000; // 5 mins

var conn = null;
var connectFailCount = 0;
var pastFirstInit = false;
var manuallyClosed = false;

export function initialize() {
if (window.WebSocket && !conn) {
if (window.WebSocket) {
let protocol = 'ws:https://';
if (window.location.protocol === 'https:') {
protocol = 'wss:https://';
}

const connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + Client.getUsersRoute() + '/websocket';

if (connectFailCount === 0) {
console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console
}

manuallyClosed = false;

conn = new WebSocket(connUrl);

conn.onopen = () => {
if (connectFailCount > 0) {
console.log('websocket re-established connection'); //eslint-disable-line no-console
AsyncClient.getChannels();
AsyncClient.getPosts(ChannelStore.getCurrentId());
}

if (pastFirstInit) {
ErrorStore.clearLastError();
ErrorStore.emitChange();
}

pastFirstInit = true;
connectFailCount = 0;
};

conn.onclose = () => {
conn = null;

if (connectFailCount === 0) {
console.log('websocket closed'); //eslint-disable-line no-console
}

if (manuallyClosed) {
return;
}

connectFailCount = connectFailCount + 1;

var retryTime = MIN_WEBSOCKET_RETRY_TIME;

if (connectFailCount > 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.')});

// If we've failed a bunch of connections then start backing off
retryTime = MIN_WEBSOCKET_RETRY_TIME * connectFailCount * connectFailCount;
if (retryTime > MAX_WEBSOCKET_RETRY_TIME) {
retryTime = MAX_WEBSOCKET_RETRY_TIME;
}
}

ErrorStore.setConnectionErrorCount(connectFailCount);
ErrorStore.emitChange();

setTimeout(
() => {
initialize();
},
retryTime
);
};

conn.onerror = (evt) => {
if (connectFailCount <= 1) {
console.log('websocket error'); //eslint-disable-line no-console
console.log(evt); //eslint-disable-line no-console
}
};

conn.onmessage = (evt) => {
const msg = JSON.parse(evt.data);
handleMessage(msg);
};
WebSocketClient.initialize(connUrl);
WebSocketClient.setEventCallback(handleEvent);
WebSocketClient.setReconnectCallback(handleReconnect);
WebSocketClient.setCloseCallback(handleClose);
}
}

function handleMessage(msg) {
// Let the store know we are online. This probably shouldn't be here.
UserStore.setStatus(msg.user_id, 'online');
export function close() {
WebSocketClient.close();
}

function handleReconnect() {
AsyncClient.getChannels();
AsyncClient.getPosts(ChannelStore.getCurrentId());
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();
}

switch (msg.action) {
function handleEvent(msg) {
switch (msg.event) {
case SocketEvents.POSTED:
case SocketEvents.EPHEMERAL_MESSAGE:
handleNewPostEvent(msg);
Expand Down Expand Up @@ -172,36 +116,14 @@ function handleMessage(msg) {
}
}

export function sendMessage(msg) {
if (conn && conn.readyState === WebSocket.OPEN) {
var teamId = TeamStore.getCurrentId();
if (teamId && teamId.length > 0) {
msg.team_id = teamId;
}

conn.send(JSON.stringify(msg));
} else if (!conn || conn.readyState === WebSocket.Closed) {
conn = null;
initialize();
}
}

export function close() {
manuallyClosed = true;
connectFailCount = 0;
if (conn && conn.readyState === WebSocket.OPEN) {
conn.close();
}
}

function handleNewPostEvent(msg) {
const post = JSON.parse(msg.props.post);
const post = JSON.parse(msg.data.post);
handleNewPost(post, msg);
}

function handlePostEditEvent(msg) {
// Store post
const post = JSON.parse(msg.props.post);
const post = JSON.parse(msg.data.post);
PostStore.storePost(post);
PostStore.emitChange();

Expand All @@ -214,7 +136,7 @@ function handlePostEditEvent(msg) {
}

function handlePostDeleteEvent(msg) {
const post = JSON.parse(msg.props.post);
const post = JSON.parse(msg.data.post);
GlobalActions.emitPostDeletedEvent(post);
}

Expand Down Expand Up @@ -257,12 +179,12 @@ function handleUserRemovedEvent(msg) {
if (UserStore.getCurrentId() === msg.user_id) {
AsyncClient.getChannels();

if (msg.props.remover_id !== msg.user_id &&
if (msg.data.remover_id !== msg.user_id &&
msg.channel_id === ChannelStore.getCurrentId() &&
$('#removed_from_channel').length > 0) {
var sentState = {};
sentState.channelName = ChannelStore.getCurrent().display_name;
sentState.remover = UserStore.getProfile(msg.props.remover_id).username;
sentState.remover = UserStore.getProfile(msg.data.remover_id).username;

BrowserStore.setItem('channel-removed-state', sentState);
$('#removed_from_channel').modal('show');
Expand Down Expand Up @@ -290,12 +212,10 @@ function handleChannelDeletedEvent(msg) {
}

function handlePreferenceChangedEvent(msg) {
const preference = JSON.parse(msg.props.preference);
const preference = JSON.parse(msg.data.preference);
GlobalActions.emitPreferenceChangedEvent(preference);
}

function handleUserTypingEvent(msg) {
if (TeamStore.getCurrentId() === msg.team_id) {
GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.props.parent_id);
}
GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.data.parent_id);
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"keymirror": "0.1.1",
"marked": "mattermost/marked#12d2be4cdf54d4ec95fead934e18840b6a2c1a7b",
"match-at": "0.1.0",
"mattermost": "mattermost/mattermost-javascript#5815f14f0d1960aa4c99797b09d949d2959eb24f",
"mattermost": "mattermost/mattermost-javascript#4cdaeba22ff82bf93dc417af1ab4e89e3248d624",
"object-assign": "4.1.0",
"perfect-scrollbar": "0.6.11",
"react": "15.0.2",
Expand Down
7 changes: 7 additions & 0 deletions utils/websocket_client.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

import WebSocketClient from 'mattermost/websocket_client.jsx';

var WebClient = new WebSocketClient();
export default WebClient;
9 changes: 9 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ var config = {
cacheDirectory: DEV
}
},
{
test: /node_modules\/mattermost\/websocket_client\.jsx?$/,
loader: 'babel',
query: {
presets: ['react', 'es2015-webpack', 'stage-0'],
plugins: ['transform-runtime'],
cacheDirectory: DEV
}
},
{
test: /\.json$/,
loader: 'json'
Expand Down

0 comments on commit d03e47c

Please sign in to comment.