refactor(notification): separate ios and android (#250)
This commit is contained in:
parent
6a64b42ab0
commit
460b74d8a6
|
@ -2,18 +2,12 @@ package gorush
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/appleboy/go-fcm"
|
"github.com/appleboy/go-fcm"
|
||||||
apns "github.com/sideshow/apns2"
|
|
||||||
"github.com/sideshow/apns2/certificate"
|
|
||||||
"github.com/sideshow/apns2/payload"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// D provide string array
|
// D provide string array
|
||||||
|
@ -182,402 +176,3 @@ func CheckPushConf() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitAPNSClient use for initialize APNs Client.
|
|
||||||
func InitAPNSClient() error {
|
|
||||||
if PushConf.Ios.Enabled {
|
|
||||||
var err error
|
|
||||||
ext := filepath.Ext(PushConf.Ios.KeyPath)
|
|
||||||
|
|
||||||
switch ext {
|
|
||||||
case ".p12":
|
|
||||||
CertificatePemIos, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password)
|
|
||||||
case ".pem":
|
|
||||||
CertificatePemIos, err = certificate.FromPemFile(PushConf.Ios.KeyPath, PushConf.Ios.Password)
|
|
||||||
default:
|
|
||||||
err = errors.New("wrong certificate key extension")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
LogError.Error("Cert Error:", err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if PushConf.Ios.Production {
|
|
||||||
ApnsClient = apns.NewClient(CertificatePemIos).Production()
|
|
||||||
} else {
|
|
||||||
ApnsClient = apns.NewClient(CertificatePemIos).Development()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitWorkers for initialize all workers.
|
|
||||||
func InitWorkers(workerNum int64, queueNum int64) {
|
|
||||||
LogAccess.Debug("worker number is ", workerNum, ", queue number is ", queueNum)
|
|
||||||
QueueNotification = make(chan PushNotification, queueNum)
|
|
||||||
for i := int64(0); i < workerNum; i++ {
|
|
||||||
go startWorker()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startWorker() {
|
|
||||||
for {
|
|
||||||
notification := <-QueueNotification
|
|
||||||
switch notification.Platform {
|
|
||||||
case PlatFormIos:
|
|
||||||
PushToIOS(notification)
|
|
||||||
case PlatFormAndroid:
|
|
||||||
PushToAndroid(notification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// queueNotification add notification to queue list.
|
|
||||||
func queueNotification(req RequestPush) (int, []LogPushEntry) {
|
|
||||||
var count int
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
newNotification := []PushNotification{}
|
|
||||||
for _, notification := range req.Notifications {
|
|
||||||
switch notification.Platform {
|
|
||||||
case PlatFormIos:
|
|
||||||
if !PushConf.Ios.Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case PlatFormAndroid:
|
|
||||||
if !PushConf.Android.Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newNotification = append(newNotification, notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
log := make([]LogPushEntry, 0, count)
|
|
||||||
for _, notification := range newNotification {
|
|
||||||
if PushConf.Core.Sync {
|
|
||||||
notification.wg = &wg
|
|
||||||
notification.log = &log
|
|
||||||
notification.AddWaitCount()
|
|
||||||
}
|
|
||||||
QueueNotification <- notification
|
|
||||||
count += len(notification.Tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
if PushConf.Core.Sync {
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
StatStorage.AddTotalCount(int64(count))
|
|
||||||
|
|
||||||
return count, log
|
|
||||||
}
|
|
||||||
|
|
||||||
func iosAlertDictionary(payload *payload.Payload, req PushNotification) *payload.Payload {
|
|
||||||
// Alert dictionary
|
|
||||||
|
|
||||||
if len(req.Title) > 0 {
|
|
||||||
payload.AlertTitle(req.Title)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.Title) > 0 {
|
|
||||||
payload.AlertTitle(req.Alert.Title)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apple Watch & Safari display this string as part of the notification interface.
|
|
||||||
if len(req.Alert.Subtitle) > 0 {
|
|
||||||
payload.AlertSubtitle(req.Alert.Subtitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.TitleLocKey) > 0 {
|
|
||||||
payload.AlertTitleLocKey(req.Alert.TitleLocKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.LocArgs) > 0 {
|
|
||||||
payload.AlertLocArgs(req.Alert.LocArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.TitleLocArgs) > 0 {
|
|
||||||
payload.AlertTitleLocArgs(req.Alert.TitleLocArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.Body) > 0 {
|
|
||||||
payload.AlertBody(req.Alert.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.LaunchImage) > 0 {
|
|
||||||
payload.AlertLaunchImage(req.Alert.LaunchImage)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.LocKey) > 0 {
|
|
||||||
payload.AlertLocKey(req.Alert.LocKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.Action) > 0 {
|
|
||||||
payload.AlertAction(req.Alert.Action)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Alert.ActionLocKey) > 0 {
|
|
||||||
payload.AlertActionLocKey(req.Alert.ActionLocKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// General
|
|
||||||
|
|
||||||
if len(req.Category) > 0 {
|
|
||||||
payload.Category(req.Category)
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIOSNotification use for define iOS notification.
|
|
||||||
// The iOS Notification Payload
|
|
||||||
// ref: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1
|
|
||||||
func GetIOSNotification(req PushNotification) *apns.Notification {
|
|
||||||
notification := &apns.Notification{
|
|
||||||
ApnsID: req.ApnsID,
|
|
||||||
Topic: req.Topic,
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Expiration > 0 {
|
|
||||||
notification.Expiration = time.Unix(req.Expiration, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Priority) > 0 && req.Priority == "normal" {
|
|
||||||
notification.Priority = apns.PriorityLow
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := payload.NewPayload()
|
|
||||||
|
|
||||||
// add alert object if message length > 0
|
|
||||||
if len(req.Message) > 0 {
|
|
||||||
payload.Alert(req.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// zero value for clear the badge on the app icon.
|
|
||||||
if req.Badge != nil && *req.Badge >= 0 {
|
|
||||||
payload.Badge(*req.Badge)
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.MutableContent {
|
|
||||||
payload.MutableContent()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Sound) > 0 {
|
|
||||||
payload.Sound(req.Sound)
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.ContentAvailable {
|
|
||||||
payload.ContentAvailable()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.URLArgs) > 0 {
|
|
||||||
payload.URLArgs(req.URLArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range req.Data {
|
|
||||||
payload.Custom(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
payload = iosAlertDictionary(payload, req)
|
|
||||||
|
|
||||||
notification.Payload = payload
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushToIOS provide send notification to APNs server.
|
|
||||||
func PushToIOS(req PushNotification) bool {
|
|
||||||
LogAccess.Debug("Start push notification for iOS")
|
|
||||||
if PushConf.Core.Sync {
|
|
||||||
defer req.WaitDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
retryCount = 0
|
|
||||||
maxRetry = PushConf.Ios.MaxRetry
|
|
||||||
)
|
|
||||||
|
|
||||||
if req.Retry > 0 && req.Retry < maxRetry {
|
|
||||||
maxRetry = req.Retry
|
|
||||||
}
|
|
||||||
|
|
||||||
Retry:
|
|
||||||
var (
|
|
||||||
isError = false
|
|
||||||
newTokens []string
|
|
||||||
)
|
|
||||||
|
|
||||||
notification := GetIOSNotification(req)
|
|
||||||
|
|
||||||
for _, token := range req.Tokens {
|
|
||||||
notification.DeviceToken = token
|
|
||||||
|
|
||||||
// send ios notification
|
|
||||||
res, err := ApnsClient.Push(notification)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// apns server error
|
|
||||||
LogPush(FailedPush, token, req, err)
|
|
||||||
if PushConf.Core.Sync {
|
|
||||||
req.AddLog(getLogPushEntry(FailedPush, token, req, err))
|
|
||||||
}
|
|
||||||
StatStorage.AddIosError(1)
|
|
||||||
newTokens = append(newTokens, token)
|
|
||||||
isError = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
// error message:
|
|
||||||
// ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65
|
|
||||||
LogPush(FailedPush, token, req, errors.New(res.Reason))
|
|
||||||
if PushConf.Core.Sync {
|
|
||||||
req.AddLog(getLogPushEntry(FailedPush, token, req, errors.New(res.Reason)))
|
|
||||||
}
|
|
||||||
StatStorage.AddIosError(1)
|
|
||||||
newTokens = append(newTokens, token)
|
|
||||||
isError = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.Sent() {
|
|
||||||
LogPush(SucceededPush, token, req, nil)
|
|
||||||
StatStorage.AddIosSuccess(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isError == true && retryCount < maxRetry {
|
|
||||||
retryCount++
|
|
||||||
|
|
||||||
// resend fail token
|
|
||||||
req.Tokens = newTokens
|
|
||||||
goto Retry
|
|
||||||
}
|
|
||||||
|
|
||||||
return isError
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
CollapseKey: req.CollapseKey,
|
|
||||||
ContentAvailable: req.ContentAvailable,
|
|
||||||
DelayWhileIdle: req.DelayWhileIdle,
|
|
||||||
TimeToLive: req.TimeToLive,
|
|
||||||
RestrictedPackageName: req.RestrictedPackageName,
|
|
||||||
DryRun: req.DryRun,
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.RegistrationIDs = req.Tokens
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.Notification = &req.Notification
|
|
||||||
|
|
||||||
// Set request message if body is empty
|
|
||||||
if len(req.Message) > 0 {
|
|
||||||
notification.Notification.Body = req.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Title) > 0 {
|
|
||||||
notification.Notification.Title = req.Title
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Sound) > 0 {
|
|
||||||
notification.Notification.Sound = req.Sound
|
|
||||||
}
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushToAndroid provide send notification to Android server.
|
|
||||||
func PushToAndroid(req PushNotification) bool {
|
|
||||||
LogAccess.Debug("Start push notification for Android")
|
|
||||||
if PushConf.Core.Sync {
|
|
||||||
defer req.WaitDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
APIKey string
|
|
||||||
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())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
Retry:
|
|
||||||
var isError = false
|
|
||||||
notification := GetAndroidNotification(req)
|
|
||||||
|
|
||||||
if APIKey = PushConf.Android.APIKey; req.APIKey != "" {
|
|
||||||
APIKey = req.APIKey
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := fcm.NewClient(APIKey)
|
|
||||||
if err != nil {
|
|
||||||
// FCM server error
|
|
||||||
LogError.Error("FCM server error: " + err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := client.Send(notification)
|
|
||||||
if err != nil {
|
|
||||||
// FCM server error
|
|
||||||
LogError.Error("FCM server error: " + err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
for k, result := range res.Results {
|
|
||||||
if result.Error != nil {
|
|
||||||
isError = true
|
|
||||||
newTokens = append(newTokens, req.Tokens[k])
|
|
||||||
LogPush(FailedPush, req.Tokens[k], req, result.Error)
|
|
||||||
if PushConf.Core.Sync {
|
|
||||||
req.AddLog(getLogPushEntry(FailedPush, req.Tokens[k], req, result.Error))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
LogPush(SucceededPush, req.Tokens[k], req, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isError == true && retryCount < maxRetry {
|
|
||||||
retryCount++
|
|
||||||
|
|
||||||
// resend fail token
|
|
||||||
req.Tokens = newTokens
|
|
||||||
goto Retry
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
package gorush
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
apns "github.com/sideshow/apns2"
|
||||||
|
"github.com/sideshow/apns2/certificate"
|
||||||
|
"github.com/sideshow/apns2/payload"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitAPNSClient use for initialize APNs Client.
|
||||||
|
func InitAPNSClient() error {
|
||||||
|
if PushConf.Ios.Enabled {
|
||||||
|
var err error
|
||||||
|
ext := filepath.Ext(PushConf.Ios.KeyPath)
|
||||||
|
|
||||||
|
switch ext {
|
||||||
|
case ".p12":
|
||||||
|
CertificatePemIos, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password)
|
||||||
|
case ".pem":
|
||||||
|
CertificatePemIos, err = certificate.FromPemFile(PushConf.Ios.KeyPath, PushConf.Ios.Password)
|
||||||
|
default:
|
||||||
|
err = errors.New("wrong certificate key extension")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
LogError.Error("Cert Error:", err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if PushConf.Ios.Production {
|
||||||
|
ApnsClient = apns.NewClient(CertificatePemIos).Production()
|
||||||
|
} else {
|
||||||
|
ApnsClient = apns.NewClient(CertificatePemIos).Development()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func iosAlertDictionary(payload *payload.Payload, req PushNotification) *payload.Payload {
|
||||||
|
// Alert dictionary
|
||||||
|
|
||||||
|
if len(req.Title) > 0 {
|
||||||
|
payload.AlertTitle(req.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.Title) > 0 {
|
||||||
|
payload.AlertTitle(req.Alert.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apple Watch & Safari display this string as part of the notification interface.
|
||||||
|
if len(req.Alert.Subtitle) > 0 {
|
||||||
|
payload.AlertSubtitle(req.Alert.Subtitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.TitleLocKey) > 0 {
|
||||||
|
payload.AlertTitleLocKey(req.Alert.TitleLocKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.LocArgs) > 0 {
|
||||||
|
payload.AlertLocArgs(req.Alert.LocArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.TitleLocArgs) > 0 {
|
||||||
|
payload.AlertTitleLocArgs(req.Alert.TitleLocArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.Body) > 0 {
|
||||||
|
payload.AlertBody(req.Alert.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.LaunchImage) > 0 {
|
||||||
|
payload.AlertLaunchImage(req.Alert.LaunchImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.LocKey) > 0 {
|
||||||
|
payload.AlertLocKey(req.Alert.LocKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.Action) > 0 {
|
||||||
|
payload.AlertAction(req.Alert.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Alert.ActionLocKey) > 0 {
|
||||||
|
payload.AlertActionLocKey(req.Alert.ActionLocKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// General
|
||||||
|
|
||||||
|
if len(req.Category) > 0 {
|
||||||
|
payload.Category(req.Category)
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIOSNotification use for define iOS notification.
|
||||||
|
// The iOS Notification Payload
|
||||||
|
// ref: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1
|
||||||
|
func GetIOSNotification(req PushNotification) *apns.Notification {
|
||||||
|
notification := &apns.Notification{
|
||||||
|
ApnsID: req.ApnsID,
|
||||||
|
Topic: req.Topic,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Expiration > 0 {
|
||||||
|
notification.Expiration = time.Unix(req.Expiration, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Priority) > 0 && req.Priority == "normal" {
|
||||||
|
notification.Priority = apns.PriorityLow
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := payload.NewPayload()
|
||||||
|
|
||||||
|
// add alert object if message length > 0
|
||||||
|
if len(req.Message) > 0 {
|
||||||
|
payload.Alert(req.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero value for clear the badge on the app icon.
|
||||||
|
if req.Badge != nil && *req.Badge >= 0 {
|
||||||
|
payload.Badge(*req.Badge)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.MutableContent {
|
||||||
|
payload.MutableContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Sound) > 0 {
|
||||||
|
payload.Sound(req.Sound)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ContentAvailable {
|
||||||
|
payload.ContentAvailable()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.URLArgs) > 0 {
|
||||||
|
payload.URLArgs(req.URLArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range req.Data {
|
||||||
|
payload.Custom(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = iosAlertDictionary(payload, req)
|
||||||
|
|
||||||
|
notification.Payload = payload
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushToIOS provide send notification to APNs server.
|
||||||
|
func PushToIOS(req PushNotification) bool {
|
||||||
|
LogAccess.Debug("Start push notification for iOS")
|
||||||
|
if PushConf.Core.Sync {
|
||||||
|
defer req.WaitDone()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
retryCount = 0
|
||||||
|
maxRetry = PushConf.Ios.MaxRetry
|
||||||
|
)
|
||||||
|
|
||||||
|
if req.Retry > 0 && req.Retry < maxRetry {
|
||||||
|
maxRetry = req.Retry
|
||||||
|
}
|
||||||
|
|
||||||
|
Retry:
|
||||||
|
var (
|
||||||
|
isError = false
|
||||||
|
newTokens []string
|
||||||
|
)
|
||||||
|
|
||||||
|
notification := GetIOSNotification(req)
|
||||||
|
|
||||||
|
for _, token := range req.Tokens {
|
||||||
|
notification.DeviceToken = token
|
||||||
|
|
||||||
|
// send ios notification
|
||||||
|
res, err := ApnsClient.Push(notification)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// apns server error
|
||||||
|
LogPush(FailedPush, token, req, err)
|
||||||
|
if PushConf.Core.Sync {
|
||||||
|
req.AddLog(getLogPushEntry(FailedPush, token, req, err))
|
||||||
|
}
|
||||||
|
StatStorage.AddIosError(1)
|
||||||
|
newTokens = append(newTokens, token)
|
||||||
|
isError = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
// error message:
|
||||||
|
// ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65
|
||||||
|
LogPush(FailedPush, token, req, errors.New(res.Reason))
|
||||||
|
if PushConf.Core.Sync {
|
||||||
|
req.AddLog(getLogPushEntry(FailedPush, token, req, errors.New(res.Reason)))
|
||||||
|
}
|
||||||
|
StatStorage.AddIosError(1)
|
||||||
|
newTokens = append(newTokens, token)
|
||||||
|
isError = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Sent() {
|
||||||
|
LogPush(SucceededPush, token, req, nil)
|
||||||
|
StatStorage.AddIosSuccess(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isError == true && retryCount < maxRetry {
|
||||||
|
retryCount++
|
||||||
|
|
||||||
|
// resend fail token
|
||||||
|
req.Tokens = newTokens
|
||||||
|
goto Retry
|
||||||
|
}
|
||||||
|
|
||||||
|
return isError
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package gorush
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/appleboy/go-fcm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
CollapseKey: req.CollapseKey,
|
||||||
|
ContentAvailable: req.ContentAvailable,
|
||||||
|
DelayWhileIdle: req.DelayWhileIdle,
|
||||||
|
TimeToLive: req.TimeToLive,
|
||||||
|
RestrictedPackageName: req.RestrictedPackageName,
|
||||||
|
DryRun: req.DryRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.RegistrationIDs = req.Tokens
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.Notification = &req.Notification
|
||||||
|
|
||||||
|
// Set request message if body is empty
|
||||||
|
if len(req.Message) > 0 {
|
||||||
|
notification.Notification.Body = req.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Title) > 0 {
|
||||||
|
notification.Notification.Title = req.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Sound) > 0 {
|
||||||
|
notification.Notification.Sound = req.Sound
|
||||||
|
}
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushToAndroid provide send notification to Android server.
|
||||||
|
func PushToAndroid(req PushNotification) bool {
|
||||||
|
LogAccess.Debug("Start push notification for Android")
|
||||||
|
if PushConf.Core.Sync {
|
||||||
|
defer req.WaitDone()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
APIKey string
|
||||||
|
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())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Retry:
|
||||||
|
var isError = false
|
||||||
|
notification := GetAndroidNotification(req)
|
||||||
|
|
||||||
|
if APIKey = PushConf.Android.APIKey; req.APIKey != "" {
|
||||||
|
APIKey = req.APIKey
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := fcm.NewClient(APIKey)
|
||||||
|
if err != nil {
|
||||||
|
// FCM server error
|
||||||
|
LogError.Error("FCM server error: " + err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := client.Send(notification)
|
||||||
|
if err != nil {
|
||||||
|
// FCM server error
|
||||||
|
LogError.Error("FCM server error: " + err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
for k, result := range res.Results {
|
||||||
|
if result.Error != nil {
|
||||||
|
isError = true
|
||||||
|
newTokens = append(newTokens, req.Tokens[k])
|
||||||
|
LogPush(FailedPush, req.Tokens[k], req, result.Error)
|
||||||
|
if PushConf.Core.Sync {
|
||||||
|
req.AddLog(getLogPushEntry(FailedPush, req.Tokens[k], req, result.Error))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPush(SucceededPush, req.Tokens[k], req, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isError == true && retryCount < maxRetry {
|
||||||
|
retryCount++
|
||||||
|
|
||||||
|
// resend fail token
|
||||||
|
req.Tokens = newTokens
|
||||||
|
goto Retry
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package gorush
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitWorkers for initialize all workers.
|
||||||
|
func InitWorkers(workerNum int64, queueNum int64) {
|
||||||
|
LogAccess.Debug("worker number is ", workerNum, ", queue number is ", queueNum)
|
||||||
|
QueueNotification = make(chan PushNotification, queueNum)
|
||||||
|
for i := int64(0); i < workerNum; i++ {
|
||||||
|
go startWorker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startWorker() {
|
||||||
|
for {
|
||||||
|
notification := <-QueueNotification
|
||||||
|
switch notification.Platform {
|
||||||
|
case PlatFormIos:
|
||||||
|
PushToIOS(notification)
|
||||||
|
case PlatFormAndroid:
|
||||||
|
PushToAndroid(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// queueNotification add notification to queue list.
|
||||||
|
func queueNotification(req RequestPush) (int, []LogPushEntry) {
|
||||||
|
var count int
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
newNotification := []PushNotification{}
|
||||||
|
for _, notification := range req.Notifications {
|
||||||
|
switch notification.Platform {
|
||||||
|
case PlatFormIos:
|
||||||
|
if !PushConf.Ios.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case PlatFormAndroid:
|
||||||
|
if !PushConf.Android.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newNotification = append(newNotification, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := make([]LogPushEntry, 0, count)
|
||||||
|
for _, notification := range newNotification {
|
||||||
|
if PushConf.Core.Sync {
|
||||||
|
notification.wg = &wg
|
||||||
|
notification.log = &log
|
||||||
|
notification.AddWaitCount()
|
||||||
|
}
|
||||||
|
QueueNotification <- notification
|
||||||
|
count += len(notification.Tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
if PushConf.Core.Sync {
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
StatStorage.AddTotalCount(int64(count))
|
||||||
|
|
||||||
|
return count, log
|
||||||
|
}
|
Loading…
Reference in New Issue