package services import ( "git.saqut.com/saqut/mwse/internal/protocol" "git.saqut.com/saqut/mwse/internal/ws" ) // ---- relationship helpers ------------------------------------------------ // // "Secure" reachability comes in two forms: a mutual pairing, or co-membership of // a room. These helpers centralise the checks the original Client.isSecure / // getSucureClients spread across files (and fix their bugs). // isPaired reports a *mutual* pairing between a and b. func isPaired(a, b *ws.Client) bool { return a.HasPair(b.ID) && b.HasPair(a.ID) } // shareRoom reports whether a and b are members of at least one common room. func shareRoom(hub *ws.Hub, a, b *ws.Client) bool { for _, rid := range a.Rooms() { if r, ok := hub.Room(rid); ok && r.Has(b.ID) { return true } } return false } // isSecure reports whether a may address the client with id peerID. func isSecure(hub *ws.Hub, a *ws.Client, peerID string) bool { peer, ok := hub.Client(peerID) if !ok { return false } return isPaired(a, peer) || shareRoom(hub, a, peer) } // secureClients returns the connected peers c may exchange info with: those c has // paired toward, and those sharing a room with c. func secureClients(hub *ws.Hub, c *ws.Client) (pairs, roompairs map[string]*ws.Client) { pairs = make(map[string]*ws.Client) for _, id := range c.Pairs() { if pc, ok := hub.Client(id); ok { pairs[id] = pc } } roompairs = make(map[string]*ws.Client) for _, rid := range c.Rooms() { r, ok := hub.Room(rid) if !ok { continue } for _, m := range r.Members() { if m.ID == c.ID { continue } roompairs[m.ID] = m } } return pairs, roompairs } func registerAuth(hub *ws.Hub) { // On disconnect: tell secure peers we are gone, then remove EVERY pairing edge // that references this client (both directions) so no stale id is ever left on // a surviving peer. Using the outgoing+incoming indexes keeps this O(degree), // not O(all clients), and prevents unbounded growth under churn. hub.OnDisconnect(func(c *ws.Client) { pairs, roompairs := secureClients(hub, c) notified := make(map[string]bool) notify := func(set map[string]*ws.Client) { for id, peer := range set { if notified[id] { continue } notified[id] = true peer.Signal("peer/disconnect", map[string]any{"id": c.ID}) } } notify(pairs) notify(roompairs) cleaned := make(map[string]bool) for _, peerID := range append(c.Pairs(), c.PairedBy()...) { if cleaned[peerID] { continue } cleaned[peerID] = true if peer, ok := hub.Client(peerID); ok { peer.ForgetPeer(c.ID) } } }) hub.Register("auth/pair-system", func(c *ws.Client, m protocol.Message) any { switch m.Str("value") { case "everybody": c.SetRequiredPair(true) return success() case "disable": c.SetRequiredPair(false) return success() } return fail("INVALID_VALUE") }) hub.Register("my/socketid", func(c *ws.Client, m protocol.Message) any { return c.ID }) hub.Register("auth/public", func(c *ws.Client, m protocol.Message) any { c.SetRequiredPair(false) return map[string]any{"value": "success", "mode": "public"} }) hub.Register("auth/private", func(c *ws.Client, m protocol.Message) any { c.SetRequiredPair(true) return map[string]any{"value": "success", "mode": "private"} }) // request/pair: ask `to` to pair with us. We record our side of the edge and // notify the target; they complete it with accept/pair. hub.Register("request/pair", func(c *ws.Client, m protocol.Message) any { to := m.Str("to") target, ok := hub.Client(to) if !ok { return fail("CLIENT_NOT_FOUND") } if isPaired(c, target) { return map[string]any{"status": "success", "message": "ALREADY-PAIRED"} } if c.HasPair(to) { return fail("ALREADY-REQUESTED") } c.AddPair(target) target.Signal("request/pair", map[string]any{"from": c.ID, "info": c.Info()}) return map[string]any{"status": "success", "message": "REQUESTED"} }) // accept/pair: complete a pairing the peer `to` requested from us. hub.Register("accept/pair", func(c *ws.Client, m protocol.Message) any { to := m.Str("to") requester, ok := hub.Client(to) if !ok { return fail("CLIENT_NOT_FOUND") } if isPaired(c, requester) { return map[string]any{"status": "success", "message": "ALREADY-PAIRED"} } if !requester.HasPair(c.ID) { return fail("NOT_REQUESTED_PAIR") } c.AddPair(requester) requester.Signal("accepted/pair", map[string]any{"from": c.ID, "info": c.Info()}) return success() }) // reject/pair and end/pair both tear the edge down in both directions and // notify the other side. teardown := func(c *ws.Client, m protocol.Message) any { to := m.Str("to") other, ok := hub.Client(to) if !ok { return fail("CLIENT_NOT_FOUND") } c.RemovePair(other) other.RemovePair(c) other.Signal("end/pair", map[string]any{"from": c.ID}) return success() } hub.Register("reject/pair", teardown) hub.Register("end/pair", teardown) hub.Register("pair/list", func(c *ws.Client, m protocol.Message) any { var list []string for _, id := range c.Pairs() { if other, ok := hub.Client(id); ok && other.HasPair(c.ID) { list = append(list, id) } } return map[string]any{"type": "pair/list", "value": list} }) hub.Register("is/reachable", func(c *ws.Client, m protocol.Message) any { to := m.Str("to") other, ok := hub.Client(to) if !ok { return false } if other.RequiredPair() && !other.HasPair(c.ID) { return false } return true }) // auth/info: set our metadata and push the change to every secure peer. hub.Register("auth/info", func(c *ws.Client, m protocol.Message) any { name := m.Str("name") value := m.Get("value") c.SetInfo(name, value) pairs, roompairs := secureClients(hub, c) payload := map[string]any{"from": c.ID, "name": name, "value": value} for _, peer := range pairs { peer.Signal("pair/info", payload) } for _, peer := range roompairs { peer.Signal("pair/info", payload) } return success() }) hub.Register("peer/info", func(c *ws.Client, m protocol.Message) any { peerID := m.Str("peer") if !isSecure(hub, c, peerID) { return map[string]any{"status": "fail", "message": "unaccessible user"} } peer, _ := hub.Client(peerID) return map[string]any{"status": "success", "info": peer.Info()} }) }