diff --git a/messaging/messaging.go b/messaging/messaging.go index 9d892b16..257e95a9 100644 --- a/messaging/messaging.go +++ b/messaging/messaging.go @@ -187,8 +187,9 @@ func (m *Message) UnmarshalJSON(b []byte) error { // Notification is the basic notification template to use across all platforms. type Notification struct { - Title string `json:"title,omitempty"` - Body string `json:"body,omitempty"` + Title string `json:"title,omitempty"` + Body string `json:"body,omitempty"` + ImageURL string `json:"image,omitempty"` } // AndroidConfig contains messaging options specific to the Android platform. @@ -274,6 +275,7 @@ type AndroidNotification struct { TitleLocKey string `json:"title_loc_key,omitempty"` TitleLocArgs []string `json:"title_loc_args,omitempty"` ChannelID string `json:"channel_id,omitempty"` + ImageURL string `json:"image,omitempty"` } // AndroidFCMOptions contains additional options for features provided by the FCM Android SDK. @@ -619,6 +621,7 @@ type ApsAlert struct { // APNSFCMOptions contains additional options for features provided by the FCM Aps SDK. type APNSFCMOptions struct { AnalyticsLabel string `json:"analytics_label,omitempty"` + ImageURL string `json:"image,omitempty"` } // FCMOptions contains additional options to use across all platforms. diff --git a/messaging/messaging_test.go b/messaging/messaging_test.go index c4b405d5..c89f0414 100644 --- a/messaging/messaging_test.go +++ b/messaging/messaging_test.go @@ -102,8 +102,9 @@ var validMessages = []struct { name: "NotificationMessage", req: &Message{ Notification: &Notification{ - Title: "t", - Body: "b", + Title: "t", + Body: "b", + ImageURL: "http://image.jpg", }, Topic: "test-topic", }, @@ -111,6 +112,7 @@ var validMessages = []struct { "notification": map[string]interface{}{ "title": "t", "body": "b", + "image": "http://image.jpg", }, "topic": "test-topic", }, @@ -157,6 +159,7 @@ var validMessages = []struct { BodyLocKey: "blk", BodyLocArgs: []string{"b1", "b2"}, ChannelID: "channel", + ImageURL: "http://image.jpg", }, TTL: &ttlWithNanos, FCMOptions: &AndroidFCMOptions{ @@ -178,6 +181,7 @@ var validMessages = []struct { "body_loc_key": "blk", "body_loc_args": []interface{}{"b1", "b2"}, "channel_id": "channel", + "image": "http://image.jpg", }, "ttl": "1.500000000s", "fcm_options": map[string]interface{}{ @@ -322,6 +326,7 @@ var validMessages = []struct { }, FCMOptions: &APNSFCMOptions{ AnalyticsLabel: "Analytics", + ImageURL: "http://image.jpg", }, }, Topic: "test-topic", @@ -344,6 +349,7 @@ var validMessages = []struct { }, "fcm_options": map[string]interface{}{ "analytics_label": "Analytics", + "image": "http://image.jpg", }, }, "topic": "test-topic", @@ -525,6 +531,16 @@ var invalidMessages = []struct { }, want: "malformed topic name", }, + { + name: "InvalidNotificationImage", + req: &Message{ + Notification: &Notification{ + ImageURL: "image.jpg", + }, + Topic: "topic", + }, + want: `invalid image URL: "image.jpg"`, + }, { name: "InvalidAndroidTTL", req: &Message{ @@ -593,6 +609,18 @@ var invalidMessages = []struct { }, want: "bodyLocKey is required when specifying bodyLocArgs", }, + { + name: "InvalidAndroidImage", + req: &Message{ + Android: &AndroidConfig{ + Notification: &AndroidNotification{ + ImageURL: "image.jpg", + }, + }, + Topic: "topic", + }, + want: `invalid image URL: "image.jpg"`, + }, { name: "APNSMultipleAps", req: &Message{ @@ -688,6 +716,18 @@ var invalidMessages = []struct { }, want: "locKey is required when specifying locArgs", }, + { + name: "InvalidAPNSImage", + req: &Message{ + APNS: &APNSConfig{ + FCMOptions: &APNSFCMOptions{ + ImageURL: "image.jpg", + }, + }, + Topic: "topic", + }, + want: `invalid image URL: "image.jpg"`, + }, { name: "MultipleSoundSpecifications", req: &Message{ diff --git a/messaging/messaging_utils.go b/messaging/messaging_utils.go index 6fd55719..dcb3a261 100644 --- a/messaging/messaging_utils.go +++ b/messaging/messaging_utils.go @@ -44,6 +44,11 @@ func validateMessage(message *Message) error { } } + // validate Notification + if err := validateNotification(message.Notification); err != nil { + return err + } + // validate AndroidConfig if err := validateAndroidConfig(message.Android); err != nil { return err @@ -58,6 +63,20 @@ func validateMessage(message *Message) error { return validateAPNSConfig(message.APNS) } +func validateNotification(notification *Notification) error { + if notification == nil { + return nil + } + + image := notification.ImageURL + if image != "" { + if _, err := url.ParseRequestURI(image); err != nil { + return fmt.Errorf("invalid image URL: %q", image) + } + } + return nil +} + func validateAndroidConfig(config *AndroidConfig) error { if config == nil { return nil @@ -69,6 +88,7 @@ func validateAndroidConfig(config *AndroidConfig) error { if config.Priority != "" && config.Priority != "normal" && config.Priority != "high" { return fmt.Errorf("priority must be 'normal' or 'high'") } + // validate AndroidNotification return validateAndroidNotification(config.Notification) } @@ -86,11 +106,26 @@ func validateAndroidNotification(notification *AndroidNotification) error { if len(notification.BodyLocArgs) > 0 && notification.BodyLocKey == "" { return fmt.Errorf("bodyLocKey is required when specifying bodyLocArgs") } + image := notification.ImageURL + if image != "" { + if _, err := url.ParseRequestURI(image); err != nil { + return fmt.Errorf("invalid image URL: %q", image) + } + } return nil } func validateAPNSConfig(config *APNSConfig) error { if config != nil { + // validate FCMOptions + if config.FCMOptions != nil { + image := config.FCMOptions.ImageURL + if image != "" { + if _, err := url.ParseRequestURI(image); err != nil { + return fmt.Errorf("invalid image URL: %q", image) + } + } + } return validateAPNSPayload(config.Payload) } return nil