mirror of
https://github.com/jech/galene.git
synced 2024-11-09 18:25:58 +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:
parent
d78a750b8d
commit
52a26327d7
8 changed files with 166 additions and 65 deletions
1
README
1
README
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
c.Joined(g.Name(), "leave")
|
||||
for _, cc := range clients {
|
||||
cc.PushClient(c.Id(), "", nil, nil, "delete")
|
||||
cc.PushClient(
|
||||
g.Name(), "delete", c.Id(), "", ClientPermissions{}, nil,
|
||||
)
|
||||
}
|
||||
}(clients)
|
||||
|
||||
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"`
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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':
|
||||
|
|
Loading…
Reference in a new issue