Add Web Push, mobile login and manual escalate for the mobile companion

Sandbar sends Web Push (VAPID + aes128gcm, standard library only) to devices
registered for a topic, serves the companion PWA at /app/, allows one CORS
origin, and exposes a read-only Hammerhead edge view and POST .../escalate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-27 16:57:07 -06:00
1 parent b4fd31a182
commit 042a60152c
12 files changed
+1056 -18

No files matched your search

+9 -1
View File
@@ -32,6 +32,7 @@ type Message struct {
Message string `json:"message"`
Priority int `json:"priority"`
Tags []string `json:"tags,omitempty"`
Alert string `json:"alert,omitempty"` // id of the on-call alert this is about; only on live delivery and push, not stored
}
var topicRe = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`)
@@ -101,6 +102,10 @@ func parseTags(s string) []string {
// Publish stores a message and delivers it to live subscribers.
func (s *Server) Publish(topic, title, body string, prio int, tags []string) (Message, error) {
return s.publish(topic, title, body, prio, tags, "", "")
}
func (s *Server) publish(topic, title, body string, prio int, tags []string, alertID, status string) (Message, error) {
if !validTopic(topic) {
return Message{}, errors.New("a topic is 1-64 letters, digits, - or _")
}
@@ -110,7 +115,9 @@ func (s *Server) Publish(topic, title, body string, prio int, tags []string) (Me
m.ID, m.Topic, m.Time, m.Title, m.Message, m.Priority, string(tj)); err != nil {
return m, err
}
m.Alert = alertID
s.hub.publish(m)
s.pushMessage(m, status)
return m, nil
}
@@ -155,6 +162,7 @@ type Note struct {
Severity string `json:"severity,omitempty"`
Link string `json:"link,omitempty"`
Time int64 `json:"time"`
Alert string `json:"alert,omitempty"` // the alert's id, for alerts
}
func (n Note) subject() string {
@@ -291,7 +299,7 @@ func (s *Server) Mail(to, subject, body string) error {
// broadcast sends a note to the topic named by the source and every enabled channel.
func (s *Server) broadcast(n Note, topics ...string) {
for _, t := range topics {
if _, err := s.Publish(t, n.subject(), n.text(), n.priority(), n.tags()); err != nil {
if _, err := s.publish(t, n.subject(), n.text(), n.priority(), n.tags(), n.Alert, n.Status); err != nil {
log.Printf("publish %s: %v", t, err)
}
}