Skip to content
3 changes: 3 additions & 0 deletions messaging/messaging.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ func (m *Message) UnmarshalJSON(b []byte) error {
type Notification struct {
Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
Image string `json:"image,omitempty"`
}

// AndroidConfig contains messaging options specific to the Android platform.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"`
Image string `json:"image,omitempty"`
}

// FCMOptions contains additional options to use across all platforms.
Expand Down
40 changes: 40 additions & 0 deletions messaging/messaging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@ var validMessages = []struct {
Notification: &Notification{
Title: "t",
Body: "b",
Image: "http://image.jpg",
},
Topic: "test-topic",
},
want: map[string]interface{}{
"notification": map[string]interface{}{
"title": "t",
"body": "b",
"image": "http://image.jpg",
},
"topic": "test-topic",
},
Expand Down Expand Up @@ -157,6 +159,7 @@ var validMessages = []struct {
BodyLocKey: "blk",
BodyLocArgs: []string{"b1", "b2"},
ChannelID: "channel",
ImageURL: "http://image.jpg",
},
TTL: &ttlWithNanos,
FCMOptions: &AndroidFCMOptions{
Expand All @@ -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{}{
Expand Down Expand Up @@ -322,6 +326,7 @@ var validMessages = []struct {
},
FCMOptions: &APNSFCMOptions{
AnalyticsLabel: "Analytics",
Image: "http://image.jpg",
},
},
Topic: "test-topic",
Expand All @@ -344,6 +349,7 @@ var validMessages = []struct {
},
"fcm_options": map[string]interface{}{
"analytics_label": "Analytics",
"image": "http://image.jpg",
},
},
"topic": "test-topic",
Expand Down Expand Up @@ -525,6 +531,16 @@ var invalidMessages = []struct {
},
want: "malformed topic name",
},
{
name: "InvalidNotificationImage",
req: &Message{
Notification: &Notification{
Image: "image.jpg",
},
Topic: "topic",
},
want: `invalid image URL: "image.jpg"`,
},
{
name: "InvalidAndroidTTL",
req: &Message{
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -688,6 +716,18 @@ var invalidMessages = []struct {
},
want: "locKey is required when specifying locArgs",
},
{
name: "InvalidAPNSImage",
req: &Message{
APNS: &APNSConfig{
FCMOptions: &APNSFCMOptions{
Image: "image.jpg",
},
},
Topic: "topic",
},
want: `invalid image URL: "image.jpg"`,
},
{
name: "MultipleSoundSpecifications",
req: &Message{
Expand Down
35 changes: 35 additions & 0 deletions messaging/messaging_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -58,6 +63,20 @@ func validateMessage(message *Message) error {
return validateAPNSConfig(message.APNS)
}

func validateNotification(notification *Notification) error {
if notification == nil {
return nil
}

if notification.Image != "" {
image := notification.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
Expand All @@ -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)
}
Expand All @@ -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")
}
if notification.ImageURL != "" {
image := notification.ImageURL
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 {
if config.FCMOptions.Image != "" {
image := config.FCMOptions.Image
if _, err := url.ParseRequestURI(image); err != nil {
return fmt.Errorf("invalid image URL: %q", image)
}
}
}
return validateAPNSPayload(config.Payload)
}
return nil
Expand Down