From 115ee18560d1c1525456968ecdef3a96b1071213 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 24 Oct 2017 04:00:08 -0500 Subject: [PATCH] Send messages to topics for android (#296) Signed-off-by: Bo-Yi Wu --- gorush/notification.go | 12 ++++- gorush/notification_fcm.go | 29 +++++++++++- gorush/notification_fcm_test.go | 20 +++++++++ gorush/notification_test.go | 42 +++++++++++++++++ gorush/worker.go | 4 ++ main.go | 20 +++++++-- vendor/github.com/appleboy/go-fcm/README.md | 41 ++++++++++------- vendor/github.com/appleboy/go-fcm/response.go | 45 +++++++++++++++++++ vendor/vendor.json | 6 +-- 9 files changed, 194 insertions(+), 25 deletions(-) diff --git a/gorush/notification.go b/gorush/notification.go index a22f5cf..8fd089b 100644 --- a/gorush/notification.go +++ b/gorush/notification.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "os" + "strings" "sync" "github.com/appleboy/go-fcm" @@ -69,6 +70,7 @@ type PushNotification struct { TimeToLive *uint `json:"time_to_live,omitempty"` RestrictedPackageName string `json:"restricted_package_name,omitempty"` DryRun bool `json:"dry_run,omitempty"` + Condition string `json:"condition,omitempty"` Notification fcm.Notification `json:"notification,omitempty"` // iOS @@ -103,11 +105,19 @@ func (p *PushNotification) AddLog(log LogPushEntry) { } } +// IsTopic check if message format is topic for FCM +// ref: https://firebase.google.com/docs/cloud-messaging/send-message#topic-http-post-request +func (p *PushNotification) IsTopic() bool { + return (p.Platform == PlatFormAndroid && p.To != "" && strings.HasPrefix(p.To, "/topics/")) || + p.Condition != "" +} + // CheckMessage for check request message func CheckMessage(req PushNotification) error { var msg string - if len(req.Tokens) == 0 { + // ignore send topic mesaage from FCM + if !req.IsTopic() && len(req.Tokens) == 0 { msg = "the message must specify at least one registration ID" LogAccess.Debug(msg) return errors.New(msg) diff --git a/gorush/notification_fcm.go b/gorush/notification_fcm.go index 0ac1311..17ed97d 100644 --- a/gorush/notification_fcm.go +++ b/gorush/notification_fcm.go @@ -33,6 +33,7 @@ func InitFCMClient(key string) (*fcm.Client, error) { func GetAndroidNotification(req PushNotification) *fcm.Message { notification := &fcm.Message{ To: req.To, + Condition: req.Condition, CollapseKey: req.CollapseKey, ContentAvailable: req.ContentAvailable, DelayWhileIdle: req.DelayWhileIdle, @@ -122,11 +123,15 @@ Retry: return false } - LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure)) + if !req.IsTopic() { + LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure)) + } + StatStorage.AddAndroidSuccess(int64(res.Success)) StatStorage.AddAndroidError(int64(res.Failure)) var newTokens []string + // result from Send messages to specific devices for k, result := range res.Results { if result.Error != nil { isError = true @@ -141,6 +146,28 @@ Retry: LogPush(SucceededPush, req.Tokens[k], req, nil) } + // result from Send messages to topics + if req.IsTopic() { + to := "" + if req.To != "" { + to = req.To + } else { + to = req.Condition + } + LogAccess.Debug("Send Topic Message: ", to) + // Success + if res.MessageID != 0 { + LogPush(SucceededPush, to, req, nil) + } else { + isError = true + // failure + LogPush(FailedPush, to, req, res.Error) + if PushConf.Core.Sync { + req.AddLog(getLogPushEntry(FailedPush, to, req, res.Error)) + } + } + } + if isError && retryCount < maxRetry { retryCount++ diff --git a/gorush/notification_fcm_test.go b/gorush/notification_fcm_test.go index 24c0013..60c4aad 100644 --- a/gorush/notification_fcm_test.go +++ b/gorush/notification_fcm_test.go @@ -140,6 +140,26 @@ func TestFCMMessage(t *testing.T) { err = CheckMessage(req) assert.Error(t, err) + // ignore check token length if send topic message + req = PushNotification{ + Message: "Test", + Platform: PlatFormAndroid, + To: "/topics/foo-bar", + } + + err = CheckMessage(req) + assert.NoError(t, err) + + // "condition": "'dogs' in topics || 'cats' in topics", + req = PushNotification{ + Message: "Test", + Platform: PlatFormAndroid, + Condition: "'dogs' in topics || 'cats' in topics", + } + + err = CheckMessage(req) + assert.NoError(t, err) + // the message may specify at most 1000 registration IDs req = PushNotification{ Message: "Test", diff --git a/gorush/notification_test.go b/gorush/notification_test.go index 0f50839..7aaffb3 100644 --- a/gorush/notification_test.go +++ b/gorush/notification_test.go @@ -132,6 +132,48 @@ func TestSyncModeForNotifications(t *testing.T) { assert.Equal(t, 2, len(logs)) } +func TestSyncModeForTopicNotification(t *testing.T) { + PushConf, _ = config.LoadConf("") + + PushConf.Android.Enabled = true + PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") + PushConf.Log.HideToken = false + + // enable sync mode + PushConf.Core.Sync = true + + req := RequestPush{ + Notifications: []PushNotification{ + // android + { + // error:InvalidParameters + // Check that the provided parameters have the right name and type. + To: "/topics/foo-bar@@@##", + Platform: PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + // android + { + // success + To: "/topics/foo-bar", + Platform: PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + // android + { + // success + Condition: "'dogs' in topics || 'cats' in topics", + Platform: PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + }, + } + + count, logs := queueNotification(req) + assert.Equal(t, 2, count) + assert.Equal(t, 1, len(logs)) +} + func TestSetProxyURL(t *testing.T) { err := SetProxy("87.236.233.92:8080") diff --git a/gorush/worker.go b/gorush/worker.go index 734d1f7..6c49681 100644 --- a/gorush/worker.go +++ b/gorush/worker.go @@ -58,6 +58,10 @@ func queueNotification(req RequestPush) (int, []LogPushEntry) { } QueueNotification <- notification count += len(notification.Tokens) + // Count topic message + if notification.To != "" { + count++ + } } if PushConf.Core.Sync { diff --git a/main.go b/main.go index ff4cb95..9893a2c 100644 --- a/main.go +++ b/main.go @@ -130,12 +130,21 @@ func main() { if opts.Android.Enabled { gorush.PushConf.Android.Enabled = opts.Android.Enabled req := gorush.PushNotification{ - Tokens: []string{token}, Platform: gorush.PlatFormAndroid, Message: message, Title: title, } + // send message to single device + if token != "" { + req.Tokens = []string{token} + } + + // send topic message + if topic != "" { + req.To = topic + } + err := gorush.CheckMessage(req) if err != nil { @@ -159,12 +168,17 @@ func main() { gorush.PushConf.Ios.Enabled = opts.Ios.Enabled req := gorush.PushNotification{ - Tokens: []string{token}, Platform: gorush.PlatFormIos, Message: message, Title: title, } + // send message to single device + if token != "" { + req.Tokens = []string{token} + } + + // send topic message if topic != "" { req.Topic = topic } @@ -259,13 +273,13 @@ Server Options: iOS Options: -i, --key certificate key file path -P, --password certificate key password - --topic iOS topic --ios enabled iOS (default: false) --production iOS production mode (default: false) Android Options: -k, --apikey Android API Key --android enabled android (default: false) Common Options: + --topic iOS or Android topic message -h, --help Show this message -v, --version Show version ` diff --git a/vendor/github.com/appleboy/go-fcm/README.md b/vendor/github.com/appleboy/go-fcm/README.md index 7a40672..3eb867a 100644 --- a/vendor/github.com/appleboy/go-fcm/README.md +++ b/vendor/github.com/appleboy/go-fcm/README.md @@ -1,8 +1,8 @@ # go-fcm -[![GoDoc](https://godoc.org/github.com/edganiukov/fcm?status.svg)](https://godoc.org/github.com/edganiukov/fcm) -[![Build Status](https://travis-ci.org/edganiukov/fcm.svg?branch=master)](https://travis-ci.org/edganiukov/fcm) -[![Go Report Card](https://goreportcard.com/badge/github.com/edganiukov/fcm)](https://goreportcard.com/report/github.com/edganiukov/fcm) +[![GoDoc](https://godoc.org/github.com/appleboy/go-fcm?status.svg)](https://godoc.org/github.com/edganiukov/fcm) +[![Build Status](https://travis-ci.org/appleboy/go-fcm.svg?branch=master)](https://travis-ci.org/edganiukov/fcm) +[![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/go-fcm)](https://goreportcard.com/report/github.com/edganiukov/fcm) This project was forked from [github.com/edganiukov/fcmfcm](https://github.com/edganiukov/fcm). @@ -10,6 +10,12 @@ Golang client library for Firebase Cloud Messaging. Implemented only [HTTP clien More information on [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) +## Feature + +* [x] Send messages to a topic +* [x] Send messages to a device list +* [x] Supports condition attribute (fcm only) + ## Getting Started To install fcm, use `go get`: @@ -34,31 +40,32 @@ Here is a simple example illustrating how to use FCM library: package main import ( - "github.com/edganiukov/fcm" + "log" + + "github.com/appleboy/go-fcm" ) func main() { // Create the message to be sent. msg := &fcm.Message{ - Token: "sample_device_token", - Data: map[string]interface{}{ - "foo": "bar", - } - } + To: "sample_device_token", + Data: map[string]interface{}{ + "foo": "bar", + }, + } // Create a FCM client to send the message. - client := fcm.NewClient("sample_api_key") + client, err := fcm.NewClient("sample_api_key") + if err != nil { + log.Fatalln(err) + } // Send the message and receive the response without retries. response, err := client.Send(msg) if err != nil { - /* ... */ + log.Fatalln(err) } - /* ... */ + + log.Printf("%#v\n", response) } ``` - - -#### TODO: ---------- -- [ ] Retry only failed messages while multicast messaging. diff --git a/vendor/github.com/appleboy/go-fcm/response.go b/vendor/github.com/appleboy/go-fcm/response.go index 2863df4..715f745 100644 --- a/vendor/github.com/appleboy/go-fcm/response.go +++ b/vendor/github.com/appleboy/go-fcm/response.go @@ -46,6 +46,9 @@ var ( // ErrTopicsMessageRateExceeded occurs when client sent to many requests to // the topics. ErrTopicsMessageRateExceeded = errors.New("topics message rate exceeded") + + // ErrInvalidParameters occurs when provided parameters have the right name and type + ErrInvalidParameters = errors.New("check that the provided parameters have the right name and type") ) var ( @@ -62,6 +65,7 @@ var ( "InternalServerError": ErrInternalServerError, "DeviceMessageRateExceeded": ErrDeviceMessageRateExceeded, "TopicsMessageRateExceeded": ErrTopicsMessageRateExceeded, + "InvalidParameters": ErrInvalidParameters, } ) @@ -105,6 +109,47 @@ type Response struct { Failure int `json:"failure"` CanonicalIDs int `json:"canonical_ids"` Results []Result `json:"results"` + + // Device Group HTTP Response + FailedRegistrationIDs []string `json:"failed_registration_ids"` + + // Topic HTTP response + MessageID int64 `json:"message_id"` + Error error `json:"error"` +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (r *Response) UnmarshalJSON(data []byte) error { + var response struct { + MulticastID int64 `json:"multicast_id"` + Success int `json:"success"` + Failure int `json:"failure"` + CanonicalIDs int `json:"canonical_ids"` + Results []Result `json:"results"` + + // Device Group HTTP Response + FailedRegistrationIDs []string `json:"failed_registration_ids"` + + // Topic HTTP response + MessageID int64 `json:"message_id"` + Error string `json:"error"` + } + + if err := json.Unmarshal(data, &response); err != nil { + return err + } + + r.MulticastID = response.MulticastID + r.Success = response.Success + r.Failure = response.Failure + r.CanonicalIDs = response.CanonicalIDs + r.Results = response.Results + r.Success = response.Success + r.FailedRegistrationIDs = response.FailedRegistrationIDs + r.MessageID = response.MessageID + r.Error = errMap[response.Error] + + return nil } // Result represents the status of a processed message. diff --git a/vendor/vendor.json b/vendor/vendor.json index 32dac0e..3d807b8 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,10 +3,10 @@ "ignore": "test", "package": [ { - "checksumSHA1": "DuVew6znkXuUG1wjGQF+pUyXrHU=", + "checksumSHA1": "TVTjsXflagrWbTXmbxPJdCtTFRo=", "path": "github.com/appleboy/go-fcm", - "revision": "5f2cb2866531e4e37c7a9ff5fb7a1536e1ffc566", - "revisionTime": "2017-06-01T07:42:50Z" + "revision": "c12f9e2e95b14802da2b4d3807dd12ef0dd80a42", + "revisionTime": "2017-10-24T08:00:40Z" }, { "checksumSHA1": "Ab7MUtqX0iq2PUzzBxWpgzPSydw=",