From 08339e36d7f525df8207534b84668c1511955e6e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 13 Apr 2016 10:06:21 +0800 Subject: [PATCH 1/3] Enhance document and android payload Signed-off-by: Bo-Yi Wu --- README.md | 41 ++++++++++++++++++++++++++---- gorush/notification.go | 50 +++++++++++++++++++++++++------------ gorush/notification_test.go | 50 +++++++++++++++++++++++++++++++++---- 3 files changed, 115 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7a44575..52d5adf 100644 --- a/README.md +++ b/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. |name|type|description|required|note| @@ -192,6 +195,9 @@ Request body must has a notifications array. The following is a parameter table |message|string|message for notification|o|| |priority|string|Sets the priority of the message.|-|| |content_available|bool|data messages wake the app by default.|-|| +|sound|string|sound type|-|| +|title|string|notification title|-|| +|extend|string array|extensible partition|-|| |api_key|string|Android api key|-|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| @@ -200,17 +206,42 @@ Request body must has a notifications array. The following is a parameter table |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| |data|string array|data payload of a GCM message|-|only Android| -|notification|string array|payload of a GCM message|-|only Android| +|notification|string array|payload of a GCM message|-|only Android. See the [detail]()| |expiration|int|expiration for notification|-|only iOS| |apns_id|string|A canonical UUID that identifies the notification|-|only iOS| |topic|string|topic of the remote notification|-|only iOS| |badge|int|badge count|-|only iOS| -|sound|string|sound type|-|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| +|alert|string array|payload of a iOS message|-|only iOS. See the [detail]()| -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 structure + +|name|type|description|required|note| +|-------|-------|--------|--------|---------| +|action||||| +|action-loc-key||||| +|launch-image||||| +|loc-args||||| +|loc-key||||| +|title-loc-args||||| +|title-loc-key||||| + +See more detail about [APNs Provider API](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/APNsProviderAPI.html#//apple_ref/doc/uid/TP40008194-CH101-SW1). + +### Android notification structure + +|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). ## License diff --git a/gorush/notification.go b/gorush/notification.go index 07e0267..e0aaf8e 100644 --- a/gorush/notification.go +++ b/gorush/notification.go @@ -47,11 +47,14 @@ type RequestPush struct { type PushNotification struct { // Common - Tokens []string `json:"tokens" binding:"required"` - Platform int `json:"platform" binding:"required"` - Message string `json:"message" binding:"required"` - Priority string `json:"priority,omitempty"` - ContentAvailable bool `json:"content_available,omitempty"` + Tokens []string `json:"tokens" binding:"required"` + Platform int `json:"platform" binding:"required"` + Message string `json:"message" binding:"required"` + Title string `json:"title,omitempty"` + Priority string `json:"priority,omitempty"` + ContentAvailable bool `json:"content_available,omitempty"` + Sound string `json:"sound,omitempty"` + Extend []ExtendJSON `json:"extend,omitempty"` // Android ApiKey string `json:"api_key,omitempty"` @@ -65,15 +68,13 @@ type PushNotification struct { Notification gcm.Notification `json:"notification,omitempty"` // iOS - Expiration int64 `json:"expiration,omitempty"` - ApnsID string `json:"apns_id,omitempty"` - Topic string `json:"topic,omitempty"` - Badge int `json:"badge,omitempty"` - Sound string `json:"sound,omitempty"` - Category string `json:"category,omitempty"` - URLArgs []string `json:"url-args,omitempty"` - Extend []ExtendJSON `json:"extend,omitempty"` - Alert Alert `json:"alert,omitempty"` + Expiration int64 `json:"expiration,omitempty"` + ApnsID string `json:"apns_id,omitempty"` + Topic string `json:"topic,omitempty"` + Badge int `json:"badge,omitempty"` + Category string `json:"category,omitempty"` + URLArgs []string `json:"url-args,omitempty"` + Alert Alert `json:"alert,omitempty"` } func CheckPushConf() error { @@ -185,8 +186,8 @@ func GetIOSNotification(req PushNotification) *apns.Notification { // Alert dictionary - if len(req.Alert.Title) > 0 { - payload.AlertTitle(req.Alert.Title) + if len(req.Title) > 0 { + payload.AlertTitle(req.Title) } if len(req.Alert.TitleLocKey) > 0 { @@ -308,6 +309,15 @@ func GetAndroidNotification(req PushNotification) gcm.HttpMessage { notification.DryRun = true } + if len(req.Extend) > 0 { + notification.Data = make(map[string]interface{}) + + for _, extend := range req.Extend { + notification.Data[extend.Key] = extend.Value + } + } + + // overwrite Extend if len(req.Data) > 0 { notification.Data = req.Data } @@ -319,6 +329,14 @@ func GetAndroidNotification(req PushNotification) gcm.HttpMessage { 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 } diff --git a/gorush/notification_test.go b/gorush/notification_test.go index cea931b..a39f112 100644 --- a/gorush/notification_test.go +++ b/gorush/notification_test.go @@ -128,6 +128,8 @@ func TestIOSAlertNotificationStructure(t *testing.T) { test := "test" req := PushNotification{ + Message: "Welcome", + Title: test, Alert: Alert{ Action: test, ActionLocKey: test, @@ -135,7 +137,6 @@ func TestIOSAlertNotificationStructure(t *testing.T) { LaunchImage: test, LocArgs: []string{"a", "b"}, LocKey: test, - Title: test, TitleLocArgs: []string{"a", "b"}, TitleLocKey: test, }, @@ -190,12 +191,21 @@ func TestAndroidNotificationStructure(t *testing.T) { TimeToLive: 100, RestrictedPackageName: test, DryRun: true, - Data: map[string]interface{}{ - "a": "1", - "b": "2", + Title: test, + Sound: test, + Extend: []ExtendJSON{ + { + Key: "key1", + Value: "1", + }, + { + Key: "key2", + Value: "2", + }, }, Notification: gcm.Notification{ - Title: test, + Color: test, + Tag: test, }, } @@ -210,7 +220,37 @@ func TestAndroidNotificationStructure(t *testing.T) { assert.Equal(t, test, notification.RestrictedPackageName) assert.True(t, notification.DryRun) 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, "1", notification.Data["key1"]) + + // add data file to overwrite `Extend` + req = PushNotification{ + Tokens: []string{"a", "b"}, + Message: "Welcome", + To: test, + Data: map[string]interface{}{ + "a": "1", + "b": "2", + }, + Extend: []ExtendJSON{ + { + Key: "key1", + Value: "1", + }, + { + Key: "key2", + Value: "2", + }, + }, + } + + notification = GetAndroidNotification(req) + + assert.Equal(t, "1", notification.Data["a"]) + assert.Equal(t, "2", notification.Data["b"]) } func TestPushToIOS(t *testing.T) { From 9bc2df6baa71163f544c65391ebfb919b0783e9c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 13 Apr 2016 11:15:24 +0800 Subject: [PATCH 2/3] replace Extend with data field. Signed-off-by: Bo-Yi Wu --- README.md | 79 +++++++++++++++++++++++++++++-------- gorush/notification.go | 43 ++++++++------------ gorush/notification_test.go | 59 ++++++--------------------- 3 files changed, 91 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 52d5adf..07f726f 100644 --- a/README.md +++ b/README.md @@ -193,11 +193,11 @@ Request body must has a notifications array. The following is a parameter table |tokens|string array|device tokens|o|| |platform|int|platform(iOS,Android)|o|1=iOS, 2=Android| |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.|-|| |sound|string|sound type|-|| -|title|string|notification title|-|| -|extend|string array|extensible partition|-|| +|data|string array|extensible partition|-|| |api_key|string|Android api key|-|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| @@ -205,30 +205,29 @@ 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| |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| -|data|string array|data payload of a GCM message|-|only Android| -|notification|string array|payload of a GCM message|-|only Android. See the [detail]()| +|notification|string array|payload of a GCM message|-|only Android. See the [detail](#android-notification-payload)| |expiration|int|expiration for notification|-|only iOS| |apns_id|string|A canonical UUID that identifies the notification|-|only iOS| |topic|string|topic of the remote notification|-|only iOS| |badge|int|badge count|-|only iOS| |category|string|the UIMutableUserNotificationCategory object|-|only iOS| -|alert|string array|payload of a iOS message|-|only iOS. See the [detail]()| +|alert|string array|payload of a iOS message|-|only iOS. See the [detail](#ios-alert-payload)| -### iOS alert structure +### iOS alert payload |name|type|description|required|note| |-------|-------|--------|--------|---------| -|action||||| -|action-loc-key||||| -|launch-image||||| -|loc-args||||| -|loc-key||||| -|title-loc-args||||| -|title-loc-key||||| +|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 Provider API](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/APNsProviderAPI.html#//apple_ref/doc/uid/TP40008194-CH101-SW1). +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 structure +### Android notification payload |name|type|description|required|note| |-------|-------|--------|--------|---------| @@ -243,6 +242,54 @@ See more detail about [APNs Provider API](https://developer.apple.com/library/io 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 + } + } + ] +``` + + ## License Copyright 2016 Bo-Yi Wu [@appleboy](https://twitter.com/appleboy). diff --git a/gorush/notification.go b/gorush/notification.go index e0aaf8e..7e76848 100644 --- a/gorush/notification.go +++ b/gorush/notification.go @@ -10,10 +10,7 @@ import ( "time" ) -type ExtendJSON struct { - Key string `json:"key"` - Value string `json:"val"` -} +type D map[string]interface{} const ( // PriorityLow will tell APNs to send the push message at a time that takes @@ -47,14 +44,14 @@ type RequestPush struct { type PushNotification struct { // Common - Tokens []string `json:"tokens" binding:"required"` - Platform int `json:"platform" binding:"required"` - Message string `json:"message" binding:"required"` - Title string `json:"title,omitempty"` - Priority string `json:"priority,omitempty"` - ContentAvailable bool `json:"content_available,omitempty"` - Sound string `json:"sound,omitempty"` - Extend []ExtendJSON `json:"extend,omitempty"` + Tokens []string `json:"tokens" binding:"required"` + Platform int `json:"platform" binding:"required"` + Message string `json:"message" binding:"required"` + Title string `json:"title,omitempty"` + Priority string `json:"priority,omitempty"` + ContentAvailable bool `json:"content_available,omitempty"` + Sound string `json:"sound,omitempty"` + Data D `json:"data,omitempty"` // Android ApiKey string `json:"api_key,omitempty"` @@ -64,7 +61,6 @@ type PushNotification struct { TimeToLive uint `json:"time_to_live,omitempty"` RestrictedPackageName string `json:"restricted_package_name,omitempty"` DryRun bool `json:"dry_run,omitempty"` - Data gcm.Data `json:"data,omitempty"` Notification gcm.Notification `json:"notification,omitempty"` // iOS @@ -178,9 +174,9 @@ func GetIOSNotification(req PushNotification) *apns.Notification { payload.ContentAvailable() } - if len(req.Extend) > 0 { - for _, extend := range req.Extend { - payload.Custom(extend.Key, extend.Value) + if len(req.Data) > 0 { + for k, v := range req.Data { + payload.Custom(k, v) } } @@ -309,17 +305,12 @@ func GetAndroidNotification(req PushNotification) gcm.HttpMessage { notification.DryRun = true } - if len(req.Extend) > 0 { - notification.Data = make(map[string]interface{}) - - for _, extend := range req.Extend { - notification.Data[extend.Key] = extend.Value - } - } - - // overwrite Extend + // Add another field 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 diff --git a/gorush/notification_test.go b/gorush/notification_test.go index a39f112..0747dce 100644 --- a/gorush/notification_test.go +++ b/gorush/notification_test.go @@ -74,15 +74,9 @@ func TestIOSNotificationStructure(t *testing.T) { Badge: 1, Sound: test, ContentAvailable: true, - Extend: []ExtendJSON{ - { - Key: "key1", - Value: "1", - }, - { - Key: "key2", - Value: "2", - }, + Data: D{ + "key1": "test", + "key2": 2, }, Category: test, URLArgs: []string{"a", "b"}, @@ -103,8 +97,8 @@ func TestIOSNotificationStructure(t *testing.T) { sound, _ := jsonparser.GetString(data, "aps", "sound") contentAvailable, _ := jsonparser.GetInt(data, "aps", "content-available") category, _ := jsonparser.GetString(data, "aps", "category") - key1 := dat["key1"].(string) - key2 := dat["key2"].(string) + key1 := dat["key1"].(interface{}) + key2 := dat["key2"].(interface{}) aps := dat["aps"].(map[string]interface{}) urlArgs := aps["url-args"].([]interface{}) @@ -116,8 +110,8 @@ func TestIOSNotificationStructure(t *testing.T) { assert.Equal(t, 1, int(badge)) assert.Equal(t, test, sound) assert.Equal(t, 1, int(contentAvailable)) - assert.Equal(t, "1", key1) - assert.Equal(t, "2", key2) + assert.Equal(t, "test", key1) + assert.Equal(t, 2, int(key2.(float64))) assert.Equal(t, test, category) assert.Contains(t, urlArgs, "a") assert.Contains(t, urlArgs, "b") @@ -193,15 +187,9 @@ func TestAndroidNotificationStructure(t *testing.T) { DryRun: true, Title: test, Sound: test, - Extend: []ExtendJSON{ - { - Key: "key1", - Value: "1", - }, - { - Key: "key2", - Value: "2", - }, + Data: D{ + "a": "1", + "b": 2, }, Notification: gcm.Notification{ Color: test, @@ -224,33 +212,8 @@ func TestAndroidNotificationStructure(t *testing.T) { assert.Equal(t, test, notification.Notification.Color) assert.Equal(t, test, notification.Notification.Tag) assert.Equal(t, "Welcome", notification.Notification.Body) - assert.Equal(t, "1", notification.Data["key1"]) - - // add data file to overwrite `Extend` - req = PushNotification{ - Tokens: []string{"a", "b"}, - Message: "Welcome", - To: test, - Data: map[string]interface{}{ - "a": "1", - "b": "2", - }, - Extend: []ExtendJSON{ - { - Key: "key1", - Value: "1", - }, - { - Key: "key2", - Value: "2", - }, - }, - } - - notification = GetAndroidNotification(req) - assert.Equal(t, "1", notification.Data["a"]) - assert.Equal(t, "2", notification.Data["b"]) + assert.Equal(t, 2, notification.Data["b"]) } func TestPushToIOS(t *testing.T) { From dab1d731494251459247d6f96a81541317666a92 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 13 Apr 2016 11:43:07 +0800 Subject: [PATCH 3/3] [ci skip] update readme. Signed-off-by: Bo-Yi Wu --- README.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ gorush/server.go | 4 +-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 07f726f..bf00fc6 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,73 @@ Add other fields which user defined via `data` field. ] ``` +### 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 diff --git a/gorush/server.go b/gorush/server.go index e5ea23b..49167c2 100644 --- a/gorush/server.go +++ b/gorush/server.go @@ -34,7 +34,7 @@ func pushHandler(c *gin.Context) { } if len(form.Notifications) == 0 { - msg = "Notification field is empty." + msg = "Notifications field is empty." LogAccess.Debug(msg) AbortWithError(c, http.StatusBadRequest, msg) return @@ -51,7 +51,7 @@ func pushHandler(c *gin.Context) { go SendNotification(form) c.JSON(http.StatusOK, gin.H{ - "text": "Welcome to notification server.", + "success": "ok", }) }