282 lines
7.9 KiB
Go
282 lines
7.9 KiB
Go
package services
|
|
|
|
import (
|
|
"git.saqut.com/saqut/mwse/internal/protocol"
|
|
"git.saqut.com/saqut/mwse/internal/ws"
|
|
)
|
|
|
|
func registerRoom(hub *ws.Hub) {
|
|
// Every client gets a private room named after its own id on connect, and is
|
|
// ejected from all rooms on disconnect.
|
|
hub.OnConnect(func(c *ws.Client) {
|
|
room := ws.NewRoom(hub)
|
|
room.ID = c.ID
|
|
room.AccessType = "private"
|
|
room.JoinType = "notify"
|
|
room.Description = "Private room"
|
|
room.Name = "Your Room | " + c.ID
|
|
room.OwnerID = c.ID
|
|
room.Publish()
|
|
room.Join(c)
|
|
})
|
|
|
|
hub.OnDisconnect(func(c *ws.Client) {
|
|
if room, ok := hub.Room(c.ID); ok {
|
|
room.Eject(c)
|
|
}
|
|
for _, rid := range c.Rooms() {
|
|
if r, ok := hub.Room(rid); ok {
|
|
r.Eject(c)
|
|
}
|
|
}
|
|
})
|
|
|
|
hub.Register("myroom-info", func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.Room(c.ID)
|
|
if !ok {
|
|
return fail("NOT-FOUND-ROOM")
|
|
}
|
|
return map[string]any{"status": "success", "room": room.ToJSON(false)}
|
|
})
|
|
|
|
hub.Register("room-peers", func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.Room(m.Str("roomId"))
|
|
if !ok {
|
|
return map[string]any{"status": "fail"}
|
|
}
|
|
return map[string]any{"status": "success", "peers": ids(room.FilterPeers(toMap(m.Get("filter"))))}
|
|
})
|
|
|
|
hub.Register("room/peer-count", func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.Room(m.Str("roomId"))
|
|
if !ok {
|
|
return map[string]any{"status": "fail"}
|
|
}
|
|
return map[string]any{"status": "success", "count": len(room.FilterPeers(toMap(m.Get("filter"))))}
|
|
})
|
|
|
|
hub.Register("room-info", func(c *ws.Client, m protocol.Message) any {
|
|
if room, ok := hub.RoomByName(m.Str("name")); ok {
|
|
return map[string]any{"status": "success", "room": room.ToJSON(false)}
|
|
}
|
|
return fail("NOT-FOUND-ROOM")
|
|
})
|
|
|
|
hub.Register("joinedrooms", func(c *ws.Client, m protocol.Message) any {
|
|
var rooms []map[string]any
|
|
for _, rid := range c.Rooms() {
|
|
if r, ok := hub.Room(rid); ok {
|
|
rooms = append(rooms, r.ToJSON(false))
|
|
}
|
|
}
|
|
return rooms
|
|
})
|
|
|
|
hub.Register("closeroom", func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.Room(m.Str("roomId"))
|
|
if !ok {
|
|
return map[string]any{"status": "fail"}
|
|
}
|
|
if room.OwnerID == c.ID {
|
|
room.Down()
|
|
return success()
|
|
}
|
|
return map[string]any{"status": "fail"}
|
|
})
|
|
|
|
hub.Register("create-room", func(c *ws.Client, m protocol.Message) any {
|
|
if msg := validateCreateRoom(m); msg != "" {
|
|
return map[string]any{"status": "fail", "messages": msg}
|
|
}
|
|
name := m.Str("name")
|
|
if _, exists := hub.RoomByName(name); exists {
|
|
return fail("ALREADY-EXISTS")
|
|
}
|
|
|
|
room := ws.NewRoom(hub)
|
|
room.AccessType = m.Str("accessType")
|
|
room.NotifyActionInvite = m.Truthy("notifyActionInvite")
|
|
room.NotifyActionJoined = m.Truthy("notifyActionJoined")
|
|
room.NotifyActionEjected = m.Truthy("notifyActionEjected")
|
|
room.JoinType = m.Str("joinType")
|
|
room.Description = m.Str("description")
|
|
room.Name = name
|
|
room.OwnerID = c.ID
|
|
if cred := m.Str("credential"); cred != "" {
|
|
room.Credential = sha256hex(cred)
|
|
}
|
|
room.Publish()
|
|
room.Join(c)
|
|
return map[string]any{"status": "success", "room": room.ToJSON(false)}
|
|
})
|
|
|
|
hub.Register("joinroom", func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.RoomByName(m.Str("name"))
|
|
if !ok {
|
|
return fail("NOT-FOUND-ROOM")
|
|
}
|
|
|
|
fetchInfo := func(resp map[string]any) map[string]any {
|
|
if m.Truthy("autoFetchInfo") {
|
|
resp["info"] = room.Info()
|
|
}
|
|
return resp
|
|
}
|
|
|
|
switch room.JoinType {
|
|
case "lock":
|
|
return fail("LOCKED-ROOM")
|
|
case "password":
|
|
if room.Credential == sha256hex(m.Str("credential")) {
|
|
room.Join(c)
|
|
return fetchInfo(map[string]any{"status": "success", "room": room.ToJSON(false)})
|
|
}
|
|
return map[string]any{"status": "fail", "message": "WRONG-PASSWORD", "area": "credential"}
|
|
case "free":
|
|
room.Join(c)
|
|
return fetchInfo(map[string]any{"status": "success", "room": room.ToJSON(false)})
|
|
case "invite":
|
|
room.AddWaiting(c.ID)
|
|
invite := map[string]any{"id": c.ID}
|
|
if room.NotifyActionInvite {
|
|
room.Broadcast("room/invite", invite, "", nil)
|
|
} else if owner, ok := hub.Client(room.OwnerID); ok {
|
|
owner.Signal("room/invite", invite)
|
|
}
|
|
return map[string]any{"status": "success", "message": "INVITE-REQUESTED"}
|
|
}
|
|
return fail("NOT-FOUND-ROOM")
|
|
})
|
|
|
|
hub.Register("ejectroom", func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.Room(m.Str("roomId"))
|
|
if !ok {
|
|
return fail("NOT-FOUND-ROOM")
|
|
}
|
|
if !room.Has(c.ID) {
|
|
return fail("ALREADY-ROOM-OUT")
|
|
}
|
|
room.Eject(c)
|
|
return success()
|
|
})
|
|
|
|
hub.Register("accept/invite-room", inviteDecision(hub, true))
|
|
hub.Register("reject/invite-room", inviteDecision(hub, false))
|
|
|
|
hub.Register("room/list", func(c *ws.Client, m protocol.Message) any {
|
|
var rooms []map[string]any
|
|
for _, room := range hub.Rooms() {
|
|
if room.AccessType == "public" {
|
|
rooms = append(rooms, map[string]any{
|
|
"name": room.Name,
|
|
"joinType": room.JoinType,
|
|
"description": room.Description,
|
|
"id": room.ID,
|
|
})
|
|
}
|
|
}
|
|
return map[string]any{"type": "public/rooms", "rooms": rooms}
|
|
})
|
|
|
|
hub.Register("room/info", func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.Room(m.Str("roomId"))
|
|
if !ok {
|
|
return fail("NOT-FOUND-ROOM")
|
|
}
|
|
if !c.InRoom(room.ID) {
|
|
return fail("NO-JOINED-ROOM")
|
|
}
|
|
if name := m.Str("name"); name != "" {
|
|
v, _ := room.InfoValue(name)
|
|
return map[string]any{"status": "success", "value": v}
|
|
}
|
|
return map[string]any{"status": "success", "value": room.Info()}
|
|
})
|
|
|
|
hub.Register("room/setinfo", func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.Room(m.Str("roomId"))
|
|
if !ok {
|
|
return fail("NOT-FOUND-ROOM")
|
|
}
|
|
if !c.InRoom(room.ID) {
|
|
return fail("NO-JOINED-ROOM")
|
|
}
|
|
name := m.Str("name")
|
|
value := m.Get("value")
|
|
room.SetInfo(name, value)
|
|
room.Broadcast(
|
|
"room/info",
|
|
map[string]any{"name": name, "value": value, "roomId": room.ID},
|
|
c.ID,
|
|
(*ws.Client).RoomInfoNotifiable,
|
|
)
|
|
return success()
|
|
})
|
|
}
|
|
|
|
// inviteDecision builds the accept/reject invite handlers. The original code was
|
|
// non-functional here (it called Array methods on a Set and inverted the joinType
|
|
// check); this implements the intended flow: only the rooms a member belongs to,
|
|
// only invite rooms, only ids actually on the waiting list.
|
|
func inviteDecision(hub *ws.Hub, accept bool) handler {
|
|
return func(c *ws.Client, m protocol.Message) any {
|
|
room, ok := hub.Room(m.Str("roomId"))
|
|
if !ok {
|
|
return fail("NOT-FOUND-ROOM")
|
|
}
|
|
if !c.InRoom(room.ID) {
|
|
return fail("FORBIDDEN-INVITE-ACTIONS")
|
|
}
|
|
if room.JoinType != "invite" {
|
|
return fail("INVALID-DATA")
|
|
}
|
|
clientID := m.Str("clientId")
|
|
if !room.IsWaiting(clientID) {
|
|
return fail("NO-WAITING-INVITED")
|
|
}
|
|
joinClient, ok := hub.Client(clientID)
|
|
if !ok {
|
|
room.RemoveWaiting(clientID)
|
|
return fail("NO-CLIENT")
|
|
}
|
|
room.RemoveWaiting(clientID)
|
|
|
|
if accept {
|
|
room.Join(joinClient)
|
|
joinClient.Signal("room/invite/status", map[string]any{"status": "accepted"})
|
|
} else {
|
|
room.Broadcast("room/invite/status", map[string]any{"id": clientID, "roomId": room.ID}, "", nil)
|
|
joinClient.Signal("room/invite/status", map[string]any{"status": "rejected"})
|
|
}
|
|
return success()
|
|
}
|
|
}
|
|
|
|
// validateCreateRoom checks the create-room payload against the same constraints
|
|
// the original joi schema described. It returns an empty string when valid.
|
|
func validateCreateRoom(m protocol.Message) string {
|
|
if !m.Has("type") {
|
|
return "type is required"
|
|
}
|
|
switch m.Str("accessType") {
|
|
case "public", "private":
|
|
default:
|
|
return "accessType must be public or private"
|
|
}
|
|
switch m.Str("joinType") {
|
|
case "free", "invite", "password", "lock":
|
|
default:
|
|
return "joinType must be one of free, invite, password, lock"
|
|
}
|
|
if !m.Has("notifyActionInvite") || !m.Has("notifyActionJoined") || !m.Has("notifyActionEjected") {
|
|
return "notify flags are required"
|
|
}
|
|
if m.Str("description") == "" {
|
|
return "description is required"
|
|
}
|
|
if m.Str("name") == "" {
|
|
return "name is required"
|
|
}
|
|
return ""
|
|
}
|