Merge pull request #47 from appleboy/payload
Fix #46 Enhance document and android payload
This commit is contained in:
commit
3a8c8d1c9d
159
README.md
159
README.md
|
@ -183,6 +183,9 @@ Send multiple notifications as below:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Request body
|
||||||
|
|
||||||
Request body must has a notifications array. The following is a parameter table for each notification.
|
Request body must has a notifications array. The following is a parameter table for each notification.
|
||||||
|
|
||||||
|name|type|description|required|note|
|
|name|type|description|required|note|
|
||||||
|
@ -190,8 +193,11 @@ Request body must has a notifications array. The following is a parameter table
|
||||||
|tokens|string array|device tokens|o||
|
|tokens|string array|device tokens|o||
|
||||||
|platform|int|platform(iOS,Android)|o|1=iOS, 2=Android|
|
|platform|int|platform(iOS,Android)|o|1=iOS, 2=Android|
|
||||||
|message|string|message for notification|o||
|
|message|string|message for notification|o||
|
||||||
|priority|string|Sets the priority of the message.|-||
|
|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.|-||
|
|content_available|bool|data messages wake the app by default.|-||
|
||||||
|
|sound|string|sound type|-||
|
||||||
|
|data|string array|extensible partition|-||
|
||||||
|api_key|string|Android api key|-|only Android|
|
|api_key|string|Android api key|-|only Android|
|
||||||
|to|string|The value must be a registration token, notification key, or topic.|-|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|
|
|collapse_key|string|a key for collapsing notifications|-|only Android|
|
||||||
|
@ -199,18 +205,157 @@ Request body must has a notifications array. The following is a parameter table
|
||||||
|time_to_live|int|expiration of message kept on GCM storage|-|only Android|
|
|time_to_live|int|expiration of message kept on GCM storage|-|only Android|
|
||||||
|restricted_package_name|string|the package name of the application|-|only Android|
|
|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|
|
|dry_run|bool|allows developers to test a request without actually sending a message|-|only Android|
|
||||||
|data|string array|data payload of a GCM message|-|only Android|
|
|notification|string array|payload of a GCM message|-|only Android. See the [detail](#android-notification-payload)|
|
||||||
|notification|string array|payload of a GCM message|-|only Android|
|
|
||||||
|expiration|int|expiration for notification|-|only iOS|
|
|expiration|int|expiration for notification|-|only iOS|
|
||||||
|apns_id|string|A canonical UUID that identifies the notification|-|only iOS|
|
|apns_id|string|A canonical UUID that identifies the notification|-|only iOS|
|
||||||
|topic|string|topic of the remote notification|-|only iOS|
|
|topic|string|topic of the remote notification|-|only iOS|
|
||||||
|badge|int|badge count|-|only iOS|
|
|badge|int|badge count|-|only iOS|
|
||||||
|sound|string|sound type|-|only iOS|
|
|
||||||
|category|string|the UIMutableUserNotificationCategory object|-|only iOS|
|
|category|string|the UIMutableUserNotificationCategory object|-|only iOS|
|
||||||
|extend|string array|extensible partition|-|only iOS|
|
|alert|string array|payload of a iOS message|-|only iOS. See the [detail](#ios-alert-payload)|
|
||||||
|alert|string array|payload of a iOS message|-|only iOS|
|
|
||||||
|
|
||||||
See more detail [APNs](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/APNsProviderAPI.html#//apple_ref/doc/uid/TP40008194-CH101-SW1) and [GCM](https://developers.google.com/cloud-messaging/http-server-ref#send-downstream) reference.
|
### iOS alert payload
|
||||||
|
|
||||||
|
|name|type|description|required|note|
|
||||||
|
|-------|-------|--------|--------|---------|
|
||||||
|
|action|string|The label of the action button. This one is required for Safari Push Notifications.|-||
|
||||||
|
|action-loc-key|string|If a string is specified, the system displays an alert that includes the Close and View buttons.|-||
|
||||||
|
|launch-image|string|The filename of an image file in the app bundle, with or without the filename extension.|-||
|
||||||
|
|loc-args|array of strings|Variable string values to appear in place of the format specifiers in loc-key.|-||
|
||||||
|
|loc-key|string|A key to an alert-message string in a Localizable.strings file for the current localization.|-||
|
||||||
|
|title-loc-args|array of strings|Variable string values to appear in place of the format specifiers in title-loc-key.|-||
|
||||||
|
|title-loc-key|string|The key to a title string in the Localizable.strings file for the current localization.|-||
|
||||||
|
|
||||||
|
See more detail about [APNs Remote Notification Payload](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/TheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH107-SW1).
|
||||||
|
|
||||||
|
### Android notification payload
|
||||||
|
|
||||||
|
|name|type|description|required|note|
|
||||||
|
|-------|-------|--------|--------|---------|
|
||||||
|
|icon|string|Indicates notification icon.|-||
|
||||||
|
|tag|string|Indicates whether each notification message results in a new entry on the notification center on Android.|-||
|
||||||
|
|color|string|Indicates color of the icon, expressed in #rrggbb format|-||
|
||||||
|
|click_action|string|The action associated with a user click on the notification.|-||
|
||||||
|
|body_loc_key|string|Indicates the key to the body string for localization.|-||
|
||||||
|
|body_loc_args|string|Indicates the string value to replace format specifiers in body string for localization.|-||
|
||||||
|
|title_loc_key|string|Indicates the key to the title string for localization.|-||
|
||||||
|
|title_loc_args|string|Indicates the string value to replace format specifiers in title string for localization.|-||
|
||||||
|
|
||||||
|
See more detail about [GCM server reference](https://developers.google.com/cloud-messaging/http-server-ref#send-downstream).
|
||||||
|
|
||||||
|
### iOS Example
|
||||||
|
|
||||||
|
Send normal notification.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"notifications": [
|
||||||
|
{
|
||||||
|
"tokens": ["token_a", "token_b"],
|
||||||
|
"platform": 1,
|
||||||
|
"message": "Hello World iOS!",
|
||||||
|
"title": "You got message"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The app icon be badged with the number `9` and that a bundled alert sound be played when the notification is delivered.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"notifications": [
|
||||||
|
{
|
||||||
|
"tokens": ["token_a", "token_b"],
|
||||||
|
"platform": 1,
|
||||||
|
"message": "Hello World iOS!",
|
||||||
|
"title": "You got message",
|
||||||
|
"badge": 9,
|
||||||
|
"sound": "bingbong.aiff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Add other fields which user defined via `data` field.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"notifications": [
|
||||||
|
{
|
||||||
|
"tokens": ["token_a", "token_b"],
|
||||||
|
"platform": 1,
|
||||||
|
"message": "Hello World iOS!",
|
||||||
|
"title": "You got message",
|
||||||
|
"data": {
|
||||||
|
"key1": "welcome",
|
||||||
|
"key2": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Android Example
|
||||||
|
|
||||||
|
Send normal notification.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"notifications": [
|
||||||
|
{
|
||||||
|
"tokens": ["token_a", "token_b"],
|
||||||
|
"platform": 2,
|
||||||
|
"message": "Hello World Android!",
|
||||||
|
"title": "You got message"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `notification` payload.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"notifications": [
|
||||||
|
{
|
||||||
|
"tokens": ["token_a", "token_b"],
|
||||||
|
"platform": 2,
|
||||||
|
"message": "Hello World Android!",
|
||||||
|
"title": "You got message",
|
||||||
|
"notification" : {
|
||||||
|
"icon": "myicon",
|
||||||
|
"color": "#112244"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Add other fields which user defined via `data` field.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"notifications": [
|
||||||
|
{
|
||||||
|
"tokens": ["token_a", "token_b"],
|
||||||
|
"platform": 2,
|
||||||
|
"message": "Hello World Android!",
|
||||||
|
"title": "You got message",
|
||||||
|
"data": {
|
||||||
|
"Nick" : "Mario",
|
||||||
|
"body" : "great match!",
|
||||||
|
"Room" : "PortugalVSDenmark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response body
|
||||||
|
|
||||||
|
Error response message table:
|
||||||
|
|
||||||
|
|status code|message|
|
||||||
|
|-------|-------|
|
||||||
|
|400|Missing `notifications` field.|
|
||||||
|
|400|Notifications field is empty.|
|
||||||
|
|400|Number of notifications(50) over limit(10)|
|
||||||
|
|
||||||
|
Success response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": "ok"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExtendJSON struct {
|
type D map[string]interface{}
|
||||||
Key string `json:"key"`
|
|
||||||
Value string `json:"val"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// PriorityLow will tell APNs to send the push message at a time that takes
|
// PriorityLow will tell APNs to send the push message at a time that takes
|
||||||
|
@ -50,8 +47,11 @@ type PushNotification struct {
|
||||||
Tokens []string `json:"tokens" binding:"required"`
|
Tokens []string `json:"tokens" binding:"required"`
|
||||||
Platform int `json:"platform" binding:"required"`
|
Platform int `json:"platform" binding:"required"`
|
||||||
Message string `json:"message" binding:"required"`
|
Message string `json:"message" binding:"required"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
Priority string `json:"priority,omitempty"`
|
Priority string `json:"priority,omitempty"`
|
||||||
ContentAvailable bool `json:"content_available,omitempty"`
|
ContentAvailable bool `json:"content_available,omitempty"`
|
||||||
|
Sound string `json:"sound,omitempty"`
|
||||||
|
Data D `json:"data,omitempty"`
|
||||||
|
|
||||||
// Android
|
// Android
|
||||||
ApiKey string `json:"api_key,omitempty"`
|
ApiKey string `json:"api_key,omitempty"`
|
||||||
|
@ -61,19 +61,16 @@ 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"`
|
||||||
Data gcm.Data `json:"data,omitempty"`
|
|
||||||
Notification gcm.Notification `json:"notification,omitempty"`
|
Notification gcm.Notification `json:"notification,omitempty"`
|
||||||
|
|
||||||
// iOS
|
// iOS
|
||||||
Expiration int64 `json:"expiration,omitempty"`
|
Expiration int64 `json:"expiration,omitempty"`
|
||||||
ApnsID string `json:"apns_id,omitempty"`
|
ApnsID string `json:"apns_id,omitempty"`
|
||||||
Topic string `json:"topic,omitempty"`
|
Topic string `json:"topic,omitempty"`
|
||||||
Badge int `json:"badge,omitempty"`
|
Badge int `json:"badge,omitempty"`
|
||||||
Sound string `json:"sound,omitempty"`
|
Category string `json:"category,omitempty"`
|
||||||
Category string `json:"category,omitempty"`
|
URLArgs []string `json:"url-args,omitempty"`
|
||||||
URLArgs []string `json:"url-args,omitempty"`
|
Alert Alert `json:"alert,omitempty"`
|
||||||
Extend []ExtendJSON `json:"extend,omitempty"`
|
|
||||||
Alert Alert `json:"alert,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckPushConf() error {
|
func CheckPushConf() error {
|
||||||
|
@ -177,16 +174,16 @@ func GetIOSNotification(req PushNotification) *apns.Notification {
|
||||||
payload.ContentAvailable()
|
payload.ContentAvailable()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.Extend) > 0 {
|
if len(req.Data) > 0 {
|
||||||
for _, extend := range req.Extend {
|
for k, v := range req.Data {
|
||||||
payload.Custom(extend.Key, extend.Value)
|
payload.Custom(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert dictionary
|
// Alert dictionary
|
||||||
|
|
||||||
if len(req.Alert.Title) > 0 {
|
if len(req.Title) > 0 {
|
||||||
payload.AlertTitle(req.Alert.Title)
|
payload.AlertTitle(req.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.Alert.TitleLocKey) > 0 {
|
if len(req.Alert.TitleLocKey) > 0 {
|
||||||
|
@ -308,8 +305,12 @@ func GetAndroidNotification(req PushNotification) gcm.HttpMessage {
|
||||||
notification.DryRun = true
|
notification.DryRun = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add another field
|
||||||
if len(req.Data) > 0 {
|
if len(req.Data) > 0 {
|
||||||
notification.Data = req.Data
|
notification.Data = make(map[string]interface{})
|
||||||
|
for k, v := range req.Data {
|
||||||
|
notification.Data[k] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.Notification = &req.Notification
|
notification.Notification = &req.Notification
|
||||||
|
@ -319,6 +320,14 @@ func GetAndroidNotification(req PushNotification) gcm.HttpMessage {
|
||||||
notification.Notification.Body = req.Message
|
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
|
return notification
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,15 +74,9 @@ func TestIOSNotificationStructure(t *testing.T) {
|
||||||
Badge: 1,
|
Badge: 1,
|
||||||
Sound: test,
|
Sound: test,
|
||||||
ContentAvailable: true,
|
ContentAvailable: true,
|
||||||
Extend: []ExtendJSON{
|
Data: D{
|
||||||
{
|
"key1": "test",
|
||||||
Key: "key1",
|
"key2": 2,
|
||||||
Value: "1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "key2",
|
|
||||||
Value: "2",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Category: test,
|
Category: test,
|
||||||
URLArgs: []string{"a", "b"},
|
URLArgs: []string{"a", "b"},
|
||||||
|
@ -103,8 +97,8 @@ func TestIOSNotificationStructure(t *testing.T) {
|
||||||
sound, _ := jsonparser.GetString(data, "aps", "sound")
|
sound, _ := jsonparser.GetString(data, "aps", "sound")
|
||||||
contentAvailable, _ := jsonparser.GetInt(data, "aps", "content-available")
|
contentAvailable, _ := jsonparser.GetInt(data, "aps", "content-available")
|
||||||
category, _ := jsonparser.GetString(data, "aps", "category")
|
category, _ := jsonparser.GetString(data, "aps", "category")
|
||||||
key1 := dat["key1"].(string)
|
key1 := dat["key1"].(interface{})
|
||||||
key2 := dat["key2"].(string)
|
key2 := dat["key2"].(interface{})
|
||||||
aps := dat["aps"].(map[string]interface{})
|
aps := dat["aps"].(map[string]interface{})
|
||||||
urlArgs := aps["url-args"].([]interface{})
|
urlArgs := aps["url-args"].([]interface{})
|
||||||
|
|
||||||
|
@ -116,8 +110,8 @@ func TestIOSNotificationStructure(t *testing.T) {
|
||||||
assert.Equal(t, 1, int(badge))
|
assert.Equal(t, 1, int(badge))
|
||||||
assert.Equal(t, test, sound)
|
assert.Equal(t, test, sound)
|
||||||
assert.Equal(t, 1, int(contentAvailable))
|
assert.Equal(t, 1, int(contentAvailable))
|
||||||
assert.Equal(t, "1", key1)
|
assert.Equal(t, "test", key1)
|
||||||
assert.Equal(t, "2", key2)
|
assert.Equal(t, 2, int(key2.(float64)))
|
||||||
assert.Equal(t, test, category)
|
assert.Equal(t, test, category)
|
||||||
assert.Contains(t, urlArgs, "a")
|
assert.Contains(t, urlArgs, "a")
|
||||||
assert.Contains(t, urlArgs, "b")
|
assert.Contains(t, urlArgs, "b")
|
||||||
|
@ -128,6 +122,8 @@ func TestIOSAlertNotificationStructure(t *testing.T) {
|
||||||
|
|
||||||
test := "test"
|
test := "test"
|
||||||
req := PushNotification{
|
req := PushNotification{
|
||||||
|
Message: "Welcome",
|
||||||
|
Title: test,
|
||||||
Alert: Alert{
|
Alert: Alert{
|
||||||
Action: test,
|
Action: test,
|
||||||
ActionLocKey: test,
|
ActionLocKey: test,
|
||||||
|
@ -135,7 +131,6 @@ func TestIOSAlertNotificationStructure(t *testing.T) {
|
||||||
LaunchImage: test,
|
LaunchImage: test,
|
||||||
LocArgs: []string{"a", "b"},
|
LocArgs: []string{"a", "b"},
|
||||||
LocKey: test,
|
LocKey: test,
|
||||||
Title: test,
|
|
||||||
TitleLocArgs: []string{"a", "b"},
|
TitleLocArgs: []string{"a", "b"},
|
||||||
TitleLocKey: test,
|
TitleLocKey: test,
|
||||||
},
|
},
|
||||||
|
@ -190,12 +185,15 @@ func TestAndroidNotificationStructure(t *testing.T) {
|
||||||
TimeToLive: 100,
|
TimeToLive: 100,
|
||||||
RestrictedPackageName: test,
|
RestrictedPackageName: test,
|
||||||
DryRun: true,
|
DryRun: true,
|
||||||
Data: map[string]interface{}{
|
Title: test,
|
||||||
|
Sound: test,
|
||||||
|
Data: D{
|
||||||
"a": "1",
|
"a": "1",
|
||||||
"b": "2",
|
"b": 2,
|
||||||
},
|
},
|
||||||
Notification: gcm.Notification{
|
Notification: gcm.Notification{
|
||||||
Title: test,
|
Color: test,
|
||||||
|
Tag: test,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +208,12 @@ func TestAndroidNotificationStructure(t *testing.T) {
|
||||||
assert.Equal(t, test, notification.RestrictedPackageName)
|
assert.Equal(t, test, notification.RestrictedPackageName)
|
||||||
assert.True(t, notification.DryRun)
|
assert.True(t, notification.DryRun)
|
||||||
assert.Equal(t, test, notification.Notification.Title)
|
assert.Equal(t, test, notification.Notification.Title)
|
||||||
|
assert.Equal(t, test, notification.Notification.Sound)
|
||||||
|
assert.Equal(t, test, notification.Notification.Color)
|
||||||
|
assert.Equal(t, test, notification.Notification.Tag)
|
||||||
assert.Equal(t, "Welcome", notification.Notification.Body)
|
assert.Equal(t, "Welcome", notification.Notification.Body)
|
||||||
|
assert.Equal(t, "1", notification.Data["a"])
|
||||||
|
assert.Equal(t, 2, notification.Data["b"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPushToIOS(t *testing.T) {
|
func TestPushToIOS(t *testing.T) {
|
||||||
|
|
|
@ -34,7 +34,7 @@ func pushHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Notifications) == 0 {
|
if len(form.Notifications) == 0 {
|
||||||
msg = "Notification field is empty."
|
msg = "Notifications field is empty."
|
||||||
LogAccess.Debug(msg)
|
LogAccess.Debug(msg)
|
||||||
AbortWithError(c, http.StatusBadRequest, msg)
|
AbortWithError(c, http.StatusBadRequest, msg)
|
||||||
return
|
return
|
||||||
|
@ -51,7 +51,7 @@ func pushHandler(c *gin.Context) {
|
||||||
go SendNotification(form)
|
go SendNotification(form)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"text": "Welcome to notification server.",
|
"success": "ok",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue