package roomserver import ( "context" "slices" "strings" "github.com/dariuszjaniak/warp/server/internal/storage" ) // The reads sliding sync needs that /sync does not: a room list ordered by // activity, each room's public face, and pieces of a room's state picked // tuple by tuple rather than all of it. // // A classic sync sends a room's whole state once and then every change; // sliding sync sends only the tuples the client asked for, which for a room // list is a name and an avatar out of a state that may hold ten thousand // member events. Reading the tuples before the event bodies is what makes // that cheap. // bumpEventTypes are the events that move a room up a room list: the // things a user would call activity. A membership change, a reaction or a // receipt is not, or a busy room would sit on top of the list for reasons // nobody looking at it could see. var bumpEventTypes = []string{ "m.room.create", TypeMessage, "m.room.encrypted", "m.sticker", "m.call.invite", "m.poll.start", "m.beacon_info", } // BumpStamps returns each room's latest activity at or before to. func (r *API) BumpStamps(ctx context.Context, roomIDs []string, to int64) (map[string]int64, error) { return r.store.LatestEventPositions(ctx, roomIDs, bumpEventTypes, to) } // RoomListEntry is what a room list shows of a room. type RoomListEntry struct { storage.RoomSummary // Encrypted reports an m.room.encryption in the room's state. Encrypted bool } // RoomListEntry reads one room's public face. func (r *API) RoomListEntry(ctx context.Context, roomID string) (RoomListEntry, error) { summary, err := r.store.RoomSummary(ctx, roomID) if err != nil { return RoomListEntry{}, err } out := RoomListEntry{RoomSummary: summary} if _, err := r.store.CurrentStateEvent(ctx, roomID, TypeEncryption, ""); err == nil { out.Encrypted = true } else if !isNotFound(err) { return RoomListEntry{}, err } return out, nil } // RequiredStateRequest picks pieces of a room's state. type RequiredStateRequest struct { RoomID string User string // Since, when set, asks only for the tuples that changed after it: the // incremental half of a sliding sync. Zero is the state in full. Since int64 // To is the position the state is read at, clamped to what the user // may see. Zero is the top of the stream. To int64 // Match picks the tuples. Match func(eventType, stateKey string) bool } // RequiredState reads the tuples of a room's state that Match picks. func (r *API) RequiredState(ctx context.Context, req RequiredStateRequest) ([]ClientEvent, error) { room, rules, err := r.roomAndRules(ctx, req.RoomID) if err != nil { return nil, err } visible, err := r.requireReadable(ctx, room.ID, req.User) if err != nil { return nil, err } to := req.To if visible.To != 0 && (to == 0 || to > visible.To) { to = visible.To } var after []storage.StateEntry if to == 0 { after, err = r.store.CurrentState(ctx, req.RoomID) } else { after, err = r.store.StateAtStream(ctx, req.RoomID, to) } if err != nil { return nil, err } var before map[[2]string]string if req.Since != 0 { entries, err := r.store.StateAtStream(ctx, req.RoomID, req.Since) if err != nil { return nil, err } before = make(map[[2]string]string, len(entries)) for _, e := range entries { before[[2]string{e.Type, e.StateKey}] = e.EventID } } var ids []string for _, e := range after { if !req.Match(e.Type, e.StateKey) { continue } if before != nil && before[[2]string{e.Type, e.StateKey}] == e.EventID { continue } ids = append(ids, e.EventID) } if len(ids) == 0 { return []ClientEvent{}, nil } events, err := r.clientEventsByID(ctx, req.RoomID, rules, ids) if err != nil { return nil, err } slices.SortFunc(events, func(a, b ClientEvent) int { if c := strings.Compare(a.Type, b.Type); c != 0 { return c } return strings.Compare(*a.StateKey, *b.StateKey) }) return events, nil } // SpaceChildRooms lists the rooms a space this server is in names as its // children. func (r *API) SpaceChildRooms(ctx context.Context, spaceID string) ([]string, error) { local, err := r.participating(ctx, spaceID) if err != nil || !local { return nil, err } read, err := r.readSpaceRoom(ctx, spaceID, false) if err != nil { return nil, err } out := make([]string, 0, len(read.children)) for _, c := range read.children { out = append(out, c.roomID) } return out, nil }