Add Huawei Mobile Services Support to Gorush (#523)

Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
M. Salih Karakaşlı 2020-09-04 06:01:21 +03:00 committed by GitHub
parent 3db8b4f006
commit 3918fab908
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 829 additions and 14 deletions

155
README.md
View File

@ -64,6 +64,7 @@ A push notification micro server using [Gin](https://github.com/gin-gonic/gin) f
- [APNS](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html)
- [FCM](https://firebase.google.com/)
- [HMS](https://developer.huawei.com/consumer/en/hms/)
[A live demo on Netlify](https://gorush.netlify.com/).
@ -71,6 +72,7 @@ A push notification micro server using [Gin](https://github.com/gin-gonic/gin) f
- Support [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) using [go-fcm](https://github.com/appleboy/go-fcm) library for Android.
- Support [HTTP/2](https://http2.github.io/) Apple Push Notification Service using [apns2](https://github.com/sideshow/apns2) library.
- Support [HMS Push Service](https://developer.huawei.com/consumer/en/hms/huawei-pushkit) using [go-hms-push](https://github.com/msalihkarakasli/go-hms-push) library for Huawei Devices.
- Support [YAML](https://github.com/go-yaml/yaml) configuration.
- Support command line to send single Android or iOS notification.
- Support Web API to send push notification.
@ -138,6 +140,12 @@ android:
apikey: "YOUR_API_KEY"
max_retry: 0 # resend fail notification, default value zero is disabled
huawei:
enabled: true
apikey: "YOUR_API_KEY"
appid: "YOUR_APP_ID"
max_retry: 0 # resend fail notification, default value zero is disabled
ios:
enabled: false
key_path: "key.pem"
@ -275,6 +283,10 @@ iOS Options:
Android Options:
-k, --apikey <api_key> Android API Key
--android enabled android (default: false)
Huawei Options:
-hk, --hmskey <hms_key> HMS API Key
-hid, --hmsid <hms_id> HMS APP Id
--huawei enabled huawei (default: false)
Common Options:
--topic <topic> iOS or Android topic message
-h, --help Show this message
@ -304,6 +316,31 @@ gorush --android --topic "/topics/foo-bar" \
- `--topic`: Send messages to topics. note: don't add device token.
- `--proxy`: Set `http`, `https` or `socks5` proxy url.
### Send Huawei (HMS) notification
Send single notification with the following command.
```bash
gorush -huawei -title "Gorush with HMS" -m "your message" -hk "API Key" -hid "APP Id" -t "Device token"
```
Send messages to topics.
```bash
gorush --huawei --topic "foo-bar" \
-title "Gorush with HMS" \
-m "This is a Huawei Mobile Services Topic Message" \
-hk "API Key" \
-hid "APP Id"
```
- `-m`: Notification message.
- `-hk`: [Huawei Mobile Services](https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/Preparations) api secret key
- `-t`: Device token.
- `--title`: Notification title.
- `--topic`: Send messages to topics. note: don't add device token.
- `--proxy`: Set `http`, `https` or `socks5` proxy url.
### Send iOS notification
Send single notification with the following command.
@ -360,7 +397,7 @@ Gorush support the following API.
- **GET** `/api/stat/go` Golang cpu, memory, gc, etc information. Thanks for [golang-stats-api-handler](https://github.com/fukata/golang-stats-api-handler).
- **GET** `/api/stat/app` show notification success and failure counts.
- **GET** `/api/config` show server yml config file.
- **POST** `/api/push` push ios and android notifications.
- **POST** `/api/push` push ios, android or huawei notifications.
### GET /api/stat/go
@ -418,6 +455,10 @@ Show success or failure counts information of notification.
"android": {
"push_success": 10,
"push_error": 10
},
"huawei": {
"push_success": 3,
"push_error": 1
}
}
```
@ -482,6 +523,21 @@ Simple send Android notification example, the `platform` value is `2`:
}
```
Simple send Huawei notification example, the `platform` value is `3`:
```json
{
"notifications": [
{
"tokens": ["token_a", "token_b"],
"platform": 3,
"title": "Gorush with HMS",
"message": "Hello World Huawei!"
}
]
}
```
Simple send notification on Android and iOS devices using Firebase, the `platform` value is `2`:
```json
@ -513,15 +569,16 @@ Send multiple notifications as below:
},
{
"tokens": ["token_a", "token_b"],
"platform": 2,
"message": "Hello World!"
"platform": 3,
"message": "Hello World Huawei!",
"title": "Gorush with HMS"
},
.....
]
}
```
See more example about [iOS](#ios-example) or [Android](#android-example).
See more example about [iOS](#ios-example), [Android](#android-example) or [Huawei](#huawei-example)
### Request body
@ -531,24 +588,31 @@ The Request body must have a notifications array. The following is a parameter t
|-------------------------|--------------|---------------------------------------------------------------------------------------------------|----------|---------------------------------------------------------------|
| notif_id | string | A unique string that identifies the notification for async feedback | - | |
| tokens | string array | device tokens | o | |
| platform | int | platform(iOS,Android) | o | 1=iOS, 2=Android (Firebase) |
| platform | int | platform(iOS,Android) | o | 1=iOS, 2=Android (Firebase), 3=Huawei (HMS) |
| message | string | message for notification | - | |
| title | string | notification title | - | |
| priority | string | Sets the priority of the message. | - | `normal` or `high` |
| content_available | bool | data messages wake the app by default. | - | |
| sound | interface{} | sound type | - | |
| data | string array | extensible partition | - | |
| data | string array | extensible partition | - | only Android and IOS |
| huawei_data | string | JSON object as string to extensible partition partition | - | only Huawei. See the [detail](#huawei-notification) |
| retry | int | retry send notification if fail response from server. Value must be small than `max_retry` field. | - | |
| topic | string | send messages to topics | | |
| image | string | image url to show in notification | - | only Android |
| image | string | image url to show in notification | - | only Android and Huawei |
| api_key | string | api key for firebase cloud message | - | only Android |
| to | string | The value must be a registration token, notification key, or topic. | - | only Android |
| collapse_key | string | a key for collapsing notifications | - | only Android |
| huawei_collapse_key | int | a key integer for collapsing notifications | - | only Huawei See the [detail](#huawei-notification) |
| delay_while_idle | bool | a flag for device idling | - | only Android |
| time_to_live | uint | expiration of message kept on FCM storage | - | only Android |
| huawei_ttl | string | expiration of message kept on HMS storage | - | only Huawei See the [detail](#huawei-notification) |
| restricted_package_name | string | the package name of the application | - | only Android |
| dry_run | bool | allows developers to test a request without actually sending a message | - | only Android |
| notification | string array | payload of a FCM message | - | only Android. See the [detail](#android-notification-payload) |
| huawei_notification | string array | payload of a HMS message | - | only Huawei. See the [detail](#huawei-notification) |
| app_id | string | hms app id | - | only Huawei. See the [detail](#huawei-notification) |
| bi_tag | string | Tag of a message in a batch delivery task | - | only Huawei. See the [detail](#huawei-notification) |
| fast_app_target | int | State of a mini program when a quick app sends a data message. | - | only Huawei. See the [detail](#huawei-notification) |
| expiration | int | expiration for notification | - | only iOS |
| apns_id | string | A canonical UUID that identifies the notification | - | only iOS |
| collapse_id | string | An identifier you use to coalesce multiple notifications into a single notification for the user | - | only iOS |
@ -612,6 +676,18 @@ request format:
See more detail about [Firebase Cloud Messaging HTTP Protocol reference](https://firebase.google.com/docs/cloud-messaging/http-server-ref#send-downstream).
### Huawei notification
* app_id: app id from huawei developer console
* huawei_data: mapped to data
* huawei_notification: mapped to notification
* huawei_ttl: mapped to ttl
* huawei_collapse_key: mapped to collapse_key
* bi_tag:
* fast_app_target:
See more detail about [Huawei Mobulse Services Push API reference](https://developer.huawei.com/consumer/en/doc/development/HMS-References/push-sendapi).
### iOS Example
Send normal notification.
@ -776,6 +852,71 @@ Send messages to topics
}
```
### Huawei Example
Send normal notification.
```json
{
"notifications": [
{
"tokens": ["token_a", "token_b"],
"platform": 3,
"message": "Hello World Huawei!",
"title": "You got message"
}
]
}
```
Add `notification` payload.
```json
{
"notifications": [
{
"tokens": ["token_a", "token_b"],
"platform": 3,
"message": "Hello World Huawei!",
"title": "You got message",
"huawei_notification" : {
"icon": "myicon",
"color": "#112244"
}
}
]
}
```
Add other fields which user defined via `huawei_data` field.
```json
{
"notifications": [
{
"tokens": ["token_a", "token_b"],
"platform": 3,
"huawei_data": "{'title' : 'Mario','message' : 'great match!', 'Room' : 'PortugalVSDenmark'}"
}
]
}
```
Send messages to topics
```json
{
"notifications": [
{
"topic": "foo-bar",
"platform": 3,
"message": "This is a Huawei Mobile Services Topic Message",
"title": "You got message"
}
]
}
```
### Response body
Error response message table:

View File

@ -56,6 +56,12 @@ android:
apikey: "YOUR_API_KEY"
max_retry: 0 # resend fail notification, default value zero is disabled
huawei:
enabled: true
apikey: "YOUR_API_KEY"
appid: "YOUR_APP_ID"
max_retry: 0 # resend fail notification, default value zero is disabled
ios:
enabled: false
key_path: ""
@ -98,6 +104,7 @@ type ConfYaml struct {
Core SectionCore `yaml:"core"`
API SectionAPI `yaml:"api"`
Android SectionAndroid `yaml:"android"`
Huawei SectionHuawei `yaml:"huawei"`
Ios SectionIos `yaml:"ios"`
Log SectionLog `yaml:"log"`
Stat SectionStat `yaml:"stat"`
@ -152,6 +159,14 @@ type SectionAndroid struct {
MaxRetry int `yaml:"max_retry"`
}
// SectionHuawei is sub section of config.
type SectionHuawei struct {
Enabled bool `yaml:"enabled"`
APIKey string `yaml:"apikey"`
APPId string `yaml:"appid"`
MaxRetry int `yaml:"max_retry"`
}
// SectionIos is sub section of config.
type SectionIos struct {
Enabled bool `yaml:"enabled"`
@ -303,6 +318,12 @@ func LoadConf(confPath string) (ConfYaml, error) {
conf.Android.APIKey = viper.GetString("android.apikey")
conf.Android.MaxRetry = viper.GetInt("android.max_retry")
// Huawei
conf.Huawei.Enabled = viper.GetBool("huawei.enabled")
conf.Huawei.APIKey = viper.GetString("huawei.apikey")
conf.Huawei.APPId = viper.GetString("huawei.appid")
conf.Huawei.MaxRetry = viper.GetInt("huawei.max_retry")
// iOS
conf.Ios.Enabled = viper.GetBool("ios.enabled")
conf.Ios.KeyPath = viper.GetString("ios.key_path")

View File

@ -43,6 +43,12 @@ android:
apikey: "YOUR_API_KEY"
max_retry: 0 # resend fail notification, default value zero is disabled
huawei:
enabled: true
apikey: "YOUR_API_KEY"
appid: "YOUR_APP_ID"
max_retry: 0 # resend fail notification, default value zero is disabled
ios:
enabled: false
key_path: "key.pem"

1
go.mod
View File

@ -21,6 +21,7 @@ require (
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/mattn/go-isatty v0.0.12
github.com/mitchellh/mapstructure v1.1.2
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616114002-91cd23dfeed4
github.com/pelletier/go-toml v1.6.0 // indirect
github.com/prometheus/client_golang v1.2.1
github.com/prometheus/client_model v0.0.0-20191202183732-d1d2010b5bee // indirect

10
go.sum
View File

@ -180,6 +180,16 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200615195759-7a970b71c22c h1:OG1NL81rADpFFYqfWQFGWQZLgxajiOS9cZApwDaPpNg=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200615195759-7a970b71c22c/go.mod h1:4X2lQHsWGt+e3uRK124A6ndq3IIVymTAzEI9A1kIQKc=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616075053-ff4aeca924ee h1:GhSc8Bz6jeSlPukx9t3eyWK30udyAoACCR9jtci4tVI=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616075053-ff4aeca924ee/go.mod h1:4X2lQHsWGt+e3uRK124A6ndq3IIVymTAzEI9A1kIQKc=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616081907-aa182701f7a8 h1:anVdFSIgATqyCKkgRV8TZs8JLvzvlocQmYdW4/QXlfw=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616081907-aa182701f7a8/go.mod h1:4X2lQHsWGt+e3uRK124A6ndq3IIVymTAzEI9A1kIQKc=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616090445-02629345434a h1:8RhjWJXeVuHBGjGDbjNVT9/k1oOblgYkZR11hTfbHXM=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616090445-02629345434a/go.mod h1:4X2lQHsWGt+e3uRK124A6ndq3IIVymTAzEI9A1kIQKc=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616114002-91cd23dfeed4 h1:3cAiZwpOmIXbP+3/SAriDi9XlxBwZCGhS0UGhnlYhi0=
github.com/msalihkarakasli/go-hms-push v0.0.0-20200616114002-91cd23dfeed4/go.mod h1:4X2lQHsWGt+e3uRK124A6ndq3IIVymTAzEI9A1kIQKc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=

View File

@ -5,6 +5,8 @@ const (
PlatFormIos = iota + 1
// PlatFormAndroid constant is 2 for Android
PlatFormAndroid
// PlatFormHuawei constant is 3 for Huawei
PlatFormHuawei
)
const (
@ -21,4 +23,6 @@ const (
IosErrorKey = "gorush-ios-error-count"
AndroidSuccessKey = "gorush-android-success-count"
AndroidErrorKey = "gorush-android-error-count"
HuaweiSuccessKey = "gorush-huawei-success-count"
HuaweiErrorKey = "gorush-huawei-error-count"
)

View File

@ -5,6 +5,7 @@ import (
"github.com/appleboy/gorush/storage"
"github.com/appleboy/go-fcm"
"github.com/msalihkarakasli/go-hms-push/push/core"
"github.com/sideshow/apns2"
"github.com/sirupsen/logrus"
)
@ -18,6 +19,8 @@ var (
ApnsClient *apns2.Client
// FCMClient is apns client
FCMClient *fcm.Client
// HMSClient is Huawei push client
HMSClient *core.HMSClient
// LogAccess is log server request log
LogAccess *logrus.Logger
// LogError is log server error log

View File

@ -122,6 +122,8 @@ func colorForPlatForm(platform int) string {
return blue
case PlatFormAndroid:
return yellow
case PlatFormHuawei:
return green
default:
return reset
}
@ -133,6 +135,8 @@ func typeForPlatForm(platform int) string {
return "ios"
case PlatFormAndroid:
return "android"
case PlatFormHuawei:
return "huawei"
default:
return ""
}

View File

@ -14,6 +14,8 @@ type Metrics struct {
IosError *prometheus.Desc
AndroidSuccess *prometheus.Desc
AndroidError *prometheus.Desc
HuaweiSuccess *prometheus.Desc
HuaweiError *prometheus.Desc
QueueUsage *prometheus.Desc
}
@ -46,6 +48,16 @@ func NewMetrics() Metrics {
"Number of android fail count",
nil, nil,
),
HuaweiSuccess: prometheus.NewDesc(
namespace+"huawei_success",
"Number of huawei success count",
nil, nil,
),
HuaweiError: prometheus.NewDesc(
namespace+"huawei_fail",
"Number of huawei fail count",
nil, nil,
),
QueueUsage: prometheus.NewDesc(
namespace+"queue_usage",
"Length of internal queue",
@ -61,6 +73,8 @@ func (c Metrics) Describe(ch chan<- *prometheus.Desc) {
ch <- c.IosError
ch <- c.AndroidSuccess
ch <- c.AndroidError
ch <- c.HuaweiSuccess
ch <- c.HuaweiError
ch <- c.QueueUsage
}
@ -91,6 +105,16 @@ func (c Metrics) Collect(ch chan<- prometheus.Metric) {
prometheus.GaugeValue,
float64(StatStorage.GetAndroidError()),
)
ch <- prometheus.MustNewConstMetric(
c.HuaweiSuccess,
prometheus.GaugeValue,
float64(StatStorage.GetHuaweiSuccess()),
)
ch <- prometheus.MustNewConstMetric(
c.HuaweiError,
prometheus.GaugeValue,
float64(StatStorage.GetHuaweiError()),
)
ch <- prometheus.MustNewConstMetric(
c.QueueUsage,
prometheus.GaugeValue,

View File

@ -9,6 +9,7 @@ import (
"sync"
"github.com/appleboy/go-fcm"
"github.com/msalihkarakasli/go-hms-push/push/model"
)
// D provide string array
@ -79,6 +80,15 @@ type PushNotification struct {
Condition string `json:"condition,omitempty"`
Notification *fcm.Notification `json:"notification,omitempty"`
// Huawei
APPId string `json:"app_id,omitempty"`
HuaweiNotification *model.AndroidNotification `json:"huawei_notification,omitempty"`
HuaweiData string `json:"huawei_data,omitempty"`
HuaweiCollapseKey int `json:"huawei_collapse_key,omitempty"`
HuaweiTTL string `json:"huawei_ttl,omitempty"`
BiTag string `json:"bi_tag,omitempty"`
FastAppTarget int `json:"fast_app_target,omitempty"`
// iOS
Expiration *int64 `json:"expiration,omitempty"`
ApnsID string `json:"apns_id,omitempty"`
@ -121,8 +131,16 @@ 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 != ""
if p.Platform == PlatFormAndroid {
return p.To != "" && strings.HasPrefix(p.To, "/topics/") || p.Condition != ""
}
if p.Platform == PlatFormHuawei {
return p.Topic != "" || p.Condition != ""
}
return false
}
// CheckMessage for check request message
@ -148,6 +166,12 @@ func CheckMessage(req PushNotification) error {
return errors.New(msg)
}
if req.Platform == PlatFormHuawei && len(req.Tokens) > 500 {
msg = "the message may specify at most 500 registration IDs for Huawei"
LogAccess.Debug(msg)
return errors.New(msg)
}
// ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref
if req.Platform == PlatFormAndroid && req.TimeToLive != nil && (*req.TimeToLive < uint(0) || uint(2419200) < *req.TimeToLive) {
msg = "the message's TimeToLive field must be an integer " +
@ -176,8 +200,8 @@ func SetProxy(proxy string) error {
// CheckPushConf provide check your yml config.
func CheckPushConf() error {
if !PushConf.Ios.Enabled && !PushConf.Android.Enabled {
return errors.New("Please enable iOS or Android config in yml config")
if !PushConf.Ios.Enabled && !PushConf.Android.Enabled && !PushConf.Huawei.Enabled {
return errors.New("Please enable iOS, Android or Huawei config in yml config")
}
if PushConf.Ios.Enabled {
@ -199,5 +223,15 @@ func CheckPushConf() error {
}
}
if PushConf.Huawei.Enabled {
if PushConf.Huawei.APIKey == "" {
return errors.New("Missing Huawei API Key")
}
if PushConf.Huawei.APPId == "" {
return errors.New("Missing Huawei APP Id")
}
}
return nil
}

View File

@ -27,11 +27,12 @@ const authkeyValidP8 = `LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5
func TestDisabledAndroidIosConf(t *testing.T) {
PushConf, _ = config.LoadConf("")
PushConf.Android.Enabled = false
PushConf.Huawei.Enabled = false
err := CheckPushConf()
assert.Error(t, err)
assert.Equal(t, "Please enable iOS or Android config in yml config", err.Error())
assert.Equal(t, "Please enable iOS, Android or Huawei config in yml config", err.Error())
}
func TestMissingIOSCertificate(t *testing.T) {

238
gorush/notification_hms.go Normal file
View File

@ -0,0 +1,238 @@
package gorush
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"github.com/msalihkarakasli/go-hms-push/push/config"
"github.com/msalihkarakasli/go-hms-push/push/core"
"github.com/msalihkarakasli/go-hms-push/push/model"
)
var (
pushError error
pushClient *core.HMSClient
once sync.Once
)
// GetPushClient use for create HMS Push
func GetPushClient(conf *config.Config) (*core.HMSClient, error) {
once.Do(func() {
client, err := core.NewHttpClient(conf)
if err != nil {
fmt.Printf("Failed to new common client! Error is %s\n", err.Error())
panic(err)
}
pushClient = client
pushError = err
})
return pushClient, pushError
}
// InitHMSClient use for initialize HMS Client.
func InitHMSClient(apiKey string, appID string) (*core.HMSClient, error) {
if apiKey == "" {
return nil, errors.New("Missing Huawei API Key")
}
if appID == "" {
return nil, errors.New("Missing Huawei APP Id")
}
var conf = &config.Config{
AppId: appID,
AppSecret: apiKey,
AuthUrl: "https://login.cloud.huawei.com/oauth2/v2/token",
PushUrl: "https://api.push.hicloud.com",
}
if apiKey != PushConf.Huawei.APIKey || appID != PushConf.Huawei.APPId {
return GetPushClient(conf)
}
if HMSClient == nil {
return GetPushClient(conf)
}
return HMSClient, nil
}
// GetHuaweiNotification use for define HMS notification.
// HTTP Connection Server Reference for HMS
// https://developer.huawei.com/consumer/en/doc/development/HMS-References/push-sendapi
func GetHuaweiNotification(req PushNotification) (*model.MessageRequest, error) {
msgRequest := model.NewNotificationMsgRequest()
msgRequest.Message.Android = model.GetDefaultAndroid()
if len(req.Tokens) > 0 {
msgRequest.Message.Token = req.Tokens
}
if len(req.Topic) > 0 {
msgRequest.Message.Topic = req.Topic
}
if len(req.To) > 0 {
msgRequest.Message.Topic = req.To
}
if len(req.Condition) > 0 {
msgRequest.Message.Condition = req.Condition
}
if req.Priority == "high" {
msgRequest.Message.Android.Urgency = "HIGH"
}
//if req.HuaweiCollapseKey != nil {
msgRequest.Message.Android.CollapseKey = req.HuaweiCollapseKey
//}
if len(req.Category) > 0 {
msgRequest.Message.Android.Category = req.Category
}
if len(req.HuaweiTTL) > 0 {
msgRequest.Message.Android.TTL = req.HuaweiTTL
}
if len(req.BiTag) > 0 {
msgRequest.Message.Android.BiTag = req.BiTag
}
//if req.FastAppTarget != nil {
msgRequest.Message.Android.FastAppTarget = req.FastAppTarget
//}
//Add data fields
if len(req.HuaweiData) > 0 {
msgRequest.Message.Data = req.HuaweiData
} else {
//Notification Message
msgRequest.Message.Android.Notification = model.GetDefaultAndroidNotification()
n := msgRequest.Message.Android.Notification
isNotificationSet := false
if req.HuaweiNotification != nil {
isNotificationSet = true
n = req.HuaweiNotification
if n.ClickAction == nil {
n.ClickAction = model.GetDefaultClickAction()
}
}
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
} else {
n.DefaultSound = true
}
if isNotificationSet {
msgRequest.Message.Android.Notification = n
}
}
// if len(req.Apns) > 0 {
// notification.Apns = req.Apns
// }
b, err := json.Marshal(msgRequest)
if err != nil {
fmt.Printf("Failed to marshal the default message! Error is %s\n", err.Error())
return nil, err
}
fmt.Printf("Default message is %s\n", string(b))
return msgRequest, nil
}
// PushToHuawei provide send notification to Android server.
func PushToHuawei(req PushNotification) bool {
LogAccess.Debug("Start push notification for Huawei")
var (
client *core.HMSClient
retryCount = 0
maxRetry = PushConf.Huawei.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, err := GetHuaweiNotification(req)
client, err = InitHMSClient(PushConf.Huawei.APIKey, PushConf.Huawei.APPId)
if err != nil {
// HMS server error
LogError.Error("HMS server error: " + err.Error())
return false
}
res, err := client.SendMessage(context.Background(), notification)
if err != nil {
// Send Message error
LogError.Error("HMS server send message error: " + err.Error())
return false
}
fmt.Println(res.Code)
fmt.Println(res.Msg)
fmt.Println(res.RequestId)
// Huawei Push Send API does not support exact results for each token
if res.Code == "80000000" {
StatStorage.AddHuaweiSuccess(int64(1))
LogAccess.Debug(fmt.Sprintf("Huwaei Send Notification is completed successfully!"))
} else {
isError = true
StatStorage.AddHuaweiError(int64(1))
LogAccess.Debug(fmt.Sprintf("Huwaei Send Notification is failed!"))
}
if isError && retryCount < maxRetry {
retryCount++
// resend all tokens
goto Retry
}
return isError
}

View File

@ -0,0 +1,67 @@
package gorush
import (
"context"
"log"
"sync"
"testing"
"github.com/appleboy/gorush/config"
"github.com/stretchr/testify/assert"
)
func init() {
PushConf, _ = config.LoadConf("")
if err := InitLog(); err != nil {
log.Fatal(err)
}
ctx := context.Background()
wg := &sync.WaitGroup{}
wg.Add(int(PushConf.Core.WorkerNum))
InitWorkers(ctx, wg, PushConf.Core.WorkerNum, PushConf.Core.QueueNum)
if err := InitAppStatus(); err != nil {
log.Fatal(err)
}
}
func TestMissingHuaweiAPIKey(t *testing.T) {
PushConf, _ = config.LoadConf("")
PushConf.Huawei.Enabled = true
PushConf.Huawei.APIKey = ""
err := CheckPushConf()
assert.Error(t, err)
assert.Equal(t, "Missing Huawei API Key", err.Error())
}
func TestMissingHuaweiAPPId(t *testing.T) {
PushConf, _ = config.LoadConf("")
PushConf.Huawei.Enabled = true
PushConf.Huawei.APPId = ""
err := CheckPushConf()
assert.Error(t, err)
assert.Equal(t, "Missing Huawei APP Id", err.Error())
}
func TestMissingKeyForInitHMSClient(t *testing.T) {
client, err := InitHMSClient("", "APP_ID")
assert.Nil(t, client)
assert.Error(t, err)
assert.Equal(t, "Missing Huawei API Key", err.Error())
}
func TestMissingAppIDForInitHMSClient(t *testing.T) {
client, err := InitHMSClient("APP_KEY", "")
assert.Nil(t, client)
assert.Error(t, err)
assert.Equal(t, "Missing Huawei APP Id", err.Error())
}

View File

@ -26,6 +26,7 @@ type StatusApp struct {
TotalCount int64 `json:"total_count"`
Ios IosStatus `json:"ios"`
Android AndroidStatus `json:"android"`
Huawei HuaweiStatus `json:"huawei"`
}
// AndroidStatus is android structure
@ -40,6 +41,12 @@ type IosStatus struct {
PushError int64 `json:"push_error"`
}
// HuaweiStatus is huawei structure
type HuaweiStatus struct {
PushSuccess int64 `json:"push_success"`
PushError int64 `json:"push_error"`
}
// InitAppStatus for initialize app status
func InitAppStatus() error {
LogAccess.Info("Init App Status Engine as ", PushConf.Stat.Engine)
@ -81,6 +88,8 @@ func appStatusHandler(c *gin.Context) {
result.Ios.PushError = StatStorage.GetIosError()
result.Android.PushSuccess = StatStorage.GetAndroidSuccess()
result.Android.PushError = StatStorage.GetAndroidError()
result.Huawei.PushSuccess = StatStorage.GetHuaweiSuccess()
result.Huawei.PushError = StatStorage.GetHuaweiError()
c.JSON(http.StatusOK, result)
}

View File

@ -15,7 +15,7 @@ func InitWorkers(ctx context.Context, wg *sync.WaitGroup, workerNum int64, queue
}
}
// SendNotification is send message to iOS or Android
// SendNotification is send message to iOS, Android or Huawei
func SendNotification(ctx context.Context, req PushNotification) {
if PushConf.Core.Sync {
defer req.WaitDone()
@ -26,6 +26,8 @@ func SendNotification(ctx context.Context, req PushNotification) {
PushToIOS(req)
case PlatFormAndroid:
PushToAndroid(req)
case PlatFormHuawei:
PushToHuawei(req)
}
}
@ -62,6 +64,10 @@ func queueNotification(ctx context.Context, req RequestPush) (int, []LogPushEntr
if !PushConf.Android.Enabled {
continue
}
case PlatFormHuawei:
if !PushConf.Huawei.Enabled {
continue
}
}
newNotification = append(newNotification, notification)
}

57
main.go
View File

@ -66,6 +66,10 @@ func main() {
flag.StringVar(&opts.Ios.Password, "password", "", "iOS certificate password for gorush")
flag.StringVar(&opts.Android.APIKey, "k", "", "Android api key configuration for gorush")
flag.StringVar(&opts.Android.APIKey, "apikey", "", "Android api key configuration for gorush")
flag.StringVar(&opts.Huawei.APIKey, "hk", "", "Huawei api key configuration for gorush")
flag.StringVar(&opts.Huawei.APIKey, "hmskey", "", "Huawei api key configuration for gorush")
flag.StringVar(&opts.Huawei.APPId, "hid", "", "HMS app id configuration for gorush")
flag.StringVar(&opts.Huawei.APPId, "hmsid", "", "HMS app id configuration for gorush")
flag.StringVar(&opts.Core.Address, "A", "", "address to bind")
flag.StringVar(&opts.Core.Address, "address", "", "address to bind")
flag.StringVar(&opts.Core.Port, "p", "", "port number for gorush")
@ -79,6 +83,7 @@ func main() {
flag.StringVar(&message, "message", "", "notification message")
flag.StringVar(&title, "title", "", "notification title")
flag.BoolVar(&opts.Android.Enabled, "android", false, "send android notification")
flag.BoolVar(&opts.Huawei.Enabled, "huawei", false, "send huawei notification")
flag.BoolVar(&opts.Ios.Enabled, "ios", false, "send ios notification")
flag.BoolVar(&opts.Ios.Production, "production", false, "production mode in iOS")
flag.StringVar(&topic, "topic", "", "apns topic in iOS")
@ -129,6 +134,14 @@ func main() {
gorush.PushConf.Android.APIKey = opts.Android.APIKey
}
if opts.Huawei.APIKey != "" {
gorush.PushConf.Huawei.APIKey = opts.Huawei.APIKey
}
if opts.Huawei.APPId != "" {
gorush.PushConf.Huawei.APPId = opts.Huawei.APPId
}
if opts.Stat.Engine != "" {
gorush.PushConf.Stat.Engine = opts.Stat.Engine
}
@ -202,6 +215,40 @@ func main() {
return
}
// send huawei notification
if opts.Huawei.Enabled {
gorush.PushConf.Huawei.Enabled = opts.Huawei.Enabled
req := gorush.PushNotification{
Platform: gorush.PlatFormHuawei,
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 {
gorush.LogError.Fatal(err)
}
if err := gorush.InitAppStatus(); err != nil {
return
}
gorush.PushToHuawei(req)
return
}
// send ios notification
if opts.Ios.Enabled {
if opts.Ios.Production {
@ -287,6 +334,10 @@ func main() {
gorush.LogError.Fatal(err)
}
if _, err = gorush.InitHMSClient(gorush.PushConf.Huawei.APIKey, gorush.PushConf.Huawei.APPId); err != nil {
gorush.LogError.Fatal(err)
}
var g errgroup.Group
// Run httpd server
@ -345,8 +396,12 @@ iOS Options:
Android Options:
-k, --apikey <api_key> Android API Key
--android enabled android (default: false)
Huawei Options:
-hk, --hmskey <hms_key> HMS API Key
-hid, --hmsid <hms_id> HMS APP Id
--huawei enabled huawei (default: false)
Common Options:
--topic <topic> iOS or Android topic message
--topic <topic> iOS, Android or Huawei topic message
-h, --help Show this message
-v, --version Show version
`

View File

@ -59,6 +59,8 @@ func (s *Storage) Reset() {
s.setBadger(storage.IosErrorKey, 0)
s.setBadger(storage.AndroidSuccessKey, 0)
s.setBadger(storage.AndroidErrorKey, 0)
s.setBadger(storage.HuaweiSuccessKey, 0)
s.setBadger(storage.HuaweiErrorKey, 0)
}
func (s *Storage) setBadger(key string, count int64) {
@ -129,6 +131,18 @@ func (s *Storage) AddAndroidError(count int64) {
s.setBadger(storage.AndroidErrorKey, total)
}
// AddHuaweiSuccess record counts of success Huawei push notification.
func (s *Storage) AddHuaweiSuccess(count int64) {
total := s.GetHuaweiSuccess() + count
s.setBadger(storage.HuaweiSuccessKey, total)
}
// AddHuaweiError record counts of error Huawei push notification.
func (s *Storage) AddHuaweiError(count int64) {
total := s.GetHuaweiError() + count
s.setBadger(storage.HuaweiErrorKey, total)
}
// GetTotalCount show counts of all notification.
func (s *Storage) GetTotalCount() int64 {
var count int64
@ -168,3 +182,19 @@ func (s *Storage) GetAndroidError() int64 {
return count
}
// GetHuaweiSuccess show success counts of Huawei notification.
func (s *Storage) GetHuaweiSuccess() int64 {
var count int64
s.getBadger(storage.HuaweiSuccessKey, &count)
return count
}
// GetHuaweiError show error counts of Huawei notification.
func (s *Storage) GetHuaweiError() int64 {
var count int64
s.getBadger(storage.HuaweiErrorKey, &count)
return count
}

View File

@ -45,6 +45,8 @@ func (s *Storage) Reset() {
s.setBoltDB(storage.IosErrorKey, 0)
s.setBoltDB(storage.AndroidSuccessKey, 0)
s.setBoltDB(storage.AndroidErrorKey, 0)
s.setBoltDB(storage.HuaweiSuccessKey, 0)
s.setBoltDB(storage.HuaweiErrorKey, 0)
}
func (s *Storage) setBoltDB(key string, count int64) {
@ -91,6 +93,18 @@ func (s *Storage) AddAndroidError(count int64) {
s.setBoltDB(storage.AndroidErrorKey, total)
}
// AddHuaweiSuccess record counts of success Huawei push notification.
func (s *Storage) AddHuaweiSuccess(count int64) {
total := s.GetHuaweiSuccess() + count
s.setBoltDB(storage.HuaweiSuccessKey, total)
}
// AddHuaweiError record counts of error Huawei push notification.
func (s *Storage) AddHuaweiError(count int64) {
total := s.GetHuaweiError() + count
s.setBoltDB(storage.HuaweiErrorKey, total)
}
// GetTotalCount show counts of all notification.
func (s *Storage) GetTotalCount() int64 {
var count int64
@ -130,3 +144,19 @@ func (s *Storage) GetAndroidError() int64 {
return count
}
// GetHuaweiSuccess show success counts of Huawei notification.
func (s *Storage) GetHuaweiSuccess() int64 {
var count int64
s.getBoltDB(storage.HuaweiSuccessKey, &count)
return count
}
// GetHuaweiError show error counts of Huawei notification.
func (s *Storage) GetHuaweiError() int64 {
var count int64
s.getBoltDB(storage.HuaweiErrorKey, &count)
return count
}

View File

@ -47,6 +47,8 @@ func (s *Storage) Reset() {
s.setBuntDB(storage.IosErrorKey, 0)
s.setBuntDB(storage.AndroidSuccessKey, 0)
s.setBuntDB(storage.AndroidErrorKey, 0)
s.setBuntDB(storage.HuaweiSuccessKey, 0)
s.setBuntDB(storage.HuaweiErrorKey, 0)
}
func (s *Storage) setBuntDB(key string, count int64) {
@ -104,6 +106,18 @@ func (s *Storage) AddAndroidError(count int64) {
s.setBuntDB(storage.AndroidErrorKey, total)
}
// AddHuaweiSuccess record counts of success Huawei push notification.
func (s *Storage) AddHuaweiSuccess(count int64) {
total := s.GetHuaweiSuccess() + count
s.setBuntDB(storage.HuaweiSuccessKey, total)
}
// AddHuaweiError record counts of error Huawei push notification.
func (s *Storage) AddHuaweiError(count int64) {
total := s.GetHuaweiError() + count
s.setBuntDB(storage.HuaweiErrorKey, total)
}
// GetTotalCount show counts of all notification.
func (s *Storage) GetTotalCount() int64 {
var count int64
@ -143,3 +157,19 @@ func (s *Storage) GetAndroidError() int64 {
return count
}
// GetHuaweiSuccess show success counts of Huawei notification.
func (s *Storage) GetHuaweiSuccess() int64 {
var count int64
s.getBuntDB(storage.HuaweiSuccessKey, &count)
return count
}
// GetHuaweiError show error counts of Huawei notification.
func (s *Storage) GetHuaweiError() int64 {
var count int64
s.getBuntDB(storage.HuaweiErrorKey, &count)
return count
}

View File

@ -56,6 +56,8 @@ func (s *Storage) Reset() {
s.setLevelDB(storage.IosErrorKey, 0)
s.setLevelDB(storage.AndroidSuccessKey, 0)
s.setLevelDB(storage.AndroidErrorKey, 0)
s.setLevelDB(storage.HuaweiSuccessKey, 0)
s.setLevelDB(storage.HuaweiErrorKey, 0)
}
// AddTotalCount record push notification count.
@ -88,6 +90,18 @@ func (s *Storage) AddAndroidError(count int64) {
s.setLevelDB(storage.AndroidErrorKey, total)
}
// AddHuaweiSuccess record counts of success Huawei push notification.
func (s *Storage) AddHuaweiSuccess(count int64) {
total := s.GetHuaweiSuccess() + count
s.setLevelDB(storage.HuaweiSuccessKey, total)
}
// AddHuaweiError record counts of error Huawei push notification.
func (s *Storage) AddHuaweiError(count int64) {
total := s.GetHuaweiError() + count
s.setLevelDB(storage.HuaweiErrorKey, total)
}
// GetTotalCount show counts of all notification.
func (s *Storage) GetTotalCount() int64 {
var count int64
@ -127,3 +141,19 @@ func (s *Storage) GetAndroidError() int64 {
return count
}
// GetHuaweiSuccess show success counts of Huawei notification.
func (s *Storage) GetHuaweiSuccess() int64 {
var count int64
s.getLevelDB(storage.HuaweiSuccessKey, &count)
return count
}
// GetHuaweiError show error counts of Huawei notification.
func (s *Storage) GetHuaweiError() int64 {
var count int64
s.getLevelDB(storage.HuaweiErrorKey, &count)
return count
}

View File

@ -9,6 +9,7 @@ type statApp struct {
TotalCount int64 `json:"total_count"`
Ios IosStatus `json:"ios"`
Android AndroidStatus `json:"android"`
Huawei HuaweiStatus `json:"huawei"`
}
// AndroidStatus is android structure
@ -23,6 +24,12 @@ type IosStatus struct {
PushError int64 `json:"push_error"`
}
// HuaweiStatus is android structure
type HuaweiStatus struct {
PushSuccess int64 `json:"push_success"`
PushError int64 `json:"push_error"`
}
// New func implements the storage interface for gorush (https://github.com/appleboy/gorush)
func New() *Storage {
return &Storage{
@ -52,6 +59,8 @@ func (s *Storage) Reset() {
atomic.StoreInt64(&s.stat.Ios.PushError, 0)
atomic.StoreInt64(&s.stat.Android.PushSuccess, 0)
atomic.StoreInt64(&s.stat.Android.PushError, 0)
atomic.StoreInt64(&s.stat.Huawei.PushSuccess, 0)
atomic.StoreInt64(&s.stat.Huawei.PushError, 0)
}
// AddTotalCount record push notification count.
@ -79,6 +88,16 @@ func (s *Storage) AddAndroidError(count int64) {
atomic.AddInt64(&s.stat.Android.PushError, count)
}
// AddHuaweiSuccess record counts of success Huawei push notification.
func (s *Storage) AddHuaweiSuccess(count int64) {
atomic.AddInt64(&s.stat.Huawei.PushSuccess, count)
}
// AddHuaweiError record counts of error Huawei push notification.
func (s *Storage) AddHuaweiError(count int64) {
atomic.AddInt64(&s.stat.Huawei.PushError, count)
}
// GetTotalCount show counts of all notification.
func (s *Storage) GetTotalCount() int64 {
count := atomic.LoadInt64(&s.stat.TotalCount)
@ -113,3 +132,17 @@ func (s *Storage) GetAndroidError() int64 {
return count
}
// GetHuaweiSuccess show success counts of Huawei notification.
func (s *Storage) GetHuaweiSuccess() int64 {
count := atomic.LoadInt64(&s.stat.Huawei.PushSuccess)
return count
}
// GetHuaweiError show error counts of Huawei notification.
func (s *Storage) GetHuaweiError() int64 {
count := atomic.LoadInt64(&s.stat.Huawei.PushError)
return count
}

View File

@ -55,6 +55,8 @@ func (s *Storage) Reset() {
s.client.Set(storage.IosErrorKey, int64(0), 0)
s.client.Set(storage.AndroidSuccessKey, int64(0), 0)
s.client.Set(storage.AndroidErrorKey, int64(0), 0)
s.client.Set(storage.HuaweiSuccessKey, int64(0), 0)
s.client.Set(storage.HuaweiErrorKey, int64(0), 0)
}
// AddTotalCount record push notification count.
@ -82,6 +84,16 @@ func (s *Storage) AddAndroidError(count int64) {
s.client.IncrBy(storage.AndroidErrorKey, count)
}
// AddHuaweiSuccess record counts of success Android push notification.
func (s *Storage) AddHuaweiSuccess(count int64) {
s.client.IncrBy(storage.HuaweiSuccessKey, count)
}
// AddHuaweiError record counts of error Android push notification.
func (s *Storage) AddHuaweiError(count int64) {
s.client.IncrBy(storage.HuaweiErrorKey, count)
}
// GetTotalCount show counts of all notification.
func (s *Storage) GetTotalCount() int64 {
var count int64
@ -121,3 +133,19 @@ func (s *Storage) GetAndroidError() int64 {
return count
}
// GetHuaweiSuccess show success counts of Huawei notification.
func (s *Storage) GetHuaweiSuccess() int64 {
var count int64
s.getInt64(storage.HuaweiSuccessKey, &count)
return count
}
// GetHuaweiError show error counts of Huawei notification.
func (s *Storage) GetHuaweiError() int64 {
var count int64
s.getInt64(storage.HuaweiErrorKey, &count)
return count
}

View File

@ -15,6 +15,12 @@ const (
// AndroidErrorKey is key name for android error count of storage
AndroidErrorKey = "gorush-android-error-count"
// HuaweiSuccessKey is key name for huawei success count of storage
HuaweiSuccessKey = "gorush-huawei-success-count"
// HuaweiErrorKey is key name for huawei error count of storage
HuaweiErrorKey = "gorush-huawei-error-count"
)
// Storage interface
@ -26,10 +32,14 @@ type Storage interface {
AddIosError(int64)
AddAndroidSuccess(int64)
AddAndroidError(int64)
AddHuaweiSuccess(int64)
AddHuaweiError(int64)
GetTotalCount() int64
GetIosSuccess() int64
GetIosError() int64
GetAndroidSuccess() int64
GetAndroidError() int64
GetHuaweiSuccess() int64
GetHuaweiError() int64
Close() error
}