diff --git a/README.FRONTEND b/README.FRONTEND index 35986d3..274f418 100644 --- a/README.FRONTEND +++ b/README.FRONTEND @@ -73,7 +73,7 @@ will trigger. There, you update your user interface and request incoming streams: ```javascript -serverConnection.onjoined = function(kind, group, perms, status, message) { +serverConnection.onjoined = function(kind, group, perms, status, data, message) { switch(kind) { case 'join': this.request({'':['audio','video']}); diff --git a/README.PROTOCOL b/README.PROTOCOL index 327f0e4..5fc8857 100644 --- a/README.PROTOCOL +++ b/README.PROTOCOL @@ -108,7 +108,7 @@ The `join` message requests that the sender join or leave a group: group: group, username: username, password: password, - status: status + data: data } ``` @@ -125,6 +125,7 @@ its permissions or in the recommended RTC configuration. username: username, permissions: permissions, status: status, + data: data, rtcConfiguration: RTCConfiguration } ``` @@ -337,7 +338,7 @@ A user action requests that the server act upon a user. } ``` Currently defined kinds include `op`, `unop`, `present`, `unpresent`, -`kick` and `setstatus`. +`kick` and `setdata`. Finally, a group action requests that the server act on the current group. @@ -352,5 +353,5 @@ Finally, a group action requests that the server act on the current group. ``` Currently defined kinds include `clearchat` (not to be confused with the -`clearchat` user message), `lock`, `unlock`, `record`, `unrecord` and -`subgroups`. +`clearchat` user message), `lock`, `unlock`, `record`, `unrecord`, +`subgroups` and `setdata`. diff --git a/diskwriter/diskwriter.go b/diskwriter/diskwriter.go index 2ad78ab..c585f11 100644 --- a/diskwriter/diskwriter.go +++ b/diskwriter/diskwriter.go @@ -69,11 +69,11 @@ func (client *Client) Permissions() group.ClientPermissions { } } -func (client *Client) Status() map[string]interface{} { +func (client *Client) Data() map[string]interface{} { return nil } -func (client *Client) PushClient(group, kind, id, username string, permissions group.ClientPermissions, status map[string]interface{}) error { +func (client *Client) PushClient(group, kind, id, username string, permissions group.ClientPermissions, data map[string]interface{}) error { return nil } diff --git a/group/client.go b/group/client.go index 7b0d0d3..fdadc91 100644 --- a/group/client.go +++ b/group/client.go @@ -100,10 +100,10 @@ type Client interface { Username() string Permissions() ClientPermissions SetPermissions(ClientPermissions) - Status() map[string]interface{} + Data() map[string]interface{} PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error RequestConns(target Client, g *Group, id string) error Joined(group, kind string) error - PushClient(group, kind, id, username string, permissions ClientPermissions, status map[string]interface{}) error + PushClient(group, kind, id, username string, permissions ClientPermissions, data map[string]interface{}) error Kick(id, user, message string) error } diff --git a/group/group.go b/group/group.go index e54e1c5..fd01374 100644 --- a/group/group.go +++ b/group/group.go @@ -76,6 +76,7 @@ type Group struct { clients map[string]Client history []ChatHistoryEntry timestamp time.Time + data map[string]interface{} } func (g *Group) Name() string { @@ -107,6 +108,32 @@ func (g *Group) SetLocked(locked bool, message string) { } } +func (g *Group) Data() map[string]interface{} { + g.mu.Lock() + defer g.mu.Unlock() + return g.data +} + +func (g *Group) UpdateData(d map[string]interface{}) { + g.mu.Lock() + if g.data == nil { + g.data = make(map[string]interface{}) + } + for k, v := range d { + if v == nil { + delete(g.data, k) + } else { + g.data[k] = v + } + } + clients := g.getClientsUnlocked(nil) + g.mu.Unlock() + + for _, c := range clients { + c.Joined(g.Name(), "change") + } +} + func (g *Group) Description() *Description { g.mu.Lock() defer g.mu.Unlock() @@ -577,12 +604,12 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) id := c.Id() u := c.Username() p := c.Permissions() - s := c.Status() + s := c.Data() c.PushClient(g.Name(), "add", c.Id(), u, p, s) for _, cc := range clients { pp := cc.Permissions() c.PushClient( - g.Name(), "add", cc.Id(), cc.Username(), pp, cc.Status(), + g.Name(), "add", cc.Id(), cc.Username(), pp, cc.Data(), ) cc.PushClient(g.Name(), "add", id, u, p, s) } @@ -1070,7 +1097,7 @@ type Status struct { ClientCount *int `json:"clientCount,omitempty"` } -func GetStatus(g *Group, authentified bool) Status { +func (g *Group) Status (authentified bool) Status { desc := g.Description() d := Status{ Name: g.name, @@ -1092,7 +1119,7 @@ func GetPublic() []Status { gs := make([]Status, 0) Range(func(g *Group) bool { if g.Description().Public { - gs = append(gs, GetStatus(g, false)) + gs = append(gs, g.Status(false)) } return true }) diff --git a/rtpconn/webclient.go b/rtpconn/webclient.go index 461b472..532cd7b 100644 --- a/rtpconn/webclient.go +++ b/rtpconn/webclient.go @@ -57,7 +57,7 @@ type webClient struct { id string username string permissions group.ClientPermissions - status map[string]interface{} + data map[string]interface{} requested map[string][]string done chan struct{} writeCh chan interface{} @@ -90,17 +90,17 @@ func (c *webClient) Permissions() group.ClientPermissions { return c.permissions } -func (c *webClient) Status() map[string]interface{} { - return c.status +func (c *webClient) Data() map[string]interface{} { + return c.data } func (c *webClient) SetPermissions(perms group.ClientPermissions) { c.permissions = perms } -func (c *webClient) PushClient(group, kind, id, username string, permissions group.ClientPermissions, status map[string]interface{}) error { +func (c *webClient) PushClient(group, kind, id, username string, permissions group.ClientPermissions, data map[string]interface{}) error { return c.action(pushClientAction{ - group, kind, id, username, permissions, status, + group, kind, id, username, permissions, data, }) } @@ -115,7 +115,8 @@ type clientMessage struct { Password string `json:"password,omitempty"` Privileged bool `json:"privileged,omitempty"` Permissions *group.ClientPermissions `json:"permissions,omitempty"` - Status interface{} `json:"status,omitempty"` + Status *group.Status `json:"status,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` Group string `json:"group,omitempty"` Value interface{} `json:"value,omitempty"` NoEcho bool `json:"noecho,omitempty"` @@ -899,7 +900,7 @@ type pushClientAction struct { id string username string permissions group.ClientPermissions - status map[string]interface{} + data map[string]interface{} } type permissionsChangedAction struct{} @@ -1106,14 +1107,17 @@ func handleAction(c *webClient, a interface{}) error { Id: a.id, Username: a.username, Permissions: &a.permissions, - Status: a.status, + Data: a.data, }) case joinedAction: - var status interface{} + var status *group.Status + var data map[string]interface{} if a.group != "" { g := group.Get(a.group) if g != nil { - status = group.GetStatus(g, true) + s := g.Status(true) + status = &s + data = g.Data() } } perms := c.permissions @@ -1124,6 +1128,7 @@ func handleAction(c *webClient, a interface{}) error { Username: c.username, Permissions: &perms, Status: status, + Data: data, RTCConfiguration: ice.ICEConfiguration(), }) case permissionsChangedAction: @@ -1132,13 +1137,14 @@ func handleAction(c *webClient, a interface{}) error { return errors.New("Permissions changed in no group") } perms := c.permissions + status := g.Status(true) c.write(clientMessage{ Type: "joined", Kind: "change", Group: g.Name(), Username: c.username, Permissions: &perms, - Status: group.GetStatus(g, true), + Status: &status, RTCConfiguration: ice.ICEConfiguration(), }) if !c.permissions.Present { @@ -1157,12 +1163,12 @@ func handleAction(c *webClient, a interface{}) error { } id := c.Id() user := c.Username() - s := c.Status() + d := c.Data() clients := g.GetClients(nil) go func(clients []group.Client) { for _, cc := range clients { cc.PushClient( - g.Name(), "change", id, user, perms, s, + g.Name(), "change", id, user, perms, d, ) } }(clients) @@ -1214,7 +1220,7 @@ func leaveGroup(c *webClient) { group.DelClient(c) c.permissions = group.ClientPermissions{} - c.status = nil + c.data = nil c.requested = make(map[string][]string) c.group = nil } @@ -1321,15 +1327,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ) } c.username = m.Username - if m.Status != nil { - s, ok := m.Status.(map[string]interface{}) - if !ok { - return group.ProtocolError( - "bad type for status", - ) - } - c.status = s - } + c.data = m.Data g, err := group.AddClient(m.Group, c, group.ClientCredentials{ Username: m.Username, @@ -1615,6 +1613,17 @@ func handleClientMessage(c *webClient, m clientMessage) error { Time: group.ToJSTime(time.Now()), Value: s, }) + case "setdata": + if !c.permissions.Op { + return c.error(group.UserError("not authorised")) + } + data, ok := m.Value.(map[string]interface{}) + if !ok { + return c.error(group.UserError( + "Bad value in setdata", + )) + } + g.UpdateData(data) default: return group.ProtocolError("unknown group action") } @@ -1645,35 +1654,35 @@ func handleClientMessage(c *webClient, m clientMessage) error { if err != nil { return c.error(err) } - case "setstatus": + case "setdata": if m.Dest != c.Id() { return c.error(group.UserError("not authorised")) } - s, ok := m.Value.(map[string]interface{}) + data, ok := m.Value.(map[string]interface{}) if !ok { return c.error(group.UserError( - "Bad value in setstatus", + "Bad value in setdata", )) } - if c.status == nil { - c.status = make(map[string]interface{}) + if c.data == nil { + c.data = make(map[string]interface{}) } - for k, v := range s { + for k, v := range data { if v == nil { - delete(c.status, k) + delete(c.data, k) } else { - c.status[k] = v + c.data[k] = v } } id := c.Id() user := c.Username() perms := c.Permissions() - status := c.Status() + data = c.Data() go func(clients []group.Client) { for _, cc := range clients { cc.PushClient( g.Name(), "change", - id, user, perms, status, + id, user, perms, data, ) } }(g.GetClients(nil)) diff --git a/static/galene.js b/static/galene.js index 192cee4..0f631f8 100644 --- a/static/galene.js +++ b/static/galene.js @@ -1969,7 +1969,7 @@ function addUser(id, userinfo) { user.id = 'user-' + id; user.classList.add("user-p"); user.textContent = userinfo.username ? userinfo.username : '(anon)'; - if (userinfo.status.raisehand) + if (userinfo.data.raisehand) user.classList.add('user-status-raisehand'); else user.classList.remove('user-status-raisehand'); @@ -2001,7 +2001,7 @@ function changeUser(id, userinfo) { return; } user.textContent = userinfo.username ? userinfo.username : '(anon)'; - if (userinfo.status.raisehand) + if (userinfo.data.raisehand) user.classList.add('user-status-raisehand'); else user.classList.remove('user-status-raisehand'); @@ -2084,9 +2084,10 @@ function setTitle(title) { * @param {string} group * @param {Object} perms * @param {Object} status + * @param {Object} data * @param {string} message */ -async function gotJoined(kind, group, perms, status, message) { +async function gotJoined(kind, group, perms, status, data, message) { let present = presentRequested; presentRequested = null; @@ -2698,7 +2699,7 @@ commands.raise = { description: 'raise hand', f: (c, r) => { serverConnection.userAction( - "setstatus", serverConnection.id, {"raisehand": true}, + "setdata", serverConnection.id, {"raisehand": true}, ); } } @@ -2707,7 +2708,7 @@ commands.unraise = { description: 'unraise hand', f: (c, r) => { serverConnection.userAction( - "setstatus", serverConnection.id, {"raisehand": null}, + "setdata", serverConnection.id, {"raisehand": null}, ); } } diff --git a/static/protocol.js b/static/protocol.js index 766be50..e8d65f2 100644 --- a/static/protocol.js +++ b/static/protocol.js @@ -64,7 +64,7 @@ function newLocalId() { * @typedef {Object} user * @property {string} username * @property {Object} permissions - * @property {Object} status + * @property {Object} data * @property {Object>} down */ @@ -164,7 +164,7 @@ function ServerConnection() { * * kind is one of 'join', 'fail', 'change' or 'leave'. * - * @type{(this: ServerConnection, kind: string, group: string, permissions: Object, status: Object, message: string) => void} + * @type{(this: ServerConnection, kind: string, group: string, permissions: Object, status: Object, data: Object, message: string) => void} */ this.onjoined = null; /** @@ -208,6 +208,7 @@ function ServerConnection() { * @property {boolean} [privileged] * @property {Object} [permissions] * @property {Object} [status] + * @property {Object} [data] * @property {string} [group] * @property {unknown} [value] * @property {boolean} [noecho] @@ -284,7 +285,7 @@ ServerConnection.prototype.connect = async function(url) { sc.onuser.call(sc, id, 'delete'); } if(sc.group && sc.onjoined) - sc.onjoined.call(sc, 'leave', sc.group, {}, {}, ''); + sc.onjoined.call(sc, 'leave', sc.group, {}, {}, {}, ''); sc.group = null; sc.username = null; if(sc.onclose) @@ -336,7 +337,7 @@ ServerConnection.prototype.connect = async function(url) { if(sc.onjoined) sc.onjoined.call(sc, m.kind, m.group, m.permissions || {}, - m.status, + m.status, m.data, m.value || null); break; case 'user': @@ -347,7 +348,7 @@ ServerConnection.prototype.connect = async function(url) { sc.users[m.id] = { username: m.username, permissions: m.permissions || {}, - status: m.status || {}, + data: m.data || {}, down: {}, }; break; @@ -357,13 +358,13 @@ ServerConnection.prototype.connect = async function(url) { sc.users[m.id] = { username: m.username, permissions: m.permissions || {}, - status: m.status || {}, + data: m.data || {}, down: {}, }; } else { sc.users[m.id].username = m.username; sc.users[m.id].permissions = m.permissions || {}; - sc.users[m.id].status = m.status || {}; + sc.users[m.id].data = m.data || {}; } break; case 'delete': diff --git a/webserver/webserver.go b/webserver/webserver.go index 537347b..a97ea62 100644 --- a/webserver/webserver.go +++ b/webserver/webserver.go @@ -339,7 +339,7 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) { return } - d := group.GetStatus(g, false) + d := g.Status(false) w.Header().Set("content-type", "application/json") w.Header().Set("cache-control", "no-cache")