// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Bot.Connector.DirectLine;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Timers;
namespace Microsoft.PowerVirtualAgents.Samples.RelayBotSample
{
///
/// class for manage lifecycle of all conversations,
/// including mapping of external Azure Bot Service channel conversation to your Power Virtual Agents bot converstaion,
/// creating/ending conversations and refreshing tokens
///
public class ConversationManager
{
private static readonly object s_padlock = new object();
private static ConversationManager s_singleton = null;
public static Dictionary ConversationRouter { get; private set; } = new Dictionary();
public static double TokenRefreshCheckIntervalInMinute { get; set; }
public static double TokenRefreshIntervalInMinute { get; set; }
public static double ConversationEndAfterIdleTimeInMinute { get; set; }
public static double ConversationEndCheckIntervalInMinute { get; set; }
///
/// Singleton instance of ConversationManager
///
public static ConversationManager Instance
{
get
{
lock (s_padlock)
{
if (s_singleton == null)
{
// Initialize token refresh check timer and set interval.
Timer s_TokenRefreshTimer = new Timer();
s_TokenRefreshTimer.Interval = TokenRefreshCheckIntervalInMinute * 60 * 1000;
// Hook up the Elapsed event for the timer.
s_TokenRefreshTimer.Elapsed += OnTokenRefreshCheckEvent;
// Have the timer fire repeated events (true is the default)
s_TokenRefreshTimer.AutoReset = true;
// Start the timer
s_TokenRefreshTimer.Enabled = true;
// Initialize conversation idle check timer and set interval.
Timer s_ConversationIdleCheckTimer = new Timer();
s_ConversationIdleCheckTimer.Interval = ConversationEndCheckIntervalInMinute * 60 * 1000;
// Hook up the Elapsed event for the timer.
s_ConversationIdleCheckTimer.Elapsed += OnConversationIdleCheckEvent;
// Have the timer fire repeated events (true is the default)
s_ConversationIdleCheckTimer.AutoReset = true;
// Start the timer
s_ConversationIdleCheckTimer.Enabled = true;
s_singleton = new ConversationManager();
}
return s_singleton;
}
}
}
///
/// Search if an external Azure Bot Service channel conversation is
/// connected to an existing Power Virtual Agent bot conversation
///
/// true if conversation mapping exists, otherwiser false
/// external Azure Bot Service channel conversation ID
public bool ConversationExists(string externalCID)
{
return ConversationRouter.ContainsKey(externalCID);
}
///
/// Start a Power Virtual Agent bot conversation
/// for an external Azure Bot Service channel conversation
///
/// Created Power Virtual Agent bot conversation
/// external Azure Bot Service channel conversation ID
public async Task StartBotConversationAsync(string externalCID, IBotService botService)
{
string token = await botService.GetTokenAsync();
using (var directLineClient = new DirectLineClient(token))
{
var conversation = await directLineClient.Conversations.StartConversationAsync();
string conversationId = conversation?.ConversationId;
if (string.IsNullOrEmpty(conversationId))
{
throw new TaskCanceledException("Exception caught: directline failed to create conversation using retrieved token");
}
var newBotConversation = new RelayConversation()
{
Token = token,
ConversationtId = conversationId,
WaterMark = null,
};
ConversationRouter[externalCID] = newBotConversation;
}
return ConversationRouter[externalCID];
}
///
/// Retrive or start a Power Virtual Agent bot conversation
/// for a given external Azure Bot Service channel conversation
///
/// Power Virtual Agent bot conversation
/// external Azure Bot Service channel conversation ID
public async Task GetOrCreateBotConversationAsync(string externalCID, IBotService botService)
{
return ConversationRouter.TryGetValue(externalCID, out var botConversation) ?
botConversation : await StartBotConversationAsync(externalCID, botService);
}
private static void OnTokenRefreshCheckEvent(object source, ElapsedEventArgs e)
{
foreach (var conversation in ConversationRouter.Values)
{
if (DateTime.Now - conversation.LastTokenRefreshTime >=
TimeSpan.FromMinutes(TokenRefreshIntervalInMinute))
{
// last token refresh TokenRefreshIntervalInMinute ago, refresh token
conversation.LastTokenRefreshTime = DateTime.Now;
using (var client = new DirectLineClient(conversation.Token))
{
conversation.Token = client.Tokens.RefreshToken().Token;
}
}
}
}
private static void OnConversationIdleCheckEvent(object source, ElapsedEventArgs e)
{
foreach (var externalConversationId in ConversationRouter.Keys)
{
var conversation = ConversationRouter[externalConversationId];
if (DateTime.Now - conversation.LastConversationUpdateTime >
TimeSpan.FromMinutes(ConversationEndAfterIdleTimeInMinute))
{
// conversation inactive for > ConversationEndAfterIdleTimeInMinute, removing from s_conversationRouter
// If same external conversation active again, a new bot conversation will be created
conversation = null;
ConversationRouter.Remove(externalConversationId);
}
}
}
}
}