session.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package server
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "github.com/mkaminski/goaim/user"
  7. "log/slog"
  8. "sync"
  9. "time"
  10. "github.com/mkaminski/goaim/oscar"
  11. )
  12. var (
  13. ErrSessNotFound = errors.New("session was not found")
  14. ErrSignedOff = errors.New("user signed off")
  15. )
  16. type InMemorySessionManager struct {
  17. store map[string]*user.Session
  18. mapMutex sync.RWMutex
  19. logger *slog.Logger
  20. }
  21. func NewSessionManager(logger *slog.Logger) *InMemorySessionManager {
  22. return &InMemorySessionManager{
  23. logger: logger,
  24. store: make(map[string]*user.Session),
  25. }
  26. }
  27. func (s *InMemorySessionManager) Broadcast(ctx context.Context, msg oscar.XMessage) {
  28. s.mapMutex.RLock()
  29. defer s.mapMutex.RUnlock()
  30. for _, sess := range s.store {
  31. s.maybeSendMessage(ctx, msg, sess)
  32. }
  33. }
  34. func (s *InMemorySessionManager) maybeSendMessage(ctx context.Context, msg oscar.XMessage, sess *user.Session) {
  35. switch sess.SendMessage(msg) {
  36. case user.SessSendClosed:
  37. s.logger.WarnContext(ctx, "can't send notification because the user's session is closed", "recipient", sess.ScreenName(), "message", msg)
  38. case user.SessQueueFull:
  39. s.logger.WarnContext(ctx, "can't send notification because queue is full", "recipient", sess.ScreenName(), "message", msg)
  40. sess.Close()
  41. }
  42. }
  43. func (s *InMemorySessionManager) Empty() bool {
  44. s.mapMutex.RLock()
  45. defer s.mapMutex.RUnlock()
  46. return len(s.store) == 0
  47. }
  48. func (s *InMemorySessionManager) Participants() []*user.Session {
  49. s.mapMutex.RLock()
  50. defer s.mapMutex.RUnlock()
  51. var sessions []*user.Session
  52. for _, sess := range s.store {
  53. sessions = append(sessions, sess)
  54. }
  55. return sessions
  56. }
  57. func (s *InMemorySessionManager) BroadcastExcept(ctx context.Context, except *user.Session, msg oscar.XMessage) {
  58. s.mapMutex.RLock()
  59. defer s.mapMutex.RUnlock()
  60. for _, sess := range s.store {
  61. if sess == except {
  62. continue
  63. }
  64. s.maybeSendMessage(ctx, msg, sess)
  65. }
  66. }
  67. func (s *InMemorySessionManager) Retrieve(ID string) (*user.Session, bool) {
  68. s.mapMutex.RLock()
  69. defer s.mapMutex.RUnlock()
  70. sess, found := s.store[ID]
  71. return sess, found
  72. }
  73. func (s *InMemorySessionManager) RetrieveByScreenName(screenName string) (*user.Session, error) {
  74. s.mapMutex.RLock()
  75. defer s.mapMutex.RUnlock()
  76. for _, sess := range s.store {
  77. if screenName == sess.ScreenName() {
  78. return sess, nil
  79. }
  80. }
  81. return nil, fmt.Errorf("%w: %s", ErrSessNotFound, screenName)
  82. }
  83. func (s *InMemorySessionManager) retrieveByScreenNames(screenNames []string) []*user.Session {
  84. s.mapMutex.RLock()
  85. defer s.mapMutex.RUnlock()
  86. var ret []*user.Session
  87. for _, sn := range screenNames {
  88. for _, sess := range s.store {
  89. if sn == sess.ScreenName() {
  90. ret = append(ret, sess)
  91. }
  92. }
  93. }
  94. return ret
  95. }
  96. func (s *InMemorySessionManager) SendToScreenName(ctx context.Context, screenName string, msg oscar.XMessage) {
  97. sess, err := s.RetrieveByScreenName(screenName)
  98. if err != nil {
  99. s.logger.WarnContext(ctx, "can't send notification because user is not online", "recipient", screenName, "message", msg)
  100. return
  101. }
  102. s.maybeSendMessage(ctx, msg, sess)
  103. }
  104. func (s *InMemorySessionManager) BroadcastToScreenNames(ctx context.Context, screenNames []string, msg oscar.XMessage) {
  105. for _, sess := range s.retrieveByScreenNames(screenNames) {
  106. s.maybeSendMessage(ctx, msg, sess)
  107. }
  108. }
  109. func (s *InMemorySessionManager) NewSessionWithSN(sessID string, screenName string) *user.Session {
  110. s.mapMutex.Lock()
  111. defer s.mapMutex.Unlock()
  112. // Only allow one session at a time per screen name. A session may already
  113. // exist because:
  114. // 1) the user is signing on using an already logged-on screen name.
  115. // 2) the session might be orphaned due to an undetected client
  116. // disconnection.
  117. for _, sess := range s.store {
  118. if screenName == sess.ScreenName() {
  119. sess.Close()
  120. delete(s.store, sess.ID())
  121. break
  122. }
  123. }
  124. sess := user.NewSession()
  125. sess.SetID(sessID)
  126. sess.SetScreenName(screenName)
  127. s.store[sess.ID()] = sess
  128. return sess
  129. }
  130. func (s *InMemorySessionManager) Remove(sess *user.Session) {
  131. s.mapMutex.Lock()
  132. defer s.mapMutex.Unlock()
  133. delete(s.store, sess.ID())
  134. }
  135. type ChatRoom struct {
  136. CreateTime time.Time
  137. DetailLevel uint8
  138. Exchange uint16
  139. Cookie string
  140. InstanceNumber uint16
  141. Name string
  142. SessionManager
  143. }
  144. func (c ChatRoom) TLVList() []oscar.TLV {
  145. return []oscar.TLV{
  146. oscar.NewTLV(0x00c9, uint16(15)),
  147. oscar.NewTLV(0x00ca, uint32(c.CreateTime.Unix())),
  148. oscar.NewTLV(0x00d1, uint16(1024)),
  149. oscar.NewTLV(0x00d2, uint16(100)),
  150. oscar.NewTLV(0x00d5, uint8(2)),
  151. oscar.NewTLV(0x006a, c.Name),
  152. oscar.NewTLV(0x00d3, c.Name),
  153. }
  154. }
  155. type ChatRegistry struct {
  156. store map[string]ChatRoom
  157. mapMutex sync.RWMutex
  158. }
  159. func NewChatRegistry() *ChatRegistry {
  160. return &ChatRegistry{
  161. store: make(map[string]ChatRoom),
  162. }
  163. }
  164. func (c *ChatRegistry) Register(room ChatRoom) {
  165. c.mapMutex.Lock()
  166. defer c.mapMutex.Unlock()
  167. c.store[room.Cookie] = room
  168. }
  169. func (c *ChatRegistry) Retrieve(chatID string) (ChatRoom, error) {
  170. c.mapMutex.RLock()
  171. defer c.mapMutex.RUnlock()
  172. sm, found := c.store[chatID]
  173. if !found {
  174. return sm, errors.New("unable to find session manager for chat")
  175. }
  176. return sm, nil
  177. }
  178. func (c *ChatRegistry) MaybeRemoveRoom(chatID string) {
  179. c.mapMutex.Lock()
  180. defer c.mapMutex.Unlock()
  181. room, found := c.store[chatID]
  182. if found && room.Empty() {
  183. delete(c.store, chatID)
  184. }
  185. }