2017-07-17 03:22:48 +00:00
|
|
|
package gorush
|
|
|
|
|
|
|
|
import (
|
2017-07-25 08:41:30 +00:00
|
|
|
"errors"
|
2017-07-17 03:22:48 +00:00
|
|
|
"fmt"
|
|
|
|
|
2020-02-07 03:39:44 +00:00
|
|
|
"github.com/appleboy/go-fcm"
|
2019-09-06 07:48:42 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-07-17 03:22:48 +00:00
|
|
|
)
|
|
|
|
|
2017-07-25 08:41:30 +00:00
|
|
|
// InitFCMClient use for initialize FCM Client.
|
|
|
|
func InitFCMClient(key string) (*fcm.Client, error) {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if key == "" {
|
|
|
|
return nil, errors.New("Missing Android API Key")
|
|
|
|
}
|
|
|
|
|
|
|
|
if key != PushConf.Android.APIKey {
|
|
|
|
return fcm.NewClient(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
if FCMClient == nil {
|
|
|
|
FCMClient, err = fcm.NewClient(key)
|
|
|
|
return FCMClient, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return FCMClient, nil
|
|
|
|
}
|
|
|
|
|
2017-07-17 03:22:48 +00:00
|
|
|
// GetAndroidNotification use for define Android notification.
|
|
|
|
// HTTP Connection Server Reference for Android
|
|
|
|
// https://firebase.google.com/docs/cloud-messaging/http-server-ref
|
|
|
|
func GetAndroidNotification(req PushNotification) *fcm.Message {
|
|
|
|
notification := &fcm.Message{
|
|
|
|
To: req.To,
|
2017-10-24 09:00:08 +00:00
|
|
|
Condition: req.Condition,
|
2017-07-17 03:22:48 +00:00
|
|
|
CollapseKey: req.CollapseKey,
|
|
|
|
ContentAvailable: req.ContentAvailable,
|
2018-08-15 03:47:15 +00:00
|
|
|
MutableContent: req.MutableContent,
|
2017-07-17 03:22:48 +00:00
|
|
|
DelayWhileIdle: req.DelayWhileIdle,
|
|
|
|
TimeToLive: req.TimeToLive,
|
|
|
|
RestrictedPackageName: req.RestrictedPackageName,
|
|
|
|
DryRun: req.DryRun,
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:37:53 +00:00
|
|
|
if len(req.Tokens) > 0 {
|
|
|
|
notification.RegistrationIDs = req.Tokens
|
|
|
|
}
|
2017-07-17 03:22:48 +00:00
|
|
|
|
|
|
|
if len(req.Priority) > 0 && req.Priority == "high" {
|
|
|
|
notification.Priority = "high"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add another field
|
|
|
|
if len(req.Data) > 0 {
|
|
|
|
notification.Data = make(map[string]interface{})
|
|
|
|
for k, v := range req.Data {
|
|
|
|
notification.Data[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-27 05:40:21 +00:00
|
|
|
n := &fcm.Notification{}
|
|
|
|
isNotificationSet := false
|
2020-01-20 15:14:07 +00:00
|
|
|
if req.Notification != nil {
|
2020-02-27 05:40:21 +00:00
|
|
|
isNotificationSet = true
|
|
|
|
n = req.Notification
|
|
|
|
}
|
2017-07-17 03:22:48 +00:00
|
|
|
|
2020-02-27 05:40:21 +00:00
|
|
|
if len(req.Message) > 0 {
|
|
|
|
isNotificationSet = true
|
|
|
|
n.Body = req.Message
|
|
|
|
}
|
2017-07-17 03:22:48 +00:00
|
|
|
|
2020-02-27 05:40:21 +00:00
|
|
|
if len(req.Title) > 0 {
|
|
|
|
isNotificationSet = true
|
|
|
|
n.Title = req.Title
|
|
|
|
}
|
2020-02-07 03:39:44 +00:00
|
|
|
|
2020-02-27 05:40:21 +00:00
|
|
|
if len(req.Image) > 0 {
|
|
|
|
isNotificationSet = true
|
|
|
|
n.Image = req.Image
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := req.Sound.(string); ok && len(v) > 0 {
|
|
|
|
isNotificationSet = true
|
|
|
|
n.Sound = v
|
|
|
|
}
|
|
|
|
|
|
|
|
if isNotificationSet {
|
|
|
|
notification.Notification = n
|
2017-07-17 03:22:48 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 03:39:44 +00:00
|
|
|
// handle iOS apns in fcm
|
|
|
|
|
|
|
|
if len(req.Apns) > 0 {
|
|
|
|
notification.Apns = req.Apns
|
|
|
|
}
|
|
|
|
|
2017-07-17 03:22:48 +00:00
|
|
|
return notification
|
|
|
|
}
|
|
|
|
|
|
|
|
// PushToAndroid provide send notification to Android server.
|
2020-07-14 15:57:23 +00:00
|
|
|
func PushToAndroid(req PushNotification) {
|
2017-07-17 03:22:48 +00:00
|
|
|
LogAccess.Debug("Start push notification for Android")
|
|
|
|
|
|
|
|
var (
|
2017-07-25 08:41:30 +00:00
|
|
|
client *fcm.Client
|
2017-07-17 03:22:48 +00:00
|
|
|
retryCount = 0
|
|
|
|
maxRetry = PushConf.Android.MaxRetry
|
|
|
|
)
|
|
|
|
|
|
|
|
if req.Retry > 0 && req.Retry < maxRetry {
|
|
|
|
maxRetry = req.Retry
|
|
|
|
}
|
|
|
|
|
|
|
|
// check message
|
|
|
|
err := CheckMessage(req)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
LogError.Error("request error: " + err.Error())
|
2020-07-14 15:57:23 +00:00
|
|
|
return
|
2017-07-17 03:22:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Retry:
|
|
|
|
notification := GetAndroidNotification(req)
|
|
|
|
|
2017-07-25 08:41:30 +00:00
|
|
|
if req.APIKey != "" {
|
|
|
|
client, err = InitFCMClient(req.APIKey)
|
|
|
|
} else {
|
|
|
|
client, err = InitFCMClient(PushConf.Android.APIKey)
|
2017-07-17 03:22:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
// FCM server error
|
|
|
|
LogError.Error("FCM server error: " + err.Error())
|
2020-07-14 15:57:23 +00:00
|
|
|
return
|
2017-07-17 03:22:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
res, err := client.Send(notification)
|
|
|
|
if err != nil {
|
2017-07-25 08:41:30 +00:00
|
|
|
// Send Message error
|
|
|
|
LogError.Error("FCM server send message error: " + err.Error())
|
2020-07-14 15:57:23 +00:00
|
|
|
return
|
2017-07-17 03:22:48 +00:00
|
|
|
}
|
|
|
|
|
2017-10-24 09:00:08 +00:00
|
|
|
if !req.IsTopic() {
|
|
|
|
LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure))
|
|
|
|
}
|
|
|
|
|
2017-07-17 03:22:48 +00:00
|
|
|
StatStorage.AddAndroidSuccess(int64(res.Success))
|
|
|
|
StatStorage.AddAndroidError(int64(res.Failure))
|
|
|
|
|
|
|
|
var newTokens []string
|
2017-10-24 09:00:08 +00:00
|
|
|
// result from Send messages to specific devices
|
2017-07-17 03:22:48 +00:00
|
|
|
for k, result := range res.Results {
|
2017-10-25 02:37:53 +00:00
|
|
|
to := ""
|
|
|
|
if k < len(req.Tokens) {
|
|
|
|
to = req.Tokens[k]
|
|
|
|
} else {
|
|
|
|
to = req.To
|
|
|
|
}
|
|
|
|
|
2017-07-17 03:22:48 +00:00
|
|
|
if result.Error != nil {
|
2020-05-06 11:37:06 +00:00
|
|
|
// We should retry only "retryable" statuses. More info about response:
|
|
|
|
// https://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream-http-messages-plain-text
|
|
|
|
if !result.Unregistered() {
|
|
|
|
newTokens = append(newTokens, to)
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:37:53 +00:00
|
|
|
LogPush(FailedPush, to, req, result.Error)
|
2017-07-17 03:22:48 +00:00
|
|
|
if PushConf.Core.Sync {
|
2017-10-25 02:37:53 +00:00
|
|
|
req.AddLog(getLogPushEntry(FailedPush, to, req, result.Error))
|
2019-09-06 07:48:42 +00:00
|
|
|
} else if PushConf.Core.FeedbackURL != "" {
|
2020-02-24 14:18:50 +00:00
|
|
|
go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) {
|
|
|
|
err := DispatchFeedback(log, url, timeout)
|
2019-09-06 07:48:42 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Error(err)
|
|
|
|
}
|
2020-02-24 14:18:50 +00:00
|
|
|
}(LogError, getLogPushEntry(FailedPush, to, req, result.Error), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout)
|
2017-07-17 03:22:48 +00:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:37:53 +00:00
|
|
|
LogPush(SucceededPush, to, req, nil)
|
2017-07-17 03:22:48 +00:00
|
|
|
}
|
|
|
|
|
2017-10-24 09:00:08 +00:00
|
|
|
// 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 {
|
|
|
|
// failure
|
|
|
|
LogPush(FailedPush, to, req, res.Error)
|
|
|
|
if PushConf.Core.Sync {
|
|
|
|
req.AddLog(getLogPushEntry(FailedPush, to, req, res.Error))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:37:53 +00:00
|
|
|
// Device Group HTTP Response
|
|
|
|
if len(res.FailedRegistrationIDs) > 0 {
|
2019-10-17 15:49:21 +00:00
|
|
|
newTokens = append(newTokens, res.FailedRegistrationIDs...)
|
2017-10-25 02:37:53 +00:00
|
|
|
|
|
|
|
LogPush(FailedPush, notification.To, req, errors.New("device group: partial success or all fails"))
|
|
|
|
if PushConf.Core.Sync {
|
|
|
|
req.AddLog(getLogPushEntry(FailedPush, notification.To, req, errors.New("device group: partial success or all fails")))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-14 15:57:23 +00:00
|
|
|
if len(newTokens) > 0 && retryCount < maxRetry {
|
2017-07-17 03:22:48 +00:00
|
|
|
retryCount++
|
|
|
|
|
|
|
|
// resend fail token
|
|
|
|
req.Tokens = newTokens
|
|
|
|
goto Retry
|
|
|
|
}
|
|
|
|
}
|