diff options
Diffstat (limited to 'hotline')
| -rw-r--r-- | hotline/ban.go | 3 | ||||
| -rw-r--r-- | hotline/presence.go | 20 | ||||
| -rw-r--r-- | hotline/server.go | 32 |
3 files changed, 37 insertions, 18 deletions
diff --git a/hotline/ban.go b/hotline/ban.go index 2713fbe..43988ab 100644 --- a/hotline/ban.go +++ b/hotline/ban.go @@ -9,13 +9,12 @@ import ( // BanDuration is the length of time for temporary bans. const BanDuration = 30 * time.Minute -// Redis key constants for ban management and online user tracking +// Redis key constants for ban management const ( RedisKeyBannedIPs = "mobius:banned:ips" RedisKeyBannedUsers = "mobius:banned:users" RedisKeyBannedNicknames = "mobius:banned:nicknames" RedisKeyTempBannedIPs = "mobius:temp_banned:ips:" - RedisKeyOnline = "mobius:online" ) type BanMgr interface { diff --git a/hotline/presence.go b/hotline/presence.go new file mode 100644 index 0000000..7e41357 --- /dev/null +++ b/hotline/presence.go @@ -0,0 +1,20 @@ +package hotline + +// PresenceTracker receives best-effort notifications about user session lifecycle +// events so an external system (e.g. Redis) can maintain a list of online users. +// +// It is optional: when a Server has no PresenceTracker, online presence is derived +// from the in-memory ClientManager instead. Implementations must be safe for +// concurrent use by multiple goroutines. +type PresenceTracker interface { + // UserConnected is called after a successful login, before the nickname is known. + UserConnected(login, ip string) + + // UserRenamed is called when a user's nickname is set or changed. oldNickname is + // empty the first time a nickname is set (e.g. the 1.2.3 login flow or TranAgreed). + UserRenamed(login, oldNickname, newNickname, ip string) + + // UserDisconnected is called when a user's session ends. nickname is empty if the + // user disconnected before setting one. + UserDisconnected(login, nickname, ip string) +} diff --git a/hotline/server.go b/hotline/server.go index 8a530c2..020f878 100644 --- a/hotline/server.go +++ b/hotline/server.go @@ -16,7 +16,6 @@ import ( "sync" "time" - "github.com/redis/go-redis/v9" "golang.org/x/text/encoding" "golang.org/x/text/encoding/charmap" "golang.org/x/time/rate" @@ -62,7 +61,9 @@ type Server struct { MessageBoard io.ReadWriteSeeker - Redis *redis.Client + // Presence optionally records user session lifecycle events for an external online-user + // list. When nil, online presence is derived from ClientMgr. + Presence PresenceTracker // TrackerRegistrar handles tracker registration (injectable for testing) TrackerRegistrar TrackerRegistrar @@ -133,6 +134,13 @@ func WithTrackerRegistrar(registrar TrackerRegistrar) func(s *Server) { } } +// WithPresenceTracker optionally sets a PresenceTracker to record user session lifecycle events. +func WithPresenceTracker(p PresenceTracker) func(s *Server) { + return func(s *Server) { + s.Presence = p + } +} + // WithTLS optionally enables TLS support on the specified port. func WithTLS(tlsConfig *tls.Config, port int) func(s *Server) { return func(s *Server) { @@ -577,18 +585,14 @@ func (s *Server) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser // transaction ordering and prevents interleaved writes. go c.writeLoop() - // TODO: refactor this into a connection manager interface, maybe? - if s.Redis != nil { - s.Redis.SAdd(context.Background(), RedisKeyOnline, login+"::"+ipAddr) + if s.Presence != nil { + s.Presence.UserConnected(login, ipAddr) } // Remove the client from the list of connected clients when they disconnect defer func() { - if s.Redis != nil { - s.Redis.SRem(context.Background(), RedisKeyOnline, login+"::"+ipAddr) - if userName := c.GetUserName(); len(userName) != 0 { - s.Redis.SRem(context.Background(), RedisKeyOnline, login+":"+string(userName)+":"+ipAddr) - } + if s.Presence != nil { + s.Presence.UserDisconnected(login, string(c.GetUserName()), ipAddr) } c.Disconnect() }() @@ -665,12 +669,8 @@ func (s *Server) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser c.Logger = c.Logger.With("name", string(userName)) c.Logger.Info("Login successful") - // Update the Redis set with the new information - if s.Redis != nil { - // Remove old entry (login::ip) - s.Redis.SRem(context.Background(), RedisKeyOnline, login+"::"+ipAddr) - // Add new entry with login, nickname, ip - s.Redis.SAdd(context.Background(), RedisKeyOnline, login+":"+string(userName)+":"+ipAddr) + if s.Presence != nil { + s.Presence.UserRenamed(login, "", string(userName), ipAddr) } // Notify other clients on the server that the new user has logged in. For 1.5+ clients we don't have this |