Private read receipts (#2789)

Implement behaviours for `m.read.private` receipts.
This commit is contained in:
Neil Alexander 2022-10-11 12:27:21 +01:00 committed by GitHub
parent 3920b9f9b6
commit 0a9aebdf01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 33 deletions

View File

@ -154,33 +154,31 @@ func SaveReadMarker(
return *resErr return *resErr
} }
if r.FullyRead == "" { if r.FullyRead != "" {
return util.JSONResponse{ data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead})
Code: http.StatusBadRequest, if err != nil {
JSON: jsonerror.BadJSON("Missing m.fully_read mandatory field"), return jsonerror.InternalServerError()
}
dataReq := api.InputAccountDataRequest{
UserID: device.UserID,
DataType: "m.fully_read",
RoomID: roomID,
AccountData: data,
}
dataRes := api.InputAccountDataResponse{}
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
return util.ErrorResponse(err)
} }
} }
data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead}) // Handle the read receipts that may be included in the read marker.
if err != nil {
return jsonerror.InternalServerError()
}
dataReq := api.InputAccountDataRequest{
UserID: device.UserID,
DataType: "m.fully_read",
RoomID: roomID,
AccountData: data,
}
dataRes := api.InputAccountDataResponse{}
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
return util.ErrorResponse(err)
}
// Handle the read receipt that may be included in the read marker
if r.Read != "" { if r.Read != "" {
return SetReceipt(req, syncProducer, device, roomID, "m.read", r.Read) return SetReceipt(req, userAPI, syncProducer, device, roomID, "m.read", r.Read)
}
if r.ReadPrivate != "" {
return SetReceipt(req, userAPI, syncProducer, device, roomID, "m.read.private", r.ReadPrivate)
} }
return util.JSONResponse{ return util.JSONResponse{

View File

@ -15,19 +15,22 @@
package routing package routing
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/producers" "github.com/matrix-org/dendrite/clientapi/producers"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/dendrite/userapi/api"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/util" "github.com/matrix-org/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func SetReceipt(req *http.Request, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse { func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse {
timestamp := gomatrixserverlib.AsTimestamp(time.Now()) timestamp := gomatrixserverlib.AsTimestamp(time.Now())
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"roomID": roomID, "roomID": roomID,
@ -37,13 +40,32 @@ func SetReceipt(req *http.Request, syncProducer *producers.SyncAPIProducer, devi
"timestamp": timestamp, "timestamp": timestamp,
}).Debug("Setting receipt") }).Debug("Setting receipt")
// currently only m.read is accepted switch receiptType {
if receiptType != "m.read" { case "m.read", "m.read.private":
return util.MessageResponse(400, fmt.Sprintf("receipt type must be m.read not '%s'", receiptType)) if err := syncProducer.SendReceipt(req.Context(), device.UserID, roomID, eventID, receiptType, timestamp); err != nil {
} return util.ErrorResponse(err)
}
if err := syncProducer.SendReceipt(req.Context(), device.UserID, roomID, eventID, receiptType, timestamp); err != nil { case "m.fully_read":
return util.ErrorResponse(err) data, err := json.Marshal(fullyReadEvent{EventID: eventID})
if err != nil {
return jsonerror.InternalServerError()
}
dataReq := api.InputAccountDataRequest{
UserID: device.UserID,
DataType: "m.fully_read",
RoomID: roomID,
AccountData: data,
}
dataRes := api.InputAccountDataResponse{}
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
return util.ErrorResponse(err)
}
default:
return util.MessageResponse(400, fmt.Sprintf("Receipt type '%s' not known", receiptType))
} }
return util.JSONResponse{ return util.JSONResponse{

View File

@ -1343,7 +1343,7 @@ func Setup(
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return SetReceipt(req, syncProducer, device, vars["roomId"], vars["receiptType"], vars["eventId"]) return SetReceipt(req, userAPI, syncProducer, device, vars["roomId"], vars["receiptType"], vars["eventId"])
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/presence/{userId}/status", v3mux.Handle("/presence/{userId}/status",

View File

@ -81,6 +81,14 @@ func (t *OutputReceiptConsumer) onMessage(ctx context.Context, msgs []*nats.Msg)
Type: msg.Header.Get("type"), Type: msg.Header.Get("type"),
} }
switch receipt.Type {
case "m.read":
// These are allowed to be sent over federation
case "m.read.private", "m.fully_read":
// These must not be sent over federation
return true
}
// only send receipt events which originated from us // only send receipt events which originated from us
_, receiptServerName, err := gomatrixserverlib.SplitID('@', receipt.UserID) _, receiptServerName, err := gomatrixserverlib.SplitID('@', receipt.UserID)
if err != nil { if err != nil {

View File

@ -35,8 +35,9 @@ type AccountData struct {
} }
type ReadMarkerJSON struct { type ReadMarkerJSON struct {
FullyRead string `json:"m.fully_read"` FullyRead string `json:"m.fully_read"`
Read string `json:"m.read"` Read string `json:"m.read"`
ReadPrivate string `json:"m.read.private"`
} }
// NotificationData contains statistics about notifications, sent from // NotificationData contains statistics about notifications, sent from

View File

@ -67,6 +67,10 @@ func (p *ReceiptStreamProvider) IncrementalSync(
if _, ok := req.IgnoredUsers.List[receipt.UserID]; ok { if _, ok := req.IgnoredUsers.List[receipt.UserID]; ok {
continue continue
} }
// Don't send private read receipts to other users
if receipt.Type == "m.read.private" && req.Device.UserID != receipt.UserID {
continue
}
receiptsByRoom[receipt.RoomID] = append(receiptsByRoom[receipt.RoomID], receipt) receiptsByRoom[receipt.RoomID] = append(receiptsByRoom[receipt.RoomID], receipt)
} }