/// <summary> /// Raised when the <see cref="INotificationProcessor"/> has received a notification asynchronously. /// </summary> /// <param name="args">The <see cref="NotificationEventArgs"/> instance containing the event data.</param> /// <returns>The <see cref="Task"/>.</returns> private async Task NotificationProcessor_OnNotificationReceivedAsync(NotificationEventArgs args) { this.GraphLogger.CorrelationId = args.ScenarioId; var headers = new[] { new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.ScenarioId, new[] { args.ScenarioId.ToString() }), new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.ClientRequestId, new[] { args.RequestId.ToString() }), new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.Tenant, new[] { args.TenantId }), }; // Create obfuscation content to match what we // would have gotten from the service, then log. var notifications = new CommsNotifications { Value = new[] { args.Notification } }; var obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(notifications, Formatting.Indented); this.GraphLogger.LogHttpMessage( TraceLevel.Info, TransactionDirection.Incoming, HttpTraceType.HttpRequest, args.CallbackUri.ToString(), HttpMethods.Post, obfuscatedContent, headers, correlationId: args.ScenarioId, requestId: args.RequestId); if (args.ResourceData is Call call) { if (call.State == CallState.Established && call.MediaState?.Audio == MediaState.Active) { await this.BotPlayPromptAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } else if (args.ChangeType == ChangeType.Deleted && call.State == CallState.Terminated) { this.GraphLogger.Log(TraceLevel.Info, $"Call State:{call.State}"); } } else if (args.ResourceData is PlayPromptOperation playPromptOperation) { // checking for the call id sent in ClientContext. if (string.IsNullOrWhiteSpace(playPromptOperation.ClientContext)) { throw new ServiceException(new Error() { Message = "No call id provided in PlayPromptOperation.ClientContext.", }); } else if (playPromptOperation.Status == OperationStatus.Completed) { // The operation has been completed, hang up the call await this.BotHangupCallAsync(playPromptOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false); this.GraphLogger.Log(TraceLevel.Info, $"Disconnecting the call."); } } }
/// <summary> /// Raised when the <see cref="INotificationProcessor"/> has received a notification asynchronously. /// </summary> /// <param name="args">The <see cref="NotificationEventArgs"/> instance containing the event data.</param> /// <returns>The <see cref="Task"/>.</returns> private async Task NotificationProcessor_OnNotificationReceivedAsync(NotificationEventArgs args) { this.GraphLogger.CorrelationId = args.ScenarioId; var headers = new[] { new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.ScenarioId, new[] { args.ScenarioId.ToString() }), new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.ClientRequestId, new[] { args.RequestId.ToString() }), new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.Tenant, new[] { args.TenantId }), }; // Create obfuscation content to match what we // would have gotten from the service, then log. var notifications = new CommsNotifications { Value = new[] { args.Notification } }; var obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(notifications, Formatting.Indented); this.GraphLogger.LogHttpMessage( TraceLevel.Info, TransactionDirection.Incoming, HttpTraceType.HttpRequest, args.CallbackUri.ToString(), HttpMethods.Post, obfuscatedContent, headers, correlationId: args.ScenarioId, requestId: args.RequestId); if (args.ResourceData is Call call) { if (call.State == CallState.Established && call.MediaState?.Audio == MediaState.Active) { this.GraphLogger.Log(TraceLevel.Info, $"Call State:{call.State}"); } else if (args.ChangeType == ChangeType.Deleted && call.State == CallState.Terminated) { this.GraphLogger.Log(TraceLevel.Info, $"Call State:{call.State}"); } } else if (args.Notification.ResourceUrl.EndsWith("/participants") && args.ResourceData is List <object> participantObjects) { this.GraphLogger.Log(TraceLevel.Info, "Total count of participants found in this roster is " + participantObjects.Count()); foreach (var participantObject in participantObjects) { var participant = participantObject as Participant; this.GraphLogger.Log(TraceLevel.Info, "Id: " + participant?.Info?.Identity?.User?.Id.ToString(), "DisplayName: " + participant?.Info?.Identity?.User?.DisplayName.ToString()); } } }
/// <summary> /// Processes the notifications and raises the required callbacks. /// This function should be called in order for the SDK to raise /// any required events and process state changes. /// </summary> /// <param name="client">The stateful client.</param> /// <param name="request">The http request that is incoming from service.</param> /// <returns>Http Response Message after processed by the SDK. This has to /// be returned to the server.</returns> private static async Task <HttpResponseMessage> ProcessNotificationAsync(ICommunicationsClient client, HttpRequestMessage request) { client.NotNull(nameof(client)); request.NotNull(nameof(request)); var stopwatch = Stopwatch.StartNew(); var scenarioId = client.GraphLogger.ParseScenarioId(request); var requestId = client.GraphLogger.ParseRequestId(request); CommsNotifications notifications = null; try { // Parse out the notification content. var content = await request.Content.ReadAsStringAsync().ConfigureAwait(false); var serializer = client.Serializer; notifications = NotificationProcessor.ExtractNotifications(content, serializer); } catch (ServiceException ex) { var statusCode = (int)ex.StatusCode >= 200 ? ex.StatusCode : HttpStatusCode.BadRequest; return(client.LogAndCreateResponse(request, requestId, scenarioId, notifications, statusCode, stopwatch, ex)); } catch (Exception ex) { var statusCode = HttpStatusCode.BadRequest; return(client.LogAndCreateResponse(request, requestId, scenarioId, notifications, statusCode, stopwatch, ex)); } RequestValidationResult result; try { // Autenticate the incoming request. result = await client.AuthenticationProvider .ValidateInboundRequestAsync(request) .ConfigureAwait(false); } catch (Exception ex) { var clientEx = new ClientException( new Error { Code = ErrorConstants.Codes.ClientCallbackError, Message = ErrorConstants.Messages.ClientErrorAuthenticatingRequest, }, ex); throw clientEx; } if (!result.IsValid) { var statusCode = HttpStatusCode.Unauthorized; return(client.LogAndCreateResponse(request, requestId, scenarioId, notifications, statusCode, stopwatch)); } // The request is valid. Let's evaluate any policies on the // incoming call before sending it off to the SDK for processing. var call = notifications?.Value?.FirstOrDefault()?.GetResourceData() as Call; var response = await EvaluateAndHandleIncomingCallPoliciesAsync(call).ConfigureAwait(false); if (response != null) { var level = client.GraphLogger.LogHttpRequest(request, response.StatusCode, notifications); client.GraphLogger.LogHttpResponse(level, request, response, stopwatch.ElapsedMilliseconds); stopwatch.Stop(); return(response); } try { var additionalData = request.GetHttpAndContentHeaders().ToDictionary( pair => pair.Key, pair => (object)string.Join(",", pair.Value), StringComparer.OrdinalIgnoreCase); client.ProcessNotifications(request.RequestUri, notifications, result.TenantId, requestId, scenarioId, additionalData); } catch (ServiceException ex) { var statusCode = (int)ex.StatusCode >= 200 ? ex.StatusCode : HttpStatusCode.InternalServerError; return(client.LogAndCreateResponse(request, requestId, scenarioId, notifications, statusCode, stopwatch, ex)); } catch (Exception ex) { var statusCode = HttpStatusCode.InternalServerError; return(client.LogAndCreateResponse(request, requestId, scenarioId, notifications, statusCode, stopwatch, ex)); } return(client.LogAndCreateResponse(request, requestId, scenarioId, notifications, HttpStatusCode.Accepted, stopwatch)); }
/// <summary> /// Raised when the <see cref="INotificationProcessor"/> has received a notification asynchronously. /// </summary> /// <param name="args">The <see cref="NotificationEventArgs"/> instance containing the event data.</param> /// <returns>The <see cref="Task"/>.</returns> private async Task NotificationProcessor_OnNotificationReceivedAsync(NotificationEventArgs args) { this.GraphLogger.CorrelationId = args.ScenarioId; var headers = new[] { new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.ScenarioId, new[] { args.ScenarioId.ToString() }), new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.ClientRequestId, new[] { args.RequestId.ToString() }), new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.Tenant, new[] { args.TenantId }), }; // Create obfuscation content to match what we // would have gotten from the service, then log. var notifications = new CommsNotifications { Value = new[] { args.Notification } }; var obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(notifications, Formatting.Indented); this.GraphLogger.LogHttpMessage( TraceLevel.Info, TransactionDirection.Incoming, HttpTraceType.HttpRequest, args.CallbackUri.ToString(), HttpMethods.Post, obfuscatedContent, headers, correlationId: args.ScenarioId, requestId: args.RequestId); if (args.ResourceData is Call call) { if (args.ChangeType == ChangeType.Created && call.State == CallState.Incoming) { await this.BotAnswerIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } else if (args.ChangeType == ChangeType.Updated && call.State == CallState.Established) { this.GraphLogger.Log(TraceLevel.Info, "In Established"); if (call.ToneInfo == null && call.MediaState.Audio == MediaState.Active) { await this.BotPlayNotificationPromptAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } else if (call.ToneInfo?.SequenceId == 1) { InvitationParticipantInfo transferTarget = null; if (call.ToneInfo.Tone == Tone.Tone1) { transferTarget = new InvitationParticipantInfo { Identity = new IdentitySet { User = new Identity { Id = ConfigurationManager.AppSetting["ObjectIds:First"], }, }, }; } else if (call.ToneInfo.Tone == Tone.Tone2) { transferTarget = new InvitationParticipantInfo { Identity = new IdentitySet { User = new Identity { Id = ConfigurationManager.AppSetting["ObjectIds:Second"], }, }, }; } else { transferTarget = new InvitationParticipantInfo { Identity = new IdentitySet { User = new Identity { Id = ConfigurationManager.AppSetting["ObjectIds:Third"], }, }, }; } await this.BotTransferCallAsync(transferTarget, call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } return; } else if (args.ChangeType == ChangeType.Deleted && call.State == CallState.Terminated) { this.GraphLogger.Log(TraceLevel.Info, "Call is Terminated now"); } return; } else if (args.ResourceData is PlayPromptOperation playPromptOperation) { // checking for the call id sent in ClientContext. if (string.IsNullOrWhiteSpace(playPromptOperation.ClientContext)) { throw new ServiceException(new Error() { Message = "No call id provided in PlayPromptOperation.ClientContext.", }); } else if (playPromptOperation.CompletionReason == PlayPromptCompletionReason.CompletedSuccessfully && playPromptOperation.Status == OperationStatus.Completed) { await this.BotSubscribesToToneAsync(playPromptOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false); } } }
/// <summary> /// Raised when the <see cref="INotificationProcessor"/> has received a notification asynchronously. /// </summary> /// <param name="args">The <see cref="NotificationEventArgs"/> instance containing the event data.</param> /// <returns>The <see cref="Task"/>.</returns> private async Task NotificationProcessor_OnNotificationReceivedAsync(NotificationEventArgs args) { this.GraphLogger.CorrelationId = args.ScenarioId; var headers = new[] { new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.ScenarioId, new[] { args.ScenarioId.ToString() }), new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.ClientRequestId, new[] { args.RequestId.ToString() }), new KeyValuePair <string, IEnumerable <string> >(HttpConstants.HeaderNames.Tenant, new[] { args.TenantId }), }; // Create obfuscation content to match what we // would have gotten from the service, then log. var notifications = new CommsNotifications { Value = new[] { args.Notification } }; var obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(notifications, Formatting.Indented); this.GraphLogger.LogHttpMessage( TraceLevel.Info, TransactionDirection.Incoming, HttpTraceType.HttpRequest, args.CallbackUri.ToString(), HttpMethods.Post, obfuscatedContent, headers, correlationId: args.ScenarioId, requestId: args.RequestId); if (args.ResourceData is Call call) { if (args.ChangeType == ChangeType.Created && call.State == CallState.Incoming) { await this.BotAnswerIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } else if (args.ChangeType == ChangeType.Updated && call.State == CallState.Established && call.MediaState?.Audio == MediaState.Active) { // there can potentially be multiple established notifications for the same call, // but since this is just sample code, it is not handled. // your production code must handle such a case and not call record multiple times for the same call await this.BotRecordsIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } else if (args.ChangeType == ChangeType.Deleted && call.State == CallState.Terminated) { this.CleanupCall(call.Id); } } // Receiving updates for the play prompt operation. else if (args.ResourceData is PlayPromptOperation playPromptOperation) { // checking for the call id sent in ClientContext. if (string.IsNullOrWhiteSpace(playPromptOperation.ClientContext)) { throw new ServiceException(new Error() { Message = "No call id provided in PlayPromptOperation.ClientContext.", }); } else if (playPromptOperation.Status == OperationStatus.Completed) { // The operation has been completed, hang up the call await this.BotHangupCallAsync(playPromptOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false); this.GraphLogger.Log(TraceLevel.Info, $"Disconnecting the call."); } } else if (args.ResourceData is RecordOperation recordOperation) { if (recordOperation.Status == OperationStatus.Completed && recordOperation.ResultInfo.Code == 200) { var recordingFileName = $"audio/recording-{recordOperation.ClientContext}.wav"; await this.DownloadRecording(recordingFileName, recordOperation).ConfigureAwait(false); var prompts = new Prompt[] { new MediaPrompt { MediaInfo = new MediaInfo() { Uri = new Uri(this.botBaseUri, recordingFileName).ToString() }, }, }; await this.BotPlayPromptAsync(prompts, recordOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false); } } }