diff --git a/server/server.go b/server/server.go index 890690fb..a22ed7ba 100644 --- a/server/server.go +++ b/server/server.go @@ -114,13 +114,15 @@ var ( ) const ( - firebaseControlTopic = "~control" // See Android if changed - firebasePollTopic = "~poll" // See iOS if changed - emptyMessageBody = "triggered" // Used if message body is empty - newMessageBody = "New message" // Used in poll requests as generic message - defaultAttachmentMessage = "You received a file: %s" // Used if message body is empty, and there is an attachment - encodingBase64 = "base64" // Used mainly for binary UnifiedPush messages - jsonBodyBytesLimit = 16384 + firebaseControlTopic = "~control" // See Android if changed + firebasePollTopic = "~poll" // See iOS if changed + emptyMessageBody = "triggered" // Used if message body is empty + newMessageBody = "New message" // Used in poll requests as generic message + defaultAttachmentMessage = "You received a file: %s" // Used if message body is empty, and there is an attachment + encodingBase64 = "base64" // Used mainly for binary UnifiedPush messages + jsonBodyBytesLimit = 16384 + subscriberBilledTopicPrefix = "up_" + subscriberBilledValidity = 12 * time.Hour ) // WebSocket constants diff --git a/server/topic.go b/server/topic.go index fca5ee0a..6911ec0b 100644 --- a/server/topic.go +++ b/server/topic.go @@ -1,17 +1,21 @@ package server import ( - "heckel.io/ntfy/log" "math/rand" "sync" + "time" + + "heckel.io/ntfy/log" ) // topic represents a channel to which subscribers can subscribe, and publishers // can publish a message type topic struct { - ID string - subscribers map[int]*topicSubscriber - mu sync.Mutex + ID string + subscribers map[int]*topicSubscriber + lastVisitor *visitor + lastVisitorExpires time.Time + mu sync.Mutex } type topicSubscriber struct { @@ -44,10 +48,30 @@ func (t *topic) Subscribe(s subscriber, visitor *visitor, cancel func()) int { return subscriberID } +func (t *topic) Stale() bool { + return t.getBillee() == nil +} + +func (t *topic) getBillee() *visitor { + for _, this_subscriber := range t.subscribers { + return this_subscriber.visitor + } + if t.lastVisitor != nil && t.lastVisitorExpires.After(time.Now()) { + t.lastVisitor = nil + } + return t.lastVisitor + +} + // Unsubscribe removes the subscription from the list of subscribers func (t *topic) Unsubscribe(id int) { t.mu.Lock() defer t.mu.Unlock() + + if len(t.subscribers) == 1 { + t.lastVisitor = t.subscribers[id].visitor + t.lastVisitorExpires = time.Now().Add(subscriberBilledValidity) + } delete(t.subscribers, id) }