mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-11-21 19:03:26 +01:00
Polish the poll_request stuff
This commit is contained in:
parent
6a43c1a126
commit
96bb357435
9 changed files with 328 additions and 200 deletions
10
cmd/serve.go
10
cmd/serve.go
|
@ -41,7 +41,7 @@ var flagsServe = []cli.Flag{
|
|||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}),
|
||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", Aliases: []string{"web_root"}, EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home), web app (app) or disabled (disable)"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "forward-poll-url", Aliases: []string{"forward_poll_url"}, EnvVars: []string{"NTFY_FORWARD_POLL_URL"}, Value: "", Usage: ""}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "upstream-base-url", Aliases: []string{"upstream_base_url"}, EnvVars: []string{"NTFY_UPSTREAM_BASE_URL"}, Value: "", Usage: "forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", Aliases: []string{"smtp_sender_pass"}, EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
||||
|
@ -103,7 +103,7 @@ func execServe(c *cli.Context) error {
|
|||
keepaliveInterval := c.Duration("keepalive-interval")
|
||||
managerInterval := c.Duration("manager-interval")
|
||||
webRoot := c.String("web-root")
|
||||
forwardPollURL := c.String("forward-poll-url")
|
||||
upstreamBaseURL := c.String("upstream-base-url")
|
||||
smtpSenderAddr := c.String("smtp-sender-addr")
|
||||
smtpSenderUser := c.String("smtp-sender-user")
|
||||
smtpSenderPass := c.String("smtp-sender-pass")
|
||||
|
@ -149,8 +149,8 @@ func execServe(c *cli.Context) error {
|
|||
return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
||||
} else if !util.InStringList([]string{"app", "home", "disable"}, webRoot) {
|
||||
return errors.New("if set, web-root must be 'home' or 'app'")
|
||||
} else if forwardPollURL != "" && !strings.HasPrefix(forwardPollURL, "http://") && !strings.HasPrefix(forwardPollURL, "https://") {
|
||||
return errors.New("if set, forward-poll-url must start with http:// or https://")
|
||||
} else if upstreamBaseURL != "" && !strings.HasPrefix(upstreamBaseURL, "http://") && !strings.HasPrefix(upstreamBaseURL, "https://") {
|
||||
return errors.New("if set, upstream-base-url must start with http:// or https://")
|
||||
}
|
||||
|
||||
webRootIsApp := webRoot == "app"
|
||||
|
@ -219,7 +219,7 @@ func execServe(c *cli.Context) error {
|
|||
conf.KeepaliveInterval = keepaliveInterval
|
||||
conf.ManagerInterval = managerInterval
|
||||
conf.WebRootIsApp = webRootIsApp
|
||||
conf.ForwardPollURL = forwardPollURL
|
||||
conf.UpstreamBaseURL = upstreamBaseURL
|
||||
conf.SMTPSenderAddr = smtpSenderAddr
|
||||
conf.SMTPSenderUser = smtpSenderUser
|
||||
conf.SMTPSenderPass = smtpSenderPass
|
||||
|
|
3
go.mod
3
go.mod
|
@ -31,7 +31,6 @@ require (
|
|||
cloud.google.com/go/compute v1.6.1 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
|
@ -49,7 +48,7 @@ require (
|
|||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58 // indirect
|
||||
google.golang.org/grpc v1.46.2 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
|
|
28
go.sum
28
go.sum
|
@ -27,8 +27,6 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW
|
|||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
|
||||
cloud.google.com/go v0.101.1 h1:3+/0TAm9JD/PyhkrDWQWi2L197h3euCsM+H+J4iYTR8=
|
||||
cloud.google.com/go v0.101.1/go.mod h1:55HwjsGW4CHD3JrNuMdZtSDsgTs0CuCB/bBTugD+7AA=
|
||||
cloud.google.com/go v0.102.0 h1:DAq3r8y4mDgyB/ZPJ9v/5VJNqjgJAxTn6ZYLlUywOu8=
|
||||
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
|
@ -58,7 +56,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE=
|
||||
cloud.google.com/go/storage v1.22.1 h1:F6IlQJZrZM++apn9V5/VfS3gbTUYg98PS3EMQAzqtfg=
|
||||
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
|
@ -73,8 +70,6 @@ github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQaNqJe0IVznjKRNm23ohAIh9lgtlzc=
|
||||
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
|
@ -175,8 +170,6 @@ github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIG
|
|||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
|
@ -242,8 +235,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/urfave/cli/v2 v2.7.1 h1:DsAOFeI9T0vmUW4LiGR5mhuCIn5kqGIE4WMU2ytmH00=
|
||||
github.com/urfave/cli/v2 v2.7.1/go.mod h1:TYFbtzt/azQoJOrGH5mDfZtS0jIkl/OeFwlRWPR9KRM=
|
||||
github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
|
||||
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
|
@ -267,8 +258,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
|
||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -348,10 +337,7 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
|||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
|
||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca h1:xTaFYiPROfpPhqrfTIDXj0ri1SpfueYT951s4bAuDO8=
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -373,7 +359,6 @@ golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 h1:zwrSfklXn0gxyLRX/aR+q6cgHbV/ItVyzbPlbA+dkAw=
|
||||
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
|
@ -451,8 +436,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbuf
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -565,9 +548,7 @@ google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/S
|
|||
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
||||
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
||||
google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
||||
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
||||
google.golang.org/api v0.80.0 h1:IQWaGVCYnsm4MO3hh+WtSXMzMzuyFx/fuR8qkN3A0Qo=
|
||||
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
||||
google.golang.org/api v0.81.0 h1:o8WF5AvfidafWbFjsRyupxyEQJNUWxLZJCK5NXrxZZ8=
|
||||
google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=
|
||||
|
@ -649,22 +630,17 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2
|
|||
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I=
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220525015930-6ca3db687a9d h1:8BnRR08DxAQ+e2pFx64Q3Ltg/AkrrxyG1LLa1WpomyA=
|
||||
google.golang.org/genproto v0.0.0-20220525015930-6ca3db687a9d/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
|
||||
google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 h1:56rmjc5LUAanErbiNrY+s/Nd47wDQEJkpqS7i43M1I0=
|
||||
google.golang.org/genproto v0.0.0-20220526192754-51939a95c655/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
|
||||
google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58 h1:a221mAAEAzq4Lz6ZWRkcS8ptb2mxoxYSt4N68aRyQHM=
|
||||
google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
|
|
@ -69,7 +69,7 @@ type Config struct {
|
|||
AtSenderInterval time.Duration
|
||||
FirebaseKeepaliveInterval time.Duration
|
||||
FirebasePollInterval time.Duration
|
||||
ForwardPollURL string
|
||||
UpstreamBaseURL string
|
||||
SMTPSenderAddr string
|
||||
SMTPSenderUser string
|
||||
SMTPSenderPass string
|
||||
|
|
|
@ -440,40 +440,13 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
|||
}
|
||||
}
|
||||
if s.firebase != nil && firebase && !delayed {
|
||||
go func() {
|
||||
if err := s.firebase(m); err != nil {
|
||||
log.Printf("[%s] FB - Unable to publish to Firebase: %v", v.ip, err.Error())
|
||||
}
|
||||
}()
|
||||
go s.sendToFirebase(v, m)
|
||||
}
|
||||
if s.mailer != nil && email != "" && !delayed {
|
||||
go func() {
|
||||
if err := s.mailer.Send(v.ip, email, m); err != nil {
|
||||
log.Printf("[%s] MAIL - Unable to send email: %v", v.ip, err.Error())
|
||||
}
|
||||
}()
|
||||
go s.sendEmail(v, m, email)
|
||||
}
|
||||
if s.config.ForwardPollURL != "" {
|
||||
go func() {
|
||||
topicURL := fmt.Sprintf("%s/%s", s.config.BaseURL, m.Topic)
|
||||
topicHash := fmt.Sprintf("%x", sha256.Sum256([]byte(topicURL)))
|
||||
forwardURL := fmt.Sprintf("%s/%s", s.config.ForwardPollURL, topicHash)
|
||||
log.Printf("forwarding: topicURL %s, to upstream url %s", topicURL, forwardURL)
|
||||
req, err := http.NewRequest("POST", forwardURL, strings.NewReader(""))
|
||||
if err != nil {
|
||||
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Poll-ID", m.ID)
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
||||
return
|
||||
} else if response.StatusCode != http.StatusOK {
|
||||
log.Printf("[%s] FWD - Unable to forward poll request, unexpected status: %d", v.ip, response.StatusCode)
|
||||
return
|
||||
}
|
||||
}()
|
||||
if s.config.UpstreamBaseURL != "" {
|
||||
go s.forwardPollRequest(v, m)
|
||||
}
|
||||
if cache {
|
||||
if err := s.messageCache.AddMessage(m); err != nil {
|
||||
|
@ -491,6 +464,38 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) sendToFirebase(v *visitor, m *message) {
|
||||
if err := s.firebase(m); err != nil {
|
||||
log.Printf("[%s] FB - Unable to publish to Firebase: %v", v.ip, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) sendEmail(v *visitor, m *message, email string) {
|
||||
if err := s.mailer.Send(v.ip, email, m); err != nil {
|
||||
log.Printf("[%s] MAIL - Unable to send email: %v", v.ip, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) forwardPollRequest(v *visitor, m *message) {
|
||||
topicURL := fmt.Sprintf("%s/%s", s.config.BaseURL, m.Topic)
|
||||
topicHash := fmt.Sprintf("%x", sha256.Sum256([]byte(topicURL)))
|
||||
forwardURL := fmt.Sprintf("%s/%s", s.config.UpstreamBaseURL, topicHash)
|
||||
req, err := http.NewRequest("POST", forwardURL, strings.NewReader(""))
|
||||
if err != nil {
|
||||
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Poll-ID", m.ID)
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
||||
return
|
||||
} else if response.StatusCode != http.StatusOK {
|
||||
log.Printf("[%s] FWD - Unable to forward poll request, unexpected status: %d", v.ip, response.StatusCode)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (cache bool, firebase bool, email string, unifiedpush bool, err error) {
|
||||
cache = readBoolParam(r, true, "x-cache", "cache")
|
||||
firebase = readBoolParam(r, true, "x-firebase", "firebase")
|
||||
|
@ -587,29 +592,31 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
|||
|
||||
// handlePublishBody consumes the PUT/POST body and decides whether the body is an attachment or the message.
|
||||
//
|
||||
// 1. curl -T somebinarydata.bin "ntfy.sh/mytopic?up=1"
|
||||
// 1. curl -X POST -H "Poll: 1234" ntfy.sh/...
|
||||
// If a message is flagged as poll request, the body does not matter and is discarded
|
||||
// 2. curl -T somebinarydata.bin "ntfy.sh/mytopic?up=1"
|
||||
// If body is binary, encode as base64, if not do not encode
|
||||
// 2. curl -H "Attach: http://example.com/file.jpg" ntfy.sh/mytopic
|
||||
// 3. curl -H "Attach: http://example.com/file.jpg" ntfy.sh/mytopic
|
||||
// Body must be a message, because we attached an external URL
|
||||
// 3. curl -T short.txt -H "Filename: short.txt" ntfy.sh/mytopic
|
||||
// 4. curl -T short.txt -H "Filename: short.txt" ntfy.sh/mytopic
|
||||
// Body must be attachment, because we passed a filename
|
||||
// 4. curl -T file.txt ntfy.sh/mytopic
|
||||
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
|
||||
// 5. curl -T file.txt ntfy.sh/mytopic
|
||||
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
|
||||
// 6. curl -T file.txt ntfy.sh/mytopic
|
||||
// If file.txt is > message limit, treat it as an attachment
|
||||
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, unifiedpush bool) error {
|
||||
if m.Event == pollRequestEvent {
|
||||
return nil // Ignore body
|
||||
if m.Event == pollRequestEvent { // Case 1
|
||||
return nil
|
||||
} else if unifiedpush {
|
||||
return s.handleBodyAsMessageAutoDetect(m, body) // Case 1
|
||||
return s.handleBodyAsMessageAutoDetect(m, body) // Case 2
|
||||
} else if m.Attachment != nil && m.Attachment.URL != "" {
|
||||
return s.handleBodyAsTextMessage(m, body) // Case 2
|
||||
return s.handleBodyAsTextMessage(m, body) // Case 3
|
||||
} else if m.Attachment != nil && m.Attachment.Name != "" {
|
||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 3
|
||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 4
|
||||
} else if !body.LimitReached && utf8.Valid(body.PeekedBytes) {
|
||||
return s.handleBodyAsTextMessage(m, body) // Case 4
|
||||
return s.handleBodyAsTextMessage(m, body) // Case 5
|
||||
}
|
||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 5
|
||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 6
|
||||
}
|
||||
|
||||
func (s *Server) handleBodyAsMessageAutoDetect(m *message, body *util.PeekedReadCloser) error {
|
||||
|
@ -745,7 +752,6 @@ func (s *Server) handleSubscribeHTTP(w http.ResponseWriter, r *http.Request, v *
|
|||
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8") // Android/Volley client needs charset!
|
||||
if poll {
|
||||
log.Printf("polling %#v", r.URL)
|
||||
return s.sendOldMessages(topics, since, scheduled, sub)
|
||||
}
|
||||
subscriberIDs := make([]int, 0)
|
||||
|
@ -1114,7 +1120,6 @@ func (s *Server) runFirebaseKeepaliver() {
|
|||
log.Printf("error sending Firebase keepalive message to %s: %s", firebaseControlTopic, err.Error())
|
||||
}
|
||||
case <-time.After(s.config.FirebasePollInterval):
|
||||
log.Printf("Sending to timer topic %s", firebasePollTopic)
|
||||
if err := s.firebase(newKeepaliveMessage(firebasePollTopic)); err != nil {
|
||||
log.Printf("error sending Firebase keepalive message to %s: %s", firebasePollTopic, err.Error())
|
||||
}
|
||||
|
|
|
@ -135,6 +135,18 @@
|
|||
#
|
||||
# web-root: app
|
||||
|
||||
# Server URL of a Firebase/APNS-connected ntfy server (likely "https://ntfy.sh").
|
||||
#
|
||||
# iOS users:
|
||||
# If you use the iOS ntfy app, you MUST configure this to receive timely notifications. You'll like want this:
|
||||
# upstream-base-url: "https://ntfy.sh"
|
||||
#
|
||||
# If set, all incoming messages will publish a "poll_request" message to the configured upstream server, containing
|
||||
# the message ID of the original message, instructing the iOS app to poll this server for the actual message contents.
|
||||
# This is to prevent the upstream server and Firebase/APNS from being able to read the message.
|
||||
#
|
||||
# upstream-base-url:
|
||||
|
||||
# Rate limiting: Total number of topics before the server rejects new topics.
|
||||
#
|
||||
# global-topic-limit: 15000
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
firebase "firebase.google.com/go"
|
||||
|
@ -18,39 +17,6 @@ const (
|
|||
fcmApnsBodyMessageLimit = 100
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// maybeTruncateAPNSBodyMessage truncates the body for APNS.
|
||||
//
|
||||
// The "body" of the push notification can contain the entire message, which would count doubly for the overall length
|
||||
// of the APNS payload. I set a limit of 100 characters before truncating the notification "body" with ellipsis.
|
||||
// The message would not be changed (unless truncated for being too long). Note: if the payload is too large (>4KB),
|
||||
// APNS will simply reject / discard the notification, meaning it will never arrive on the iOS device.
|
||||
func maybeTruncateAPNSBodyMessage(s string) string {
|
||||
if len(s) >= fcmApnsBodyMessageLimit {
|
||||
over := len(s) - fcmApnsBodyMessageLimit + 3 // len("...")
|
||||
return s[:len(s)-over] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func createFirebaseSubscriber(credentialsFile string, auther auth.Auther) (subscriber, error) {
|
||||
fb, err := firebase.NewApp(context.Background(), nil, option.WithCredentialsFile(credentialsFile))
|
||||
if err != nil {
|
||||
|
@ -65,12 +31,31 @@ func createFirebaseSubscriber(credentialsFile string, auther auth.Auther) (subsc
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Sending %#v %#v", m, fbm)
|
||||
_, err = msg.Send(context.Background(), fbm)
|
||||
return err
|
||||
}, nil
|
||||
}
|
||||
|
||||
// toFirebaseMessage converts a message to a Firebase message.
|
||||
//
|
||||
// Normal messages ("message"):
|
||||
// - For Android, we can receive data messages from Firebase and process them as code, so we just send all fields
|
||||
// in the "data" attribute. In the Android app, we then turn those into a notification and display it.
|
||||
// - On iOS, we are not allowed to receive data-only messages, so we build messages with an "alert" (with title and
|
||||
// message), and still send the rest of the data along in the "aps" attribute. We can then locally modify the
|
||||
// message in the Notification Service Extension.
|
||||
//
|
||||
// Keepalive messages ("keepalive"):
|
||||
// - On Android, we subscribe to the "~control" topic, which is used to restart the foreground service (if it died,
|
||||
// e.g. after an app update). We send these keepalive messages regularly (see Config.FirebaseKeepaliveInterval).
|
||||
// - On iOS, we subscribe to the "~poll" topic, which is used to poll all topics regularly. This is because iOS
|
||||
// does not allow any background or scheduled activity at all.
|
||||
//
|
||||
// Poll request messages ("poll_request"):
|
||||
// - Normal messages are turned into poll request messages if anonymous users are not allowed to read the message.
|
||||
// On Android, this will trigger the app to poll the topic and thereby displaying new messages.
|
||||
// - If UpstreamBaseURL is set, messages are forwarded as poll requests to an upstream server and then forwarded
|
||||
// to Firebase here. This is mainly for iOS to support self-hosted servers.
|
||||
func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, error) {
|
||||
var data map[string]string // Mostly matches https://ntfy.sh/docs/subscribe/api/#json-message-format
|
||||
var apnsConfig *messaging.APNSConfig
|
||||
|
@ -82,24 +67,7 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
|||
"event": m.Event,
|
||||
"topic": m.Topic,
|
||||
}
|
||||
// Silent notification; only 2-3 per hour are allowed; delivery not guaranteed
|
||||
// See https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app
|
||||
apnsData := make(map[string]interface{})
|
||||
for k, v := range data {
|
||||
apnsData[k] = v
|
||||
}
|
||||
apnsConfig = &messaging.APNSConfig{
|
||||
Headers: map[string]string{
|
||||
"apns-push-type": "background",
|
||||
"apns-priority": "5",
|
||||
},
|
||||
Payload: &messaging.APNSPayload{
|
||||
Aps: &messaging.Aps{
|
||||
ContentAvailable: true,
|
||||
},
|
||||
CustomData: apnsData,
|
||||
},
|
||||
}
|
||||
apnsConfig = createAPNSBackgroundConfig(data)
|
||||
case pollRequestEvent:
|
||||
data = map[string]string{
|
||||
"id": m.ID,
|
||||
|
@ -109,22 +77,7 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
|||
"message": m.Message,
|
||||
"poll_id": m.PollID,
|
||||
}
|
||||
apnsData := make(map[string]interface{})
|
||||
for k, v := range data {
|
||||
apnsData[k] = v
|
||||
}
|
||||
apnsConfig = &messaging.APNSConfig{
|
||||
Payload: &messaging.APNSPayload{
|
||||
CustomData: apnsData,
|
||||
Aps: &messaging.Aps{
|
||||
MutableContent: true,
|
||||
Alert: &messaging.ApsAlert{
|
||||
Title: m.Title,
|
||||
Body: maybeTruncateAPNSBodyMessage(m.Message),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
apnsConfig = createAPNSAlertConfig(m, data)
|
||||
case messageEvent:
|
||||
allowForward := true
|
||||
if auther != nil {
|
||||
|
@ -157,22 +110,7 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
|||
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
|
||||
data["attachment_url"] = m.Attachment.URL
|
||||
}
|
||||
apnsData := make(map[string]interface{})
|
||||
for k, v := range data {
|
||||
apnsData[k] = v
|
||||
}
|
||||
apnsConfig = &messaging.APNSConfig{
|
||||
Payload: &messaging.APNSPayload{
|
||||
CustomData: apnsData,
|
||||
Aps: &messaging.Aps{
|
||||
MutableContent: true,
|
||||
Alert: &messaging.ApsAlert{
|
||||
Title: m.Title,
|
||||
Body: maybeTruncateAPNSBodyMessage(m.Message),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
apnsConfig = createAPNSAlertConfig(m, data)
|
||||
} else {
|
||||
// If anonymous read for a topic is not allowed, we cannot send the message along
|
||||
// via Firebase. Instead, we send a "poll_request" message, asking the client to poll.
|
||||
|
@ -182,6 +120,7 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
|||
"event": pollRequestEvent,
|
||||
"topic": m.Topic,
|
||||
}
|
||||
// TODO Handle APNS?
|
||||
}
|
||||
}
|
||||
var androidConfig *messaging.AndroidConfig
|
||||
|
@ -197,3 +136,82 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
|||
APNS: apnsConfig,
|
||||
}), 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
|
||||
}
|
||||
|
||||
// createAPNSAlertConfig creates an APNS config for iOS notifications that show up as an alert (only relevant for iOS).
|
||||
// We must set the Alert struct ("alert"), and we need to set MutableContent ("mutable-content"), so the Notification Service
|
||||
// Extension in iOS can modify the message.
|
||||
func createAPNSAlertConfig(m *message, data map[string]string) *messaging.APNSConfig {
|
||||
apnsData := make(map[string]interface{})
|
||||
for k, v := range data {
|
||||
apnsData[k] = v
|
||||
}
|
||||
return &messaging.APNSConfig{
|
||||
Payload: &messaging.APNSPayload{
|
||||
CustomData: apnsData,
|
||||
Aps: &messaging.Aps{
|
||||
MutableContent: true,
|
||||
Alert: &messaging.ApsAlert{
|
||||
Title: m.Title,
|
||||
Body: maybeTruncateAPNSBodyMessage(m.Message),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// createAPNSBackgroundConfig creates an APNS config for a silent background message (only relevant for iOS). Apple only
|
||||
// allows us to send 2-3 of these notifications per hour, and delivery not guaranteed. We use this only for the ~poll
|
||||
// topic, which triggers the iOS app to poll all topics for changes.
|
||||
//
|
||||
// See https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app
|
||||
func createAPNSBackgroundConfig(data map[string]string) *messaging.APNSConfig {
|
||||
apnsData := make(map[string]interface{})
|
||||
for k, v := range data {
|
||||
apnsData[k] = v
|
||||
}
|
||||
return &messaging.APNSConfig{
|
||||
Headers: map[string]string{
|
||||
"apns-push-type": "background",
|
||||
"apns-priority": "5",
|
||||
},
|
||||
Payload: &messaging.APNSPayload{
|
||||
Aps: &messaging.Aps{
|
||||
ContentAvailable: true,
|
||||
},
|
||||
CustomData: apnsData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// maybeTruncateAPNSBodyMessage truncates the body for APNS.
|
||||
//
|
||||
// The "body" of the push notification can contain the entire message, which would count doubly for the overall length
|
||||
// of the APNS payload. I set a limit of 100 characters before truncating the notification "body" with ellipsis.
|
||||
// The message would not be changed (unless truncated for being too long). Note: if the payload is too large (>4KB),
|
||||
// APNS will simply reject / discard the notification, meaning it will never arrive on the iOS device.
|
||||
func maybeTruncateAPNSBodyMessage(s string) string {
|
||||
if len(s) >= fcmApnsBodyMessageLimit {
|
||||
over := len(s) - fcmApnsBodyMessageLimit + 3 // len("...")
|
||||
return s[:len(s)-over] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -32,6 +32,23 @@ func TestToFirebaseMessage_Keepalive(t *testing.T) {
|
|||
require.Nil(t, err)
|
||||
require.Equal(t, "mytopic", fbm.Topic)
|
||||
require.Nil(t, fbm.Android)
|
||||
require.Equal(t, &messaging.APNSConfig{
|
||||
Headers: map[string]string{
|
||||
"apns-push-type": "background",
|
||||
"apns-priority": "5",
|
||||
},
|
||||
Payload: &messaging.APNSPayload{
|
||||
Aps: &messaging.Aps{
|
||||
ContentAvailable: true,
|
||||
},
|
||||
CustomData: map[string]interface{}{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": m.Event,
|
||||
"topic": m.Topic,
|
||||
},
|
||||
},
|
||||
}, fbm.APNS)
|
||||
require.Equal(t, map[string]string{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
|
@ -46,6 +63,23 @@ func TestToFirebaseMessage_Open(t *testing.T) {
|
|||
require.Nil(t, err)
|
||||
require.Equal(t, "mytopic", fbm.Topic)
|
||||
require.Nil(t, fbm.Android)
|
||||
require.Equal(t, &messaging.APNSConfig{
|
||||
Headers: map[string]string{
|
||||
"apns-push-type": "background",
|
||||
"apns-priority": "5",
|
||||
},
|
||||
Payload: &messaging.APNSPayload{
|
||||
Aps: &messaging.Aps{
|
||||
ContentAvailable: true,
|
||||
},
|
||||
CustomData: map[string]interface{}{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": m.Event,
|
||||
"topic": m.Topic,
|
||||
},
|
||||
},
|
||||
}, fbm.APNS)
|
||||
require.Equal(t, map[string]string{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
|
@ -60,6 +94,25 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
|
|||
m.Tags = []string{"tag 1", "tag2"}
|
||||
m.Click = "https://google.com"
|
||||
m.Title = "some title"
|
||||
m.Actions = []*action{
|
||||
{
|
||||
ID: "123",
|
||||
Action: "view",
|
||||
Label: "Open page",
|
||||
Clear: true,
|
||||
URL: "https://ntfy.sh",
|
||||
},
|
||||
{
|
||||
ID: "456",
|
||||
Action: "http",
|
||||
Label: "Close door",
|
||||
URL: "https://door.com/close",
|
||||
Method: "PUT",
|
||||
Headers: map[string]string{
|
||||
"really": "yes",
|
||||
},
|
||||
},
|
||||
}
|
||||
m.Attachment = &attachment{
|
||||
Name: "some file.jpg",
|
||||
Type: "image/jpeg",
|
||||
|
@ -74,6 +127,35 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
|
|||
require.Equal(t, &messaging.AndroidConfig{
|
||||
Priority: "high",
|
||||
}, fbm.Android)
|
||||
require.Equal(t, &messaging.APNSConfig{
|
||||
Payload: &messaging.APNSPayload{
|
||||
Aps: &messaging.Aps{
|
||||
MutableContent: true,
|
||||
Alert: &messaging.ApsAlert{
|
||||
Title: "some title",
|
||||
Body: "this is a message",
|
||||
},
|
||||
},
|
||||
CustomData: map[string]interface{}{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": "message",
|
||||
"topic": "mytopic",
|
||||
"priority": "4",
|
||||
"tags": strings.Join(m.Tags, ","),
|
||||
"click": "https://google.com",
|
||||
"title": "some title",
|
||||
"message": "this is a message",
|
||||
"actions": `[{"id":"123","action":"view","label":"Open page","clear":true,"url":"https://ntfy.sh"},{"id":"456","action":"http","label":"Close door","clear":false,"url":"https://door.com/close","method":"PUT","headers":{"really":"yes"}}]`,
|
||||
"encoding": "",
|
||||
"attachment_name": "some file.jpg",
|
||||
"attachment_type": "image/jpeg",
|
||||
"attachment_size": "12345",
|
||||
"attachment_expires": "98765543",
|
||||
"attachment_url": "https://example.com/file.jpg",
|
||||
},
|
||||
},
|
||||
}, fbm.APNS)
|
||||
require.Equal(t, map[string]string{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
|
@ -84,6 +166,7 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
|
|||
"click": "https://google.com",
|
||||
"title": "some title",
|
||||
"message": "this is a message",
|
||||
"actions": `[{"id":"123","action":"view","label":"Open page","clear":true,"url":"https://ntfy.sh"},{"id":"456","action":"http","label":"Close door","clear":false,"url":"https://door.com/close","method":"PUT","headers":{"really":"yes"}}]`,
|
||||
"encoding": "",
|
||||
"attachment_name": "some file.jpg",
|
||||
"attachment_type": "image/jpeg",
|
||||
|
@ -112,6 +195,41 @@ func TestToFirebaseMessage_Message_Normal_Not_Allowed(t *testing.T) {
|
|||
}, fbm.Data)
|
||||
}
|
||||
|
||||
func TestToFirebaseMessage_PollRequest(t *testing.T) {
|
||||
m := newPollRequestMessage("mytopic", "fOv6k1QbCzo6")
|
||||
fbm, err := toFirebaseMessage(m, nil)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "mytopic", fbm.Topic)
|
||||
require.Nil(t, fbm.Android)
|
||||
require.Equal(t, &messaging.APNSConfig{
|
||||
Payload: &messaging.APNSPayload{
|
||||
Aps: &messaging.Aps{
|
||||
MutableContent: true,
|
||||
Alert: &messaging.ApsAlert{
|
||||
Title: "",
|
||||
Body: "New message",
|
||||
},
|
||||
},
|
||||
CustomData: map[string]interface{}{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": "poll_request",
|
||||
"topic": "mytopic",
|
||||
"message": "New message",
|
||||
"poll_id": "fOv6k1QbCzo6",
|
||||
},
|
||||
},
|
||||
}, fbm.APNS)
|
||||
require.Equal(t, map[string]string{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": "poll_request",
|
||||
"topic": "mytopic",
|
||||
"message": "New message",
|
||||
"poll_id": "fOv6k1QbCzo6",
|
||||
}, fbm.Data)
|
||||
}
|
||||
|
||||
func TestMaybeTruncateFCMMessage(t *testing.T) {
|
||||
origMessage := strings.Repeat("this is a long string", 300)
|
||||
origFCMMessage := &messaging.Message{
|
||||
|
|
72
web/package-lock.json
generated
72
web/package-lock.json
generated
|
@ -5335,9 +5335,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001343",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001343.tgz",
|
||||
"integrity": "sha512-8KeCrAtPMabo/XW14B+R9sZYoClx1n0b+WYgwDKZPtWR3TcdvWzdSy7mPyFEmR5WU1St9v1PW6sdO5dkFOEzfA==",
|
||||
"version": "1.0.30001344",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz",
|
||||
"integrity": "sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -5694,7 +5694,7 @@
|
|||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.22.7",
|
||||
|
@ -6372,13 +6372,13 @@
|
|||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"node_modules/detective": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
|
||||
"integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz",
|
||||
"integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==",
|
||||
"dependencies": {
|
||||
"acorn-node": "^1.6.1",
|
||||
"acorn-node": "^1.8.2",
|
||||
"defined": "^1.0.0",
|
||||
"minimist": "^1.1.1"
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"detective": "bin/detective.js"
|
||||
|
@ -6595,9 +6595,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.139",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.139.tgz",
|
||||
"integrity": "sha512-lYxzcUCjWxxVug+A7UxBCUiVr13TCjfZFYJS9Lq1VpU/ErwV4a6zUQo9dfojuGpw/L/x9REGuBl6ICQPGgbs3g=="
|
||||
"version": "1.4.140",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.140.tgz",
|
||||
"integrity": "sha512-NLz5va823QfJBYOO/hLV4AfU4Crmkl/6Hl2pH3qdJcmi0ySZ3YTWHxOlDm3uJOFBEPy3pIhu8gKQo6prQTWKKA=="
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
"version": "0.8.1",
|
||||
|
@ -7817,9 +7817,9 @@
|
|||
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg=="
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz",
|
||||
"integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==",
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
|
@ -14359,9 +14359,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.74.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.74.1.tgz",
|
||||
"integrity": "sha512-K2zW7kV8Voua5eGkbnBtWYfMIhYhT9Pel2uhBk2WO5eMee161nPze/XRfvEQPFYz7KgrCCnmh2Wy0AMFLGGmMA==",
|
||||
"version": "2.75.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.0.tgz",
|
||||
"integrity": "sha512-1/wxtweHJ7YwI2AIK3ZgCBU3nbW8sLnBIFwN46cwOTnVzt8f1o6J8zPKjwoiuADvzSjmnLqJce31p0q2vQ+dqw==",
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
|
@ -20477,9 +20477,9 @@
|
|||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001343",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001343.tgz",
|
||||
"integrity": "sha512-8KeCrAtPMabo/XW14B+R9sZYoClx1n0b+WYgwDKZPtWR3TcdvWzdSy7mPyFEmR5WU1St9v1PW6sdO5dkFOEzfA=="
|
||||
"version": "1.0.30001344",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz",
|
||||
"integrity": "sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g=="
|
||||
},
|
||||
"case-sensitive-paths-webpack-plugin": {
|
||||
"version": "2.4.0",
|
||||
|
@ -20747,7 +20747,7 @@
|
|||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.22.7",
|
||||
|
@ -21217,13 +21217,13 @@
|
|||
}
|
||||
},
|
||||
"detective": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
|
||||
"integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz",
|
||||
"integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==",
|
||||
"requires": {
|
||||
"acorn-node": "^1.6.1",
|
||||
"acorn-node": "^1.8.2",
|
||||
"defined": "^1.0.0",
|
||||
"minimist": "^1.1.1"
|
||||
"minimist": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"dexie": {
|
||||
|
@ -21384,9 +21384,9 @@
|
|||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.139",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.139.tgz",
|
||||
"integrity": "sha512-lYxzcUCjWxxVug+A7UxBCUiVr13TCjfZFYJS9Lq1VpU/ErwV4a6zUQo9dfojuGpw/L/x9REGuBl6ICQPGgbs3g=="
|
||||
"version": "1.4.140",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.140.tgz",
|
||||
"integrity": "sha512-NLz5va823QfJBYOO/hLV4AfU4Crmkl/6Hl2pH3qdJcmi0ySZ3YTWHxOlDm3uJOFBEPy3pIhu8gKQo6prQTWKKA=="
|
||||
},
|
||||
"emittery": {
|
||||
"version": "0.8.1",
|
||||
|
@ -22296,9 +22296,9 @@
|
|||
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg=="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz",
|
||||
"integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ=="
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin": {
|
||||
"version": "6.5.2",
|
||||
|
@ -26848,9 +26848,9 @@
|
|||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.74.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.74.1.tgz",
|
||||
"integrity": "sha512-K2zW7kV8Voua5eGkbnBtWYfMIhYhT9Pel2uhBk2WO5eMee161nPze/XRfvEQPFYz7KgrCCnmh2Wy0AMFLGGmMA==",
|
||||
"version": "2.75.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.0.tgz",
|
||||
"integrity": "sha512-1/wxtweHJ7YwI2AIK3ZgCBU3nbW8sLnBIFwN46cwOTnVzt8f1o6J8zPKjwoiuADvzSjmnLqJce31p0q2vQ+dqw==",
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue