github.com/ashishbhate/mattermost-server@v5.11.1+incompatible/model/websocket_client.go (about) 1 // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package model 5 6 import ( 7 "encoding/json" 8 "net/http" 9 "time" 10 11 "github.com/gorilla/websocket" 12 ) 13 14 const ( 15 SOCKET_MAX_MESSAGE_SIZE_KB = 8 * 1024 // 8KB 16 PING_TIMEOUT_BUFFER_SECONDS = 5 17 ) 18 19 type WebSocketClient struct { 20 Url string // The location of the server like "ws://localhost:8065" 21 ApiUrl string // The api location of the server like "ws://localhost:8065/api/v3" 22 ConnectUrl string // The websocket URL to connect to like "ws://localhost:8065/api/v3/path/to/websocket" 23 Conn *websocket.Conn // The WebSocket connection 24 AuthToken string // The token used to open the WebSocket 25 Sequence int64 // The ever-incrementing sequence attached to each WebSocket action 26 PingTimeoutChannel chan bool // The channel used to signal ping timeouts 27 EventChannel chan *WebSocketEvent 28 ResponseChannel chan *WebSocketResponse 29 ListenError *AppError 30 pingTimeoutTimer *time.Timer 31 } 32 33 // NewWebSocketClient constructs a new WebSocket client with convenience 34 // methods for talking to the server. 35 func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { 36 return NewWebSocketClientWithDialer(websocket.DefaultDialer, url, authToken) 37 } 38 39 // NewWebSocketClientWithDialer constructs a new WebSocket client with convenience 40 // methods for talking to the server using a custom dialer. 41 func NewWebSocketClientWithDialer(dialer *websocket.Dialer, url, authToken string) (*WebSocketClient, *AppError) { 42 conn, _, err := dialer.Dial(url+API_URL_SUFFIX+"/websocket", nil) 43 if err != nil { 44 return nil, NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) 45 } 46 47 client := &WebSocketClient{ 48 url, 49 url + API_URL_SUFFIX, 50 url + API_URL_SUFFIX + "/websocket", 51 conn, 52 authToken, 53 1, 54 make(chan bool, 1), 55 make(chan *WebSocketEvent, 100), 56 make(chan *WebSocketResponse, 100), 57 nil, 58 nil, 59 } 60 61 client.configurePingHandling() 62 63 client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) 64 65 return client, nil 66 } 67 68 // NewWebSocketClient4 constructs a new WebSocket client with convenience 69 // methods for talking to the server. Uses the v4 endpoint. 70 func NewWebSocketClient4(url, authToken string) (*WebSocketClient, *AppError) { 71 return NewWebSocketClient4WithDialer(websocket.DefaultDialer, url, authToken) 72 } 73 74 // NewWebSocketClient4WithDialer constructs a new WebSocket client with convenience 75 // methods for talking to the server using a custom dialer. Uses the v4 endpoint. 76 func NewWebSocketClient4WithDialer(dialer *websocket.Dialer, url, authToken string) (*WebSocketClient, *AppError) { 77 return NewWebSocketClientWithDialer(dialer, url, authToken) 78 } 79 80 func (wsc *WebSocketClient) Connect() *AppError { 81 return wsc.ConnectWithDialer(websocket.DefaultDialer) 82 } 83 84 func (wsc *WebSocketClient) ConnectWithDialer(dialer *websocket.Dialer) *AppError { 85 var err error 86 wsc.Conn, _, err = dialer.Dial(wsc.ConnectUrl, nil) 87 if err != nil { 88 return NewAppError("Connect", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) 89 } 90 91 wsc.configurePingHandling() 92 93 wsc.EventChannel = make(chan *WebSocketEvent, 100) 94 wsc.ResponseChannel = make(chan *WebSocketResponse, 100) 95 96 wsc.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": wsc.AuthToken}) 97 98 return nil 99 } 100 101 func (wsc *WebSocketClient) Close() { 102 wsc.Conn.Close() 103 } 104 105 func (wsc *WebSocketClient) Listen() { 106 go func() { 107 defer func() { 108 wsc.Conn.Close() 109 close(wsc.EventChannel) 110 close(wsc.ResponseChannel) 111 }() 112 113 for { 114 var rawMsg json.RawMessage 115 var err error 116 if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { 117 if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { 118 wsc.ListenError = NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) 119 } 120 121 return 122 } 123 124 var event WebSocketEvent 125 if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { 126 wsc.EventChannel <- &event 127 continue 128 } 129 130 var response WebSocketResponse 131 if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { 132 wsc.ResponseChannel <- &response 133 continue 134 } 135 136 } 137 }() 138 } 139 140 func (wsc *WebSocketClient) SendMessage(action string, data map[string]interface{}) { 141 req := &WebSocketRequest{} 142 req.Seq = wsc.Sequence 143 req.Action = action 144 req.Data = data 145 146 wsc.Sequence++ 147 148 wsc.Conn.WriteJSON(req) 149 } 150 151 // UserTyping will push a user_typing event out to all connected users 152 // who are in the specified channel 153 func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { 154 data := map[string]interface{}{ 155 "channel_id": channelId, 156 "parent_id": parentId, 157 } 158 159 wsc.SendMessage("user_typing", data) 160 } 161 162 // GetStatuses will return a map of string statuses using user id as the key 163 func (wsc *WebSocketClient) GetStatuses() { 164 wsc.SendMessage("get_statuses", nil) 165 } 166 167 // GetStatusesByIds will fetch certain user statuses based on ids and return 168 // a map of string statuses using user id as the key 169 func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) { 170 data := map[string]interface{}{ 171 "user_ids": userIds, 172 } 173 wsc.SendMessage("get_statuses_by_ids", data) 174 } 175 176 func (wsc *WebSocketClient) configurePingHandling() { 177 wsc.Conn.SetPingHandler(wsc.pingHandler) 178 wsc.pingTimeoutTimer = time.NewTimer(time.Second * (60 + PING_TIMEOUT_BUFFER_SECONDS)) 179 go wsc.pingWatchdog() 180 } 181 182 func (wsc *WebSocketClient) pingHandler(appData string) error { 183 if !wsc.pingTimeoutTimer.Stop() { 184 <-wsc.pingTimeoutTimer.C 185 } 186 187 wsc.pingTimeoutTimer.Reset(time.Second * (60 + PING_TIMEOUT_BUFFER_SECONDS)) 188 wsc.Conn.WriteMessage(websocket.PongMessage, []byte{}) 189 return nil 190 } 191 192 func (wsc *WebSocketClient) pingWatchdog() { 193 <-wsc.pingTimeoutTimer.C 194 wsc.PingTimeoutChannel <- true 195 }