From 96bb357435b578d4bfb4e4f255f2492e018c7ceb Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Fri, 27 May 2022 20:30:20 -0400 Subject: [PATCH] Polish the poll_request stuff --- cmd/serve.go | 10 +- go.mod | 3 +- go.sum | 28 +---- server/config.go | 2 +- server/server.go | 95 +++++++++-------- server/server.yml | 12 +++ server/server_firebase.go | 188 ++++++++++++++++++--------------- server/server_firebase_test.go | 118 +++++++++++++++++++++ web/package-lock.json | 72 ++++++------- 9 files changed, 328 insertions(+), 200 deletions(-) diff --git a/cmd/serve.go b/cmd/serve.go index cfaecda..52008c2 100644 --- a/cmd/serve.go +++ b/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 diff --git a/go.mod b/go.mod index c7abd48..f00c1c3 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 5f020b9..4a4b581 100644 --- a/go.sum +++ b/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= diff --git a/server/config.go b/server/config.go index 2bb4b89..4db52a3 100644 --- a/server/config.go +++ b/server/config.go @@ -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 diff --git a/server/server.go b/server/server.go index e3b738d..e7439f5 100644 --- a/server/server.go +++ b/server/server.go @@ -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()) } diff --git a/server/server.yml b/server/server.yml index 9502ebc..ce7b1c7 100644 --- a/server/server.yml +++ b/server/server.yml @@ -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 diff --git a/server/server_firebase.go b/server/server_firebase.go index 373cb45..40ca806 100644 --- a/server/server_firebase.go +++ b/server/server_firebase.go @@ -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 +} diff --git a/server/server_firebase_test.go b/server/server_firebase_test.go index 1fdd8a6..c990b93 100644 --- a/server/server_firebase_test.go +++ b/server/server_firebase_test.go @@ -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{ diff --git a/web/package-lock.json b/web/package-lock.json index 0c37763..50828d4 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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" }