Skip to content

Commit b6e0cb6

Browse files
authored
Added Additional WebpushNotification Fields (#165)
* Updating WebpushNotification type * Added additional fields and tests * Testing for action serialization * Fixing a formatting error * Supporting auto in the Direction field
1 parent a60f3cb commit b6e0cb6

File tree

4 files changed

+174
-11
lines changed

4 files changed

+174
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
- [fixed] Fixing a regression introduced in 3.2.0, where `VerifyIDToken()`
44
cannot be used in App Engine.
5+
- [added] `messaging.WebpushNotification` type now supports arbitrary key-value
6+
pairs in its payload.
57

68
# v3.2.0
79

messaging/messaging.go

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,82 @@ type WebpushConfig struct {
227227
Notification *WebpushNotification `json:"notification,omitempty"`
228228
}
229229

230+
// WebpushNotificationAction represents an action that can be performed upon receiving a WebPush notification.
231+
type WebpushNotificationAction struct {
232+
Action string `json:"action,omitempty"`
233+
Title string `json:"title,omitempty"`
234+
Icon string `json:"icon,omitempty"`
235+
}
236+
230237
// WebpushNotification is a notification to send via WebPush protocol.
238+
//
239+
// See https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification for additional
240+
// details.
231241
type WebpushNotification struct {
232-
Title string `json:"title,omitempty"` // if specified, overrides the Title field of the Notification type
233-
Body string `json:"body,omitempty"` // if specified, overrides the Body field of the Notification type
234-
Icon string `json:"icon,omitempty"`
242+
Actions []*WebpushNotificationAction
243+
Title string `json:"title,omitempty"` // if specified, overrides the Title field of the Notification type
244+
Body string `json:"body,omitempty"` // if specified, overrides the Body field of the Notification type
245+
Icon string `json:"icon,omitempty"`
246+
Badge string `json:"badge,omitempty"`
247+
Direction string `json:"dir,omitempty"` // one of 'ltr' or 'rtl'
248+
Data interface{} `json:"data,omitempty"`
249+
Image string `json:"image,omitempty"`
250+
Language string `json:"lang,omitempty"`
251+
Renotify bool `json:"renotify,omitempty"`
252+
RequireInteraction bool `json:"requireInteraction,omitempty"`
253+
Silent bool `json:"silent,omitempty"`
254+
Tag string `json:"tag,omitempty"`
255+
TimestampMillis *int64 `json:"timestamp,omitempty"`
256+
Vibrate []int `json:"vibrate,omitempty"`
257+
CustomData map[string]interface{}
258+
}
259+
260+
// standardFields creates a map containing all the fields except the custom data.
261+
func (n *WebpushNotification) standardFields() map[string]interface{} {
262+
m := make(map[string]interface{})
263+
addNonEmpty := func(key, value string) {
264+
if value != "" {
265+
m[key] = value
266+
}
267+
}
268+
addTrue := func(key string, value bool) {
269+
if value {
270+
m[key] = value
271+
}
272+
}
273+
if len(n.Actions) > 0 {
274+
m["actions"] = n.Actions
275+
}
276+
addNonEmpty("title", n.Title)
277+
addNonEmpty("body", n.Body)
278+
addNonEmpty("icon", n.Icon)
279+
addNonEmpty("badge", n.Badge)
280+
addNonEmpty("dir", n.Direction)
281+
addNonEmpty("image", n.Image)
282+
addNonEmpty("lang", n.Language)
283+
addTrue("renotify", n.Renotify)
284+
addTrue("requireInteraction", n.RequireInteraction)
285+
addTrue("silent", n.Silent)
286+
addNonEmpty("tag", n.Tag)
287+
if n.Data != nil {
288+
m["data"] = n.Data
289+
}
290+
if n.TimestampMillis != nil {
291+
m["timestamp"] = *n.TimestampMillis
292+
}
293+
if len(n.Vibrate) > 0 {
294+
m["vibrate"] = n.Vibrate
295+
}
296+
return m
297+
}
298+
299+
// MarshalJSON marshals a WebpushNotification into JSON (for internal use only).
300+
func (n *WebpushNotification) MarshalJSON() ([]byte, error) {
301+
m := n.standardFields()
302+
for k, v := range n.CustomData {
303+
m[k] = v
304+
}
305+
return json.Marshal(m)
235306
}
236307

237308
// APNSConfig contains messaging options specific to the Apple Push Notification Service (APNS).

messaging/messaging_test.go

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ var (
4444
ttl = time.Duration(10) * time.Second
4545
invalidTTL = time.Duration(-10) * time.Second
4646

47-
badge = 42
48-
badgeZero = 0
47+
badge = 42
48+
badgeZero = 0
49+
timestampMillis = int64(12345)
4950
)
5051

5152
var validMessages = []struct {
@@ -199,18 +200,60 @@ var validMessages = []struct {
199200
"k2": "v2",
200201
},
201202
Notification: &WebpushNotification{
202-
Title: "t",
203-
Body: "b",
204-
Icon: "i",
203+
Title: "title",
204+
Body: "body",
205+
Icon: "icon",
206+
Actions: []*WebpushNotificationAction{
207+
{
208+
Action: "a1",
209+
Title: "a1-title",
210+
},
211+
{
212+
Action: "a2",
213+
Title: "a2-title",
214+
Icon: "a2-icon",
215+
},
216+
},
217+
Badge: "badge",
218+
Data: "data",
219+
Image: "image",
220+
Language: "lang",
221+
Renotify: true,
222+
RequireInteraction: true,
223+
Silent: true,
224+
Tag: "tag",
225+
TimestampMillis: &timestampMillis,
226+
Vibrate: []int{100, 200, 100},
227+
CustomData: map[string]interface{}{"k1": "v1", "k2": "v2"},
205228
},
206229
},
207230
Topic: "test-topic",
208231
},
209232
want: map[string]interface{}{
210233
"webpush": map[string]interface{}{
211-
"headers": map[string]interface{}{"h1": "v1", "h2": "v2"},
212-
"data": map[string]interface{}{"k1": "v1", "k2": "v2"},
213-
"notification": map[string]interface{}{"title": "t", "body": "b", "icon": "i"},
234+
"headers": map[string]interface{}{"h1": "v1", "h2": "v2"},
235+
"data": map[string]interface{}{"k1": "v1", "k2": "v2"},
236+
"notification": map[string]interface{}{
237+
"title": "title",
238+
"body": "body",
239+
"icon": "icon",
240+
"actions": []interface{}{
241+
map[string]interface{}{"action": "a1", "title": "a1-title"},
242+
map[string]interface{}{"action": "a2", "title": "a2-title", "icon": "a2-icon"},
243+
},
244+
"badge": "badge",
245+
"data": "data",
246+
"image": "image",
247+
"lang": "lang",
248+
"renotify": true,
249+
"requireInteraction": true,
250+
"silent": true,
251+
"tag": "tag",
252+
"timestamp": float64(12345),
253+
"vibrate": []interface{}{float64(100), float64(200), float64(100)},
254+
"k1": "v1",
255+
"k2": "v2",
256+
},
214257
},
215258
"topic": "test-topic",
216259
},
@@ -525,6 +568,31 @@ var invalidMessages = []struct {
525568
},
526569
want: "locKey is required when specifying locArgs",
527570
},
571+
{
572+
name: "InvalidWebpushNotificationDirection",
573+
req: &Message{
574+
Webpush: &WebpushConfig{
575+
Notification: &WebpushNotification{
576+
Direction: "invalid",
577+
},
578+
},
579+
Topic: "topic",
580+
},
581+
want: "direction must be 'ltr', 'rtl' or 'auto'",
582+
},
583+
{
584+
name: "WebpushNotificationMultipleFieldSpecifications",
585+
req: &Message{
586+
Webpush: &WebpushConfig{
587+
Notification: &WebpushNotification{
588+
Direction: "ltr",
589+
CustomData: map[string]interface{}{"dir": "rtl"},
590+
},
591+
},
592+
Topic: "topic",
593+
},
594+
want: `multiple specifications for the key "dir"`,
595+
},
528596
}
529597

530598
var invalidTopicMgtArgs = []struct {

messaging/messaging_utils.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ func validateMessage(message *Message) error {
4848
return err
4949
}
5050

51+
// validate WebpushConfig
52+
if err := validateWebpushConfig(message.Webpush); err != nil {
53+
return err
54+
}
55+
5156
// validate APNSConfig
5257
return validateAPNSConfig(message.APNS)
5358
}
@@ -126,6 +131,23 @@ func validateApsAlert(alert *ApsAlert) error {
126131
return nil
127132
}
128133

134+
func validateWebpushConfig(webpush *WebpushConfig) error {
135+
if webpush == nil || webpush.Notification == nil {
136+
return nil
137+
}
138+
dir := webpush.Notification.Direction
139+
if dir != "" && dir != "ltr" && dir != "rtl" && dir != "auto" {
140+
return fmt.Errorf("direction must be 'ltr', 'rtl' or 'auto'")
141+
}
142+
m := webpush.Notification.standardFields()
143+
for k := range webpush.Notification.CustomData {
144+
if _, contains := m[k]; contains {
145+
return fmt.Errorf("multiple specifications for the key %q", k)
146+
}
147+
}
148+
return nil
149+
}
150+
129151
func countNonEmpty(strings ...string) int {
130152
count := 0
131153
for _, s := range strings {

0 commit comments

Comments
 (0)