Browse Source

Rename to sequence_id

binwiederhier 2 months ago
parent
commit
1ab7ca876c

+ 1 - 1
server/errors.go

@@ -125,7 +125,7 @@ var (
 	errHTTPBadRequestInvalidUsername                 = &errHTTP{40046, http.StatusBadRequest, "invalid request: invalid username", "", nil}
 	errHTTPBadRequestInvalidUsername                 = &errHTTP{40046, http.StatusBadRequest, "invalid request: invalid username", "", nil}
 	errHTTPBadRequestTemplateFileNotFound            = &errHTTP{40047, http.StatusBadRequest, "invalid request: template file not found", "https://ntfy.sh/docs/publish/#message-templating", nil}
 	errHTTPBadRequestTemplateFileNotFound            = &errHTTP{40047, http.StatusBadRequest, "invalid request: template file not found", "https://ntfy.sh/docs/publish/#message-templating", nil}
 	errHTTPBadRequestTemplateFileInvalid             = &errHTTP{40048, http.StatusBadRequest, "invalid request: template file invalid", "https://ntfy.sh/docs/publish/#message-templating", nil}
 	errHTTPBadRequestTemplateFileInvalid             = &errHTTP{40048, http.StatusBadRequest, "invalid request: template file invalid", "https://ntfy.sh/docs/publish/#message-templating", nil}
-	errHTTPBadRequestSIDInvalid                      = &errHTTP{40049, http.StatusBadRequest, "invalid request: SID invalid", "https://ntfy.sh/docs/publish/#TODO", nil}
+	errHTTPBadRequestSIDInvalid                      = &errHTTP{40049, http.StatusBadRequest, "invalid request: sequence ID invalid", "https://ntfy.sh/docs/publish/#TODO", nil}
 	errHTTPNotFound                                  = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
 	errHTTPNotFound                                  = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
 	errHTTPUnauthorized                              = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
 	errHTTPUnauthorized                              = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
 	errHTTPForbidden                                 = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}
 	errHTTPForbidden                                 = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}

+ 19 - 19
server/message_cache.go

@@ -29,7 +29,7 @@ const (
 		CREATE TABLE IF NOT EXISTS messages (
 		CREATE TABLE IF NOT EXISTS messages (
 			id INTEGER PRIMARY KEY AUTOINCREMENT,
 			id INTEGER PRIMARY KEY AUTOINCREMENT,
 			mid TEXT NOT NULL,
 			mid TEXT NOT NULL,
-			sid TEXT NOT NULL,
+			sequence_id TEXT NOT NULL,
 			time INT NOT NULL,
 			time INT NOT NULL,
 			expires INT NOT NULL,
 			expires INT NOT NULL,
 			topic TEXT NOT NULL,
 			topic TEXT NOT NULL,
@@ -54,7 +54,7 @@ const (
 			deleted INT NOT NULL
 			deleted INT NOT NULL
 		);
 		);
 		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_sequence_id ON messages (sequence_id);
 		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_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);
@@ -69,50 +69,50 @@ const (
 		COMMIT;
 		COMMIT;
 	`
 	`
 	insertMessageQuery = `
 	insertMessageQuery = `
-		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)
+		INSERT INTO messages (mid, sequence_id, 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 		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, 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, sequence_id, 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, 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, sequence_id, 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 time, id
 		ORDER BY time, id
 	`
 	`
 	selectMessagesSinceTimeIncludeScheduledQuery = `
 	selectMessagesSinceTimeIncludeScheduledQuery = `
-		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, sequence_id, 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 time, id
 		ORDER BY time, id
 	`
 	`
 	selectMessagesSinceIDQuery = `
 	selectMessagesSinceIDQuery = `
-		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, sequence_id, 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 time, id
 		ORDER BY time, id
 	`
 	`
 	selectMessagesSinceIDIncludeScheduledQuery = `
 	selectMessagesSinceIDIncludeScheduledQuery = `
-		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, sequence_id, 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 time, id
 		ORDER BY time, id
 	`
 	`
 	selectMessagesLatestQuery = `
 	selectMessagesLatestQuery = `
-		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, sequence_id, 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, 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, sequence_id, 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 time, id
 		ORDER BY time, id
@@ -267,9 +267,9 @@ const (
 
 
 	//13 -> 14
 	//13 -> 14
 	migrate13To14AlterMessagesTableQuery = `
 	migrate13To14AlterMessagesTableQuery = `
-	  ALTER TABLE messages ADD COLUMN sid TEXT NOT NULL DEFAULT('');
+		ALTER TABLE messages ADD COLUMN sequence_id TEXT NOT NULL DEFAULT('');
 		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_sequence_id ON messages (sequence_id);
 	`
 	`
 )
 )
 
 
@@ -409,7 +409,7 @@ func (c *messageCache) addMessages(ms []*message) error {
 		}
 		}
 		_, err := stmt.Exec(
 		_, err := stmt.Exec(
 			m.ID,
 			m.ID,
-			m.SID,
+			m.SequenceID,
 			m.Time,
 			m.Time,
 			m.Expires,
 			m.Expires,
 			m.Topic,
 			m.Topic,
@@ -720,11 +720,11 @@ 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 int
 	var priority int
-	var id, sid, topic, msg, title, tagsStr, click, icon, actionsStr, attachmentName, attachmentType, attachmentURL, sender, user, contentType, encoding string
+	var id, sequenceID, topic, msg, title, tagsStr, click, icon, actionsStr, attachmentName, attachmentType, attachmentURL, sender, user, contentType, encoding string
 	var deleted bool
 	var deleted bool
 	err := rows.Scan(
 	err := rows.Scan(
 		&id,
 		&id,
-		&sid,
+		&sequenceID,
 		&timestamp,
 		&timestamp,
 		&expires,
 		&expires,
 		&topic,
 		&topic,
@@ -773,13 +773,13 @@ func readMessage(rows *sql.Rows) (*message, error) {
 			URL:     attachmentURL,
 			URL:     attachmentURL,
 		}
 		}
 	}
 	}
-	// Clear SID if it equals ID (we do not want the SID in the message output)
-	if sid == id {
-		sid = ""
+	// Clear SequenceID if it equals ID (we do not want the SequenceID in the message output)
+	if sequenceID == id {
+		sequenceID = ""
 	}
 	}
 	return &message{
 	return &message{
 		ID:          id,
 		ID:          id,
-		SID:         sid,
+		SequenceID:  sequenceID,
 		Time:        timestamp,
 		Time:        timestamp,
 		Expires:     expires,
 		Expires:     expires,
 		Event:       messageEvent,
 		Event:       messageEvent,

+ 7 - 7
server/message_cache_test.go

@@ -319,7 +319,7 @@ func testCacheAttachments(t *testing.T, c *messageCache) {
 	expires1 := time.Now().Add(-4 * time.Hour).Unix() // Expired
 	expires1 := time.Now().Add(-4 * time.Hour).Unix() // Expired
 	m := newDefaultMessage("mytopic", "flower for you")
 	m := newDefaultMessage("mytopic", "flower for you")
 	m.ID = "m1"
 	m.ID = "m1"
-	m.SID = "m1"
+	m.SequenceID = "m1"
 	m.Sender = netip.MustParseAddr("1.2.3.4")
 	m.Sender = netip.MustParseAddr("1.2.3.4")
 	m.Attachment = &attachment{
 	m.Attachment = &attachment{
 		Name:    "flower.jpg",
 		Name:    "flower.jpg",
@@ -333,7 +333,7 @@ func testCacheAttachments(t *testing.T, c *messageCache) {
 	expires2 := time.Now().Add(2 * time.Hour).Unix() // Future
 	expires2 := time.Now().Add(2 * time.Hour).Unix() // Future
 	m = newDefaultMessage("mytopic", "sending you a car")
 	m = newDefaultMessage("mytopic", "sending you a car")
 	m.ID = "m2"
 	m.ID = "m2"
-	m.SID = "m2"
+	m.SequenceID = "m2"
 	m.Sender = netip.MustParseAddr("1.2.3.4")
 	m.Sender = netip.MustParseAddr("1.2.3.4")
 	m.Attachment = &attachment{
 	m.Attachment = &attachment{
 		Name:    "car.jpg",
 		Name:    "car.jpg",
@@ -347,7 +347,7 @@ func testCacheAttachments(t *testing.T, c *messageCache) {
 	expires3 := time.Now().Add(1 * time.Hour).Unix() // Future
 	expires3 := time.Now().Add(1 * time.Hour).Unix() // Future
 	m = newDefaultMessage("another-topic", "sending you another car")
 	m = newDefaultMessage("another-topic", "sending you another car")
 	m.ID = "m3"
 	m.ID = "m3"
-	m.SID = "m3"
+	m.SequenceID = "m3"
 	m.User = "u_BAsbaAa"
 	m.User = "u_BAsbaAa"
 	m.Sender = netip.MustParseAddr("5.6.7.8")
 	m.Sender = netip.MustParseAddr("5.6.7.8")
 	m.Attachment = &attachment{
 	m.Attachment = &attachment{
@@ -403,13 +403,13 @@ func TestMemCache_Attachments_Expired(t *testing.T) {
 func testCacheAttachmentsExpired(t *testing.T, c *messageCache) {
 func testCacheAttachmentsExpired(t *testing.T, c *messageCache) {
 	m := newDefaultMessage("mytopic", "flower for you")
 	m := newDefaultMessage("mytopic", "flower for you")
 	m.ID = "m1"
 	m.ID = "m1"
-	m.SID = "m1"
+	m.SequenceID = "m1"
 	m.Expires = time.Now().Add(time.Hour).Unix()
 	m.Expires = time.Now().Add(time.Hour).Unix()
 	require.Nil(t, c.AddMessage(m))
 	require.Nil(t, c.AddMessage(m))
 
 
 	m = newDefaultMessage("mytopic", "message with attachment")
 	m = newDefaultMessage("mytopic", "message with attachment")
 	m.ID = "m2"
 	m.ID = "m2"
-	m.SID = "m2"
+	m.SequenceID = "m2"
 	m.Expires = time.Now().Add(2 * time.Hour).Unix()
 	m.Expires = time.Now().Add(2 * time.Hour).Unix()
 	m.Attachment = &attachment{
 	m.Attachment = &attachment{
 		Name:    "car.jpg",
 		Name:    "car.jpg",
@@ -422,7 +422,7 @@ func testCacheAttachmentsExpired(t *testing.T, c *messageCache) {
 
 
 	m = newDefaultMessage("mytopic", "message with external attachment")
 	m = newDefaultMessage("mytopic", "message with external attachment")
 	m.ID = "m3"
 	m.ID = "m3"
-	m.SID = "m3"
+	m.SequenceID = "m3"
 	m.Expires = time.Now().Add(2 * time.Hour).Unix()
 	m.Expires = time.Now().Add(2 * time.Hour).Unix()
 	m.Attachment = &attachment{
 	m.Attachment = &attachment{
 		Name:    "car.jpg",
 		Name:    "car.jpg",
@@ -434,7 +434,7 @@ func testCacheAttachmentsExpired(t *testing.T, c *messageCache) {
 
 
 	m = newDefaultMessage("mytopic2", "message with expired attachment")
 	m = newDefaultMessage("mytopic2", "message with expired attachment")
 	m.ID = "m4"
 	m.ID = "m4"
-	m.SID = "m4"
+	m.SequenceID = "m4"
 	m.Expires = time.Now().Add(2 * time.Hour).Unix()
 	m.Expires = time.Now().Add(2 * time.Hour).Unix()
 	m.Attachment = &attachment{
 	m.Attachment = &attachment{
 		Name:    "expired-car.jpg",
 		Name:    "expired-car.jpg",

+ 13 - 13
server/server.go

@@ -917,13 +917,13 @@ func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request, v *visitor
 	if !util.ContainsIP(s.config.VisitorRequestExemptPrefixes, v.ip) && !vrate.MessageAllowed() {
 	if !util.ContainsIP(s.config.VisitorRequestExemptPrefixes, v.ip) && !vrate.MessageAllowed() {
 		return errHTTPTooManyRequestsLimitMessages.With(t)
 		return errHTTPTooManyRequestsLimitMessages.With(t)
 	}
 	}
-	sid, e := s.sidFromPath(r.URL.Path)
+	sequenceID, e := s.sequenceIDFromPath(r.URL.Path)
 	if e != nil {
 	if e != nil {
 		return e.With(t)
 		return e.With(t)
 	}
 	}
-	// Create a delete message: empty body, same SID, deleted flag set
+	// Create a delete message: empty body, same SequenceID, deleted flag set
 	m := newDefaultMessage(t.ID, deletedMessageBody)
 	m := newDefaultMessage(t.ID, deletedMessageBody)
-	m.SID = sid
+	m.SequenceID = sequenceID
 	m.Deleted = true
 	m.Deleted = true
 	m.Sender = v.IP()
 	m.Sender = v.IP()
 	m.User = v.MaybeUserID()
 	m.User = v.MaybeUserID()
@@ -944,7 +944,7 @@ func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request, v *visitor
 	if err := s.messageCache.AddMessage(m); err != nil {
 	if err := s.messageCache.AddMessage(m); err != nil {
 		return err
 		return err
 	}
 	}
-	logvrm(v, r, m).Tag(tagPublish).Debug("Deleted message with SID %s", sid)
+	logvrm(v, r, m).Tag(tagPublish).Debug("Deleted message with sequence ID %s", sequenceID)
 	s.mu.Lock()
 	s.mu.Lock()
 	s.messages++
 	s.messages++
 	s.mu.Unlock()
 	s.mu.Unlock()
@@ -1009,21 +1009,21 @@ func (s *Server) forwardPollRequest(v *visitor, m *message) {
 
 
 func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email, call string, template templateMode, unifiedpush bool, err *errHTTP) {
 func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email, call string, template templateMode, unifiedpush bool, err *errHTTP) {
 	if r.Method != http.MethodGet && updatePathRegex.MatchString(r.URL.Path) {
 	if r.Method != http.MethodGet && updatePathRegex.MatchString(r.URL.Path) {
-		pathSID, err := s.sidFromPath(r.URL.Path)
+		pathSequenceID, err := s.sequenceIDFromPath(r.URL.Path)
 		if err != nil {
 		if err != nil {
 			return false, false, "", "", "", false, err
 			return false, false, "", "", "", false, err
 		}
 		}
-		m.SID = pathSID
+		m.SequenceID = pathSequenceID
 	} else {
 	} else {
-		sid := readParam(r, "x-sequence-id", "sequence-id", "sid")
-		if sid != "" {
-			if sidRegex.MatchString(sid) {
-				m.SID = sid
+		sequenceID := readParam(r, "x-sequence-id", "sequence-id", "sid")
+		if sequenceID != "" {
+			if sidRegex.MatchString(sequenceID) {
+				m.SequenceID = sequenceID
 			} else {
 			} else {
 				return false, false, "", "", "", false, errHTTPBadRequestSIDInvalid
 				return false, false, "", "", "", false, errHTTPBadRequestSIDInvalid
 			}
 			}
 		} else {
 		} else {
-			m.SID = m.ID
+			m.SequenceID = m.ID
 		}
 		}
 	}
 	}
 	cache = readBoolParam(r, true, "x-cache", "cache")
 	cache = readBoolParam(r, true, "x-cache", "cache")
@@ -1764,8 +1764,8 @@ func (s *Server) topicsFromPath(path string) ([]*topic, string, error) {
 	return topics, parts[1], nil
 	return topics, parts[1], nil
 }
 }
 
 
-// sidFromPath returns the SID from a POST path like /mytopic/sidHere
-func (s *Server) sidFromPath(path string) (string, *errHTTP) {
+// sequenceIDFromPath returns the sequence ID from a POST path like /mytopic/sequenceIdHere
+func (s *Server) sequenceIDFromPath(path string) (string, *errHTTP) {
 	parts := strings.Split(path, "/")
 	parts := strings.Split(path, "/")
 	if len(parts) != 3 {
 	if len(parts) != 3 {
 		return "", errHTTPBadRequestSIDInvalid
 		return "", errHTTPBadRequestSIDInvalid

+ 5 - 5
server/server_test.go

@@ -684,7 +684,7 @@ func TestServer_PublishWithSIDInPath(t *testing.T) {
 	response := request(t, s, "POST", "/mytopic/sid", "message", nil)
 	response := request(t, s, "POST", "/mytopic/sid", "message", nil)
 	msg := toMessage(t, response.Body.String())
 	msg := toMessage(t, response.Body.String())
 	require.NotEmpty(t, msg.ID)
 	require.NotEmpty(t, msg.ID)
-	require.Equal(t, "sid", msg.SID)
+	require.Equal(t, "sid", msg.SequenceID)
 }
 }
 
 
 func TestServer_PublishWithSIDInHeader(t *testing.T) {
 func TestServer_PublishWithSIDInHeader(t *testing.T) {
@@ -695,7 +695,7 @@ func TestServer_PublishWithSIDInHeader(t *testing.T) {
 	})
 	})
 	msg := toMessage(t, response.Body.String())
 	msg := toMessage(t, response.Body.String())
 	require.NotEmpty(t, msg.ID)
 	require.NotEmpty(t, msg.ID)
-	require.Equal(t, "sid", msg.SID)
+	require.Equal(t, "sid", msg.SequenceID)
 }
 }
 
 
 func TestServer_PublishWithSIDInPathAndHeader(t *testing.T) {
 func TestServer_PublishWithSIDInPathAndHeader(t *testing.T) {
@@ -706,7 +706,7 @@ func TestServer_PublishWithSIDInPathAndHeader(t *testing.T) {
 	})
 	})
 	msg := toMessage(t, response.Body.String())
 	msg := toMessage(t, response.Body.String())
 	require.NotEmpty(t, msg.ID)
 	require.NotEmpty(t, msg.ID)
-	require.Equal(t, "sid1", msg.SID) // SID in path has priority over SID in header
+	require.Equal(t, "sid1", msg.SequenceID) // Sequence ID in path has priority over header
 }
 }
 
 
 func TestServer_PublishWithSIDInQuery(t *testing.T) {
 func TestServer_PublishWithSIDInQuery(t *testing.T) {
@@ -715,7 +715,7 @@ func TestServer_PublishWithSIDInQuery(t *testing.T) {
 	response := request(t, s, "PUT", "/mytopic?sid=sid1", "message", nil)
 	response := request(t, s, "PUT", "/mytopic?sid=sid1", "message", nil)
 	msg := toMessage(t, response.Body.String())
 	msg := toMessage(t, response.Body.String())
 	require.NotEmpty(t, msg.ID)
 	require.NotEmpty(t, msg.ID)
-	require.Equal(t, "sid1", msg.SID)
+	require.Equal(t, "sid1", msg.SequenceID)
 }
 }
 
 
 func TestServer_PublishWithSIDViaGet(t *testing.T) {
 func TestServer_PublishWithSIDViaGet(t *testing.T) {
@@ -724,7 +724,7 @@ func TestServer_PublishWithSIDViaGet(t *testing.T) {
 	response := request(t, s, "GET", "/mytopic/publish?sid=sid1", "message", nil)
 	response := request(t, s, "GET", "/mytopic/publish?sid=sid1", "message", nil)
 	msg := toMessage(t, response.Body.String())
 	msg := toMessage(t, response.Body.String())
 	require.NotEmpty(t, msg.ID)
 	require.NotEmpty(t, msg.ID)
-	require.Equal(t, "sid1", msg.SID)
+	require.Equal(t, "sid1", msg.SequenceID)
 }
 }
 
 
 func TestServer_PublishWithInvalidSIDInPath(t *testing.T) {
 func TestServer_PublishWithInvalidSIDInPath(t *testing.T) {

+ 28 - 28
server/types.go

@@ -24,11 +24,11 @@ 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,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
+	ID          string      `json:"id"`                    // Random message ID
+	SequenceID  string      `json:"sequence_id,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"`
 	Topic       string      `json:"topic"`
 	Title       string      `json:"title,omitempty"`
 	Title       string      `json:"title,omitempty"`
 	Message     string      `json:"message"` // Allow empty message body
 	Message     string      `json:"message"` // Allow empty message body
@@ -48,12 +48,12 @@ type message struct {
 
 
 func (m *message) Context() log.Context {
 func (m *message) Context() log.Context {
 	fields := map[string]any{
 	fields := map[string]any{
-		"topic":             m.Topic,
-		"message_id":        m.ID,
-		"message_sid":       m.SID,
-		"message_time":      m.Time,
-		"message_event":     m.Event,
-		"message_body_size": len(m.Message),
+		"topic":               m.Topic,
+		"message_id":          m.ID,
+		"message_sequence_id": m.SequenceID,
+		"message_time":        m.Time,
+		"message_event":       m.Event,
+		"message_body_size":   len(m.Message),
 	}
 	}
 	if m.Sender.IsValid() {
 	if m.Sender.IsValid() {
 		fields["message_sender"] = m.Sender.String()
 		fields["message_sender"] = m.Sender.String()
@@ -94,23 +94,23 @@ func newAction() *action {
 
 
 // publishMessage is used as input when publishing as JSON
 // publishMessage is used as input when publishing as JSON
 type publishMessage struct {
 type publishMessage struct {
-	Topic    string   `json:"topic"`
-	SID      string   `json:"sid"`
-	Title    string   `json:"title"`
-	Message  string   `json:"message"`
-	Priority int      `json:"priority"`
-	Tags     []string `json:"tags"`
-	Click    string   `json:"click"`
-	Icon     string   `json:"icon"`
-	Actions  []action `json:"actions"`
-	Attach   string   `json:"attach"`
-	Markdown bool     `json:"markdown"`
-	Filename string   `json:"filename"`
-	Email    string   `json:"email"`
-	Call     string   `json:"call"`
-	Cache    string   `json:"cache"`    // use string as it defaults to true (or use &bool instead)
-	Firebase string   `json:"firebase"` // use string as it defaults to true (or use &bool instead)
-	Delay    string   `json:"delay"`
+	Topic      string   `json:"topic"`
+	SequenceID string   `json:"sequence_id"`
+	Title      string   `json:"title"`
+	Message    string   `json:"message"`
+	Priority   int      `json:"priority"`
+	Tags       []string `json:"tags"`
+	Click      string   `json:"click"`
+	Icon       string   `json:"icon"`
+	Actions    []action `json:"actions"`
+	Attach     string   `json:"attach"`
+	Markdown   bool     `json:"markdown"`
+	Filename   string   `json:"filename"`
+	Email      string   `json:"email"`
+	Call       string   `json:"call"`
+	Cache      string   `json:"cache"`    // use string as it defaults to true (or use &bool instead)
+	Firebase   string   `json:"firebase"` // use string as it defaults to true (or use &bool instead)
+	Delay      string   `json:"delay"`
 }
 }
 
 
 // messageEncoder is a function that knows how to encode a message
 // messageEncoder is a function that knows how to encode a message

+ 6 - 5
web/public/sw.js

@@ -8,7 +8,7 @@ import { dbAsync } from "../src/app/db";
 
 
 import { toNotificationParams, icon, badge } from "../src/app/notificationUtils";
 import { toNotificationParams, icon, badge } from "../src/app/notificationUtils";
 import initI18n from "../src/app/i18n";
 import initI18n from "../src/app/i18n";
-import { messageWithSID } from "../src/app/utils";
+import { messageWithSequenceId } from "../src/app/utils";
 
 
 /**
 /**
  * General docs for service workers and PWAs:
  * General docs for service workers and PWAs:
@@ -27,14 +27,15 @@ const addNotification = async ({ subscriptionId, message }) => {
 
 
   // Note: SubscriptionManager duplicates this logic, so if you change it here, change it there too
   // Note: SubscriptionManager duplicates this logic, so if you change it here, change it there too
 
 
-  // Delete existing notification with same SID (if any)
-  if (message.sid) {
-    await db.notifications.where({ subscriptionId, sid: message.sid }).delete();
+  // Delete existing notification with same sequence ID (if any)
+  const sequenceId = message.sequence_id || message.id;
+  if (sequenceId) {
+    await db.notifications.where({ subscriptionId, sequenceId }).delete();
   }
   }
 
 
   // Add notification to database
   // Add notification to database
   await db.notifications.add({
   await db.notifications.add({
-    ...messageWithSID(message),
+    ...messageWithSequenceId(message),
     subscriptionId,
     subscriptionId,
     new: 1, // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
     new: 1, // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
   });
   });

+ 19 - 16
web/src/app/Poller.js

@@ -47,22 +47,25 @@ class Poller {
     // Filter out notifications older than the prune threshold
     // Filter out notifications older than the prune threshold
     const deleteAfterSeconds = await prefs.deleteAfter();
     const deleteAfterSeconds = await prefs.deleteAfter();
     const pruneThresholdTimestamp = deleteAfterSeconds > 0 ? Math.round(Date.now() / 1000) - deleteAfterSeconds : 0;
     const pruneThresholdTimestamp = deleteAfterSeconds > 0 ? Math.round(Date.now() / 1000) - deleteAfterSeconds : 0;
-    const recentNotifications = pruneThresholdTimestamp > 0 ? notifications.filter((n) => n.time >= pruneThresholdTimestamp) : notifications;
+    const recentNotifications =
+      pruneThresholdTimestamp > 0 ? notifications.filter((n) => n.time >= pruneThresholdTimestamp) : notifications;
 
 
     // Find the latest notification for each sequence ID
     // Find the latest notification for each sequence ID
-    const latestBySid = this.latestNotificationsBySid(recentNotifications);
+    const latestBySequenceId = this.latestNotificationsBySequenceId(recentNotifications);
 
 
     // Delete all existing notifications for which the latest notification is marked as deleted
     // Delete all existing notifications for which the latest notification is marked as deleted
-    const deletedSids = Object.entries(latestBySid)
+    const deletedSequenceIds = Object.entries(latestBySequenceId)
       .filter(([, notification]) => notification.deleted)
       .filter(([, notification]) => notification.deleted)
-      .map(([sid]) => sid);
-    if (deletedSids.length > 0) {
-      console.log(`[Poller] Deleting notifications with deleted sequence IDs for ${subscription.id}`, deletedSids);
-      await Promise.all(deletedSids.map((sid) => subscriptionManager.deleteNotificationBySid(subscription.id, sid)));
+      .map(([sequenceId]) => sequenceId);
+    if (deletedSequenceIds.length > 0) {
+      console.log(`[Poller] Deleting notifications with deleted sequence IDs for ${subscription.id}`, deletedSequenceIds);
+      await Promise.all(
+        deletedSequenceIds.map((sequenceId) => subscriptionManager.deleteNotificationBySequenceId(subscription.id, sequenceId))
+      );
     }
     }
 
 
     // Add only the latest notification for each non-deleted sequence
     // Add only the latest notification for each non-deleted sequence
-    const notificationsToAdd = Object.values(latestBySid).filter((n) => !n.deleted);
+    const notificationsToAdd = Object.values(latestBySequenceId).filter((n) => !n.deleted);
     if (notificationsToAdd.length > 0) {
     if (notificationsToAdd.length > 0) {
       console.log(`[Poller] Adding ${notificationsToAdd.length} notification(s) for ${subscription.id}`);
       console.log(`[Poller] Adding ${notificationsToAdd.length} notification(s) for ${subscription.id}`);
       await subscriptionManager.addNotifications(subscription.id, notificationsToAdd);
       await subscriptionManager.addNotifications(subscription.id, notificationsToAdd);
@@ -82,18 +85,18 @@ class Poller {
   }
   }
 
 
   /**
   /**
-   * Groups notifications by sid and returns only the latest (highest time) for each sequence.
-   * Returns an object mapping sid -> latest notification.
+   * Groups notifications by sequenceId and returns only the latest (highest time) for each sequence.
+   * Returns an object mapping sequenceId -> latest notification.
    */
    */
-  latestNotificationsBySid(notifications) {
-    const latestBySid = {};
+  latestNotificationsBySequenceId(notifications) {
+    const latestBySequenceId = {};
     notifications.forEach((notification) => {
     notifications.forEach((notification) => {
-      const sid = notification.sid || notification.id;
-      if (!(sid in latestBySid) || notification.time >= latestBySid[sid].time) {
-        latestBySid[sid] = notification;
+      const sequenceId = notification.sequence_id || notification.id;
+      if (!(sequenceId in latestBySequenceId) || notification.time >= latestBySequenceId[sequenceId].time) {
+        latestBySequenceId[sequenceId] = notification;
       }
       }
     });
     });
-    return latestBySid;
+    return latestBySequenceId;
   }
   }
 }
 }
 
 

+ 16 - 16
web/src/app/SubscriptionManager.js

@@ -2,7 +2,7 @@ import api from "./Api";
 import notifier from "./Notifier";
 import notifier from "./Notifier";
 import prefs from "./Prefs";
 import prefs from "./Prefs";
 import db from "./db";
 import db from "./db";
-import { messageWithSID, topicUrl } from "./utils";
+import { messageWithSequenceId, topicUrl } from "./utils";
 
 
 class SubscriptionManager {
 class SubscriptionManager {
   constructor(dbImpl) {
   constructor(dbImpl) {
@@ -15,7 +15,7 @@ class SubscriptionManager {
     return Promise.all(
     return Promise.all(
       subscriptions.map(async (s) => ({
       subscriptions.map(async (s) => ({
         ...s,
         ...s,
-        new: await this.db.notifications.where({ subscriptionId: s.id, new: 1 }).count()
+        new: await this.db.notifications.where({ subscriptionId: s.id, new: 1 }).count(),
       }))
       }))
     );
     );
   }
   }
@@ -83,7 +83,7 @@ class SubscriptionManager {
       baseUrl,
       baseUrl,
       topic,
       topic,
       mutedUntil: 0,
       mutedUntil: 0,
-      last: null
+      last: null,
     };
     };
 
 
     await this.db.subscriptions.put(subscription);
     await this.db.subscriptions.put(subscription);
@@ -101,7 +101,7 @@ class SubscriptionManager {
 
 
         const local = await this.add(remote.base_url, remote.topic, {
         const local = await this.add(remote.base_url, remote.topic, {
           displayName: remote.display_name, // May be undefined
           displayName: remote.display_name, // May be undefined
-          reservation // May be null!
+          reservation, // May be null!
         });
         });
 
 
         return local.id;
         return local.id;
@@ -183,15 +183,15 @@ class SubscriptionManager {
 
 
       // Add notification to database
       // Add notification to database
       await this.db.notifications.add({
       await this.db.notifications.add({
-        ...messageWithSID(notification),
+        ...messageWithSequenceId(notification),
         subscriptionId,
         subscriptionId,
-        new: 1 // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
+        new: 1, // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
       });
       });
 
 
       // FIXME consider put() for double tab
       // FIXME consider put() for double tab
       // Update subscription last message id (for ?since=... queries)
       // Update subscription last message id (for ?since=... queries)
       await this.db.subscriptions.update(subscriptionId, {
       await this.db.subscriptions.update(subscriptionId, {
-        last: notification.id
+        last: notification.id,
       });
       });
     } catch (e) {
     } catch (e) {
       console.error(`[SubscriptionManager] Error adding notification`, e);
       console.error(`[SubscriptionManager] Error adding notification`, e);
@@ -202,12 +202,12 @@ class SubscriptionManager {
   /** Adds/replaces notifications, will not throw if they exist */
   /** Adds/replaces notifications, will not throw if they exist */
   async addNotifications(subscriptionId, notifications) {
   async addNotifications(subscriptionId, notifications) {
     const notificationsWithSubscriptionId = notifications.map((notification) => {
     const notificationsWithSubscriptionId = notifications.map((notification) => {
-      return { ...messageWithSID(notification), subscriptionId };
+      return { ...messageWithSequenceId(notification), subscriptionId };
     });
     });
     const lastNotificationId = notifications.at(-1).id;
     const lastNotificationId = notifications.at(-1).id;
     await this.db.notifications.bulkPut(notificationsWithSubscriptionId);
     await this.db.notifications.bulkPut(notificationsWithSubscriptionId);
     await this.db.subscriptions.update(subscriptionId, {
     await this.db.subscriptions.update(subscriptionId, {
-      last: lastNotificationId
+      last: lastNotificationId,
     });
     });
   }
   }
 
 
@@ -228,8 +228,8 @@ class SubscriptionManager {
     await this.db.notifications.delete(notificationId);
     await this.db.notifications.delete(notificationId);
   }
   }
 
 
-  async deleteNotificationBySid(subscriptionId, sid) {
-    await this.db.notifications.where({ subscriptionId, sid }).delete();
+  async deleteNotificationBySequenceId(subscriptionId, sequenceId) {
+    await this.db.notifications.where({ subscriptionId, sequenceId }).delete();
   }
   }
 
 
   async deleteNotifications(subscriptionId) {
   async deleteNotifications(subscriptionId) {
@@ -240,8 +240,8 @@ class SubscriptionManager {
     await this.db.notifications.where({ id: notificationId }).modify({ new: 0 });
     await this.db.notifications.where({ id: notificationId }).modify({ new: 0 });
   }
   }
 
 
-  async markNotificationReadBySid(subscriptionId, sid) {
-    await this.db.notifications.where({ subscriptionId, sid }).modify({ new: 0 });
+  async markNotificationReadBySequenceId(subscriptionId, sequenceId) {
+    await this.db.notifications.where({ subscriptionId, sequenceId }).modify({ new: 0 });
   }
   }
 
 
   async markNotificationsRead(subscriptionId) {
   async markNotificationsRead(subscriptionId) {
@@ -250,19 +250,19 @@ class SubscriptionManager {
 
 
   async setMutedUntil(subscriptionId, mutedUntil) {
   async setMutedUntil(subscriptionId, mutedUntil) {
     await this.db.subscriptions.update(subscriptionId, {
     await this.db.subscriptions.update(subscriptionId, {
-      mutedUntil
+      mutedUntil,
     });
     });
   }
   }
 
 
   async setDisplayName(subscriptionId, displayName) {
   async setDisplayName(subscriptionId, displayName) {
     await this.db.subscriptions.update(subscriptionId, {
     await this.db.subscriptions.update(subscriptionId, {
-      displayName
+      displayName,
     });
     });
   }
   }
 
 
   async setReservation(subscriptionId, reservation) {
   async setReservation(subscriptionId, reservation) {
     await this.db.subscriptions.update(subscriptionId, {
     await this.db.subscriptions.update(subscriptionId, {
-      reservation
+      reservation,
     });
     });
   }
   }
 
 

+ 3 - 4
web/src/app/db.js

@@ -11,12 +11,11 @@ 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(6).stores({
-    // FIXME Should be 3
+  db.version(3).stores({
     subscriptions: "&id,baseUrl,[baseUrl+mutedUntil]",
     subscriptions: "&id,baseUrl,[baseUrl+mutedUntil]",
-    notifications: "&id,sid,subscriptionId,time,new,deleted,[subscriptionId+new],[subscriptionId+sid]",
+    notifications: "&id,sequenceId,subscriptionId,time,new,deleted,[subscriptionId+new],[subscriptionId+sequenceId]",
     users: "&baseUrl,username",
     users: "&baseUrl,username",
-    prefs: "&key",
+    prefs: "&key"
   });
   });
 
 
   return db;
   return db;

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

@@ -62,7 +62,7 @@ export const toNotificationParams = ({ subscriptionId, message, defaultTitle, to
       icon,
       icon,
       image,
       image,
       timestamp: message.time * 1000,
       timestamp: message.time * 1000,
-      tag: message.sid || message.id, // Update notification if there is a sequence ID
+      tag: message.sequence_id || message.id, // Update notification if there is a sequence ID
       renotify: true,
       renotify: true,
       silent: false,
       silent: false,
       // This is used by the notification onclick event
       // This is used by the notification onclick event

+ 3 - 3
web/src/app/utils.js

@@ -103,9 +103,9 @@ export const maybeActionErrors = (notification) => {
   return actionErrors;
   return actionErrors;
 };
 };
 
 
-export const messageWithSID = (message) => {
-  if (!message.sid) {
-    message.sid = message.id;
+export const messageWithSequenceId = (message) => {
+  if (!message.sequenceId) {
+    message.sequenceId = message.sequence_id || message.id;
   }
   }
   return message;
   return message;
 };
 };

+ 4 - 3
web/src/components/hooks.js

@@ -53,9 +53,10 @@ export const useConnectionListeners = (account, subscriptions, users, webPushTop
         // Note: This logic is duplicated in the Android app in SubscriberService::onNotificationReceived()
         // Note: This logic is duplicated in the Android app in SubscriberService::onNotificationReceived()
         //       and FirebaseService::handleMessage().
         //       and FirebaseService::handleMessage().
 
 
-        // Delete existing notification with same sid, if any
-        if (notification.sid) {
-          await subscriptionManager.deleteNotificationBySid(subscriptionId, notification.sid);
+        // Delete existing notification with same sequenceId, if any
+        const sequenceId = notification.sequence_id || notification.id;
+        if (sequenceId) {
+          await subscriptionManager.deleteNotificationBySequenceId(subscriptionId, sequenceId);
         }
         }
         // Add notification to database
         // Add notification to database
         if (!notification.deleted) {
         if (!notification.deleted) {