├── dist └── .gitkeep ├── docs └── .gitkeep ├── test └── .gitkeep ├── 3rd-party └── .gitkeep ├── .gitignore ├── src ├── AGConnectAdmin.snk ├── AGConnectAdmin.PK.snk ├── AGConnectAdmin.Examples │ ├── .editorconfig │ ├── AGConnectAdmin.Examples.csproj │ ├── Example.SendDataMessage.cs │ ├── Example.cs │ ├── Example.SendTopicMessage.cs │ ├── Example.SendAndroidMessage.cs │ ├── Example.SendTestMessage.cs │ ├── Example.SendInstanceAppMessage.cs │ ├── Example.SendConditionMessage.cs │ ├── Example.SendWebpushMessage.cs │ └── Example.SendApnsMessage.cs ├── AGConnectAdmin.Tests │ ├── Messaging │ │ ├── MessageTest.cs │ │ ├── ValidatorTest.cs │ │ ├── ConvertorTest.cs │ │ ├── MessagingTest.cs │ │ └── MessagingClientTest.cs │ ├── .editorconfig │ ├── options.json │ ├── AGConnectAdmin.Tests.csproj │ └── TestUtils.cs ├── .editorconfig ├── AGConnectAdmin │ ├── Messaging │ │ ├── NotificationStyle.cs │ │ ├── TopicListRequest.cs │ │ ├── TopicListResponse.cs │ │ ├── NotificationImportance.cs │ │ ├── Direction.cs │ │ ├── TopicEntity.cs │ │ ├── MessagePart.cs │ │ ├── NotificationVisibility.cs │ │ ├── TopicManagementResponse.cs │ │ ├── TopicManagementRequest.cs │ │ ├── SendRequest.cs │ │ ├── SingleMessageResponse.cs │ │ ├── WebpushAction.cs │ │ ├── ApnsHmsOptions.cs │ │ ├── Notification.cs │ │ ├── LightSettings.cs │ │ ├── BadgeNotification.cs │ │ ├── CriticalSound.cs │ │ ├── WebpushConfig.cs │ │ ├── Convertors │ │ │ └── TimeSpanJsonConverter.cs │ │ ├── LightColor.cs │ │ ├── AndroidConfig.cs │ │ ├── Message.cs │ │ ├── ClickAction.cs │ │ ├── ApnsConfig.cs │ │ ├── ApsAlert.cs │ │ ├── WebpushNotification.cs │ │ ├── Aps.cs │ │ ├── AGConnectMessaging.cs │ │ ├── AndroidNotification.cs │ │ └── AGConnectMessagingClient.cs │ ├── IAGConnectService.cs │ ├── AGConnectAdmin.csproj │ ├── AGConnectException.cs │ ├── Utils │ │ ├── Extensions.cs │ │ ├── Validator.cs │ │ └── NewtonsoftJsonSerializer.cs │ ├── Auth │ │ └── TokenResponse.cs │ ├── AppOptions.cs │ └── AGConnectApp.cs └── AGConnectAdmin.sln ├── Third Party Open Source Software Notice.docx ├── README_ZH.md ├── README.md └── LICENSE /dist/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3rd-party/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vscode/ 4 | .vs/ 5 | 6 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMS-Core/hms-push-serverdemo-csharp/HEAD/src/AGConnectAdmin.snk -------------------------------------------------------------------------------- /src/AGConnectAdmin.PK.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMS-Core/hms-push-serverdemo-csharp/HEAD/src/AGConnectAdmin.PK.snk -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA2007: 考虑对等待的任务调用 ConfigureAwait 4 | dotnet_diagnostic.CA2007.severity = suggestion 5 | -------------------------------------------------------------------------------- /Third Party Open Source Software Notice.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMS-Core/hms-push-serverdemo-csharp/HEAD/Third Party Open Source Software Notice.docx -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/Messaging/MessageTest.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMS-Core/hms-push-serverdemo-csharp/HEAD/src/AGConnectAdmin.Tests/Messaging/MessageTest.cs -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA2007: 考虑对等待的任务调用 ConfigureAwait 4 | dotnet_diagnostic.CA2007.severity = suggestion 5 | 6 | # CA2000: 丢失范围之前释放对象 7 | dotnet_diagnostic.CA2000.severity = suggestion 8 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "login_uri": "https://login.cloud.huawei.com/oauth2/v2/token", 3 | "api_base_uri": "https://push-api.cloud.huawei.com", 4 | "dev_app_id": "YOUR_REGISTRATION_APPID", 5 | "client_secret": "YOUR_REGISTRATION_APPSECRET" 6 | } 7 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA1303: 请不要将文本作为本地化参数传递 4 | dotnet_diagnostic.CA1303.severity = suggestion 5 | 6 | # CA1707: 从成员名称 AGConnectAdmin.Messaging.NotificationPriority.PRIORITY_UNSPECIFIED 中删除下划线。 7 | dotnet_diagnostic.CA1707.severity = suggestion 8 | 9 | # CA1305: 指定 IFormatProvider 10 | dotnet_diagnostic.CA1305.severity = suggestion 11 | 12 | # CA2227: 集合属性应为只读 13 | dotnet_diagnostic.CA2227.severity = suggestion 14 | 15 | # CA1062: 验证公共方法的参数 16 | dotnet_diagnostic.CA1062.severity = suggestion 17 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/AGConnectAdmin.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/AGConnectAdmin.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | true 9 | 10 | ../AGConnectAdmin.snk 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/NotificationStyle.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | namespace AGConnectAdmin.Messaging 17 | { 18 | /// 19 | /// Style param of notification 20 | /// 21 | public enum NotificationStyle 22 | { 23 | /// 24 | /// Default style 25 | /// 26 | Default = 0, 27 | 28 | /// 29 | /// Big text style 30 | /// 31 | BigText = 1 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/TopicListRequest.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | 18 | namespace AGConnectAdmin.Messaging 19 | { 20 | /// 21 | /// Represents request parameter for retreive topic list. 22 | /// 23 | public sealed class TopicListRequest 24 | { 25 | /// 26 | /// Gets or sets the token of target device. 27 | /// 28 | [JsonProperty("token")] 29 | public string Token { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/IAGConnectService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | namespace AGConnectAdmin 17 | { 18 | /// 19 | /// A stateful service that can be associated with an . This 20 | /// interface enables tearing down the service when the parent app instance is deleted. 21 | /// 22 | internal interface IAGConnectService 23 | { 24 | /// 25 | /// Cleans up any state associated with this service making it unsuitable for further use. 26 | /// 27 | void Delete(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/TopicListResponse.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | using System.Collections.Generic; 18 | 19 | namespace AGConnectAdmin.Messaging 20 | { 21 | /// 22 | /// Represents response result for retreive topic list. 23 | /// 24 | public sealed class TopicListResponse : SingleMessageResponse 25 | { 26 | /// 27 | /// Gets or sets the list of topics 28 | /// 29 | [JsonProperty("topics")] 30 | public List Topics { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/AGConnectAdmin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0 5 | netstandard2.0;net45 6 | true 7 | ../AGConnectAdmin.snk 8 | true 9 | Huawei 10 | AGConnect Admin SDK enables server-side .NET developers to integrate HCM into their 11 | services and applications 12 | Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. 13 | Huawei 14 | Debug;Release;Proxy 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/NotificationImportance.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | namespace AGConnectAdmin.Messaging 17 | { 18 | /// 19 | /// The enumerations of notification priority. 20 | /// 21 | public enum NotificationImportance 22 | { 23 | /// 24 | /// LOW PRIORITY 25 | /// 26 | LOW, 27 | 28 | /// 29 | /// NORMAL PRIORITY 30 | /// 31 | NORMAL, 32 | 33 | /// 34 | /// HIGH PRIORITY 35 | /// 36 | HIGH 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/Direction.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | namespace AGConnectAdmin.Messaging 17 | { 18 | /// 19 | /// Different directions a notification can be displayed in. 20 | /// 21 | public enum Direction 22 | { 23 | /// 24 | /// Direction automatically determined. 25 | /// 26 | Auto, 27 | 28 | /// 29 | /// Left to right. 30 | /// 31 | LeftToRight, 32 | 33 | /// 34 | /// Right to left. 35 | /// 36 | RightToLeft, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/TopicEntity.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using Newtonsoft.Json; 18 | 19 | namespace AGConnectAdmin.Messaging 20 | { 21 | /// 22 | /// Represents a topic entity. 23 | /// 24 | public sealed class TopicEntity 25 | { 26 | /// 27 | /// Gets or sets the topic name. 28 | /// 29 | [JsonProperty("name")] 30 | public string Name { get; set; } 31 | 32 | /// 33 | /// Gets or sets the topic creation date. 34 | /// 35 | [JsonProperty("addDate")] 36 | public DateTime AddDate { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/MessagePart.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | namespace AGConnectAdmin.Messaging 17 | { 18 | /// 19 | /// Represents a part of push message. 20 | /// 21 | /// The type itself. 22 | public abstract class MessagePart 23 | { 24 | /// 25 | /// Copies this message part, and validates the content of it to ensure that it can be 26 | /// serialized into the JSON format expected by the AGConnect Cloud Messaging service. 27 | /// 28 | /// A copy of this message part 29 | internal protected abstract T CopyAndValidate(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/Messaging/ValidatorTest.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Utils; 17 | using Xunit; 18 | 19 | namespace AGConnectAdmin.Tests 20 | { 21 | public class ValidatorTest 22 | { 23 | [Fact] 24 | public void TestValidator() 25 | { 26 | Assert.False(Validator.IsInRange(0, 1, 5), "out of range at low"); 27 | Assert.True(Validator.IsInRange(1, 1, 5), "low bound of range"); 28 | Assert.True(Validator.IsInRange(2, 1, 5), "in range"); 29 | Assert.True(Validator.IsInRange(5, 1, 5), "high bound of range"); 30 | Assert.False(Validator.IsInRange(6, 1, 5), "out of range at high"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/NotificationVisibility.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | namespace AGConnectAdmin.Messaging 17 | { 18 | /// 19 | /// The enumerations of notification visibility 20 | /// 21 | public enum NotificationVisibility 22 | { 23 | /// 24 | /// Visibility 25 | /// 26 | VISIBILITY_UNSPECIFIED, 27 | 28 | /// 29 | /// Private 30 | /// 31 | PRIVATE, 32 | 33 | /// 34 | /// Public 35 | /// 36 | PUBLIC, 37 | 38 | /// 39 | /// Secret 40 | /// 41 | SECRET 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/TopicManagementResponse.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | 18 | namespace AGConnectAdmin.Messaging 19 | { 20 | /// 21 | /// Represents response result for topic management. 22 | /// 23 | public sealed class TopicManagementResponse : SingleMessageResponse 24 | { 25 | /// 26 | /// Gets or sets the count of failure. 27 | /// 28 | [JsonProperty("failureCount")] 29 | public int FailureCount { get; set; } 30 | 31 | /// 32 | /// Gets or sets the count of success. 33 | /// 34 | [JsonProperty("successCount")] 35 | public int SuccessCount { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/TopicManagementRequest.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | using System.Collections.Generic; 18 | 19 | namespace AGConnectAdmin.Messaging 20 | { 21 | /// 22 | /// Represents request parameter for topic management. 23 | /// 24 | public sealed class TopicManagementRequest 25 | { 26 | /// 27 | /// Gets or sets the specified topic. 28 | /// 29 | [JsonProperty("topic")] 30 | public string Topic { get; set; } 31 | 32 | /// 33 | /// Gets or sets the specified tokens. 34 | /// 35 | [JsonProperty("tokenArray")] 36 | public List Tokens { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.SendDataMessage.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | namespace AGConnectAdmin.Examples 22 | { 23 | partial class Example 24 | { 25 | [Fact] 26 | public async Task SendDataMessage() 27 | { 28 | await AGConnectMessaging.DefaultInstance.SendAsync(new Message() 29 | { 30 | Data = "{'key1':'value1', 'key2':'value2'}", 31 | Android = new AndroidConfig() 32 | { 33 | Urgency = UrgencyPriority.HIGH 34 | }, 35 | Token = new List() { TOKEN_ANDROID } 36 | }); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/AGConnectException.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | 18 | namespace AGConnectAdmin 19 | { 20 | /// 21 | /// Common error type for all exceptions raised by AGConnect APIs. 22 | /// 23 | [Serializable] 24 | public class AGConnectException : Exception 25 | { 26 | internal AGConnectException() {} 27 | 28 | internal AGConnectException(string message) 29 | : base(message) { } 30 | 31 | internal AGConnectException(string message, Exception inner) 32 | : base(message, inner) { } 33 | 34 | /// 35 | protected AGConnectException( 36 | System.Runtime.Serialization.SerializationInfo info, 37 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Xunit.Abstractions; 17 | 18 | namespace AGConnectAdmin.Examples 19 | { 20 | public partial class Example 21 | { 22 | #region Tokens 23 | private const string TOKEN_IOS = "your ios token"; 24 | private const string TOKEN_ANDROID = "your android token"; 25 | private const string TOKEN_WEB = "your web token"; 26 | #endregion 27 | 28 | protected ITestOutputHelper Logger { get; private set; } 29 | 30 | public Example(ITestOutputHelper logger) 31 | { 32 | Logger = logger; 33 | 34 | AGConnectApp.Create(new AppOptions() 35 | { 36 | ClientId = "your client id", 37 | ClientSecret = "your cliient secret", 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/SendRequest.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | 18 | namespace AGConnectAdmin.Messaging 19 | { 20 | /// 21 | /// Represents the envelope message accepted by the AGC backend service, including the message 22 | /// payload and other options like validate_only. 23 | /// 24 | internal class SendRequest 25 | { 26 | /// 27 | /// Gets or sets the message body. 28 | /// 29 | [JsonProperty("message")] 30 | public Message Message { get; set; } 31 | 32 | /// 33 | /// Gets or sets if it's a validation message only that it will not be sent actually. 34 | /// 35 | [JsonProperty("validate_only")] 36 | public bool ValidateOnly { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.SendTopicMessage.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Threading.Tasks; 18 | using Xunit; 19 | 20 | namespace AGConnectAdmin.Examples 21 | { 22 | partial class Example 23 | { 24 | [Fact] 25 | public async Task SendTopicMessage() 26 | { 27 | await AGConnectMessaging.DefaultInstance.SendAsync(new Message() 28 | { 29 | Android = new AndroidConfig() 30 | { 31 | Notification = new AndroidNotification() 32 | { 33 | Title = "Notification from .NET", 34 | Body = "Hello world!", 35 | ClickAction = ClickAction.OpenApp() 36 | } 37 | }, 38 | Topic = "News" 39 | }); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.SendAndroidMessage.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | namespace AGConnectAdmin.Examples 22 | { 23 | partial class Example 24 | { 25 | [Fact] 26 | public async Task SendAndroidMessage() 27 | { 28 | await AGConnectMessaging.DefaultInstance.SendAsync(new Message() 29 | { 30 | Android = new AndroidConfig() 31 | { 32 | Notification = new AndroidNotification() 33 | { 34 | Title = "Notification from .NET", 35 | Body = "Hello world!", 36 | ClickAction = ClickAction.OpenApp() 37 | } 38 | }, 39 | Token = new List() { TOKEN_ANDROID } 40 | }); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.SendTestMessage.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | namespace AGConnectAdmin.Examples 22 | { 23 | partial class Example 24 | { 25 | [Fact] 26 | public async Task SendTestMessage() 27 | { 28 | await AGConnectMessaging.DefaultInstance.SendAsync(new Message() 29 | { 30 | Android = new AndroidConfig() 31 | { 32 | Notification = new AndroidNotification() 33 | { 34 | Title = "Notification from .NET", 35 | Body = "Hello world!", 36 | ClickAction = ClickAction.OpenApp() 37 | } 38 | }, 39 | Token = new List() { TOKEN_ANDROID } 40 | }, true); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.SendInstanceAppMessage.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | namespace AGConnectAdmin.Examples 22 | { 23 | partial class Example 24 | { 25 | [Fact] 26 | public async Task SendInstanceAppMessage() 27 | { 28 | await AGConnectMessaging.DefaultInstance.SendAsync(new Message() 29 | { 30 | Android = new AndroidConfig() 31 | { 32 | Notification = new AndroidNotification() 33 | { 34 | Title = "Notification from .NET", 35 | Body = "Hello world!", 36 | ClickAction = ClickAction.OpenApp() 37 | }, 38 | FastAppTarget = FastAppTarget.Development 39 | }, 40 | Token = new List() { TOKEN_ANDROID } 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/SingleMessageResponse.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | 18 | namespace AGConnectAdmin.Messaging 19 | { 20 | /// 21 | /// Represents the response messages sent by the AGC backend service when sending a single 22 | /// message. Primarily consists of the message ID (Name) that indicates success handoff to AGC. 23 | /// 24 | public class SingleMessageResponse 25 | { 26 | /// 27 | /// Gets or sets the result code. 28 | /// 29 | [JsonProperty("code")] 30 | public string Code { get; set; } 31 | 32 | /// 33 | /// Gets or set the result message. 34 | /// 35 | [JsonProperty("msg")] 36 | public string Message { get; set; } 37 | 38 | /// 39 | /// Gets or set a request ID corresponding to the request. 40 | /// 41 | [JsonProperty("requestId")] 42 | public string RequestId { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.SendConditionMessage.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | namespace AGConnectAdmin.Examples 22 | { 23 | partial class Example 24 | { 25 | [Fact] 26 | public async Task SendConditionMessage() 27 | { 28 | await AGConnectMessaging.DefaultInstance.SendAsync(new Message() 29 | { 30 | Android = new AndroidConfig() 31 | { 32 | Notification = new AndroidNotification() 33 | { 34 | Title = "Notification from .NET", 35 | Body = "Hello world!", 36 | ClickAction = ClickAction.OpenApp() 37 | } 38 | }, 39 | Condition = "'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)", 40 | Token = new List() { TOKEN_ANDROID }, 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.SendWebpushMessage.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | namespace AGConnectAdmin.Examples 22 | { 23 | partial class Example 24 | { 25 | [Fact] 26 | public async Task SendWebpushMessage() 27 | { 28 | await AGConnectMessaging.DefaultInstance.SendAsync(new Message() 29 | { 30 | Webpush = new WebpushConfig() 31 | { 32 | Notification = new WebpushNotification() 33 | { 34 | Title = "Notification from .NET", 35 | Body = "Hello world!", 36 | }, 37 | HmsOptions = new WebpushHmsOptions() 38 | { 39 | Link = "https://example.com" 40 | } 41 | }, 42 | Token = new List() { TOKEN_WEB } 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/TestUtils.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.IO; 19 | 20 | namespace AGConnectAdmin.Tests 21 | { 22 | internal static class TestUtils 23 | { 24 | private const string ServiceAccountFile = "/options.json"; 25 | 26 | private static bool Initialized = false; 27 | public static void GlobalInit() 28 | { 29 | if (!Initialized) 30 | { 31 | Initialized = true; 32 | AGConnectApp.Create(TestUtils.ReadOptionsFromDisk()); 33 | } 34 | } 35 | 36 | public static AppOptions ReadOptionsFromDisk() 37 | { 38 | var content = File.ReadAllText(Environment.CurrentDirectory + ServiceAccountFile); 39 | IDictionary keyValuePairs = NewtonsoftJsonSerializer.Instance.Deserialize>(content); 40 | return new AppOptions() 41 | { 42 | LoginUri = keyValuePairs["login_uri"], 43 | ApiBaseUri = keyValuePairs["api_base_uri"], 44 | ClientId = keyValuePairs["dev_app_id"], 45 | ClientSecret = keyValuePairs["client_secret"] 46 | }; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Utils/Extensions.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | 19 | namespace AGConnectAdmin 20 | { 21 | /// 22 | /// A collection of extension methods for internal use in the SDK. 23 | /// 24 | internal static class Extensions 25 | { 26 | /// 27 | /// A utility method for throwing an System.ArgumentNullException if the object is null. 28 | /// 29 | public static T ThrowIfNull(this T obj, string paramName) 30 | { 31 | if (obj == null) 32 | { 33 | throw new ArgumentNullException(paramName); 34 | } 35 | 36 | return obj; 37 | } 38 | 39 | /// 40 | /// Creates a shallow copy of a collection of key-value pairs. 41 | /// 42 | public static IReadOnlyDictionary Copy( 43 | this IEnumerable> source) 44 | { 45 | var copy = new Dictionary(); 46 | foreach (var entry in source) 47 | { 48 | copy[entry.Key] = entry.Value; 49 | } 50 | 51 | return copy; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Examples/Example.SendApnsMessage.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | namespace AGConnectAdmin.Examples 22 | { 23 | partial class Example 24 | { 25 | [Fact] 26 | public async Task SendApnsMessage() 27 | { 28 | await AGConnectMessaging.DefaultInstance.SendAsync(new Message() 29 | { 30 | Apns = new ApnsConfig() 31 | { 32 | Aps = new Aps() 33 | { 34 | Alert = new ApsAlert() 35 | { 36 | Title = "Notification from .NET", 37 | Body = "Hello world!" 38 | } 39 | }, 40 | HmsOptions = new ApnsHmsOptions() 41 | { 42 | TargetUserType = ApnsTargetUserType.Test 43 | }, 44 | CustomData = new Dictionary() 45 | { 46 | ["key1"] = "value1" 47 | } 48 | }, 49 | Token = new List() { TOKEN_IOS } 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/WebpushAction.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class and some properties 16 | */ 17 | 18 | using Newtonsoft.Json; 19 | 20 | namespace AGConnectAdmin.Messaging 21 | { 22 | /// 23 | /// Represents an action available to users when the notification is presented. 24 | /// 25 | public class WebpushAction 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public WebpushAction() { } 31 | 32 | internal WebpushAction(WebpushAction action) 33 | { 34 | this.Action = action.Action; 35 | this.Title = action.Title; 36 | this.Icon = action.Icon; 37 | } 38 | 39 | /// 40 | /// Gets or sets the name of the Action. 41 | /// 42 | [JsonProperty("action")] 43 | public string Action { get; set; } 44 | 45 | /// 46 | /// Gets or sets the title text. 47 | /// 48 | [JsonProperty("title")] 49 | public string Title { get; set; } 50 | 51 | /// 52 | /// Gets or sets the icon URL. 53 | /// 54 | [JsonProperty("icon")] 55 | public string Icon { get; set; } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/ApnsHmsOptions.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | 18 | namespace AGConnectAdmin.Messaging 19 | { 20 | /// 21 | /// Represents Apple Push Notification Service HMS options. 22 | /// 23 | public class ApnsHmsOptions : MessagePart 24 | { 25 | /// 26 | /// Gets or sets target user type. 27 | /// 28 | [JsonProperty("target_user_type")] 29 | public ApnsTargetUserType TargetUserType { get; set; } = ApnsTargetUserType.Test; 30 | 31 | /// 32 | internal protected override ApnsHmsOptions CopyAndValidate() 33 | { 34 | var copy = new ApnsHmsOptions() 35 | { 36 | TargetUserType = this.TargetUserType, 37 | }; 38 | 39 | return copy; 40 | } 41 | } 42 | 43 | /// 44 | /// Represents the target user type. 45 | /// 46 | public enum ApnsTargetUserType 47 | { 48 | /// 49 | /// Test user. 50 | /// 51 | Test = 1, 52 | 53 | /// 54 | /// Normal user. 55 | /// 56 | Normal = 2, 57 | 58 | /// 59 | /// VoIP user. 60 | /// 61 | VoIP = 3 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Auth/TokenResponse.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using Newtonsoft.Json; 18 | 19 | namespace AGConnectAdmin.Auth 20 | { 21 | internal sealed class TokenResponse 22 | { 23 | [JsonProperty("access_token")] 24 | public string AccessToken { get; set; } 25 | 26 | [JsonProperty("expires_in")] 27 | public long? Expires { get; set; } 28 | 29 | [JsonProperty("scope")] 30 | public string Scope { get; set; } 31 | 32 | [JsonProperty("error")] 33 | public long? Error { get; set; } 34 | 35 | [JsonProperty("error_description")] 36 | public string Description { get; set; } 37 | 38 | [JsonIgnore] 39 | private DateTime CreateTime; 40 | 41 | TokenResponse() 42 | { 43 | CreateTime = DateTime.UtcNow; 44 | } 45 | 46 | private static TimeSpan TimeInAdvance = TimeSpan.FromMinutes(5); 47 | 48 | internal string GetValidAccessToken() 49 | { 50 | if (string.IsNullOrEmpty(AccessToken)) 51 | { 52 | return null; 53 | } 54 | 55 | TimeSpan leftDuration = CreateTime.AddSeconds(Expires ?? 0) - DateTime.UtcNow; 56 | if (leftDuration < TimeInAdvance) 57 | { 58 | return null; 59 | } 60 | 61 | return AccessToken; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/Notification.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class and some properties 16 | */ 17 | 18 | using AGConnectAdmin.Utils; 19 | using Newtonsoft.Json; 20 | using System; 21 | 22 | namespace AGConnectAdmin.Messaging 23 | { 24 | /// 25 | /// Represents the notification parameters that can be included in a . 26 | /// 27 | public class Notification : MessagePart 28 | { 29 | /// 30 | /// Gets or sets the title of the notification. 31 | /// 32 | [JsonProperty("title")] 33 | public string Title { get; set; } 34 | 35 | /// 36 | /// Gets or sets the body of the notification. 37 | /// 38 | [JsonProperty("body")] 39 | public string Body { get; set; } 40 | 41 | /// 42 | /// Gets or sets the image url of the notification. 43 | /// 44 | [JsonProperty("image")] 45 | public string Image { get; set; } 46 | 47 | /// 48 | internal protected override Notification CopyAndValidate() 49 | { 50 | if (!string.IsNullOrEmpty(Image) && !Validator.IsHttpsUrl(Image)) 51 | { 52 | throw new ArgumentException("Image must be a https url."); 53 | } 54 | 55 | return new Notification() 56 | { 57 | Title = Title, 58 | Body = Body, 59 | Image = Image 60 | }; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/LightSettings.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging.Convertors; 17 | using Newtonsoft.Json; 18 | using System; 19 | 20 | namespace AGConnectAdmin.Messaging 21 | { 22 | /// 23 | /// Represents the light settings. 24 | /// 25 | public class LightSettings : MessagePart 26 | { 27 | /// 28 | /// Gets or sets the light color. 29 | /// 30 | [JsonProperty("color")] 31 | public LightColor Color { get; set; } 32 | 33 | /// 34 | /// Gets or sets the duration of turning on the light. 35 | /// 36 | [JsonProperty("light_on_duration")] 37 | [JsonConverter(typeof(TimeSpanJsonConverter))] 38 | public TimeSpan? LightOnDuration { get; set; } 39 | 40 | /// 41 | /// Gets or set the duration of turning off the light. 42 | /// 43 | [JsonProperty("light_off_duration")] 44 | [JsonConverter(typeof(TimeSpanJsonConverter))] 45 | public TimeSpan? LightOffDuration { get; set; } 46 | 47 | /// 48 | internal protected override LightSettings CopyAndValidate() 49 | { 50 | //copy 51 | var copy = new LightSettings() 52 | { 53 | Color = Color?.CopyAndValidate(), 54 | LightOffDuration = LightOffDuration, 55 | LightOnDuration = LightOnDuration 56 | }; 57 | 58 | return copy; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Utils/Validator.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Text.RegularExpressions; 19 | using System.Linq; 20 | 21 | namespace AGConnectAdmin.Utils 22 | { 23 | internal static class Validator 24 | { 25 | private static Regex COLOR_PATTERN = new Regex("^#[0-9a-f]{6}$", RegexOptions.IgnoreCase); 26 | 27 | internal static bool IsUrl(string url) 28 | { 29 | Uri uri; 30 | if (Uri.TryCreate(url, UriKind.Absolute, out uri)) 31 | { 32 | return uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps; 33 | } 34 | return false; 35 | } 36 | 37 | internal static bool IsHttpsUrl(string url) 38 | { 39 | Uri uri; 40 | if (Uri.TryCreate(url, UriKind.Absolute, out uri)) 41 | { 42 | return uri.Scheme == Uri.UriSchemeHttps; 43 | } 44 | return false; 45 | } 46 | 47 | internal static bool IsColor(string color) 48 | { 49 | return COLOR_PATTERN.Match(color).Success; 50 | } 51 | 52 | internal static bool IsInRange(T value, T min, T max) where T : IComparable 53 | { 54 | if (min.CompareTo(max) == 1) 55 | { 56 | throw new ArgumentException("The specified range is not correct, maximum must greater than or equal to minimum."); 57 | } 58 | return min.CompareTo(value) <= 0 && max.CompareTo(value) >= 0; 59 | } 60 | 61 | internal static bool IsAllInRange(IEnumerable values, T min, T max) where T : IComparable 62 | { 63 | return values.All(i => IsInRange(i, min, max)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/AppOptions.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | namespace AGConnectAdmin 17 | { 18 | /// 19 | /// Configurable options that can be specified when creating a . 20 | /// 21 | public sealed class AppOptions 22 | { 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | public AppOptions() 27 | { 28 | LoginUri = "https://oauth-login.cloud.huawei.com/oauth2/v3/token"; 29 | ApiBaseUri = "https://push-api.cloud.huawei.com/v1"; 30 | } 31 | 32 | internal AppOptions(AppOptions options) 33 | { 34 | this.LoginUri = options.LoginUri; 35 | this.ApiBaseUri = options.ApiBaseUri; 36 | this.ClientId = options.ClientId; 37 | this.ClientSecret = options.ClientSecret; 38 | } 39 | 40 | internal string GetApiUri() 41 | { 42 | return ApiBaseUri + string.Format("/{0}/messages:send", ClientId) ; 43 | } 44 | 45 | /// 46 | /// Gets or sets the login url that use to request access token, it's optional. 47 | /// 48 | public string LoginUri { get; set; } 49 | 50 | /// 51 | /// Gets or sets the API base path, it's optional. 52 | /// This property is optional. 53 | /// 54 | public string ApiBaseUri { get; set; } 55 | 56 | /// 57 | /// Gets or sets the APP ID from AGC. 58 | /// 59 | public string ClientId { get; set; } 60 | 61 | /// 62 | /// Gets orset the App Secret from AGC. 63 | /// 64 | public string ClientSecret { get; set; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/BadgeNotification.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class 16 | */ 17 | 18 | using AGConnectAdmin.Utils; 19 | using Newtonsoft.Json; 20 | using System; 21 | 22 | namespace AGConnectAdmin.Messaging 23 | { 24 | /// 25 | /// Represents the badge notification options that can be included in a 26 | /// . 27 | /// 28 | public class BadgeNotification : MessagePart 29 | { 30 | /// 31 | /// Gets or sets the increase number of the click badge notification. 32 | /// 33 | /// The number must between 0 to 100 34 | [JsonProperty("add_num")] 35 | public int? AddNum { get; set; } 36 | 37 | /// 38 | /// Gets or sets the number of the click badge notification. 39 | /// 40 | /// The number must between 0 to 100 41 | [JsonProperty("set_num")] 42 | public int? SetNum { get; set; } 43 | 44 | /// 45 | /// Gets or sets the class of the click badge notification. 46 | /// 47 | [JsonProperty("class")] 48 | public string Class { get; set; } 49 | 50 | /// 51 | internal protected override BadgeNotification CopyAndValidate() 52 | { 53 | var copy = new BadgeNotification() 54 | { 55 | AddNum = this.AddNum, 56 | SetNum = this.SetNum, 57 | Class = this.Class, 58 | }; 59 | 60 | if(AddNum != null && !Validator.IsInRange(AddNum.Value, 0, 100)) 61 | { 62 | throw new ArgumentException("AddNum must be within [0, 100]."); 63 | } 64 | 65 | if (SetNum != null && !Validator.IsInRange(SetNum.Value, 0, 100)) 66 | { 67 | throw new ArgumentException("SetNum must be within [0, 100]."); 68 | } 69 | 70 | return copy; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29025.244 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AGConnectAdmin", "AGConnectAdmin\AGConnectAdmin.csproj", "{B4BD90E7-D50F-4962-AEAD-9E067C38ECE1}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AGConnectAdmin.Tests", "AGConnectAdmin.Tests\AGConnectAdmin.Tests.csproj", "{87F67C2E-1F28-4371-9894-E09D819EB04B}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AGConnectAdmin.Examples", "AGConnectAdmin.Examples\AGConnectAdmin.Examples.csproj", "{72BE72BF-0248-4AE1-A662-0F326DE9F567}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{196D2272-7E6C-488A-A8B9-6D05FD9CF3EC}" 13 | ProjectSection(SolutionItems) = preProject 14 | .editorconfig = .editorconfig 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Proxy|Any CPU = Proxy|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {B4BD90E7-D50F-4962-AEAD-9E067C38ECE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {B4BD90E7-D50F-4962-AEAD-9E067C38ECE1}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {B4BD90E7-D50F-4962-AEAD-9E067C38ECE1}.Proxy|Any CPU.ActiveCfg = Proxy|Any CPU 27 | {B4BD90E7-D50F-4962-AEAD-9E067C38ECE1}.Proxy|Any CPU.Build.0 = Proxy|Any CPU 28 | {B4BD90E7-D50F-4962-AEAD-9E067C38ECE1}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {B4BD90E7-D50F-4962-AEAD-9E067C38ECE1}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {87F67C2E-1F28-4371-9894-E09D819EB04B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {87F67C2E-1F28-4371-9894-E09D819EB04B}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {87F67C2E-1F28-4371-9894-E09D819EB04B}.Proxy|Any CPU.ActiveCfg = Debug|Any CPU 33 | {87F67C2E-1F28-4371-9894-E09D819EB04B}.Proxy|Any CPU.Build.0 = Debug|Any CPU 34 | {87F67C2E-1F28-4371-9894-E09D819EB04B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {87F67C2E-1F28-4371-9894-E09D819EB04B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {72BE72BF-0248-4AE1-A662-0F326DE9F567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {72BE72BF-0248-4AE1-A662-0F326DE9F567}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {72BE72BF-0248-4AE1-A662-0F326DE9F567}.Proxy|Any CPU.ActiveCfg = Debug|Any CPU 39 | {72BE72BF-0248-4AE1-A662-0F326DE9F567}.Proxy|Any CPU.Build.0 = Debug|Any CPU 40 | {72BE72BF-0248-4AE1-A662-0F326DE9F567}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {72BE72BF-0248-4AE1-A662-0F326DE9F567}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {ABC1FD25-D921-4EB8-90DF-67FCBF0FC1F9} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # 华为推送服务服务端C#示例代码 2 | [English](README.md) | 中文 3 | 4 | ## 目录 5 | * [简介](#简介) 6 | * [安装](#安装) 7 | * [配置](#配置) 8 | * [环境要求](#环境要求) 9 | * [示例代码](#示例代码) 10 | * [技术支持](#技术支持) 11 | * [授权许可](#授权许可) 12 | 13 | ## 简介 14 | 15 | C#示例代码对华为推送服务(HUAWEI Push Kit)服务端接口进行封装,供您参考使用。 16 | 17 | 示例代码目录结构如下: 18 | | 文件夹 | 说明 | 19 | | ------------ | ----------- | 20 | |AGConnectAdmin|封装服务端接口的类库| 21 | |AGConnectAdmin.Examples|类库使用示例| 22 | 23 | 示例代码中的主要的类定义如下: 24 | | 类名 | 说明 | 25 | | ----------- | ----------- | 26 | |AppOptions|应用相关配置| 27 | |AGConnectApp|应用| 28 | |AGConnectMessaging|推送服务相关接口的调用方法| 29 | |Message|消息体| 30 | 31 | ## 安装 32 | 33 | 1. 解压示例代码。 34 | 2. 将解压后的AGConnectAdmin复制到你的Visual Studio Solution中适当的位置,在你的应用工程中引用对应的程序集即可。 35 | 3. 参考示例代码来使用AGConnectAdmin中的类。 36 | 37 | ## 配置 38 | 39 | 以下描述了AppOptions类的相关参数。 40 | 41 | | 参数 | 说明 | 42 | | ----------- | ----------- | 43 | |ClientId|应用ID,从应用信息中获取| 44 | |ClientSecret|应用访问密钥,从应用信息中获取| 45 | |LoginUri|华为OAuth 2.0获取token的地址。详情请参见[基于OAuth 2.0开放鉴权-客户端模式](https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/oauth2-0000001212610981#section128682386159?ha_source=hms1)。| 46 | |ApiBaseUri|推送服务的访问地址。详情请参见[推送服务-下行消息](https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/android-server-dev-0000001050040110?ha_source=hms1)。| 47 | 48 | ## 环境要求 49 | 50 | 示例代码工程需要使用Visual Studio 2017或以上版本的开发工具打开,类库提供以下种框架版本: 51 | 52 | - .NET Framework 4.5以上 53 | - .NET Standard 2.0以上 54 | 55 | ## 示例代码 56 | 57 | AGConnectAdmin.Examples提供所有示例代码及相应功能。 58 | 59 | 1. 发送Android透传消息。文件目录:[SendDataMessage.cs](src/AGConnectAdmin.Examples/Example.SendDataMessage.cs) 60 | 61 | 2. 发送Android通知栏消息。文件目录:[SendAndroidMessage.cs](src/AGConnectAdmin.Examples/Example.SendAndroidMessage.cs) 62 | 63 | 3. 基于主题发送消息。文件目录:[SendTopicMessage.cs](src/AGConnectAdmin.Examples/Example.SendTopicMessage.cs) 64 | 65 | 4. 基于条件发送消息。文件目录:[SendConditionMessage.cs](src/AGConnectAdmin.Examples/Example.SendConditionMessage.cs) 66 | 67 | 5. 向华为快应用发送消息。文件目录:[SendInstanceAppMessage.cs](src/AGConnectAdmin.Examples/Example.SendInstanceAppMessage.cs) 68 | 69 | 6. 基于WebPush代理发送消息。文件目录:[SendWebpushMessage.cs](src/AGConnectAdmin.Examples/Example.SendWebpushMessage.cs) 70 | 71 | 7. 基于APNs代理发送消息。文件目录:[SendApnsMessage.cs](src/AGConnectAdmin.Examples/Example.SendApnsMessage.cs) 72 | 73 | 8. 发送测试消息。文件目录:[SendTestMessage.cs](src/AGConnectAdmin.Examples/Example.SendTestMessage.cs) 74 | 75 | 76 | ## 技术支持 77 | 如果您对HMS Core还处于评估阶段,可在[Reddit社区](https://www.reddit.com/r/HuaweiDevelopers/)获取关于HMS Core的最新讯息,并与其他开发者交流见解。 78 | 79 | 如果您对使用HMS示例代码有疑问,请尝试: 80 | - 开发过程遇到问题上[Stack Overflow](https://stackoverflow.com/questions/tagged/huawei-mobile-services?tab=Votes),在`huawei-mobile-services`标签下提问,有华为研发专家在线一对一解决您的问题。 81 | - 到[华为开发者论坛](https://developer.huawei.com/consumer/cn/forum/blockdisplay?fid=18?ha_source=hms1) HMS Core板块与其他开发者进行交流。 82 | 83 | 如果您在尝试示例代码中遇到问题,请向仓库提交[issue](https://github.com/HMS-Core/hms-push-serverdemo-csharp/issues),也欢迎您提交[Pull Request](https://github.com/HMS-Core/hms-push-serverdemo-csharp/pulls)。 84 | 85 | ## 授权许可 86 | 华为推送服务C#示例代码经过[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0)授权许可. 87 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/CriticalSound.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class 16 | */ 17 | 18 | using Newtonsoft.Json; 19 | using System; 20 | 21 | namespace AGConnectAdmin.Messaging 22 | { 23 | /// 24 | /// The sound configuration for APNs critical alerts. 25 | /// 26 | public class CriticalSound : MessagePart 27 | { 28 | /// 29 | /// Gets or sets a value indicating whether to set the critical alert flag on the sound 30 | /// configuration. 31 | /// 32 | [JsonIgnore] 33 | public bool Critical { get; set; } 34 | 35 | /// 36 | /// Gets or sets the name of the sound to be played. This should be a sound file in your 37 | /// app's main bundle or in the Library/Sounds folder of your app's container 38 | /// directory. Specify the string default to play the system sound. 39 | /// 40 | [JsonProperty("name")] 41 | public string Name { get; set; } 42 | 43 | /// 44 | /// Gets or sets the volume for the critical alert's sound. Must be a value between 0.0 45 | /// (silent) and 1.0 (full volume). 46 | /// 47 | [JsonProperty("volume")] 48 | public double? Volume { get; set; } 49 | 50 | /// 51 | /// Gets or sets the integer representation of the property, which 52 | /// is how APNs expects it. 53 | /// 54 | [JsonProperty("critical")] 55 | private int? CriticalInt 56 | { 57 | get 58 | { 59 | if (this.Critical) 60 | { 61 | return 1; 62 | } 63 | 64 | return null; 65 | } 66 | 67 | set 68 | { 69 | this.Critical = value == 1; 70 | } 71 | } 72 | 73 | /// 74 | internal protected override CriticalSound CopyAndValidate() 75 | { 76 | var copy = new CriticalSound() 77 | { 78 | Critical = this.Critical, 79 | Name = this.Name, 80 | Volume = this.Volume, 81 | }; 82 | if (string.IsNullOrEmpty(copy.Name)) 83 | { 84 | throw new ArgumentException("Name must be specified for CriticalSound"); 85 | } 86 | 87 | if (copy.Volume < 0 || copy.Volume > 1) 88 | { 89 | throw new ArgumentException("Volume of CriticalSound must be in the interval [0, 1]"); 90 | } 91 | 92 | return copy; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/WebpushConfig.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class and some properties 16 | */ 17 | 18 | using AGConnectAdmin.Utils; 19 | using Newtonsoft.Json; 20 | using System; 21 | using System.Collections.Generic; 22 | 23 | namespace AGConnectAdmin.Messaging 24 | { 25 | /// 26 | /// Represents Web push configuration of notification. 27 | /// 28 | public class WebpushConfig : MessagePart 29 | { 30 | /// 31 | /// Gets or sets the Webpush options included in the message. 32 | /// 33 | [JsonProperty("hms_options")] 34 | public WebpushHmsOptions HmsOptions { get; set; } 35 | 36 | /// 37 | /// Gets or sets the Webpush HTTP headers. Refer 38 | /// 39 | /// Webpush specification for supported headers. 40 | /// 41 | [JsonProperty("headers")] 42 | public IReadOnlyDictionary Headers { get; set; } 43 | 44 | /// 45 | /// Gets or sets the Webpush notification included in the message. 46 | /// 47 | [JsonProperty("notification")] 48 | public WebpushNotification Notification { get; set; } 49 | 50 | /// 51 | internal protected override WebpushConfig CopyAndValidate() 52 | { 53 | return new WebpushConfig() 54 | { 55 | Headers = this.Headers?.Copy(), 56 | Notification = this.Notification?.CopyAndValidate(), 57 | HmsOptions = this.HmsOptions?.CopyAndValidate(), 58 | }; 59 | } 60 | } 61 | 62 | /// 63 | /// Represents the Webpush-specific notification options. 64 | /// 65 | public sealed class WebpushHmsOptions : MessagePart 66 | { 67 | /// 68 | /// Gets or sets the link to open when the user clicks on the notification. 69 | /// 70 | [JsonProperty("link")] 71 | public string Link { get; set; } 72 | 73 | /// 74 | internal protected override WebpushHmsOptions CopyAndValidate() 75 | { 76 | var copy = new WebpushHmsOptions() 77 | { 78 | Link = this.Link, 79 | }; 80 | 81 | if (copy.Link != null) 82 | { 83 | if (!Validator.IsHttpsUrl(copy.Link)) 84 | { 85 | throw new ArgumentException("The link options should be a valid https url."); 86 | } 87 | } 88 | 89 | return copy; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/Convertors/TimeSpanJsonConverter.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | using System; 18 | 19 | namespace AGConnectAdmin.Messaging.Convertors 20 | { 21 | /// 22 | /// Convert TimeSpan to a string. 23 | /// 24 | public class TimeSpanJsonConverter : JsonConverter 25 | { 26 | /// 27 | public override bool CanConvert(Type objectType) 28 | { 29 | return typeof(TimeSpan).Equals(objectType) || typeof(TimeSpan?).Equals(objectType); 30 | } 31 | 32 | /// 33 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 34 | { 35 | if (reader.TokenType == JsonToken.Null) 36 | { 37 | return null; 38 | } 39 | if (reader.TokenType != JsonToken.String) 40 | { 41 | throw new JsonSerializationException("Only string value can be convert to TimeSpan."); 42 | } 43 | try 44 | { 45 | var value = reader.Value.ToString(); 46 | var segments = value.TrimEnd('s').Split('.'); 47 | var seconds = long.Parse(segments[0]); 48 | var timeSpan = TimeSpan.FromSeconds(seconds); 49 | if (segments.Length == 2) 50 | { 51 | var subsecondNanos = long.Parse(segments[1].TrimStart('0')); 52 | timeSpan = timeSpan.Add(TimeSpan.FromMilliseconds(subsecondNanos / 1e6)); 53 | } 54 | return timeSpan; 55 | } 56 | catch (Exception e) 57 | { 58 | throw new JsonSerializationException($"Convert value \"{reader.Value}\" to TimeSpan failed.", e); 59 | } 60 | } 61 | 62 | /// 63 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 64 | { 65 | if (value == null) 66 | { 67 | if (serializer.NullValueHandling == NullValueHandling.Include) 68 | { 69 | writer.WriteNull(); 70 | } 71 | return; 72 | } 73 | 74 | var timeSpan = value as TimeSpan?; 75 | var totalSeconds = timeSpan.Value.TotalSeconds; 76 | var seconds = (long)Math.Floor(totalSeconds); 77 | var subsecondNanos = (long)((totalSeconds - seconds) * 1e9); 78 | if (subsecondNanos > 0) 79 | { 80 | writer.WriteValue(string.Format("{0}.{1:D9}s", seconds, subsecondNanos)); 81 | } 82 | 83 | writer.WriteValue(string.Format("{0}s", seconds)); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/Messaging/ConvertorTest.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using AGConnectAdmin.Messaging.Convertors; 18 | using Newtonsoft.Json; 19 | using Xunit; 20 | 21 | namespace AGConnectAdmin.Tests 22 | { 23 | public class ConvertorTest 24 | { 25 | [Fact] 26 | public void SerializeTimeSpan() 27 | { 28 | string json = JsonConvert.SerializeObject(new TestObject1() 29 | { 30 | TTL = TimeSpan.FromSeconds(5) 31 | }); 32 | 33 | Assert.Equal("{\"TTL\":\"5s\"}", json); 34 | } 35 | 36 | [Fact] 37 | public void SerializeNullableTimeSpan() 38 | { 39 | JsonConvert.DefaultSettings = () => new JsonSerializerSettings() 40 | { 41 | NullValueHandling = NullValueHandling.Ignore 42 | }; 43 | 44 | string json1 = JsonConvert.SerializeObject(new TestObject2() 45 | { 46 | }); 47 | string json2 = JsonConvert.SerializeObject(new TestObject2() 48 | { 49 | TTL = TimeSpan.FromSeconds(5), 50 | TTLS = new TimeSpan[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5) } 51 | }); 52 | 53 | Assert.Equal("{\"TTL\":\"5s\"}", json2); 54 | } 55 | 56 | [Fact] 57 | public void NoConvertor() 58 | { 59 | string json = JsonConvert.SerializeObject(new TestObject3() 60 | { 61 | TTL = TimeSpan.FromSeconds(5) 62 | }); 63 | 64 | Assert.NotEqual("{\"TTL\":\"5s\"}", json); 65 | } 66 | 67 | [Fact] 68 | public void DeserializeTimeSpan() 69 | { 70 | TestObject1 to = JsonConvert.DeserializeObject("{\"TTL\":\"5s\"}"); 71 | Assert.Equal(5, to.TTL.TotalSeconds); 72 | } 73 | 74 | [Fact] 75 | public void DeserializeNullableTimeSpan() 76 | { 77 | var to1 = JsonConvert.DeserializeObject("{}"); 78 | var to2 = JsonConvert.DeserializeObject("{\"TTL\":\"5s\"}"); 79 | 80 | Assert.Null(to1.TTL); 81 | Assert.NotNull(to2.TTL); 82 | Assert.Equal(5, to2.TTL.Value.TotalSeconds); 83 | } 84 | 85 | class TestObject1 86 | { 87 | [JsonConverter(typeof(TimeSpanJsonConverter))] 88 | public TimeSpan TTL { get; set; } 89 | } 90 | 91 | class TestObject2 92 | { 93 | [JsonConverter(typeof(TimeSpanJsonConverter))] 94 | public TimeSpan? TTL { get; set; } 95 | 96 | [JsonConverter(typeof(TimeSpanJsonConverter))] 97 | public TimeSpan[] TTLS { get; set; } 98 | } 99 | 100 | class TestObject3 101 | { 102 | public TimeSpan? TTL { get; set; } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/LightColor.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Utils; 17 | using Newtonsoft.Json; 18 | using System; 19 | 20 | namespace AGConnectAdmin.Messaging 21 | { 22 | /// 23 | /// Represents an ARGB (alpha, red, green, blue) color. 24 | /// 25 | public sealed class LightColor : MessagePart 26 | { 27 | /// 28 | /// Gets the alpha component value of this Color structure. 29 | /// 30 | [JsonProperty("alpha")] 31 | public double Alpha { get; private set; } 32 | 33 | /// 34 | /// Gets the red component value of this Color structure. 35 | /// 36 | [JsonProperty("red")] 37 | public double Red { get; private set; } 38 | 39 | /// 40 | /// Gets the green component value of this Color structure. 41 | /// 42 | [JsonProperty("green")] 43 | public double Green { get; private set; } 44 | 45 | /// 46 | /// Gets the blue component value of this Color structure. 47 | /// 48 | [JsonProperty("blue")] 49 | public double Blue { get; private set; } 50 | 51 | /// 52 | /// Creates a Color structure from the four ARGB component (alpha, red, green, and blue) values. 53 | /// 54 | /// The alpha value (from 0 to 1) 55 | /// The red value (from 0 to 1) 56 | /// The green value (from 0 to 1) 57 | /// The blue value (from 0 to 1) 58 | /// 59 | public static LightColor FromArbg(double alpha, double red, double green, double blue) 60 | { 61 | return new LightColor() 62 | { 63 | Alpha = alpha, 64 | Red = red, 65 | Green = green, 66 | Blue = blue 67 | }; 68 | } 69 | 70 | /// 71 | /// Creates a Color structure from the specified 8-bit color values (red, green, and blue). The alpha value is implicitly 1.0 (fully opaque). 72 | /// 73 | /// The red value (from 0 to 1) 74 | /// The green value (from 0 to 1) 75 | /// The blue value (from 0 to 1) 76 | /// 77 | public static LightColor FromArbg(double red, double green, double blue) 78 | { 79 | return FromArbg(1.0f, red, green, blue); 80 | } 81 | 82 | /// 83 | internal protected override LightColor CopyAndValidate() 84 | { 85 | if (!Validator.IsInRange(this.Red, 0, 1) || 86 | !Validator.IsInRange(this.Green, 0, 1) || 87 | !Validator.IsInRange(this.Blue, 0, 1)) 88 | { 89 | throw new ArgumentException("The value of red/blue/green color must be within [0,1]."); 90 | } 91 | 92 | return FromArbg(Alpha, Red, Green, Blue); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HMS Core Push Kit Sample Code (C#) 2 | English | [中文](README_ZH.md) 3 | ## Contents 4 | * [Introduction](#Introduction) 5 | * [Installation](#Installation) 6 | * [Configuration](#Configuration) 7 | * [Environment Requirements](#Environment-Requirements) 8 | * [Sample Code](#Sample-Code) 9 | * [Technical Support](#technical-support) 10 | * [License](#License) 11 | 12 | ## Introduction 13 | 14 | The sample code for C# encapsulates the server-side APIs of Push Kit, for your reference or direct use. 15 | 16 | The following table describes folders of C# sample code. 17 | | Folder| Description| 18 | | ------------ | ----------- | 19 | |AGConnectAdmin|Class library where Push Kit server APIs are encapsulated.| 20 | |AGConnectAdmin.Examples|Class library usage examples.| 21 | 22 | The following table describes main classes used in the sample code. 23 | | Class Name| Description| 24 | | ----------- | ----------- | 25 | |AppOptions|App-related configuration.| 26 | |AGConnectApp|App.| 27 | |AGConnectMessaging|Push Kit API calling methods.| 28 | |Message|Message body.| 29 | 30 | ## Installation 31 | 32 | 1. Decompress the sample code. 33 | 2. Copy **AGConnectAdmin** to a proper position in your Visual Studio solution and reference the corresponding assembly in your project. 34 | 3. Use the classes in **AGConnectAdmin** by referring to the sample code. 35 | 36 | ## Configuration 37 | 38 | The following table describes the parameters related to the **AppOptions** class. 39 | 40 | | Parameter| Description| 41 | | ----------- | ----------- | 42 | |ClientId|App ID, which is obtained from the app information.| 43 | |ClientSecret|App secret, which is obtained from the app information.| 44 | |LoginUri|URL for Huawei OAuth 2.0 to obtain a token. For details, please refer to [OAuth 2.0-based Authentication](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/oauth2-0000001212610981).| 45 | |ApiBaseUri|Access address of Push Kit. For details, please refer to [Downlink Message Sending](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-server-dev-0000001050040110?ha_source=hms1).| 46 | 47 | ## Environment Requirements 48 | 49 | The demo projects need to be opened using Visual Studio 2017 or a later version. The following framework versions are supported: 50 | 51 | - .NET Framework 4.5 or later 52 | - .NET Standard 2.0 or later 53 | 54 | ## Sample Code 55 | 56 | **AGConnectAdmin.Examples** provides all sample code and corresponding functions. 57 | 58 | 1. Send an Android data message. Code location: [SendDataMessage.cs](src/AGConnectAdmin.Examples/Example.SendDataMessage.cs) 59 | 60 | 2. Send an Android notification message. Code location: [SendAndroidMessage.cs](src/AGConnectAdmin.Examples/Example.SendAndroidMessage.cs) 61 | 62 | 3. Send a message by topic.Code location: [SendTopicMessage.cs](src/AGConnectAdmin.Examples/Example.SendTopicMessage.cs) 63 | 64 | 4. Send a message by conditions. Code location: [SendConditionMessage.cs](src/AGConnectAdmin.Examples/Example.SendConditionMessage.cs) 65 | 66 | 5. Send a message to a Huawei quick app. Code location: [SendInstanceAppMessage.cs](src/AGConnectAdmin.Examples/Example.SendInstanceAppMessage.cs) 67 | 68 | 6. Send a message through the WebPush agent. Code location: [SendWebpushMessage.cs](src/AGConnectAdmin.Examples/Example.SendWebpushMessage.cs) 69 | 70 | 7. Send a message through the APNs agent. Code location: [SendApnsMessage.cs](src/AGConnectAdmin.Examples/Example.SendApnsMessage.cs) 71 | 72 | 8. Send a test message. Code location: [SendTestMessage.cs](src/AGConnectAdmin.Examples/Example.SendTestMessage.cs) 73 | 74 | 75 | ## Technical Support 76 | You can visit the [Reddit community](https://www.reddit.com/r/HuaweiDevelopers/) to obtain the latest information about HMS Core and communicate with other developers. 77 | 78 | If you have any questions about the sample code, try the following: 79 | - Visit [Stack Overflow](https://stackoverflow.com/questions/tagged/huawei-mobile-services?tab=Votes), submit your questions, and tag them with `huawei-mobile-services`. Huawei experts will answer your questions. 80 | - Visit the HMS Core section in the [HUAWEI Developer Forum](https://forums.developer.huawei.com/forumPortal/en/home?fid=0101187876626530001?ha_source=hms1) and communicate with other developers. 81 | 82 | If you encounter any issues when using the sample code, submit your [issues](https://github.com/HMS-Core/hms-push-serverdemo-csharp/issues) or submit a [pull request](https://github.com/HMS-Core/hms-push-serverdemo-csharp/pulls). 83 | 84 | ## License 85 | The sample code is licensed under [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). 86 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/AndroidConfig.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class and some properties 16 | */ 17 | 18 | using System; 19 | using AGConnectAdmin.Messaging.Convertors; 20 | using Newtonsoft.Json; 21 | using Newtonsoft.Json.Converters; 22 | 23 | namespace AGConnectAdmin.Messaging 24 | { 25 | /// 26 | /// Represents the Android-specific options that can be included in a . 27 | /// 28 | public class AndroidConfig : MessagePart 29 | { 30 | /// 31 | /// Gets or sets a collapse key for the message. Collapse key serves as an identifier for a 32 | /// group of messages that can be collapsed, so that only the last message gets sent when 33 | /// delivery can be resumed. A maximum of 4 different collapse keys may be active at any 34 | /// given time. 35 | /// 36 | [JsonProperty("collapse_key")] 37 | public int? CollapseKey { get; set; } 38 | 39 | /// 40 | /// Gets or sets the priority of the message. 41 | /// 42 | [JsonProperty("urgency")] 43 | [JsonConverter(typeof(StringEnumConverter))] 44 | public UrgencyPriority? Urgency { get; set; } 45 | 46 | /// 47 | /// Gets or sets the Category, eg. "PLAY_VOICE" 48 | /// 49 | [JsonProperty("category")] 50 | public string Category { get; set; } 51 | 52 | /// 53 | /// Gets or sets the time-to-live duration of the message. 54 | /// 55 | [JsonProperty("ttl")] 56 | [JsonConverter(typeof(TimeSpanJsonConverter))] 57 | public TimeSpan? TTL { get; set; } 58 | 59 | /// 60 | /// Gets or sets the BI-tag of the message. 61 | /// 62 | [JsonProperty("bi_tag")] 63 | public string BITag { get; set; } 64 | 65 | /// 66 | /// Gets or sets the fast app target of the message. 67 | /// 68 | [JsonProperty("fast_app_target")] 69 | public FastAppTarget? FastAppTarget { get; set; } 70 | 71 | /// 72 | /// Gets or sets the custom data of the message. 73 | /// 74 | [JsonProperty("data")] 75 | public string Data { get; set; } 76 | 77 | /// 78 | /// Gets or sets the Android notification to be included in the message. 79 | /// 80 | [JsonProperty("notification")] 81 | public AndroidNotification Notification { get; set; } 82 | 83 | /// 84 | internal protected override AndroidConfig CopyAndValidate() 85 | { 86 | // copy 87 | var copy = new AndroidConfig() 88 | { 89 | CollapseKey = this.CollapseKey, 90 | Urgency = this.Urgency, 91 | TTL = this.TTL, 92 | Category = this.Category, 93 | BITag = this.BITag, 94 | FastAppTarget = this.FastAppTarget, 95 | Data = this.Data, 96 | Notification = this.Notification?.CopyAndValidate() 97 | }; 98 | 99 | // validate 100 | var totalSeconds = copy.TTL?.TotalSeconds ?? 0; 101 | if (totalSeconds < 0) 102 | { 103 | throw new ArgumentException("TTL must not be negative."); 104 | } 105 | 106 | return copy; 107 | } 108 | } 109 | 110 | /// 111 | /// Priority that can be set on an . 112 | /// 113 | public enum UrgencyPriority 114 | { 115 | /// 116 | /// High priority message. 117 | /// 118 | HIGH, 119 | 120 | /// 121 | /// Normal priority message. 122 | /// 123 | NORMAL 124 | } 125 | 126 | /// 127 | /// Fast app target of . 128 | /// 129 | public enum FastAppTarget 130 | { 131 | /// 132 | /// Development target 133 | /// 134 | Development = 1, 135 | 136 | /// 137 | /// Production target 138 | /// 139 | Production = 2, 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/Message.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class and some properties 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Linq; 21 | using Newtonsoft.Json; 22 | 23 | namespace AGConnectAdmin.Messaging 24 | { 25 | /// 26 | /// Represents a message that can be sent via AGConnect Cloud Messaging. Contains payload 27 | /// information as well as the recipient information. The recipient information must be 28 | /// specified by setting exactly one of the , or 29 | /// fields. 30 | /// 31 | public class Message : MessagePart 32 | { 33 | /// 34 | /// Gets or sets the data to be included in the message. 35 | /// 36 | [JsonProperty("data")] 37 | public string Data { get; set; } 38 | 39 | /// 40 | /// Gets or sets the notification information to be included in the message. 41 | /// 42 | [JsonProperty("notification")] 43 | public Notification Notification { get; set; } 44 | 45 | /// 46 | /// Gets or sets the Android-specific information to be included in the message. 47 | /// 48 | [JsonProperty("android")] 49 | public AndroidConfig Android { get; set; } 50 | 51 | /// 52 | /// Gets or sets the APNs-specific information to be included in the message. 53 | /// 54 | [JsonProperty("apns")] 55 | public ApnsConfig Apns { get; set; } 56 | 57 | /// 58 | /// Gets or sets the Webpush-specific information to be included in the message. 59 | /// 60 | [JsonProperty("webpush")] 61 | public WebpushConfig Webpush { get; set; } 62 | 63 | /// 64 | /// Gets or sets the registration tokens of the devices to which the message should be sent. 65 | /// 66 | [JsonProperty("token")] 67 | public IReadOnlyList Token { get; set; } 68 | 69 | /// 70 | /// Gets or sets the name of the AGC messaging topic to which the message should be sent. 71 | /// 72 | [JsonProperty("topic")] 73 | public string Topic { get; set; } 74 | 75 | /// 76 | /// Gets or sets the condition to which the message should be sent. Must be a valid 77 | /// condition string such as "'foo' in topics". 78 | /// 79 | [JsonProperty("condition")] 80 | public string Condition { get; set; } 81 | 82 | /// 83 | internal protected override Message CopyAndValidate() 84 | { 85 | // copy 86 | var copy = new Message() 87 | { 88 | Data = Data, 89 | Notification = Notification?.CopyAndValidate(), 90 | Android = Android?.CopyAndValidate(), 91 | Apns = Apns?.CopyAndValidate(), 92 | Webpush = Webpush?.CopyAndValidate(), 93 | Token = Token?.ToList(), 94 | Topic = Topic, 95 | Condition = Condition, 96 | }; 97 | 98 | // validate 99 | var tokenList = Token != null ? new List(Token) : new List(); 100 | var nonnullTokens = tokenList.FindAll((target) => !string.IsNullOrEmpty(target)); 101 | 102 | var fieldList = new List() 103 | { 104 | copy.Topic, copy.Condition, 105 | }; 106 | var nonnullFields = fieldList.FindAll((target) => !string.IsNullOrEmpty(target)); 107 | 108 | if (nonnullFields.Count > 1) 109 | { 110 | throw new ArgumentException("Exactly one of Token, Topic or Condition is required."); 111 | } 112 | 113 | if (nonnullFields.Count == 1 && nonnullTokens.Count > 0) 114 | { 115 | throw new ArgumentException("Exactly one of Token, Topic or Condition is required."); 116 | } 117 | 118 | if (nonnullFields.Count == 0 && nonnullTokens.Count == 0) 119 | { 120 | throw new ArgumentException("Exactly one of Token, Topic or Condition is required."); 121 | } 122 | 123 | return copy; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/Messaging/MessagingTest.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using AGConnectAdmin.Messaging; 17 | using System.Collections.Generic; 18 | using System.Threading; 19 | using System.Threading.Tasks; 20 | using Xunit; 21 | 22 | namespace AGConnectAdmin.Tests 23 | { 24 | public class MessagingTest 25 | { 26 | static MessagingTest() 27 | { 28 | TestUtils.GlobalInit(); 29 | } 30 | 31 | [Fact] 32 | public async Task SendMessageCancel() 33 | { 34 | var app = AGConnectApp.DefaultInstance; 35 | var messaging = AGConnectMessaging.DefaultInstance; 36 | var canceller = new CancellationTokenSource(); 37 | canceller.Cancel(); 38 | await Assert.ThrowsAsync( 39 | async () => await messaging.SendAsync( 40 | new Message() { Topic = "test-topic" }, canceller.Token)); 41 | } 42 | 43 | [Fact] 44 | public async Task SendTopicMessage() 45 | { 46 | var app = AGConnectApp.Create(new AppOptions() 47 | { 48 | ClientId = "11111111", 49 | ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 50 | }); 51 | 52 | var msg = AGConnectMessaging.GetMessaging(app); 53 | 54 | await msg.SendAsync(new Message() 55 | { 56 | Notification = new Notification() 57 | { 58 | Title = "Test Message", 59 | Body = "Detail Message" 60 | }, 61 | Topic = "News", 62 | Token = new string[] { "yyyyyyyyyyyyyyyyyy" } 63 | }); 64 | } 65 | 66 | [Fact] 67 | public async Task SendConditionMessage() 68 | { 69 | var app = AGConnectApp.Create(new AppOptions() 70 | { 71 | ClientId = "11111111", 72 | ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 73 | }); 74 | 75 | var msg = AGConnectMessaging.GetMessaging(app); 76 | 77 | await msg.SendAsync(new Message() 78 | { 79 | Notification = new Notification() 80 | { 81 | Title = "Test Message", 82 | Body = "Detail Message", 83 | }, 84 | Android = new AndroidConfig() 85 | { 86 | Notification = new AndroidNotification() 87 | { 88 | ClickAction = ClickAction.OpenUrl("http://example.com") 89 | } 90 | }, 91 | Token = new string[] { "yyyyyyyyyyyyyyyyyy" } 92 | }); 93 | } 94 | 95 | [Fact] 96 | public async Task SubscribeTopic() 97 | { 98 | var app = AGConnectApp.Create(new AppOptions() 99 | { 100 | ClientId = "11111111", 101 | ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 102 | }); 103 | 104 | var msg = AGConnectMessaging.GetMessaging(app); 105 | var tokens = new List() { "sdjlfjiwekfnskdjfksdfjskdfjsdf" }; 106 | var topic = "News"; 107 | await msg.SubscribeToTopicAsync(tokens.AsReadOnly(), topic); 108 | } 109 | 110 | [Fact] 111 | public async Task UnsubscribeTopic() 112 | { 113 | var app = AGConnectApp.Create(new AppOptions() 114 | { 115 | ClientId = "11111111", 116 | ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 117 | }); 118 | 119 | var msg = AGConnectMessaging.GetMessaging(app); 120 | var tokens = new List() { "sdjlfjiwekfnskdjfksdfjskdfjsdf" }; 121 | var topic = "News"; 122 | await msg.UnsubscribeFromTopicAsync(tokens.AsReadOnly(), topic); 123 | } 124 | 125 | [Fact] 126 | public async Task GetTopicList() 127 | { 128 | var app = AGConnectApp.Create(new AppOptions() 129 | { 130 | ClientId = "11111111", 131 | ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 132 | }); 133 | 134 | var msg = AGConnectMessaging.GetMessaging(app); 135 | var token = "sdjlfjiwekfnskdjfksdfjskdfjsdf"; 136 | TopicListResponse resp = await msg.GetTopicListAsync(token); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/ClickAction.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class 16 | */ 17 | 18 | using AGConnectAdmin.Utils; 19 | using Newtonsoft.Json; 20 | using System; 21 | 22 | namespace AGConnectAdmin.Messaging 23 | { 24 | /// 25 | /// Represents the click action options that can be included in a 26 | /// . 27 | /// 28 | public class ClickAction : MessagePart 29 | { 30 | /// 31 | /// Gets or sets the type of the click action. 32 | /// 33 | [JsonProperty("type")] 34 | private int Type { get; set; } 35 | 36 | /// 37 | /// Gets or sets the intent of the click action. 38 | /// 39 | [JsonProperty("intent")] 40 | private string Intent { get; set; } 41 | 42 | /// 43 | /// Gets or sets the URL of the click action. 44 | /// 45 | [JsonProperty("url")] 46 | private string URL { get; set; } 47 | 48 | /// 49 | /// Gets or sets the rich resource of the click action. 50 | /// 51 | [JsonProperty("rich_resource")] 52 | private string RichResource { get; set; } 53 | 54 | /// 55 | /// Gets or sets the action of the click event. 56 | /// 57 | [JsonProperty("action")] 58 | private string Action { get; set; } 59 | 60 | internal ClickAction() { } 61 | 62 | /// 63 | /// Custom intent 64 | /// 65 | /// 66 | /// 67 | public static ClickAction CustomIntent(string intent) 68 | { 69 | if (string.IsNullOrEmpty(intent)) 70 | { 71 | return OpenApp(); 72 | } 73 | 74 | return new ClickAction() 75 | { 76 | Type = 1, 77 | Intent = intent, 78 | }; 79 | } 80 | 81 | /// 82 | /// Custom action 83 | /// 84 | /// 85 | /// 86 | public static ClickAction CustomAction(string action) 87 | { 88 | if (string.IsNullOrEmpty(action)) 89 | { 90 | return OpenApp(); 91 | } 92 | 93 | return new ClickAction() 94 | { 95 | Type = 1, 96 | Action = action, 97 | }; 98 | } 99 | 100 | /// 101 | /// Open specified url 102 | /// 103 | /// Url to be opened, must be https 104 | /// 105 | public static ClickAction OpenUrl(string url) 106 | { 107 | return new ClickAction() 108 | { 109 | Type = 2, 110 | URL = url, 111 | }; 112 | } 113 | 114 | 115 | /// 116 | /// Open app 117 | /// 118 | public static ClickAction OpenApp() 119 | { 120 | return new ClickAction() 121 | { 122 | Type = 3 123 | }; 124 | } 125 | 126 | /// 127 | /// Open rich resource 128 | /// 129 | /// Url of zip file of rich resource, must be https 130 | /// 131 | public static ClickAction OpenRichResource(string resourceUrl) 132 | { 133 | return new ClickAction() 134 | { 135 | Type = 4, 136 | RichResource = resourceUrl, 137 | }; 138 | } 139 | 140 | /// 141 | internal protected override ClickAction CopyAndValidate() 142 | { 143 | var copy = new ClickAction() 144 | { 145 | Type = this.Type, 146 | Intent = this.Intent, 147 | URL = this.URL, 148 | RichResource = this.RichResource, 149 | Action = this.Action 150 | }; 151 | 152 | if (copy.URL != null && !Validator.IsHttpsUrl(copy.URL)) 153 | { 154 | throw new ArgumentException("Click action url must be a https url."); 155 | } 156 | 157 | if (copy.RichResource != null && !Validator.IsHttpsUrl(copy.RichResource)) 158 | { 159 | throw new ArgumentException("Rich resource url must be a https url."); 160 | } 161 | 162 | return copy; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/ApnsConfig.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class and some properties 16 | */ 17 | 18 | using Newtonsoft.Json; 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | 23 | namespace AGConnectAdmin.Messaging 24 | { 25 | /// 26 | /// Represents the APNS-specific options that can be included in a . Refer 27 | /// to 28 | /// APNs documentation for various headers and payload fields supported by APNS. 29 | /// 30 | public class ApnsConfig : MessagePart 31 | { 32 | private ApnsPayload payload = new ApnsPayload(); 33 | 34 | /// 35 | /// Gets or sets the FCM options to be included in the message. 36 | /// 37 | [JsonProperty("hms_options")] 38 | public ApnsHmsOptions HmsOptions { get; set; } = new ApnsHmsOptions(); 39 | 40 | /// 41 | /// Gets or sets the APNs headers. Refer to 42 | /// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html 43 | /// 44 | [JsonProperty("headers")] 45 | public IReadOnlyDictionary Headers { get; set; } 46 | 47 | /// 48 | /// Gets or sets the aps dictionary to be included in the APNs payload. 49 | /// 50 | [JsonIgnore] 51 | public Aps Aps 52 | { 53 | get 54 | { 55 | return this.Payload.Aps; 56 | } 57 | 58 | set 59 | { 60 | this.Payload.Aps = value; 61 | } 62 | } 63 | 64 | /// 65 | /// Gets or sets a collection of arbitrary key-value data that will be included in the APNs 66 | /// payload. 67 | /// 68 | [JsonIgnore] 69 | public IDictionary CustomData 70 | { 71 | get 72 | { 73 | return this.Payload.CustomData; 74 | } 75 | 76 | set 77 | { 78 | this.Payload.CustomData = value; 79 | } 80 | } 81 | 82 | /// 83 | /// Gets or sets the APNs payload as accepted by the FCM backend servers. 84 | /// 85 | [JsonProperty("payload")] 86 | private ApnsPayload Payload 87 | { 88 | get 89 | { 90 | if (this.payload.Aps != null && this.payload.CustomData?.ContainsKey("aps") == true) 91 | { 92 | throw new ArgumentException("Multiple specifications for ApnsConfig key: aps"); 93 | } 94 | 95 | return this.payload; 96 | } 97 | 98 | set 99 | { 100 | this.payload = value; 101 | } 102 | } 103 | 104 | /// 105 | internal protected override ApnsConfig CopyAndValidate() 106 | { 107 | var copy = new ApnsConfig() 108 | { 109 | Headers = this.Headers?.Copy(), 110 | Payload = this.Payload.CopyAndValidate(), 111 | HmsOptions = this.HmsOptions.CopyAndValidate(), 112 | }; 113 | return copy; 114 | } 115 | 116 | /// 117 | /// The APNs payload object as expected by the FCM backend service. 118 | /// 119 | private class ApnsPayload 120 | { 121 | [JsonProperty("aps")] 122 | internal Aps Aps { get; set; } 123 | 124 | [JsonExtensionData] 125 | internal IDictionary CustomData { get; set; } 126 | 127 | /// 128 | /// Copies this APNs payload, and validates the content of it to ensure that it can be 129 | /// serialized into the JSON format expected by the FCM service. 130 | /// 131 | internal ApnsPayload CopyAndValidate() 132 | { 133 | var copy = new ApnsPayload() 134 | { 135 | CustomData = this.CustomData?.ToDictionary(e => e.Key, e => e.Value), 136 | }; 137 | var aps = this.Aps; 138 | if (aps == null && copy.CustomData?.ContainsKey("aps") == false) 139 | { 140 | throw new ArgumentException("Aps dictionary is required in ApnsConfig"); 141 | } 142 | 143 | copy.Aps = aps?.CopyAndValidate(); 144 | return copy; 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/ApsAlert.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class 16 | */ 17 | 18 | using Newtonsoft.Json; 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | 23 | namespace AGConnectAdmin.Messaging 24 | { 25 | /// 26 | /// Represents the 27 | /// alert property that can be included in the aps dictionary of an APNs 28 | /// payload. 29 | /// 30 | public class ApsAlert : MessagePart 31 | { 32 | /// 33 | /// Gets or sets the title of the alert. When provided, overrides the title set via 34 | /// . 35 | /// 36 | [JsonProperty("title")] 37 | public string Title { get; set; } 38 | 39 | /// 40 | /// Gets or sets the subtitle of the alert. 41 | /// 42 | [JsonProperty("subtitle")] 43 | public string Subtitle { get; set; } 44 | 45 | /// 46 | /// Gets or sets the body of the alert. When provided, overrides the body set via 47 | /// . 48 | /// 49 | [JsonProperty("body")] 50 | public string Body { get; set; } 51 | 52 | /// 53 | /// Gets or sets the launch image for the notification action. 54 | /// 55 | [JsonProperty("launch-image")] 56 | public string LaunchImage { get; set; } 57 | 58 | /// 59 | /// Gets or sets the key of the title string in the app's string resources to use to 60 | /// localize the title text. 61 | /// 62 | [JsonProperty("title-loc-key")] 63 | public string TitleLocKey { get; set; } 64 | 65 | /// 66 | /// Gets or sets the resource key strings that will be used in place of the format 67 | /// specifiers in . 68 | /// 69 | [JsonProperty("title-loc-args")] 70 | public IEnumerable TitleLocArgs { get; set; } 71 | 72 | /// 73 | /// Gets or sets the key of the subtitle string in the app's string resources to use to 74 | /// localize the subtitle text. 75 | /// 76 | [JsonProperty("subtitle-loc-key")] 77 | public string SubtitleLocKey { get; set; } 78 | 79 | /// 80 | /// Gets or sets the resource key strings that will be used in place of the format 81 | /// specifiers in . 82 | /// 83 | [JsonProperty("subtitle-loc-args")] 84 | public IEnumerable SubtitleLocArgs { get; set; } 85 | 86 | /// 87 | /// Gets or sets the key of the body string in the app's string resources to use to 88 | /// localize the body text. 89 | /// 90 | [JsonProperty("loc-key")] 91 | public string LocKey { get; set; } 92 | 93 | /// 94 | /// Gets or sets the resource key strings that will be used in place of the format 95 | /// specifiers in . 96 | /// 97 | [JsonProperty("loc-args")] 98 | public IEnumerable LocArgs { get; set; } 99 | 100 | /// 101 | internal protected override ApsAlert CopyAndValidate() 102 | { 103 | var copy = new ApsAlert() 104 | { 105 | Title = this.Title, 106 | Subtitle = this.Subtitle, 107 | Body = this.Body, 108 | LocKey = this.LocKey, 109 | LocArgs = this.LocArgs?.ToList(), 110 | TitleLocKey = this.TitleLocKey, 111 | TitleLocArgs = this.TitleLocArgs?.ToList(), 112 | SubtitleLocKey = this.SubtitleLocKey, 113 | SubtitleLocArgs = this.SubtitleLocArgs?.ToList(), 114 | LaunchImage = this.LaunchImage, 115 | }; 116 | if (copy.TitleLocArgs?.Any() == true && string.IsNullOrEmpty(copy.TitleLocKey)) 117 | { 118 | throw new ArgumentException("TitleLocKey is required when specifying TitleLocArgs."); 119 | } 120 | 121 | if (copy.SubtitleLocArgs?.Any() == true && string.IsNullOrEmpty(copy.SubtitleLocKey)) 122 | { 123 | throw new ArgumentException("SubtitleLocKey is required when specifying SubtitleLocArgs."); 124 | } 125 | 126 | if (copy.LocArgs?.Any() == true && string.IsNullOrEmpty(copy.LocKey)) 127 | { 128 | throw new ArgumentException("LocKey is required when specifying LocArgs."); 129 | } 130 | 131 | return copy; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/AGConnectAdmin.Tests/Messaging/MessagingClientTest.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Threading; 19 | using System.Threading.Tasks; 20 | using AGConnectAdmin.Messaging; 21 | using Xunit; 22 | 23 | namespace AGConnectAdmin.Tests 24 | { 25 | public class MessagingClientTest 26 | { 27 | static MessagingClientTest() 28 | { 29 | TestUtils.GlobalInit(); 30 | } 31 | 32 | 33 | private Message GetMessage(string content) 34 | { 35 | var message = new Message() 36 | { 37 | Notification = new Notification() 38 | { 39 | Title = content, 40 | Body = "Body", 41 | }, 42 | Android = new AndroidConfig() 43 | { 44 | CollapseKey = -1, 45 | Urgency = UrgencyPriority.HIGH, 46 | TTL = TimeSpan.FromHours(1), 47 | BITag = "tag", 48 | FastAppTarget = FastAppTarget.Development, 49 | Notification = new AndroidNotification() 50 | { 51 | Title = "title", 52 | Body = "body", 53 | Icon = "icon", 54 | Color = "#AACCDD", 55 | Sound = "sound", 56 | Tag = "tag", 57 | ClickAction = ClickAction.CustomIntent("intent"), 58 | BodyLocKey = "key", 59 | BodyLocArgs = new List { "arg1", "arg2" }, 60 | TitleLocKey = "key", 61 | TitleLocArgs = new List { "arg1", "arg2" }, 62 | ChannelId = "channel", 63 | NotifySummary = "summary", 64 | Style = NotificationStyle.Default, 65 | AutoClear = 3000, 66 | NotifyId = 123, 67 | Group = "group", 68 | Badge = new BadgeNotification() 69 | { 70 | AddNum = 32, 71 | Class = "class" 72 | } 73 | }, 74 | }, 75 | Token = new List { "YOUR_REGISTRATION_TOKEN" } 76 | 77 | }; 78 | return message; 79 | } 80 | 81 | [Fact] 82 | public async Task SendSingle() 83 | { 84 | var requestId = await AGConnectMessaging.DefaultInstance.SendAsync(GetMessage("content"), dryRun: true); 85 | Assert.True(!string.IsNullOrEmpty(requestId)); 86 | } 87 | 88 | [Fact] 89 | public void SendMany() 90 | { 91 | List> tasks = new List>(); 92 | for (int i = 0; i < 100; i++) 93 | { 94 | var task = AGConnectMessaging.DefaultInstance.SendAsync(GetMessage("content " + i), dryRun: true); 95 | tasks.Add(task); 96 | } 97 | 98 | Task.WaitAll(tasks.ToArray()); 99 | 100 | foreach (var task in tasks) 101 | { 102 | Assert.True(!string.IsNullOrEmpty(task.Result)); 103 | } 104 | } 105 | 106 | [Fact] 107 | public void SendManyConcurrent() 108 | { 109 | List> tasks = new List>(); 110 | 111 | for (int i = 0; i < 100; i++) 112 | { 113 | var task = Task.Run(() => 114 | { 115 | return AGConnectMessaging.DefaultInstance.SendAsync(GetMessage("content " + i), dryRun: true); 116 | }); 117 | tasks.Add(task); 118 | } 119 | 120 | Task.WaitAll(tasks.ToArray()); 121 | 122 | foreach (var task in tasks) 123 | { 124 | Assert.True(!string.IsNullOrEmpty(task.Result)); 125 | } 126 | } 127 | 128 | [Fact] 129 | public void SendManyConcurrentAndDelayed() 130 | { 131 | List> tasks = new List>(); 132 | 133 | for (int i = 0; i < 100; i++) 134 | { 135 | var task = Task.Run(() => 136 | { 137 | Thread.Sleep(i); 138 | return AGConnectMessaging.DefaultInstance.SendAsync(GetMessage("content " + i), dryRun: true); 139 | }); 140 | tasks.Add(task); 141 | } 142 | 143 | Task.WaitAll(tasks.ToArray()); 144 | 145 | foreach (var task in tasks) 146 | { 147 | Assert.True(!string.IsNullOrEmpty(task.Result)); 148 | } 149 | } 150 | 151 | [Fact] 152 | public void SendManyConcurrentAndDelayed2() 153 | { 154 | List> tasks = new List>(); 155 | 156 | 157 | for (int i = 0; i < 100; i++) 158 | { 159 | Thread.Sleep(i); 160 | var task = Task.Run(() => 161 | { 162 | return AGConnectMessaging.DefaultInstance.SendAsync(GetMessage("content " + i), dryRun: true); 163 | }); 164 | tasks.Add(task); 165 | } 166 | 167 | Task.WaitAll(tasks.ToArray()); 168 | 169 | foreach (var task in tasks) 170 | { 171 | Assert.True(!string.IsNullOrEmpty(task.Result)); 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/WebpushNotification.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class and some properties 16 | */ 17 | 18 | using Newtonsoft.Json; 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | 23 | namespace AGConnectAdmin.Messaging 24 | { 25 | /// 26 | /// Represents the Webpush-specific notification options that can be included in a 27 | /// . Supports most standard options defined in the 28 | /// 29 | /// Web Notification specification. 30 | /// 31 | public class WebpushNotification: MessagePart 32 | { 33 | /// 34 | /// Gets or sets the title text of the notification. 35 | /// 36 | [JsonProperty("title")] 37 | public string Title { get; set; } 38 | 39 | /// 40 | /// Gets or sets the body text of the notification. 41 | /// 42 | [JsonProperty("body")] 43 | public string Body { get; set; } 44 | 45 | /// 46 | /// Gets or sets the URL to the icon of the notification. 47 | /// 48 | [JsonProperty("icon")] 49 | public string Icon { get; set; } 50 | 51 | /// 52 | /// Gets or sets the URL of an image to be displayed in the notification. 53 | /// 54 | [JsonProperty("image")] 55 | public string Image { get; set; } 56 | 57 | /// 58 | /// Gets or sets the URL of the image used to represent the notification when there is not 59 | /// enough space to display the notification itself. 60 | /// 61 | [JsonProperty("badge")] 62 | public string Badge { get; set; } 63 | 64 | /// 65 | /// Gets or sets the direction in which to display the notification. 66 | /// 67 | [JsonIgnore] 68 | public Direction? Direction { get; set; } 69 | 70 | /// 71 | /// Gets or sets the language of the notification. 72 | /// 73 | [JsonProperty("lang")] 74 | public string Language { get; set; } 75 | 76 | /// 77 | /// Gets or sets whether the user should be notified after a new notification replaces an 78 | /// old one. 79 | /// 80 | [JsonProperty("renotify")] 81 | public bool? Renotify { get; set; } 82 | 83 | /// 84 | /// Gets or sets whether the notification should remain active until the user clicks or 85 | /// dismisses it, rather than closing it automatically. 86 | /// 87 | [JsonProperty("requireInteraction")] 88 | public bool? RequireInteraction { get; set; } 89 | 90 | /// 91 | /// Gets or sets whether the notification should be silent. 92 | /// 93 | [JsonProperty("silent")] 94 | public bool? Silent { get; set; } 95 | 96 | /// 97 | /// Gets or sets an identifying tag for the notification. 98 | /// 99 | [JsonProperty("tag")] 100 | public string Tag { get; set; } 101 | 102 | /// 103 | /// Gets or sets the notification's timestamp value in milliseconds. 104 | /// 105 | [JsonProperty("timestamp")] 106 | public long? Timestamp { get; set; } 107 | 108 | /// 109 | /// Gets or sets a vibration pattern for the receiving device's vibration hardware. 110 | /// 111 | [JsonProperty("vibrate")] 112 | public IEnumerable Vibrate { get; set; } 113 | 114 | /// 115 | /// Gets or sets a collection of Webpush notification actions. 116 | /// 117 | [JsonProperty("actions")] 118 | public IEnumerable Actions { get; set; } 119 | 120 | /// 121 | /// Gets or sets some arbitrary data that will be included in the notification. 122 | /// 123 | [JsonProperty("data")] 124 | public object Data { get; set; } 125 | 126 | /// 127 | /// Gets or sets the custom key-value pairs that will be included in the 128 | /// notification. This is exposed as an to support 129 | /// correct deserialization of custom properties. 130 | /// 131 | [JsonExtensionData] 132 | public IDictionary CustomData { get; set; } 133 | 134 | /// 135 | /// Gets or sets the string representation of the property. 136 | /// 137 | [JsonProperty("dir")] 138 | private string DirectionString 139 | { 140 | get 141 | { 142 | switch (this.Direction) 143 | { 144 | case Messaging.Direction.Auto: 145 | return "auto"; 146 | case Messaging.Direction.LeftToRight: 147 | return "ltr"; 148 | case Messaging.Direction.RightToLeft: 149 | return "rtl"; 150 | default: 151 | return null; 152 | } 153 | } 154 | 155 | set 156 | { 157 | switch (value) 158 | { 159 | case "auto": 160 | this.Direction = Messaging.Direction.Auto; 161 | return; 162 | case "ltr": 163 | this.Direction = Messaging.Direction.LeftToRight; 164 | return; 165 | case "rtl": 166 | this.Direction = Messaging.Direction.RightToLeft; 167 | return; 168 | default: 169 | throw new ArgumentException( 170 | $"Invalid direction value: {value}. Only 'auto', 'rtl' and 'ltr' " 171 | + "are allowed."); 172 | } 173 | } 174 | } 175 | 176 | /// 177 | internal protected override WebpushNotification CopyAndValidate() 178 | { 179 | var copy = new WebpushNotification() 180 | { 181 | Title = this.Title, 182 | Body = this.Body, 183 | Icon = this.Icon, 184 | Image = this.Image, 185 | Language = this.Language, 186 | Tag = this.Tag, 187 | Direction = this.Direction, 188 | Badge = this.Badge, 189 | Renotify = this.Renotify, 190 | RequireInteraction = this.RequireInteraction, 191 | Silent = this.Silent, 192 | Actions = this.Actions?.Select(item => new WebpushAction(item)).ToList(), 193 | Vibrate = this.Vibrate, 194 | Timestamp = this.Timestamp, 195 | Data = this.Data, 196 | }; 197 | 198 | var customData = this.CustomData?.ToDictionary(e => e.Key, e => e.Value); 199 | if (customData?.Count > 0) 200 | { 201 | var serializer = NewtonsoftJsonSerializer.Instance; 202 | var json = serializer.Serialize(copy); 203 | var standardProperties = serializer.Deserialize>(json); 204 | var duplicates = customData.Keys 205 | .Where(customKey => standardProperties.ContainsKey(customKey)) 206 | .ToList(); 207 | if (duplicates.Any()) 208 | { 209 | throw new ArgumentException( 210 | "Multiple specifications for WebpushNotification keys: " 211 | + string.Join(",", duplicates)); 212 | } 213 | 214 | copy.CustomData = customData; 215 | } 216 | 217 | return copy; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | You must cause any modified files to carry prominent notices stating that You changed the files; and 39 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 41 | 42 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 43 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 44 | 45 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 46 | 47 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 48 | 49 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 50 | 51 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 52 | 53 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /src/AGConnectAdmin/Utils/NewtonsoftJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using Newtonsoft.Json; 17 | using Newtonsoft.Json.Serialization; 18 | using System; 19 | using System.IO; 20 | using System.Reflection; 21 | using System.Linq; 22 | 23 | namespace AGConnectAdmin 24 | { 25 | /// 26 | /// All values of a type with this attribute are represented as a literal null in JSON. 27 | /// 28 | [AttributeUsage(AttributeTargets.Class)] 29 | internal class JsonExplicitNullAttribute : Attribute { } 30 | 31 | /// 32 | /// A JSON converter which honers RFC 3339 and the serialized date is accepted by Huawei services. 33 | /// 34 | internal class RFC3339DateTimeConverter : JsonConverter 35 | { 36 | /// 37 | public override bool CanRead => false; 38 | 39 | /// 40 | public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, 41 | JsonSerializer serializer) 42 | { 43 | throw new NotImplementedException("Unnecessary because CanRead is false."); 44 | } 45 | 46 | /// 47 | public override bool CanConvert(Type objectType) => 48 | // Convert DateTime only. 49 | objectType == typeof(DateTime) || objectType == typeof(Nullable); 50 | 51 | /// 52 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 53 | { 54 | if (value != null) 55 | { 56 | DateTime date = (DateTime)value; 57 | serializer.Serialize(writer, ConvertToRFC3339(date)); 58 | } 59 | } 60 | 61 | internal static string ConvertToRFC3339(DateTime date) 62 | { 63 | if (date.Kind == DateTimeKind.Unspecified) 64 | { 65 | date = date.ToUniversalTime(); 66 | } 67 | return date.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", System.Globalization.DateTimeFormatInfo.InvariantInfo); 68 | } 69 | } 70 | 71 | /// 72 | /// A JSON converter to write null literals into JSON when explicitly requested. 73 | /// 74 | internal class ExplicitNullConverter : JsonConverter 75 | { 76 | /// 77 | public override bool CanRead => false; 78 | 79 | /// 80 | public override bool CanConvert(Type objectType) => objectType.GetTypeInfo().GetCustomAttributes(typeof(JsonExplicitNullAttribute), false).Any(); 81 | 82 | /// 83 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 84 | { 85 | throw new NotImplementedException("Unnecessary because CanRead is false."); 86 | } 87 | 88 | /// 89 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteNull(); 90 | } 91 | 92 | /// 93 | /// A JSON contract resolver to apply and as necessary. 94 | /// 95 | /// 96 | /// Using a contract resolver is recommended in the Json.NET performance tips: https://www.newtonsoft.com/json/help/html/Performance.htm#JsonConverters 97 | /// 98 | internal class NewtonsoftJsonContractResolver : DefaultContractResolver 99 | { 100 | private static readonly JsonConverter DateTimeConverter = new RFC3339DateTimeConverter(); 101 | private static readonly JsonConverter ExplicitNullConverter = new ExplicitNullConverter(); 102 | 103 | /// 104 | protected override JsonContract CreateContract(Type objectType) 105 | { 106 | JsonContract contract = base.CreateContract(objectType); 107 | 108 | if (DateTimeConverter.CanConvert(objectType)) 109 | { 110 | contract.Converter = DateTimeConverter; 111 | } 112 | else if (ExplicitNullConverter.CanConvert(objectType)) 113 | { 114 | contract.Converter = ExplicitNullConverter; 115 | } 116 | 117 | return contract; 118 | } 119 | } 120 | 121 | /// Class for serialization and deserialization of JSON documents using the Newtonsoft Library. 122 | internal class NewtonsoftJsonSerializer 123 | { 124 | private readonly JsonSerializerSettings settings; 125 | private readonly JsonSerializer serializer; 126 | 127 | /// The default instance of the Newtonsoft JSON Serializer, with default settings. 128 | public static NewtonsoftJsonSerializer Instance { get; } = new NewtonsoftJsonSerializer(); 129 | 130 | /// 131 | /// Constructs a new instance with the default serialization settings, equivalent to . 132 | /// 133 | public NewtonsoftJsonSerializer() : this(CreateDefaultSettings()) 134 | { 135 | } 136 | 137 | /// 138 | /// Constructs a new instance with the given settings. 139 | /// 140 | /// The settings to apply when serializing and deserializing. Must not be null. 141 | public NewtonsoftJsonSerializer(JsonSerializerSettings settings) 142 | { 143 | if (settings == null) 144 | { 145 | throw new ArgumentNullException(nameof(settings)); 146 | } 147 | this.settings = settings; 148 | serializer = JsonSerializer.Create(settings); 149 | } 150 | 151 | /// 152 | /// Creates a new instance of with the same behavior 153 | /// as the ones used in . This method is expected to be used to construct 154 | /// settings which are then passed to . 155 | /// 156 | /// A new set of default settings. 157 | public static JsonSerializerSettings CreateDefaultSettings() => 158 | new JsonSerializerSettings 159 | { 160 | NullValueHandling = NullValueHandling.Ignore, 161 | MetadataPropertyHandling = MetadataPropertyHandling.Ignore, 162 | ContractResolver = new NewtonsoftJsonContractResolver() 163 | }; 164 | 165 | /// 166 | public string Format => "json"; 167 | 168 | /// 169 | public void Serialize(object obj, Stream target) 170 | { 171 | using (var writer = new StreamWriter(target)) 172 | { 173 | if (obj == null) 174 | { 175 | obj = string.Empty; 176 | } 177 | serializer.Serialize(writer, obj); 178 | } 179 | } 180 | 181 | /// 182 | public string Serialize(object obj) 183 | { 184 | using (TextWriter tw = new StringWriter()) 185 | { 186 | if (obj == null) 187 | { 188 | obj = string.Empty; 189 | } 190 | serializer.Serialize(tw, obj); 191 | return tw.ToString(); 192 | } 193 | } 194 | 195 | /// 196 | public T Deserialize(string input) 197 | { 198 | if (string.IsNullOrEmpty(input)) 199 | { 200 | return default(T); 201 | } 202 | return JsonConvert.DeserializeObject(input, settings); 203 | } 204 | 205 | /// 206 | public object Deserialize(string input, Type type) 207 | { 208 | if (string.IsNullOrEmpty(input)) 209 | { 210 | return null; 211 | } 212 | return JsonConvert.DeserializeObject(input, type, settings); 213 | } 214 | 215 | /// 216 | public T Deserialize(Stream input) 217 | { 218 | // Convert the JSON document into an object. 219 | using (StreamReader streamReader = new StreamReader(input)) 220 | { 221 | return (T)serializer.Deserialize(streamReader, typeof(T)); 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/AGConnectApp.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Reflection; 19 | using System.Runtime.CompilerServices; 20 | 21 | [assembly: InternalsVisibleTo("AGConnectAdmin.Tests,PublicKey=" + 22 | "002400000480000094000000060200000024000052534131000400000100010081328559eaab41" + 23 | "055b84af73469863499d81625dcbba8d8decb298b69e0f783a0958cf471fd4f76327b85a7d4b02" + 24 | "3003684e85e61cf15f13150008c81f0b75a252673028e530ea95d0c581378da8c6846526ab9597" + 25 | "4c6d0bc66d2462b51af69968a0e25114bde8811e0d6ee1dc22d4a59eee6a8bba4712cba839652f" + 26 | "badddb9c")] 27 | namespace AGConnectAdmin 28 | { 29 | internal delegate TResult ServiceFactory() 30 | where TResult : IAGConnectService; 31 | 32 | /// 33 | /// This is the entry point to the AGConnect Admin SDK. It holds configuration and state common 34 | /// to all APIs exposed from the SDK. 35 | /// Use one of the provided Create() methods to obtain a new instance. 36 | /// 37 | public sealed class AGConnectApp 38 | { 39 | private const string DefaultAppName = "[DEFAULT]"; 40 | 41 | private static readonly Dictionary Apps = new Dictionary(); 42 | 43 | // Guards the mutable state local to an app instance. 44 | private readonly object appLock = new object(); 45 | private readonly AppOptions options; 46 | 47 | // A collection of stateful services initialized using this app instance (e.g. 48 | // AGConnectAuth). Services are tracked here so they can be cleaned up when the app is 49 | // deleted. 50 | private readonly Dictionary services = new Dictionary(); 51 | private bool deleted = false; 52 | 53 | /// 54 | /// Gets the default app instance. This property is null if the default app instance 55 | /// doesn't yet exist. 56 | /// 57 | public static AGConnectApp DefaultInstance 58 | { 59 | get 60 | { 61 | return GetInstance(DefaultAppName); 62 | } 63 | } 64 | 65 | private AGConnectApp(AppOptions options, string name) 66 | { 67 | this.options = new AppOptions(options); 68 | this.Name = name; 69 | } 70 | 71 | /// 72 | /// Gets a copy of the this app was created with. 73 | /// 74 | public AppOptions Options 75 | { 76 | get 77 | { 78 | return new AppOptions(this.options); 79 | } 80 | } 81 | 82 | /// 83 | /// Gets the name of this app. 84 | /// 85 | private string Name { get; } 86 | 87 | /// 88 | /// Returns the app instance identified by the given name. 89 | /// 90 | /// The instance with the specified name or null if it 91 | /// doesn't exist. 92 | /// If the name argument is null or empty. 93 | /// Name of the app to retrieve. 94 | private static AGConnectApp GetInstance(string name) 95 | { 96 | if (string.IsNullOrEmpty(name)) 97 | { 98 | throw new ArgumentException("App name to lookup must not be null or empty"); 99 | } 100 | 101 | lock (Apps) 102 | { 103 | AGConnectApp app; 104 | if (Apps.TryGetValue(name, out app)) 105 | { 106 | return app; 107 | } 108 | } 109 | 110 | return null; 111 | } 112 | 113 | /// 114 | /// Creates the default app instance with the specified options. 115 | /// 116 | /// The newly created instance. 117 | /// If the default app instance already 118 | /// exists. 119 | /// Options to create the app with. Must at least contain the 120 | /// Credential. 121 | public static AGConnectApp Create(AppOptions options) 122 | { 123 | return Create(options, DefaultAppName); 124 | } 125 | 126 | /// 127 | /// Creates an app with the specified name and options. 128 | /// 129 | /// The newly created instance. 130 | /// If the default app instance already 131 | /// exists. 132 | /// Options to create the app with. Must at least contain the 133 | /// Credential. 134 | /// Name of the app. 135 | private static AGConnectApp Create(AppOptions options, string name) 136 | { 137 | if (string.IsNullOrEmpty(name)) 138 | { 139 | throw new ArgumentException("App name must not be null or empty"); 140 | } 141 | 142 | if (options == null) 143 | { 144 | throw new ArgumentException("options must not be null"); 145 | } 146 | 147 | lock (Apps) 148 | { 149 | if (Apps.ContainsKey(name)) 150 | { 151 | throw new ArgumentException($"AGConnectApp named {name} already exists."); 152 | } 153 | 154 | var app = new AGConnectApp(options, name); 155 | Apps.Add(name, app); 156 | return app; 157 | } 158 | } 159 | 160 | /// 161 | /// Deletes this app instance and cleans up any state associated with it. Once an app has 162 | /// been deleted, accessing any services related to it will result in an exception. 163 | /// If the app is already deleted, this method is a no-op. 164 | /// 165 | public void Delete() 166 | { 167 | // Clean up local state 168 | lock (this.appLock) 169 | { 170 | this.deleted = true; 171 | foreach (var entry in this.services) 172 | { 173 | try 174 | { 175 | entry.Value.Delete(); 176 | } 177 | catch (Exception e) 178 | { 179 | Console.WriteLine(e); 180 | } 181 | } 182 | 183 | this.services.Clear(); 184 | } 185 | 186 | // Clean up global state 187 | lock (Apps) 188 | { 189 | Apps.Remove(this.Name); 190 | } 191 | } 192 | 193 | /// 194 | /// Deleted all the apps created so far. Used for unit testing. 195 | /// 196 | internal static void DeleteAll() 197 | { 198 | lock (Apps) 199 | { 200 | var copy = new Dictionary(Apps); 201 | foreach (var entry in copy) 202 | { 203 | entry.Value.Delete(); 204 | } 205 | 206 | if (Apps.Count > 0) 207 | { 208 | throw new InvalidOperationException("Failed to delete all apps"); 209 | } 210 | } 211 | } 212 | 213 | /// 214 | /// Returns the current version of the .NET assembly. 215 | /// 216 | /// A version string in major.minor.patch format. 217 | internal static string GetSdkVersion() 218 | { 219 | const int majorMinorPatch = 3; 220 | return typeof(AGConnectApp).GetTypeInfo().Assembly.GetName().Version.ToString(majorMinorPatch); 221 | } 222 | 223 | internal T GetOrInit(string id, ServiceFactory initializer) 224 | where T : class, IAGConnectService 225 | { 226 | lock (this.appLock) 227 | { 228 | if (this.deleted) 229 | { 230 | throw new InvalidOperationException("Cannot use an app after it has been deleted"); 231 | } 232 | 233 | IAGConnectService service; 234 | if (!this.services.TryGetValue(id, out service)) 235 | { 236 | service = initializer(); 237 | this.services.Add(id, service); 238 | } 239 | 240 | return (T)service; 241 | } 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/Aps.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class 16 | */ 17 | 18 | using Newtonsoft.Json; 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | 23 | namespace AGConnectAdmin.Messaging 24 | { 25 | /// 26 | /// Represents the 27 | /// aps dictionary that is part of every APNs message. 28 | /// 29 | public class Aps : MessagePart 30 | { 31 | private static readonly NewtonsoftJsonSerializer Serializer = NewtonsoftJsonSerializer.Instance; 32 | 33 | /// 34 | /// Gets or sets the alert configuration of the aps dictionary. Read from either 35 | /// or property. 36 | /// 37 | [JsonProperty("alert")] 38 | private object AlertObject 39 | { 40 | get 41 | { 42 | object alert = this.AlertString; 43 | if (string.IsNullOrEmpty(alert as string)) 44 | { 45 | alert = this.Alert; 46 | } 47 | else if (this.Alert != null) 48 | { 49 | throw new ArgumentException( 50 | "Multiple specifications for alert (Alert and AlertString"); 51 | } 52 | 53 | return alert; 54 | } 55 | 56 | set 57 | { 58 | if (value == null) 59 | { 60 | return; 61 | } 62 | else if (value.GetType() == typeof(string)) 63 | { 64 | this.AlertString = (string)value; 65 | } 66 | else if (value.GetType() == typeof(ApsAlert)) 67 | { 68 | this.Alert = (ApsAlert)value; 69 | } 70 | else 71 | { 72 | var json = Serializer.Serialize(value); 73 | this.Alert = Serializer.Deserialize(json); 74 | } 75 | } 76 | } 77 | 78 | /// 79 | /// Gets or sets an advanced alert configuration to be included in the message. It is an 80 | /// error to set both and properties 81 | /// together. 82 | /// 83 | [JsonIgnore] 84 | public ApsAlert Alert { get; set; } 85 | 86 | /// 87 | /// Gets or sets the alert text to be included in the message. To specify a more advanced 88 | /// alert configuration, use the property instead. It is an error to 89 | /// set both and properties together. 90 | /// 91 | [JsonIgnore] 92 | public string AlertString { get; set; } 93 | 94 | /// 95 | /// Gets or sets the badge to be displayed with the message. Set to 0 to remove the badge. 96 | /// When not specified, the badge will remain unchanged. 97 | /// 98 | [JsonProperty("badge")] 99 | public int? Badge { get; set; } 100 | 101 | /// 102 | /// Gets or sets the name of a sound file in your app's main bundle or in the 103 | /// Library/Sounds folder of your app's container directory. Specify the 104 | /// string default to play the system sound. It is an error to set both 105 | /// and properties together. 106 | /// 107 | [JsonIgnore] 108 | public string Sound { get; set; } 109 | 110 | /// 111 | /// Gets or sets the critical alert sound to be played with the message. It is an error to 112 | /// set both and properties together. 113 | /// 114 | [JsonIgnore] 115 | public CriticalSound CriticalSound { get; set; } 116 | 117 | /// 118 | /// Gets or sets a value indicating whether to configure a background update notification. 119 | /// 120 | [JsonIgnore] 121 | public bool ContentAvailable { get; set; } 122 | 123 | /// 124 | /// Gets or sets a value indicating whether to include the mutable-content property 125 | /// in the message. When set, this property allows clients to modify the notification via 126 | /// app extensions. 127 | /// 128 | [JsonIgnore] 129 | public bool MutableContent { get; set; } 130 | 131 | /// 132 | /// Gets or sets the type of the notification. 133 | /// 134 | [JsonProperty("category")] 135 | public string Category { get; set; } 136 | 137 | /// 138 | /// Gets or sets the app-specific identifier for grouping notifications. 139 | /// 140 | [JsonProperty("thread-id")] 141 | public string ThreadId { get; set; } 142 | 143 | /// 144 | /// Gets or sets the identifier of the window brought forward. 145 | /// 146 | [JsonProperty("target-content-id")] 147 | public string TargetContentId { get; set; } 148 | 149 | /// 150 | /// Gets or sets the sound configuration of the aps dictionary. Read from either 151 | /// or property. 152 | /// 153 | [JsonProperty("sound")] 154 | private object SoundObject 155 | { 156 | get 157 | { 158 | object sound = this.Sound; 159 | if (string.IsNullOrEmpty(sound as string)) 160 | { 161 | sound = this.CriticalSound; 162 | } 163 | else if (this.CriticalSound != null) 164 | { 165 | throw new ArgumentException( 166 | "Multiple specifications for sound (CriticalSound and Sound"); 167 | } 168 | 169 | return sound; 170 | } 171 | 172 | set 173 | { 174 | if (value == null) 175 | { 176 | return; 177 | } 178 | else if (value.GetType() == typeof(string)) 179 | { 180 | this.Sound = (string)value; 181 | } 182 | else if (value.GetType() == typeof(CriticalSound)) 183 | { 184 | this.CriticalSound = (CriticalSound)value; 185 | } 186 | else 187 | { 188 | var json = Serializer.Serialize(value); 189 | this.CriticalSound = Serializer.Deserialize(json); 190 | } 191 | } 192 | } 193 | 194 | /// 195 | /// Gets or sets the integer representation of the property, 196 | /// which is how APNs expects it. 197 | /// 198 | [JsonProperty("content-available")] 199 | private int? ContentAvailableInt 200 | { 201 | get 202 | { 203 | return this.ContentAvailable ? 1 : (int?)null; 204 | } 205 | 206 | set 207 | { 208 | this.ContentAvailable = value == 1; 209 | } 210 | } 211 | 212 | /// 213 | /// Gets or sets the integer representation of the property, 214 | /// which is how APNs expects it. 215 | /// 216 | [JsonProperty("mutable-content")] 217 | private int? MutableContentInt 218 | { 219 | get 220 | { 221 | return this.MutableContent ? 1 : (int?)null; 222 | } 223 | 224 | set 225 | { 226 | this.MutableContent = value == 1; 227 | } 228 | } 229 | 230 | /// 231 | /// Gets or sets a collection of arbitrary key-value data to be included in the aps 232 | /// dictionary. This is exposed as an to support 233 | /// correct deserialization of custom properties. 234 | /// 235 | [JsonExtensionData] 236 | public IDictionary CustomData { get; set; } 237 | 238 | /// 239 | internal protected override Aps CopyAndValidate() 240 | { 241 | var copy = new Aps 242 | { 243 | AlertObject = this.AlertObject, 244 | Badge = this.Badge, 245 | ContentAvailable = this.ContentAvailable, 246 | MutableContent = this.MutableContent, 247 | Category = this.Category, 248 | SoundObject = this.SoundObject, 249 | ThreadId = this.ThreadId, 250 | TargetContentId = this.TargetContentId 251 | }; 252 | 253 | var customData = this.CustomData?.ToDictionary(e => e.Key, e => e.Value); 254 | if (customData?.Count > 0) 255 | { 256 | var serializer = NewtonsoftJsonSerializer.Instance; 257 | var json = serializer.Serialize(copy); 258 | var standardProperties = serializer.Deserialize>(json); 259 | var duplicates = customData.Keys 260 | .Where(customKey => standardProperties.ContainsKey(customKey)) 261 | .ToList(); 262 | if (duplicates.Any()) 263 | { 264 | throw new ArgumentException( 265 | $"Multiple specifications for Aps keys: {string.Join(",", duplicates)}"); 266 | } 267 | 268 | copy.CustomData = customData; 269 | } 270 | 271 | copy.Alert = copy.Alert?.CopyAndValidate(); 272 | copy.CriticalSound = copy.CriticalSound?.CopyAndValidate(); 273 | return copy; 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/AGConnectMessaging.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed implement 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Runtime.CompilerServices; 21 | using System.Threading; 22 | using System.Threading.Tasks; 23 | 24 | [assembly: InternalsVisibleTo("AGConnectAdmin.Tests,PublicKey=" + 25 | "0024000004800000940000000602000000240000525341310004000001000100cd7e49c156e0f4" + 26 | "abc12b5177925ac69822032c9fad95fb82c8a79d2ff277e3c541311856b65c2e1b67d8b3964a65" + 27 | "bfb1c09bbca8475e8234c6ba56004f568db317c65fcd067fcda1972a97d623c20eb4c19cc6d450" + 28 | "f2233fd5bde42943f6d08dec916414f76b3187439603176223bda09146c1836a64b6b5bd36e9cd" + 29 | "435643ba")] 30 | namespace AGConnectAdmin.Messaging 31 | { 32 | /// 33 | /// This is the entry point to all server-side AGConnect Cloud Messaging (HCM) operations. You 34 | /// can get an instance of this class via AGConnectMessaging.DefaultInstance. 35 | /// 36 | public sealed class AGConnectMessaging : IAGConnectService 37 | { 38 | private readonly AGConnectMessagingClient messagingClient; 39 | 40 | private AGConnectMessaging(AGConnectApp app) 41 | { 42 | this.messagingClient = new AGConnectMessagingClient(app.Options); 43 | } 44 | 45 | /// 46 | /// Gets the messaging instance associated with the default AGConnect app. This property is 47 | /// null if the default app doesn't yet exist. 48 | /// 49 | public static AGConnectMessaging DefaultInstance 50 | { 51 | get 52 | { 53 | var app = AGConnectApp.DefaultInstance; 54 | if (app == null) 55 | { 56 | return null; 57 | } 58 | 59 | return GetMessaging(app); 60 | } 61 | } 62 | 63 | /// 64 | /// Returns the messaging instance for the specified app. 65 | /// 66 | /// The instance associated with the specified 67 | /// app. 68 | /// If the app argument is null. 69 | /// An app instance. 70 | internal static AGConnectMessaging GetMessaging(AGConnectApp app) 71 | { 72 | if (app == null) 73 | { 74 | throw new ArgumentNullException(nameof(app)); 75 | } 76 | 77 | return app.GetOrInit(typeof(AGConnectMessaging).Name, () => 78 | { 79 | return new AGConnectMessaging(app); 80 | }); 81 | } 82 | 83 | /// 84 | /// Sends a message to the HCM service for delivery. The message gets validated both by 85 | /// the Admin SDK, and the remote HCM service. A successful return value indicates 86 | /// that the message has been successfully sent to HCM, where it has been accepted by the 87 | /// AGC service. 88 | /// 89 | /// A task that completes with a message ID string, which represents 90 | /// successful handoff to HCM. 91 | /// If the message argument is null. 92 | /// If the message contains any invalid 93 | /// fields. 94 | /// If an error occurs while sending the 95 | /// message. 96 | /// The message to be sent. Must not be null. 97 | public async Task SendAsync(Message message) 98 | { 99 | return await this.SendAsync(message, false).ConfigureAwait(false); 100 | } 101 | 102 | /// 103 | /// Sends a message to the AGC service for delivery. The message gets validated both by 104 | /// the Admin SDK, and the remote AGC service. A successful return value indicates 105 | /// that the message has been successfully sent to AGC, where it has been accepted by the 106 | /// AGC service. 107 | /// 108 | /// A task that completes with a message ID string, which represents 109 | /// successful handoff to AGC. 110 | /// If the message argument is null. 111 | /// If the message contains any invalid 112 | /// fields. 113 | /// If an error occurs while sending the 114 | /// message. 115 | /// The message to be sent. Must not be null. 116 | /// A cancellation token to monitor the asynchronous 117 | /// operation. 118 | public async Task SendAsync(Message message, CancellationToken cancellationToken) 119 | { 120 | return await this.SendAsync(message, false, cancellationToken).ConfigureAwait(false); 121 | } 122 | 123 | /// 124 | /// Sends a message to the AGC service for delivery. The message gets validated both by 125 | /// the Admin SDK, and the remote AGC service. A successful return value indicates 126 | /// that the message has been successfully sent to AGC, where it has been accepted by the 127 | /// AGC service. 128 | /// If the option is set to true, the message will not be 129 | /// actually sent to the recipients. Instead, the AGC service performs all the necessary 130 | /// validations, and emulates the send operation. This is a good way to check if a 131 | /// certain message will be accepted by AGC for delivery. 132 | /// 133 | /// A task that completes with a message ID string, which represents 134 | /// successful handoff to AGC. 135 | /// If the message argument is null. 136 | /// If the message contains any invalid 137 | /// fields. 138 | /// If an error occurs while sending the 139 | /// message. 140 | /// The message to be sent. Must not be null. 141 | /// A boolean indicating whether to perform a dry run (validation 142 | /// only) of the send. If set to true, the message will be sent to the AGC backend service, 143 | /// but it will not be delivered to any actual recipients. 144 | public async Task SendAsync(Message message, bool dryRun) 145 | { 146 | return await this.SendAsync(message, dryRun, default(CancellationToken)).ConfigureAwait(false); 147 | } 148 | 149 | /// 150 | /// Sends a message to the AGC service for delivery. The message gets validated both by 151 | /// the Admin SDK, and the remote AGC service. A successful return value indicates 152 | /// that the message has been successfully sent to AGC, where it has been accepted by the 153 | /// AGC service. 154 | /// If the option is set to true, the message will not be 155 | /// actually sent to the recipients. Instead, the AGC service performs all the necessary 156 | /// validations, and emulates the send operation. This is a good way to check if a 157 | /// certain message will be accepted by AGC for delivery. 158 | /// 159 | /// A task that completes with a message ID string, which represents 160 | /// successful handoff to AGC. 161 | /// If the message argument is null. 162 | /// If the message contains any invalid 163 | /// fields. 164 | /// If an error occurs while sending the 165 | /// message. 166 | /// The message to be sent. Must not be null. 167 | /// A boolean indicating whether to perform a dry run (validation 168 | /// only) of the send. If set to true, the message will be sent to the AGC backend service, 169 | /// but it will not be delivered to any actual recipients. 170 | /// A cancellation token to monitor the asynchronous 171 | /// operation. 172 | public async Task SendAsync( 173 | Message message, bool dryRun, CancellationToken cancellationToken) 174 | { 175 | return await this.messagingClient.SendAsync( 176 | message, dryRun, cancellationToken).ConfigureAwait(false); 177 | } 178 | 179 | 180 | /// 181 | /// Subscribes a list of registration tokens to a topic. 182 | /// 183 | /// A list of registration tokens to subscribe. 184 | /// The topic name to subscribe to. 185 | /// A task that completes with a , giving details about the topic subscription operations. 186 | public async Task SubscribeToTopicAsync( 187 | IReadOnlyList registrationTokens, string topic) 188 | { 189 | return await this.messagingClient.SubscribeToTopicAsync(registrationTokens, topic).ConfigureAwait(false); 190 | } 191 | 192 | /// 193 | /// Unsubscribes a list of registration tokens from a topic. 194 | /// 195 | /// A list of registration tokens to unsubscribe. 196 | /// The topic name to unsubscribe from. 197 | /// A task that completes with a , giving details about the topic unsubscription operations. 198 | public async Task UnsubscribeFromTopicAsync( 199 | IReadOnlyList registrationTokens, string topic) 200 | { 201 | return await this.messagingClient.UnsubscribeFromTopicAsync(registrationTokens, topic).ConfigureAwait(false); 202 | } 203 | 204 | /// 205 | /// Retrieve a list of topics that the specified registration token subscribe to. 206 | /// 207 | /// The topic name to unsubscribe from. 208 | /// A task that completes with a , giving details about the topic retrieve operations. 209 | public async Task GetTopicListAsync(string registrationToken) 210 | { 211 | return await this.messagingClient.GetTopicListAsync(registrationToken).ConfigureAwait(false); 212 | } 213 | 214 | /// 215 | /// Deletes this service instance. 216 | /// 217 | void IAGConnectService.Delete() 218 | { 219 | this.messagingClient.Dispose(); 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/AndroidNotification.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed base class and some properties 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Linq; 21 | using AGConnectAdmin.Utils; 22 | using Newtonsoft.Json; 23 | using Newtonsoft.Json.Converters; 24 | using Newtonsoft.Json.Linq; 25 | 26 | namespace AGConnectAdmin.Messaging 27 | { 28 | /// 29 | /// Represents the Android-specific notification options that can be included in a 30 | /// . 31 | /// 32 | public class AndroidNotification : MessagePart 33 | { 34 | /// 35 | /// Gets or sets the title of the Android notification. When provided, overrides the title 36 | /// set via . 37 | /// 38 | [JsonProperty("title")] 39 | public string Title { get; set; } 40 | 41 | /// 42 | /// Gets or sets the title of the Android notification. When provided, overrides the title 43 | /// set via . 44 | /// 45 | [JsonProperty("body")] 46 | public string Body { get; set; } 47 | 48 | /// 49 | /// Gets or sets the icon of the Android notification. 50 | /// 51 | [JsonProperty("icon")] 52 | public string Icon { get; set; } 53 | 54 | /// 55 | /// Gets or sets the notification icon color. Must be of the form #RRGGBB. 56 | /// 57 | [JsonProperty("color")] 58 | public string Color { get; set; } 59 | 60 | /// 61 | /// Gets or sets the sound to be played when the device receives the notification. 62 | /// 63 | [JsonProperty("sound")] 64 | public string Sound { get; set; } 65 | 66 | /// 67 | /// Indicate whether the default sound should be played when the device receives the notification. 68 | /// 69 | [JsonProperty("default_sound")] 70 | public bool DefaultSound { get; set; } 71 | 72 | /// 73 | /// Gets or sets the notification tag. This is an identifier used to replace existing 74 | /// notifications in the notification drawer. If not specified, each request creates a new 75 | /// notification. 76 | /// 77 | [JsonProperty("tag")] 78 | public string Tag { get; set; } 79 | 80 | /// 81 | /// Gets or sets the action associated with a user click on the notification. If specified, 82 | /// an activity with a matching Intent Filter is launched when a user clicks on the 83 | /// notification. 84 | /// 85 | [JsonProperty("click_action")] 86 | public ClickAction ClickAction { get; set; } 87 | 88 | /// 89 | /// Gets or sets the key of the body string in the app's string resources to use to 90 | /// localize the body text. 91 | /// 92 | [JsonProperty("body_loc_key")] 93 | public string BodyLocKey { get; set; } 94 | 95 | /// 96 | /// Gets or sets the collection of resource key strings that will be used in place of the 97 | /// format specifiers in . 98 | /// 99 | [JsonProperty("body_loc_args")] 100 | public IEnumerable BodyLocArgs { get; set; } 101 | 102 | /// 103 | /// Gets or sets the key of the title string in the app's string resources to use to 104 | /// localize the title text. 105 | /// 106 | [JsonProperty("title_loc_key")] 107 | public string TitleLocKey { get; set; } 108 | 109 | /// 110 | /// Gets or sets the collection of resource key strings that will be used in place of the 111 | /// format specifiers in . 112 | /// 113 | [JsonProperty("title_loc_args")] 114 | public IEnumerable TitleLocArgs { get; set; } 115 | 116 | /// 117 | /// Gets or sets the multi localization keys. 118 | /// 119 | [JsonProperty("multi_lang_key")] 120 | public JObject MultiLangKey { get; set; } 121 | 122 | /// 123 | /// Gets or sets the Android notification channel ID (new in Android O). The app must 124 | /// create a channel with this channel ID before any notification with this channel ID is 125 | /// received. If you don't send this channel ID in the request, or if the channel ID 126 | /// provided has not yet been created by the app, AGC uses the channel ID specified in the 127 | /// app manifest. 128 | /// 129 | [JsonProperty("channel_id")] 130 | public string ChannelId { get; set; } 131 | 132 | /// 133 | /// Gets or sets the notify summary. 134 | /// 135 | [JsonProperty("notify_summary")] 136 | public string NotifySummary { get; set; } 137 | 138 | /// 139 | /// Gets or sets the image of the message. 140 | /// 141 | [JsonProperty("image")] 142 | public string Image { get; set; } 143 | 144 | /// 145 | /// Gets or set notification style. 146 | /// 147 | [JsonProperty("style")] 148 | public NotificationStyle Style { get; set; } 149 | 150 | /// 151 | /// Gets or sets big size title. It will override the 152 | /// if the is . 153 | /// 154 | [JsonProperty("big_title")] 155 | public string BigTitle { get; set; } 156 | 157 | /// 158 | /// Gets or sets big size body. It will override the 159 | /// if the is . 160 | /// 161 | [JsonProperty("big_body")] 162 | public string BigBody { get; set; } 163 | 164 | /// 165 | /// Gets or sets the auto clear time (milliseconds) of the message. 166 | /// 167 | [JsonProperty("auto_clear")] 168 | public int? AutoClear { get; set; } 169 | 170 | /// 171 | /// 172 | /// 173 | [JsonProperty("notify_id")] 174 | public int? NotifyId { get; set; } 175 | 176 | /// 177 | /// Group of notification 178 | /// 179 | [JsonProperty("group")] 180 | public string Group { get; set; } 181 | 182 | /// 183 | /// Badge notification 184 | /// 185 | [JsonProperty("badge")] 186 | public BadgeNotification Badge { get; set; } 187 | 188 | /// 189 | /// Gets or sets the ticker of the notification. 190 | /// 191 | [JsonProperty("ticker")] 192 | public string Ticker { get; set; } 193 | 194 | /// 195 | /// Indicate whether the notification should be hide automatically. 196 | /// 197 | [JsonProperty("auto_cancel")] 198 | public bool AutoCancel { get; set; } 199 | 200 | /// 201 | /// Gets or sets the event time of the notification which will affect the sorting. 202 | /// 203 | [JsonProperty("when")] 204 | public DateTime? EventTime { get; set; } 205 | 206 | /// 207 | /// Gets or set the priority of the notification. 208 | /// 209 | [JsonProperty("importance")] 210 | [JsonConverter(typeof(StringEnumConverter))] 211 | public NotificationImportance? Importance { get; set; } 212 | 213 | /// 214 | /// Indicate whether should be use default vibrate timings of device for the notification. 215 | /// 216 | [JsonProperty("use_default_vibrate")] 217 | public bool? DefaultVibrateTimings { get; set; } 218 | 219 | /// 220 | /// Gets or sets the vibrate timings of the notification. 221 | /// Each item represents seconds of vibrate timings and must be within 60. 222 | /// 223 | [JsonProperty("vibrate_config")] 224 | public IEnumerable VibrateTimings { get; set; } 225 | 226 | /// 227 | /// Gets or sets the visibility of the notification. 228 | /// 229 | [JsonProperty("visibility")] 230 | [JsonConverter(typeof(StringEnumConverter))] 231 | public NotificationVisibility? Visibility { get; set; } 232 | 233 | /// 234 | /// Indicate whether should be use default light settings of device for the notification. 235 | /// 236 | [JsonProperty("use_default_light")] 237 | public bool? DefaultLightSettings { get; set; } 238 | 239 | /// 240 | /// Gets or sets the light settings. 241 | /// 242 | [JsonProperty("light_settings")] 243 | public LightSettings LightSettings { get; set; } 244 | 245 | /// 246 | /// Indicate whether show the notification when the application is active. 247 | /// 248 | [JsonProperty("foreground_show")] 249 | public bool? ForegroundShow { get; set; } 250 | 251 | /// 252 | internal protected override AndroidNotification CopyAndValidate() 253 | { 254 | // copy 255 | var copy = new AndroidNotification() 256 | { 257 | Title = this.Title, 258 | Body = this.Body, 259 | Icon = this.Icon, 260 | Color = this.Color, 261 | Sound = this.Sound, 262 | DefaultSound = this.DefaultSound, 263 | Tag = this.Tag, 264 | ClickAction = this.ClickAction?.CopyAndValidate(), 265 | BodyLocKey = this.BodyLocKey, 266 | BodyLocArgs = this.BodyLocArgs?.ToList(), 267 | TitleLocKey = this.TitleLocKey, 268 | TitleLocArgs = this.TitleLocArgs?.ToList(), 269 | MultiLangKey = MultiLangKey == null ? null : new JObject(this.MultiLangKey), 270 | ChannelId = this.ChannelId, 271 | NotifySummary = this.NotifySummary, 272 | Image = this.Image, 273 | Style = this.Style, 274 | BigTitle = this.BigTitle, 275 | BigBody = BigBody, 276 | AutoClear = AutoClear, 277 | NotifyId = NotifyId, 278 | Group = Group, 279 | Badge = this.Badge?.CopyAndValidate(), 280 | Ticker = this.Ticker, 281 | AutoCancel = this.AutoCancel, 282 | EventTime = this.EventTime, 283 | Importance = this.Importance, 284 | DefaultVibrateTimings = this.DefaultVibrateTimings, 285 | DefaultLightSettings = this.DefaultLightSettings, 286 | VibrateTimings = this.VibrateTimings?.ToArray(), 287 | Visibility = this.Visibility, 288 | LightSettings = this.LightSettings?.CopyAndValidate(), 289 | ForegroundShow = this.ForegroundShow 290 | }; 291 | 292 | // validate 293 | if (copy.Color != null && !Validator.IsColor(copy.Color)) 294 | { 295 | throw new ArgumentException("Color must be in the form #RRGGBB."); 296 | } 297 | 298 | if (copy.Image != null && !Validator.IsHttpsUrl(copy.Image)) 299 | { 300 | throw new ArgumentException("Image must be a https url."); 301 | } 302 | 303 | if (copy.TitleLocArgs?.Any() == true && string.IsNullOrEmpty(copy.TitleLocKey)) 304 | { 305 | throw new ArgumentException($"{nameof(TitleLocKey)} is required when specifying {nameof(TitleLocArgs)}."); 306 | } 307 | 308 | if (copy.BodyLocArgs?.Any() == true && string.IsNullOrEmpty(copy.BodyLocKey)) 309 | { 310 | throw new ArgumentException($"{nameof(BodyLocKey)} is required when specifying {nameof(BodyLocArgs)}."); 311 | } 312 | 313 | if (copy.VibrateTimings?.Any() == true && !Validator.IsAllInRange(copy.VibrateTimings, 0, 60)) 314 | { 315 | throw new ArgumentException("Vibrate timing must be within 60 seconds"); 316 | } 317 | 318 | return copy; 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/AGConnectAdmin/Messaging/AGConnectMessagingClient.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2018, Google Inc. All rights reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * 2019.12.27-Changed implement 16 | */ 17 | 18 | using System; 19 | using System.Net.Http; 20 | using System.Net.Http.Headers; 21 | using System.Threading; 22 | using System.Threading.Tasks; 23 | using Newtonsoft.Json; 24 | using System.Net; 25 | using System.Text; 26 | using System.Collections.Generic; 27 | using AGConnectAdmin.Auth; 28 | 29 | namespace AGConnectAdmin.Messaging 30 | { 31 | /// 32 | /// A client for making authorized HTTP calls to the HCM backend service. Handles request 33 | /// serialization, response parsing, and HTTP error handling. 34 | /// 35 | internal sealed class AGConnectMessagingClient : IDisposable 36 | { 37 | private const string SUCCESS_CODE = "80000000"; 38 | private readonly HttpClient httpClient; 39 | private string TokenUrl; 40 | private readonly string sendUrl; 41 | private AppOptions options; 42 | 43 | private TokenResponse tokenResponse; 44 | private Task accessTokenTask; 45 | private object accessTokenTaskLock = new object(); 46 | 47 | public AGConnectMessagingClient(AppOptions options) 48 | { 49 | this.options = options; 50 | TokenUrl = options.LoginUri; 51 | sendUrl = options.GetApiUri(); 52 | #if PROXY 53 | httpClient = new HttpClient(new HttpClientHandler() 54 | { 55 | UseProxy = true, 56 | // Use cntlm proxy setting to accesss internet. 57 | Proxy = new WebProxy("localhost", 3128), 58 | }); 59 | #else 60 | httpClient = new HttpClient(); 61 | #endif 62 | } 63 | 64 | public void Dispose() 65 | { 66 | httpClient.Dispose(); 67 | } 68 | 69 | /// 70 | /// Sends a message to the AGC service for delivery. The message gets validated both by 71 | /// the Admin SDK, and the remote AGC service. A successful return value indicates 72 | /// that the message has been successfully sent to AGC, where it has been accepted by the 73 | /// AGC service. 74 | /// 75 | /// A task that completes with a message ID string, which represents 76 | /// successful handoff to AGC. 77 | /// If the message argument is null. 78 | /// If the message contains any invalid 79 | /// fields. 80 | /// If an error occurs while sending the 81 | /// message. 82 | /// The message to be sent. Must not be null. 83 | /// A boolean indicating whether to perform a dry run (validation 84 | /// only) of the send. If set to true, the message will be sent to the AGC backend service, 85 | /// but it will not be delivered to any actual recipients. 86 | /// A cancellation token to monitor the asynchronous 87 | /// operation. 88 | public async Task SendAsync( 89 | Message message, 90 | bool dryRun = false, 91 | CancellationToken cancellationToken = default) 92 | { 93 | try 94 | { 95 | var sendRequest = new SendRequest() 96 | { 97 | Message = (message.ThrowIfNull(nameof(message)) as MessagePart).CopyAndValidate(), 98 | ValidateOnly = dryRun, 99 | }; 100 | var accessToken = await GetAccessTokenAsync().ConfigureAwait(false); 101 | var payload = NewtonsoftJsonSerializer.Instance.Serialize(sendRequest); 102 | var content = new StringContent(payload, Encoding.UTF8, "application/json"); 103 | var request = new HttpRequestMessage() 104 | { 105 | Method = HttpMethod.Post, 106 | RequestUri = new Uri(sendUrl), 107 | Content = content, 108 | }; 109 | 110 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 111 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); 112 | 113 | var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 114 | 115 | if (response.StatusCode != HttpStatusCode.OK) 116 | { 117 | var error = $"Response status code does not indicate success: {response.StatusCode}"; 118 | throw new AGConnectException(error); 119 | } 120 | var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 121 | var parsed = JsonConvert.DeserializeObject(json); 122 | if (parsed.Code != SUCCESS_CODE) 123 | { 124 | throw new AGConnectException($"Send message failed: {parsed.Code}{Environment.NewLine}{parsed.Message}"); 125 | } 126 | return parsed.RequestId; 127 | } 128 | catch (HttpRequestException e) 129 | { 130 | throw new AGConnectException("Error while calling the AGC messaging service.", e); 131 | } 132 | } 133 | 134 | /// 135 | /// Subscribes a list of registration tokens to a topic. 136 | /// 137 | /// A list of registration tokens to subscribe. 138 | /// The topic name to subscribe to. 139 | /// A task that completes with a , giving details about the topic subscription operations. 140 | public async Task SubscribeToTopicAsync( 141 | IReadOnlyList registrationTokens, string topic) 142 | { 143 | if (string.IsNullOrWhiteSpace(topic)) 144 | { 145 | throw new ArgumentException("Topic can not be empty"); 146 | } 147 | if (registrationTokens == null) 148 | { 149 | throw new ArgumentException("Registration tokens can not be null"); 150 | } 151 | if (registrationTokens.Count == 0) 152 | { 153 | throw new ArgumentException("Registration tokens can not be empty"); 154 | } 155 | if (registrationTokens.Count > 1000) 156 | { 157 | throw new ArgumentException("Registration token list must not contain more than 1000 tokens"); 158 | } 159 | 160 | var accessToken = await GetAccessTokenAsync().ConfigureAwait(false); 161 | var body = new TopicManagementRequest() 162 | { 163 | Topic = topic, 164 | Tokens = new List(registrationTokens) 165 | }; 166 | var url = $"{options.ApiBaseUri}/v1/{options.ClientId}/topic:subscribe"; 167 | var payload = NewtonsoftJsonSerializer.Instance.Serialize(body); 168 | var content = new StringContent(payload, Encoding.UTF8, "application/json"); 169 | var request = new HttpRequestMessage() 170 | { 171 | Method = HttpMethod.Post, 172 | RequestUri = new Uri(url), 173 | Content = content, 174 | }; 175 | 176 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 177 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); 178 | 179 | var resp = await httpClient.SendAsync(request).ConfigureAwait(false); 180 | var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); 181 | return NewtonsoftJsonSerializer.Instance.Deserialize(json); 182 | } 183 | 184 | /// 185 | /// Unsubscribes a list of registration tokens from a topic. 186 | /// 187 | /// A list of registration tokens to unsubscribe. 188 | /// The topic name to unsubscribe from. 189 | /// A task that completes with a , giving details about the topic unsubscription operations. 190 | public async Task UnsubscribeFromTopicAsync( 191 | IReadOnlyList registrationTokens, string topic) 192 | { 193 | if (string.IsNullOrWhiteSpace(topic)) 194 | { 195 | throw new ArgumentException("Topic can not be empty"); 196 | } 197 | if (registrationTokens == null) 198 | { 199 | throw new ArgumentException("Registration tokens can not be null"); 200 | } 201 | if (registrationTokens.Count == 0) 202 | { 203 | throw new ArgumentException("Registration tokens can not be empty"); 204 | } 205 | if (registrationTokens.Count > 1000) 206 | { 207 | throw new ArgumentException("Registration token list must not contain more than 1000 tokens"); 208 | } 209 | 210 | var accessToken = await GetAccessTokenAsync().ConfigureAwait(false); 211 | var body = new TopicManagementRequest() 212 | { 213 | Topic = topic, 214 | Tokens = new List(registrationTokens) 215 | }; 216 | var url = $"{options.ApiBaseUri}/v1/{options.ClientId}/topic:unsubscribe"; 217 | var payload = NewtonsoftJsonSerializer.Instance.Serialize(body); 218 | var content = new StringContent(payload, Encoding.UTF8, "application/json"); 219 | var request = new HttpRequestMessage() 220 | { 221 | Method = HttpMethod.Post, 222 | RequestUri = new Uri(url), 223 | Content = content, 224 | }; 225 | 226 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 227 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); 228 | 229 | var resp = await httpClient.SendAsync(request).ConfigureAwait(false); 230 | var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); 231 | return NewtonsoftJsonSerializer.Instance.Deserialize(json); 232 | } 233 | 234 | /// 235 | /// Retrieve a list of topics that the specified registration token subscribe to. 236 | /// 237 | /// The topic name to unsubscribe from. 238 | /// A task that completes with a , giving details about the topic retrieve operations. 239 | public async Task GetTopicListAsync(string registrationToken) 240 | { 241 | if (string.IsNullOrWhiteSpace(registrationToken)) 242 | { 243 | throw new ArgumentException("Registration token can not be empty"); 244 | } 245 | var accessToken = await GetAccessTokenAsync().ConfigureAwait(false); 246 | var body = new TopicListRequest() 247 | { 248 | Token = registrationToken 249 | }; 250 | var url = $"{options.ApiBaseUri}/v1/{options.ClientId}/topic:list"; 251 | var payload = NewtonsoftJsonSerializer.Instance.Serialize(body); 252 | var content = new StringContent(payload, Encoding.UTF8, "application/json"); 253 | var request = new HttpRequestMessage() 254 | { 255 | Method = HttpMethod.Post, 256 | RequestUri = new Uri(url), 257 | Content = content, 258 | }; 259 | 260 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 261 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); 262 | 263 | var resp = await httpClient.SendAsync(request).ConfigureAwait(false); 264 | var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); 265 | return NewtonsoftJsonSerializer.Instance.Deserialize(json); 266 | } 267 | 268 | private async Task GetAccessTokenAsync() 269 | { 270 | string accessToken = tokenResponse?.GetValidAccessToken(); 271 | if (string.IsNullOrEmpty(accessToken)) 272 | { 273 | lock (accessTokenTaskLock) 274 | { 275 | if (accessTokenTask == null) 276 | { 277 | // access token task may be shared by multiple messaging task, thus should not pass cancellationToken to it 278 | accessTokenTask = RequestAccessTokenAsync(default); 279 | } 280 | } 281 | 282 | accessToken = await accessTokenTask.ConfigureAwait(false); 283 | 284 | lock (accessTokenTaskLock) 285 | { 286 | accessTokenTask = null; 287 | } 288 | } 289 | 290 | if (string.IsNullOrEmpty(accessToken)) 291 | { 292 | var error = "AccessToken is null or empty"; 293 | throw new AGConnectException(error); 294 | } 295 | 296 | return accessToken; 297 | } 298 | 299 | private async Task RequestAccessTokenAsync(CancellationToken cancellationToken) 300 | { 301 | string content = string.Format("grant_type=client_credentials&client_secret={0}&client_id={1}", options.ClientSecret, options.ClientId); 302 | var request = new HttpRequestMessage() 303 | { 304 | Method = HttpMethod.Post, 305 | RequestUri = new Uri(TokenUrl), 306 | Content = new StringContent(content), 307 | }; 308 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); 309 | var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 310 | if (response.StatusCode != HttpStatusCode.OK) 311 | { 312 | var error = $"Response status code does not indicate success: {response.StatusCode}"; 313 | throw new AGConnectException(error); 314 | } 315 | 316 | var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 317 | var parsed = JsonConvert.DeserializeObject(json); 318 | switch (parsed.Error) 319 | { 320 | case 1101: 321 | throw new AGConnectException("Get access token failed: invalid request"); 322 | case 1102: 323 | throw new AGConnectException("Get access token failed: missing required param"); 324 | case 1104: 325 | throw new AGConnectException("Get access token failed: unsupported response type"); 326 | case 1105: 327 | throw new AGConnectException("Get access token failed: unsupported grant type"); 328 | case 1107: 329 | throw new AGConnectException("Get access token failed: access denied"); 330 | case 1201: 331 | throw new AGConnectException("Get access token failed: invalid ticket"); 332 | case 1202: 333 | throw new AGConnectException("Get access token failed: invalid sso_st"); 334 | default: 335 | break; 336 | } 337 | 338 | var token = parsed.GetValidAccessToken(); 339 | if (string.IsNullOrEmpty(token)) 340 | { 341 | var error = "AccessToken return by AGC is null or empty"; 342 | throw new AGConnectException(error); 343 | } 344 | 345 | tokenResponse = parsed; 346 | return token; 347 | } 348 | } 349 | } 350 | --------------------------------------------------------------------------------