gorush/notify/notification_fcm.go

248 lines
5.8 KiB
Go

package notify
import (
"errors"
"fmt"
"github.com/appleboy/gorush/config"
"github.com/appleboy/gorush/core"
"github.com/appleboy/gorush/logx"
"github.com/appleboy/gorush/status"
"github.com/appleboy/go-fcm"
)
// InitFCMClient use for initialize FCM Client.
func InitFCMClient(cfg *config.ConfYaml, key string) (*fcm.Client, error) {
var err error
if key == "" && cfg.Android.APIKey == "" {
return nil, errors.New("missing android api key")
}
if key != "" && key != cfg.Android.APIKey {
return fcm.NewClient(key)
}
if FCMClient == nil {
FCMClient, err = fcm.NewClient(cfg.Android.APIKey)
return FCMClient, err
}
return FCMClient, nil
}
// 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,
Condition: req.Condition,
CollapseKey: req.CollapseKey,
ContentAvailable: req.ContentAvailable,
MutableContent: req.MutableContent,
DelayWhileIdle: req.DelayWhileIdle,
TimeToLive: req.TimeToLive,
RestrictedPackageName: req.RestrictedPackageName,
DryRun: req.DryRun,
}
if len(req.Tokens) > 0 {
notification.RegistrationIDs = req.Tokens
}
if req.Priority == HIGH || req.Priority == "normal" {
notification.Priority = req.Priority
}
// Add another field
if len(req.Data) > 0 {
notification.Data = make(map[string]interface{})
for k, v := range req.Data {
notification.Data[k] = v
}
}
n := &fcm.Notification{}
isNotificationSet := false
if req.Notification != nil {
isNotificationSet = true
n = req.Notification
}
if len(req.Message) > 0 {
isNotificationSet = true
n.Body = req.Message
}
if len(req.Title) > 0 {
isNotificationSet = true
n.Title = req.Title
}
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
}
// handle iOS apns in fcm
if len(req.Apns) > 0 {
notification.Apns = req.Apns
}
return notification
}
// PushToAndroid provide send notification to Android server.
func PushToAndroid(req *PushNotification, cfg *config.ConfYaml) (resp *ResponsePush, err error) {
logx.LogAccess.Debug("Start push notification for Android")
var (
client *fcm.Client
retryCount = 0
maxRetry = cfg.Android.MaxRetry
)
if req.Retry > 0 && req.Retry < maxRetry {
maxRetry = req.Retry
}
// check message
err = CheckMessage(req)
if err != nil {
logx.LogError.Error("request error: " + err.Error())
return
}
resp = &ResponsePush{}
Retry:
notification := GetAndroidNotification(req)
if req.APIKey != "" {
client, err = InitFCMClient(cfg, req.APIKey)
} else {
client, err = InitFCMClient(cfg, cfg.Android.APIKey)
}
if err != nil {
// FCM server error
logx.LogError.Error("FCM server error: " + err.Error())
return
}
res, err := client.Send(notification)
if err != nil {
// Send Message error
logx.LogError.Error("FCM server send message error: " + err.Error())
if req.IsTopic() {
errLog := logPush(cfg, core.FailedPush, req.To, req, err)
resp.Logs = append(resp.Logs, errLog)
status.StatStorage.AddAndroidError(1)
} else {
for _, token := range req.Tokens {
errLog := logPush(cfg, core.FailedPush, token, req, err)
resp.Logs = append(resp.Logs, errLog)
}
status.StatStorage.AddAndroidError(int64(len(req.Tokens)))
}
return
}
if !req.IsTopic() {
logx.LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure))
}
status.StatStorage.AddAndroidSuccess(int64(res.Success))
status.StatStorage.AddAndroidError(int64(res.Failure))
var newTokens []string
// result from Send messages to specific devices
for k, result := range res.Results {
to := ""
if k < len(req.Tokens) {
to = req.Tokens[k]
} else {
to = req.To
}
if result.Error != nil {
// 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)
}
errLog := logPush(cfg, core.FailedPush, to, req, result.Error)
resp.Logs = append(resp.Logs, errLog)
continue
}
logPush(cfg, core.SucceededPush, to, req, nil)
}
// result from Send messages to topics
if req.IsTopic() {
to := ""
if req.To != "" {
to = req.To
} else {
to = req.Condition
}
logx.LogAccess.Debug("Send Topic Message: ", to)
// Success
if res.MessageID != 0 {
logPush(cfg, core.SucceededPush, to, req, nil)
} else {
// failure
errLog := logPush(cfg, core.FailedPush, to, req, res.Error)
resp.Logs = append(resp.Logs, errLog)
}
}
// Device Group HTTP Response
if len(res.FailedRegistrationIDs) > 0 {
newTokens = append(newTokens, res.FailedRegistrationIDs...)
//nolint
errLog := logPush(cfg, core.FailedPush, notification.To, req, errors.New("device group: partial success or all fails"))
resp.Logs = append(resp.Logs, errLog)
}
if len(newTokens) > 0 && retryCount < maxRetry {
retryCount++
// resend fail token
req.Tokens = newTokens
goto Retry
}
return resp, nil
}
func logPush(cfg *config.ConfYaml, status, token string, req *PushNotification, err error) logx.LogPushEntry {
return logx.LogPush(&logx.InputLog{
ID: req.ID,
Status: status,
Token: token,
Message: req.Message,
Platform: req.Platform,
Error: err,
HideToken: cfg.Log.HideToken,
HideMessage: cfg.Log.HideMessages,
Format: cfg.Log.Format,
})
}