binwiederhier hace 1 mes
padre
commit
aca9a77498

+ 15 - 22
server/message_cache.go

@@ -31,7 +31,6 @@ const (
 			mid TEXT NOT NULL,
 			mid TEXT NOT NULL,
 			sid TEXT NOT NULL,
 			sid TEXT NOT NULL,
 			time INT NOT NULL,
 			time INT NOT NULL,
-			mtime INT NOT NULL,
 			expires INT NOT NULL,
 			expires INT NOT NULL,
 			topic TEXT NOT NULL,
 			topic TEXT NOT NULL,
 			message TEXT NOT NULL,
 			message TEXT NOT NULL,
@@ -57,7 +56,6 @@ const (
 		CREATE INDEX IF NOT EXISTS idx_mid ON messages (mid);
 		CREATE INDEX IF NOT EXISTS idx_mid ON messages (mid);
 		CREATE INDEX IF NOT EXISTS idx_sid ON messages (sid);
 		CREATE INDEX IF NOT EXISTS idx_sid ON messages (sid);
 		CREATE INDEX IF NOT EXISTS idx_time ON messages (time);
 		CREATE INDEX IF NOT EXISTS idx_time ON messages (time);
-		CREATE INDEX IF NOT EXISTS idx_mtime ON messages (mtime);
 		CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
 		CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
 		CREATE INDEX IF NOT EXISTS idx_expires ON messages (expires);
 		CREATE INDEX IF NOT EXISTS idx_expires ON messages (expires);
 		CREATE INDEX IF NOT EXISTS idx_sender ON messages (sender);
 		CREATE INDEX IF NOT EXISTS idx_sender ON messages (sender);
@@ -71,53 +69,53 @@ const (
 		COMMIT;
 		COMMIT;
 	`
 	`
 	insertMessageQuery = `
 	insertMessageQuery = `
-		INSERT INTO messages (mid, sid, time, mtime, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, attachment_deleted, sender, user, content_type, encoding, published, deleted)
-		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+		INSERT INTO messages (mid, sid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, attachment_deleted, sender, user, content_type, encoding, published, deleted)
+		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 	`
 	`
 	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, mtime, 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 = ?
 	`
 	`
 	selectMessagesSinceTimeQuery = `
 	selectMessagesSinceTimeQuery = `
-		SELECT mid, sid, time, mtime, 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 topic = ? AND time >= ? AND published = 1
 		WHERE topic = ? AND time >= ? AND published = 1
-		ORDER BY mtime, id
+		ORDER BY time, id
 	`
 	`
 	selectMessagesSinceTimeIncludeScheduledQuery = `
 	selectMessagesSinceTimeIncludeScheduledQuery = `
-		SELECT mid, sid, time, mtime, 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 topic = ? AND time >= ?
 		WHERE topic = ? AND time >= ?
-		ORDER BY mtime, id
+		ORDER BY time, id
 	`
 	`
 	selectMessagesSinceIDQuery = `
 	selectMessagesSinceIDQuery = `
-		SELECT mid, sid, time, mtime, 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 topic = ? AND id > ? AND published = 1
 		WHERE topic = ? AND id > ? AND published = 1
-		ORDER BY mtime, id
+		ORDER BY time, id
 	`
 	`
 	selectMessagesSinceIDIncludeScheduledQuery = `
 	selectMessagesSinceIDIncludeScheduledQuery = `
-		SELECT mid, sid, time, mtime, 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 topic = ? AND (id > ? OR published = 0)
 		WHERE topic = ? AND (id > ? OR published = 0)
-		ORDER BY mtime, id
+		ORDER BY time, id
 	`
 	`
 	selectMessagesLatestQuery = `
 	selectMessagesLatestQuery = `
-		SELECT mid, sid, time, mtime, 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 topic = ? AND published = 1
 		WHERE topic = ? AND published = 1
 		ORDER BY time DESC, id DESC
 		ORDER BY time DESC, id DESC
 		LIMIT 1
 		LIMIT 1
 	`
 	`
 	selectMessagesDueQuery = `
 	selectMessagesDueQuery = `
-		SELECT mid, sid, time, mtime, 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 time <= ? AND published = 0
 		WHERE time <= ? AND published = 0
-		ORDER BY mtime, id
+		ORDER BY time, id
 	`
 	`
 	selectMessagesExpiredQuery      = `SELECT mid FROM messages WHERE expires <= ? AND published = 1`
 	selectMessagesExpiredQuery      = `SELECT mid FROM messages WHERE expires <= ? AND published = 1`
 	updateMessagePublishedQuery     = `UPDATE messages SET published = 1 WHERE mid = ?`
 	updateMessagePublishedQuery     = `UPDATE messages SET published = 1 WHERE mid = ?`
@@ -270,10 +268,8 @@ const (
 	//13 -> 14
 	//13 -> 14
 	migrate13To14AlterMessagesTableQuery = `
 	migrate13To14AlterMessagesTableQuery = `
 	  ALTER TABLE messages ADD COLUMN sid TEXT NOT NULL DEFAULT('');
 	  ALTER TABLE messages ADD COLUMN sid TEXT NOT NULL DEFAULT('');
-		ALTER TABLE messages ADD COLUMN mtime INT NOT NULL DEFAULT('0');
 		ALTER TABLE messages ADD COLUMN deleted INT NOT NULL DEFAULT('0');
 		ALTER TABLE messages ADD COLUMN deleted INT NOT NULL DEFAULT('0');
 		CREATE INDEX IF NOT EXISTS idx_sid ON messages (sid);
 		CREATE INDEX IF NOT EXISTS idx_sid ON messages (sid);
-		CREATE INDEX IF NOT EXISTS idx_mtime ON messages (mtime);
 	`
 	`
 )
 )
 
 
@@ -415,7 +411,6 @@ func (c *messageCache) addMessages(ms []*message) error {
 			m.ID,
 			m.ID,
 			m.SID,
 			m.SID,
 			m.Time,
 			m.Time,
-			m.MTime,
 			m.Expires,
 			m.Expires,
 			m.Topic,
 			m.Topic,
 			m.Message,
 			m.Message,
@@ -723,14 +718,13 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
 }
 }
 
 
 func readMessage(rows *sql.Rows) (*message, error) {
 func readMessage(rows *sql.Rows) (*message, error) {
-	var timestamp, mtimestamp, expires, attachmentSize, attachmentExpires int64
+	var timestamp, expires, attachmentSize, attachmentExpires int64
 	var priority, deleted int
 	var priority, deleted 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
 	err := rows.Scan(
 	err := rows.Scan(
 		&id,
 		&id,
 		&sid,
 		&sid,
 		&timestamp,
 		&timestamp,
-		&mtimestamp,
 		&expires,
 		&expires,
 		&topic,
 		&topic,
 		&msg,
 		&msg,
@@ -782,7 +776,6 @@ func readMessage(rows *sql.Rows) (*message, error) {
 		ID:          id,
 		ID:          id,
 		SID:         sid,
 		SID:         sid,
 		Time:        timestamp,
 		Time:        timestamp,
-		MTime:       mtimestamp,
 		Expires:     expires,
 		Expires:     expires,
 		Event:       messageEvent,
 		Event:       messageEvent,
 		Topic:       topic,
 		Topic:       topic,

+ 3 - 19
server/message_cache_test.go

@@ -24,11 +24,9 @@ func TestMemCache_Messages(t *testing.T) {
 func testCacheMessages(t *testing.T, c *messageCache) {
 func testCacheMessages(t *testing.T, c *messageCache) {
 	m1 := newDefaultMessage("mytopic", "my message")
 	m1 := newDefaultMessage("mytopic", "my message")
 	m1.Time = 1
 	m1.Time = 1
-	m1.MTime = 1000
 
 
 	m2 := newDefaultMessage("mytopic", "my other message")
 	m2 := newDefaultMessage("mytopic", "my other message")
 	m2.Time = 2
 	m2.Time = 2
-	m2.MTime = 2000
 
 
 	require.Nil(t, c.AddMessage(m1))
 	require.Nil(t, c.AddMessage(m1))
 	require.Nil(t, c.AddMessage(newDefaultMessage("example", "my example message")))
 	require.Nil(t, c.AddMessage(newDefaultMessage("example", "my example message")))
@@ -126,13 +124,10 @@ func testCacheMessagesScheduled(t *testing.T, c *messageCache) {
 	m1 := newDefaultMessage("mytopic", "message 1")
 	m1 := newDefaultMessage("mytopic", "message 1")
 	m2 := newDefaultMessage("mytopic", "message 2")
 	m2 := newDefaultMessage("mytopic", "message 2")
 	m2.Time = time.Now().Add(time.Hour).Unix()
 	m2.Time = time.Now().Add(time.Hour).Unix()
-	m2.MTime = time.Now().Add(time.Hour).UnixMilli()
 	m3 := newDefaultMessage("mytopic", "message 3")
 	m3 := newDefaultMessage("mytopic", "message 3")
-	m3.Time = time.Now().Add(time.Minute).Unix()       // earlier than m2!
-	m3.MTime = time.Now().Add(time.Minute).UnixMilli() // earlier than m2!
+	m3.Time = time.Now().Add(time.Minute).Unix() // earlier than m2!
 	m4 := newDefaultMessage("mytopic2", "message 4")
 	m4 := newDefaultMessage("mytopic2", "message 4")
 	m4.Time = time.Now().Add(time.Minute).Unix()
 	m4.Time = time.Now().Add(time.Minute).Unix()
-	m4.MTime = time.Now().Add(time.Minute).UnixMilli()
 	require.Nil(t, c.AddMessage(m1))
 	require.Nil(t, c.AddMessage(m1))
 	require.Nil(t, c.AddMessage(m2))
 	require.Nil(t, c.AddMessage(m2))
 	require.Nil(t, c.AddMessage(m3))
 	require.Nil(t, c.AddMessage(m3))
@@ -206,25 +201,18 @@ func TestMemCache_MessagesSinceID(t *testing.T) {
 func testCacheMessagesSinceID(t *testing.T, c *messageCache) {
 func testCacheMessagesSinceID(t *testing.T, c *messageCache) {
 	m1 := newDefaultMessage("mytopic", "message 1")
 	m1 := newDefaultMessage("mytopic", "message 1")
 	m1.Time = 100
 	m1.Time = 100
-	m1.MTime = 100000
 	m2 := newDefaultMessage("mytopic", "message 2")
 	m2 := newDefaultMessage("mytopic", "message 2")
 	m2.Time = 200
 	m2.Time = 200
-	m2.MTime = 200000
 	m3 := newDefaultMessage("mytopic", "message 3")
 	m3 := newDefaultMessage("mytopic", "message 3")
-	m3.Time = time.Now().Add(time.Hour).Unix()       // Scheduled, in the future, later than m7 and m5
-	m3.MTime = time.Now().Add(time.Hour).UnixMilli() // Scheduled, in the future, later than m7 and m5
+	m3.Time = time.Now().Add(time.Hour).Unix() // Scheduled, in the future, later than m7 and m5
 	m4 := newDefaultMessage("mytopic", "message 4")
 	m4 := newDefaultMessage("mytopic", "message 4")
 	m4.Time = 400
 	m4.Time = 400
-	m4.MTime = 400000
 	m5 := newDefaultMessage("mytopic", "message 5")
 	m5 := newDefaultMessage("mytopic", "message 5")
-	m5.Time = time.Now().Add(time.Minute).Unix()       // Scheduled, in the future, later than m7
-	m5.MTime = time.Now().Add(time.Minute).UnixMilli() // Scheduled, in the future, later than m7
+	m5.Time = time.Now().Add(time.Minute).Unix() // Scheduled, in the future, later than m7
 	m6 := newDefaultMessage("mytopic", "message 6")
 	m6 := newDefaultMessage("mytopic", "message 6")
 	m6.Time = 600
 	m6.Time = 600
-	m6.MTime = 600000
 	m7 := newDefaultMessage("mytopic", "message 7")
 	m7 := newDefaultMessage("mytopic", "message 7")
 	m7.Time = 700
 	m7.Time = 700
-	m7.MTime = 700000
 
 
 	require.Nil(t, c.AddMessage(m1))
 	require.Nil(t, c.AddMessage(m1))
 	require.Nil(t, c.AddMessage(m2))
 	require.Nil(t, c.AddMessage(m2))
@@ -285,17 +273,14 @@ func testCachePrune(t *testing.T, c *messageCache) {
 
 
 	m1 := newDefaultMessage("mytopic", "my message")
 	m1 := newDefaultMessage("mytopic", "my message")
 	m1.Time = now - 10
 	m1.Time = now - 10
-	m1.MTime = (now - 10) * 1000
 	m1.Expires = now - 5
 	m1.Expires = now - 5
 
 
 	m2 := newDefaultMessage("mytopic", "my other message")
 	m2 := newDefaultMessage("mytopic", "my other message")
 	m2.Time = now - 5
 	m2.Time = now - 5
-	m2.MTime = (now - 5) * 1000
 	m2.Expires = now + 5 // In the future
 	m2.Expires = now + 5 // In the future
 
 
 	m3 := newDefaultMessage("another_topic", "and another one")
 	m3 := newDefaultMessage("another_topic", "and another one")
 	m3.Time = now - 12
 	m3.Time = now - 12
-	m3.MTime = (now - 12) * 1000
 	m3.Expires = now - 2
 	m3.Expires = now - 2
 
 
 	require.Nil(t, c.AddMessage(m1))
 	require.Nil(t, c.AddMessage(m1))
@@ -546,7 +531,6 @@ func TestSqliteCache_Migration_From1(t *testing.T) {
 	// Add delayed message
 	// Add delayed message
 	delayedMessage := newDefaultMessage("mytopic", "some delayed message")
 	delayedMessage := newDefaultMessage("mytopic", "some delayed message")
 	delayedMessage.Time = time.Now().Add(time.Minute).Unix()
 	delayedMessage.Time = time.Now().Add(time.Minute).Unix()
-	delayedMessage.MTime = time.Now().Add(time.Minute).UnixMilli()
 	require.Nil(t, c.AddMessage(delayedMessage))
 	require.Nil(t, c.AddMessage(delayedMessage))
 
 
 	// 10, not 11!
 	// 10, not 11!

+ 3 - 3
server/server.go

@@ -874,7 +874,7 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
 		return err
 		return err
 	}
 	}
 	minc(metricMessagesPublishedSuccess)
 	minc(metricMessagesPublishedSuccess)
-	return s.writeJSON(w, m)
+	return s.writeJSON(w, m.forJSON())
 }
 }
 
 
 func (s *Server) handlePublishMatrix(w http.ResponseWriter, r *http.Request, v *visitor) error {
 func (s *Server) handlePublishMatrix(w http.ResponseWriter, r *http.Request, v *visitor) error {
@@ -1291,7 +1291,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message,
 func (s *Server) handleSubscribeJSON(w http.ResponseWriter, r *http.Request, v *visitor) error {
 func (s *Server) handleSubscribeJSON(w http.ResponseWriter, r *http.Request, v *visitor) error {
 	encoder := func(msg *message) (string, error) {
 	encoder := func(msg *message) (string, error) {
 		var buf bytes.Buffer
 		var buf bytes.Buffer
-		if err := json.NewEncoder(&buf).Encode(&msg); err != nil {
+		if err := json.NewEncoder(&buf).Encode(msg.forJSON()); err != nil {
 			return "", err
 			return "", err
 		}
 		}
 		return buf.String(), nil
 		return buf.String(), nil
@@ -1302,7 +1302,7 @@ func (s *Server) handleSubscribeJSON(w http.ResponseWriter, r *http.Request, v *
 func (s *Server) handleSubscribeSSE(w http.ResponseWriter, r *http.Request, v *visitor) error {
 func (s *Server) handleSubscribeSSE(w http.ResponseWriter, r *http.Request, v *visitor) error {
 	encoder := func(msg *message) (string, error) {
 	encoder := func(msg *message) (string, error) {
 		var buf bytes.Buffer
 		var buf bytes.Buffer
-		if err := json.NewEncoder(&buf).Encode(&msg); err != nil {
+		if err := json.NewEncoder(&buf).Encode(msg.forJSON()); err != nil {
 			return "", err
 			return "", err
 		}
 		}
 		if msg.Event != messageEvent {
 		if msg.Event != messageEvent {

+ 15 - 12
server/types.go

@@ -24,10 +24,9 @@ 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
-	SID         string      `json:"sid"`               // Message sequence ID for updating message contents
-	Time        int64       `json:"time"`              // Unix time in seconds
-	MTime       int64       `json:"mtime"`             // Unix time in milliseconds
+	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)
 	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"`
@@ -53,7 +52,6 @@ func (m *message) Context() log.Context {
 		"message_id":        m.ID,
 		"message_id":        m.ID,
 		"message_sid":       m.SID,
 		"message_sid":       m.SID,
 		"message_time":      m.Time,
 		"message_time":      m.Time,
-		"message_mtime":     m.MTime,
 		"message_event":     m.Event,
 		"message_event":     m.Event,
 		"message_body_size": len(m.Message),
 		"message_body_size": len(m.Message),
 	}
 	}
@@ -66,6 +64,16 @@ func (m *message) Context() log.Context {
 	return fields
 	return fields
 }
 }
 
 
+// forJSON returns a copy of the message prepared for JSON output.
+// It clears SID if it equals ID (to avoid redundant output).
+func (m *message) forJSON() *message {
+	msg := *m
+	if msg.SID == msg.ID {
+		msg.SID = "" // Will be omitted due to omitempty
+	}
+	return &msg
+}
+
 type attachment struct {
 type attachment struct {
 	Name    string `json:"name"`
 	Name    string `json:"name"`
 	Type    string `json:"type,omitempty"`
 	Type    string `json:"type,omitempty"`
@@ -123,7 +131,6 @@ func newMessage(event, topic, msg string) *message {
 	return &message{
 	return &message{
 		ID:      util.RandomString(messageIDLength),
 		ID:      util.RandomString(messageIDLength),
 		Time:    time.Now().Unix(),
 		Time:    time.Now().Unix(),
-		MTime:   time.Now().UnixMilli(),
 		Event:   event,
 		Event:   event,
 		Topic:   topic,
 		Topic:   topic,
 		Message: msg,
 		Message: msg,
@@ -162,11 +169,7 @@ type sinceMarker struct {
 }
 }
 
 
 func newSinceTime(timestamp int64) sinceMarker {
 func newSinceTime(timestamp int64) sinceMarker {
-	return newSinceMTime(timestamp * 1000)
-}
-
-func newSinceMTime(mtimestamp int64) sinceMarker {
-	return sinceMarker{time.UnixMilli(mtimestamp), ""}
+	return sinceMarker{time.Unix(timestamp, 0), ""}
 }
 }
 
 
 func newSinceID(id string) sinceMarker {
 func newSinceID(id string) sinceMarker {
@@ -557,7 +560,7 @@ func newWebPushPayload(subscriptionID string, message *message) *webPushPayload
 	return &webPushPayload{
 	return &webPushPayload{
 		Event:          webPushMessageEvent,
 		Event:          webPushMessageEvent,
 		SubscriptionID: subscriptionID,
 		SubscriptionID: subscriptionID,
-		Message:        message,
+		Message:        message.forJSON(),
 	}
 	}
 }
 }
 
 

+ 1 - 2
web/public/static/langs/en.json

@@ -70,8 +70,7 @@
   "notifications_delete": "Delete",
   "notifications_delete": "Delete",
   "notifications_copied_to_clipboard": "Copied to clipboard",
   "notifications_copied_to_clipboard": "Copied to clipboard",
   "notifications_tags": "Tags",
   "notifications_tags": "Tags",
-  "notifications_sid": "Sequence ID",
-  "notifications_revisions": "Revisions",
+  "notifications_modified": "modified {{date}}",
   "notifications_priority_x": "Priority {{priority}}",
   "notifications_priority_x": "Priority {{priority}}",
   "notifications_new_indicator": "New notification",
   "notifications_new_indicator": "New notification",
   "notifications_attachment_image": "Attachment image",
   "notifications_attachment_image": "Attachment image",

+ 0 - 3
web/public/sw.js

@@ -25,9 +25,6 @@ const addNotification = async ({ subscriptionId, message }) => {
   const db = await dbAsync();
   const db = await dbAsync();
   const populatedMessage = message;
   const populatedMessage = message;
 
 
-  if (!("mtime" in populatedMessage)) {
-    populatedMessage.mtime = message.time * 1000;
-  }
   if (!("sid" in populatedMessage)) {
   if (!("sid" in populatedMessage)) {
     populatedMessage.sid = message.id;
     populatedMessage.sid = message.id;
   }
   }

+ 22 - 19
web/src/app/SubscriptionManager.js

@@ -157,7 +157,7 @@ class SubscriptionManager {
     // killing performance. See  https://dexie.org/docs/Collection/Collection.offset()#a-better-paging-approach
     // killing performance. See  https://dexie.org/docs/Collection/Collection.offset()#a-better-paging-approach
 
 
     const notifications = await this.db.notifications
     const notifications = await this.db.notifications
-      .orderBy("mtime") // Sort by time first
+      .orderBy("time") // Sort by time
       .filter((n) => n.subscriptionId === subscriptionId)
       .filter((n) => n.subscriptionId === subscriptionId)
       .reverse()
       .reverse()
       .toArray();
       .toArray();
@@ -167,30 +167,39 @@ class SubscriptionManager {
 
 
   async getAllNotifications() {
   async getAllNotifications() {
     const notifications = await this.db.notifications
     const notifications = await this.db.notifications
-      .orderBy("mtime") // Efficient, see docs
+      .orderBy("time") // Efficient, see docs
       .reverse()
       .reverse()
       .toArray();
       .toArray();
 
 
     return this.groupNotificationsBySID(notifications);
     return this.groupNotificationsBySID(notifications);
   }
   }
 
 
-  // Collapse notification updates based on sids
+  // Collapse notification updates based on sids, keeping only the latest version
+  // Also tracks the original time (earliest) for each sequence
   groupNotificationsBySID(notifications) {
   groupNotificationsBySID(notifications) {
-    const results = {};
+    const latestBySid = {};
+    const originalTimeBySid = {};
+
     notifications.forEach((notification) => {
     notifications.forEach((notification) => {
       const key = `${notification.subscriptionId}:${notification.sid}`;
       const key = `${notification.subscriptionId}:${notification.sid}`;
-      if (key in results) {
-        if ("history" in results[key]) {
-          results[key].history.push(notification);
-        } else {
-          results[key].history = [notification];
-        }
-      } else {
-        results[key] = notification;
+
+      // Track the latest notification for each sid (first one since sorted DESC)
+      if (!(key in latestBySid)) {
+        latestBySid[key] = notification;
+      }
+
+      // Track the original (earliest) time for each sid
+      const currentOriginal = originalTimeBySid[key];
+      if (currentOriginal === undefined || notification.time < currentOriginal) {
+        originalTimeBySid[key] = notification.time;
       }
       }
     });
     });
 
 
-    return Object.values(results);
+    // Return latest notifications with originalTime set
+    return Object.entries(latestBySid).map(([key, notification]) => ({
+      ...notification,
+      originalTime: originalTimeBySid[key],
+    }));
   }
   }
 
 
   /** Adds notification, or returns false if it already exists */
   /** Adds notification, or returns false if it already exists */
@@ -201,9 +210,6 @@ class SubscriptionManager {
     }
     }
     try {
     try {
       const populatedNotification = notification;
       const populatedNotification = notification;
-      if (!("mtime" in populatedNotification)) {
-        populatedNotification.mtime = notification.time * 1000;
-      }
       if (!("sid" in populatedNotification)) {
       if (!("sid" in populatedNotification)) {
         populatedNotification.sid = notification.id;
         populatedNotification.sid = notification.id;
       }
       }
@@ -227,9 +233,6 @@ class SubscriptionManager {
   async addNotifications(subscriptionId, notifications) {
   async addNotifications(subscriptionId, notifications) {
     const notificationsWithSubscriptionId = notifications.map((notification) => {
     const notificationsWithSubscriptionId = notifications.map((notification) => {
       const populatedNotification = notification;
       const populatedNotification = notification;
-      if (!("mtime" in populatedNotification)) {
-        populatedNotification.mtime = notification.time * 1000;
-      }
       if (!("sid" in populatedNotification)) {
       if (!("sid" in populatedNotification)) {
         populatedNotification.sid = notification.id;
         populatedNotification.sid = notification.id;
       }
       }

+ 2 - 2
web/src/app/db.js

@@ -11,9 +11,9 @@ const createDatabase = (username) => {
   const dbName = username ? `ntfy-${username}` : "ntfy"; // IndexedDB database is based on the logged-in user
   const dbName = username ? `ntfy-${username}` : "ntfy"; // IndexedDB database is based on the logged-in user
   const db = new Dexie(dbName);
   const db = new Dexie(dbName);
 
 
-  db.version(3).stores({
+  db.version(4).stores({
     subscriptions: "&id,baseUrl,[baseUrl+mutedUntil]",
     subscriptions: "&id,baseUrl,[baseUrl+mutedUntil]",
-    notifications: "&id,sid,subscriptionId,time,mtime,new,[subscriptionId+new]", // compound key for query performance
+    notifications: "&id,sid,subscriptionId,time,new,[subscriptionId+new]", // compound key for query performance
     users: "&baseUrl,username",
     users: "&baseUrl,username",
     prefs: "&key",
     prefs: "&key",
   });
   });

+ 1 - 1
web/src/app/notificationUtils.js

@@ -69,7 +69,7 @@ export const toNotificationParams = ({ subscriptionId, message, defaultTitle, to
       badge,
       badge,
       icon,
       icon,
       image,
       image,
-      timestamp: message.mtime,
+      timestamp: message.time * 1000,
       tag,
       tag,
       renotify: true,
       renotify: true,
       silent: false,
       silent: false,

+ 5 - 14
web/src/components/Notifications.jsx

@@ -236,7 +236,9 @@ const NotificationItem = (props) => {
   const { t, i18n } = useTranslation();
   const { t, i18n } = useTranslation();
   const { notification } = props;
   const { notification } = props;
   const { attachment } = notification;
   const { attachment } = notification;
-  const date = formatShortDateTime(notification.time, i18n.language);
+  const isModified = notification.originalTime && notification.originalTime !== notification.time;
+  const originalDate = formatShortDateTime(notification.originalTime || notification.time, i18n.language);
+  const modifiedDate = isModified ? formatShortDateTime(notification.time, i18n.language) : null;
   const otherTags = unmatchedTags(notification.tags);
   const otherTags = unmatchedTags(notification.tags);
   const tags = otherTags.length > 0 ? otherTags.join(", ") : null;
   const tags = otherTags.length > 0 ? otherTags.join(", ") : null;
   const handleDelete = async () => {
   const handleDelete = async () => {
@@ -267,8 +269,6 @@ const NotificationItem = (props) => {
   const hasUserActions = notification.actions && notification.actions.length > 0;
   const hasUserActions = notification.actions && notification.actions.length > 0;
   const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
   const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
 
 
-  const showSid = notification.id !== notification.sid || notification.history;
-
   return (
   return (
     <Card sx={{ padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
     <Card sx={{ padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
       <CardContent>
       <CardContent>
@@ -289,7 +289,8 @@ const NotificationItem = (props) => {
           </Tooltip>
           </Tooltip>
         )}
         )}
         <Typography sx={{ fontSize: 14 }} color="text.secondary">
         <Typography sx={{ fontSize: 14 }} color="text.secondary">
-          {date}
+          {originalDate}
+          {modifiedDate && ` (${t("notifications_modified", { date: modifiedDate })})`}
           {[1, 2, 4, 5].includes(notification.priority) && (
           {[1, 2, 4, 5].includes(notification.priority) && (
             <img
             <img
               src={priorityFiles[notification.priority]}
               src={priorityFiles[notification.priority]}
@@ -325,16 +326,6 @@ const NotificationItem = (props) => {
             {t("notifications_tags")}: {tags}
             {t("notifications_tags")}: {tags}
           </Typography>
           </Typography>
         )}
         )}
-        {showSid && (
-          <Typography sx={{ fontSize: 14 }} color="text.secondary">
-            {t("notifications_sid")}: {notification.sid}
-          </Typography>
-        )}
-        {notification.history && (
-          <Typography sx={{ fontSize: 14 }} color="text.secondary">
-            {t("notifications_revisions")}: {notification.history.length + 1}
-          </Typography>
-        )}
       </CardContent>
       </CardContent>
       {showActions && (
       {showActions && (
         <CardActions sx={{ paddingTop: 0 }}>
         <CardActions sx={{ paddingTop: 0 }}>