3
0

Improves presence/activity handling over websocket

This commit is contained in:
Denis Arh
2019-04-27 14:20:33 +02:00
parent 5be1b607f5
commit 1ce5b355ae
6 changed files with 65 additions and 35 deletions
+1
View File
@@ -9,6 +9,7 @@ type (
Kind string `json:"kind,omitempty"`
MessageID uint64 `json:"messageID,string,omitempty"`
ChannelID uint64 `json:"channelID,string,omitempty"`
Present bool `json:"present"`
}
)
-25
View File
@@ -1,25 +0,0 @@
package outgoing
import (
"encoding/json"
)
type (
Connected struct {
// Who did connect?
UserID string `json:"uid"`
}
Disconnected struct {
// Who did disconnect?
UserID string `json:"uid"`
}
)
func (p *Connected) EncodeMessage() ([]byte, error) {
return json.Marshal(Payload{Connected: p})
}
func (p *Disconnected) EncodeMessage() ([]byte, error) {
return json.Marshal(Payload{Disconnected: p})
}
-3
View File
@@ -4,9 +4,6 @@ type (
Payload struct {
*Error `json:"error,omitempty"`
*Connected `json:"clientConnected,omitempty"`
*Disconnected `json:"clientDisconnected,omitempty"`
*Message `json:"message,omitempty"`
*MessageSet `json:"messages,omitempty"`
+7
View File
@@ -29,6 +29,13 @@ func (ctrl *Activity) Send(ctx context.Context, r *request.ActivitySend) (interf
return nil, errors.New("can not send activity on message without channel ID")
}
switch r.Kind {
case "":
return nil, errors.New("missing value for activity kind")
case "connected", "disconnected":
return nil, errors.New("can not use reserved values for activity kind")
}
return true, ctrl.event.With(ctx).Activity(&types.Activity{
UserID: auth.GetIdentityFromContext(ctx).Identity(),
ChannelID: r.ChannelID,
+45 -6
View File
@@ -23,7 +23,9 @@ type (
id uint64
once sync.Once
conn *websocket.Conn
ctx context.Context
ctx context.Context
ctxCancel context.CancelFunc
subs *Subscriptions
@@ -44,15 +46,17 @@ type (
)
func (Session) New(ctx context.Context, config *repository.Flags, conn *websocket.Conn) *Session {
s := &Session{
conn: conn,
ctx: ctx,
config: config,
subs: NewSubscriptions(),
send: make(chan []byte, 512),
stop: make(chan []byte, 1),
}
s.ctx, s.ctxCancel = context.WithCancel(ctx)
s.svc.ch = service.DefaultChannel
s.svc.msg = service.DefaultMessage
@@ -86,16 +90,53 @@ func (sess *Session) connected() (err error) {
}
// Tell everyone that user has connected
if err = sess.sendToAll(&outgoing.Connected{UserID: payload.Uint64toa(sess.user.Identity())}); err != nil {
if err = sess.sendPresence("connected"); err != nil {
return
}
// Create a heartbeat every minute for this user
go func() {
t := time.NewTicker(time.Second * 3)
for {
select {
case <-sess.ctx.Done():
return
case <-t.C:
sess.sendPresence("")
}
}
}()
return nil
}
func (sess *Session) disconnected() {
// Tell everyone that user has disconnected
_ = sess.sendToAll(&outgoing.Disconnected{UserID: payload.Uint64toa(sess.user.Identity())})
_ = sess.sendPresence("disconnected")
// Cancel context
sess.ctxCancel()
// Close connection
sess.conn.Close()
sess.conn = nil
}
// Sends user presence information to all subscribers
//
// It sends "connected", "disconnected" and "" activity kinds
func (sess *Session) sendPresence(kind string) error {
connections := store.CountConnections(sess.user.Identity())
if kind == "disconnected" {
connections--
}
// Tell everyone that user has disconnected
return sess.sendToAll(&outgoing.Activity{
UserID: sess.user.Identity(),
Kind: kind,
Present: connections > 0,
})
}
func (sess *Session) Handle() (err error) {
@@ -111,8 +152,6 @@ func (sess *Session) Handle() (err error) {
func (sess *Session) Close() {
sess.once.Do(func() {
sess.disconnected()
sess.conn.Close()
sess.conn = nil
store.Delete(sess.id)
})
}
+12 -1
View File
@@ -1,8 +1,9 @@
package websocket
import (
"github.com/titpetric/factory"
"sync"
"github.com/titpetric/factory"
)
type (
@@ -39,6 +40,16 @@ func (s *Store) Walk(callback func(*Session)) {
}
}
func (s *Store) CountConnections(userID uint64) (count uint) {
s.Walk(func(session *Session) {
if session.user.Identity() == userID {
count++
}
})
return
}
func (s *Store) Get(id uint64) *Session {
s.RLock()
defer s.RUnlock()