Files
Warp/server/internal/storage/push.go
T
tajniak81andClaude Opus 5.5 b507753172 Phase 8: push and notifications
Implements PLAN.md Phase 8. internal/pushrules is the rule language: the
spec's default ruleset, all six condition kinds, glob and word-boundary
matching, intentional mentions switching off the legacy body rules, and
rule ordering as Synapse has it. internal/pushserver keeps the rules,
pushers and notifications and talks to push gateways.

Push rules are the m.push_rules account data, written only by the push
server; reads keep the user's own rules and the enabled/actions of each
default and take the rest from the current defaults, so new default rules
reach every user without a migration. PUT account_data/m.push_rules is
refused with 405.

The push server listens on the roomserver's emit stage and evaluates each
event on its own worker for every local member and an invite's target.
Notifications and per-room unread counts are stored in migration 0010;
counts are a seventh sync token stream, so /sync reports
unread_notifications and picks up count changes made after the event or by
a receipt from another device. m.read and m.read.private receipts clear
the counts; leaving a room clears it.

Pushers (http kind only) push through a bounded sender pool with retries,
honour event_id_only, drop push keys the gateway rejects, and go away with
their device. GET /notifications pages the history. New config section
push: enabled, include_content, gateway_timeout, notification_retention.

Account data writes now wake the user's blocked syncs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:43:36 +02:00

131 lines
4.6 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
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
}
// 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.
MarkNotificationsRead(ctx context.Context, userID, roomID 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)
// 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)
}