|
|
@ -22,9 +22,6 @@ import ( |
|
|
|
|
|
|
|
|
|
|
|
var _server *Server |
|
|
|
var _server *Server |
|
|
|
|
|
|
|
|
|
|
|
// a map of user IDs and when they last were active.
|
|
|
|
|
|
|
|
var _lastSeenCache = map[string]time.Time{} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Server represents an instance of the chat server.
|
|
|
|
// Server represents an instance of the chat server.
|
|
|
|
type Server struct { |
|
|
|
type Server struct { |
|
|
|
clients map[uint]*Client |
|
|
|
clients map[uint]*Client |
|
|
@ -43,6 +40,9 @@ type Server struct { |
|
|
|
maxSocketConnectionLimit int64 |
|
|
|
maxSocketConnectionLimit int64 |
|
|
|
|
|
|
|
|
|
|
|
mu sync.RWMutex |
|
|
|
mu sync.RWMutex |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// a map of user IDs and timers that fire for chat part messages.
|
|
|
|
|
|
|
|
userPartedTimers map[string]*time.Ticker |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// NewChat will return a new instance of the chat server.
|
|
|
|
// NewChat will return a new instance of the chat server.
|
|
|
@ -57,6 +57,7 @@ func NewChat() *Server { |
|
|
|
unregister: make(chan uint), |
|
|
|
unregister: make(chan uint), |
|
|
|
maxSocketConnectionLimit: maximumConcurrentConnectionLimit, |
|
|
|
maxSocketConnectionLimit: maximumConcurrentConnectionLimit, |
|
|
|
geoipClient: geoip.NewClient(), |
|
|
|
geoipClient: geoip.NewClient(), |
|
|
|
|
|
|
|
userPartedTimers: map[string]*time.Ticker{}, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return server |
|
|
|
return server |
|
|
@ -67,7 +68,8 @@ func (s *Server) Run() { |
|
|
|
for { |
|
|
|
for { |
|
|
|
select { |
|
|
|
select { |
|
|
|
case clientID := <-s.unregister: |
|
|
|
case clientID := <-s.unregister: |
|
|
|
if _, ok := s.clients[clientID]; ok { |
|
|
|
if client, ok := s.clients[clientID]; ok { |
|
|
|
|
|
|
|
s.handleClientDisconnected(client) |
|
|
|
s.mu.Lock() |
|
|
|
s.mu.Lock() |
|
|
|
delete(s.clients, clientID) |
|
|
|
delete(s.clients, clientID) |
|
|
|
s.mu.Unlock() |
|
|
|
s.mu.Unlock() |
|
|
@ -92,18 +94,22 @@ func (s *Server) Addclient(conn *websocket.Conn, user *user.User, accessToken st |
|
|
|
ConnectedAt: time.Now(), |
|
|
|
ConnectedAt: time.Now(), |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Do not send user re-joined broadcast message if they've been active within 10 minutes.
|
|
|
|
shouldSendJoinedMessages := data.GetChatJoinPartMessagesEnabled() |
|
|
|
shouldSendJoinedMessages := data.GetChatJoinMessagesEnabled() |
|
|
|
|
|
|
|
if previouslyLastSeen, ok := _lastSeenCache[user.ID]; ok && time.Since(previouslyLastSeen) < time.Minute*10 { |
|
|
|
|
|
|
|
shouldSendJoinedMessages = false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.mu.Lock() |
|
|
|
s.mu.Lock() |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
// If there is a pending disconnect timer then clear it.
|
|
|
|
|
|
|
|
// Do not send user joined message if enough time hasn't passed where the
|
|
|
|
|
|
|
|
// user chat part message hasn't been sent yet.
|
|
|
|
|
|
|
|
if ticker, ok := s.userPartedTimers[user.ID]; ok { |
|
|
|
|
|
|
|
ticker.Stop() |
|
|
|
|
|
|
|
delete(s.userPartedTimers, user.ID) |
|
|
|
|
|
|
|
shouldSendJoinedMessages = false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
client.Id = s.seq |
|
|
|
client.Id = s.seq |
|
|
|
s.clients[client.Id] = client |
|
|
|
s.clients[client.Id] = client |
|
|
|
s.seq++ |
|
|
|
s.seq++ |
|
|
|
_lastSeenCache[user.ID] = time.Now() |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
s.mu.Unlock() |
|
|
|
s.mu.Unlock() |
|
|
|
|
|
|
|
|
|
|
@ -143,16 +149,43 @@ func (s *Server) sendUserJoinedMessage(c *Client) { |
|
|
|
webhooks.SendChatEventUserJoined(userJoinedEvent) |
|
|
|
webhooks.SendChatEventUserJoined(userJoinedEvent) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ClientClosed is fired when a client disconnects or connection is dropped.
|
|
|
|
func (s *Server) handleClientDisconnected(c *Client) { |
|
|
|
func (s *Server) ClientClosed(c *Client) { |
|
|
|
|
|
|
|
s.mu.Lock() |
|
|
|
|
|
|
|
defer s.mu.Unlock() |
|
|
|
|
|
|
|
c.close() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if _, ok := s.clients[c.Id]; ok { |
|
|
|
if _, ok := s.clients[c.Id]; ok { |
|
|
|
log.Debugln("Deleting", c.Id) |
|
|
|
log.Debugln("Deleting", c.Id) |
|
|
|
delete(s.clients, c.Id) |
|
|
|
delete(s.clients, c.Id) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
additionalClientCheck, _ := GetClientsForUser(c.User.ID) |
|
|
|
|
|
|
|
if len(additionalClientCheck) > 0 { |
|
|
|
|
|
|
|
// This user is still connected to chat with another client.
|
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.userPartedTimers[c.User.ID] = time.NewTicker(10 * time.Second) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go func() { |
|
|
|
|
|
|
|
<-s.userPartedTimers[c.User.ID].C |
|
|
|
|
|
|
|
s.sendUserPartedMessage(c) |
|
|
|
|
|
|
|
}() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Server) sendUserPartedMessage(c *Client) { |
|
|
|
|
|
|
|
s.userPartedTimers[c.User.ID].Stop() |
|
|
|
|
|
|
|
delete(s.userPartedTimers, c.User.ID) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
userPartEvent := events.UserPartEvent{} |
|
|
|
|
|
|
|
userPartEvent.SetDefaults() |
|
|
|
|
|
|
|
userPartEvent.User = c.User |
|
|
|
|
|
|
|
userPartEvent.ClientID = c.Id |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If part messages are disabled.
|
|
|
|
|
|
|
|
if data.GetChatJoinPartMessagesEnabled() { |
|
|
|
|
|
|
|
if err := s.Broadcast(userPartEvent.GetBroadcastPayload()); err != nil { |
|
|
|
|
|
|
|
log.Errorln("error sending chat part message", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Send chat user joined webhook
|
|
|
|
|
|
|
|
webhooks.SendChatEventUserParted(userPartEvent) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// HandleClientConnection is fired when a single client connects to the websocket.
|
|
|
|
// HandleClientConnection is fired when a single client connects to the websocket.
|
|
|
|