Private read receipts
Implement behaviours for `m.read.private` receipts.
MODIFY
clientapi/routing/account_data.go
clientapi/routing/account_data.go
@@ -154,13 +154,7 @@ func SaveReadMarker(
return *resErr
}
- if r.FullyRead == "" {
- return util.JSONResponse{
- Code: http.StatusBadRequest,
- JSON: jsonerror.BadJSON("Missing m.fully_read mandatory field"),
- }
- }
-
+ if r.FullyRead != "" {
data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead})
if err != nil {
return jsonerror.InternalServerError()
@@ -177,10 +171,14 @@ func SaveReadMarker(
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
+ // Handle the read receipts that may be included in the read marker.
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{

MODIFY
clientapi/routing/receipt.go
clientapi/routing/receipt.go
package routing
import (
+ "encoding/json"
"fmt"
"net/http"
"time"
+ "github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/producers"
"github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/dendrite/userapi/api"
userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/util"
"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())
logrus.WithFields(logrus.Fields{
"roomID": roomID,
@@ -37,15 +40,34 @@ func SetReceipt(req *http.Request, syncProducer *producers.SyncAPIProducer, devi
"timestamp": timestamp,
}).Debug("Setting receipt")
- // currently only m.read is accepted
- if receiptType != "m.read" {
- return util.MessageResponse(400, fmt.Sprintf("receipt type must be m.read not '%s'", receiptType))
+ switch receiptType {
+ case "m.read", "m.read.private":
+ 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":
+ 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{
Code: http.StatusOK,
JSON: struct{}{},

MODIFY
clientapi/routing/routing.go
clientapi/routing/routing.go
@@ -1343,7 +1343,7 @@ func Setup(
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)
v3mux.Handle("/presence/{userId}/status",

MODIFY
federationapi/consumers/receipts.go
federationapi/consumers/receipts.go
@@ -81,6 +81,14 @@ func (t *OutputReceiptConsumer) onMessage(ctx context.Context, msgs []*nats.Msg)
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
_, receiptServerName, err := gomatrixserverlib.SplitID('@', receipt.UserID)
if err != nil {

MODIFY
internal/eventutil/types.go
internal/eventutil/types.go
@@ -37,6 +37,7 @@ type AccountData struct {
type ReadMarkerJSON struct {
FullyRead string `json:"m.fully_read"`
Read string `json:"m.read"`
+ ReadPrivate string `json:"m.read.private"`
}
// NotificationData contains statistics about notifications, sent from

MODIFY
syncapi/streams/stream_receipt.go
syncapi/streams/stream_receipt.go
@@ -67,6 +67,10 @@ func (p *ReceiptStreamProvider) IncrementalSync(
if _, ok := req.IgnoredUsers.List[receipt.UserID]; ok {
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)
}

Private read receipts (#2789)

Implement behaviours for `m.read.private` receipts.