CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
Implements PLAN.md Phase 10. Simplified Sliding Sync (MSC4186) is served at the unstable org.matrix.simplified_msc3575 endpoint and advertised in /versions. Lists are sorted by activity and filtered by DM, invite, encryption, room type, space and tag; room subscriptions, required_state with wildcards, $ME and $LAZY, and the to_device, e2ee, account_data, receipts and typing extensions are supported. Per-connection state is kept in memory and an unknown pos answers M_UNKNOWN_POS. Threads and relations: m.relates_to is indexed on roomserver_events (migration 0012), /relations (with recursion) and /threads are served, and every event a timeline read returns carries its m.thread, m.replace and m.reference summaries in unsigned.m.relations. Notifications carry a thread ID (migration 0013): threaded receipts clear only their thread, and sync reports unread_thread_notifications when the filter asks. Spaces: /hierarchy walks a space breadth first, asking other servers about rooms this one is not in through the new federation hierarchy endpoint; room_summary describes one room. /search runs on FTS5 (sqlite, pocketbase) or tsvector (postgres), bounded to what each user may see, with context, profiles, state, grouping and paging. The PocketBase driver is real: the sqlite schema and store on an embedded PocketBase app's database, with media in its filesystem, local or S3 (database.pocketbase.s3). It runs the full storage test suite. Ops: OpenTelemetry tracing over OTLP/HTTP (tracing.*), with server spans per route, component spans, and trace propagation on federation, push and application service calls. New metrics for persisted events, waiting syncs, sliding sync connections and the database pool, with a Grafana dashboard. deploy/ adds a Compose setup (PostgreSQL, Prometheus, Grafana, Jaeger) and a Helm chart; cmd/warp-loadtest loads a server and reports request and delivery latencies. The load tester found a long-standing bug, fixed here: on a server where no account data had been written, every incremental /sync re-sent the push rules and returned at once, and an all-zero token was taken for an initial sync, so long polls became busy loops. The PostgreSQL migrations and search dialect compile but were not run against a live PostgreSQL in this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
158 lines
5.9 KiB
Go
158 lines
5.9 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
)
|
|
|
|
// ErrNoSuchPusher is a pusher that does not exist.
|
|
var ErrNoSuchPusher = errors.New("no such pusher")
|
|
|
|
// Pusher is one place a user's notifications are pushed to: an app on a
|
|
// device, reached through its push gateway.
|
|
type Pusher struct {
|
|
Localpart string
|
|
|
|
// DeviceID is the session that registered the pusher, so that logging
|
|
// the device out removes it. Empty for a pusher whose session is not
|
|
// known.
|
|
DeviceID string
|
|
|
|
Kind string
|
|
AppID string
|
|
PushKey string
|
|
|
|
AppDisplayName string
|
|
DeviceDisplayName string
|
|
ProfileTag string
|
|
Lang string
|
|
|
|
// Data is the pusher's data object as the client sent it: the gateway
|
|
// URL, the payload format, and anything else the app wants passed to
|
|
// its gateway.
|
|
Data []byte
|
|
|
|
// PushKeyTSMS is when the push key was last set, which a gateway uses
|
|
// to tell an old registration of a key from a newer one.
|
|
PushKeyTSMS int64
|
|
}
|
|
|
|
// NewNotification is one event that notifies one user.
|
|
type NewNotification struct {
|
|
UserID string
|
|
RoomID string
|
|
EventID string
|
|
|
|
// EventPos is the event's position in the room event stream, which is
|
|
// what a read receipt is compared against.
|
|
EventPos int64
|
|
|
|
// ThreadID is the thread the event is in: the root its m.relates_to
|
|
// names when rel_type is m.thread, or ThreadMain for an event on the
|
|
// main timeline. A thread root is itself on the main timeline.
|
|
ThreadID string
|
|
|
|
TimestampMS int64
|
|
Highlight bool
|
|
|
|
// Actions are the matched rule's actions, as JSON, which GET
|
|
// /notifications reports.
|
|
Actions []byte
|
|
}
|
|
|
|
// Notification is a stored notification.
|
|
type Notification struct {
|
|
NewNotification
|
|
|
|
// ID orders a user's notifications, newest highest, and is what GET
|
|
// /notifications pages by.
|
|
ID int64
|
|
|
|
Read bool
|
|
}
|
|
|
|
// Threads a read receipt can be for, as the thread argument of
|
|
// MarkNotificationsRead. Any other value is a thread root's event ID.
|
|
const (
|
|
// ThreadMain is the main timeline: the thread ID of an event in no
|
|
// thread, and what a receipt with thread_id "main" reads.
|
|
ThreadMain = ""
|
|
|
|
// AllThreads is an unthreaded receipt, which reads the main timeline
|
|
// and every thread together. It cannot be mistaken for a thread root:
|
|
// an event ID starts with '$'.
|
|
AllThreads = "*"
|
|
)
|
|
|
|
// NotificationCounts is a room's unread notifications for one user.
|
|
type NotificationCounts struct {
|
|
Notifications int
|
|
Highlights int
|
|
}
|
|
|
|
// PushStore persists pushers and the notifications push rules produce.
|
|
//
|
|
// Notification counts are a stream of their own: every write that changes
|
|
// a user's counts in a room stamps the room with a new position, inside
|
|
// the transaction that changes them, so that an incremental sync can ask
|
|
// which rooms' counts moved since its token. A count moves without an
|
|
// event — a read receipt sent from another device sets it back to zero —
|
|
// and a sync that only looked at events would go on showing the old one.
|
|
type PushStore interface {
|
|
// Pushers lists a user's pushers.
|
|
Pushers(ctx context.Context, localpart string) ([]Pusher, error)
|
|
|
|
// SetPusher creates or replaces the user's pusher with the same app ID
|
|
// and push key. Unless appendOthers is set, any other user's pusher
|
|
// with the same app ID and push key is removed: a push key names a
|
|
// device, and the device is now this user's.
|
|
SetPusher(ctx context.Context, p Pusher, appendOthers bool) error
|
|
|
|
// DeletePusher removes one of a user's pushers. Removing one that is
|
|
// not there is not an error.
|
|
DeletePusher(ctx context.Context, localpart, appID, pushKey string) error
|
|
|
|
// DeletePushersForDevices removes the pushers registered by the given
|
|
// sessions, or by every session of the user when deviceIDs is empty.
|
|
DeletePushersForDevices(ctx context.Context, localpart string, deviceIDs []string) error
|
|
|
|
// AddNotifications records notifications and moves the counts of
|
|
// each (user, room) they are for, returning the position the last
|
|
// count moved to.
|
|
AddNotifications(ctx context.Context, ns []NewNotification) (int64, error)
|
|
|
|
// MarkNotificationsRead marks a user's notifications in a room up to
|
|
// and including an event position as read, and reports whether the
|
|
// counts moved. thread limits it to one thread — ThreadMain for the
|
|
// main timeline, a root's event ID for a thread — or is AllThreads.
|
|
MarkNotificationsRead(ctx context.Context, userID, roomID, thread string, upTo int64) (pos int64, moved bool, err error)
|
|
|
|
// NotificationCounts reads a user's unread counts in the given rooms.
|
|
// A room with nothing unread is in the result with zero counts.
|
|
NotificationCounts(ctx context.Context, userID string, roomIDs []string) (map[string]NotificationCounts, error)
|
|
|
|
// ThreadNotificationCounts reads a user's unread counts in the given
|
|
// rooms one thread at a time, keyed by room and then by thread ID, with
|
|
// the main timeline under ThreadMain. Only threads with something
|
|
// unread are in the result. The room's move in the notification stream
|
|
// covers its threads: any change to a thread's counts is a change to
|
|
// the room's.
|
|
ThreadNotificationCounts(ctx context.Context, userID string, roomIDs []string) (map[string]map[string]NotificationCounts, error)
|
|
|
|
// NotificationCountChanges lists the rooms whose counts for a user
|
|
// moved after from and at or before to.
|
|
NotificationCountChanges(ctx context.Context, userID string, from, to int64) ([]string, error)
|
|
|
|
// UnreadNotifications is a user's unread notifications across every
|
|
// room, which a push tells the device to show as its badge.
|
|
UnreadNotifications(ctx context.Context, userID string) (int, error)
|
|
|
|
// Notifications pages through a user's notifications, newest first,
|
|
// starting below the ID before (0 for the newest).
|
|
Notifications(ctx context.Context, userID string, before int64, limit int, onlyHighlight bool) ([]Notification, error)
|
|
|
|
// PruneNotifications removes read notifications older than a time,
|
|
// returning how many went.
|
|
PruneNotifications(ctx context.Context, beforeMS int64) (int64, error)
|
|
}
|