diff --git a/.gitignore b/.gitignore index d7149f3f..a5f45866 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ bin/ obj/ .vscode/ .vs/ +.idea/ diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj b/FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj index f7e0b43b..01f44670 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj +++ b/FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.0 diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/FcmOptionsTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/FcmOptionsTest.cs new file mode 100644 index 00000000..5fe1037d --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/FcmOptionsTest.cs @@ -0,0 +1,80 @@ +// Copyright 2019, 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 FirebaseAdmin.Messaging; +using FirebaseAdmin.Messaging.Util; +using Xunit; + +namespace FirebaseAdmin.Tests.Messaging +{ + public class FcmOptionsTest + { + [Fact] + public void FcmOptionsCopyAndValidate() + { + var options = new FcmOptions() { AnalyticsLabel = "label" }; + var result = options.CopyAndValidate(); + Assert.Equal(options.AnalyticsLabel, result.AnalyticsLabel); + } + + [Fact] + public void ApnsFcmOptionsCopyAndValidate() + { + var options = new ApnsFcmOptions() { AnalyticsLabel = "label" }; + var result = options.CopyAndValidate(); + Assert.Equal(options.AnalyticsLabel, result.AnalyticsLabel); + } + + [Fact] + public void AndroidFcmOptionsCopyAndValidate() + { + var options = new AndroidFcmOptions() { AnalyticsLabel = "label" }; + var result = options.CopyAndValidate(); + Assert.Equal(options.AnalyticsLabel, result.AnalyticsLabel); + } + + [Fact] + public void FcmOptionsCopyAndValidateNullLabel() + { + var options = new FcmOptions() { AnalyticsLabel = null }; + options.CopyAndValidate(); + } + + [Fact] + public void FcmOptionsCopyAndValidateEmptyLabel() + { + var options = new FcmOptions() { AnalyticsLabel = string.Empty }; + Assert.Throws(() => options.CopyAndValidate()); + } + + [Fact] + public void AnalyticsLabelTooLong() + { + Assert.Throws(() => AnalyticsLabelChecker.ValidateAnalyticsLabel(new string('a', 51))); + } + + [Fact] + public void AnalyticsLabelEmtpty() + { + Assert.Throws(() => AnalyticsLabelChecker.ValidateAnalyticsLabel(string.Empty)); + } + + [Fact] + public void AnalyticsLabelInvalidCharacters() + { + Assert.Throws(() => AnalyticsLabelChecker.ValidateAnalyticsLabel("label(label)")); + } + } +} diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs index 661500f3..defe7307 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Messaging/MessageTest.cs @@ -75,6 +75,10 @@ public void Notification() Title = "title", Body = "body", }, + FcmOptions = new FcmOptions() + { + AnalyticsLabel = "label", + }, }; var expected = new JObject() { @@ -86,6 +90,12 @@ public void Notification() { "body", "body" }, } }, + { + "fcm_options", new JObject() + { + { "analytics_label", "label" }, + } + }, }; this.AssertJsonEquals(expected, message); } @@ -117,6 +127,10 @@ public void MessageDeserialization() { Data = new Dictionary() { { "key", "value" } }, }, + FcmOptions = new FcmOptions() + { + AnalyticsLabel = "label", + }, }; var json = NewtonsoftJsonSerializer.Instance.Serialize(original); var copy = NewtonsoftJsonSerializer.Instance.Deserialize(json); @@ -128,6 +142,7 @@ public void MessageDeserialization() original.Android.RestrictedPackageName, copy.Android.RestrictedPackageName); Assert.Equal(original.Apns.Aps.AlertString, copy.Apns.Aps.AlertString); Assert.Equal(original.Webpush.Data, copy.Webpush.Data); + Assert.Equal(original.FcmOptions.AnalyticsLabel, copy.FcmOptions.AnalyticsLabel); } [Fact] @@ -236,6 +251,10 @@ public void AndroidConfig() BodyLocArgs = new List() { "arg3", "arg4" }, ChannelId = "channel-id", }, + FcmOptions = new AndroidFcmOptions() + { + AnalyticsLabel = "label", + }, }, }; var expected = new JObject() @@ -266,6 +285,12 @@ public void AndroidConfig() { "channel_id", "channel-id" }, } }, + { + "fcm_options", new JObject() + { + { "analytics_label", "label" }, + } + }, } }, }; @@ -843,6 +868,10 @@ public void ApnsConfig() { "custom-key3", "custom-data" }, { "custom-key4", true }, }, + FcmOptions = new ApnsFcmOptions() + { + AnalyticsLabel = "label", + }, }, }; var expected = new JObject() @@ -879,6 +908,12 @@ public void ApnsConfig() { "custom-key4", true }, } }, + { + "fcm_options", new JObject() + { + { "analytics_label", "label" }, + } + }, } }, }; @@ -1650,6 +1685,25 @@ public void WebpushNotificationWithInvalidHttpsLinkUrl() Assert.Throws(() => message.CopyAndValidate()); } + [Fact] + public void AnalyticsLabelInvalid() + { + var message = new Message() + { + Topic = "test-topic", + Notification = new Notification() + { + Title = "title", + Body = "body", + }, + FcmOptions = new FcmOptions() + { + AnalyticsLabel = "label!", + }, + }; + Assert.Throws(() => message.CopyAndValidate()); + } + private void AssertJsonEquals(JObject expected, Message actual) { var json = NewtonsoftJsonSerializer.Instance.Serialize(actual.CopyAndValidate()); diff --git a/FirebaseAdmin/FirebaseAdmin/FirebaseAdmin.csproj b/FirebaseAdmin/FirebaseAdmin/FirebaseAdmin.csproj index bb8fed0f..b891ee31 100644 --- a/FirebaseAdmin/FirebaseAdmin/FirebaseAdmin.csproj +++ b/FirebaseAdmin/FirebaseAdmin/FirebaseAdmin.csproj @@ -1,4 +1,4 @@ - + 1.7.0 diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidConfig.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidConfig.cs index 9ead102d..881011ce 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidConfig.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidConfig.cs @@ -66,6 +66,12 @@ public sealed class AndroidConfig [JsonProperty("notification")] public AndroidNotification Notification { get; set; } + /// + /// Gets or sets the FCM options to be included in the message. + /// + [JsonProperty("fcm_options")] + public AndroidFcmOptions FcmOptions { get; set; } + /// /// Gets or sets the string representation of as accepted by the FCM /// backend service. @@ -159,6 +165,7 @@ internal AndroidConfig CopyAndValidate() TimeToLive = this.TimeToLive, RestrictedPackageName = this.RestrictedPackageName, Data = this.Data?.Copy(), + FcmOptions = this.FcmOptions?.CopyAndValidate(), }; var totalSeconds = copy.TimeToLive?.TotalSeconds ?? 0; if (totalSeconds < 0) diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidFcmOptions.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidFcmOptions.cs new file mode 100644 index 00000000..06f687e3 --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/AndroidFcmOptions.cs @@ -0,0 +1,46 @@ +// Copyright 2019, 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 FirebaseAdmin.Messaging.Util; +using Newtonsoft.Json; + +namespace FirebaseAdmin.Messaging +{ + /// + /// Represents Android FCM options. + /// + public sealed class AndroidFcmOptions + { + /// + /// Gets or sets analytics label. + /// + [JsonProperty("analytics_label")] + public string AnalyticsLabel { get; set; } + + /// + /// Copies this FCM options, and validates the content of it to ensure that it can + /// be serialized into the JSON format expected by the FCM service. + /// + internal AndroidFcmOptions CopyAndValidate() + { + var copy = new AndroidFcmOptions() + { + AnalyticsLabel = this.AnalyticsLabel, + }; + AnalyticsLabelChecker.ValidateAnalyticsLabel(copy.AnalyticsLabel); + + return copy; + } + } +} diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/ApnsConfig.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/ApnsConfig.cs index 36cc68db..01c3b782 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/ApnsConfig.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/ApnsConfig.cs @@ -35,6 +35,12 @@ public sealed class ApnsConfig [JsonProperty("headers")] public IReadOnlyDictionary Headers { get; set; } + /// + /// Gets or sets the FCM options to be included in the message. + /// + [JsonProperty("fcm_options")] + public ApnsFcmOptions FcmOptions { get; set; } + /// /// Gets or sets the aps dictionary to be included in the APNs payload. /// @@ -102,6 +108,7 @@ internal ApnsConfig CopyAndValidate() { Headers = this.Headers?.Copy(), Payload = this.Payload.CopyAndValidate(), + FcmOptions = this.FcmOptions?.CopyAndValidate(), }; return copy; } diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/ApnsFcmOptions.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/ApnsFcmOptions.cs new file mode 100644 index 00000000..4cb524df --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/ApnsFcmOptions.cs @@ -0,0 +1,46 @@ +// Copyright 2019, 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 FirebaseAdmin.Messaging.Util; +using Newtonsoft.Json; + +namespace FirebaseAdmin.Messaging +{ + /// + /// Represents Apple Push Notification Service FCM options. + /// + public sealed class ApnsFcmOptions + { + /// + /// Gets or sets analytics label. + /// + [JsonProperty("analytics_label")] + public string AnalyticsLabel { get; set; } + + /// + /// Copies this FCM options, and validates the content of it to ensure that it can + /// be serialized into the JSON format expected by the FCM service. + /// + internal ApnsFcmOptions CopyAndValidate() + { + var copy = new ApnsFcmOptions() + { + AnalyticsLabel = this.AnalyticsLabel, + }; + AnalyticsLabelChecker.ValidateAnalyticsLabel(copy.AnalyticsLabel); + + return copy; + } + } +} diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/FcmOptions.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/FcmOptions.cs new file mode 100644 index 00000000..2a429724 --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/FcmOptions.cs @@ -0,0 +1,46 @@ +// Copyright 2019, 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 FirebaseAdmin.Messaging.Util; +using Newtonsoft.Json; + +namespace FirebaseAdmin.Messaging +{ + /// + /// Represents FCM options. + /// + public sealed class FcmOptions + { + /// + /// Gets or sets analytics label. + /// + [JsonProperty("analytics_label")] + public string AnalyticsLabel { get; set; } + + /// + /// Copies this FCM options, and validates the content of it to ensure that it can + /// be serialized into the JSON format expected by the FCM service. + /// + internal FcmOptions CopyAndValidate() + { + var copy = new FcmOptions() + { + AnalyticsLabel = this.AnalyticsLabel, + }; + AnalyticsLabelChecker.ValidateAnalyticsLabel(copy.AnalyticsLabel); + + return copy; + } + } +} diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs index 21c2d254..9972ee50 100644 --- a/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/Message.cs @@ -80,6 +80,12 @@ public sealed class Message [JsonProperty("apns")] public ApnsConfig Apns { get; set; } + /// + /// Gets or sets the FCM options to be included in the message. + /// + [JsonProperty("fcm_options")] + public FcmOptions FcmOptions { get; set; } + /// /// Gets or sets the formatted representation of the . Removes the /// /topics/ prefix if present. This is what's ultimately sent to the FCM @@ -119,6 +125,7 @@ internal Message CopyAndValidate() Topic = this.Topic, Condition = this.Condition, Data = this.Data?.Copy(), + FcmOptions = this.FcmOptions?.CopyAndValidate(), }; var list = new List() { diff --git a/FirebaseAdmin/FirebaseAdmin/Messaging/Util/AnalyticsLabelChecker.cs b/FirebaseAdmin/FirebaseAdmin/Messaging/Util/AnalyticsLabelChecker.cs new file mode 100644 index 00000000..8ad60e94 --- /dev/null +++ b/FirebaseAdmin/FirebaseAdmin/Messaging/Util/AnalyticsLabelChecker.cs @@ -0,0 +1,50 @@ +// Copyright 2019, 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.Text.RegularExpressions; + +namespace FirebaseAdmin.Messaging.Util +{ + /// + /// Checker for analytics label. + /// + internal static class AnalyticsLabelChecker + { + private static string pattern = "^[a-zA-Z0-9-_.~%]{0,50}$"; + + /// + /// Checks anytytics labels an throw if not valid. + /// + /// If analytics label does not match pattern. + /// Analytics label. + public static void ValidateAnalyticsLabel(string analyticsLabel) + { + if (analyticsLabel == null) + { + return; + } + + if (analyticsLabel == string.Empty) + { + throw new ArgumentException("Analytics label must have format matching'^[a-zA-Z0-9-_.~%]{1,50}$"); + } + + if (!Regex.IsMatch(analyticsLabel, pattern)) + { + throw new ArgumentException("Analytics label must have format matching'^[a-zA-Z0-9-_.~%]{1,50}$"); + } + } + } +}