websocket_client.jsx 5.92 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

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

export default class WebSocketClient {
    constructor() {
        this.conn = null;
11
        this.connectionUrl = null;
12
        this.sequence = 1;
13
        this.eventSequence = 0;
14 15 16
        this.connectFailCount = 0;
        this.eventCallback = null;
        this.responseCallbacks = {};
17
        this.firstConnectCallback = null;
18
        this.reconnectCallback = null;
19
        this.missedEventCallback = null;
20 21 22 23
        this.errorCallback = null;
        this.closeCallback = null;
    }

24
    initialize(connectionUrl = this.connectionUrl, token) {
25 26 27 28
        if (this.conn) {
            return;
        }

29 30 31 32 33
        if (connectionUrl == null) {
            console.log('websocket must have connection url'); //eslint-disable-line no-console
            return;
        }

34 35 36 37 38
        if (this.connectFailCount === 0) {
            console.log('websocket connecting to ' + connectionUrl); //eslint-disable-line no-console
        }

        this.conn = new WebSocket(connectionUrl);
39
        this.connectionUrl = connectionUrl;
40 41

        this.conn.onopen = () => {
42 43
            this.eventSequence = 0;

44 45 46 47
            if (token) {
                this.sendMessage('authentication_challenge', {token});
            }

48 49
            if (this.connectFailCount > 0) {
                console.log('websocket re-established connection'); //eslint-disable-line no-console
50 51 52 53 54
                if (this.reconnectCallback) {
                    this.reconnectCallback();
                }
            } else if (this.firstConnectCallback) {
                this.firstConnectCallback();
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
            }

            this.connectFailCount = 0;
        };

        this.conn.onclose = () => {
            this.conn = null;
            this.sequence = 1;

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

            this.connectFailCount++;

            if (this.closeCallback) {
                this.closeCallback(this.connectFailCount);
            }

            let retryTime = MIN_WEBSOCKET_RETRY_TIME;

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

            setTimeout(
                () => {
86
                    this.initialize(connectionUrl, token);
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
                },
                retryTime
            );
        };

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

            if (this.errorCallback) {
                this.errorCallback(evt);
            }
        };

        this.conn.onmessage = (evt) => {
            const msg = JSON.parse(evt.data);
            if (msg.seq_reply) {
                if (msg.error) {
                    console.log(msg); //eslint-disable-line no-console
                }

                if (this.responseCallbacks[msg.seq_reply]) {
                    this.responseCallbacks[msg.seq_reply](msg);
                    Reflect.deleteProperty(this.responseCallbacks, msg.seq_reply);
                }
            } else if (this.eventCallback) {
115 116 117 118 119
                if (msg.seq !== this.eventSequence && this.missedEventCallback) {
                    console.log('missed websocket event, act_seq=' + msg.seq + ' exp_seq=' + this.eventSequence); //eslint-disable-line no-console
                    this.missedEventCallback();
                }
                this.eventSequence = msg.seq + 1;
120 121 122 123 124 125 126 127 128
                this.eventCallback(msg);
            }
        };
    }

    setEventCallback(callback) {
        this.eventCallback = callback;
    }

129 130 131 132
    setFirstConnectCallback(callback) {
        this.firstConnectCallback = callback;
    }

133 134 135 136
    setReconnectCallback(callback) {
        this.reconnectCallback = callback;
    }

137 138 139 140
    setMissedEventCallback(callback) {
        this.missedEventCallback = callback;
    }

141 142 143 144 145 146 147 148 149 150 151 152
    setErrorCallback(callback) {
        this.errorCallback = callback;
    }

    setCloseCallback(callback) {
        this.closeCallback = callback;
    }

    close() {
        this.connectFailCount = 0;
        this.sequence = 1;
        if (this.conn && this.conn.readyState === WebSocket.OPEN) {
153
            this.conn.onclose = () => {}; //eslint-disable-line no-empty-function
154
            this.conn.close();
155 156
            this.conn = null;
            console.log('websocket closed'); //eslint-disable-line no-console
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
        }
    }

    sendMessage(action, data, responseCallback) {
        const msg = {
            action,
            seq: this.sequence++,
            data
        };

        if (responseCallback) {
            this.responseCallbacks[msg.seq] = responseCallback;
        }

        if (this.conn && this.conn.readyState === WebSocket.OPEN) {
            this.conn.send(JSON.stringify(msg));
173
        } else if (!this.conn || this.conn.readyState === WebSocket.CLOSED) {
174 175 176 177 178
            this.conn = null;
            this.initialize();
        }
    }

179
    userTyping(channelId, parentId, callback) {
180 181 182 183
        const data = {};
        data.channel_id = channelId;
        data.parent_id = parentId;

184
        this.sendMessage('user_typing', data, callback);
185 186 187 188 189
    }

    getStatuses(callback) {
        this.sendMessage('get_statuses', null, callback);
    }
190 191 192 193 194 195

    getStatusesByIds(userIds, callback) {
        const data = {};
        data.user_ids = userIds;
        this.sendMessage('get_statuses_by_ids', data, callback);
    }
196
}