Spaces Summary

kegan/spaces-summary
Kegan Dougal 5 years ago
parent 7aded5be4d
commit 059f324e7b

@ -0,0 +1,182 @@
### Spaces Summary API
*This MSC depends on [MSC1772](https://github.com/matrix-org/matrix-doc/pull/1772).*
Spaces are rooms with `m.space` as the [room type](https://github.com/matrix-org/matrix-doc/pull/1840).
Spaces can include state events to specify parent/child relationships.
These relationships point to other rooms, which may themselves be spaces.
This means spaces can have subspaces and rooms. This creates a graph: a space directory.
This MSC defines a new endpoint which can be used to reveal information about the space directory.
Consider the graph:
```
A
^
|___
| |
V V
B R1
^
|
V
R2
R1,R2 = rooms
A,B = spaces
<--> = parent/child relationship events
```
This MSC aims to create a way for clients to produce a tree view along the lines of:
```
Space A
|
|___ Room 1
|
Space B
|
|___ Room 2
```
Clients are able to do this currently by peeking into all of these rooms
(assuming they have permission to) but this is costly and slow.
#### Client API
```
POST /_matrix/client/r0/rooms/{roomID}/spaces
{
"max_rooms_per_space": 5, // The maximum number of rooms to return for a given space
"limit": 100, // The maximum number of rooms/subspaces to return, server can override this, default: 100.
"batch": "opaque_string" // A token to use if this is a subsequent HTTP hit, default: "".
}
```
which returns:
```
{
"next_batch": "opaque string",
"rooms": [
{
"aliases": [
"#murrays:cheese.bar"
],
"avatar_url": "mxc://bleeker.street/CHEDDARandBRIE",
"guest_can_join": false,
"name": "CHEESE",
"num_joined_members": 37,
"room_id": "!ol19s:bleecker.street",
"topic": "Tasty tasty cheese",
"world_readable": true,
"num_refs": 42
},
{ ... }
],
"events": [
{
"type": "m.space.child",
"state_key": "!efgh:example.com",
"content": {
"via": ["example.com"],
"present": true,
"order": "abcd",
"default": true
}
},
{
"type": "m.room.parent",
"state_key": "",
"content": {
"room_id": "!space:example.com",
"via": ["example.com"]
}
}
]
}
```
Justifications for the request API shape are as follows:
- The HTTP path: Spaces are scoped to a specific room to act as an anchor point for
navigating the directory. Alternatives are `/r0/spaces` with `room_id` inside the
body, but this feels less idiomatic for room-scoped requests.
- The HTTP method: there's a lot of data to provide to the server, and GET requests
shouldn't have an HTTP body, hence opting for POST. The same request can produce
different results over time so PUT isn't acceptable as an alternative.
- `max_rooms_per_space`: UIs can only display a set number of rooms per space, so allowing
clients to specify this limit is desirable. Subsequent rooms can be obtained by paginating.
The graph has 2 distinct types of nodes, and some UIs may want to weight one type above
the other. However, it's impossible to always know what type of node a given room ID falls
under because the server may not be joined to that room (to determine the room type) or the
caller may not have permission to see this information.
- `limit`: The maximum number of events to return in `events`. It is desirable for clients
and servers to be able to put a maximum cap on the amount of data returned to the client.
**This limit may be exceeded if the root room has `> limit` rooms.**
- `batch`: Required for pagination. Could be a query parameter but it's easier if
request data is in one place.
Justifications for the response API shape are as follows:
- `rooms`: These are the nodes of the graph. The objects in the array are exactly the same as `PublicRoomsChunk` in the
[specification](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-publicrooms)
as the information displayed to users is the same. There is one _additional_ key
which is `num_refs` which is the total number state events which point to or from this room. This
includes all `m.space.child` events in the room, _in addition to_ `m.room.parent` events which point to
this room as a parent.
- `events`: These are the edges of the graph. The objects in the array are complete (or stripped?) `m.room.parent`
or `m.space.child` events.
- `next_batch`: Its presence indicates that there are more results to return.
Server behaviour:
- Extract the room ID from the request. Sanity check request data. Begin walking the graph
starting with the room ID in the request in a queue of unvisited rooms according to the
following rules:
* If this room has already been processed, skip. NB: do not remember this between calls,
as servers will need to visit the same room more than once to return additional events.
* Is the caller currently joined to the room or is the room `world_readable`?
If no, skip this room. If yes, continue.
* If this room has not ever been in `rooms` (across multiple requests), extract the
`PublicRoomsChunk` for this room.
* Get all `m.space.child` and `m.room.parent` state events for the room. *In addition*, get
all `m.space.child` and `m.room.parent` state events which *point to* (via `state_key` or `content.room_id`)
this room. This requires servers to store reverse lookups. Add the total number of events
to `PublicRoomsChunk` under `num_refs`. Add `PublicRoomsChunk` to `rooms`.
* If this is the root room from the original request, insert all these events into `events` if
they haven't been added before (across multiple requests).
* Else add them to `events` honouring the `limit` and `max_rooms_per_space` values. If either
are exceeded, stop adding events. If the event has already been added, do not add it again.
* Mark this room as processed.
* For each referenced room ID in the events being returned to the caller (both parent and child)
add the room ID to the queue of unvisited rooms. Loop from the beginning.
- This guarantees that all edges for the root node are given to the client. Not all edges of subspaces
will be returned, nor will edges of all rooms be returned. This can be detected by clients in two ways:
* Comparing `num_refs` with the *total number* of edges pointing to/from the room.
* Comparing the number of `m.space.child` state events in the room with `max_rooms_per_space`, where
`max_rooms_per_space` is 1 greater than the actual desired maximum value.
- If not all events were returned due to reaching a `limit` or `max_rooms_per_space`, return a
`next_batch` token. The server SHOULD NOT return duplicate events or rooms on subsequent
requests: this can be achieved by remembering the event/room IDs returned to the caller between calls.
This results in each request uncovering more nodes/edges until the entire tree has been explored.
Client behaviour:
- Decide which room should be the root of the tree, then call this endpoint with the root room ID.
- The data in `rooms` determines _what_ to show. The events in `events` determine _where_ to show it.
Take all the data in `rooms` and key them by room ID.
- Loop through the `events` and keep track of parent->child relationships by looking at the `room_id`
of the event and the `state_key` which is the child room ID. Clients may want to treat
child->parent relationships (`m.room.parent` events) the same way or differently. Treating them the
same way will guarantee that the entire graph is exposed on the UI, but can cause issues because it
can result in multiple roots (a child can refer to a new unknown parent). If a child->parent relationship
exists but a corresponding parent->child relationship does not exist, this room is a "secret" room which
should be indicated as such. If a parent->child relationship exists but a corresponding child->parent
relationship does not exist, this room is a "user-curated collection" and should be indicated as such.
Persist the mappings in a map: one child can have multiple parents and one parent can have multiple
children.
- Starting at the root room ID:
* Compare the `num_refs` value in `rooms.$room_id` to the total number of events which reference this
room in `events` (across all rooms). If they differ, a partial response has been returned for this
space and additional results should be loaded when required. The API guarantees that *all* events for
the root room ID will be returned, regardless of how many events there are (even if they exceed `limit`).
* Lookup all children for this room ID. For each child:
- If the child is a room (not a space), look up the room data from `rooms` and render it.
- Else the child is a space, render the space as a heading (using the room name/topic) and
restart the lookup using the new space room ID.
Loading…
Cancel
Save