315 lines
10 KiB
JavaScript
315 lines
10 KiB
JavaScript
/**
|
|
* Mattermost driver
|
|
* Websocket code based on https://github.com/mattermost/mattermost-redux/master/client/websocket_client.js
|
|
*
|
|
* @author Aleksander Machniak <machniak@kolabsys.com>
|
|
*
|
|
* @licstart The following is the entire license notice for the
|
|
* JavaScript code in this file.
|
|
*
|
|
* Copyright (C) 2015-2018, Kolab Systems AG <contact@kolabsys.com>
|
|
* Copyright (C) 2015-2018, Mattermost, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* @licend The above is the entire license notice
|
|
* for the JavaScript code in this file.
|
|
*/
|
|
|
|
var MAX_WEBSOCKET_FAILS = 7;
|
|
var MIN_WEBSOCKET_RETRY_TIME = 3000; // 3 sec
|
|
var MAX_WEBSOCKET_RETRY_TIME = 300000; // 5 mins
|
|
|
|
function MMWebSocketClient()
|
|
{
|
|
var Socket;
|
|
|
|
this.conn = null;
|
|
this.connectionUrl = null;
|
|
this.token = null;
|
|
this.sequence = 1;
|
|
this.connectFailCount = 0;
|
|
this.eventCallback = null;
|
|
this.firstConnectCallback = null;
|
|
this.reconnectCallback = null;
|
|
this.errorCallback = null;
|
|
this.closeCallback = null;
|
|
this.connectingCallback = null;
|
|
this.dispatch = null;
|
|
this.getState = null;
|
|
this.stop = false;
|
|
this.platform = '';
|
|
|
|
this.initialize = function(token, dispatch, getState, opts)
|
|
{
|
|
var forceConnection = opts.forceConnection || true,
|
|
webSocketConnector = opts.webSocketConnector || WebSocket,
|
|
connectionUrl = opts.connectionUrl,
|
|
platform = opts.platform,
|
|
self = this;
|
|
|
|
if (platform) {
|
|
this.platform = platform;
|
|
}
|
|
|
|
if (forceConnection) {
|
|
this.stop = false;
|
|
}
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
if (self.conn) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
if (connectionUrl == null) {
|
|
console.log('websocket must have connection url');
|
|
reject('websocket must have connection url');
|
|
return;
|
|
}
|
|
|
|
if (!dispatch) {
|
|
console.log('websocket must have a dispatch');
|
|
reject('websocket must have a dispatch');
|
|
return;
|
|
}
|
|
|
|
if (self.connectFailCount === 0) {
|
|
console.log('websocket connecting to ' + connectionUrl);
|
|
}
|
|
|
|
Socket = webSocketConnector;
|
|
if (self.connectingCallback) {
|
|
self.connectingCallback(dispatch, getState);
|
|
}
|
|
|
|
var regex = /^(?:https?|wss?):(?:\/\/)?[^/]*/;
|
|
var captured = (regex).exec(connectionUrl);
|
|
var origin;
|
|
|
|
if (captured) {
|
|
origin = captured[0];
|
|
|
|
if (platform === 'android') {
|
|
// this is done cause for android having the port 80 or 443 will fail the connection
|
|
// the websocket will append them
|
|
var split = origin.split(':');
|
|
var port = split[2];
|
|
if (port === '80' || port === '443') {
|
|
origin = split[0] + ':' + split[1];
|
|
}
|
|
}
|
|
} else {
|
|
// If we're unable to set the origin header, the websocket won't connect, but the URL is likely malformed anyway
|
|
console.warn('websocket failed to parse origin from ' + connectionUrl);
|
|
}
|
|
|
|
self.conn = new Socket(connectionUrl, [], {headers: {origin}});
|
|
self.connectionUrl = connectionUrl;
|
|
self.token = token;
|
|
self.dispatch = dispatch;
|
|
self.getState = getState;
|
|
|
|
self.conn.onopen = function() {
|
|
if (token && platform !== 'android') {
|
|
// we check for the platform as a workaround until we fix on the server that further authentications
|
|
// are ignored
|
|
self.sendMessage('authentication_challenge', {token});
|
|
}
|
|
|
|
if (self.connectFailCount > 0) {
|
|
console.log('websocket re-established connection');
|
|
if (self.reconnectCallback) {
|
|
self.reconnectCallback(self.dispatch, self.getState);
|
|
}
|
|
} else if (self.firstConnectCallback) {
|
|
self.firstConnectCallback(self.dispatch, self.getState);
|
|
}
|
|
|
|
self.connectFailCount = 0;
|
|
resolve();
|
|
};
|
|
|
|
self.conn.onclose = function() {
|
|
self.conn = null;
|
|
self.sequence = 1;
|
|
|
|
if (self.connectFailCount === 0) {
|
|
console.log('websocket closed');
|
|
}
|
|
|
|
self.connectFailCount++;
|
|
|
|
if (self.closeCallback) {
|
|
self.closeCallback(self.connectFailCount, self.dispatch, self.getState);
|
|
}
|
|
|
|
var retryTime = MIN_WEBSOCKET_RETRY_TIME;
|
|
|
|
// If we've failed a bunch of connections then start backing off
|
|
if (self.connectFailCount > MAX_WEBSOCKET_FAILS) {
|
|
retryTime = MIN_WEBSOCKET_RETRY_TIME * self.connectFailCount;
|
|
if (retryTime > MAX_WEBSOCKET_RETRY_TIME) {
|
|
retryTime = MAX_WEBSOCKET_RETRY_TIME;
|
|
}
|
|
}
|
|
|
|
setTimeout(function() {
|
|
if (self.stop) {
|
|
return;
|
|
}
|
|
self.initialize(token, dispatch, getState, Object.assign({}, opts, {forceConnection: true}));
|
|
},
|
|
retryTime
|
|
);
|
|
};
|
|
|
|
self.conn.onerror = function(evt) {
|
|
if (self.connectFailCount <= 1) {
|
|
console.log('websocket error');
|
|
console.log(evt);
|
|
}
|
|
|
|
if (self.errorCallback) {
|
|
self.errorCallback(evt, self.dispatch, self.getState);
|
|
}
|
|
};
|
|
|
|
self.conn.onmessage = function(evt) {
|
|
var msg = JSON.parse(evt.data);
|
|
if (msg.seq_reply) {
|
|
if (msg.error) {
|
|
console.warn(msg);
|
|
}
|
|
} else if (self.eventCallback) {
|
|
self.eventCallback(msg, self.dispatch, self.getState);
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
this.setConnectingCallback = function(callback)
|
|
{
|
|
this.connectingCallback = callback;
|
|
}
|
|
|
|
this.setEventCallback = function(callback)
|
|
{
|
|
this.eventCallback = callback;
|
|
}
|
|
|
|
this.setFirstConnectCallback = function(callback)
|
|
{
|
|
this.firstConnectCallback = callback;
|
|
}
|
|
|
|
this.setReconnectCallback = function(callback)
|
|
{
|
|
this.reconnectCallback = callback;
|
|
}
|
|
|
|
this.setErrorCallback = function(callback)
|
|
{
|
|
this.errorCallback = callback;
|
|
}
|
|
|
|
this.setCloseCallback = function(callback) {
|
|
this.closeCallback = callback;
|
|
}
|
|
|
|
this.close = function(stop)
|
|
{
|
|
this.stop = stop;
|
|
this.connectFailCount = 0;
|
|
this.sequence = 1;
|
|
|
|
if (this.conn && this.conn.readyState === Socket.OPEN) {
|
|
this.conn.onclose = function(){};
|
|
this.conn.close();
|
|
this.conn = null;
|
|
console.log('websocket closed');
|
|
}
|
|
}
|
|
|
|
this.sendMessage = function(action, data)
|
|
{
|
|
var msg = {
|
|
action,
|
|
seq: this.sequence++,
|
|
data
|
|
};
|
|
|
|
if (this.conn && this.conn.readyState === Socket.OPEN) {
|
|
this.conn.send(JSON.stringify(msg));
|
|
} else if (!this.conn || this.conn.readyState === Socket.CLOSED) {
|
|
this.conn = null;
|
|
this.initialize(this.token, this.dispatch, this.getState, {forceConnection: true, platform: this.platform});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes and starts websocket connection with Mattermost
|
|
*/
|
|
function mattermost_websocket_init(url, token)
|
|
{
|
|
var api = new MMWebSocketClient();
|
|
|
|
api.setEventCallback(function(e) {
|
|
mattermost_event_handler(e);
|
|
});
|
|
|
|
api.initialize(token, {}, {}, {connectionUrl: url});
|
|
}
|
|
|
|
/**
|
|
* Handles websocket events
|
|
*/
|
|
function mattermost_event_handler(event)
|
|
{
|
|
// We're interested only in direct messages for now
|
|
if (event.event == 'posted' && event.data.channel_type == 'D') {
|
|
var user = event.data.sender_name,
|
|
channel_id = event.broadcast.channel_id,
|
|
// channel_name = event.data.channel_display_name,
|
|
msg = rcmail.gettext('kolab_chat.directmessage').replace('$u', user),
|
|
link = $('<a>').text(rcmail.gettext('kolab_chat.openchat'))
|
|
.attr('href', '?_task=kolab-chat&_channel=' + urlencode(channel_id));
|
|
|
|
if (rcmail.env.kolab_chat_extwin) {
|
|
link.attr('target', '_blank').attr('href', link.attr('href') + '&redirect=1');
|
|
}
|
|
|
|
msg = $('<p>').text(msg + ' ').append(link).html();
|
|
|
|
// FIXME: Should we display it indefinitely?
|
|
rcmail.display_message(msg, 'notice chat', 10 * 60 * 1000, 'chat-user-' + user);
|
|
}
|
|
}
|
|
|
|
|
|
window.WebSocket && window.rcmail && rcmail.addEventListener('init', function() {
|
|
// Use ajax to get the token for websocket connection
|
|
$.ajax({
|
|
type: 'GET',
|
|
url: '?_task=kolab-chat&_action=action&_get=token',
|
|
success: function(data) {
|
|
data = JSON.parse(data);
|
|
if (data && data.token) {
|
|
// rcmail.set_env({mattermost_url: data.url, mattermost_token: data.token});
|
|
mattermost_websocket_init(data.url, data.token);
|
|
}
|
|
}
|
|
});
|
|
});
|