Send messages to topics for android (#296)

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
Bo-Yi Wu 2017-10-24 04:00:08 -05:00 committed by GitHub
parent 4df24202cf
commit 115ee18560
9 changed files with 194 additions and 25 deletions

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strings"
"sync" "sync"
"github.com/appleboy/go-fcm" "github.com/appleboy/go-fcm"
@ -69,6 +70,7 @@ type PushNotification struct {
TimeToLive *uint `json:"time_to_live,omitempty"` TimeToLive *uint `json:"time_to_live,omitempty"`
RestrictedPackageName string `json:"restricted_package_name,omitempty"` RestrictedPackageName string `json:"restricted_package_name,omitempty"`
DryRun bool `json:"dry_run,omitempty"` DryRun bool `json:"dry_run,omitempty"`
Condition string `json:"condition,omitempty"`
Notification fcm.Notification `json:"notification,omitempty"` Notification fcm.Notification `json:"notification,omitempty"`
// iOS // 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 // CheckMessage for check request message
func CheckMessage(req PushNotification) error { func CheckMessage(req PushNotification) error {
var msg string 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" msg = "the message must specify at least one registration ID"
LogAccess.Debug(msg) LogAccess.Debug(msg)
return errors.New(msg) return errors.New(msg)

View File

@ -33,6 +33,7 @@ func InitFCMClient(key string) (*fcm.Client, error) {
func GetAndroidNotification(req PushNotification) *fcm.Message { func GetAndroidNotification(req PushNotification) *fcm.Message {
notification := &fcm.Message{ notification := &fcm.Message{
To: req.To, To: req.To,
Condition: req.Condition,
CollapseKey: req.CollapseKey, CollapseKey: req.CollapseKey,
ContentAvailable: req.ContentAvailable, ContentAvailable: req.ContentAvailable,
DelayWhileIdle: req.DelayWhileIdle, DelayWhileIdle: req.DelayWhileIdle,
@ -122,11 +123,15 @@ Retry:
return false return false
} }
if !req.IsTopic() {
LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure)) LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure))
}
StatStorage.AddAndroidSuccess(int64(res.Success)) StatStorage.AddAndroidSuccess(int64(res.Success))
StatStorage.AddAndroidError(int64(res.Failure)) StatStorage.AddAndroidError(int64(res.Failure))
var newTokens []string var newTokens []string
// result from Send messages to specific devices
for k, result := range res.Results { for k, result := range res.Results {
if result.Error != nil { if result.Error != nil {
isError = true isError = true
@ -141,6 +146,28 @@ Retry:
LogPush(SucceededPush, req.Tokens[k], req, nil) 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 { if isError && retryCount < maxRetry {
retryCount++ retryCount++

View File

@ -140,6 +140,26 @@ func TestFCMMessage(t *testing.T) {
err = CheckMessage(req) err = CheckMessage(req)
assert.Error(t, err) 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 // the message may specify at most 1000 registration IDs
req = PushNotification{ req = PushNotification{
Message: "Test", Message: "Test",

View File

@ -132,6 +132,48 @@ func TestSyncModeForNotifications(t *testing.T) {
assert.Equal(t, 2, len(logs)) 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) { func TestSetProxyURL(t *testing.T) {
err := SetProxy("87.236.233.92:8080") err := SetProxy("87.236.233.92:8080")

View File

@ -58,6 +58,10 @@ func queueNotification(req RequestPush) (int, []LogPushEntry) {
} }
QueueNotification <- notification QueueNotification <- notification
count += len(notification.Tokens) count += len(notification.Tokens)
// Count topic message
if notification.To != "" {
count++
}
} }
if PushConf.Core.Sync { if PushConf.Core.Sync {

20
main.go
View File

@ -130,12 +130,21 @@ func main() {
if opts.Android.Enabled { if opts.Android.Enabled {
gorush.PushConf.Android.Enabled = opts.Android.Enabled gorush.PushConf.Android.Enabled = opts.Android.Enabled
req := gorush.PushNotification{ req := gorush.PushNotification{
Tokens: []string{token},
Platform: gorush.PlatFormAndroid, Platform: gorush.PlatFormAndroid,
Message: message, Message: message,
Title: title, 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) err := gorush.CheckMessage(req)
if err != nil { if err != nil {
@ -159,12 +168,17 @@ func main() {
gorush.PushConf.Ios.Enabled = opts.Ios.Enabled gorush.PushConf.Ios.Enabled = opts.Ios.Enabled
req := gorush.PushNotification{ req := gorush.PushNotification{
Tokens: []string{token},
Platform: gorush.PlatFormIos, Platform: gorush.PlatFormIos,
Message: message, Message: message,
Title: title, Title: title,
} }
// send message to single device
if token != "" {
req.Tokens = []string{token}
}
// send topic message
if topic != "" { if topic != "" {
req.Topic = topic req.Topic = topic
} }
@ -259,13 +273,13 @@ Server Options:
iOS Options: iOS Options:
-i, --key <file> certificate key file path -i, --key <file> certificate key file path
-P, --password <password> certificate key password -P, --password <password> certificate key password
--topic <topic> iOS topic
--ios enabled iOS (default: false) --ios enabled iOS (default: false)
--production iOS production mode (default: false) --production iOS production mode (default: false)
Android Options: Android Options:
-k, --apikey <api_key> Android API Key -k, --apikey <api_key> Android API Key
--android enabled android (default: false) --android enabled android (default: false)
Common Options: Common Options:
--topic <topic> iOS or Android topic message
-h, --help Show this message -h, --help Show this message
-v, --version Show version -v, --version Show version
` `

View File

@ -1,8 +1,8 @@
# go-fcm # go-fcm
[![GoDoc](https://godoc.org/github.com/edganiukov/fcm?status.svg)](https://godoc.org/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/edganiukov/fcm.svg?branch=master)](https://travis-ci.org/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/edganiukov/fcm)](https://goreportcard.com/report/github.com/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). 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/) 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 ## Getting Started
To install fcm, use `go get`: To install fcm, use `go get`:
@ -34,31 +40,32 @@ Here is a simple example illustrating how to use FCM library:
package main package main
import ( import (
"github.com/edganiukov/fcm" "log"
"github.com/appleboy/go-fcm"
) )
func main() { func main() {
// Create the message to be sent. // Create the message to be sent.
msg := &fcm.Message{ msg := &fcm.Message{
Token: "sample_device_token", To: "sample_device_token",
Data: map[string]interface{}{ Data: map[string]interface{}{
"foo": "bar", "foo": "bar",
} },
} }
// Create a FCM client to send the message. // 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. // Send the message and receive the response without retries.
response, err := client.Send(msg) response, err := client.Send(msg)
if err != nil { if err != nil {
/* ... */ log.Fatalln(err)
} }
/* ... */
log.Printf("%#v\n", response)
} }
``` ```
#### TODO:
---------
- [ ] Retry only failed messages while multicast messaging.

View File

@ -46,6 +46,9 @@ var (
// ErrTopicsMessageRateExceeded occurs when client sent to many requests to // ErrTopicsMessageRateExceeded occurs when client sent to many requests to
// the topics. // the topics.
ErrTopicsMessageRateExceeded = errors.New("topics message rate exceeded") 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 ( var (
@ -62,6 +65,7 @@ var (
"InternalServerError": ErrInternalServerError, "InternalServerError": ErrInternalServerError,
"DeviceMessageRateExceeded": ErrDeviceMessageRateExceeded, "DeviceMessageRateExceeded": ErrDeviceMessageRateExceeded,
"TopicsMessageRateExceeded": ErrTopicsMessageRateExceeded, "TopicsMessageRateExceeded": ErrTopicsMessageRateExceeded,
"InvalidParameters": ErrInvalidParameters,
} }
) )
@ -105,6 +109,47 @@ type Response struct {
Failure int `json:"failure"` Failure int `json:"failure"`
CanonicalIDs int `json:"canonical_ids"` CanonicalIDs int `json:"canonical_ids"`
Results []Result `json:"results"` 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. // Result represents the status of a processed message.

6
vendor/vendor.json vendored
View File

@ -3,10 +3,10 @@
"ignore": "test", "ignore": "test",
"package": [ "package": [
{ {
"checksumSHA1": "DuVew6znkXuUG1wjGQF+pUyXrHU=", "checksumSHA1": "TVTjsXflagrWbTXmbxPJdCtTFRo=",
"path": "github.com/appleboy/go-fcm", "path": "github.com/appleboy/go-fcm",
"revision": "5f2cb2866531e4e37c7a9ff5fb7a1536e1ffc566", "revision": "c12f9e2e95b14802da2b4d3807dd12ef0dd80a42",
"revisionTime": "2017-06-01T07:42:50Z" "revisionTime": "2017-10-24T08:00:40Z"
}, },
{ {
"checksumSHA1": "Ab7MUtqX0iq2PUzzBxWpgzPSydw=", "checksumSHA1": "Ab7MUtqX0iq2PUzzBxWpgzPSydw=",