From 9e46d4cca3a9acb9a0d9ff874b4159142cd24e8d Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 18 Dec 2018 16:41:31 -0800 Subject: [PATCH 1/6] Implemented Webpush API for FCM --- .../FirebaseMessagingTest.cs | 2 +- .../Messaging/MessageTest.cs | 195 ++++++++++++++- .../FirebaseAdmin/Messaging/Message.cs | 15 +- .../FirebaseAdmin/Messaging/WebpushConfig.cs | 74 ++++++ .../Messaging/WebpushNotification.cs | 234 ++++++++++++++++++ 5 files changed, 510 insertions(+), 10 deletions(-) create mode 100644 FirebaseAdmin/FirebaseAdmin/Messaging/WebpushConfig.cs create mode 100644 FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs diff --git a/FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseMessagingTest.cs b/FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseMessagingTest.cs index 9573cfba..102ab711 100644 --- a/FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseMessagingTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseMessagingTest.cs @@ -38,7 +38,7 @@ public async Task Send() Title = "Title", Body = "Body", }, - AndroidConfig = new AndroidConfig() + Android = new AndroidConfig() { Priority = Priority.Normal, TimeToLive = TimeSpan.FromHours(1), diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index c7e2dbd4..cee6147d 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -147,7 +147,7 @@ public void AndroidConfig() var message = new Message() { Topic = "test-topic", - AndroidConfig = new AndroidConfig() + Android = new AndroidConfig() { CollapseKey = "collapse-key", Priority = Priority.High, @@ -215,7 +215,7 @@ public void AndroidConfigFullSecondsTTL() var message = new Message() { Topic = "test-topic", - AndroidConfig = new AndroidConfig() + Android = new AndroidConfig() { TimeToLive = TimeSpan.FromHours(1), }, @@ -239,7 +239,7 @@ public void AndroidConfigInvalidTTL() var message = new Message() { Topic = "test-topic", - AndroidConfig = new AndroidConfig() + Android = new AndroidConfig() { TimeToLive = TimeSpan.FromHours(-1), }, @@ -263,7 +263,7 @@ public void AndroidNotificationInvalidColor() var message = new Message() { Topic = "test-topic", - AndroidConfig = new AndroidConfig() + Android = new AndroidConfig() { Notification = new AndroidNotification() { @@ -280,7 +280,7 @@ public void AndroidNotificationInvalidTitleLocArgs() var message = new Message() { Topic = "test-topic", - AndroidConfig = new AndroidConfig() + Android = new AndroidConfig() { Notification = new AndroidNotification() { @@ -297,7 +297,7 @@ public void AndroidNotificationInvalidBodyLocArgs() var message = new Message() { Topic = "test-topic", - AndroidConfig = new AndroidConfig() + Android = new AndroidConfig() { Notification = new AndroidNotification() { @@ -308,6 +308,189 @@ public void AndroidNotificationInvalidBodyLocArgs() Assert.Throws(() => message.Validate()); } + [Fact] + public void WebpushConfig() + { + var message = new Message() + { + Topic = "test-topic", + Webpush = new WebpushConfig() + { + Headers = new Dictionary() + { + {"header1", "header-value1"}, + {"header2", "header-value2"}, + }, + Data = new Dictionary() + { + {"key1", "value1"}, + {"key2", "value2"}, + }, + Notification = new WebpushNotification() + { + Title = "title", + Body = "body", + Icon = "icon", + Badge = "badge", + Data = new Dictionary() + { + {"some", "data"}, + }, + Direction = Direction.LeftToRight, + Image = "image", + Language = "language", + Tag = "tag", + Silent = true, + RequireInteraction = true, + Renotify = true, + TimestampMillis = 100, + Vibrate = new int[]{10, 5, 10}, + Actions = new List() + { + new Action() + { + ActionName = "Accept", + Title = "Ok", + Icon = "ok-button", + }, + new Action() + { + ActionName = "Reject", + Title = "Cancel", + Icon = "cancel-button", + }, + }, + CustomData = new Dictionary() + { + {"custom-key1", "custom-data"}, + {"custom-key2", true}, + }, + }, + }, + }; + var expected = new JObject() + { + {"topic", "test-topic"}, + { + "webpush", new JObject() + { + { + "headers", new JObject() + { + {"header1", "header-value1"}, + {"header2", "header-value2"}, + } + }, + { + "data", new JObject() + { + {"key1", "value1"}, + {"key2", "value2"}, + } + }, + { + "notification", new JObject() + { + {"title", "title"}, + {"body", "body"}, + {"icon", "icon"}, + {"badge", "badge"}, + { + "data", new JObject() + { + {"some", "data"}, + } + }, + {"dir", "ltr"}, + {"image", "image"}, + {"lang", "language"}, + {"renotify", true}, + {"requireInteraction", true}, + {"silent", true}, + {"tag", "tag"}, + {"timestamp", 100}, + {"vibrate", new JArray(){10, 5, 10}}, + { + "actions", new JArray() + { + new JObject() + { + {"action", "Accept"}, + {"title", "Ok"}, + {"icon", "ok-button"}, + }, + new JObject() + { + {"action", "Reject"}, + {"title", "Cancel"}, + {"icon", "cancel-button"}, + }, + } + }, + {"custom-key1", "custom-data"}, + {"custom-key2", true}, + } + }, + } + }, + }; + AssertJsonEquals(expected, message); + } + + [Fact] + public void WebpushConfigMinimalNotification() + { + var message = new Message() + { + Topic = "test-topic", + Webpush = new WebpushConfig() + { + Notification = new WebpushNotification() + { + Title = "title", + Body = "body", + Icon = "icon", + }, + }, + }; + var expected = new JObject() + { + {"topic", "test-topic"}, + { + "webpush", new JObject() + { + { + "notification", new JObject() + { + {"title", "title"}, + {"body", "body"}, + {"icon", "icon"}, + } + }, + } + }, + }; + AssertJsonEquals(expected, message); + } + + [Fact] + public void WebpushConfigDuplicateKeys() + { + var message = new Message() + { + Topic = "test-topic", + Webpush = new WebpushConfig() + { + Notification = new WebpushNotification() + { + Title = "title", + CustomData = new Dictionary(){{"title", "other"}}, + }, + }, + }; + Assert.Throws(() => message.Validate()); + } + private void AssertJsonEquals(JObject expected, Message actual) { var json = NewtonsoftJsonSerializer.Instance.Serialize(actual.Validate()); diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs index b41ad493..16c29f09 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs @@ -61,7 +61,12 @@ public sealed class Message /// /// The Android-specific information to be included in the message. /// - public AndroidConfig AndroidConfig { get; set; } + public AndroidConfig Android { get; set; } + + /// + /// The Webpush-specific information to be included in the message. + /// + public WebpushConfig Webpush { get; set; } /// /// Validates the content and structure of this message instance, and converts it into the @@ -87,7 +92,8 @@ internal ValidatedMessage Validate() Condition = this.Condition, Data = this.Data, Notification = this.Notification, - AndroidConfig = this.AndroidConfig?.Validate(), + Android = this.Android?.Validate(), + Webpush = this.Webpush?.Validate(), }; } @@ -140,6 +146,9 @@ internal sealed class ValidatedMessage internal Notification Notification { get; set; } [JsonProperty("android")] - internal ValidatedAndroidConfig AndroidConfig { get; set; } + internal ValidatedAndroidConfig Android { get; set; } + + [JsonProperty("webpush")] + internal ValidatedWebpushConfig Webpush { get; set; } } } diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushConfig.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushConfig.cs new file mode 100644 index 00000000..86ecde20 --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushConfig.cs @@ -0,0 +1,74 @@ +// Copyright 2018, Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FirebaseAdmin.Messaging +{ + /// + /// Represents the Webpush protocol options that can be included in a . + /// + public sealed class WebpushConfig + { + /// + /// Webpush HTTP headers. Refer + /// Webpush specification for supported headers. + /// + public IReadOnlyDictionary Headers { get; set; } + + /// + /// Webpush data fields. When set, overrides any data fields set via + /// . + /// + public IReadOnlyDictionary Data { get; set; } + + /// + /// The Webpush notification to be included in the message. + /// + public WebpushNotification Notification { get; set; } + + /// + /// Validates the content and structure of this Webpush configuration, and converts it into + /// the type. This return type can be safely + /// serialized into a JSON string that is acceptable to the FCM backend service. + /// + internal ValidatedWebpushConfig Validate() + { + return new ValidatedWebpushConfig() + { + Headers = this.Headers, + Data = this.Data, + Notification = this.Notification?.Validate(), + }; + } + } + + /// + /// Represents a validated Webpush configuration that can be serialized into the JSON format + /// accepted by the FCM backend service. + /// + internal sealed class ValidatedWebpushConfig + { + [JsonProperty("headers")] + public IReadOnlyDictionary Headers { get; set; } + + [JsonProperty("data")] + public IReadOnlyDictionary Data { get; set; } + + [JsonProperty("notification")] + internal IReadOnlyDictionary Notification { get; set; } + } +} diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs new file mode 100644 index 00000000..a3333792 --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs @@ -0,0 +1,234 @@ +// Copyright 2018, Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace FirebaseAdmin.Messaging +{ + /// + /// Represents the Webpush-specific notification options that can be included in a + /// . Supports most standard options defined in the + /// + /// Web Notification specification + /// + public sealed class WebpushNotification + { + /// + /// Title text of the notification. + /// + [JsonProperty("title")] + public string Title { get; set; } + + /// + /// Body text of the notification. + /// + public string Body { get; set; } + + /// + /// The URL to the icon of the notification. + /// + public string Icon { get; set; } + + /// + /// The URL of the image used to represent the notification when there is not enough space + /// to display the notification itself. + /// + public string Badge { get; set; } + + /// + /// Any arbitrary data that should be associated with the notification. + /// + public object Data { get; set; } + + /// + /// The direction in which to display the notification. + /// + public Direction? Direction { get; set; } + + /// + /// Converts the property into a string value that can be included + /// in the json output. + /// + internal string DirectionString + { + get + { + switch (Direction) + { + case Messaging.Direction.Auto: + return "auto"; + case Messaging.Direction.LeftToRight: + return "ltr"; + case Messaging.Direction.RightToLeft: + return "rtl"; + default: + return null; + } + } + } + + /// + /// The URL of an image to be displayed in the notification. + /// + public string Image { get; set; } + + /// + /// The language of the notification. + /// + public string Language { get; set; } + + /// + /// Whether the user should be notified after a new notification replaces an old one. + /// + public bool? Renotify { get; set; } + + /// + /// Whether a notification should remain active until the user clicks or dismisses it, + /// rather than closing automatically. + /// + public bool? RequireInteraction { get; set; } + + /// + /// Whether the notification should be silent. + /// + public bool? Silent { get; set; } + + /// + /// An identifying tag for the notification. + /// + public string Tag { get; set; } + + /// + /// A timestamp value in milliseconds on the notification. + /// + public long? TimestampMillis { get; set; } + + /// + /// A vibration pattern for the receiving device's vibration hardware to emit when the + /// notification fires. + /// + public int[] Vibrate { get; set; } + + /// + /// A collection of arbitrary key-value data to be included in the notification. + /// + public IReadOnlyDictionary CustomData; + + /// + /// A collection of notification actions to be associated with the notification. + /// + [JsonProperty("actions")] + public IEnumerable Actions; + + private delegate void AddString(string key, string value); + private delegate void AddObject(string key, object value); + + /// + /// Validates the content and structure of this Webpush notification, and converts it into + /// a dictionary. + /// + internal IReadOnlyDictionary Validate() + { + var result = new Dictionary(); + AddString addString = delegate(string key, string value) + { + if (!string.IsNullOrEmpty(value)) + { + result[key] = value; + } + }; + AddObject addObject = delegate(string key, object value) + { + if (value != null) + { + result[key] = value; + } + }; + addString("title", Title); + addString("body", Body); + addString("icon", Icon); + addString("image", Image); + addString("lang", Language); + addString("tag", Tag); + addString("dir", DirectionString); + addString("badge", Badge); + addObject("renotify", Renotify); + addObject("requireInteraction", RequireInteraction); + addObject("silent", Silent); + addObject("actions", Actions); + addObject("vibrate", Vibrate); + addObject("timestamp", TimestampMillis); + addObject("data", Data); + if (CustomData != null) + { + foreach (var entry in CustomData) + { + if (result.ContainsKey(entry.Key)) + { + throw new ArgumentException($"Multiple specification for key {entry.Key}"); + } + addObject(entry.Key, entry.Value); + } + } + return result; + } + } + + /// + /// Represents an action available to users when the notification is presented. + /// + public sealed class Action + { + /// + /// Action name. + /// + [JsonProperty("action")] + public string ActionName { get; set; } + + /// + /// Title text. + /// + [JsonProperty("title")] + public string Title { get; set; } + + /// + /// Icon URL. + /// + [JsonProperty("icon")] + public string Icon { get; set; } + } + + /// + /// Different directions a notification can be displayed in. + /// + public enum Direction + { + /// + /// Direction automatically determined. + /// + Auto, + + /// + /// Left to right. + /// + LeftToRight, + + /// + /// Right to left. + /// + RightToLeft, + } +} From 08153a7e44fca5b971d6a806f50d2a984ca2fd03 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Wed, 19 Dec 2018 17:44:08 -0800 Subject: [PATCH 2/6] Fixed CustomData deserialization --- .../Messaging/MessageTest.cs | 43 ++++++++++++++- .../Messaging/WebpushNotification.cs | 53 +++++++++++-------- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index 228a88a1..61cd0f53 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -16,8 +16,8 @@ using System.Collections.Generic; using Newtonsoft.Json.Linq; using Xunit; -using FirebaseAdmin.Messaging; using Google.Apis.Json; +using Newtonsoft.Json; namespace FirebaseAdmin.Messaging.Tests { @@ -541,6 +541,24 @@ public void WebpushConfigDuplicateKeys() Assert.Throws(() => message.CopyAndValidate()); } + [Fact] + public void WebpushNotificationDeserialization() + { + var original = new WebpushNotification() + { + Direction = Direction.LeftToRight, + CustomData = new Dictionary() + { + {"custom-key1", "custom-data"}, + {"custom-key2", true}, + }, + }; + var json = NewtonsoftJsonSerializer.Instance.Serialize(original); + var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); + Assert.Equal(original.Direction, copy.Direction); + Assert.Equal(original.CustomData, copy.CustomData); + } + private void AssertJsonEquals(JObject expected, Message actual) { var json = NewtonsoftJsonSerializer.Instance.Serialize(actual.CopyAndValidate()); @@ -550,4 +568,27 @@ private void AssertJsonEquals(JObject expected, Message actual) $"Expected: {expected.ToString()}\nActual: {parsed.ToString()}"); } } + + public class Foo + { + [JsonExtensionData(ReadData = true, WriteData = true)] + private IDictionary _data = new Dictionary(); + + [JsonIgnore] + public IReadOnlyDictionary CustomData + { + get + { + return _data.Copy(); + } + set + { + _data = new Dictionary(); + foreach (var entry in value) + { + _data[entry.Key] = entry.Value; + } + } + } + } } diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs index 7668d9e5..9e1e2df0 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs @@ -85,6 +85,21 @@ internal string DirectionString return null; } } + set + { + switch (value) + { + case "auto": + Direction = Messaging.Direction.Auto; + return; + case "ltr": + Direction = Messaging.Direction.LeftToRight; + return; + case "rtl": + Direction = Messaging.Direction.RightToLeft; + return; + } + } } /// @@ -141,30 +156,26 @@ internal string DirectionString /// A collection of arbitrary key-value data to be included in the notification. /// [JsonIgnore] - public IReadOnlyDictionary CustomData; - - /// - /// A copy of exposed an IDictionary so it - /// works with JsonExtensionData annotation. - /// - [JsonExtensionData] - internal IDictionary ExtensionCustomData + public IReadOnlyDictionary CustomData { - get + get { return ExtensionCustomData; } + set { - if (CustomData?.Count > 0) + ExtensionCustomData = new Dictionary(); + foreach (var entry in value) { - var result = new Dictionary(); - foreach (var entry in CustomData) - { - result[entry.Key] = entry.Value; - } - return result; + ExtensionCustomData[entry.Key] = entry.Value; } - return null; } } + /// + /// A copy of exposed an IDictionary so it + /// works with JsonExtensionData annotation. + /// + [JsonExtensionData] + private Dictionary ExtensionCustomData; + /// /// A collection of notification actions to be associated with the notification. /// @@ -196,14 +207,14 @@ internal WebpushNotification CopyAndValidate() Data = this.Data, }; - var customDataCopy = this.CustomData?.Copy(); - if (customDataCopy?.Count > 0) + var customData = this.CustomData; + if (customData?.Count > 0) { var serializer = NewtonsoftJsonSerializer.Instance; // Serialize the notification without CustomData for validation. var json = serializer.Serialize(copy); var dict = serializer.Deserialize>(json); - foreach (var entry in customDataCopy) + foreach (var entry in customData) { if (dict.ContainsKey(entry.Key)) { @@ -211,7 +222,7 @@ internal WebpushNotification CopyAndValidate() $"Multiple specifications for WebpushNotification key: {entry.Key}"); } } - copy.CustomData = customDataCopy; + copy.CustomData = customData; } return copy; } From 0166f5f596260f4159a70966f617dbdfacfcec60 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Wed, 19 Dec 2018 22:08:35 -0800 Subject: [PATCH 3/6] Exposing CustomData as IDictionary --- .../Messaging/MessageTest.cs | 61 ++++++++----------- FirebaseAdmin/FirebaseAdmin/Extensions.cs | 8 --- .../Messaging/AndroidNotification.cs | 4 +- .../Messaging/WebpushNotification.cs | 39 ++++++------ 4 files changed, 44 insertions(+), 68 deletions(-) diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index 61cd0f53..c1814575 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -75,18 +75,6 @@ public void MultipleTargets() Assert.Throws(() => message.CopyAndValidate()); } - [Fact] - public void MessageDeserialization() - { - var original = new Message() - { - Topic = "test-topic", - }; - var json = NewtonsoftJsonSerializer.Instance.Serialize(original); - var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); - Assert.Equal(original.Topic, copy.Topic); - } - [Fact] public void DataMessage() { @@ -127,6 +115,23 @@ public void PrefixedTopicName() AssertJsonEquals(new JObject(){{"topic", "test-topic"}}, message); } + [Fact] + public void MessageDeserialization() + { + var original = new Message() + { + Topic = "test-topic", + Data = new Dictionary() + { + { "key", "value" }, + }, + }; + var json = NewtonsoftJsonSerializer.Instance.Serialize(original); + var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); + Assert.Equal(original.Topic, copy.Topic); + Assert.Equal(original.Data, copy.Data); + } + [Fact] public void Notification() { @@ -298,13 +303,20 @@ public void AndroidConfigDeserialization() { var original = new AndroidConfig() { + CollapseKey = "collapse-key", + RestrictedPackageName = "test-pkg-name", TimeToLive = TimeSpan.FromSeconds(10.5), Priority = Priority.High, + Data = new Dictionary() + { + { "key", "value" }, + }, }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); Assert.Equal(original.Priority, copy.Priority); Assert.Equal(original.TimeToLive, copy.TimeToLive); + Assert.Equal(original.Data, copy.Data); } [Fact] @@ -546,7 +558,6 @@ public void WebpushNotificationDeserialization() { var original = new WebpushNotification() { - Direction = Direction.LeftToRight, CustomData = new Dictionary() { {"custom-key1", "custom-data"}, @@ -555,7 +566,6 @@ public void WebpushNotificationDeserialization() }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); - Assert.Equal(original.Direction, copy.Direction); Assert.Equal(original.CustomData, copy.CustomData); } @@ -568,27 +578,4 @@ private void AssertJsonEquals(JObject expected, Message actual) $"Expected: {expected.ToString()}\nActual: {parsed.ToString()}"); } } - - public class Foo - { - [JsonExtensionData(ReadData = true, WriteData = true)] - private IDictionary _data = new Dictionary(); - - [JsonIgnore] - public IReadOnlyDictionary CustomData - { - get - { - return _data.Copy(); - } - set - { - _data = new Dictionary(); - foreach (var entry in value) - { - _data[entry.Key] = entry.Value; - } - } - } - } } diff --git a/FirebaseAdmin/FirebaseAdmin/Extensions.cs b/FirebaseAdmin/FirebaseAdmin/Extensions.cs index fb1ad2e7..0c71f7f1 100644 --- a/FirebaseAdmin/FirebaseAdmin/Extensions.cs +++ b/FirebaseAdmin/FirebaseAdmin/Extensions.cs @@ -100,13 +100,5 @@ public static IReadOnlyDictionary Copy( } return copy; } - - /// - /// Creates a shallow copy of a collection of items. - /// - public static IEnumerable Copy(this IEnumerable source) - { - return new List(source); - } } } diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs index 5d520ba2..417efe70 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidNotification.cs @@ -128,9 +128,9 @@ internal AndroidNotification CopyAndValidate() Tag = this.Tag, ClickAction = this.ClickAction, TitleLocKey = this.TitleLocKey, - TitleLocArgs = this.TitleLocArgs?.Copy(), + TitleLocArgs = this.TitleLocArgs?.ToList(), BodyLocKey = this.BodyLocKey, - BodyLocArgs = this.BodyLocArgs?.Copy(), + BodyLocArgs = this.BodyLocArgs?.ToList(), ChannelId = this.ChannelId, }; if (copy.Color != null && !Regex.Match(copy.Color, "^#[0-9a-fA-F]{6}$").Success) diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs index 9e1e2df0..f75276ec 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Google.Apis.Json; using Newtonsoft.Json; @@ -153,28 +154,12 @@ internal string DirectionString public int[] Vibrate { get; set; } /// - /// A collection of arbitrary key-value data to be included in the notification. - /// - [JsonIgnore] - public IReadOnlyDictionary CustomData - { - get { return ExtensionCustomData; } - set - { - ExtensionCustomData = new Dictionary(); - foreach (var entry in value) - { - ExtensionCustomData[entry.Key] = entry.Value; - } - } - } - - /// - /// A copy of exposed an IDictionary so it - /// works with JsonExtensionData annotation. + /// A collection of arbitrary key-value data to be included in the notification. This is + /// exposed as an to support correct + /// deserialization of custom properties. /// [JsonExtensionData] - private Dictionary ExtensionCustomData; + public IDictionary CustomData { get; set; } /// /// A collection of notification actions to be associated with the notification. @@ -201,7 +186,7 @@ internal WebpushNotification CopyAndValidate() Renotify = this.Renotify, RequireInteraction = this.RequireInteraction, Silent = this.Silent, - Actions = this.Actions?.Copy(), + Actions = this.Actions?.Select((item, _) => new Action(item)).ToList(), Vibrate = this.Vibrate, TimestampMillis = this.TimestampMillis, Data = this.Data, @@ -250,6 +235,18 @@ public sealed class Action /// [JsonProperty("icon")] public string Icon { get; set; } + + /// + /// Creates a new Action instance. + /// + public Action() { } + + internal Action(Action action) + { + ActionName = action.ActionName; + Title = action.Title; + Icon = action.Icon; + } } /// From 7acaaf696a2000bb98f22095254cf3c6dd51699e Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Thu, 20 Dec 2018 00:02:24 -0800 Subject: [PATCH 4/6] Minor cleanup of internal properties --- .../FirebaseAdmin.Tests/Messaging/MessageTest.cs | 12 ++---------- .../FirebaseAdmin/Messaging/AndroidConfig.cs | 4 ++-- FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs | 2 +- .../FirebaseAdmin/Messaging/WebpushNotification.cs | 2 +- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index c1814575..34512d2f 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -285,16 +285,6 @@ public void AndroidConfigInvalidTTL() TimeToLive = TimeSpan.FromHours(-1), }, }; - var expected = new JObject() - { - {"topic", "test-topic"}, - { - "android", new JObject() - { - { "ttl", "3600s" }, - } - }, - }; Assert.Throws(() => message.CopyAndValidate()); } @@ -314,6 +304,8 @@ public void AndroidConfigDeserialization() }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); + Assert.Equal(original.CollapseKey, copy.CollapseKey); + Assert.Equal(original.RestrictedPackageName, copy.RestrictedPackageName); Assert.Equal(original.Priority, copy.Priority); Assert.Equal(original.TimeToLive, copy.TimeToLive); Assert.Equal(original.Data, copy.Data); diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidConfig.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidConfig.cs index 32f61aaa..e3438197 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidConfig.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidConfig.cs @@ -42,7 +42,7 @@ public sealed class AndroidConfig /// service. /// [JsonProperty("priority")] - internal string PriorityString + private string PriorityString { get { @@ -82,7 +82,7 @@ internal string PriorityString /// by the number of seconds, with nanoseconds expressed as fractional seconds. /// [JsonProperty("ttl")] - internal string TtlString + private string TtlString { get { diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs index 0ec4098f..6635761e 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs @@ -47,7 +47,7 @@ public sealed class Message /// prefix if present. This is what's ultimately sent to the FCM service. /// [JsonProperty("topic")] - internal string UnprefixedTopic + private string UnprefixedTopic { get { diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs index f75276ec..618f1949 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs @@ -70,7 +70,7 @@ public sealed class WebpushNotification /// in the json output. /// [JsonProperty("dir")] - internal string DirectionString + private string DirectionString { get { From 7cc6f1ff5f3f11470b1c05b28245cf8d5e3c69d8 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Thu, 20 Dec 2018 13:37:29 -0800 Subject: [PATCH 5/6] More test cases --- .../Messaging/MessageTest.cs | 440 ++++++++++++++---- .../Messaging/WebpushNotification.cs | 13 +- 2 files changed, 346 insertions(+), 107 deletions(-) diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index 34512d2f..0fd03535 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json.Linq; using Xunit; using Google.Apis.Json; @@ -23,12 +24,6 @@ namespace FirebaseAdmin.Messaging.Tests { public class MessageTest { - [Fact] - public void MessageWithoutTarget() - { - Assert.Throws(() => new Message().CopyAndValidate()); - } - [Fact] public void EmptyMessage() { @@ -43,36 +38,10 @@ public void EmptyMessage() } [Fact] - public void MultipleTargets() + public void PrefixedTopicName() { - var message = new Message() - { - Token = "test-token", - Topic = "test-topic", - }; - Assert.Throws(() => message.CopyAndValidate()); - - message = new Message() - { - Token = "test-token", - Condition = "test-condition", - }; - Assert.Throws(() => message.CopyAndValidate()); - - message = new Message() - { - Condition = "test-condition", - Topic = "test-topic", - }; - Assert.Throws(() => message.CopyAndValidate()); - - message = new Message() - { - Token = "test-token", - Topic = "test-topic", - Condition = "test-condition", - }; - Assert.Throws(() => message.CopyAndValidate()); + var message = new Message(){Topic = "/topics/test-topic"}; + AssertJsonEquals(new JObject(){{"topic", "test-topic"}}, message); } [Fact] @@ -95,24 +64,29 @@ public void DataMessage() } [Fact] - public void InvalidTopicNames() + public void Notification() { - var topics = new List() + var message = new Message() { - "/topics/", "/foo/bar", "foo bar", + Topic = "test-topic", + Notification = new Notification() + { + Title = "title", + Body = "body", + }, }; - foreach (var topic in topics) + var expected = new JObject() { - var message = new Message(){Topic = topic}; - Assert.Throws(() => message.CopyAndValidate()); - } - } - - [Fact] - public void PrefixedTopicName() - { - var message = new Message(){Topic = "/topics/test-topic"}; - AssertJsonEquals(new JObject(){{"topic", "test-topic"}}, message); + {"topic", "test-topic"}, + { + "notification", new JObject() + { + {"title", "title"}, + {"body", "body"}, + } + }, + }; + AssertJsonEquals(expected, message); } [Fact] @@ -121,41 +95,102 @@ public void MessageDeserialization() var original = new Message() { Topic = "test-topic", - Data = new Dictionary() + Data = new Dictionary(){{ "key", "value" }}, + Notification = new Notification() { - { "key", "value" }, + Title = "title", + Body = "body", + }, + Android = new AndroidConfig() + { + RestrictedPackageName = "test-pkg-name", + }, + Webpush = new WebpushConfig() + { + Data = new Dictionary(){{ "key", "value" }}, }, }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); Assert.Equal(original.Topic, copy.Topic); Assert.Equal(original.Data, copy.Data); + Assert.Equal(original.Notification.Title, copy.Notification.Title); + Assert.Equal(original.Notification.Body, copy.Notification.Body); + Assert.Equal( + original.Android.RestrictedPackageName, copy.Android.RestrictedPackageName); + Assert.Equal(original.Webpush.Data, copy.Webpush.Data); } [Fact] - public void Notification() + public void MessageCopy() + { + var original = new Message() + { + Topic = "test-topic", + Data = new Dictionary(), + Notification = new Notification(), + Android = new AndroidConfig(), + Webpush = new WebpushConfig(), + }; + var copy = original.CopyAndValidate(); + Assert.NotSame(original, copy); + Assert.NotSame(original.Data, copy.Data); + Assert.NotSame(original.Notification, copy.Notification); + Assert.NotSame(original.Android, copy.Android); + Assert.NotSame(original.Webpush, copy.Webpush); + } + + [Fact] + public void MessageWithoutTarget() + { + Assert.Throws(() => new Message().CopyAndValidate()); + } + + [Fact] + public void MultipleTargets() { var message = new Message() { + Token = "test-token", Topic = "test-topic", - Notification = new Notification() - { - Title = "title", - Body = "body", - }, }; - var expected = new JObject() + Assert.Throws(() => message.CopyAndValidate()); + + message = new Message() { - {"topic", "test-topic"}, - { - "notification", new JObject() - { - {"title", "title"}, - {"body", "body"}, - } - }, + Token = "test-token", + Condition = "test-condition", }; - AssertJsonEquals(expected, message); + Assert.Throws(() => message.CopyAndValidate()); + + message = new Message() + { + Condition = "test-condition", + Topic = "test-topic", + }; + Assert.Throws(() => message.CopyAndValidate()); + + message = new Message() + { + Token = "test-token", + Topic = "test-topic", + Condition = "test-condition", + }; + Assert.Throws(() => message.CopyAndValidate()); + } + + [Fact] + public void InvalidTopicNames() + { + var topics = new List() + { + "/topics/", "/foo/bar", "foo bar", + }; + foreach (var topic in topics) + { + var message = new Message(){Topic = topic}; + Assert.Throws(() => message.CopyAndValidate()); + } } [Fact] @@ -232,20 +267,12 @@ public void AndroidConfigMinimal() var message = new Message() { Topic = "test-topic", - Android = new AndroidConfig() - { - RestrictedPackageName = "test-pkg-name", - }, + Android = new AndroidConfig(), }; var expected = new JObject() { {"topic", "test-topic"}, - { - "android", new JObject() - { - { "restricted_package_name", "test-pkg-name" }, - } - }, + {"android", new JObject()}, }; AssertJsonEquals(expected, message); } @@ -274,20 +301,6 @@ public void AndroidConfigFullSecondsTTL() AssertJsonEquals(expected, message); } - [Fact] - public void AndroidConfigInvalidTTL() - { - var message = new Message() - { - Topic = "test-topic", - Android = new AndroidConfig() - { - TimeToLive = TimeSpan.FromHours(-1), - }, - }; - Assert.Throws(() => message.CopyAndValidate()); - } - [Fact] public void AndroidConfigDeserialization() { @@ -301,6 +314,10 @@ public void AndroidConfigDeserialization() { { "key", "value" }, }, + Notification = new AndroidNotification() + { + Title = "title", + }, }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); @@ -309,6 +326,86 @@ public void AndroidConfigDeserialization() Assert.Equal(original.Priority, copy.Priority); Assert.Equal(original.TimeToLive, copy.TimeToLive); Assert.Equal(original.Data, copy.Data); + Assert.Equal(original.Notification.Title, copy.Notification.Title); + } + + [Fact] + public void AndroidConfigCopy() + { + var original = new AndroidConfig() + { + Data = new Dictionary(), + Notification = new AndroidNotification(), + }; + var copy = original.CopyAndValidate(); + Assert.NotSame(original, copy); + Assert.NotSame(original.Data, copy.Data); + Assert.NotSame(original.Notification, copy.Notification); + } + + [Fact] + public void AndroidNotificationDeserialization() + { + var original = new AndroidNotification() + { + Title = "title", + Body = "body", + Icon = "icon", + Color = "#112233", + Sound = "sound", + Tag = "tag", + ClickAction = "click-action", + TitleLocKey = "title-loc-key", + TitleLocArgs = new List(){ "arg1", "arg2" }, + BodyLocKey = "body-loc-key", + BodyLocArgs = new List(){ "arg3", "arg4" }, + ChannelId = "channel-id", + }; + var json = NewtonsoftJsonSerializer.Instance.Serialize(original); + var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); + Assert.Equal(original.Title, copy.Title); + Assert.Equal(original.Body, copy.Body); + Assert.Equal(original.Icon, copy.Icon); + Assert.Equal(original.Color, copy.Color); + Assert.Equal(original.Sound, copy.Sound); + Assert.Equal(original.Tag, copy.Tag); + Assert.Equal(original.ClickAction, copy.ClickAction); + Assert.Equal(original.TitleLocKey, copy.TitleLocKey); + Assert.Equal(original.TitleLocArgs, copy.TitleLocArgs); + Assert.Equal(original.BodyLocKey, copy.BodyLocKey); + Assert.Equal(original.BodyLocArgs, copy.BodyLocArgs); + Assert.Equal(original.ChannelId, copy.ChannelId); + } + + [Fact] + public void AndroidNotificationCopy() + { + var original = new AndroidNotification() + { + TitleLocKey = "title-loc-key", + TitleLocArgs = new List(){ "arg1", "arg2" }, + BodyLocKey = "body-loc-key", + BodyLocArgs = new List(){ "arg3", "arg4" }, + }; + var copy = original.CopyAndValidate(); + Assert.NotSame(original, copy); + Assert.NotSame(original.TitleLocArgs, copy.TitleLocArgs); + Assert.NotSame(original.BodyLocArgs, copy.BodyLocArgs); + } + + + [Fact] + public void AndroidConfigInvalidTTL() + { + var message = new Message() + { + Topic = "test-topic", + Android = new AndroidConfig() + { + TimeToLive = TimeSpan.FromHours(-1), + }, + }; + Assert.Throws(() => message.CopyAndValidate()); } [Fact] @@ -491,6 +588,22 @@ public void WebpushConfig() AssertJsonEquals(expected, message); } + [Fact] + public void WebpushConfigMinimal() + { + var message = new Message() + { + Topic = "test-topic", + Webpush = new WebpushConfig(), + }; + var expected = new JObject() + { + {"topic", "test-topic"}, + {"webpush", new JObject()}, + }; + AssertJsonEquals(expected, message); + } + [Fact] public void WebpushConfigMinimalNotification() { @@ -528,21 +641,46 @@ public void WebpushConfigMinimalNotification() } [Fact] - public void WebpushConfigDuplicateKeys() + public void WebpushConfigDeserialization() { - var message = new Message() + var original = new WebpushConfig() { - Topic = "test-topic", - Webpush = new WebpushConfig() + Headers = new Dictionary() { - Notification = new WebpushNotification() - { - Title = "title", - CustomData = new Dictionary(){{"title", "other"}}, - }, + {"header1", "header-value1"}, + {"header2", "header-value2"}, + }, + Data = new Dictionary() + { + {"key1", "value1"}, + {"key2", "value2"}, + }, + Notification = new WebpushNotification() + { + Title = "title", }, }; - Assert.Throws(() => message.CopyAndValidate()); + var json = NewtonsoftJsonSerializer.Instance.Serialize(original); + var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); + Assert.Equal(original.Headers, copy.Headers); + Assert.Equal(original.Data, copy.Data); + Assert.Equal(original.Notification.Title, copy.Notification.Title); + } + + [Fact] + public void WebpushConfigCopy() + { + var original = new WebpushConfig() + { + Headers = new Dictionary(), + Data = new Dictionary(), + Notification = new WebpushNotification(), + }; + var copy = original.CopyAndValidate(); + Assert.NotSame(original, copy); + Assert.NotSame(original.Headers, copy.Headers); + Assert.NotSame(original.Data, copy.Data); + Assert.NotSame(original.Notification, copy.Notification); } [Fact] @@ -550,6 +688,38 @@ public void WebpushNotificationDeserialization() { var original = new WebpushNotification() { + Title = "title", + Body = "body", + Icon = "icon", + Badge = "badge", + Data = new Dictionary() + { + {"some", "data"}, + }, + Direction = Direction.LeftToRight, + Image = "image", + Language = "language", + Tag = "tag", + Silent = true, + RequireInteraction = true, + Renotify = true, + TimestampMillis = 100, + Vibrate = new int[]{10, 5, 10}, + Actions = new List() + { + new Action() + { + ActionName = "Accept", + Title = "Ok", + Icon = "ok-button", + }, + new Action() + { + ActionName = "Reject", + Title = "Cancel", + Icon = "cancel-button", + }, + }, CustomData = new Dictionary() { {"custom-key1", "custom-data"}, @@ -558,9 +728,77 @@ public void WebpushNotificationDeserialization() }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); + Assert.Equal(original.Title, copy.Title); + Assert.Equal(original.Body, copy.Body); + Assert.Equal(original.Icon, copy.Icon); + Assert.Equal(original.Badge, copy.Badge); + Assert.Equal(new JObject(){{"some", "data"}}, copy.Data); + Assert.Equal(original.Direction, copy.Direction); + Assert.Equal(original.Image, copy.Image); + Assert.Equal(original.Language, copy.Language); + Assert.Equal(original.Tag, copy.Tag); + Assert.Equal(original.Silent, copy.Silent); + Assert.Equal(original.RequireInteraction, copy.RequireInteraction); + Assert.Equal(original.Renotify, copy.Renotify); + Assert.Equal(original.TimestampMillis, copy.TimestampMillis); + Assert.Equal(original.Vibrate, copy.Vibrate); + var originalActions = original.Actions.ToList(); + var copyActions = original.Actions.ToList(); + Assert.Equal(originalActions.Count, copyActions.Count); + for (int i = 0; i < originalActions.Count; i++) + { + Assert.Equal(originalActions[i].ActionName, copyActions[i].ActionName); + Assert.Equal(originalActions[i].Title, copyActions[i].Title); + Assert.Equal(originalActions[i].Icon, copyActions[i].Icon); + } + Assert.Equal(original.CustomData, copy.CustomData); + } + + [Fact] + public void WebpushNotificationCopy() + { + var original = new WebpushNotification() + { + Actions = new List() + { + new Action() + { + ActionName = "Accept", + Title = "Ok", + Icon = "ok-button", + }, + }, + CustomData = new Dictionary() + { + {"custom-key1", "custom-data"}, + }, + }; + var copy = original.CopyAndValidate(); + Assert.NotSame(original, copy); + Assert.NotSame(original.Actions, copy.Actions); + Assert.NotSame(original.Actions.First(), copy.Actions.First()); + Assert.NotSame(original.CustomData, copy.CustomData); Assert.Equal(original.CustomData, copy.CustomData); } + [Fact] + public void WebpushNotificationDuplicateKeys() + { + var message = new Message() + { + Topic = "test-topic", + Webpush = new WebpushConfig() + { + Notification = new WebpushNotification() + { + Title = "title", + CustomData = new Dictionary(){{"title", "other"}}, + }, + }, + }; + Assert.Throws(() => message.CopyAndValidate()); + } + private void AssertJsonEquals(JObject expected, Message actual) { var json = NewtonsoftJsonSerializer.Instance.Serialize(actual.CopyAndValidate()); diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs index 618f1949..c6c49d3f 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs @@ -153,6 +153,12 @@ private string DirectionString [JsonProperty("vibrate")] public int[] Vibrate { get; set; } + /// + /// A collection of notification actions to be associated with the notification. + /// + [JsonProperty("actions")] + public IEnumerable Actions; + /// /// A collection of arbitrary key-value data to be included in the notification. This is /// exposed as an to support correct @@ -161,12 +167,6 @@ private string DirectionString [JsonExtensionData] public IDictionary CustomData { get; set; } - /// - /// A collection of notification actions to be associated with the notification. - /// - [JsonProperty("actions")] - public IEnumerable Actions; - /// /// Copies this Webpush notification, and validates the content of it to ensure that it can /// be serialized into the JSON format expected by the FCM service. @@ -199,6 +199,7 @@ internal WebpushNotification CopyAndValidate() // Serialize the notification without CustomData for validation. var json = serializer.Serialize(copy); var dict = serializer.Deserialize>(json); + customData = new Dictionary(customData); foreach (var entry in customData) { if (dict.ContainsKey(entry.Key)) From b1de01f52f179e95cb1027b08635d3fa8c52d736 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Wed, 9 Jan 2019 16:44:46 -0800 Subject: [PATCH 6/6] Handling invalid direction values during deserialization --- FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs index c6c49d3f..10dd7fb3 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/WebpushNotification.cs @@ -99,6 +99,9 @@ private string DirectionString case "rtl": Direction = Messaging.Direction.RightToLeft; return; + default: + throw new FirebaseException($"Invalid direction value: {value}. Only " + + "'auto', 'rtl' and 'ltr' are allowed."); } } }