Remove web-push-(enabled|duration*), change endpoint, other cosmetic changes
parent
4ce6fdcc5a
commit
d3ac976d05
13
cmd/serve.go
13
cmd/serve.go
|
@ -94,13 +94,10 @@ var flagsServe = append(
|
||||||
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-metrics", Aliases: []string{"enable_metrics"}, EnvVars: []string{"NTFY_ENABLE_METRICS"}, Value: false, Usage: "if set, Prometheus metrics are exposed via the /metrics endpoint"}),
|
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-metrics", Aliases: []string{"enable_metrics"}, EnvVars: []string{"NTFY_ENABLE_METRICS"}, Value: false, Usage: "if set, Prometheus metrics are exposed via the /metrics endpoint"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "metrics-listen-http", Aliases: []string{"metrics_listen_http"}, EnvVars: []string{"NTFY_METRICS_LISTEN_HTTP"}, Usage: "ip:port used to expose the metrics endpoint (implicitly enables metrics)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "metrics-listen-http", Aliases: []string{"metrics_listen_http"}, EnvVars: []string{"NTFY_METRICS_LISTEN_HTTP"}, Usage: "ip:port used to expose the metrics endpoint (implicitly enables metrics)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "profile-listen-http", Aliases: []string{"profile_listen_http"}, EnvVars: []string{"NTFY_PROFILE_LISTEN_HTTP"}, Usage: "ip:port used to expose the profiling endpoints (implicitly enables profiling)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "profile-listen-http", Aliases: []string{"profile_listen_http"}, EnvVars: []string{"NTFY_PROFILE_LISTEN_HTTP"}, Usage: "ip:port used to expose the profiling endpoints (implicitly enables profiling)"}),
|
||||||
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "web-push-enabled", Aliases: []string{"web_push_enabled"}, EnvVars: []string{"NTFY_WEB_PUSH_ENABLED"}, Usage: "enable web push (requires public and private key)"}),
|
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-public-key", Aliases: []string{"web_push_public_key"}, EnvVars: []string{"NTFY_WEB_PUSH_PUBLIC_KEY"}, Usage: "public key used for web push notifications"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-public-key", Aliases: []string{"web_push_public_key"}, EnvVars: []string{"NTFY_WEB_PUSH_PUBLIC_KEY"}, Usage: "public key used for web push notifications"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-private-key", Aliases: []string{"web_push_private_key"}, EnvVars: []string{"NTFY_WEB_PUSH_PRIVATE_KEY"}, Usage: "private key used for web push notifications"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-private-key", Aliases: []string{"web_push_private_key"}, EnvVars: []string{"NTFY_WEB_PUSH_PRIVATE_KEY"}, Usage: "private key used for web push notifications"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-subscriptions-file", Aliases: []string{"web_push_subscriptions_file"}, EnvVars: []string{"NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE"}, Usage: "file used to store web push subscriptions"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-subscriptions-file", Aliases: []string{"web_push_subscriptions_file"}, EnvVars: []string{"NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE"}, Usage: "file used to store web push subscriptions"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-email-address", Aliases: []string{"web_push_email_address"}, EnvVars: []string{"NTFY_WEB_PUSH_EMAIL_ADDRESS"}, Usage: "e-mail address of sender, required to use browser push services"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-email-address", Aliases: []string{"web_push_email_address"}, EnvVars: []string{"NTFY_WEB_PUSH_EMAIL_ADDRESS"}, Usage: "e-mail address of sender, required to use browser push services"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "web-push-expiry-warning-duration", Aliases: []string{"web_push_expiry_warning_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION"}, Value: server.DefaultWebPushExpiryWarningDuration, Usage: "duration after last update to send a warning notification"}),
|
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "web-push-expiry-duration", Aliases: []string{"web_push_expiry_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_DURATION"}, Value: server.DefaultWebPushExpiryDuration, Usage: "duration after last update to expire subscription"}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdServe = &cli.Command{
|
var cmdServe = &cli.Command{
|
||||||
|
@ -136,12 +133,9 @@ func execServe(c *cli.Context) error {
|
||||||
keyFile := c.String("key-file")
|
keyFile := c.String("key-file")
|
||||||
certFile := c.String("cert-file")
|
certFile := c.String("cert-file")
|
||||||
firebaseKeyFile := c.String("firebase-key-file")
|
firebaseKeyFile := c.String("firebase-key-file")
|
||||||
webPushEnabled := c.Bool("web-push-enabled")
|
|
||||||
webPushPrivateKey := c.String("web-push-private-key")
|
webPushPrivateKey := c.String("web-push-private-key")
|
||||||
webPushPublicKey := c.String("web-push-public-key")
|
webPushPublicKey := c.String("web-push-public-key")
|
||||||
webPushSubscriptionsFile := c.String("web-push-subscriptions-file")
|
webPushSubscriptionsFile := c.String("web-push-subscriptions-file")
|
||||||
webPushExpiryWarningDuration := c.Duration("web-push-expiry-warning-duration")
|
|
||||||
webPushExpiryDuration := c.Duration("web-push-expiry-duration")
|
|
||||||
webPushEmailAddress := c.String("web-push-email-address")
|
webPushEmailAddress := c.String("web-push-email-address")
|
||||||
cacheFile := c.String("cache-file")
|
cacheFile := c.String("cache-file")
|
||||||
cacheDuration := c.Duration("cache-duration")
|
cacheDuration := c.Duration("cache-duration")
|
||||||
|
@ -197,12 +191,10 @@ func execServe(c *cli.Context) error {
|
||||||
// Check values
|
// Check values
|
||||||
if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) {
|
if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) {
|
||||||
return errors.New("if set, FCM key file must exist")
|
return errors.New("if set, FCM key file must exist")
|
||||||
} else if webPushEnabled && (webPushPrivateKey == "" || webPushPublicKey == "" || webPushSubscriptionsFile == "" || webPushEmailAddress == "" || baseURL == "") {
|
} else if webPushPublicKey != "" && (webPushPrivateKey == "" || webPushSubscriptionsFile == "" || webPushEmailAddress == "" || baseURL == "") {
|
||||||
return errors.New("if web push is enabled, web-push-private-key, web-push-public-key, web-push-subscriptions-file, web-push-email-address, and base-url should be set. run 'ntfy web-push generate-keys' to generate keys")
|
return errors.New("if web push is enabled, web-push-private-key, web-push-public-key, web-push-subscriptions-file, web-push-email-address, and base-url should be set. run 'ntfy web-push generate-keys' to generate keys")
|
||||||
} else if keepaliveInterval < 5*time.Second {
|
} else if keepaliveInterval < 5*time.Second {
|
||||||
return errors.New("keepalive interval cannot be lower than five seconds")
|
return errors.New("keepalive interval cannot be lower than five seconds")
|
||||||
} else if webPushEnabled && (webPushExpiryWarningDuration < 24*time.Hour || (webPushExpiryDuration-webPushExpiryWarningDuration < 24*time.Hour)) {
|
|
||||||
return errors.New("web push expiry warning duration must be at least 1 day, expire duration must be at least 1 day later")
|
|
||||||
} 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 cacheDuration > 0 && cacheDuration < managerInterval {
|
} else if cacheDuration > 0 && cacheDuration < managerInterval {
|
||||||
|
@ -365,13 +357,10 @@ func execServe(c *cli.Context) error {
|
||||||
conf.MetricsListenHTTP = metricsListenHTTP
|
conf.MetricsListenHTTP = metricsListenHTTP
|
||||||
conf.ProfileListenHTTP = profileListenHTTP
|
conf.ProfileListenHTTP = profileListenHTTP
|
||||||
conf.Version = c.App.Version
|
conf.Version = c.App.Version
|
||||||
conf.WebPushEnabled = webPushEnabled
|
|
||||||
conf.WebPushPrivateKey = webPushPrivateKey
|
conf.WebPushPrivateKey = webPushPrivateKey
|
||||||
conf.WebPushPublicKey = webPushPublicKey
|
conf.WebPushPublicKey = webPushPublicKey
|
||||||
conf.WebPushSubscriptionsFile = webPushSubscriptionsFile
|
conf.WebPushSubscriptionsFile = webPushSubscriptionsFile
|
||||||
conf.WebPushEmailAddress = webPushEmailAddress
|
conf.WebPushEmailAddress = webPushEmailAddress
|
||||||
conf.WebPushExpiryDuration = webPushExpiryDuration
|
|
||||||
conf.WebPushExpiryWarningDuration = webPushExpiryWarningDuration
|
|
||||||
|
|
||||||
// Set up hot-reloading of config
|
// Set up hot-reloading of config
|
||||||
go sigHandlerConfigReload(config)
|
go sigHandlerConfigReload(config)
|
||||||
|
|
|
@ -14,7 +14,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdWebPush = &cli.Command{
|
var cmdWebPush = &cli.Command{
|
||||||
Name: "web-push",
|
Name: "webpush",
|
||||||
Usage: "Generate keys, in the future manage web push subscriptions",
|
Usage: "Generate keys, in the future manage web push subscriptions",
|
||||||
UsageText: "ntfy web-push [generate-keys]",
|
UsageText: "ntfy web-push [generate-keys]",
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
|
@ -22,7 +22,7 @@ var cmdWebPush = &cli.Command{
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Action: generateWebPushKeys,
|
Action: generateWebPushKeys,
|
||||||
Name: "generate-keys",
|
Name: "keys",
|
||||||
Usage: "Generate VAPID keys to enable browser background push notifications",
|
Usage: "Generate VAPID keys to enable browser background push notifications",
|
||||||
UsageText: "ntfy web-push generate-keys",
|
UsageText: "ntfy web-push generate-keys",
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
|
@ -36,28 +36,15 @@ func generateWebPushKeys(c *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(c.App.ErrWriter, `Keys generated.
|
fmt.Fprintf(c.App.ErrWriter, `Web Push keys generated. Add the following lines to your config file:
|
||||||
|
|
||||||
VAPID Public Key:
|
|
||||||
%s
|
|
||||||
|
|
||||||
VAPID Private Key:
|
|
||||||
%s
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Add the following lines to your config file:
|
|
||||||
|
|
||||||
web-push-enabled: true
|
|
||||||
web-push-public-key: %s
|
web-push-public-key: %s
|
||||||
web-push-private-key: %s
|
web-push-private-key: %s
|
||||||
web-push-subscriptions-file: <filename>
|
web-push-subscriptions-file: /var/cache/ntfy/webpush.db # or similar
|
||||||
web-push-email-address: <email address>
|
web-push-email-address: <email address>
|
||||||
|
|
||||||
Look at the docs for other methods (e.g. command line flags & environment variables).
|
See https://ntfy.sh/docs/config/#web-push for details.
|
||||||
|
`, publicKey, privateKey)
|
||||||
You will also need to set a base-url.
|
|
||||||
`, publicKey, privateKey, publicKey, privateKey)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,15 +10,15 @@ import (
|
||||||
|
|
||||||
func TestCLI_WebPush_GenerateKeys(t *testing.T) {
|
func TestCLI_WebPush_GenerateKeys(t *testing.T) {
|
||||||
app, _, _, stderr := newTestApp()
|
app, _, _, stderr := newTestApp()
|
||||||
require.Nil(t, runWebPushCommand(app, server.NewConfig(), "generate-keys"))
|
require.Nil(t, runWebPushCommand(app, server.NewConfig(), "keys"))
|
||||||
require.Contains(t, stderr.String(), "Keys generated.")
|
require.Contains(t, stderr.String(), "Web Push keys generated.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runWebPushCommand(app *cli.App, conf *server.Config, args ...string) error {
|
func runWebPushCommand(app *cli.App, conf *server.Config, args ...string) error {
|
||||||
webPushArgs := []string{
|
webPushArgs := []string{
|
||||||
"ntfy",
|
"ntfy",
|
||||||
"--log-level=ERROR",
|
"--log-level=ERROR",
|
||||||
"web-push",
|
"webpush",
|
||||||
}
|
}
|
||||||
return app.Run(append(webPushArgs, args...))
|
return app.Run(append(webPushArgs, args...))
|
||||||
}
|
}
|
||||||
|
|
|
@ -791,9 +791,18 @@ it'll show `New message` as a popup.
|
||||||
|
|
||||||
## Web Push notifications
|
## Web Push notifications
|
||||||
[Web Push](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) ([RFC8030](https://datatracker.ietf.org/doc/html/rfc8030))
|
[Web Push](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) ([RFC8030](https://datatracker.ietf.org/doc/html/rfc8030))
|
||||||
is supported, but needs a little configuration to enable it. Since there is no central server (other than the browser's push endpoint),
|
allows ntfy to receive push notifications, even when the ntfy web app (or even the browser, depending on the platform) is closed.
|
||||||
you have to configure your own [VAPID](https://datatracker.ietf.org/doc/html/draft-thomson-webpush-vapid) keys. These identify the publisher,
|
When enabled, the user can enable **background notifications** for their topics in the wep app under Settings. Once enabled by the
|
||||||
and are used to encrypt the messages before sending them to the push endpoint.
|
user, ntfy will forward published messages to the push endpoint (browser-provided, e.g. fcm.googleapis.com), which will then
|
||||||
|
forward it to the browser.
|
||||||
|
|
||||||
|
To configure Web Push, you need to generate and configure a [VAPID](https://datatracker.ietf.org/doc/html/draft-thomson-webpush-vapid) keypair (via `ntfy webpush keys`),
|
||||||
|
a database to keep track of the browser's subscriptions, and an admin email address (you):
|
||||||
|
|
||||||
|
- `web-push-public-key` is the generated VAPID public key, e.g. AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890
|
||||||
|
- `web-push-private-key` is the generated VAPID private key, e.g. AA2BB1234567890abcdefzxcvbnm1234567890
|
||||||
|
- `web-push-subscriptions-file` is a database file to keep track of browser subscription endpoints, e.g. `/var/cache/ntfy/webpush.db`
|
||||||
|
- `web-push-email-address` is the admin email address send to the push provider, e.g. `sysadmin@example.com`
|
||||||
|
|
||||||
Limitations:
|
Limitations:
|
||||||
|
|
||||||
|
@ -806,32 +815,17 @@ Limitations:
|
||||||
To configure VAPID keys, first generate them:
|
To configure VAPID keys, first generate them:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ntfy web-push generate-keys
|
$ ntfy webpush keys
|
||||||
Keys generated.
|
Web Push keys generated.
|
||||||
|
|
||||||
VAPID Public Key:
|
|
||||||
AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890
|
|
||||||
|
|
||||||
VAPID Private Key:
|
|
||||||
AA2BB1234567890abcdefzxcvbnm1234567890
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then copy the generated values into your `server.yml` or use the corresponding environment variables or command line arguments:
|
Then copy the generated values into your `server.yml` or use the corresponding environment variables or command line arguments:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
web-push-enabled: true
|
|
||||||
web-push-public-key: AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890
|
web-push-public-key: AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890
|
||||||
web-push-private-key: AA2BB1234567890abcdefzxcvbnm1234567890
|
web-push-private-key: AA2BB1234567890abcdefzxcvbnm1234567890
|
||||||
web-push-subscriptions-file: /var/cache/ntfy/subscriptions.db
|
web-push-subscriptions-file: /var/cache/ntfy/webpush.db
|
||||||
web-push-email-address: sysadmin@example.com
|
web-push-email-address: sysadmin@example.com
|
||||||
|
|
||||||
# don't forget to set the required base-url for web push notifications
|
|
||||||
base-url: https://ntfy.example.com
|
|
||||||
|
|
||||||
# you can also set custom expiry and warning durations
|
|
||||||
# the minimum is 1 day for warning, and 1 day after warning for expiry
|
|
||||||
web-push-expiry-warning-duration: 168h
|
|
||||||
web-push-expiry-duration: 192h
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `web-push-subscriptions-file` is used to store the push subscriptions. Subscriptions do not ever expire automatically, unless the push
|
The `web-push-subscriptions-file` is used to store the push subscriptions. Subscriptions do not ever expire automatically, unless the push
|
||||||
|
@ -840,7 +834,7 @@ gateway returns an error (e.g. 410 Gone when a user has unsubscribed).
|
||||||
The web app refreshes subscriptions on start and regularly on an interval, but this file should be persisted across restarts. If the subscription
|
The web app refreshes subscriptions on start and regularly on an interval, but this file should be persisted across restarts. If the subscription
|
||||||
file is deleted or lost, any web apps that aren't open will not receive new web push notifications until you open then.
|
file is deleted or lost, any web apps that aren't open will not receive new web push notifications until you open then.
|
||||||
|
|
||||||
Changing your public/private keypair is NOT recommended. Browsers only allow one server identity (public key) per origin, and
|
Changing your public/private keypair is **not recommended**. Browsers only allow one server identity (public key) per origin, and
|
||||||
if you change them the clients will not be able to subscribe via web push until the user manually clears the notification permission.
|
if you change them the clients will not be able to subscribe via web push until the user manually clears the notification permission.
|
||||||
|
|
||||||
## Tiers
|
## Tiers
|
||||||
|
@ -1340,12 +1334,10 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
|
||||||
| `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe |
|
| `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe |
|
||||||
| `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact |
|
| `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact |
|
||||||
| `web-push-enabled` | `NTFY_WEB_PUSH_ENABLED` | *boolean* (`true` or `false`) | - | Web Push: Enable/disable (requires private and public key below). |
|
| `web-push-enabled` | `NTFY_WEB_PUSH_ENABLED` | *boolean* (`true` or `false`) | - | Web Push: Enable/disable (requires private and public key below). |
|
||||||
| `web-push-public-key` | `NTFY_WEB_PUSH_PUBLIC_KEY` | *string* | - | Web Push: Public Key. Run `ntfy web-push generate-keys` to generate |
|
| `web-push-public-key` | `NTFY_WEB_PUSH_PUBLIC_KEY` | *string* | - | Web Push: Public Key. Run `ntfy webpush generate-keys` to generate |
|
||||||
| `web-push-private-key` | `NTFY_WEB_PUSH_PRIVATE_KEY` | *string* | - | Web Push: Private Key. Run `ntfy web-push generate-keys` to generate |
|
| `web-push-private-key` | `NTFY_WEB_PUSH_PRIVATE_KEY` | *string* | - | Web Push: Private Key. Run `ntfy webpush generate-keys` to generate |
|
||||||
| `web-push-subscriptions-file` | `NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE` | *string* | - | Web Push: Subscriptions file |
|
| `web-push-subscriptions-file` | `NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE` | *string* | - | Web Push: Subscriptions file |
|
||||||
| `web-push-email-address` | `NTFY_WEB_PUSH_EMAIL_ADDRESS` | *string* | - | Web Push: Sender email address |
|
| `web-push-email-address` | `NTFY_WEB_PUSH_EMAIL_ADDRESS` | *string* | - | Web Push: Sender email address |
|
||||||
| `web-push-expiry-warning-duration` | `NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION` | *duration* | 1 week | Web Push: Time before expiry warning is sent (min 1 day) |
|
|
||||||
| `web-push-expiry-duration` | `NTFY_WEB_PUSH_EXPIRY_DURATION` | *duration* | 1 week + 1 day | Web Push: Time before subscription is expired (min 1 day after warning) |
|
|
||||||
|
|
||||||
The format for a *duration* is: `<number>(smh)`, e.g. 30s, 20m or 1h.
|
The format for a *duration* is: `<number>(smh)`, e.g. 30s, 20m or 1h.
|
||||||
The format for a *size* is: `<number>(GMK)`, e.g. 1G, 200M or 4000k.
|
The format for a *size* is: `<number>(GMK)`, e.g. 1G, 200M or 4000k.
|
||||||
|
@ -1443,8 +1435,6 @@ OPTIONS:
|
||||||
--web-push-private-key value, --web_push_private_key value private key used for web push notifications [$NTFY_WEB_PUSH_PRIVATE_KEY]
|
--web-push-private-key value, --web_push_private_key value private key used for web push notifications [$NTFY_WEB_PUSH_PRIVATE_KEY]
|
||||||
--web-push-subscriptions-file value, --web_push_subscriptions_file value file used to store web push subscriptions [$NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE]
|
--web-push-subscriptions-file value, --web_push_subscriptions_file value file used to store web push subscriptions [$NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE]
|
||||||
--web-push-email-address value, --web_push_email_address value e-mail address of sender, required to use browser push services [$NTFY_WEB_PUSH_EMAIL_ADDRESS]
|
--web-push-email-address value, --web_push_email_address value e-mail address of sender, required to use browser push services [$NTFY_WEB_PUSH_EMAIL_ADDRESS]
|
||||||
--web-push-expiry-warning-duration value, --web_push_expiry_warning_duration value duration after last update to send a warning notification (default: 168h0m0s) [$NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION]
|
|
||||||
--web-push-expiry-duration value, --web_push_expiry_duration value duration after last update to expire subscription (default: 192h0m0s) [$NTFY_WEB_PUSH_EXPIRY_DURATION]
|
|
||||||
--help, -h show help
|
--help, -h show help
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -153,7 +153,6 @@ type Config struct {
|
||||||
EnableMetrics bool
|
EnableMetrics bool
|
||||||
AccessControlAllowOrigin string // CORS header field to restrict access from web clients
|
AccessControlAllowOrigin string // CORS header field to restrict access from web clients
|
||||||
Version string // injected by App
|
Version string // injected by App
|
||||||
WebPushEnabled bool
|
|
||||||
WebPushPrivateKey string
|
WebPushPrivateKey string
|
||||||
WebPushPublicKey string
|
WebPushPublicKey string
|
||||||
WebPushSubscriptionsFile string
|
WebPushSubscriptionsFile string
|
||||||
|
@ -241,7 +240,6 @@ func NewConfig() *Config {
|
||||||
EnableReservations: false,
|
EnableReservations: false,
|
||||||
AccessControlAllowOrigin: "*",
|
AccessControlAllowOrigin: "*",
|
||||||
Version: "",
|
Version: "",
|
||||||
WebPushEnabled: false,
|
|
||||||
WebPushPrivateKey: "",
|
WebPushPrivateKey: "",
|
||||||
WebPushPublicKey: "",
|
WebPushPublicKey: "",
|
||||||
WebPushSubscriptionsFile: "",
|
WebPushSubscriptionsFile: "",
|
||||||
|
|
|
@ -94,7 +94,7 @@ var (
|
||||||
apiAccountSettingsPath = "/v1/account/settings"
|
apiAccountSettingsPath = "/v1/account/settings"
|
||||||
apiAccountSubscriptionPath = "/v1/account/subscription"
|
apiAccountSubscriptionPath = "/v1/account/subscription"
|
||||||
apiAccountReservationPath = "/v1/account/reservation"
|
apiAccountReservationPath = "/v1/account/reservation"
|
||||||
apiAccountWebPushPath = "/v1/account/web-push"
|
apiAccountWebPushPath = "/v1/account/webpush"
|
||||||
apiAccountPhonePath = "/v1/account/phone"
|
apiAccountPhonePath = "/v1/account/phone"
|
||||||
apiAccountPhoneVerifyPath = "/v1/account/phone/verify"
|
apiAccountPhoneVerifyPath = "/v1/account/phone/verify"
|
||||||
apiAccountBillingPortalPath = "/v1/account/billing/portal"
|
apiAccountBillingPortalPath = "/v1/account/billing/portal"
|
||||||
|
@ -157,7 +157,7 @@ func New(conf *Config) (*Server, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var webPush *webPushStore
|
var webPush *webPushStore
|
||||||
if conf.WebPushEnabled {
|
if conf.WebPushPublicKey != "" {
|
||||||
webPush, err = newWebPushStore(conf.WebPushSubscriptionsFile)
|
webPush, err = newWebPushStore(conf.WebPushSubscriptionsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -574,7 +574,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
|
||||||
EnableCalls: s.config.TwilioAccount != "",
|
EnableCalls: s.config.TwilioAccount != "",
|
||||||
EnableEmails: s.config.SMTPSenderFrom != "",
|
EnableEmails: s.config.SMTPSenderFrom != "",
|
||||||
EnableReservations: s.config.EnableReservations,
|
EnableReservations: s.config.EnableReservations,
|
||||||
EnableWebPush: s.config.WebPushEnabled,
|
EnableWebPush: s.config.WebPushPublicKey != "",
|
||||||
BillingContact: s.config.BillingContact,
|
BillingContact: s.config.BillingContact,
|
||||||
WebPushPublicKey: s.config.WebPushPublicKey,
|
WebPushPublicKey: s.config.WebPushPublicKey,
|
||||||
DisallowedTopics: s.config.DisallowedTopics,
|
DisallowedTopics: s.config.DisallowedTopics,
|
||||||
|
@ -792,7 +792,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
|
||||||
if s.config.UpstreamBaseURL != "" && !unifiedpush { // UP messages are not sent to upstream
|
if s.config.UpstreamBaseURL != "" && !unifiedpush { // UP messages are not sent to upstream
|
||||||
go s.forwardPollRequest(v, m)
|
go s.forwardPollRequest(v, m)
|
||||||
}
|
}
|
||||||
if s.config.WebPushEnabled {
|
if s.config.WebPushPublicKey != "" {
|
||||||
go s.publishToWebPushEndpoints(v, m)
|
go s.publishToWebPushEndpoints(v, m)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1724,7 +1724,7 @@ func (s *Server) sendDelayedMessage(v *visitor, m *message) error {
|
||||||
if s.config.UpstreamBaseURL != "" {
|
if s.config.UpstreamBaseURL != "" {
|
||||||
go s.forwardPollRequest(v, m)
|
go s.forwardPollRequest(v, m)
|
||||||
}
|
}
|
||||||
if s.config.WebPushEnabled {
|
if s.config.WebPushPublicKey != "" {
|
||||||
go s.publishToWebPushEndpoints(v, m)
|
go s.publishToWebPushEndpoints(v, m)
|
||||||
}
|
}
|
||||||
if err := s.messageCache.MarkPublished(m); err != nil {
|
if err := s.messageCache.MarkPublished(m); err != nil {
|
||||||
|
|
|
@ -40,15 +40,12 @@
|
||||||
|
|
||||||
# Enable web push
|
# Enable web push
|
||||||
#
|
#
|
||||||
# Run "ntfy web-push generate-keys" to generate the keys
|
# Run "ntfy webpush keys" to generate the keys
|
||||||
#
|
#
|
||||||
# web-push-enabled: false
|
|
||||||
# web-push-public-key:
|
# web-push-public-key:
|
||||||
# web-push-private-key:
|
# web-push-private-key:
|
||||||
# web-push-subscriptions-file:
|
# web-push-subscriptions-file:
|
||||||
# web-push-email-address:
|
# web-push-email-address:
|
||||||
# web-push-expiry-warning-duration: 168h
|
|
||||||
# web-push-expiry-duration: 192h
|
|
||||||
|
|
||||||
# If "cache-file" is set, messages are cached in a local SQLite database instead of only in-memory.
|
# If "cache-file" is 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.
|
# This allows for service restarts without losing messages in support of the since= parameter.
|
||||||
|
|
|
@ -15,7 +15,7 @@ func (s *Server) execManager() {
|
||||||
s.pruneTokens()
|
s.pruneTokens()
|
||||||
s.pruneAttachments()
|
s.pruneAttachments()
|
||||||
s.pruneMessages()
|
s.pruneMessages()
|
||||||
if s.config.WebPushEnabled {
|
if s.config.WebPushPublicKey != "" {
|
||||||
s.expireOrNotifyOldSubscriptions()
|
s.expireOrNotifyOldSubscriptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ func (s *Server) ensureWebEnabled(next handleFunc) handleFunc {
|
||||||
|
|
||||||
func (s *Server) ensureWebPushEnabled(next handleFunc) handleFunc {
|
func (s *Server) ensureWebPushEnabled(next handleFunc) handleFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
if !s.config.WebPushEnabled {
|
if s.config.WebPushPublicKey == "" {
|
||||||
return errHTTPNotFound
|
return errHTTPNotFound
|
||||||
}
|
}
|
||||||
return next(w, r, v)
|
return next(w, r, v)
|
||||||
|
|
|
@ -2622,8 +2622,7 @@ func newTestConfigWithWebPush(t *testing.T) *Config {
|
||||||
conf := newTestConfig(t)
|
conf := newTestConfig(t)
|
||||||
privateKey, publicKey, err := webpush.GenerateVAPIDKeys()
|
privateKey, publicKey, err := webpush.GenerateVAPIDKeys()
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
conf.WebPushEnabled = true
|
conf.WebPushSubscriptionsFile = filepath.Join(t.TempDir(), "webpush.db")
|
||||||
conf.WebPushSubscriptionsFile = filepath.Join(t.TempDir(), "subscriptions.db")
|
|
||||||
conf.WebPushEmailAddress = "testing@example.com"
|
conf.WebPushEmailAddress = "testing@example.com"
|
||||||
conf.WebPushPrivateKey = privateKey
|
conf.WebPushPrivateKey = privateKey
|
||||||
conf.WebPushPublicKey = publicKey
|
conf.WebPushPublicKey = publicKey
|
||||||
|
|
|
@ -76,7 +76,7 @@ func (s *Server) publishToWebPushEndpoints(v *visitor, m *message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this should return error
|
// TODO this should return error
|
||||||
// TODO the updated_at field is not actually updated ever
|
// TODO rate limiting
|
||||||
|
|
||||||
func (s *Server) expireOrNotifyOldSubscriptions() {
|
func (s *Server) expireOrNotifyOldSubscriptions() {
|
||||||
subscriptions, err := s.webPush.ExpireAndGetExpiringSubscriptions(s.config.WebPushExpiryWarningDuration, s.config.WebPushExpiryDuration)
|
subscriptions, err := s.webPush.ExpireAndGetExpiringSubscriptions(s.config.WebPushExpiryWarningDuration, s.config.WebPushExpiryDuration)
|
||||||
|
|
|
@ -23,7 +23,7 @@ const (
|
||||||
func TestServer_WebPush_TopicAdd(t *testing.T) {
|
func TestServer_WebPush_TopicAdd(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithWebPush(t))
|
s := newTestServer(t, newTestConfigWithWebPush(t))
|
||||||
|
|
||||||
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), nil)
|
response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), nil)
|
||||||
require.Equal(t, 200, response.Code)
|
require.Equal(t, 200, response.Code)
|
||||||
require.Equal(t, `{"success":true}`+"\n", response.Body.String())
|
require.Equal(t, `{"success":true}`+"\n", response.Body.String())
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ func TestServer_WebPush_TopicAdd(t *testing.T) {
|
||||||
func TestServer_WebPush_TopicAdd_InvalidEndpoint(t *testing.T) {
|
func TestServer_WebPush_TopicAdd_InvalidEndpoint(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithWebPush(t))
|
s := newTestServer(t, newTestConfigWithWebPush(t))
|
||||||
|
|
||||||
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}, "https://ddos-target.example.com/webpush"), nil)
|
response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, "https://ddos-target.example.com/webpush"), nil)
|
||||||
require.Equal(t, 400, response.Code)
|
require.Equal(t, 400, response.Code)
|
||||||
require.Equal(t, `{"code":40039,"http":400,"error":"invalid request: web push endpoint unknown"}`+"\n", response.Body.String())
|
require.Equal(t, `{"code":40039,"http":400,"error":"invalid request: web push endpoint unknown"}`+"\n", response.Body.String())
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ func TestServer_WebPush_TopicAdd_TooManyTopics(t *testing.T) {
|
||||||
topicList[i] = util.RandomString(5)
|
topicList[i] = util.RandomString(5)
|
||||||
}
|
}
|
||||||
|
|
||||||
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, topicList, defaultEndpoint), nil)
|
response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, topicList, defaultEndpoint), nil)
|
||||||
require.Equal(t, 400, response.Code)
|
require.Equal(t, 400, response.Code)
|
||||||
require.Equal(t, `{"code":40040,"http":400,"error":"invalid request: too many web push topic subscriptions"}`+"\n", response.Body.String())
|
require.Equal(t, `{"code":40040,"http":400,"error":"invalid request: too many web push topic subscriptions"}`+"\n", response.Body.String())
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ func TestServer_WebPush_TopicUnsubscribe(t *testing.T) {
|
||||||
addSubscription(t, s, "test-topic", defaultEndpoint)
|
addSubscription(t, s, "test-topic", defaultEndpoint)
|
||||||
requireSubscriptionCount(t, s, "test-topic", 1)
|
requireSubscriptionCount(t, s, "test-topic", 1)
|
||||||
|
|
||||||
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{}, defaultEndpoint), nil)
|
response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{}, defaultEndpoint), nil)
|
||||||
require.Equal(t, 200, response.Code)
|
require.Equal(t, 200, response.Code)
|
||||||
require.Equal(t, `{"success":true}`+"\n", response.Body.String())
|
require.Equal(t, `{"success":true}`+"\n", response.Body.String())
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func TestServer_WebPush_TopicSubscribeProtected_Allowed(t *testing.T) {
|
||||||
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
|
||||||
require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
|
require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
|
||||||
|
|
||||||
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), map[string]string{
|
response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), map[string]string{
|
||||||
"Authorization": util.BasicAuth("ben", "ben"),
|
"Authorization": util.BasicAuth("ben", "ben"),
|
||||||
})
|
})
|
||||||
require.Equal(t, 200, response.Code)
|
require.Equal(t, 200, response.Code)
|
||||||
|
@ -96,7 +96,7 @@ func TestServer_WebPush_TopicSubscribeProtected_Denied(t *testing.T) {
|
||||||
config.AuthDefault = user.PermissionDenyAll
|
config.AuthDefault = user.PermissionDenyAll
|
||||||
s := newTestServer(t, config)
|
s := newTestServer(t, config)
|
||||||
|
|
||||||
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), nil)
|
response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), nil)
|
||||||
require.Equal(t, 403, response.Code)
|
require.Equal(t, 403, response.Code)
|
||||||
|
|
||||||
requireSubscriptionCount(t, s, "test-topic", 0)
|
requireSubscriptionCount(t, s, "test-topic", 0)
|
||||||
|
@ -109,7 +109,7 @@ func TestServer_WebPush_DeleteAccountUnsubscribe(t *testing.T) {
|
||||||
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
|
||||||
require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
|
require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
|
||||||
|
|
||||||
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), map[string]string{
|
response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), map[string]string{
|
||||||
"Authorization": util.BasicAuth("ben", "ben"),
|
"Authorization": util.BasicAuth("ben", "ben"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,9 @@ const (
|
||||||
INSERT OR REPLACE INTO subscriptions (topic, user_id, endpoint, key_auth, key_p256dh)
|
INSERT OR REPLACE INTO subscriptions (topic, user_id, endpoint, key_auth, key_p256dh)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
deleteWebPushSubscriptionByEndpointQuery = `DELETE FROM subscriptions WHERE endpoint = ?`
|
deleteWebPushSubscriptionByEndpointQuery = `DELETE FROM subscriptions WHERE endpoint = ?`
|
||||||
deleteWebPushSubscriptionByUserIDQuery = `DELETE FROM subscriptions WHERE user_id = ?`
|
deleteWebPushSubscriptionByUserIDQuery = `DELETE FROM subscriptions WHERE user_id = ?`
|
||||||
deleteWebPushSubscriptionByTopicAndEndpointQuery = `DELETE FROM subscriptions WHERE topic = ? AND endpoint = ?`
|
deleteWebPushSubscriptionsByAgeQuery = `DELETE FROM subscriptions WHERE warning_sent = 1 AND updated_at <= datetime('now', ?)`
|
||||||
deleteWebPushSubscriptionsByAgeQuery = `DELETE FROM subscriptions WHERE warning_sent = 1 AND updated_at <= datetime('now', ?)`
|
|
||||||
|
|
||||||
selectWebPushSubscriptionsForTopicQuery = `SELECT endpoint, key_auth, key_p256dh, user_id FROM subscriptions WHERE topic = ?`
|
selectWebPushSubscriptionsForTopicQuery = `SELECT endpoint, key_auth, key_p256dh, user_id FROM subscriptions WHERE topic = ?`
|
||||||
selectWebPushSubscriptionsExpiringSoonQuery = `SELECT DISTINCT endpoint, key_auth, key_p256dh FROM subscriptions WHERE warning_sent = 0 AND updated_at <= datetime('now', ?)`
|
selectWebPushSubscriptionsExpiringSoonQuery = `SELECT DISTINCT endpoint, key_auth, key_p256dh FROM subscriptions WHERE warning_sent = 0 AND updated_at <= datetime('now', ?)`
|
||||||
|
@ -169,8 +168,7 @@ func (c *webPushStore) ExpireAndGetExpiringSubscriptions(warningDuration time.Du
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
if err = tx.Commit(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
topicUrlAuth,
|
topicUrlAuth,
|
||||||
topicUrlJsonPoll,
|
topicUrlJsonPoll,
|
||||||
topicUrlJsonPollWithSince,
|
topicUrlJsonPollWithSince,
|
||||||
webPushSubscriptionsUrl,
|
accountWebPushUrl,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import userManager from "./UserManager";
|
import userManager from "./UserManager";
|
||||||
import { fetchOrThrow } from "./errors";
|
import { fetchOrThrow } from "./errors";
|
||||||
|
@ -117,7 +117,7 @@ class Api {
|
||||||
|
|
||||||
async updateWebPushSubscriptions(topics, browserSubscription) {
|
async updateWebPushSubscriptions(topics, browserSubscription) {
|
||||||
const user = await userManager.get(config.base_url);
|
const user = await userManager.get(config.base_url);
|
||||||
const url = webPushSubscriptionsUrl(config.base_url);
|
const url = accountWebPushUrl(config.base_url);
|
||||||
console.log(`[Api] Sending Web Push Subscriptions`, { url, topics, endpoint: browserSubscription.endpoint });
|
console.log(`[Api] Sending Web Push Subscriptions`, { url, topics, endpoint: browserSubscription.endpoint });
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
|
|
|
@ -52,17 +52,14 @@ class Notifier {
|
||||||
if (!this.pushPossible()) {
|
if (!this.pushPossible()) {
|
||||||
throw new Error("Unsupported or denied");
|
throw new Error("Unsupported or denied");
|
||||||
}
|
}
|
||||||
|
|
||||||
const pushManager = await this.pushManager();
|
const pushManager = await this.pushManager();
|
||||||
|
|
||||||
const existingSubscription = await pushManager.getSubscription();
|
const existingSubscription = await pushManager.getSubscription();
|
||||||
|
|
||||||
if (existingSubscription) {
|
if (existingSubscription) {
|
||||||
return existingSubscription;
|
return existingSubscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new subscription only if web push is enabled
|
// Create a new subscription only if web push is enabled.
|
||||||
// it is possible that web push was previously enabled and then disabled again
|
// It is possible that web push was previously enabled and then disabled again
|
||||||
// in which case there would be an existingSubscription.
|
// in which case there would be an existingSubscription.
|
||||||
// but if it was _not_ enabled previously, we reach here, and only create a new
|
// but if it was _not_ enabled previously, we reach here, and only create a new
|
||||||
// subscription if it is now enabled.
|
// subscription if it is now enabled.
|
||||||
|
|
|
@ -113,7 +113,6 @@ class SubscriptionManager {
|
||||||
|
|
||||||
async refreshWebPushSubscriptions(presetTopics) {
|
async refreshWebPushSubscriptions(presetTopics) {
|
||||||
const topics = presetTopics ?? (await this.webPushTopics());
|
const topics = presetTopics ?? (await this.webPushTopics());
|
||||||
|
|
||||||
const browserSubscription = await notifier.getBrowserSubscription();
|
const browserSubscription = await notifier.getBrowserSubscription();
|
||||||
|
|
||||||
if (!browserSubscription) {
|
if (!browserSubscription) {
|
||||||
|
|
|
@ -21,7 +21,6 @@ export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, top
|
||||||
export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`;
|
export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`;
|
||||||
export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`;
|
export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`;
|
||||||
export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic));
|
export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic));
|
||||||
export const webPushSubscriptionsUrl = (baseUrl) => `${baseUrl}/v1/account/web-push`;
|
|
||||||
export const accountUrl = (baseUrl) => `${baseUrl}/v1/account`;
|
export const accountUrl = (baseUrl) => `${baseUrl}/v1/account`;
|
||||||
export const accountPasswordUrl = (baseUrl) => `${baseUrl}/v1/account/password`;
|
export const accountPasswordUrl = (baseUrl) => `${baseUrl}/v1/account/password`;
|
||||||
export const accountTokenUrl = (baseUrl) => `${baseUrl}/v1/account/token`;
|
export const accountTokenUrl = (baseUrl) => `${baseUrl}/v1/account/token`;
|
||||||
|
@ -33,6 +32,7 @@ export const accountBillingSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account
|
||||||
export const accountBillingPortalUrl = (baseUrl) => `${baseUrl}/v1/account/billing/portal`;
|
export const accountBillingPortalUrl = (baseUrl) => `${baseUrl}/v1/account/billing/portal`;
|
||||||
export const accountPhoneUrl = (baseUrl) => `${baseUrl}/v1/account/phone`;
|
export const accountPhoneUrl = (baseUrl) => `${baseUrl}/v1/account/phone`;
|
||||||
export const accountPhoneVerifyUrl = (baseUrl) => `${baseUrl}/v1/account/phone/verify`;
|
export const accountPhoneVerifyUrl = (baseUrl) => `${baseUrl}/v1/account/phone/verify`;
|
||||||
|
export const accountWebPushUrl = (baseUrl) => `${baseUrl}/v1/account/webpush`;
|
||||||
|
|
||||||
export const validUrl = (url) => url.match(/^https?:\/\/.+/);
|
export const validUrl = (url) => url.match(/^https?:\/\/.+/);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue