binwiederhier il y a 1 mois
Parent
commit
2856793eff
6 fichiers modifiés avec 74 ajouts et 10 suppressions
  1. 4 3
      server/message_cache.go
  2. 48 0
      server/server.go
  3. 6 6
      server/types.go
  4. 6 0
      web/public/sw.js
  5. 3 1
      web/src/app/SubscriptionManager.js
  6. 7 0
      web/src/app/db.js

+ 4 - 3
server/message_cache.go

@@ -75,7 +75,7 @@ const (
 	deleteMessageQuery                = `DELETE FROM messages WHERE mid = ?`
 	updateMessagesForTopicExpiryQuery = `UPDATE messages SET expires = ? WHERE topic = ?`
 	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
 		FROM messages
 		WHERE mid = ?
@@ -431,7 +431,7 @@ func (c *messageCache) addMessages(ms []*message) error {
 			m.ContentType,
 			m.Encoding,
 			published,
-			0,
+			m.Deleted,
 		)
 		if err != nil {
 			return err
@@ -719,8 +719,9 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
 
 func readMessage(rows *sql.Rows) (*message, error) {
 	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 deleted bool
 	err := rows.Scan(
 		&id,
 		&sid,

+ 48 - 0
server/server.go

@@ -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)
 	} 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)
+	} 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) {
 		return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublish))(w, r, v)
 	} 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)
 }
 
+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) {
 	logvm(v, m).Tag(tagFirebase).Debug("Publishing to Firebase")
 	if err := s.firebaseClient.Send(v, m); err != nil {

+ 6 - 6
server/types.go

@@ -24,14 +24,14 @@ const (
 
 // message represents a message published to a topic
 type message struct {
-	ID          string      `json:"id"`                     // Random message 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
+	ID          string      `json:"id"`                // Random message 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
 	Expires     int64       `json:"expires,omitempty"` // Unix time in seconds (not required for open/keepalive)
 	Event       string      `json:"event"`             // One of the above
 	Topic       string      `json:"topic"`
 	Title       string      `json:"title,omitempty"`
-	Message     string      `json:"message,omitempty"`
+	Message     string      `json:"message"` // Allow empty message body
 	Priority    int         `json:"priority,omitempty"`
 	Tags        []string    `json:"tags,omitempty"`
 	Click       string      `json:"click,omitempty"`
@@ -40,10 +40,10 @@ type message struct {
 	Attachment  *attachment `json:"attachment,omitempty"`
 	PollID      string      `json:"poll_id,omitempty"`
 	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
 	User        string      `json:"-"`                      // UserID of the uploader, used to associated attachments
-	Deleted     int         `json:"deleted,omitempty"`
 }
 
 func (m *message) Context() log.Context {

+ 6 - 0
web/public/sw.js

@@ -57,6 +57,12 @@ const handlePushMessage = async (data) => {
   broadcastChannel.postMessage(message); // To potentially play sound
 
   await addNotification({ subscriptionId, message });
+
+  // Don't show a notification for deleted messages
+  if (message.deleted) {
+    return;
+  }
+
   await self.registration.showNotification(
     ...toNotificationParams({
       subscriptionId,

+ 3 - 1
web/src/app/SubscriptionManager.js

@@ -175,6 +175,7 @@ class SubscriptionManager {
   }
 
   // Collapse notification updates based on sids, keeping only the latest version
+  // Filters out notifications where the latest in the sequence is deleted
   groupNotificationsBySID(notifications) {
     const latestBySid = {};
     notifications.forEach((notification) => {
@@ -184,7 +185,8 @@ class SubscriptionManager {
         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 */

+ 7 - 0
web/src/app/db.js

@@ -18,6 +18,13 @@ const createDatabase = (username) => {
     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;
 };