mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-12-23 02:02:33 +01:00
WIP: iOS poll_request forwarder
This commit is contained in:
parent
4dabc56952
commit
6a43c1a126
5 changed files with 85 additions and 11 deletions
|
@ -41,6 +41,7 @@ var flagsServe = []cli.Flag{
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", Aliases: []string{"web_root"}, EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home), web app (app) or disabled (disable)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", Aliases: []string{"web_root"}, EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home), web app (app) or disabled (disable)"}),
|
||||||
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "forward-poll-url", Aliases: []string{"forward_poll_url"}, EnvVars: []string{"NTFY_FORWARD_POLL_URL"}, Value: "", Usage: ""}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", Aliases: []string{"smtp_sender_pass"}, EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", Aliases: []string{"smtp_sender_pass"}, EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
||||||
|
@ -102,6 +103,7 @@ func execServe(c *cli.Context) error {
|
||||||
keepaliveInterval := c.Duration("keepalive-interval")
|
keepaliveInterval := c.Duration("keepalive-interval")
|
||||||
managerInterval := c.Duration("manager-interval")
|
managerInterval := c.Duration("manager-interval")
|
||||||
webRoot := c.String("web-root")
|
webRoot := c.String("web-root")
|
||||||
|
forwardPollURL := c.String("forward-poll-url")
|
||||||
smtpSenderAddr := c.String("smtp-sender-addr")
|
smtpSenderAddr := c.String("smtp-sender-addr")
|
||||||
smtpSenderUser := c.String("smtp-sender-user")
|
smtpSenderUser := c.String("smtp-sender-user")
|
||||||
smtpSenderPass := c.String("smtp-sender-pass")
|
smtpSenderPass := c.String("smtp-sender-pass")
|
||||||
|
@ -147,6 +149,8 @@ func execServe(c *cli.Context) error {
|
||||||
return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
||||||
} else if !util.InStringList([]string{"app", "home", "disable"}, webRoot) {
|
} else if !util.InStringList([]string{"app", "home", "disable"}, webRoot) {
|
||||||
return errors.New("if set, web-root must be 'home' or 'app'")
|
return errors.New("if set, web-root must be 'home' or 'app'")
|
||||||
|
} else if forwardPollURL != "" && !strings.HasPrefix(forwardPollURL, "http://") && !strings.HasPrefix(forwardPollURL, "https://") {
|
||||||
|
return errors.New("if set, forward-poll-url must start with http:// or https://")
|
||||||
}
|
}
|
||||||
|
|
||||||
webRootIsApp := webRoot == "app"
|
webRootIsApp := webRoot == "app"
|
||||||
|
@ -215,6 +219,7 @@ func execServe(c *cli.Context) error {
|
||||||
conf.KeepaliveInterval = keepaliveInterval
|
conf.KeepaliveInterval = keepaliveInterval
|
||||||
conf.ManagerInterval = managerInterval
|
conf.ManagerInterval = managerInterval
|
||||||
conf.WebRootIsApp = webRootIsApp
|
conf.WebRootIsApp = webRootIsApp
|
||||||
|
conf.ForwardPollURL = forwardPollURL
|
||||||
conf.SMTPSenderAddr = smtpSenderAddr
|
conf.SMTPSenderAddr = smtpSenderAddr
|
||||||
conf.SMTPSenderUser = smtpSenderUser
|
conf.SMTPSenderUser = smtpSenderUser
|
||||||
conf.SMTPSenderPass = smtpSenderPass
|
conf.SMTPSenderPass = smtpSenderPass
|
||||||
|
|
|
@ -69,6 +69,7 @@ type Config struct {
|
||||||
AtSenderInterval time.Duration
|
AtSenderInterval time.Duration
|
||||||
FirebaseKeepaliveInterval time.Duration
|
FirebaseKeepaliveInterval time.Duration
|
||||||
FirebasePollInterval time.Duration
|
FirebasePollInterval time.Duration
|
||||||
|
ForwardPollURL string
|
||||||
SMTPSenderAddr string
|
SMTPSenderAddr string
|
||||||
SMTPSenderUser string
|
SMTPSenderUser string
|
||||||
SMTPSenderPass string
|
SMTPSenderPass string
|
||||||
|
|
|
@ -3,6 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -93,6 +94,7 @@ const (
|
||||||
firebaseControlTopic = "~control" // See Android if changed
|
firebaseControlTopic = "~control" // See Android if changed
|
||||||
firebasePollTopic = "~poll" // See iOS if changed
|
firebasePollTopic = "~poll" // See iOS if changed
|
||||||
emptyMessageBody = "triggered" // Used if message body is empty
|
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
|
defaultAttachmentMessage = "You received a file: %s" // Used if message body is empty, and there is an attachment
|
||||||
encodingBase64 = "base64"
|
encodingBase64 = "base64"
|
||||||
)
|
)
|
||||||
|
@ -422,6 +424,9 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if m.PollID != "" {
|
||||||
|
m = newPollRequestMessage(t.ID, m.PollID)
|
||||||
|
}
|
||||||
if err := s.handlePublishBody(r, v, m, body, unifiedpush); err != nil {
|
if err := s.handlePublishBody(r, v, m, body, unifiedpush); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -448,6 +453,28 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
if s.config.ForwardPollURL != "" {
|
||||||
|
go func() {
|
||||||
|
topicURL := fmt.Sprintf("%s/%s", s.config.BaseURL, m.Topic)
|
||||||
|
topicHash := fmt.Sprintf("%x", sha256.Sum256([]byte(topicURL)))
|
||||||
|
forwardURL := fmt.Sprintf("%s/%s", s.config.ForwardPollURL, topicHash)
|
||||||
|
log.Printf("forwarding: topicURL %s, to upstream url %s", topicURL, forwardURL)
|
||||||
|
req, err := http.NewRequest("POST", forwardURL, strings.NewReader(""))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set("X-Poll-ID", m.ID)
|
||||||
|
response, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
||||||
|
return
|
||||||
|
} else if response.StatusCode != http.StatusOK {
|
||||||
|
log.Printf("[%s] FWD - Unable to forward poll request, unexpected status: %d", v.ip, response.StatusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
if cache {
|
if cache {
|
||||||
if err := s.messageCache.AddMessage(m); err != nil {
|
if err := s.messageCache.AddMessage(m); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -549,6 +576,12 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
||||||
firebase = false
|
firebase = false
|
||||||
unifiedpush = true
|
unifiedpush = true
|
||||||
}
|
}
|
||||||
|
m.PollID = readParam(r, "x-poll-id", "poll-id", "poll")
|
||||||
|
if m.PollID != "" {
|
||||||
|
unifiedpush = false
|
||||||
|
cache = false
|
||||||
|
email = ""
|
||||||
|
}
|
||||||
return cache, firebase, email, unifiedpush, nil
|
return cache, firebase, email, unifiedpush, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,7 +598,9 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
||||||
// 5. curl -T file.txt ntfy.sh/mytopic
|
// 5. curl -T file.txt ntfy.sh/mytopic
|
||||||
// If file.txt is > message limit, treat it as an attachment
|
// If file.txt is > message limit, treat it as an attachment
|
||||||
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, unifiedpush bool) error {
|
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, unifiedpush bool) error {
|
||||||
if unifiedpush {
|
if m.Event == pollRequestEvent {
|
||||||
|
return nil // Ignore body
|
||||||
|
} else if unifiedpush {
|
||||||
return s.handleBodyAsMessageAutoDetect(m, body) // Case 1
|
return s.handleBodyAsMessageAutoDetect(m, body) // Case 1
|
||||||
} else if m.Attachment != nil && m.Attachment.URL != "" {
|
} else if m.Attachment != nil && m.Attachment.URL != "" {
|
||||||
return s.handleBodyAsTextMessage(m, body) // Case 2
|
return s.handleBodyAsTextMessage(m, body) // Case 2
|
||||||
|
@ -710,6 +745,7 @@ func (s *Server) handleSubscribeHTTP(w http.ResponseWriter, r *http.Request, v *
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
||||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8") // Android/Volley client needs charset!
|
w.Header().Set("Content-Type", contentType+"; charset=utf-8") // Android/Volley client needs charset!
|
||||||
if poll {
|
if poll {
|
||||||
|
log.Printf("polling %#v", r.URL)
|
||||||
return s.sendOldMessages(topics, since, scheduled, sub)
|
return s.sendOldMessages(topics, since, scheduled, sub)
|
||||||
}
|
}
|
||||||
subscriberIDs := make([]int, 0)
|
subscriberIDs := make([]int, 0)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
firebase "firebase.google.com/go"
|
firebase "firebase.google.com/go"
|
||||||
|
@ -64,6 +65,7 @@ func createFirebaseSubscriber(credentialsFile string, auther auth.Auther) (subsc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Printf("Sending %#v %#v", m, fbm)
|
||||||
_, err = msg.Send(context.Background(), fbm)
|
_, err = msg.Send(context.Background(), fbm)
|
||||||
return err
|
return err
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -98,6 +100,31 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
||||||
CustomData: apnsData,
|
CustomData: apnsData,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
case pollRequestEvent:
|
||||||
|
data = map[string]string{
|
||||||
|
"id": m.ID,
|
||||||
|
"time": fmt.Sprintf("%d", m.Time),
|
||||||
|
"event": m.Event,
|
||||||
|
"topic": m.Topic,
|
||||||
|
"message": m.Message,
|
||||||
|
"poll_id": m.PollID,
|
||||||
|
}
|
||||||
|
apnsData := make(map[string]interface{})
|
||||||
|
for k, v := range data {
|
||||||
|
apnsData[k] = v
|
||||||
|
}
|
||||||
|
apnsConfig = &messaging.APNSConfig{
|
||||||
|
Payload: &messaging.APNSPayload{
|
||||||
|
CustomData: apnsData,
|
||||||
|
Aps: &messaging.Aps{
|
||||||
|
MutableContent: true,
|
||||||
|
Alert: &messaging.ApsAlert{
|
||||||
|
Title: m.Title,
|
||||||
|
Body: maybeTruncateAPNSBodyMessage(m.Message),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
case messageEvent:
|
case messageEvent:
|
||||||
allowForward := true
|
allowForward := true
|
||||||
if auther != nil {
|
if auther != nil {
|
||||||
|
|
|
@ -24,13 +24,14 @@ type message struct {
|
||||||
Time int64 `json:"time"` // Unix time in seconds
|
Time int64 `json:"time"` // Unix time in seconds
|
||||||
Event string `json:"event"` // One of the above
|
Event string `json:"event"` // One of the above
|
||||||
Topic string `json:"topic"`
|
Topic string `json:"topic"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
Priority int `json:"priority,omitempty"`
|
Priority int `json:"priority,omitempty"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
Click string `json:"click,omitempty"`
|
Click string `json:"click,omitempty"`
|
||||||
Actions []*action `json:"actions,omitempty"`
|
Actions []*action `json:"actions,omitempty"`
|
||||||
Attachment *attachment `json:"attachment,omitempty"`
|
Attachment *attachment `json:"attachment,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
PollID string `json:"poll_id,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
|
Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,14 +85,11 @@ type messageEncoder func(msg *message) (string, error)
|
||||||
// newMessage creates a new message with the current timestamp
|
// newMessage creates a new message with the current timestamp
|
||||||
func newMessage(event, topic, msg string) *message {
|
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(),
|
||||||
Event: event,
|
Event: event,
|
||||||
Topic: topic,
|
Topic: topic,
|
||||||
Priority: 0,
|
Message: msg,
|
||||||
Tags: nil,
|
|
||||||
Title: "",
|
|
||||||
Message: msg,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +108,13 @@ func newDefaultMessage(topic, msg string) *message {
|
||||||
return newMessage(messageEvent, topic, msg)
|
return newMessage(messageEvent, topic, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newPollRequestMessage is a convenience method to create a poll request message
|
||||||
|
func newPollRequestMessage(topic, pollID string) *message {
|
||||||
|
m := newMessage(pollRequestEvent, topic, newMessageBody)
|
||||||
|
m.PollID = pollID
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func validMessageID(s string) bool {
|
func validMessageID(s string) bool {
|
||||||
return util.ValidRandomString(s, messageIDLength)
|
return util.ValidRandomString(s, messageIDLength)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue