Deleted
This commit is contained in:
@@ -75,7 +75,7 @@ const (
|
|||||||
deleteMessageQuery = `DELETE FROM messages WHERE mid = ?`
|
deleteMessageQuery = `DELETE FROM messages WHERE mid = ?`
|
||||||
updateMessagesForTopicExpiryQuery = `UPDATE messages SET expires = ? WHERE topic = ?`
|
updateMessagesForTopicExpiryQuery = `UPDATE messages SET expires = ? WHERE topic = ?`
|
||||||
selectRowIDFromMessageID = `SELECT id FROM messages WHERE mid = ?` // Do not include topic, see #336 and TestServer_PollSinceID_MultipleTopics
|
selectRowIDFromMessageID = `SELECT id FROM messages WHERE mid = ?` // Do not include topic, see #336 and TestServer_PollSinceID_MultipleTopics
|
||||||
selectMessagesByIDQuery = `
|
selectMessagesByIDQuery = `
|
||||||
SELECT mid, sid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding, deleted
|
SELECT mid, sid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding, deleted
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE mid = ?
|
WHERE mid = ?
|
||||||
@@ -431,7 +431,7 @@ func (c *messageCache) addMessages(ms []*message) error {
|
|||||||
m.ContentType,
|
m.ContentType,
|
||||||
m.Encoding,
|
m.Encoding,
|
||||||
published,
|
published,
|
||||||
0,
|
m.Deleted,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -719,8 +719,9 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
|||||||
|
|
||||||
func readMessage(rows *sql.Rows) (*message, error) {
|
func readMessage(rows *sql.Rows) (*message, error) {
|
||||||
var timestamp, expires, attachmentSize, attachmentExpires int64
|
var timestamp, expires, attachmentSize, attachmentExpires int64
|
||||||
var priority, deleted int
|
var priority int
|
||||||
var id, sid, topic, msg, title, tagsStr, click, icon, actionsStr, attachmentName, attachmentType, attachmentURL, sender, user, contentType, encoding string
|
var id, sid, topic, msg, title, tagsStr, click, icon, actionsStr, attachmentName, attachmentType, attachmentURL, sender, user, contentType, encoding string
|
||||||
|
var deleted bool
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&id,
|
&id,
|
||||||
&sid,
|
&sid,
|
||||||
|
|||||||
@@ -547,6 +547,8 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
|
|||||||
return s.transformMatrixJSON(s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublishMatrix)))(w, r, v)
|
return s.transformMatrixJSON(s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublishMatrix)))(w, r, v)
|
||||||
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && (topicPathRegex.MatchString(r.URL.Path) || updatePathRegex.MatchString(r.URL.Path)) {
|
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && (topicPathRegex.MatchString(r.URL.Path) || updatePathRegex.MatchString(r.URL.Path)) {
|
||||||
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublish))(w, r, v)
|
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublish))(w, r, v)
|
||||||
|
} else if r.Method == http.MethodDelete && updatePathRegex.MatchString(r.URL.Path) {
|
||||||
|
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handleDelete))(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && publishPathRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && publishPathRegex.MatchString(r.URL.Path) {
|
||||||
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublish))(w, r, v)
|
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublish))(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && jsonPathRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && jsonPathRegex.MatchString(r.URL.Path) {
|
||||||
@@ -902,6 +904,52 @@ func (s *Server) handlePublishMatrix(w http.ResponseWriter, r *http.Request, v *
|
|||||||
return writeMatrixSuccess(w)
|
return writeMatrixSuccess(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
t, err := fromContext[*topic](r, contextTopic)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vrate, err := fromContext[*visitor](r, contextRateVisitor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !util.ContainsIP(s.config.VisitorRequestExemptPrefixes, v.ip) && !vrate.MessageAllowed() {
|
||||||
|
return errHTTPTooManyRequestsLimitMessages.With(t)
|
||||||
|
}
|
||||||
|
sid, e := s.sidFromPath(r.URL.Path)
|
||||||
|
if e != nil {
|
||||||
|
return e.With(t)
|
||||||
|
}
|
||||||
|
// Create a delete message: empty body, same SID, deleted flag set
|
||||||
|
m := newDefaultMessage(t.ID, "")
|
||||||
|
m.SID = sid
|
||||||
|
m.Deleted = true
|
||||||
|
m.Sender = v.IP()
|
||||||
|
m.User = v.MaybeUserID()
|
||||||
|
m.Expires = time.Unix(m.Time, 0).Add(v.Limits().MessageExpiryDuration).Unix()
|
||||||
|
// Publish to subscribers
|
||||||
|
if err := t.Publish(v, m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Send to Firebase for Android clients
|
||||||
|
if s.firebaseClient != nil {
|
||||||
|
go s.sendToFirebase(v, m)
|
||||||
|
}
|
||||||
|
// Send to web push endpoints
|
||||||
|
if s.config.WebPushPublicKey != "" {
|
||||||
|
go s.publishToWebPushEndpoints(v, m)
|
||||||
|
}
|
||||||
|
// Add to message cache
|
||||||
|
if err := s.messageCache.AddMessage(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logvrm(v, r, m).Tag(tagPublish).Debug("Deleted message with SID %s", sid)
|
||||||
|
s.mu.Lock()
|
||||||
|
s.messages++
|
||||||
|
s.mu.Unlock()
|
||||||
|
return s.writeJSON(w, m.forJSON())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) sendToFirebase(v *visitor, m *message) {
|
func (s *Server) sendToFirebase(v *visitor, m *message) {
|
||||||
logvm(v, m).Tag(tagFirebase).Debug("Publishing to Firebase")
|
logvm(v, m).Tag(tagFirebase).Debug("Publishing to Firebase")
|
||||||
if err := s.firebaseClient.Send(v, m); err != nil {
|
if err := s.firebaseClient.Send(v, m); err != nil {
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ const (
|
|||||||
|
|
||||||
// message represents a message published to a topic
|
// message represents a message published to a topic
|
||||||
type message struct {
|
type message struct {
|
||||||
ID string `json:"id"` // Random message ID
|
ID string `json:"id"` // Random message ID
|
||||||
SID string `json:"sid,omitempty"` // Message sequence ID for updating message contents (omitted if same as ID)
|
SID string `json:"sid,omitempty"` // Message sequence ID for updating message contents (omitted if same as ID)
|
||||||
Time int64 `json:"time"` // Unix time in seconds
|
Time int64 `json:"time"` // Unix time in seconds
|
||||||
Expires int64 `json:"expires,omitempty"` // Unix time in seconds (not required for open/keepalive)
|
Expires int64 `json:"expires,omitempty"` // Unix time in seconds (not required for open/keepalive)
|
||||||
Event string `json:"event"` // One of the above
|
Event string `json:"event"` // One of the above
|
||||||
Topic string `json:"topic"`
|
Topic string `json:"topic"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message"` // Allow empty message body
|
||||||
Priority int `json:"priority,omitempty"`
|
Priority int `json:"priority,omitempty"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
Click string `json:"click,omitempty"`
|
Click string `json:"click,omitempty"`
|
||||||
@@ -40,10 +40,10 @@ type message struct {
|
|||||||
Attachment *attachment `json:"attachment,omitempty"`
|
Attachment *attachment `json:"attachment,omitempty"`
|
||||||
PollID string `json:"poll_id,omitempty"`
|
PollID string `json:"poll_id,omitempty"`
|
||||||
ContentType string `json:"content_type,omitempty"` // text/plain by default (if empty), or text/markdown
|
ContentType string `json:"content_type,omitempty"` // text/plain by default (if empty), or text/markdown
|
||||||
Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
|
Encoding string `json:"encoding,omitempty"` // Empty for raw UTF-8, or "base64" for encoded bytes
|
||||||
|
Deleted bool `json:"deleted,omitempty"` // True if message is marked as deleted
|
||||||
Sender netip.Addr `json:"-"` // IP address of uploader, used for rate limiting
|
Sender netip.Addr `json:"-"` // IP address of uploader, used for rate limiting
|
||||||
User string `json:"-"` // UserID of the uploader, used to associated attachments
|
User string `json:"-"` // UserID of the uploader, used to associated attachments
|
||||||
Deleted int `json:"deleted,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *message) Context() log.Context {
|
func (m *message) Context() log.Context {
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ const handlePushMessage = async (data) => {
|
|||||||
broadcastChannel.postMessage(message); // To potentially play sound
|
broadcastChannel.postMessage(message); // To potentially play sound
|
||||||
|
|
||||||
await addNotification({ subscriptionId, message });
|
await addNotification({ subscriptionId, message });
|
||||||
|
|
||||||
|
// Don't show a notification for deleted messages
|
||||||
|
if (message.deleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await self.registration.showNotification(
|
await self.registration.showNotification(
|
||||||
...toNotificationParams({
|
...toNotificationParams({
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ class SubscriptionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collapse notification updates based on sids, keeping only the latest version
|
// Collapse notification updates based on sids, keeping only the latest version
|
||||||
|
// Filters out notifications where the latest in the sequence is deleted
|
||||||
groupNotificationsBySID(notifications) {
|
groupNotificationsBySID(notifications) {
|
||||||
const latestBySid = {};
|
const latestBySid = {};
|
||||||
notifications.forEach((notification) => {
|
notifications.forEach((notification) => {
|
||||||
@@ -184,7 +185,8 @@ class SubscriptionManager {
|
|||||||
latestBySid[key] = notification;
|
latestBySid[key] = notification;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Object.values(latestBySid);
|
// Filter out notifications where the latest is deleted
|
||||||
|
return Object.values(latestBySid).filter((n) => !n.deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds notification, or returns false if it already exists */
|
/** Adds notification, or returns false if it already exists */
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ const createDatabase = (username) => {
|
|||||||
prefs: "&key",
|
prefs: "&key",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
db.version(5).stores({
|
||||||
|
subscriptions: "&id,baseUrl,[baseUrl+mutedUntil]",
|
||||||
|
notifications: "&id,sid,subscriptionId,time,new,deleted,[subscriptionId+new]", // added deleted index
|
||||||
|
users: "&baseUrl,username",
|
||||||
|
prefs: "&key",
|
||||||
|
});
|
||||||
|
|
||||||
return db;
|
return db;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user