1
Fork 0

Encode group location in the status.json file.

This commit is contained in:
Juliusz Chroboczek 2022-09-01 15:38:29 +02:00
parent 4bc873a574
commit b55e531aa5
5 changed files with 67 additions and 34 deletions

View File

@ -28,31 +28,33 @@ server to client direction.
## Before connecting
Before it connects and joins a group, a client may perform an HTTP GET
request on the URL `/public-groups.json`. This yields a JSON array of
objects, one for each group that has been marked public in its
configuration file. Each object has the following fields:
The client needs to know the location of the group, the (user-visible) URL
at which the group is found. This may be obtained either by explicit
configuration by the user, or by parsing the `/public-groups.json` file
which may contain an array of group statuses (see below).
A client then performs an HTTP GET request on the file `.status.json` at
the group's location. This yields a single JSON object, which contains
the following fields:
- `name`: the group's name
- `displayName` (optional): a longer version of the name used for display;
- `description` (optional): a user-readable description.
- `location`: the group's location
- `endpoint`: the URL of the server's WebSocket endpoint
- `displayName`: a longer version of the name used for display;
- `description`: a user-readable description;
- `authServer`: the URL of the authentication server, if any;
- `authPortal`: the uRL of the authentication portal, if any;
- `locked`: true if the group is locked;
- `clientCount`: the number of clients currently in the group.
If token-based authorisation is in use for the group, then the dictionary
contains the following additional field:
All fields are optional except `name`, `location` and `endpoint`.
- `authServer`: the URL of the authorisation server.
A client may also fetch the URL `/group/name/.status.json` to retrieve the
status of a single group. If the group has not been marked as public,
then the fields `locked` and `clientCount` are omitted.
## Connecting
The client connects to the websocket at `/ws`. Galene uses a symmetric,
asynchronous protocol: there are no requests and responses, and most
messages may be sent by either peer.
The client connects to the websocket at the URL obtained at the previous
step. Galene uses a symmetric, asynchronous protocol: there are no
requests and responses, and most messages may be sent by either peer.
## Message syntax

View File

@ -1177,6 +1177,7 @@ func (desc *Description) GetPermission(group string, creds ClientCredentials) (s
type Status struct {
Name string `json:"name"`
Location string `json:"location"`
Endpoint string `json:"endpoint"`
DisplayName string `json:"displayName,omitempty"`
Description string `json:"description,omitempty"`
@ -1186,10 +1187,40 @@ type Status struct {
ClientCount *int `json:"clientCount,omitempty"`
}
func (g *Group) Status(authentified bool, endpoint string) Status {
// Status returns a group's status.
// Base is the base URL for groups; if omitted, then both the Location and
// Endpoint members are omitted from the result.
func (g *Group) Status(authentified bool, base string) Status {
desc := g.Description()
var location, endpoint string
if base != "" {
burl, err := url.Parse(base)
if err == nil {
wss := "wss"
if burl.Scheme == "http" {
wss = "ws"
}
l := url.URL{
Scheme: burl.Scheme,
Host: burl.Host,
Path: path.Join(burl.Path, g.name) + "/",
}
location = l.String()
e := url.URL{
Scheme: wss,
Host: burl.Host,
Path: "/ws",
}
endpoint = e.String()
} else {
log.Printf("Couldn't parse base URL %v", base)
}
}
d := Status{
Name: g.name,
Location: location,
Endpoint: endpoint,
DisplayName: desc.DisplayName,
AuthServer: desc.AuthServer,
@ -1207,11 +1238,11 @@ func (g *Group) Status(authentified bool, endpoint string) Status {
return d
}
func GetPublic() []Status {
func GetPublic(base string) []Status {
gs := make([]Status, 0)
Range(func(g *Group) bool {
if g.Description().Public {
gs = append(gs, g.Status(false, ""))
gs = append(gs, g.Status(false, base))
}
return true
})

View File

@ -42,7 +42,7 @@ func TestGroup(t *testing.T) {
t.Errorf("Expected [], got %v", subs)
}
if public := GetPublic(); len(public) != 1 || public[0].Name != "group/subgroup" {
if public := GetPublic(""); len(public) != 1 || public[0].Name != "group/subgroup" {
t.Errorf("Expected group/subgroup, got %v", public)
}
}

View File

@ -110,7 +110,7 @@ async function listPublicGroups() {
let td = document.createElement('td');
let a = document.createElement('a');
a.textContent = group.displayName || group.name;
a.href = '/group/' + group.name + '/';
a.href = group.location;
td.appendChild(a);
tr.appendChild(td);
let td2 = document.createElement('td');

View File

@ -332,9 +332,18 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
serveFile(w, r, filepath.Join(StaticRoot, "galene.html"))
}
func groupBase(r *http.Request) string {
base := url.URL{
Scheme: r.URL.Scheme,
Host: r.Host,
Path: "/group/",
}
return base.String()
}
func groupStatusHandler(w http.ResponseWriter, r *http.Request) {
path := path.Dir(r.URL.Path)
name := parseGroupName("/group/", path)
pth := path.Dir(r.URL.Path)
name := parseGroupName("/group/", pth)
if name == "" {
notFound(w)
return
@ -351,16 +360,7 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) {
return
}
scheme := "wss"
if Insecure {
scheme = "ws"
}
endpoint := url.URL{
Scheme: scheme,
Host: r.Host,
Path: "/ws",
}
d := g.Status(false, endpoint.String())
d := g.Status(false, groupBase(r))
w.Header().Set("content-type", "application/json")
w.Header().Set("cache-control", "no-cache")
@ -380,7 +380,7 @@ func publicHandler(w http.ResponseWriter, r *http.Request) {
return
}
g := group.GetPublic()
g := group.GetPublic(groupBase(r))
e := json.NewEncoder(w)
e.Encode(g)
}