From b63e9ac9977f381dee5e63837d7a540ae412ad9d Mon Sep 17 00:00:00 2001 From: Bas Gijzen Date: Mon, 8 Jan 2024 13:39:56 +0100 Subject: [PATCH 1/6] Removing redundant 'this' --- CM.Text/BusinessMessaging/CarouselBuilder.cs | 6 +- CM.Text/BusinessMessaging/MessageBuilder.cs | 44 ++++++------ .../Model/MultiChannel/MediaContent.cs | 6 +- .../Model/MultiChannel/MediaMessage.cs | 2 +- .../Model/MultiChannel/RichContent.cs | 24 +++---- .../Model/MultiChannel/TextMessage.cs | 2 +- CM.Text/TextClient.cs | 18 ++--- CM.Text/TextClientFactory.cs | 6 +- .../JetBrains.Annotations.cs | 72 +++++++++---------- 9 files changed, 90 insertions(+), 90 deletions(-) diff --git a/CM.Text/BusinessMessaging/CarouselBuilder.cs b/CM.Text/BusinessMessaging/CarouselBuilder.cs index 5c43d3c..5431e1f 100644 --- a/CM.Text/BusinessMessaging/CarouselBuilder.cs +++ b/CM.Text/BusinessMessaging/CarouselBuilder.cs @@ -19,7 +19,7 @@ public class CarouselBuilder /// public CarouselBuilder(CarouselCardWidth carouselCardWidth) { - this._carouselCardWidth = carouselCardWidth; + _carouselCardWidth = carouselCardWidth; } /// @@ -29,7 +29,7 @@ public CarouselBuilder(CarouselCardWidth carouselCardWidth) /// public CarouselBuilder AddCard(RichCard card) { - this._cards.Add(card); + _cards.Add(card); return this; } @@ -41,7 +41,7 @@ public CarouselMessage Build() { return new CarouselMessage { - Carousel = new Carousel {Cards = this._cards.ToArray(), CarouselCardWidth = this._carouselCardWidth} + Carousel = new Carousel {Cards = _cards.ToArray(), CarouselCardWidth = _carouselCardWidth} }; } } diff --git a/CM.Text/BusinessMessaging/MessageBuilder.cs b/CM.Text/BusinessMessaging/MessageBuilder.cs index e3b6839..9a96f17 100644 --- a/CM.Text/BusinessMessaging/MessageBuilder.cs +++ b/CM.Text/BusinessMessaging/MessageBuilder.cs @@ -24,7 +24,7 @@ public class MessageBuilder /// public MessageBuilder(string messageText, string from, params string[] to) { - this._message = new Message + _message = new Message { Body = new Body { @@ -44,8 +44,8 @@ public MessageBuilder(string messageText, string from, params string[] to) /// public Message Build() { - this._message.RichContent = this._richContent; - return this._message; + _message.RichContent = _richContent; + return _message; } /// @@ -59,7 +59,7 @@ public Message Build() /// public MessageBuilder WithAllowedChannels(params Channel[] channels) { - this._message.AllowedChannels = channels; + _message.AllowedChannels = channels; return this; } @@ -70,7 +70,7 @@ public MessageBuilder WithAllowedChannels(params Channel[] channels) /// public MessageBuilder WithReference(string reference) { - this._message.Reference = reference; + _message.Reference = reference; return this; } @@ -97,7 +97,7 @@ public MessageBuilder WithReference(string reference) /// public MessageBuilder WithValidityPeriod(string period) { - this._message.Validity = period; + _message.Validity = period; return this; } @@ -110,10 +110,10 @@ public MessageBuilder WithValidityPeriod(string period) /// public MessageBuilder WithRichMessage(IRichMessage richMessage) { - if (this._richContent == null) - this._richContent = new RichContent(); + if (_richContent == null) + _richContent = new RichContent(); - this._richContent.AddConversationPart(richMessage); + _richContent.AddConversationPart(richMessage); return this; } @@ -125,10 +125,10 @@ public MessageBuilder WithRichMessage(IRichMessage richMessage) /// public MessageBuilder WithSuggestions(params SuggestionBase[] suggestions) { - if (this._richContent == null) - this._richContent = new RichContent(); + if (_richContent == null) + _richContent = new RichContent(); - this._richContent.Suggestions = suggestions; + _richContent.Suggestions = suggestions; return this; } @@ -138,7 +138,7 @@ public MessageBuilder WithSuggestions(params SuggestionBase[] suggestions) /// public MessageBuilder WitHybridAppKey(Guid appKey) { - this._message.HybridAppKey = appKey; + _message.HybridAppKey = appKey; return this; } @@ -150,10 +150,10 @@ public MessageBuilder WitHybridAppKey(Guid appKey) /// public MessageBuilder WithTemplate(TemplateMessage template) { - if (this._richContent == null) - this._richContent = new RichContent(); + if (_richContent == null) + _richContent = new RichContent(); - this._richContent.AddConversationPart(template); + _richContent.AddConversationPart(template); return this; } @@ -164,10 +164,10 @@ public MessageBuilder WithTemplate(TemplateMessage template) /// public MessageBuilder WithInteractive(WhatsAppInteractiveMessage interactive) { - if (this._richContent == null) - this._richContent = new RichContent(); + if (_richContent == null) + _richContent = new RichContent(); - this._richContent.AddConversationPart(interactive); + _richContent.AddConversationPart(interactive); return this; } @@ -178,10 +178,10 @@ public MessageBuilder WithInteractive(WhatsAppInteractiveMessage interactive) /// public MessageBuilder WithApplePay(ApplePayRequest applePayRequest) { - if (this._richContent == null) - this._richContent = new RichContent(); + if (_richContent == null) + _richContent = new RichContent(); - this._richContent.AddConversationPart(applePayRequest); + _richContent.AddConversationPart(applePayRequest); return this; } } diff --git a/CM.Text/BusinessMessaging/Model/MultiChannel/MediaContent.cs b/CM.Text/BusinessMessaging/Model/MultiChannel/MediaContent.cs index 8a43784..599c54d 100644 --- a/CM.Text/BusinessMessaging/Model/MultiChannel/MediaContent.cs +++ b/CM.Text/BusinessMessaging/Model/MultiChannel/MediaContent.cs @@ -24,9 +24,9 @@ public MediaContent() /// public MediaContent(string mediaName, string mediaUri, string mimeType) { - this.MediaName = mediaName; - this.MediaUri = mediaUri; - this.MimeType = mimeType; + MediaName = mediaName; + MediaUri = mediaUri; + MimeType = mimeType; } /// diff --git a/CM.Text/BusinessMessaging/Model/MultiChannel/MediaMessage.cs b/CM.Text/BusinessMessaging/Model/MultiChannel/MediaMessage.cs index 6a14e04..b4aed6d 100644 --- a/CM.Text/BusinessMessaging/Model/MultiChannel/MediaMessage.cs +++ b/CM.Text/BusinessMessaging/Model/MultiChannel/MediaMessage.cs @@ -25,7 +25,7 @@ public MediaMessage() /// public MediaMessage(string mediaName, string mediaUri, string mimeType) { - this.Media = new MediaContent(mediaName, mediaUri, mimeType); + Media = new MediaContent(mediaName, mediaUri, mimeType); } /// diff --git a/CM.Text/BusinessMessaging/Model/MultiChannel/RichContent.cs b/CM.Text/BusinessMessaging/Model/MultiChannel/RichContent.cs index 3018429..01b6ff9 100644 --- a/CM.Text/BusinessMessaging/Model/MultiChannel/RichContent.cs +++ b/CM.Text/BusinessMessaging/Model/MultiChannel/RichContent.cs @@ -16,8 +16,8 @@ public class RichContent /// public RichContent() { - this.Conversation = null; - this.Suggestions = null; + Conversation = null; + Suggestions = null; } /// @@ -38,14 +38,14 @@ public RichContent() /// public void AddConversationPart(IRichMessage part) { - if (this.Conversation == null) - this.Conversation = new[] {part}; + if (Conversation == null) + Conversation = new[] {part}; else { - var newArr = this.Conversation; - Array.Resize(ref newArr, this.Conversation.Length + 1); + var newArr = Conversation; + Array.Resize(ref newArr, Conversation.Length + 1); newArr[newArr.Length - 1] = part; - this.Conversation = newArr; + Conversation = newArr; } } @@ -55,14 +55,14 @@ public void AddConversationPart(IRichMessage part) /// public void AddSuggestion(SuggestionBase suggestion) { - if (this.Suggestions == null) - this.Suggestions = new[] {suggestion}; + if (Suggestions == null) + Suggestions = new[] {suggestion}; else { - var newArr = this.Suggestions; - Array.Resize(ref newArr, this.Suggestions.Length + 1); + var newArr = Suggestions; + Array.Resize(ref newArr, Suggestions.Length + 1); newArr[newArr.Length - 1] = suggestion; - this.Suggestions = newArr; + Suggestions = newArr; } } } diff --git a/CM.Text/BusinessMessaging/Model/MultiChannel/TextMessage.cs b/CM.Text/BusinessMessaging/Model/MultiChannel/TextMessage.cs index 2490384..5287a68 100644 --- a/CM.Text/BusinessMessaging/Model/MultiChannel/TextMessage.cs +++ b/CM.Text/BusinessMessaging/Model/MultiChannel/TextMessage.cs @@ -23,7 +23,7 @@ public TextMessage() /// public TextMessage(string text) { - this.Text = text; + Text = text; } /// diff --git a/CM.Text/TextClient.cs b/CM.Text/TextClient.cs index a3afb47..abdc07a 100644 --- a/CM.Text/TextClient.cs +++ b/CM.Text/TextClient.cs @@ -54,9 +54,9 @@ public TextClient(Guid apiKey, [CanBeNull] HttpClient httpClient): this(apiKey, [PublicAPI] public TextClient(Guid apiKey, [CanBeNull] HttpClient httpClient, [CanBeNull] Uri endPointOverride) { - this._apiKey = apiKey; - this._httpClient = httpClient ?? ClientSingletonLazy.Value; - this._endPointOverride = endPointOverride; + _apiKey = apiKey; + _httpClient = httpClient ?? ClientSingletonLazy.Value; + _endPointOverride = endPointOverride; } /// @@ -70,16 +70,16 @@ public async Task SendMessageAsync( { using (var request = new HttpRequestMessage( HttpMethod.Post, - this._endPointOverride ?? new Uri(Constant.BusinessMessagingGatewayJsonEndpoint) + _endPointOverride ?? new Uri(Constant.BusinessMessagingGatewayJsonEndpoint) )) { request.Content = new StringContent( - BusinessMessagingApi.GetHttpPostBody(this._apiKey, messageText, from, to, reference), + BusinessMessagingApi.GetHttpPostBody(_apiKey, messageText, from, to, reference), Encoding.UTF8, Constant.BusinessMessagingGatewayMediaTypeJson ); - using (var requestResult = await this._httpClient.SendAsync(request, cancellationToken) + using (var requestResult = await _httpClient.SendAsync(request, cancellationToken) .ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); @@ -105,16 +105,16 @@ public async Task SendMessageAsync( { using (var request = new HttpRequestMessage( HttpMethod.Post, - this._endPointOverride ?? new Uri(Constant.BusinessMessagingGatewayJsonEndpoint) + _endPointOverride ?? new Uri(Constant.BusinessMessagingGatewayJsonEndpoint) )) { request.Content = new StringContent( - BusinessMessagingApi.GetHttpPostBody(this._apiKey, message), + BusinessMessagingApi.GetHttpPostBody(_apiKey, message), Encoding.UTF8, Constant.BusinessMessagingGatewayMediaTypeJson ); - using (var requestResult = await this._httpClient.SendAsync(request, cancellationToken) + using (var requestResult = await _httpClient.SendAsync(request, cancellationToken) .ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/CM.Text/TextClientFactory.cs b/CM.Text/TextClientFactory.cs index 028c429..82ec0d8 100644 --- a/CM.Text/TextClientFactory.cs +++ b/CM.Text/TextClientFactory.cs @@ -35,14 +35,14 @@ public class TextClientFactory : ITextClientFactory /// (Optional) The end point to use, instead of the default "https://gw.cmtelecom.com/v1.0/message". public TextClientFactory(HttpClient httpClient, Uri endPointOverride = null) { - this._httpClient = httpClient; - this._endPointOverride = endPointOverride; + _httpClient = httpClient; + _endPointOverride = endPointOverride; } /// public ITextClient GetClient(Guid productToken) { - return new TextClient(productToken, this._httpClient, this._endPointOverride); + return new TextClient(productToken, _httpClient, _endPointOverride); } } } diff --git a/CM.Text/[JetBrains.Annotations]/JetBrains.Annotations.cs b/CM.Text/[JetBrains.Annotations]/JetBrains.Annotations.cs index 9b9858e..697bc7a 100644 --- a/CM.Text/[JetBrains.Annotations]/JetBrains.Annotations.cs +++ b/CM.Text/[JetBrains.Annotations]/JetBrains.Annotations.cs @@ -150,7 +150,7 @@ internal sealed class StringFormatMethodAttribute : Attribute /// public StringFormatMethodAttribute([NotNull] string formatParameterName) { - this.FormatParameterName = formatParameterName; + FormatParameterName = formatParameterName; } [NotNull] @@ -166,7 +166,7 @@ internal sealed class ValueProviderAttribute : Attribute { public ValueProviderAttribute([NotNull] string name) { - this.Name = name; + Name = name; } [NotNull] @@ -257,7 +257,7 @@ public NotifyPropertyChangedInvocatorAttribute() public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) { - this.ParameterName = parameterName; + ParameterName = parameterName; } [CanBeNull] @@ -328,8 +328,8 @@ public ContractAnnotationAttribute([NotNull] string contract) : this(contract, f public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) { - this.Contract = contract; - this.ForceFullStates = forceFullStates; + Contract = contract; + ForceFullStates = forceFullStates; } [NotNull] @@ -358,7 +358,7 @@ public LocalizationRequiredAttribute() : this(true) public LocalizationRequiredAttribute(bool required) { - this.Required = required; + Required = required; } public bool Required { get; } @@ -410,7 +410,7 @@ internal sealed class BaseTypeRequiredAttribute : Attribute { public BaseTypeRequiredAttribute([NotNull] Type baseType) { - this.BaseType = baseType; + BaseType = baseType; } [NotNull] @@ -440,8 +440,8 @@ public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) : this(Implic public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { - this.UseKindFlags = useKindFlags; - this.TargetFlags = targetFlags; + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; } public ImplicitUseTargetFlags TargetFlags { get; } @@ -471,8 +471,8 @@ public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) : this(Impl public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { - this.UseKindFlags = useKindFlags; - this.TargetFlags = targetFlags; + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; } [UsedImplicitly] @@ -533,7 +533,7 @@ public PublicAPIAttribute() public PublicAPIAttribute([NotNull] string comment) { - this.Comment = comment; + Comment = comment; } [CanBeNull] @@ -580,7 +580,7 @@ public MustUseReturnValueAttribute() public MustUseReturnValueAttribute([NotNull] string justification) { - this.Justification = justification; + Justification = justification; } [CanBeNull] @@ -630,7 +630,7 @@ public PathReferenceAttribute() public PathReferenceAttribute([NotNull] [PathReference] string basePath) { - this.BasePath = basePath; + BasePath = basePath; } [CanBeNull] @@ -725,7 +725,7 @@ internal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute { public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) { - this.Format = format; + Format = format; } [NotNull] @@ -737,7 +737,7 @@ internal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute { public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) { - this.Format = format; + Format = format; } [NotNull] @@ -749,7 +749,7 @@ internal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute { public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) { - this.Format = format; + Format = format; } [NotNull] @@ -761,7 +761,7 @@ internal sealed class AspMvcMasterLocationFormatAttribute : Attribute { public AspMvcMasterLocationFormatAttribute(string format) { - this.Format = format; + Format = format; } public string Format { get; } @@ -772,7 +772,7 @@ internal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute { public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) { - this.Format = format; + Format = format; } [NotNull] @@ -784,7 +784,7 @@ internal sealed class AspMvcViewLocationFormatAttribute : Attribute { public AspMvcViewLocationFormatAttribute([NotNull] string format) { - this.Format = format; + Format = format; } [NotNull] @@ -806,7 +806,7 @@ public AspMvcActionAttribute() public AspMvcActionAttribute([NotNull] string anonymousProperty) { - this.AnonymousProperty = anonymousProperty; + AnonymousProperty = anonymousProperty; } [CanBeNull] @@ -827,7 +827,7 @@ public AspMvcAreaAttribute() public AspMvcAreaAttribute([NotNull] string anonymousProperty) { - this.AnonymousProperty = anonymousProperty; + AnonymousProperty = anonymousProperty; } [CanBeNull] @@ -849,7 +849,7 @@ public AspMvcControllerAttribute() public AspMvcControllerAttribute([NotNull] string anonymousProperty) { - this.AnonymousProperty = anonymousProperty; + AnonymousProperty = anonymousProperty; } [CanBeNull] @@ -979,7 +979,7 @@ public HtmlElementAttributesAttribute() public HtmlElementAttributesAttribute([NotNull] string name) { - this.Name = name; + Name = name; } [CanBeNull] @@ -991,7 +991,7 @@ internal sealed class HtmlAttributeValueAttribute : Attribute { public HtmlAttributeValueAttribute([NotNull] string name) { - this.Name = name; + Name = name; } [NotNull] @@ -1017,7 +1017,7 @@ internal sealed class CollectionAccessAttribute : Attribute { public CollectionAccessAttribute(CollectionAccessType collectionAccessType) { - this.CollectionAccessType = collectionAccessType; + CollectionAccessType = collectionAccessType; } public CollectionAccessType CollectionAccessType { get; } @@ -1059,7 +1059,7 @@ internal sealed class AssertionConditionAttribute : Attribute { public AssertionConditionAttribute(AssertionConditionType conditionType) { - this.ConditionType = conditionType; + ConditionType = conditionType; } public AssertionConditionType ConditionType { get; } @@ -1148,8 +1148,8 @@ internal sealed class AspChildControlTypeAttribute : Attribute { public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) { - this.TagName = tagName; - this.ControlType = controlType; + TagName = tagName; + ControlType = controlType; } [NotNull] @@ -1179,7 +1179,7 @@ internal sealed class AspRequiredAttributeAttribute : Attribute { public AspRequiredAttributeAttribute([NotNull] string attribute) { - this.Attribute = attribute; + Attribute = attribute; } [NotNull] @@ -1191,7 +1191,7 @@ internal sealed class AspTypePropertyAttribute : Attribute { public AspTypePropertyAttribute(bool createConstructorReferences) { - this.CreateConstructorReferences = createConstructorReferences; + CreateConstructorReferences = createConstructorReferences; } public bool CreateConstructorReferences { get; } @@ -1202,7 +1202,7 @@ internal sealed class RazorImportNamespaceAttribute : Attribute { public RazorImportNamespaceAttribute([NotNull] string name) { - this.Name = name; + Name = name; } [NotNull] @@ -1214,8 +1214,8 @@ internal sealed class RazorInjectionAttribute : Attribute { public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) { - this.Type = type; - this.FieldName = fieldName; + Type = type; + FieldName = fieldName; } [NotNull] @@ -1230,7 +1230,7 @@ internal sealed class RazorDirectiveAttribute : Attribute { public RazorDirectiveAttribute([NotNull] string directive) { - this.Directive = directive; + Directive = directive; } [NotNull] @@ -1272,4 +1272,4 @@ internal sealed class RazorWriteMethodParameterAttribute : Attribute internal sealed class NoReorder : Attribute { } -} \ No newline at end of file +} From 3501d8d1af6937206912fc4650c893521f5688a0 Mon Sep 17 00:00:00 2001 From: Bas Gijzen Date: Mon, 8 Jan 2024 14:24:38 +0100 Subject: [PATCH 2/6] Updating dependencies --- CM.Text.Tests/CM.Text.Tests.csproj | 13 ++++++++----- CM.Text/CM.Text.csproj | 2 +- README.md | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CM.Text.Tests/CM.Text.Tests.csproj b/CM.Text.Tests/CM.Text.Tests.csproj index 218649b..76e45a1 100644 --- a/CM.Text.Tests/CM.Text.Tests.csproj +++ b/CM.Text.Tests/CM.Text.Tests.csproj @@ -9,11 +9,14 @@ - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/CM.Text/CM.Text.csproj b/CM.Text/CM.Text.csproj index 91933e3..939a1f9 100644 --- a/CM.Text/CM.Text.csproj +++ b/CM.Text/CM.Text.csproj @@ -63,7 +63,7 @@ - + diff --git a/README.md b/README.md index 65a5772..038f00b 100644 --- a/README.md +++ b/README.md @@ -415,3 +415,19 @@ var client = new TextClient(apiKey); var message = builder.Build(); var result = await client.SendMessageAsync(message); ``` + +## Using the OTP API +Send a simple OTP code +```cs + var result = await client.SendOtp("Sender_Name", "316012345678", "sms").ConfigureAwait(false); +``` + +Verify the response code +```cs + client.VerifyOtp("OTP-ID", "code") +``` + +More advanced scenarios +```cs + +``` \ No newline at end of file From 09bec63ae45e990186e5a8a0a5aa11452815297d Mon Sep 17 00:00:00 2001 From: Bas Gijzen Date: Mon, 8 Jan 2024 16:50:26 +0100 Subject: [PATCH 3/6] Implementing OTP API --- .editorconfig | 2 +- CM.Text/Common/Constant.cs | 4 + CM.Text/Identity/OtpRequest.cs | 83 ++++++++++++++++++++ CM.Text/Identity/OtpRequestBuilder.cs | 105 ++++++++++++++++++++++++++ CM.Text/Identity/OtpResult.cs | 37 +++++++++ CM.Text/TextClient.cs | 72 ++++++++++++++++++ README.md | 13 ++-- 7 files changed, 309 insertions(+), 7 deletions(-) create mode 100644 CM.Text/Identity/OtpRequest.cs create mode 100644 CM.Text/Identity/OtpRequestBuilder.cs create mode 100644 CM.Text/Identity/OtpResult.cs diff --git a/.editorconfig b/.editorconfig index 9d52482..a2a4e25 100644 --- a/.editorconfig +++ b/.editorconfig @@ -24,7 +24,7 @@ dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = false # this. preferences -dotnet_style_qualification_for_field = true +dotnet_style_qualification_for_field = false:silent dotnet_style_qualification_for_property = false:silent dotnet_style_qualification_for_method = false:silent dotnet_style_qualification_for_event = false:silent diff --git a/CM.Text/Common/Constant.cs b/CM.Text/Common/Constant.cs index 14e53d2..f6fd876 100644 --- a/CM.Text/Common/Constant.cs +++ b/CM.Text/Common/Constant.cs @@ -5,6 +5,10 @@ internal static class Constant internal static readonly string TextSdkReference = $"text-sdk-dotnet-{typeof(TextClient).Assembly.GetName().Version}"; internal const string BusinessMessagingGatewayJsonEndpoint = "https://gw.cmtelecom.com/v1.0/message"; + + internal const string OtpRequestEndpoint = "https://api.cm.com/otp/v2/otp"; + internal const string OtpVerifyEndpointPrefix = "https://api.cm.com/otp/v2/otp/{0}/verify"; + internal static readonly string BusinessMessagingGatewayMediaTypeJson = "application/json"; internal static readonly string BusinessMessagingBodyTypeAuto = "AUTO"; internal static readonly int BusinessMessagingMessagePartsMinDefault = 1; diff --git a/CM.Text/Identity/OtpRequest.cs b/CM.Text/Identity/OtpRequest.cs new file mode 100644 index 0000000..14c8b05 --- /dev/null +++ b/CM.Text/Identity/OtpRequest.cs @@ -0,0 +1,83 @@ +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace CM.Text.Identity +{ + /// + /// A request to send an OTP towards an end-user. + /// + [PublicAPI] + public class OtpRequest + { + /// + /// Required: This is the sender name. + /// The maximum length is 11 alphanumerical characters or 16 digits. Example: 'MyCompany' + /// + [JsonPropertyName("from")] + public string From { get; set; } + + /// + /// Required: The destination mobile numbers. + /// This value should be in international format. + /// A single mobile number per request. Example: '00447911123456' + /// + [JsonPropertyName("to")] + public string To { get; set; } + + /// + /// The length of the code (min 4, max 10). default: 5. + /// + [JsonPropertyName("digits")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? Digits { get; set; } + + /// + /// The expiry in seconds (min 10, max 3600). default: 60 seconds. + /// + [JsonPropertyName("expiry")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? Expiry { get; set; } + + /// + /// The channel to send the code. + /// Supported values: auto, sms, push, whatsapp, voice, email. + /// Channel auto is only available with a SOLiD subscription. + /// + [JsonPropertyName("channel")] + public string Channel { get; set; } = "sms"; + + /// + /// The locale, for WhatsApp supported values: en, nl, fr, de, it, es. + /// Default: en + /// + /// For Voice: the spoken language in the voice call, + /// supported values: de-DE, en-AU, en-GB, en-IN, en-US, es-ES, fr-CA, fr-FR, it-IT, ja-JP, nl-NL + /// Default: en-GB. + /// + /// For Email: The locale for the email template. + /// + [JsonPropertyName("locale")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [CanBeNull] + public string Locale { get; set; } + + /// + /// The app key, when is 'push' + /// + [JsonPropertyName("pushAppKey")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [CanBeNull] + public string PushAppKey { get; set; } + + /// + /// For WhatsApp, set a custom message. You can use the placeholder {code}, this will be replaced by the actual code. + /// Example: Your code is: {code}. This is only used as a fallback in case the message could not be delivered via WhatsApp. + /// + /// For email, Set a custom message to be used in the email message. Do not include the {code} placeholder. + /// + [JsonPropertyName("message")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [CanBeNull] + public string Message { get; set; } + } +} diff --git a/CM.Text/Identity/OtpRequestBuilder.cs b/CM.Text/Identity/OtpRequestBuilder.cs new file mode 100644 index 0000000..b15a16d --- /dev/null +++ b/CM.Text/Identity/OtpRequestBuilder.cs @@ -0,0 +1,105 @@ + +using JetBrains.Annotations; + +namespace CM.Text.Identity +{ + /// + /// Builder class to construct messages + /// + [PublicAPI] + public class OtpRequestBuilder + { + private readonly OtpRequest _otpRequest; + + /// + /// Creates a new OtpRequestBuilder + /// + /// + /// + public OtpRequestBuilder(string from, string to) + { + _otpRequest = new OtpRequest { From = from, To = to }; + } + + /// + /// Constructs the request. + /// + /// + public OtpRequest Build() + { + return _otpRequest; + } + + /// + /// Set the channel + /// + public OtpRequestBuilder WithChannel(string channel) + { + _otpRequest.Channel = channel; + return this; + } + + /// + /// Sets The length of the code (min 4, max 10). default: 5. + /// + /// + /// + public OtpRequestBuilder WithDigits(int digits) + { + _otpRequest.Digits = digits; + return this; + } + + /// + /// The expiry in seconds (min 10, max 3600). default: 60 seconds. + /// + public OtpRequestBuilder WithExpiry(int expiryInSeconds) + { + _otpRequest.Expiry = expiryInSeconds; + return this; + } + + /// + /// The locale, for WhatsApp supported values: en, nl, fr, de, it, es. + /// Default: en + /// + /// For Voice: the spoken language in the voice call, + /// supported values: de-DE, en-AU, en-GB, en-IN, en-US, es-ES, fr-CA, fr-FR, it-IT, ja-JP, nl-NL + /// Default: en-GB. + /// + /// For Email: The locale for the email template. + /// + /// + /// + public OtpRequestBuilder WithLocale(string locale) + { + _otpRequest.Locale = locale; + return this; + } + + /// + /// The app key, when the channel is 'push' + /// + /// + /// + public OtpRequestBuilder WithPushAppKey(string pushAppKey) + { + _otpRequest.PushAppKey = pushAppKey; + return this; + } + + /// + /// For WhatsApp, set a custom message. You can use the placeholder {code}, this will be replaced by the actual code. + /// Example: Your code is: {code}. This is only used as a fallback in case the message could not be delivered via WhatsApp. + /// + /// For email, Set a custom message to be used in the email message. Do not include the {code} placeholder. + /// + /// + /// + public OtpRequestBuilder WithMessage(string message) + { + _otpRequest.Message = message; + return this; + } + } +} diff --git a/CM.Text/Identity/OtpResult.cs b/CM.Text/Identity/OtpResult.cs new file mode 100644 index 0000000..966408d --- /dev/null +++ b/CM.Text/Identity/OtpResult.cs @@ -0,0 +1,37 @@ +using System; +using System.Text.Json.Serialization; + +namespace CM.Text.Identity +{ + /// + /// The result of an OTP request. + /// + public class OtpResult + { + /// + /// The identifier of the OTP. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + /// + /// The channel used to send the code. + /// + [JsonPropertyName("channel")] + public string Channel { get; set; } + /// + /// Indicates if the code was valid. + /// + [JsonPropertyName("verified")] + public bool Verified { get; set; } + /// + /// The date the OTP was created. + /// + [JsonPropertyName("createdAt")] + public DateTime CreatedAt { get; set; } + /// + /// The date the OTP will expire. + /// + [JsonPropertyName("expiresAt")] + public DateTime ExpiresAt { get; set; } + } +} diff --git a/CM.Text/TextClient.cs b/CM.Text/TextClient.cs index abdc07a..24b6d3c 100644 --- a/CM.Text/TextClient.cs +++ b/CM.Text/TextClient.cs @@ -3,11 +3,13 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using CM.Text.BusinessMessaging; using CM.Text.BusinessMessaging.Model; using CM.Text.Common; +using CM.Text.Identity; using CM.Text.Interfaces; using JetBrains.Annotations; @@ -126,5 +128,75 @@ await requestResult.Content.ReadAsStringAsync() } } } + + /// + /// Sends an One Time Password asynchronously. + /// + /// The otp to send. + /// The cancellation token. + /// + [PublicAPI] + public async Task SendOtpAsync( + OtpRequest otpRequest, + CancellationToken cancellationToken = default(CancellationToken)) + { + using (var request = new HttpRequestMessage( + HttpMethod.Post, + _endPointOverride ?? new Uri(Constant.OtpRequestEndpoint) + )) + { + request.Content = new StringContent( + JsonSerializer.Serialize(otpRequest), + Encoding.UTF8, + Constant.BusinessMessagingGatewayMediaTypeJson + ); + + return await SendOtpApiRequestAsync(request, cancellationToken); + } + } + + /// + /// Checks an One Time Password asynchronously. + /// + /// id of the OTP to check. + /// The code the end user used + /// The cancellation token. + /// + [PublicAPI] + public async Task VerifyOtpAsync( + string id, + string code, + CancellationToken cancellationToken = default(CancellationToken)) + { + using (var request = new HttpRequestMessage( + HttpMethod.Post, + _endPointOverride ?? new Uri(string.Format(Constant.OtpVerifyEndpointPrefix, id)) + )) + { + request.Content = new StringContent( + JsonSerializer.Serialize(new { code = code } ), + Encoding.UTF8, + Constant.BusinessMessagingGatewayMediaTypeJson + ); + + return await SendOtpApiRequestAsync(request, cancellationToken); + } + } + + private async Task SendOtpApiRequestAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + request.Headers.Add("X-CM-ProductToken", _apiKey.ToString()); + using (var requestResult = await _httpClient.SendAsync(request, cancellationToken) + .ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + + return JsonSerializer.Deserialize( + await requestResult.Content.ReadAsStringAsync() + .ConfigureAwait(false) + ); + } + } } } diff --git a/README.md b/README.md index 038f00b..98e61b3 100644 --- a/README.md +++ b/README.md @@ -419,15 +419,16 @@ var result = await client.SendMessageAsync(message); ## Using the OTP API Send a simple OTP code ```cs - var result = await client.SendOtp("Sender_Name", "316012345678", "sms").ConfigureAwait(false); + var client = new TextClient(new Guid(ConfigurationManager.AppSettings["ApiKey"])); + var otpBuilder = new OtpRequestBuilder("Sender_name", "Recipient_PhoneNumber"); + otpBuilder.WithMessage("Your otp code is {code}."); + var result = await textClient.SendOtpAsync(otpBuilder.Build()); ``` Verify the response code ```cs - client.VerifyOtp("OTP-ID", "code") + var verifyResult = client.VerifyOtp("OTP-ID", "code"); + bool isValid = verifyResult.Verified; ``` -More advanced scenarios -```cs - -``` \ No newline at end of file +For more advanced scenarios see also https://developers.cm.com/identity/docs/one-time-password-create \ No newline at end of file From fb3274c8b47014d591b42a1a9cdef824ef3e35c5 Mon Sep 17 00:00:00 2001 From: Bas Gijzen Date: Mon, 8 Jan 2024 16:53:34 +0100 Subject: [PATCH 4/6] Restore JetBrains.Annotations.cs --- .../JetBrains.Annotations.cs | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/CM.Text/[JetBrains.Annotations]/JetBrains.Annotations.cs b/CM.Text/[JetBrains.Annotations]/JetBrains.Annotations.cs index 697bc7a..90159d9 100644 --- a/CM.Text/[JetBrains.Annotations]/JetBrains.Annotations.cs +++ b/CM.Text/[JetBrains.Annotations]/JetBrains.Annotations.cs @@ -150,7 +150,7 @@ internal sealed class StringFormatMethodAttribute : Attribute /// public StringFormatMethodAttribute([NotNull] string formatParameterName) { - FormatParameterName = formatParameterName; + this.FormatParameterName = formatParameterName; } [NotNull] @@ -166,7 +166,7 @@ internal sealed class ValueProviderAttribute : Attribute { public ValueProviderAttribute([NotNull] string name) { - Name = name; + this.Name = name; } [NotNull] @@ -257,7 +257,7 @@ public NotifyPropertyChangedInvocatorAttribute() public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) { - ParameterName = parameterName; + this.ParameterName = parameterName; } [CanBeNull] @@ -328,8 +328,8 @@ public ContractAnnotationAttribute([NotNull] string contract) : this(contract, f public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) { - Contract = contract; - ForceFullStates = forceFullStates; + this.Contract = contract; + this.ForceFullStates = forceFullStates; } [NotNull] @@ -358,7 +358,7 @@ public LocalizationRequiredAttribute() : this(true) public LocalizationRequiredAttribute(bool required) { - Required = required; + this.Required = required; } public bool Required { get; } @@ -410,7 +410,7 @@ internal sealed class BaseTypeRequiredAttribute : Attribute { public BaseTypeRequiredAttribute([NotNull] Type baseType) { - BaseType = baseType; + this.BaseType = baseType; } [NotNull] @@ -440,8 +440,8 @@ public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) : this(Implic public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; + this.UseKindFlags = useKindFlags; + this.TargetFlags = targetFlags; } public ImplicitUseTargetFlags TargetFlags { get; } @@ -471,8 +471,8 @@ public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) : this(Impl public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; + this.UseKindFlags = useKindFlags; + this.TargetFlags = targetFlags; } [UsedImplicitly] @@ -533,7 +533,7 @@ public PublicAPIAttribute() public PublicAPIAttribute([NotNull] string comment) { - Comment = comment; + this.Comment = comment; } [CanBeNull] @@ -580,7 +580,7 @@ public MustUseReturnValueAttribute() public MustUseReturnValueAttribute([NotNull] string justification) { - Justification = justification; + this.Justification = justification; } [CanBeNull] @@ -630,7 +630,7 @@ public PathReferenceAttribute() public PathReferenceAttribute([NotNull] [PathReference] string basePath) { - BasePath = basePath; + this.BasePath = basePath; } [CanBeNull] @@ -725,7 +725,7 @@ internal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute { public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) { - Format = format; + this.Format = format; } [NotNull] @@ -737,7 +737,7 @@ internal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute { public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) { - Format = format; + this.Format = format; } [NotNull] @@ -749,7 +749,7 @@ internal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute { public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) { - Format = format; + this.Format = format; } [NotNull] @@ -761,7 +761,7 @@ internal sealed class AspMvcMasterLocationFormatAttribute : Attribute { public AspMvcMasterLocationFormatAttribute(string format) { - Format = format; + this.Format = format; } public string Format { get; } @@ -772,7 +772,7 @@ internal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute { public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) { - Format = format; + this.Format = format; } [NotNull] @@ -784,7 +784,7 @@ internal sealed class AspMvcViewLocationFormatAttribute : Attribute { public AspMvcViewLocationFormatAttribute([NotNull] string format) { - Format = format; + this.Format = format; } [NotNull] @@ -806,7 +806,7 @@ public AspMvcActionAttribute() public AspMvcActionAttribute([NotNull] string anonymousProperty) { - AnonymousProperty = anonymousProperty; + this.AnonymousProperty = anonymousProperty; } [CanBeNull] @@ -827,7 +827,7 @@ public AspMvcAreaAttribute() public AspMvcAreaAttribute([NotNull] string anonymousProperty) { - AnonymousProperty = anonymousProperty; + this.AnonymousProperty = anonymousProperty; } [CanBeNull] @@ -849,7 +849,7 @@ public AspMvcControllerAttribute() public AspMvcControllerAttribute([NotNull] string anonymousProperty) { - AnonymousProperty = anonymousProperty; + this.AnonymousProperty = anonymousProperty; } [CanBeNull] @@ -979,7 +979,7 @@ public HtmlElementAttributesAttribute() public HtmlElementAttributesAttribute([NotNull] string name) { - Name = name; + this.Name = name; } [CanBeNull] @@ -991,7 +991,7 @@ internal sealed class HtmlAttributeValueAttribute : Attribute { public HtmlAttributeValueAttribute([NotNull] string name) { - Name = name; + this.Name = name; } [NotNull] @@ -1017,7 +1017,7 @@ internal sealed class CollectionAccessAttribute : Attribute { public CollectionAccessAttribute(CollectionAccessType collectionAccessType) { - CollectionAccessType = collectionAccessType; + this.CollectionAccessType = collectionAccessType; } public CollectionAccessType CollectionAccessType { get; } @@ -1059,7 +1059,7 @@ internal sealed class AssertionConditionAttribute : Attribute { public AssertionConditionAttribute(AssertionConditionType conditionType) { - ConditionType = conditionType; + this.ConditionType = conditionType; } public AssertionConditionType ConditionType { get; } @@ -1148,8 +1148,8 @@ internal sealed class AspChildControlTypeAttribute : Attribute { public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) { - TagName = tagName; - ControlType = controlType; + this.TagName = tagName; + this.ControlType = controlType; } [NotNull] @@ -1179,7 +1179,7 @@ internal sealed class AspRequiredAttributeAttribute : Attribute { public AspRequiredAttributeAttribute([NotNull] string attribute) { - Attribute = attribute; + this.Attribute = attribute; } [NotNull] @@ -1191,7 +1191,7 @@ internal sealed class AspTypePropertyAttribute : Attribute { public AspTypePropertyAttribute(bool createConstructorReferences) { - CreateConstructorReferences = createConstructorReferences; + this.CreateConstructorReferences = createConstructorReferences; } public bool CreateConstructorReferences { get; } @@ -1202,7 +1202,7 @@ internal sealed class RazorImportNamespaceAttribute : Attribute { public RazorImportNamespaceAttribute([NotNull] string name) { - Name = name; + this.Name = name; } [NotNull] @@ -1214,8 +1214,8 @@ internal sealed class RazorInjectionAttribute : Attribute { public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) { - Type = type; - FieldName = fieldName; + this.Type = type; + this.FieldName = fieldName; } [NotNull] @@ -1230,7 +1230,7 @@ internal sealed class RazorDirectiveAttribute : Attribute { public RazorDirectiveAttribute([NotNull] string directive) { - Directive = directive; + this.Directive = directive; } [NotNull] From fb167669f4da742d3a6dc5f3290329c93e7a385f Mon Sep 17 00:00:00 2001 From: Bas Gijzen Date: Mon, 8 Jan 2024 16:54:27 +0100 Subject: [PATCH 5/6] Renaming --- CM.Text/Common/Constant.cs | 2 +- CM.Text/TextClient.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CM.Text/Common/Constant.cs b/CM.Text/Common/Constant.cs index f6fd876..4800299 100644 --- a/CM.Text/Common/Constant.cs +++ b/CM.Text/Common/Constant.cs @@ -7,7 +7,7 @@ internal static class Constant internal const string BusinessMessagingGatewayJsonEndpoint = "https://gw.cmtelecom.com/v1.0/message"; internal const string OtpRequestEndpoint = "https://api.cm.com/otp/v2/otp"; - internal const string OtpVerifyEndpointPrefix = "https://api.cm.com/otp/v2/otp/{0}/verify"; + internal const string OtpVerifyEndpointFormatter = "https://api.cm.com/otp/v2/otp/{0}/verify"; internal static readonly string BusinessMessagingGatewayMediaTypeJson = "application/json"; internal static readonly string BusinessMessagingBodyTypeAuto = "AUTO"; diff --git a/CM.Text/TextClient.cs b/CM.Text/TextClient.cs index 24b6d3c..d7d549c 100644 --- a/CM.Text/TextClient.cs +++ b/CM.Text/TextClient.cs @@ -170,7 +170,7 @@ public async Task VerifyOtpAsync( { using (var request = new HttpRequestMessage( HttpMethod.Post, - _endPointOverride ?? new Uri(string.Format(Constant.OtpVerifyEndpointPrefix, id)) + _endPointOverride ?? new Uri(string.Format(Constant.OtpVerifyEndpointFormatter, id)) )) { request.Content = new StringContent( From 087e30d4a51afa658036e092bb8333ba9eee1be9 Mon Sep 17 00:00:00 2001 From: Bas Gijzen Date: Mon, 8 Jan 2024 17:19:10 +0100 Subject: [PATCH 6/6] Updating version and CHANGELOG.md --- CHANGELOG.md | 4 ++++ CM.Text/CM.Text.csproj | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c23cb6..4f7aca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.8.0] - 2024-01-08 +### Added +- Implementing OTP Request and verify + ## [2.7.0] - 2023-06-26 ### Added - Whatsapp Multi-Product Template Messages diff --git a/CM.Text/CM.Text.csproj b/CM.Text/CM.Text.csproj index 939a1f9..ce4b5ac 100644 --- a/CM.Text/CM.Text.csproj +++ b/CM.Text/CM.Text.csproj @@ -13,12 +13,12 @@ LICENSE icon.png $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/../CHANGELOG.md")) - 2.7.0 + 2.8.0 https://github.com/cmdotcom/text-sdk-dotnet en true - 2.7.0 - 2.7.0 + 2.8.0 + 2.8.0 True