Merge branch 'main' into attachments
This commit is contained in:
commit
24eb27d41c
8 changed files with 194 additions and 20 deletions
|
@ -6,7 +6,8 @@
|
||||||
[![Tests](https://github.com/binwiederhier/ntfy/workflows/test/badge.svg)](https://github.com/binwiederhier/ntfy/actions)
|
[![Tests](https://github.com/binwiederhier/ntfy/workflows/test/badge.svg)](https://github.com/binwiederhier/ntfy/actions)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/binwiederhier/ntfy)](https://goreportcard.com/report/github.com/binwiederhier/ntfy)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/binwiederhier/ntfy)](https://goreportcard.com/report/github.com/binwiederhier/ntfy)
|
||||||
[![codecov](https://codecov.io/gh/binwiederhier/ntfy/branch/main/graph/badge.svg?token=A597KQ463G)](https://codecov.io/gh/binwiederhier/ntfy)
|
[![codecov](https://codecov.io/gh/binwiederhier/ntfy/branch/main/graph/badge.svg?token=A597KQ463G)](https://codecov.io/gh/binwiederhier/ntfy)
|
||||||
[![Discord](https://img.shields.io/discord/874398661709295626)](https://discord.gg/cT7ECsZj9w)
|
[![Discord](https://img.shields.io/discord/874398661709295626?label=Discord)](https://discord.gg/cT7ECsZj9w)
|
||||||
|
[![Matrix](https://img.shields.io/matrix/ntfy:matrix.org?label=Matrix)](https://matrix.to/#/#ntfy:matrix.org)
|
||||||
[![Healthcheck](https://healthchecks.io/badge/68b65976-b3b0-4102-aec9-980921/kcoEgrLY.svg)](https://ntfy.statuspage.io/)
|
[![Healthcheck](https://healthchecks.io/badge/68b65976-b3b0-4102-aec9-980921/kcoEgrLY.svg)](https://ntfy.statuspage.io/)
|
||||||
|
|
||||||
**ntfy** (pronounce: *notify*) is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) notification service.
|
**ntfy** (pronounce: *notify*) is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) notification service.
|
||||||
|
@ -36,8 +37,9 @@ too.
|
||||||
I welcome any and all contributions. Just create a PR or an issue.
|
I welcome any and all contributions. Just create a PR or an issue.
|
||||||
|
|
||||||
## Contact me
|
## Contact me
|
||||||
You can directly contact me **[on Discord](https://discord.gg/cT7ECsZj9w)**, or via the [GitHub issues](https://github.com/binwiederhier/ntfy/issues),
|
You can directly contact me **[on Discord](https://discord.gg/cT7ECsZj9w)** or [on Matrix](https://matrix.to/#/#ntfy:matrix.org)
|
||||||
or find more contact information [on my website](https://heckel.io/about).
|
(bridged from Discord), or via the [GitHub issues](https://github.com/binwiederhier/ntfy/issues), or find more contact information
|
||||||
|
[on my website](https://heckel.io/about).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).
|
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).
|
||||||
|
|
|
@ -45,6 +45,11 @@ func WithDelay(delay string) PublishOption {
|
||||||
return WithHeader("X-Delay", delay)
|
return WithHeader("X-Delay", delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithClick makes the notification action open the given URL as opposed to entering the detail view
|
||||||
|
func WithClick(url string) PublishOption {
|
||||||
|
return WithHeader("X-Click", url)
|
||||||
|
}
|
||||||
|
|
||||||
// WithEmail instructs the server to also send the message to the given e-mail address
|
// WithEmail instructs the server to also send the message to the given e-mail address
|
||||||
func WithEmail(email string) PublishOption {
|
func WithEmail(email string) PublishOption {
|
||||||
return WithHeader("X-Email", email)
|
return WithHeader("X-Email", email)
|
||||||
|
|
|
@ -20,6 +20,7 @@ var cmdPublish = &cli.Command{
|
||||||
&cli.StringFlag{Name: "priority", Aliases: []string{"p"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"},
|
&cli.StringFlag{Name: "priority", Aliases: []string{"p"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"},
|
||||||
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, Usage: "comma separated list of tags and emojis"},
|
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, Usage: "comma separated list of tags and emojis"},
|
||||||
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, Usage: "delay/schedule message"},
|
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, Usage: "delay/schedule message"},
|
||||||
|
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, Usage: "URL to open when notification is clicked"},
|
||||||
&cli.StringFlag{Name: "email", Aliases: []string{"e-mail", "mail", "e"}, Usage: "also send to e-mail address"},
|
&cli.StringFlag{Name: "email", Aliases: []string{"e-mail", "mail", "e"}, Usage: "also send to e-mail address"},
|
||||||
&cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, Usage: "do not cache message server-side"},
|
&cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, Usage: "do not cache message server-side"},
|
||||||
&cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, Usage: "do not forward message to Firebase"},
|
&cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, Usage: "do not forward message to Firebase"},
|
||||||
|
@ -35,6 +36,7 @@ Examples:
|
||||||
ntfy pub --delay=10s delayed_topic Laterzz # Delay message by 10s
|
ntfy pub --delay=10s delayed_topic Laterzz # Delay message by 10s
|
||||||
ntfy pub --at=8:30am delayed_topic Laterzz # Send message at 8:30am
|
ntfy pub --at=8:30am delayed_topic Laterzz # Send message at 8:30am
|
||||||
ntfy pub -e phil@example.com alerts 'App is down!' # Also send email to phil@example.com
|
ntfy pub -e phil@example.com alerts 'App is down!' # Also send email to phil@example.com
|
||||||
|
ntfy pub --click="https://reddit.com" redd 'New msg' # Opens Reddit when notification is clicked
|
||||||
ntfy trigger mywebhook # Sending without message, useful for webhooks
|
ntfy trigger mywebhook # Sending without message, useful for webhooks
|
||||||
|
|
||||||
Please also check out the docs on publishing messages. Especially for the --tags and --delay options,
|
Please also check out the docs on publishing messages. Especially for the --tags and --delay options,
|
||||||
|
@ -56,6 +58,7 @@ func execPublish(c *cli.Context) error {
|
||||||
priority := c.String("priority")
|
priority := c.String("priority")
|
||||||
tags := c.String("tags")
|
tags := c.String("tags")
|
||||||
delay := c.String("delay")
|
delay := c.String("delay")
|
||||||
|
click := c.String("click")
|
||||||
email := c.String("email")
|
email := c.String("email")
|
||||||
noCache := c.Bool("no-cache")
|
noCache := c.Bool("no-cache")
|
||||||
noFirebase := c.Bool("no-firebase")
|
noFirebase := c.Bool("no-firebase")
|
||||||
|
@ -78,6 +81,9 @@ func execPublish(c *cli.Context) error {
|
||||||
if delay != "" {
|
if delay != "" {
|
||||||
options = append(options, client.WithDelay(delay))
|
options = append(options, client.WithDelay(delay))
|
||||||
}
|
}
|
||||||
|
if click != "" {
|
||||||
|
options = append(options, client.WithClick(email))
|
||||||
|
}
|
||||||
if email != "" {
|
if email != "" {
|
||||||
options = append(options, client.WithEmail(email))
|
options = append(options, client.WithEmail(email))
|
||||||
}
|
}
|
||||||
|
|
|
@ -592,6 +592,73 @@ Here's an example with a custom message, tags and a priority:
|
||||||
file_get_contents('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull');
|
file_get_contents('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Click action
|
||||||
|
You can define which URL to open when a notification is clicked. This may be useful if your notification is related
|
||||||
|
to a Zabbix alert or a transaction that you'd like to provide the deep-link for. Tapping the notification will open
|
||||||
|
the web browser (or the app) and open the website.
|
||||||
|
|
||||||
|
Here's an example that will open Reddit when the notification is clicked:
|
||||||
|
|
||||||
|
=== "Command line (curl)"
|
||||||
|
```
|
||||||
|
curl \
|
||||||
|
-d "New messages on Reddit" \
|
||||||
|
-H "Click: https://www.reddit.com/message/messages" \
|
||||||
|
ntfy.sh/reddit_alerts
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "ntfy CLI"
|
||||||
|
```
|
||||||
|
ntfy publish \
|
||||||
|
--click="https://www.reddit.com/message/messages" \
|
||||||
|
reddit_alerts "New messages on Reddit"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "HTTP"
|
||||||
|
``` http
|
||||||
|
POST /reddit_alerts HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
Click: https://www.reddit.com/message/messages
|
||||||
|
|
||||||
|
New messages on Reddit
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
``` javascript
|
||||||
|
fetch('https://ntfy.sh/reddit_alerts', {
|
||||||
|
method: 'POST',
|
||||||
|
body: 'New messages on Reddit',
|
||||||
|
headers: { 'Click': 'https://www.reddit.com/message/messages' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
``` go
|
||||||
|
req, _ := http.NewRequest("POST", "https://ntfy.sh/reddit_alerts", strings.NewReader("New messages on Reddit"))
|
||||||
|
req.Header.Set("Click", "https://www.reddit.com/message/messages")
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
``` python
|
||||||
|
requests.post("https://ntfy.sh/reddit_alerts",
|
||||||
|
data="New messages on Reddit",
|
||||||
|
headers={ "Click": "https://www.reddit.com/message/messages" })
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PHP"
|
||||||
|
``` php-inline
|
||||||
|
file_get_contents('https://ntfy.sh/reddit_alerts', false, stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'method' => 'POST',
|
||||||
|
'header' =>
|
||||||
|
"Content-Type: text/plain\r\n" .
|
||||||
|
"Click: https://www.reddit.com/message/messages",
|
||||||
|
'content' => 'New messages on Reddit'
|
||||||
|
]
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
|
||||||
## Send files + URLs
|
## Send files + URLs
|
||||||
```
|
```
|
||||||
curl -T image.jpg ntfy.sh/howdy
|
curl -T image.jpg ntfy.sh/howdy
|
||||||
|
@ -903,6 +970,7 @@ and can be passed as **HTTP headers** or **query parameters in the URL**. They a
|
||||||
| `X-Priority` | `Priority`, `prio`, `p` | [Message priority](#message-priority) |
|
| `X-Priority` | `Priority`, `prio`, `p` | [Message priority](#message-priority) |
|
||||||
| `X-Tags` | `Tags`, `Tag`, `ta` | [Tags and emojis](#tags-emojis) |
|
| `X-Tags` | `Tags`, `Tag`, `ta` | [Tags and emojis](#tags-emojis) |
|
||||||
| `X-Delay` | `Delay`, `X-At`, `At`, `X-In`, `In` | Timestamp or duration for [delayed delivery](#scheduled-delivery) |
|
| `X-Delay` | `Delay`, `X-At`, `At`, `X-In`, `In` | Timestamp or duration for [delayed delivery](#scheduled-delivery) |
|
||||||
|
| `X-Click` | `Click` | URL to open when [notification is clicked](#click-action) |
|
||||||
| `X-Filename` | `Filename`, `file`, `f` | XXXXXXXXXXXXXXXX |
|
| `X-Filename` | `Filename`, `file`, `f` | XXXXXXXXXXXXXXXX |
|
||||||
| `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications) |
|
| `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications) |
|
||||||
| `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) |
|
| `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) |
|
||||||
|
|
|
@ -22,6 +22,7 @@ const (
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
priority INT NOT NULL,
|
priority INT NOT NULL,
|
||||||
tags TEXT NOT NULL,
|
tags TEXT NOT NULL,
|
||||||
|
click TEXT NOT NULL,
|
||||||
attachment_name TEXT NOT NULL,
|
attachment_name TEXT NOT NULL,
|
||||||
attachment_type TEXT NOT NULL,
|
attachment_type TEXT NOT NULL,
|
||||||
attachment_size INT NOT NULL,
|
attachment_size INT NOT NULL,
|
||||||
|
@ -34,24 +35,24 @@ const (
|
||||||
COMMIT;
|
COMMIT;
|
||||||
`
|
`
|
||||||
insertMessageQuery = `
|
insertMessageQuery = `
|
||||||
INSERT INTO messages (id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url, published)
|
INSERT INTO messages (id, time, topic, message, title, priority, tags, click, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url, published)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
|
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
|
||||||
selectMessagesSinceTimeQuery = `
|
selectMessagesSinceTimeQuery = `
|
||||||
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
SELECT id, time, topic, message, title, priority, tags, click, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE topic = ? AND time >= ? AND published = 1
|
WHERE topic = ? AND time >= ? AND published = 1
|
||||||
ORDER BY time ASC
|
ORDER BY time ASC
|
||||||
`
|
`
|
||||||
selectMessagesSinceTimeIncludeScheduledQuery = `
|
selectMessagesSinceTimeIncludeScheduledQuery = `
|
||||||
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
SELECT id, time, topic, message, title, priority, tags, click, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE topic = ? AND time >= ?
|
WHERE topic = ? AND time >= ?
|
||||||
ORDER BY time ASC
|
ORDER BY time ASC
|
||||||
`
|
`
|
||||||
selectMessagesDueQuery = `
|
selectMessagesDueQuery = `
|
||||||
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
SELECT id, time, topic, message, title, priority, tags, click, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE time <= ? AND published = 0
|
WHERE time <= ? AND published = 0
|
||||||
`
|
`
|
||||||
|
@ -91,11 +92,13 @@ const (
|
||||||
// 2 -> 3
|
// 2 -> 3
|
||||||
migrate2To3AlterMessagesTableQuery = `
|
migrate2To3AlterMessagesTableQuery = `
|
||||||
BEGIN;
|
BEGIN;
|
||||||
ALTER TABLE messages ADD COLUMN attachment_name TEXT NOT NULL;
|
ALTER TABLE messages ADD COLUMN click TEXT NOT NULL DEFAULT('');
|
||||||
ALTER TABLE messages ADD COLUMN attachment_type TEXT NOT NULL;
|
ALTER TABLE messages ADD COLUMN attachment_name TEXT NOT NULL DEFAULT('');
|
||||||
ALTER TABLE messages ADD COLUMN attachment_size INT NOT NULL;
|
ALTER TABLE messages ADD COLUMN attachment_type TEXT NOT NULL DEFAULT('');
|
||||||
ALTER TABLE messages ADD COLUMN attachment_expires INT NOT NULL;
|
ALTER TABLE messages ADD COLUMN attachment_size INT NOT NULL DEFAULT('0');
|
||||||
ALTER TABLE messages ADD COLUMN attachment_url TEXT NOT NULL;
|
ALTER TABLE messages ADD COLUMN attachment_expires INT NOT NULL DEFAULT('0');
|
||||||
|
ALTER TABLE messages ADD COLUMN attachment_preview_url TEXT NOT NULL DEFAULT('');
|
||||||
|
ALTER TABLE messages ADD COLUMN attachment_url TEXT NOT NULL DEFAULT('');
|
||||||
COMMIT;
|
COMMIT;
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
@ -144,6 +147,7 @@ func (c *sqliteCache) AddMessage(m *message) error {
|
||||||
m.Title,
|
m.Title,
|
||||||
m.Priority,
|
m.Priority,
|
||||||
tags,
|
tags,
|
||||||
|
m.Click,
|
||||||
attachmentName,
|
attachmentName,
|
||||||
attachmentType,
|
attachmentType,
|
||||||
attachmentSize,
|
attachmentSize,
|
||||||
|
@ -234,8 +238,8 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var timestamp, attachmentSize, attachmentExpires int64
|
var timestamp, attachmentSize, attachmentExpires int64
|
||||||
var priority int
|
var priority int
|
||||||
var id, topic, msg, title, tagsStr, attachmentName, attachmentType, attachmentPreviewURL, attachmentURL string
|
var id, topic, msg, title, tagsStr, click, attachmentName, attachmentType, attachmentPreviewURL, attachmentURL string
|
||||||
if err := rows.Scan(&id, ×tamp, &topic, &msg, &title, &priority, &tagsStr, &attachmentName, &attachmentType, &attachmentSize, &attachmentExpires, &attachmentPreviewURL, &attachmentURL); err != nil {
|
if err := rows.Scan(&id, ×tamp, &topic, &msg, &title, &priority, &tagsStr, &click, &attachmentName, &attachmentType, &attachmentSize, &attachmentExpires, &attachmentPreviewURL, &attachmentURL); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var tags []string
|
var tags []string
|
||||||
|
@ -262,6 +266,7 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
||||||
Title: title,
|
Title: title,
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
|
Click: click,
|
||||||
Attachment: att,
|
Attachment: att,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,10 @@ type message struct {
|
||||||
Topic string `json:"topic"`
|
Topic string `json:"topic"`
|
||||||
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"`
|
||||||
|
Attachment *attachment `json:"attachment,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Attachment *attachment `json:"attachment,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type attachment struct {
|
type attachment struct {
|
||||||
|
|
|
@ -148,6 +148,7 @@ var (
|
||||||
const (
|
const (
|
||||||
firebaseControlTopic = "~control" // See Android if changed
|
firebaseControlTopic = "~control" // See Android if changed
|
||||||
emptyMessageBody = "triggered"
|
emptyMessageBody = "triggered"
|
||||||
|
fcmMessageLimit = 4000 // see maybeTruncateFCMMessage for details
|
||||||
)
|
)
|
||||||
|
|
||||||
// New instantiates a new Server. It creates the cache and adds a Firebase
|
// New instantiates a new Server. It creates the cache and adds a Firebase
|
||||||
|
@ -224,6 +225,7 @@ func createFirebaseSubscriber(conf *Config) (subscriber, error) {
|
||||||
"topic": m.Topic,
|
"topic": m.Topic,
|
||||||
"priority": fmt.Sprintf("%d", m.Priority),
|
"priority": fmt.Sprintf("%d", m.Priority),
|
||||||
"tags": strings.Join(m.Tags, ","),
|
"tags": strings.Join(m.Tags, ","),
|
||||||
|
"click": m.Click,
|
||||||
"title": m.Title,
|
"title": m.Title,
|
||||||
"message": m.Message,
|
"message": m.Message,
|
||||||
}
|
}
|
||||||
|
@ -236,14 +238,40 @@ func createFirebaseSubscriber(conf *Config) (subscriber, error) {
|
||||||
data["attachment_url"] = m.Attachment.URL
|
data["attachment_url"] = m.Attachment.URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := msg.Send(context.Background(), &messaging.Message{
|
var androidConfig *messaging.AndroidConfig
|
||||||
Topic: m.Topic,
|
if m.Priority >= 4 {
|
||||||
Data: data,
|
androidConfig = &messaging.AndroidConfig{
|
||||||
})
|
Priority: "high",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := msg.Send(context.Background(), maybeTruncateFCMMessage(&messaging.Message{
|
||||||
|
Topic: m.Topic,
|
||||||
|
Data: data,
|
||||||
|
Android: androidConfig,
|
||||||
|
}))
|
||||||
return err
|
return err
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maybeTruncateFCMMessage performs best-effort truncation of FCM messages.
|
||||||
|
// The docs say the limit is 4000 characters, but during testing it wasn't quite clear
|
||||||
|
// what fields matter; so we're just capping the serialized JSON to 4000 bytes.
|
||||||
|
func maybeTruncateFCMMessage(m *messaging.Message) *messaging.Message {
|
||||||
|
s, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
if len(s) > fcmMessageLimit {
|
||||||
|
over := len(s) - fcmMessageLimit + 16 // = len("truncated":"1",), sigh ...
|
||||||
|
message, ok := m.Data["message"]
|
||||||
|
if ok && len(message) > over {
|
||||||
|
m.Data["truncated"] = "1"
|
||||||
|
m.Data["message"] = message[:len(message)-over]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// Run executes the main server. It listens on HTTP (+ HTTPS, if configured), and starts
|
// Run executes the main server. It listens on HTTP (+ HTTPS, if configured), and starts
|
||||||
// a manager go routine to print stats and prune messages.
|
// a manager go routine to print stats and prune messages.
|
||||||
func (s *Server) Run() error {
|
func (s *Server) Run() error {
|
||||||
|
@ -514,6 +542,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
||||||
firebase = readParam(r, "x-firebase", "firebase") != "no"
|
firebase = readParam(r, "x-firebase", "firebase") != "no"
|
||||||
email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
|
email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
|
||||||
m.Title = readParam(r, "x-title", "title", "t")
|
m.Title = readParam(r, "x-title", "title", "t")
|
||||||
|
m.Click = readParam(r, "x-click", "click")
|
||||||
messageStr := readParam(r, "x-message", "message", "m")
|
messageStr := readParam(r, "x-message", "message", "m")
|
||||||
if messageStr != "" {
|
if messageStr != "" {
|
||||||
m.Message = messageStr
|
m.Message = messageStr
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"firebase.google.com/go/messaging"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -582,6 +583,63 @@ func TestServer_UnifiedPushDiscovery(t *testing.T) {
|
||||||
require.Equal(t, `{"unifiedpush":{"version":1}}`+"\n", response.Body.String())
|
require.Equal(t, `{"unifiedpush":{"version":1}}`+"\n", response.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_MaybeTruncateFCMMessage(t *testing.T) {
|
||||||
|
origMessage := strings.Repeat("this is a long string", 300)
|
||||||
|
origFCMMessage := &messaging.Message{
|
||||||
|
Topic: "mytopic",
|
||||||
|
Data: map[string]string{
|
||||||
|
"id": "abcdefg",
|
||||||
|
"time": "1641324761",
|
||||||
|
"event": "message",
|
||||||
|
"topic": "mytopic",
|
||||||
|
"priority": "0",
|
||||||
|
"tags": "",
|
||||||
|
"title": "",
|
||||||
|
"message": origMessage,
|
||||||
|
},
|
||||||
|
Android: &messaging.AndroidConfig{
|
||||||
|
Priority: "high",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
origMessageLength := len(origFCMMessage.Data["message"])
|
||||||
|
serializedOrigFCMMessage, _ := json.Marshal(origFCMMessage)
|
||||||
|
require.Greater(t, len(serializedOrigFCMMessage), fcmMessageLimit) // Pre-condition
|
||||||
|
|
||||||
|
truncatedFCMMessage := maybeTruncateFCMMessage(origFCMMessage)
|
||||||
|
truncatedMessageLength := len(truncatedFCMMessage.Data["message"])
|
||||||
|
serializedTruncatedFCMMessage, _ := json.Marshal(truncatedFCMMessage)
|
||||||
|
require.Equal(t, fcmMessageLimit, len(serializedTruncatedFCMMessage))
|
||||||
|
require.Equal(t, "1", truncatedFCMMessage.Data["truncated"])
|
||||||
|
require.NotEqual(t, origMessageLength, truncatedMessageLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_MaybeTruncateFCMMessage_NotTooLong(t *testing.T) {
|
||||||
|
origMessage := "not really a long string"
|
||||||
|
origFCMMessage := &messaging.Message{
|
||||||
|
Topic: "mytopic",
|
||||||
|
Data: map[string]string{
|
||||||
|
"id": "abcdefg",
|
||||||
|
"time": "1641324761",
|
||||||
|
"event": "message",
|
||||||
|
"topic": "mytopic",
|
||||||
|
"priority": "0",
|
||||||
|
"tags": "",
|
||||||
|
"title": "",
|
||||||
|
"message": origMessage,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
origMessageLength := len(origFCMMessage.Data["message"])
|
||||||
|
serializedOrigFCMMessage, _ := json.Marshal(origFCMMessage)
|
||||||
|
require.LessOrEqual(t, len(serializedOrigFCMMessage), fcmMessageLimit) // Pre-condition
|
||||||
|
|
||||||
|
notTruncatedFCMMessage := maybeTruncateFCMMessage(origFCMMessage)
|
||||||
|
notTruncatedMessageLength := len(notTruncatedFCMMessage.Data["message"])
|
||||||
|
serializedNotTruncatedFCMMessage, _ := json.Marshal(notTruncatedFCMMessage)
|
||||||
|
require.Equal(t, origMessageLength, notTruncatedMessageLength)
|
||||||
|
require.Equal(t, len(serializedOrigFCMMessage), len(serializedNotTruncatedFCMMessage))
|
||||||
|
require.Equal(t, "", notTruncatedFCMMessage.Data["truncated"])
|
||||||
|
}
|
||||||
|
|
||||||
func newTestConfig(t *testing.T) *Config {
|
func newTestConfig(t *testing.T) *Config {
|
||||||
conf := NewConfig()
|
conf := NewConfig()
|
||||||
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
|
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
|
||||||
|
|
Loading…
Reference in a new issue