feat(notify): web push as a notification channel
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m15s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m15s
- VAPID keys from VAPID_* or generated once (push_keys, migration 9); devices in push_subscriptions, forgotten on 404/410 from the push service - push column for every event (also due tasks) and the daily summary, bundles as one notification; /push/key, /devices, /subscribe, /unsubscribe, /test - settings: switch push on per device, test it, see the devices; the service worker shows notifications and focuses MindSift on click - tests decrypt a sent push like a browser (RFC 8291) and check VAPID Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a49e6b304
commit
38b8c92899
27 files changed
+918
-27
No files matched your search
+30
-3
@@ -190,7 +190,7 @@ func (s *Server) flushNotifications(now time.Time) {
|
||||
|
||||
// channelItems collects what goes out on each channel.
|
||||
type channelItems struct {
|
||||
email, discord, webhook []notifyItem
|
||||
email, discord, webhook, push []notifyItem
|
||||
}
|
||||
|
||||
func (c *channelItems) add(ch domain.ChannelChoice, items ...notifyItem) {
|
||||
@@ -203,6 +203,9 @@ func (c *channelItems) add(ch domain.ChannelChoice, items ...notifyItem) {
|
||||
if ch.Webhook {
|
||||
c.webhook = append(c.webhook, items...)
|
||||
}
|
||||
if ch.Push {
|
||||
c.push = append(c.push, items...)
|
||||
}
|
||||
}
|
||||
|
||||
// deliver sends the items to the user's mail address, Discord webhook and/or own webhook.
|
||||
@@ -236,6 +239,11 @@ func (s *Server) deliver(userID, heading string, out channelItems) error {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
if len(out.push) > 0 {
|
||||
if err := s.sendPush(userID, pushDigest(heading, out.push, s.notifyLink())); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
@@ -254,6 +262,25 @@ func (s *Server) sendNotifyDiscord(webhookURL, heading string, items []notifyIte
|
||||
return s.sendDiscordDigest(webhookURL, heading, items)
|
||||
}
|
||||
|
||||
// pushDigest: one notification per bundle; a single event is its own title
|
||||
func pushDigest(heading string, items []notifyItem, link string) pushMessage {
|
||||
if link == "" {
|
||||
link = "/"
|
||||
}
|
||||
if len(items) == 1 {
|
||||
return pushMessage{Title: "MindSift", Body: items[0].text, URL: link, Tag: "digest"}
|
||||
}
|
||||
lines := make([]string, 0, 5)
|
||||
for i, it := range items {
|
||||
if i == 4 {
|
||||
lines = append(lines, fmt.Sprintf("… und %d weitere", len(items)-4))
|
||||
break
|
||||
}
|
||||
lines = append(lines, "• "+it.text)
|
||||
}
|
||||
return pushMessage{Title: "MindSift: " + heading, Body: strings.Join(lines, "\n"), URL: link, Tag: "digest"}
|
||||
}
|
||||
|
||||
func (s *Server) sendNotifyWebhook(webhookURL, heading string, items []notifyItem) error {
|
||||
if s.notifyWebhookHook != nil {
|
||||
return s.notifyWebhookHook(webhookURL, heading, items)
|
||||
@@ -377,7 +404,7 @@ func (s *Server) sendDailySummaries(now time.Time) {
|
||||
for _, uid := range s.store.ActiveUserIDs() {
|
||||
set := s.store.NotificationSettingsOf(uid)
|
||||
ds := set.DailySummary
|
||||
if set.Paused || !ds.Enabled || (!ds.Email && !ds.Discord && !ds.Webhook) {
|
||||
if set.Paused || !ds.Enabled || (!ds.Email && !ds.Discord && !ds.Webhook && !ds.Push) {
|
||||
continue
|
||||
}
|
||||
at, err := time.ParseInLocation("2006-01-02 15:04", day+" "+ds.Time, now.Location())
|
||||
@@ -389,7 +416,7 @@ func (s *Server) sendDailySummaries(now time.Time) {
|
||||
continue
|
||||
}
|
||||
var out channelItems
|
||||
out.add(domain.ChannelChoice{Email: ds.Email, Discord: ds.Discord, Webhook: ds.Webhook}, items...)
|
||||
out.add(domain.ChannelChoice{Email: ds.Email, Discord: ds.Discord, Webhook: ds.Webhook, Push: ds.Push}, items...)
|
||||
if err := s.deliver(uid, "Deine Aufgaben für heute", out); err != nil {
|
||||
slog.Warn("Tageszusammenfassung konnte nicht verschickt werden", "user_id", uid, "error", err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user