1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-12-26 09:15:46 +01:00

Implement group status.

We now inform clients of the status of a group (locked, etc.).  Also
cleans up the handling of administrative messages, which solves the
issue of receiving "user" before "joined".
This commit is contained in:
Juliusz Chroboczek 2021-07-16 19:41:00 +02:00
parent d78a750b8d
commit 52a26327d7
8 changed files with 166 additions and 65 deletions

1
README
View file

@ -207,6 +207,7 @@ following fields are allowed:
respectively with operator privileges, with presenter privileges, and
as passive listeners;
- `public`: if true, then the group is visible on the landing page;
- `displayName`: a human-friendly version of the group name;
- `description`: a human-readable description of the group; this is
displayed on the landing page for public groups;
- `contact`: a human-readable contact for this group, such as an e-mail

View file

@ -100,12 +100,15 @@ its permissions or in the recommended RTC configuration.
group: group,
username: username,
permissions: permissions,
status: status,
rtcConfiguration: RTCConfiguration
}
```
The `permissions` field is an array of strings that may contain the values
`present`, `op` and `record`.
`present`, `op` and `record`. The `status` field is a dictionary that
contains random information that can be usefully displayed in the user
interface.
## Maintaining group membership

View file

@ -77,7 +77,7 @@ 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 {
func (client *Client) PushClient(group, kind, id, username string, permissions group.ClientPermissions, status map[string]interface{}) error {
return nil
}
@ -103,6 +103,10 @@ func (client *Client) Kick(id, user, message string) error {
return err
}
func (client *Client) Joined(group, kind string) error {
return nil
}
func (client *Client) PushConn(g *group.Group, id string, up conn.Up, tracks []conn.UpTrack, replace string) error {
if client.group != g {
return nil

View file

@ -102,6 +102,7 @@ type Client interface {
OverridePermissions(*Group) bool
PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error
RequestConns(target Client, g *Group, id string) error
PushClient(id, username string, permissions *ClientPermissions, status map[string]interface{}, kind string) error
Joined(group, kind string) error
PushClient(group, kind, id, username string, permissions ClientPermissions, status map[string]interface{}) error
Kick(id, user, message string) error
}

View file

@ -92,12 +92,17 @@ func (g *Group) Locked() (bool, string) {
func (g *Group) SetLocked(locked bool, message string) {
g.mu.Lock()
defer g.mu.Unlock()
if locked {
g.locked = &message
} else {
g.locked = nil
}
clients := g.getClientsUnlocked(nil)
g.mu.Unlock()
for _, c := range clients {
c.Joined(g.Name(), "change")
}
}
func (g *Group) Public() bool {
@ -118,6 +123,12 @@ func (g *Group) AllowRecording() bool {
return g.description.AllowRecording
}
func (g *Group) DisplayName() string {
g.mu.Lock()
defer g.mu.Unlock()
return g.description.DisplayName
}
var groups struct {
mu sync.Mutex
groups map[string]*Group
@ -293,8 +304,16 @@ func APIFromNames(names []string) (*webrtc.API, error) {
}
func Add(name string, desc *Description) (*Group, error) {
g, notify, err := add(name, desc)
for _, c := range notify {
c.Joined(g.Name(), "change")
}
return g, err
}
func add(name string, desc *Description) (*Group, []Client, error) {
if name == "" || strings.HasSuffix(name, "/") {
return nil, UserError("illegal group name")
return nil, nil, UserError("illegal group name")
}
groups.mu.Lock()
@ -311,7 +330,7 @@ func Add(name string, desc *Description) (*Group, error) {
if desc == nil {
desc, err = GetDescription(name)
if err != nil {
return nil, err
return nil, nil, err
}
}
@ -321,9 +340,10 @@ func Add(name string, desc *Description) (*Group, error) {
clients: make(map[string]Client),
timestamp: time.Now(),
}
autoLockKick(g, g.getClientsUnlocked(nil))
clients := g.getClientsUnlocked(nil)
autoLockKick(g, clients)
groups.groups[name] = g
return g, nil
return g, clients, nil
}
g.mu.Lock()
@ -332,7 +352,7 @@ func Add(name string, desc *Description) (*Group, error) {
if desc != nil {
g.description = desc
} else if !descriptionChanged(name, g.description) {
return g, nil
return g, nil, nil
}
desc, err = GetDescription(name)
@ -341,12 +361,13 @@ func Add(name string, desc *Description) (*Group, error) {
log.Printf("Reading group %v: %v", name, err)
}
deleteUnlocked(g)
return nil, err
return nil, nil, err
}
g.description = desc
autoLockKick(g, g.getClientsUnlocked(nil))
clients := g.getClientsUnlocked(nil)
autoLockKick(g, clients)
return g, nil
return g, clients, nil
}
func Range(f func(g *Group) bool) {
@ -511,15 +532,19 @@ func AddClient(group string, c Client) (*Group, error) {
g.clients[c.Id()] = c
g.timestamp = time.Now()
c.Joined(g.Name(), "join")
id := c.Id()
u := c.Username()
p := c.Permissions()
s := c.Status()
c.PushClient(c.Id(), u, &p, s, "add")
c.PushClient(g.Name(), "add", c.Id(), u, p, s)
for _, cc := range clients {
pp := cc.Permissions()
c.PushClient(cc.Id(), cc.Username(), &pp, cc.Status(), "add")
cc.PushClient(id, u, &p, s, "add")
c.PushClient(
g.Name(), "add", cc.Id(), cc.Username(), pp, cc.Status(),
)
cc.PushClient(g.Name(), "add", id, u, p, s)
}
return g, nil
@ -539,6 +564,11 @@ func autoLockKick(g *Group, clients []Client) {
if g.description.Autolock && g.locked == nil {
m := "this group is locked"
g.locked = &m
go func(clients []Client) {
for _, c := range clients {
c.Joined(g.Name(), "change")
}
}(g.getClientsUnlocked(nil))
}
if g.description.Autokick {
@ -552,23 +582,22 @@ func DelClient(c Client) {
return
}
g.mu.Lock()
defer g.mu.Unlock()
if g.clients[c.Id()] != c {
log.Printf("Deleting unknown client")
g.mu.Unlock()
return
}
delete(g.clients, c.Id())
g.timestamp = time.Now()
clients := g.getClientsUnlocked(nil)
g.mu.Unlock()
go func(clients []Client) {
for _, cc := range clients {
cc.PushClient(c.Id(), "", nil, nil, "delete")
}
}(clients)
c.Joined(g.Name(), "leave")
for _, cc := range clients {
cc.PushClient(
g.Name(), "delete", c.Id(), "", ClientPermissions{}, nil,
)
}
autoLockKick(g, clients)
}
@ -740,6 +769,9 @@ type Description struct {
modTime time.Time `json:"-"`
fileSize int64 `json:"-"`
// The user-friendly group name
DisplayName string `json:"displayName,omitempty"`
// A user-readable description of the group.
Description string `json:"description,omitempty"`

View file

@ -111,14 +111,9 @@ func (c *webClient) OverridePermissions(g *group.Group) bool {
return false
}
func (c *webClient) PushClient(id, username string, permissions *group.ClientPermissions, status map[string]interface{}, kind string) error {
return c.write(clientMessage{
Type: "user",
Kind: kind,
Id: id,
Username: username,
Permissions: permissions,
Status: status,
func (c *webClient) PushClient(group, kind, id, username string, permissions group.ClientPermissions, status map[string]interface{}) error {
return c.action(pushClientAction{
group, kind, id, username, permissions, status,
})
}
@ -804,6 +799,17 @@ func (c *webClient) PushConn(g *group.Group, id string, up conn.Up, tracks []con
return nil
}
func getGroupStatus(g *group.Group) map[string]interface{} {
status := make(map[string]interface{})
if locked, _ := g.Locked(); locked {
status["locked"] = true
}
if dn := g.DisplayName(); dn != "" {
status["displayName"] = dn
}
return status
}
func readMessage(conn *websocket.Conn, m *clientMessage) error {
err := conn.SetReadDeadline(time.Now().Add(15 * time.Second))
if err != nil {
@ -880,8 +886,22 @@ type connectionFailedAction struct {
id string
}
type pushClientAction struct {
group string
kind string
id string
username string
permissions group.ClientPermissions
status map[string]interface{}
}
type permissionsChangedAction struct{}
type joinedAction struct {
group string
kind string
}
type kickAction struct {
id string
username string
@ -1067,6 +1087,37 @@ func handleAction(c *webClient, a interface{}) error {
"unknown connection")
}
case pushClientAction:
if a.group != c.group.Name() {
log.Printf("got client for wrong group")
return nil
}
return c.write(clientMessage{
Type: "user",
Kind: a.kind,
Id: a.id,
Username: a.username,
Permissions: &a.permissions,
Status: a.status,
})
case joinedAction:
var status map[string]interface{}
if a.group != "" {
g := group.Get(a.group)
if g != nil {
status = getGroupStatus(g)
}
}
perms := c.permissions
return c.write(clientMessage{
Type: "joined",
Kind: a.kind,
Group: a.group,
Username: c.username,
Permissions: &perms,
Status: status,
RTCConfiguration: ice.ICEConfiguration(),
})
case permissionsChangedAction:
g := c.Group()
if g == nil {
@ -1079,6 +1130,7 @@ func handleAction(c *webClient, a interface{}) error {
Group: g.Name(),
Username: c.username,
Permissions: &perms,
Status: getGroupStatus(g),
RTCConfiguration: ice.ICEConfiguration(),
})
if !c.permissions.Present {
@ -1101,7 +1153,9 @@ func handleAction(c *webClient, a interface{}) error {
clients := g.GetClients(nil)
go func(clients []group.Client) {
for _, cc := range clients {
cc.PushClient(id, user, &perms, s, "change")
cc.PushClient(
g.Name(), "change", id, user, perms, s,
)
}
}(clients)
case kickAction:
@ -1212,6 +1266,10 @@ func (c *webClient) Kick(id, user, message string) error {
return c.action(kickAction{id, user, message})
}
func (c *webClient) Joined(group, kind string) error {
return c.action(joinedAction{group, kind})
}
func kickClient(g *group.Group, id, user, dest string, message string) error {
client := g.GetClient(dest)
if client == nil {
@ -1243,14 +1301,6 @@ func handleClientMessage(c *webClient, m clientMessage) error {
return group.ProtocolError("you are not joined")
}
leaveGroup(c)
perms := c.permissions
return c.write(clientMessage{
Type: "joined",
Kind: "leave",
Group: m.Group,
Username: c.username,
Permissions: &perms,
})
}
if m.Kind != "join" {
@ -1298,18 +1348,6 @@ func handleClientMessage(c *webClient, m clientMessage) error {
})
}
c.group = g
perms := c.permissions
err = c.write(clientMessage{
Type: "joined",
Kind: "join",
Group: m.Group,
Username: c.username,
Permissions: &perms,
RTCConfiguration: ice.ICEConfiguration(),
})
if err != nil {
return err
}
h := c.group.GetChatHistory()
for _, m := range h {
err := c.write(clientMessage{
@ -1608,8 +1646,10 @@ func handleClientMessage(c *webClient, m clientMessage) error {
status := c.Status()
go func(clients []group.Client) {
for _, cc := range clients {
cc.PushClient(id, user, &perms, status,
"change")
cc.PushClient(
g.Name(), "change",
id, user, perms, status,
)
}
}(g.GetClients(nil))
default:

View file

@ -2091,12 +2091,35 @@ function displayUsername() {
let presentRequested = null;
/**
* @param {string} [title]
*/
function setTitle(title) {
function set(title) {
document.title = title;
document.getElementById('title').textContent = title;
}
if(title) {
set(title);
return;
}
let t = group.charAt(0).toUpperCase() + group.slice(1);
if(t) {
set(t);
return;
}
set('Galène');
}
/**
* @this {ServerConnection}
* @param {string} group
* @param {Object<string,boolean>} perms
* @param {Object<string,any>} status
* @param {string} message
*/
async function gotJoined(kind, group, perms, message) {
async function gotJoined(kind, group, perms, status, message) {
let present = presentRequested;
presentRequested = null;
@ -2108,7 +2131,7 @@ async function gotJoined(kind, group, perms, message) {
return;
case 'redirect':
this.close();
document.location = message;
document.location.href = message;
return;
case 'leave':
this.close();
@ -2116,6 +2139,7 @@ async function gotJoined(kind, group, perms, message) {
return;
case 'join':
case 'change':
setTitle(status.displayName || group);
displayUsername();
setButtonsVisibility();
if(kind === 'change')
@ -3061,12 +3085,7 @@ async function serverConnect() {
function start() {
group = decodeURIComponent(location.pathname.replace(/^\/[a-z]*\//, ''));
let title = group.charAt(0).toUpperCase() + group.slice(1);
if(group !== '') {
document.title = title;
document.getElementById('title').textContent = title;
}
setTitle();
addFilters();
setMediaChoices(false).then(e => reflectSettings());

View file

@ -164,7 +164,7 @@ function ServerConnection() {
*
* kind is one of 'join', 'fail', 'change' or 'leave'.
*
* @type{(this: ServerConnection, kind: string, group: string, permissions: Object<string,boolean>, message: string) => void}
* @type{(this: ServerConnection, kind: string, group: string, permissions: Object<string,boolean>, status: Object<string,any>, message: string) => void}
*/
this.onjoined = null;
/**
@ -284,7 +284,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,6 +336,7 @@ ServerConnection.prototype.connect = async function(url) {
if(sc.onjoined)
sc.onjoined.call(sc, m.kind, m.group,
m.permissions || {},
m.status,
m.value || null);
break;
case 'user':