Clean up readme
parent
7b810acfb5
commit
30a1ffa7cf
132
README.md
132
README.md
|
@ -1,47 +1,110 @@
|
||||||
# ntfy
|
# ntfy
|
||||||
|
|
||||||
ntfy (pronounce: *notify*) is a super simple pub-sub notification service. It allows you to send desktop and (soon) phone notifications
|
**ntfy** (pronounce: *notify*) is a simple HTTP-based [pub-sub]https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) notification service.
|
||||||
via scripts. I run a free version of it on *[ntfy.sh](https://ntfy.sh)*. **No signups or cost.**
|
It allows you to send notifications to your phone or desktop via scripts from any computer, entirely without signup or cost.
|
||||||
|
It's also open source (as you can plainly see) if you want to run your own.
|
||||||
|
|
||||||
|
I run a free version of it at **[ntfy.sh](https://ntfy.sh)**, and there's an [Android app](https://play.google.com/store/apps/details?id=io.heckel.ntfy)
|
||||||
|
too.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Subscribe to a topic
|
### Publishing messages
|
||||||
Topics are created on the fly by subscribing to them. You can create and subscribe to a topic either in a web UI, or in
|
|
||||||
your own app by subscribing to an [SSE](https://en.wikipedia.org/wiki/Server-sent_events)/[EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource),
|
|
||||||
or a JSON or raw feed.
|
|
||||||
|
|
||||||
Because there is no sign-up, **the topic is essentially a password**, so pick something that's not easily guessable.
|
Publishing messages can be done via PUT or POST using. Topics are created on the fly by subscribing or publishing to them.
|
||||||
|
Because there is no sign-up, **the topic is essentially a password**, so pick something that's not easily guessable.
|
||||||
|
|
||||||
Here's how you can create a topic `mytopic`, subscribe to it topic and wait for events. This is using `curl`, but you
|
Here's an example showing how to publish a message using `curl`:
|
||||||
can use any library that can do HTTP GETs:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Subscribe to "mytopic" and output one message per line (\n are replaced with a space)
|
|
||||||
curl -s ntfy.sh/mytopic/raw
|
|
||||||
|
|
||||||
# Subscribe to "mytopic" and output one JSON message per line
|
|
||||||
curl -s ntfy.sh/mytopic/json
|
|
||||||
|
|
||||||
# Subscribe to "mytopic" and output an SSE stream (supported via JS/EventSource)
|
|
||||||
curl -s ntfy.sh/mytopic/sse
|
|
||||||
```
|
|
||||||
|
|
||||||
You can easily script it to execute any command when a message arrives. This sends desktop notifications (just like
|
|
||||||
the web UI, but without it):
|
|
||||||
```
|
|
||||||
while read msg; do
|
|
||||||
[ -n "$msg" ] && notify-send "$msg"
|
|
||||||
done < <(stdbuf -i0 -o0 curl -s ntfy.sh/mytopic/raw)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Publish messages
|
|
||||||
Publishing messages can be done via PUT or POST using. Here's an example using `curl`:
|
|
||||||
```
|
```
|
||||||
curl -d "long process is done" ntfy.sh/mytopic
|
curl -d "long process is done" ntfy.sh/mytopic
|
||||||
```
|
```
|
||||||
|
|
||||||
Messages published to a non-existing topic or a topic without subscribers will not be delivered later. There is (currently)
|
Here's an example in JS with `fetch()` (see [full example](examples)):
|
||||||
no buffering of any kind. If you're not listening, the message won't be delivered.
|
|
||||||
|
```
|
||||||
|
fetch('https://ntfy.sh/mytopic', {
|
||||||
|
method: 'POST', // PUT works too
|
||||||
|
body: 'Hello from the other side.'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subscribe to a topic
|
||||||
|
You can create and subscribe to a topic either in this web UI, or in your own app by subscribing to an
|
||||||
|
[EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource), a JSON feed, or raw feed.
|
||||||
|
|
||||||
|
#### Subscribe via web
|
||||||
|
If you subscribe to a topic via this web UI in the field below, messages published to any subscribed topic
|
||||||
|
will show up as **desktop notification**.
|
||||||
|
|
||||||
|
You can try this easily on **[ntfy.sh](https://ntfy.sh)**.
|
||||||
|
|
||||||
|
#### Subscribe via phone
|
||||||
|
You can use the [Ntfy Android App](https://play.google.com/store/apps/details?id=io.heckel.ntfy) to receive
|
||||||
|
notifications directly on your phone. Just like the server, this app is also [open source](https://github.com/binwiederhier/ntfy-android).
|
||||||
|
|
||||||
|
#### Subscribe via your app, or via the CLI
|
||||||
|
Using [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) in JS, you can consume
|
||||||
|
notifications like this (see [full example](examples)):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const eventSource = new EventSource('https://ntfy.sh/mytopic/sse');<br/>
|
||||||
|
eventSource.onmessage = (e) => {<br/>
|
||||||
|
// Do something with e.data<br/>
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the same `/sse` endpoint via `curl` or any other HTTP library:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s ntfy.sh/mytopic/sse
|
||||||
|
event: open
|
||||||
|
data: {"id":"weSj9RtNkj","time":1635528898,"event":"open","topic":"mytopic"}
|
||||||
|
|
||||||
|
data: {"id":"p0M5y6gcCY","time":1635528909,"event":"message","topic":"mytopic","message":"Hi!"}
|
||||||
|
|
||||||
|
event: keepalive
|
||||||
|
data: {"id":"VNxNIg5fpt","time":1635528928,"event":"keepalive","topic":"test"}
|
||||||
|
```
|
||||||
|
|
||||||
|
To consume JSON instead, use the `/json` endpoint, which prints one message per line:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s ntfy.sh/mytopic/json
|
||||||
|
{"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"}
|
||||||
|
{"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Hi!"}
|
||||||
|
{"id":"DGUDShMCsc","time":1635528787,"event":"keepalive","topic":"mytopic"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the `/raw` endpoint if you need something super simple (empty lines are keepalive messages):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s ntfy.sh/mytopic/raw
|
||||||
|
|
||||||
|
This is a notification
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Message buffering and polling
|
||||||
|
Messages are buffered in memory for a few hours to account for network interruptions of subscribers.
|
||||||
|
You can read back what you missed by using the `since=...` query parameter. It takes either a
|
||||||
|
duration (e.g. `10m` or `30s`) or a Unix timestamp (e.g. `1635528757`):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s "ntfy.sh/mytopic/json?since=10m"
|
||||||
|
# Same output as above, but includes messages from up to 10 minutes ago
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also just poll for messages if you don't like the long-standing connection using the `poll=1`
|
||||||
|
query parameter. The connection will end after all available messages have been read. This parameter has to be
|
||||||
|
combined with `since=`.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s "ntfy.sh/mytopic/json?poll=1&since=10m"
|
||||||
|
# Returns messages from up to 10 minutes ago and ends the connection
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
There are a few usage examples in the [examples](examples) directory. I'm sure there are tons of other ways to use it.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and
|
Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and
|
||||||
|
@ -104,7 +167,6 @@ To build releases, I use [GoReleaser](https://goreleaser.com/). If you have that
|
||||||
## TODO
|
## TODO
|
||||||
- add HTTPS
|
- add HTTPS
|
||||||
- make limits configurable
|
- make limits configurable
|
||||||
- limit max number of subscriptions
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
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.
|
||||||
|
@ -116,4 +178,6 @@ Third party libraries and resources:
|
||||||
* [github.com/urfave/cli/v2](https://github.com/urfave/cli/v2) (MIT) is used to drive the CLI
|
* [github.com/urfave/cli/v2](https://github.com/urfave/cli/v2) (MIT) is used to drive the CLI
|
||||||
* [Mixkit sound](https://mixkit.co/free-sound-effects/notification/) (Mixkit Free License) used as notification sound
|
* [Mixkit sound](https://mixkit.co/free-sound-effects/notification/) (Mixkit Free License) used as notification sound
|
||||||
* [Lato Font](https://www.latofonts.com/) (OFL) is used as a font in the Web UI
|
* [Lato Font](https://www.latofonts.com/) (OFL) is used as a font in the Web UI
|
||||||
* [GoReleaser](https://goreleaser.com/) (MIT) is used to create releases
|
* [GoReleaser](https://goreleaser.com/) (MIT) is used to create releases
|
||||||
|
* [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) (MIT) is used to provide the persistent message cache
|
||||||
|
* [Firebase Admin SDK](https://github.com/firebase/firebase-admin-go) (Apache 2.0) is used to send FCM messages
|
||||||
|
|
16
cmd/app.go
16
cmd/app.go
|
@ -19,9 +19,9 @@ func New() *cli.App {
|
||||||
flags := []cli.Flag{
|
flags := []cli.Flag{
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/config.yml", DefaultText: "/etc/ntfy/config.yml", Usage: "config file"},
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/config.yml", DefaultText: "/etc/ntfy/config.yml", Usage: "config file"},
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: config.DefaultListenHTTP, Usage: "ip:port used to as listen address"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: config.DefaultListenHTTP, Usage: "ip:port used to as listen address"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}),
|
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "firebase-key-file", Aliases: []string{"F"}, EnvVars: []string{"NTFY_FIREBASE_KEY_FILE"}, Usage: "Firebase credentials file; if set additionally publish to FCM topic"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "firebase-key-file", Aliases: []string{"F"}, EnvVars: []string{"NTFY_FIREBASE_KEY_FILE"}, Usage: "Firebase credentials file; if set additionally publish to FCM topic"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "message-buffer-duration", Aliases: []string{"b"}, EnvVars: []string{"NTFY_MESSAGE_BUFFER_DURATION"}, Value: config.DefaultMessageBufferDuration, Usage: "buffer messages in memory for this time to allow `since` requests"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}),
|
||||||
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-duration", Aliases: []string{"b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: config.DefaultCacheDuration, Usage: "buffer messages for this time to allow `since` requests"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: config.DefaultKeepaliveInterval, Usage: "default interval of keepalive messages"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: config.DefaultKeepaliveInterval, Usage: "default interval of keepalive messages"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: config.DefaultManagerInterval, Usage: "default interval of for message pruning and stats printing"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: config.DefaultManagerInterval, Usage: "default interval of for message pruning and stats printing"}),
|
||||||
}
|
}
|
||||||
|
@ -45,9 +45,9 @@ func New() *cli.App {
|
||||||
func execRun(c *cli.Context) error {
|
func execRun(c *cli.Context) error {
|
||||||
// Read all the options
|
// Read all the options
|
||||||
listenHTTP := c.String("listen-http")
|
listenHTTP := c.String("listen-http")
|
||||||
cacheFile := c.String("cache-file")
|
|
||||||
firebaseKeyFile := c.String("firebase-key-file")
|
firebaseKeyFile := c.String("firebase-key-file")
|
||||||
messageBufferDuration := c.Duration("message-buffer-duration")
|
cacheFile := c.String("cache-file")
|
||||||
|
cacheDuration := c.Duration("cache-duration")
|
||||||
keepaliveInterval := c.Duration("keepalive-interval")
|
keepaliveInterval := c.Duration("keepalive-interval")
|
||||||
managerInterval := c.Duration("manager-interval")
|
managerInterval := c.Duration("manager-interval")
|
||||||
|
|
||||||
|
@ -58,15 +58,15 @@ func execRun(c *cli.Context) error {
|
||||||
return errors.New("keepalive interval cannot be lower than five seconds")
|
return errors.New("keepalive interval cannot be lower than five seconds")
|
||||||
} else if managerInterval < 5*time.Second {
|
} else if managerInterval < 5*time.Second {
|
||||||
return errors.New("manager interval cannot be lower than five seconds")
|
return errors.New("manager interval cannot be lower than five seconds")
|
||||||
} else if messageBufferDuration < managerInterval {
|
} else if cacheDuration < managerInterval {
|
||||||
return errors.New("message buffer duration cannot be lower than manager interval")
|
return errors.New("cache duration cannot be lower than manager interval")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run server
|
// Run server
|
||||||
conf := config.New(listenHTTP)
|
conf := config.New(listenHTTP)
|
||||||
conf.CacheFile = cacheFile
|
|
||||||
conf.FirebaseKeyFile = firebaseKeyFile
|
conf.FirebaseKeyFile = firebaseKeyFile
|
||||||
conf.MessageBufferDuration = messageBufferDuration
|
conf.CacheFile = cacheFile
|
||||||
|
conf.CacheDuration = cacheDuration
|
||||||
conf.KeepaliveInterval = keepaliveInterval
|
conf.KeepaliveInterval = keepaliveInterval
|
||||||
conf.ManagerInterval = managerInterval
|
conf.ManagerInterval = managerInterval
|
||||||
s, err := server.New(conf)
|
s, err := server.New(conf)
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
|
|
||||||
// Defines default config settings
|
// Defines default config settings
|
||||||
const (
|
const (
|
||||||
DefaultListenHTTP = ":80"
|
DefaultListenHTTP = ":80"
|
||||||
DefaultMessageBufferDuration = 12 * time.Hour
|
DefaultCacheDuration = 12 * time.Hour
|
||||||
DefaultKeepaliveInterval = 30 * time.Second
|
DefaultKeepaliveInterval = 30 * time.Second
|
||||||
DefaultManagerInterval = time.Minute
|
DefaultManagerInterval = time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines all the limits
|
// Defines all the limits
|
||||||
|
@ -28,9 +28,9 @@ var (
|
||||||
// Config is the main config struct for the application. Use New to instantiate a default config struct.
|
// Config is the main config struct for the application. Use New to instantiate a default config struct.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ListenHTTP string
|
ListenHTTP string
|
||||||
CacheFile string
|
|
||||||
FirebaseKeyFile string
|
FirebaseKeyFile string
|
||||||
MessageBufferDuration time.Duration
|
CacheFile string
|
||||||
|
CacheDuration time.Duration
|
||||||
KeepaliveInterval time.Duration
|
KeepaliveInterval time.Duration
|
||||||
ManagerInterval time.Duration
|
ManagerInterval time.Duration
|
||||||
GlobalTopicLimit int
|
GlobalTopicLimit int
|
||||||
|
@ -43,9 +43,9 @@ type Config struct {
|
||||||
func New(listenHTTP string) *Config {
|
func New(listenHTTP string) *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
ListenHTTP: listenHTTP,
|
ListenHTTP: listenHTTP,
|
||||||
CacheFile: "",
|
|
||||||
FirebaseKeyFile: "",
|
FirebaseKeyFile: "",
|
||||||
MessageBufferDuration: DefaultMessageBufferDuration,
|
CacheFile: "",
|
||||||
|
CacheDuration: DefaultCacheDuration,
|
||||||
KeepaliveInterval: DefaultKeepaliveInterval,
|
KeepaliveInterval: DefaultKeepaliveInterval,
|
||||||
ManagerInterval: DefaultManagerInterval,
|
ManagerInterval: DefaultManagerInterval,
|
||||||
GlobalTopicLimit: defaultGlobalTopicLimit,
|
GlobalTopicLimit: defaultGlobalTopicLimit,
|
||||||
|
|
|
@ -10,10 +10,15 @@
|
||||||
#
|
#
|
||||||
# firebase-key-file: <filename>
|
# firebase-key-file: <filename>
|
||||||
|
|
||||||
|
# If set, messages are cached in a local SQLite database instead of only in-memory. This
|
||||||
|
# allows for service restarts without losing messages in support of the since= parameter.
|
||||||
|
#
|
||||||
|
# cache-file: <filename>
|
||||||
|
|
||||||
# Duration for which messages will be buffered before they are deleted.
|
# Duration for which messages will be buffered before they are deleted.
|
||||||
# This is required to support the "since=..." and "poll=1" parameter.
|
# This is required to support the "since=..." and "poll=1" parameter.
|
||||||
#
|
#
|
||||||
# message-buffer-duration: 12h
|
# cache-duration: 12h
|
||||||
|
|
||||||
# Interval in which keepalive messages are sent to the client. This is to prevent
|
# Interval in which keepalive messages are sent to the client. This is to prevent
|
||||||
# intermediaries closing the connection for inactivity.
|
# intermediaries closing the connection for inactivity.
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type memCache struct {
|
type memCache struct {
|
||||||
messages map[string][]*message
|
messages map[string][]*message
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ cache = (*memCache)(nil)
|
var _ cache = (*memCache)(nil)
|
||||||
|
|
|
@ -19,8 +19,8 @@ const (
|
||||||
CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
|
CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
|
||||||
COMMIT;
|
COMMIT;
|
||||||
`
|
`
|
||||||
insertMessageQuery = `INSERT INTO messages (id, time, topic, message) VALUES (?, ?, ?, ?)`
|
insertMessageQuery = `INSERT INTO messages (id, time, topic, message) VALUES (?, ?, ?, ?)`
|
||||||
pruneMessagesQuery = `DELETE FROM messages WHERE time < ?`
|
pruneMessagesQuery = `DELETE FROM messages WHERE time < ?`
|
||||||
selectMessagesSinceTimeQuery = `
|
selectMessagesSinceTimeQuery = `
|
||||||
SELECT id, time, message
|
SELECT id, time, message
|
||||||
FROM messages
|
FROM messages
|
||||||
|
@ -46,7 +46,7 @@ func newSqliteCache(filename string) (*sqliteCache, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &sqliteCache{
|
return &sqliteCache{
|
||||||
db: db,
|
db: db,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +122,6 @@ func (s *sqliteCache) Topics() (map[string]*topic, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sqliteCache) Prune(keep time.Duration) error {
|
func (c *sqliteCache) Prune(keep time.Duration) error {
|
||||||
_, err := c.db.Exec(pruneMessagesQuery, time.Now().Add(-1 * keep).Unix())
|
_, err := c.db.Exec(pruneMessagesQuery, time.Now().Add(-1*keep).Unix())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<h1><img src="static/img/ntfy.png" alt="ntfy"/><br/>ntfy.sh - simple HTTP-based pub-sub</h1>
|
<h1><img src="static/img/ntfy.png" alt="ntfy"/><br/>ntfy.sh - simple HTTP-based pub-sub</h1>
|
||||||
<p>
|
<p>
|
||||||
<b>ntfy</b> (pronounce: <i>notify</i>) is a simple HTTP-based <a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern">pub-sub</a> notification service.
|
<b>ntfy</b> (pronounce: <i>notify</i>) is a simple HTTP-based <a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern">pub-sub</a> notification service.
|
||||||
It allows you to send <b>desktop notifications via scripts from any computer</b>, entirely <b>without signup or cost</b>.
|
It allows you to send <b>notifications to your phone or desktop via scripts from any computer</b>, entirely <b>without signup or cost</b>.
|
||||||
It's also <a href="https://github.com/binwiederhier/ntfy">open source</a> if you want to run your own.
|
It's also <a href="https://github.com/binwiederhier/ntfy">open source</a> if you want to run your own.
|
||||||
</p>
|
</p>
|
||||||
<p id="error"></p>
|
<p id="error"></p>
|
||||||
|
@ -83,8 +83,8 @@
|
||||||
|
|
||||||
<h3>Subscribe via phone</h3>
|
<h3>Subscribe via phone</h3>
|
||||||
<p>
|
<p>
|
||||||
Once it's approved, you can use the <b>Ntfy Android App</b> to receive notifications directly on your phone. Just like
|
You can use the <a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy">Ntfy Android App</a>
|
||||||
the server, this app is also <a href="https://github.com/binwiederhier/ntfy-android">open source</a>.
|
to receive notifications directly on your phone. Just like the server, this app is also <a href="https://github.com/binwiederhier/ntfy-android">open source</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Subscribe via your app, or via the CLI</h3>
|
<h3>Subscribe via your app, or via the CLI</h3>
|
||||||
|
@ -184,7 +184,7 @@
|
||||||
<h2>Privacy policy</h2>
|
<h2>Privacy policy</h2>
|
||||||
<p>
|
<p>
|
||||||
Neither the server nor the app record any personal information, or share any of the messages and topics with
|
Neither the server nor the app record any personal information, or share any of the messages and topics with
|
||||||
any outside service. All data is exclusively used to make the service function properly. The notable exception
|
any outside service. All data is exclusively used to make the service function properly. The one exception
|
||||||
is the Firebase Cloud Messaging (FCM) service, which is required to provide instant Android notifications (see
|
is the Firebase Cloud Messaging (FCM) service, which is required to provide instant Android notifications (see
|
||||||
FAQ for details).
|
FAQ for details).
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -204,6 +204,9 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
||||||
|
if err := json.NewEncoder(w).Encode(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.messages++
|
s.messages++
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
@ -360,7 +363,7 @@ func (s *Server) updateStatsAndExpire() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune cache
|
// Prune cache
|
||||||
if err := s.cache.Prune(s.config.MessageBufferDuration); err != nil {
|
if err := s.cache.Prune(s.config.CacheDuration); err != nil {
|
||||||
log.Printf("error pruning cache: %s", err.Error())
|
log.Printf("error pruning cache: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue