Commit b55e531a authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Encode group location in the status.json file.

parent 4bc873a5
...@@ -28,31 +28,33 @@ server to client direction. ...@@ -28,31 +28,33 @@ server to client direction.
## Before connecting ## Before connecting
Before it connects and joins a group, a client may perform an HTTP GET The client needs to know the location of the group, the (user-visible) URL
request on the URL `/public-groups.json`. This yields a JSON array of at which the group is found. This may be obtained either by explicit
objects, one for each group that has been marked public in its configuration by the user, or by parsing the `/public-groups.json` file
configuration file. Each object has the following fields: 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 - `name`: the group's name
- `displayName` (optional): a longer version of the name used for display; - `location`: the group's location
- `description` (optional): a user-readable description. - `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; - `locked`: true if the group is locked;
- `clientCount`: the number of clients currently in the group. - `clientCount`: the number of clients currently in the group.
If token-based authorisation is in use for the group, then the dictionary All fields are optional except `name`, `location` and `endpoint`.
contains the following additional field:
- `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 ## Connecting
The client connects to the websocket at `/ws`. Galene uses a symmetric, The client connects to the websocket at the URL obtained at the previous
asynchronous protocol: there are no requests and responses, and most step. Galene uses a symmetric, asynchronous protocol: there are no
messages may be sent by either peer. requests and responses, and most messages may be sent by either peer.
## Message syntax ## Message syntax
......
...@@ -1177,6 +1177,7 @@ func (desc *Description) GetPermission(group string, creds ClientCredentials) (s ...@@ -1177,6 +1177,7 @@ func (desc *Description) GetPermission(group string, creds ClientCredentials) (s
type Status struct { type Status struct {
Name string `json:"name"` Name string `json:"name"`
Location string `json:"location"`
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
DisplayName string `json:"displayName,omitempty"` DisplayName string `json:"displayName,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
...@@ -1186,10 +1187,40 @@ type Status struct { ...@@ -1186,10 +1187,40 @@ type Status struct {
ClientCount *int `json:"clientCount,omitempty"` 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() 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{ d := Status{
Name: g.name, Name: g.name,
Location: location,
Endpoint: endpoint, Endpoint: endpoint,
DisplayName: desc.DisplayName, DisplayName: desc.DisplayName,
AuthServer: desc.AuthServer, AuthServer: desc.AuthServer,
...@@ -1207,11 +1238,11 @@ func (g *Group) Status(authentified bool, endpoint string) Status { ...@@ -1207,11 +1238,11 @@ func (g *Group) Status(authentified bool, endpoint string) Status {
return d return d
} }
func GetPublic() []Status { func GetPublic(base string) []Status {
gs := make([]Status, 0) gs := make([]Status, 0)
Range(func(g *Group) bool { Range(func(g *Group) bool {
if g.Description().Public { if g.Description().Public {
gs = append(gs, g.Status(false, "")) gs = append(gs, g.Status(false, base))
} }
return true return true
}) })
......
...@@ -42,7 +42,7 @@ func TestGroup(t *testing.T) { ...@@ -42,7 +42,7 @@ func TestGroup(t *testing.T) {
t.Errorf("Expected [], got %v", subs) 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) t.Errorf("Expected group/subgroup, got %v", public)
} }
} }
......
...@@ -110,7 +110,7 @@ async function listPublicGroups() { ...@@ -110,7 +110,7 @@ async function listPublicGroups() {
let td = document.createElement('td'); let td = document.createElement('td');
let a = document.createElement('a'); let a = document.createElement('a');
a.textContent = group.displayName || group.name; a.textContent = group.displayName || group.name;
a.href = '/group/' + group.name + '/'; a.href = group.location;
td.appendChild(a); td.appendChild(a);
tr.appendChild(td); tr.appendChild(td);
let td2 = document.createElement('td'); let td2 = document.createElement('td');
......
...@@ -332,9 +332,18 @@ func groupHandler(w http.ResponseWriter, r *http.Request) { ...@@ -332,9 +332,18 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
serveFile(w, r, filepath.Join(StaticRoot, "galene.html")) 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) { func groupStatusHandler(w http.ResponseWriter, r *http.Request) {
path := path.Dir(r.URL.Path) pth := path.Dir(r.URL.Path)
name := parseGroupName("/group/", path) name := parseGroupName("/group/", pth)
if name == "" { if name == "" {
notFound(w) notFound(w)
return return
...@@ -351,16 +360,7 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) { ...@@ -351,16 +360,7 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
scheme := "wss" d := g.Status(false, groupBase(r))
if Insecure {
scheme = "ws"
}
endpoint := url.URL{
Scheme: scheme,
Host: r.Host,
Path: "/ws",
}
d := g.Status(false, endpoint.String())
w.Header().Set("content-type", "application/json") w.Header().Set("content-type", "application/json")
w.Header().Set("cache-control", "no-cache") w.Header().Set("cache-control", "no-cache")
...@@ -380,7 +380,7 @@ func publicHandler(w http.ResponseWriter, r *http.Request) { ...@@ -380,7 +380,7 @@ func publicHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
g := group.GetPublic() g := group.GetPublic(groupBase(r))
e := json.NewEncoder(w) e := json.NewEncoder(w)
e.Encode(g) e.Encode(g)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment