aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeil Alexander <neilalexander@users.noreply.github.com>2022-10-11 12:27:21 +0100
committerGitHub <noreply@github.com>2022-10-11 12:27:21 +0100
commit0a9aebdf011921680e5b0646bf50d7900423aa69 (patch)
treeae111f8aba12c73a0d4c4ec32f3e109d401693e3
parent3920b9f9b6155db69822d0dbcd36acb1eaa51c34 (diff)
Private read receipts (#2789)
Implement behaviours for `m.read.private` receipts.
-rw-r--r--clientapi/routing/account_data.go42
-rw-r--r--clientapi/routing/receipt.go36
-rw-r--r--clientapi/routing/routing.go2
-rw-r--r--federationapi/consumers/receipts.go8
-rw-r--r--internal/eventutil/types.go5
-rw-r--r--syncapi/streams/stream_receipt.go4
6 files changed, 65 insertions, 32 deletions
diff --git a/clientapi/routing/account_data.go b/clientapi/routing/account_data.go
index b28f0bb1..4742b124 100644
--- a/clientapi/routing/account_data.go
+++ b/clientapi/routing/account_data.go
@@ -154,33 +154,31 @@ 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()
}
- }
- data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead})
- 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)
+ 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
+ // 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{
diff --git a/clientapi/routing/receipt.go b/clientapi/routing/receipt.go
index 0f9b1b4f..99217a78 100644
--- a/clientapi/routing/receipt.go
+++ b/clientapi/routing/receipt.go
@@ -15,19 +15,22 @@
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,13 +40,32 @@ 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)
+ }
+
+ 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)
+ }
- if err := syncProducer.SendReceipt(req.Context(), device.UserID, roomID, eventID, receiptType, timestamp); err != nil {
- return util.ErrorResponse(err)
+ default:
+ return util.MessageResponse(400, fmt.Sprintf("Receipt type '%s' not known", receiptType))
}
return util.JSONResponse{
diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go
index f1fa66ca..e72880ec 100644
--- a/clientapi/routing/routing.go
+++ b/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",
diff --git a/federationapi/consumers/receipts.go b/federationapi/consumers/receipts.go
index 366cb264..75827cb6 100644
--- a/federationapi/consumers/receipts.go
+++ b/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 {
diff --git a/internal/eventutil/types.go b/internal/eventutil/types.go
index afc62d8c..18175d6a 100644
--- a/internal/eventutil/types.go
+++ b/internal/eventutil/types.go
@@ -35,8 +35,9 @@ type AccountData struct {
}
type ReadMarkerJSON struct {
- FullyRead string `json:"m.fully_read"`
- Read string `json:"m.read"`
+ FullyRead string `json:"m.fully_read"`
+ Read string `json:"m.read"`
+ ReadPrivate string `json:"m.read.private"`
}
// NotificationData contains statistics about notifications, sent from
diff --git a/syncapi/streams/stream_receipt.go b/syncapi/streams/stream_receipt.go
index bba91102..97781507 100644
--- a/syncapi/streams/stream_receipt.go
+++ b/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)
}