aboutsummaryrefslogtreecommitdiff
path: root/hotline
diff options
context:
space:
mode:
Diffstat (limited to 'hotline')
-rw-r--r--hotline/ban.go3
-rw-r--r--hotline/presence.go20
-rw-r--r--hotline/server.go32
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