1
Fork 0

Implement user statuses.

The server now maintains a set of statuses for each user that are not
interpreted by the server but communicated to the other members of the
group using 'user' messages.
This commit is contained in:
Juliusz Chroboczek 2021-04-28 14:45:45 +02:00
parent f0a39fca48
commit 3ba2394be7
7 changed files with 87 additions and 15 deletions

View File

@ -118,7 +118,8 @@ users a `user` message:
kind: 'add' or 'change' or 'delete', kind: 'add' or 'change' or 'delete',
id: id, id: id,
username: username, username: username,
permissions: permissions permissions: permissions,
status: status
} }
``` ```
@ -277,8 +278,8 @@ A user action requests that the server act upon a user.
value: value value: value
} }
``` ```
Currently defined kinds include `op`, `unop`, `present`, `unpresent`, and Currently defined kinds include `op`, `unop`, `present`, `unpresent`,
`kick`. `kick` and `setstatus`.
Finally, a group action requests that the server act on the current group. Finally, a group action requests that the server act on the current group.

View File

@ -71,7 +71,11 @@ func (client *Client) Permissions() group.ClientPermissions {
return group.ClientPermissions{} return group.ClientPermissions{}
} }
func (client *Client) PushClient(id, username string, permissions group.ClientPermissions, kind string) error { func (client *Client) Status() map[string]interface{} {
return nil
}
func (client *Client) PushClient(id, username string, permissions group.ClientPermissions, status map[string]interface{}, kind string) error {
return nil return nil
} }

View File

@ -98,8 +98,9 @@ type Client interface {
Challengeable Challengeable
Permissions() ClientPermissions Permissions() ClientPermissions
SetPermissions(ClientPermissions) SetPermissions(ClientPermissions)
Status() map[string]interface{}
OverridePermissions(*Group) bool OverridePermissions(*Group) bool
PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error
PushClient(id, username string, permissions ClientPermissions, kind string) error PushClient(id, username string, permissions ClientPermissions, status map[string]interface{}, kind string) error
Kick(id, user, message string) error Kick(id, user, message string) error
} }

View File

@ -488,10 +488,14 @@ func AddClient(group string, c Client) (*Group, error) {
id := c.Id() id := c.Id()
u := c.Username() u := c.Username()
p := c.Permissions() p := c.Permissions()
c.PushClient(c.Id(), u, p, "add") s := c.Status()
c.PushClient(c.Id(), u, p, s, "add")
for _, cc := range clients { for _, cc := range clients {
c.PushClient(cc.Id(), cc.Username(), cc.Permissions(), "add") c.PushClient(
cc.PushClient(id, u, p, "add") cc.Id(), cc.Username(), cc.Permissions(), cc.Status(),
"add",
)
cc.PushClient(id, u, p, s, "add")
} }
return g, nil return g, nil
@ -537,7 +541,7 @@ func DelClient(c Client) {
go func(clients []Client) { go func(clients []Client) {
for _, cc := range clients { for _, cc := range clients {
cc.PushClient(c.Id(), c.Username(), c.Permissions(), "delete") cc.PushClient(c.Id(), c.Username(), c.Permissions(), c.Status(), "delete")
} }
}(clients) }(clients)

View File

@ -58,6 +58,7 @@ type webClient struct {
username string username string
password string password string
permissions group.ClientPermissions permissions group.ClientPermissions
status map[string]interface{}
requested map[string]uint32 requested map[string]uint32
done chan struct{} done chan struct{}
writeCh chan interface{} writeCh chan interface{}
@ -98,6 +99,10 @@ func (c *webClient) Permissions() group.ClientPermissions {
return c.permissions return c.permissions
} }
func (c *webClient) Status() map[string]interface{} {
return c.status
}
func (c *webClient) SetPermissions(perms group.ClientPermissions) { func (c *webClient) SetPermissions(perms group.ClientPermissions) {
c.permissions = perms c.permissions = perms
} }
@ -106,13 +111,14 @@ func (c *webClient) OverridePermissions(g *group.Group) bool {
return false return false
} }
func (c *webClient) PushClient(id, username string, permissions group.ClientPermissions, kind string) error { func (c *webClient) PushClient(id, username string, permissions group.ClientPermissions, status map[string]interface{}, kind string) error {
return c.write(clientMessage{ return c.write(clientMessage{
Type: "user", Type: "user",
Kind: kind, Kind: kind,
Id: id, Id: id,
Username: username, Username: username,
Permissions: &permissions, Permissions: &permissions,
Status: status,
}) })
} }
@ -174,6 +180,7 @@ type clientMessage struct {
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Privileged bool `json:"privileged,omitempty"` Privileged bool `json:"privileged,omitempty"`
Permissions *group.ClientPermissions `json:"permissions,omitempty"` Permissions *group.ClientPermissions `json:"permissions,omitempty"`
Status map[string]interface{} `json:"status,omitempty"`
Group string `json:"group,omitempty"` Group string `json:"group,omitempty"`
Value interface{} `json:"value,omitempty"` Value interface{} `json:"value,omitempty"`
NoEcho bool `json:"noecho,omitempty"` NoEcho bool `json:"noecho,omitempty"`
@ -988,10 +995,11 @@ func handleAction(c *webClient, a interface{}) error {
} }
id := c.Id() id := c.Id()
user := c.Username() user := c.Username()
s := c.Status()
clients := g.GetClients(nil) clients := g.GetClients(nil)
go func(clients []group.Client) { go func(clients []group.Client) {
for _, cc := range clients { for _, cc := range clients {
cc.PushClient(id, user, perms, "change") cc.PushClient(id, user, perms, s, "change")
} }
}(clients) }(clients)
case kickAction: case kickAction:
@ -1042,6 +1050,7 @@ func leaveGroup(c *webClient) {
group.DelClient(c) group.DelClient(c)
c.permissions = group.ClientPermissions{} c.permissions = group.ClientPermissions{}
c.status = nil
c.requested = map[string]uint32{} c.requested = map[string]uint32{}
c.group = nil c.group = nil
} }
@ -1454,6 +1463,36 @@ func handleClientMessage(c *webClient, m clientMessage) error {
if err != nil { if err != nil {
return c.error(err) return c.error(err)
} }
case "setstatus":
if m.Dest != c.Id() {
return c.error(group.UserError("not authorised"))
}
s, ok := m.Value.(map[string]interface{})
if !ok {
return c.error(group.UserError(
"Bad value in setstatus",
))
}
if c.status == nil {
c.status = make(map[string]interface{})
}
for k, v := range s {
if v == nil {
delete(c.status, k)
} else {
c.status[k] = v
}
}
id := c.Id()
user := c.Username()
perms := c.Permissions()
status := c.Status()
go func(clients []group.Client) {
for _, cc := range clients {
cc.PushClient(id, user, perms, status,
"change")
}
}(g.GetClients(nil))
default: default:
return group.ProtocolError("unknown user action") return group.ProtocolError("unknown user action")
} }

View File

@ -2365,6 +2365,24 @@ commands.wall = {
}, },
}; };
commands.raise = {
description: 'raise hand',
f: (c, r) => {
serverConnection.userAction(
"setstatus", serverConnection.id, {"raisehand": true},
);
}
}
commands.unraise = {
description: 'unraise hand',
f: (c, r) => {
serverConnection.userAction(
"setstatus", serverConnection.id, {"raisehand": null},
);
}
}
/** /**
* Test loopback through a TURN relay. * Test loopback through a TURN relay.
* *

View File

@ -64,6 +64,7 @@ function newLocalId() {
* @typedef {Object} user * @typedef {Object} user
* @property {string} username * @property {string} username
* @property {Object<string,boolean>} permissions * @property {Object<string,boolean>} permissions
* @property {Object<string,any>} status
*/ */
/** /**
@ -205,6 +206,7 @@ function ServerConnection() {
* @property {string} [password] * @property {string} [password]
* @property {boolean} [privileged] * @property {boolean} [privileged]
* @property {Object<string,boolean>} [permissions] * @property {Object<string,boolean>} [permissions]
* @property {Object<string,any>} [status]
* @property {string} [group] * @property {string} [group]
* @property {unknown} [value] * @property {unknown} [value]
* @property {boolean} [noecho] * @property {boolean} [noecho]
@ -342,7 +344,8 @@ ServerConnection.prototype.connect = async function(url) {
console.warn(`Duplicate user ${m.id} ${m.username}`); console.warn(`Duplicate user ${m.id} ${m.username}`);
sc.users[m.id] = { sc.users[m.id] = {
username: m.username, username: m.username,
permissions: m.permissions, permissions: m.permissions || {},
status: m.status || {},
}; };
break; break;
case 'change': case 'change':
@ -350,11 +353,13 @@ ServerConnection.prototype.connect = async function(url) {
console.warn(`Unknown user ${m.id} ${m.username}`); console.warn(`Unknown user ${m.id} ${m.username}`);
sc.users[m.id] = { sc.users[m.id] = {
username: m.username, username: m.username,
permissions: m.permissions, permissions: m.permissions || {},
status: m.status || {},
}; };
} else { } else {
sc.users[m.id].username = m.username; sc.users[m.id].username = m.username;
sc.users[m.id].permissions = m.permissions; sc.users[m.id].permissions = m.permissions || {};
sc.users[m.id].status = m.status || {};
} }
break; break;
case 'delete': case 'delete':
@ -562,7 +567,7 @@ ServerConnection.prototype.chat = function(kind, dest, value) {
* *
* @param {string} kind - One of "op", "unop", "kick", "present", "unpresent". * @param {string} kind - One of "op", "unop", "kick", "present", "unpresent".
* @param {string} dest - The id of the user to act upon. * @param {string} dest - The id of the user to act upon.
* @param {string} [value] - An optional user-readable message. * @param {any} [value] - An action-dependent parameter.
*/ */
ServerConnection.prototype.userAction = function(kind, dest, value) { ServerConnection.prototype.userAction = function(kind, dest, value) {
this.send({ this.send({