feat(membership): room discovery — GET /members/{endpoint}/rooms + ListMyRooms
A peer invited to an encrypted room needs to find it: the control plane is
pull-based (no server push of invitations), so add a discovery endpoint that
lists every room an endpoint belongs to, with the room's metadata and the
endpoint's role.
- store.ListRoomsForEndpoint: JOIN members+rooms, ordered by room id, empty
slice (not error) for an endpoint in no rooms.
- membershipd: GET /members/{endpoint}/rooms returns {room_id, subject, epoch,
policy, role}[].
- client.ListMyRooms + RoomRef: a bot polls this to discover and then Join +
Subscribe rooms it was invited to.
Tests: store-level (owner in N rooms, member in one, unknown endpoint → []) and
client-level e2e through the embedded harness (B discovers a room A invited it
to, without prior knowledge of the room id; owner sees role=owner).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -231,8 +231,48 @@ type blobResp struct {
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
type memberRoomJSON struct {
|
||||
RoomID string `json:"room_id"`
|
||||
Subject string `json:"subject"`
|
||||
Epoch int `json:"epoch"`
|
||||
Policy policyJSON `json:"policy"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// ---- room operations ------------------------------------------------------
|
||||
|
||||
// RoomRef is a room this peer belongs to, returned by ListMyRooms. It is the
|
||||
// unit of room discovery: a peer that was invited to a new room finds it here
|
||||
// and can then Join (fetch the sealed key) and Subscribe.
|
||||
type RoomRef struct {
|
||||
RoomID string
|
||||
Subject string
|
||||
Epoch int
|
||||
Policy room.Policy
|
||||
Role string
|
||||
}
|
||||
|
||||
// ListMyRooms returns every room this peer is currently a member of. A peer
|
||||
// polls this to discover rooms it has been invited to (the control plane is
|
||||
// pull-based: there is no server push of invitations).
|
||||
func (c *Client) ListMyRooms() ([]RoomRef, error) {
|
||||
var resp []memberRoomJSON
|
||||
if err := c.doJSON("GET", "/members/"+c.endpoint+"/rooms", nil, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]RoomRef, 0, len(resp))
|
||||
for _, r := range resp {
|
||||
out = append(out, RoomRef{
|
||||
RoomID: r.RoomID,
|
||||
Subject: r.Subject,
|
||||
Epoch: r.Epoch,
|
||||
Policy: room.Policy{Encrypt: r.Policy.Encrypt, Persist: r.Policy.Persist, SignMsgs: r.Policy.SignMsgs},
|
||||
Role: r.Role,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// newRoomKey returns 32 random bytes for a symmetric room key.
|
||||
func newRoomKey() ([]byte, error) {
|
||||
k := make([]byte, 32)
|
||||
|
||||
Reference in New Issue
Block a user