Incremental work on CallRingGroup and other Telnyx activities
This commit is contained in:
parent
dfbba1e176
commit
aa0c2fe711
|
|
@ -5,15 +5,17 @@ using Elsa.Activities.Telnyx.Extensions;
|
|||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Design;
|
||||
using Elsa.Exceptions;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Activities
|
||||
{
|
||||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Answer an incoming call. You must issue this command before executing subsequent commands on an incoming call",
|
||||
Outcomes = new[] { OutcomeNames.Done },
|
||||
Outcomes = new[] { OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
DisplayName = "Answer Call"
|
||||
)]
|
||||
public class AnswerCall : Activity
|
||||
|
|
@ -44,16 +46,21 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
public string? WebhookUrlMethod { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await AnswerCallAsync(context);
|
||||
return Done();
|
||||
}
|
||||
|
||||
private async ValueTask AnswerCallAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var callControlId = context.GetCallControlId(CallControlId);
|
||||
var request = new AnswerCallRequest(BillingGroupId, ClientState, CommandId, WebhookUrl, WebhookUrlMethod);
|
||||
await _telnyxClient.Calls.AnswerCallAsync(callControlId, request, context.CancellationToken);
|
||||
{
|
||||
try
|
||||
{
|
||||
var callControlId = context.GetCallControlId(CallControlId);
|
||||
var request = new AnswerCallRequest(BillingGroupId, ClientState, CommandId, WebhookUrl, WebhookUrlMethod);
|
||||
await _telnyxClient.Calls.AnswerCallAsync(callControlId, request, context.CancellationToken);
|
||||
return Done();
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
if (await e.CallIsNoLongerActiveAsync())
|
||||
return Outcome(TelnyxOutcomeNames.CallIsNoLongerActive);
|
||||
|
||||
throw new WorkflowException(e.Content ?? e.Message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs
Normal file
104
src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Client.Models;
|
||||
using Elsa.Activities.Telnyx.Client.Services;
|
||||
using Elsa.Activities.Telnyx.Extensions;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Builders;
|
||||
using Elsa.Design;
|
||||
using Elsa.Exceptions;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Activities
|
||||
{
|
||||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Bridge two call control calls.",
|
||||
Outcomes = new[] { OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
DisplayName = "Bridge Calls"
|
||||
)]
|
||||
public class BridgeCalls : Activity
|
||||
{
|
||||
private readonly ITelnyxClient _telnyxClient;
|
||||
|
||||
public BridgeCalls(ITelnyxClient telnyxClient)
|
||||
{
|
||||
_telnyxClient = telnyxClient;
|
||||
}
|
||||
|
||||
[ActivityProperty(Label = "Call Control ID A", Hint = "Unique identifier and token for controlling the call.")]
|
||||
public string CallControlIdA { get; set; } = default!;
|
||||
|
||||
[ActivityProperty(Label = "Call Control ID B", Hint = "The Call Control ID of the call you want to bridge with.")]
|
||||
public string CallControlIdB { get; set; } = default!;
|
||||
|
||||
[ActivityProperty(Label = "Command ID", Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.", Category = PropertyCategories.Advanced)]
|
||||
public string? CommandId { get; set; }
|
||||
|
||||
[ActivityProperty(Label = "Client State", Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.", Category = PropertyCategories.Advanced)]
|
||||
public string? ClientState { get; set; }
|
||||
|
||||
[ActivityProperty(Label = "Webhook URL Method", Hint = "HTTP request type used for Webhook URL", UIHint = ActivityPropertyUIHints.Dropdown, Options = new[] { "", "self" }, Category = PropertyCategories.Advanced)]
|
||||
public string? ParkAfterUnbridged { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var callControlIdA = context.GetCallControlId(CallControlIdA);
|
||||
|
||||
var request = new BridgeCallsRequest(
|
||||
CallControlIdB,
|
||||
ClientState,
|
||||
CommandId,
|
||||
ParkAfterUnbridged
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
await _telnyxClient.Calls.BridgeCallsAsync(callControlIdA, request, context.CancellationToken);
|
||||
return Done();
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
if (await e.CallIsNoLongerActiveAsync())
|
||||
return Outcome(TelnyxOutcomeNames.CallIsNoLongerActive);
|
||||
|
||||
throw new WorkflowException(e.Content ?? e.Message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class BridgeCallsExtensions
|
||||
{
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdA(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CallControlIdA, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdA(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CallControlIdA, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdA(this ISetupActivity<BridgeCalls> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CallControlIdA, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdA(this ISetupActivity<BridgeCalls> setup, Func<string?> value) => setup.Set(x => x.CallControlIdA, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdA(this ISetupActivity<BridgeCalls> setup, string? value) => setup.Set(x => x.CallControlIdA, value);
|
||||
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdB(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CallControlIdB, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdB(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CallControlIdB, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdB(this ISetupActivity<BridgeCalls> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CallControlIdB, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdB(this ISetupActivity<BridgeCalls> setup, Func<string?> value) => setup.Set(x => x.CallControlIdB, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCallControlIdB(this ISetupActivity<BridgeCalls> setup, string? value) => setup.Set(x => x.CallControlIdB, value);
|
||||
|
||||
public static ISetupActivity<BridgeCalls> WithCommandId(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CommandId, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCommandId(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CommandId, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCommandId(this ISetupActivity<BridgeCalls> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CommandId, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCommandId(this ISetupActivity<BridgeCalls> setup, Func<string?> value) => setup.Set(x => x.CommandId, value);
|
||||
public static ISetupActivity<BridgeCalls> WithCommandId(this ISetupActivity<BridgeCalls> setup, string? value) => setup.Set(x => x.CommandId, value);
|
||||
|
||||
public static ISetupActivity<BridgeCalls> WithClientState(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.ClientState, value);
|
||||
public static ISetupActivity<BridgeCalls> WithClientState(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.ClientState, value);
|
||||
public static ISetupActivity<BridgeCalls> WithClientState(this ISetupActivity<BridgeCalls> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.ClientState, value);
|
||||
public static ISetupActivity<BridgeCalls> WithClientState(this ISetupActivity<BridgeCalls> setup, Func<string?> value) => setup.Set(x => x.ClientState, value);
|
||||
public static ISetupActivity<BridgeCalls> WithClientState(this ISetupActivity<BridgeCalls> setup, string? value) => setup.Set(x => x.ClientState, value);
|
||||
|
||||
public static ISetupActivity<BridgeCalls> WithParkAfterUnbridged(this ISetupActivity<BridgeCalls> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.ParkAfterUnbridged, value);
|
||||
public static ISetupActivity<BridgeCalls> WithParkAfterUnbridged(this ISetupActivity<BridgeCalls> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.ParkAfterUnbridged, value);
|
||||
public static ISetupActivity<BridgeCalls> WithParkAfterUnbridged(this ISetupActivity<BridgeCalls> setup, Func<string?> value) => setup.Set(x => x.ParkAfterUnbridged, value);
|
||||
public static ISetupActivity<BridgeCalls> WithParkAfterUnbridged(this ISetupActivity<BridgeCalls> setup, string? value) => setup.Set(x => x.ParkAfterUnbridged, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Activities.Telnyx.Client.Models;
|
||||
using Elsa.Activities.Telnyx.Client.Services;
|
||||
using Elsa.Activities.Telnyx.Models;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Models;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.Activities.Temporal;
|
||||
|
|
@ -14,7 +16,9 @@ using Elsa.Design;
|
|||
using Elsa.Metadata;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NodaTime;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Activities
|
||||
{
|
||||
|
|
@ -26,6 +30,13 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
)]
|
||||
public class CallRingGroup : CompositeActivity, IActivityPropertyDefaultValueProvider
|
||||
{
|
||||
private readonly ILogger<CallRingGroup> _logger;
|
||||
|
||||
public CallRingGroup(ILogger<CallRingGroup> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[ActivityProperty(UIHint = ActivityPropertyUIHints.MultiText)]
|
||||
public IList<string> Extensions
|
||||
{
|
||||
|
|
@ -33,13 +44,13 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
set => SetState(value);
|
||||
}
|
||||
|
||||
[ActivityProperty(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call.", Category = PropertyCategories.Advanced)]
|
||||
[ActivityProperty(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call.")]
|
||||
public string? CallControlId
|
||||
{
|
||||
get => GetState<string>();
|
||||
set => SetState(value);
|
||||
}
|
||||
|
||||
|
||||
[ActivityProperty]
|
||||
public RingGroupStrategy Strategy
|
||||
{
|
||||
|
|
@ -69,6 +80,18 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
get => GetState(() => Duration.FromSeconds(20));
|
||||
set => SetState(value);
|
||||
}
|
||||
|
||||
private string? DialedControlId
|
||||
{
|
||||
get => GetState<string?>();
|
||||
set => SetState(value);
|
||||
}
|
||||
|
||||
private CallAnsweredPayload? CallAnsweredPayload
|
||||
{
|
||||
get => GetState<CallAnsweredPayload?>();
|
||||
set => SetState(value);
|
||||
}
|
||||
|
||||
private IList<string> CollectedCallControlIds
|
||||
{
|
||||
|
|
@ -86,16 +109,25 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
protected override async ValueTask OnExitAsync(ActivityExecutionContext context, object? output)
|
||||
{
|
||||
string? answeredCallControlId = null;
|
||||
|
||||
if (output is CallAnsweredPayload callAnsweredPayload)
|
||||
|
||||
if (output is CallAnsweredPayload callAnsweredPayload)
|
||||
answeredCallControlId = callAnsweredPayload.CallControlId;
|
||||
|
||||
|
||||
// Hang up any pending calls.
|
||||
var outgoingCalls = CollectedCallControlIds.Where(x => x != answeredCallControlId).ToList();
|
||||
var client = context.GetService<ITelnyxClient>();
|
||||
|
||||
foreach (var outgoingCall in outgoingCalls)
|
||||
await client.Calls.HangupCallAsync(outgoingCall, new HangupCallRequest(null, null), context.CancellationToken);
|
||||
foreach (var outgoingCall in outgoingCalls)
|
||||
{
|
||||
try
|
||||
{
|
||||
await client.Calls.HangupCallAsync(outgoingCall, new HangupCallRequest(null, null), context.CancellationToken);
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
_logger.LogTrace(e, "Error while trying to hang up an outgoing call");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildPrioritizedHuntFlow(IOutcomeBuilder builder) =>
|
||||
|
|
@ -108,14 +140,19 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
.WithTimeoutSecs(() => (int) RingTime.TotalSeconds)
|
||||
.WithFrom(() => From)
|
||||
.WithFromDisplayName(() => FromDisplayName)
|
||||
.WithClientState(context => new ClientStatePayload(context.CorrelationId!).ToBase64())
|
||||
)
|
||||
.Then(context => DialedControlId = context.GetInput<DialResponse>()!.CallControlId)
|
||||
.Then<Fork>(fork => fork.WithBranches("Connected", "No Response"), fork =>
|
||||
{
|
||||
fork
|
||||
.When("Connected")
|
||||
.ThenTypeNamed(CallAnsweredPayload.ActivityTypeName)
|
||||
// Bridge
|
||||
.Then<Finish>(finish => finish.WithOutcome("Connected").WithOutput(context => context.GetInput<TelnyxWebhook>()!.Data.Payload));
|
||||
.Then(context => CallAnsweredPayload = (CallAnsweredPayload) context.GetInput<TelnyxWebhook>()!.Data.Payload)
|
||||
.Then<BridgeCalls>(bridge => bridge
|
||||
.WithCallControlIdA(() => DialedControlId)
|
||||
.WithCallControlIdB(() => CallAnsweredPayload!.CallControlId))
|
||||
.Then<Finish>(finish => finish.WithOutcome("Connected").WithOutput(() => CallAnsweredPayload));
|
||||
|
||||
fork
|
||||
.When("No Response")
|
||||
|
|
@ -131,8 +168,11 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
fork
|
||||
.When("Connected")
|
||||
.ThenTypeNamed(CallAnsweredPayload.ActivityTypeName)
|
||||
// Bridge
|
||||
.Then<Finish>(finish => finish.WithOutcome("Connected").WithOutput(context => context.GetInput<TelnyxWebhook>()!.Data.Payload));
|
||||
.Then(context => CallAnsweredPayload = (CallAnsweredPayload) context.GetInput<TelnyxWebhook>()!.Data.Payload)
|
||||
.Then<BridgeCalls>(bridge => bridge
|
||||
.WithCallControlIdA(() => DialedControlId)
|
||||
.WithCallControlIdB(() => CallAnsweredPayload!.CallControlId))
|
||||
.Then<Finish>(finish => finish.WithOutcome("Connected").WithOutput(() => CallAnsweredPayload));
|
||||
|
||||
fork
|
||||
.When("Timeout")
|
||||
|
|
@ -149,9 +189,11 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
.WithTimeoutSecs(() => (int) RingTime.TotalSeconds)
|
||||
.WithFrom(() => From)
|
||||
.WithFromDisplayName(() => FromDisplayName)
|
||||
.WithClientState(context => new ClientStatePayload(context.CorrelationId!).ToBase64())
|
||||
)
|
||||
// TODO: Store list of dial results so we can pick correct one from Call Answered Payload webhook.
|
||||
//.Then(context => DialedControlId = context.GetInput<DialResponse>()!.CallControlId)
|
||||
.Then(CollectCallControlIds));
|
||||
|
||||
});
|
||||
|
||||
private void CollectCallControlIds(ActivityExecutionContext context)
|
||||
|
|
@ -161,7 +203,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
collection.Add(callControlId);
|
||||
CollectedCallControlIds = collection;
|
||||
}
|
||||
|
||||
|
||||
object IActivityPropertyDefaultValueProvider.GetDefaultValue(PropertyInfo property) => Duration.FromSeconds(20);
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +109,8 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
|
||||
try
|
||||
{
|
||||
return await _telnyxClient.Calls.DialAsync(request, context.CancellationToken);
|
||||
var response = await _telnyxClient.Calls.DialAsync(request, context.CancellationToken);
|
||||
return response.Data;
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Client.Models;
|
||||
using Elsa.Activities.Telnyx.Client.Services;
|
||||
|
|
@ -16,7 +18,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Play an audio file on the call until the required DTMF signals are gathered to build interactive menus",
|
||||
Outcomes = new[] { OutcomeNames.Done },
|
||||
Outcomes = new[] { OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
DisplayName = "Gather Using Audio"
|
||||
)]
|
||||
public class GatherUsingAudio : Activity
|
||||
|
|
@ -65,12 +67,6 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
public int? TimeoutMillis { get; set; } = 60000;
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await GatherUsingAudioAsync(context);
|
||||
return Done();
|
||||
}
|
||||
|
||||
private async ValueTask GatherUsingAudioAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var request = new GatherUsingAudioRequest(
|
||||
AudioUrl,
|
||||
|
|
@ -91,9 +87,13 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
try
|
||||
{
|
||||
await _telnyxClient.Calls.GatherUsingAudioAsync(callControlId, request, context.CancellationToken);
|
||||
return Done();
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
if (await e.CallIsNoLongerActiveAsync())
|
||||
return Outcome(TelnyxOutcomeNames.CallIsNoLongerActive);
|
||||
|
||||
throw new WorkflowException(e.Content ?? e.Message, e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Hang up the call.",
|
||||
Outcomes = new[] { OutcomeNames.Done },
|
||||
Outcomes = new[] { OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
DisplayName = "Hangup Call"
|
||||
)]
|
||||
public class HangupCall : Activity
|
||||
|
|
@ -29,7 +29,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
|
||||
[ActivityProperty(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call", Category = PropertyCategories.Advanced)]
|
||||
public string CallControlId { get; set; } = default!;
|
||||
|
||||
|
||||
[ActivityProperty(Label = "Client State", Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.", Category = PropertyCategories.Advanced)]
|
||||
public string? ClientState { get; set; }
|
||||
|
||||
|
|
@ -37,22 +37,20 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
public string? CommandId { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await HangupCallAsync(context);
|
||||
return Done();
|
||||
}
|
||||
|
||||
private async ValueTask HangupCallAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var callControlId = context.GetCallControlId(CallControlId);
|
||||
var request = new HangupCallRequest(ClientState, CommandId);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
await _telnyxClient.Calls.HangupCallAsync(callControlId, request, context.CancellationToken);
|
||||
return Done();
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
if (await e.CallIsNoLongerActiveAsync())
|
||||
return Outcome(TelnyxOutcomeNames.CallIsNoLongerActive);
|
||||
|
||||
throw new WorkflowException(e.Content ?? e.Message, e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
{
|
||||
var resolvedNumber = await _extensionProvider.GetAsync(Extension, context.CancellationToken);
|
||||
var result = resolvedNumber != null ? Outcome("Resolved", resolvedNumber) : Outcome("Unresolved", Extension);
|
||||
return Combine(result, Done());
|
||||
return Combine(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ using Elsa.ActivityProviders;
|
|||
using Elsa.ActivityResults;
|
||||
using Elsa.Metadata;
|
||||
using Elsa.Services.Models;
|
||||
using Microsoft.AspNetCore.Authorization.Policy;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.ActivityTypes
|
||||
{
|
||||
|
|
@ -41,12 +40,18 @@ namespace Elsa.Activities.Telnyx.ActivityTypes
|
|||
var activityTypes = payloadTypes.Select(CreateWebhookActivityType).ToList();
|
||||
|
||||
// Add variations on the same webhooks. The webhook filters will conditionally select the appropriate one.
|
||||
var hangupWebhookAttribute = payloadTypes.First(x => x == typeof(CallHangupPayload)).GetCustomAttribute<WebhookAttribute>()!;
|
||||
activityTypes.Add(CreateWebhookActivityType(new WebhookAttribute(hangupWebhookAttribute.EventType, "OriginatorCallHangup", "Originator Call Hangup", "Triggered when an incoming call was hangup by the originator.")));
|
||||
activityTypes.Add(CreateWebhookActivityTypeVariation<CallInitiatedPayload>("BridgeCallInitiated", "Bridge Call Initiated", "Triggered when an incoming bridging call was received."));
|
||||
activityTypes.Add(CreateWebhookActivityTypeVariation<CallHangupPayload>("OriginatorCallHangup", "Originator Call Hangup", "Triggered when an incoming call was hangup by the originator."));
|
||||
|
||||
return activityTypes;
|
||||
}
|
||||
|
||||
private ActivityType CreateWebhookActivityTypeVariation<T>(string activityType, string displayName, string description)
|
||||
{
|
||||
var hangupWebhookAttribute = typeof(T).GetCustomAttribute<WebhookAttribute>()!;
|
||||
return CreateWebhookActivityType(new WebhookAttribute(hangupWebhookAttribute.EventType, activityType, displayName, description));
|
||||
}
|
||||
|
||||
private static ActivityType CreateWebhookActivityType(Type payloadType)
|
||||
{
|
||||
var webhookAttribute = payloadType.GetCustomAttribute<WebhookAttribute>();
|
||||
|
|
@ -92,11 +97,8 @@ namespace Elsa.Activities.Telnyx.ActivityTypes
|
|||
{
|
||||
var webhook = (TelnyxWebhook) context.Input!;
|
||||
|
||||
if (webhook.Data.Payload is CallPayload callPayload)
|
||||
{
|
||||
if (webhook.Data.Payload is CallPayload callPayload)
|
||||
context.WorkflowExecutionContext.CorrelationId ??= callPayload.CallSessionId;
|
||||
context.SetVariable("CallControlId", callPayload.CallControlId);
|
||||
}
|
||||
|
||||
return new(new CombinedResult(new OutputResult(context.Input), new DoneResult()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Client.Models
|
||||
{
|
||||
public record ErrorResponse(IList<Error> Errors);
|
||||
public record Error(int Code, string Title, string Detail);
|
||||
|
||||
public static class ErrorCodes
|
||||
{
|
||||
public const int CallHasAlreadyEnded = 90018;
|
||||
}
|
||||
}
|
||||
|
|
@ -55,4 +55,11 @@ namespace Elsa.Activities.Telnyx.Client.Models
|
|||
string? WebhookUrl,
|
||||
string? WebhookUrlMethod
|
||||
);
|
||||
|
||||
public record BridgeCallsRequest(
|
||||
string CallControlId,
|
||||
string? ClientState,
|
||||
string? CommandId,
|
||||
string? ParkAfterUnbridge
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
namespace Elsa.Activities.Telnyx.Client.Models
|
||||
{
|
||||
public record TelnyxResponse<T>(T Data);
|
||||
public record DialResponse(string CallControlId, string CallLegId, string CallSessionId, bool IsAlive, string RecordType);
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace Elsa.Activities.Telnyx.Client.Services
|
|||
public interface ICallsApi
|
||||
{
|
||||
[Post("/v2/calls")]
|
||||
Task<DialResponse> DialAsync([Body]DialRequest request, CancellationToken cancellationToken = default);
|
||||
Task<TelnyxResponse<DialResponse>> DialAsync([Body]DialRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
[Post("/v2/calls/{callControlId}/actions/answer")]
|
||||
Task AnswerCallAsync(string callControlId, [Body]AnswerCallRequest request, CancellationToken cancellationToken = default);
|
||||
|
|
@ -21,5 +21,8 @@ namespace Elsa.Activities.Telnyx.Client.Services
|
|||
|
||||
[Post("/v2/calls/{callControlId}/actions/gather_using_audio")]
|
||||
Task GatherUsingAudioAsync(string callControlId, [Body]GatherUsingAudioRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
[Post("/v2/calls/{callControlId}/actions/bridge")]
|
||||
Task BridgeCallsAsync(string callControlId, [Body]BridgeCallsRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
<ProjectReference Include="..\..\scripting\Elsa.Scripting.Liquid\Elsa.Scripting.Liquid.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Activities.Temporal.Common\Elsa.Activities.Temporal.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ namespace Elsa.Activities.Telnyx.Extensions
|
|||
if (!string.IsNullOrWhiteSpace(callControlId))
|
||||
return callControlId;
|
||||
|
||||
callControlId = context.GetVariable<string>("CallControlId");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(callControlId))
|
||||
return callControlId;
|
||||
// callControlId = context.GetVariable<string>("CallControlId");
|
||||
//
|
||||
// if (!string.IsNullOrWhiteSpace(callControlId))
|
||||
// return callControlId;
|
||||
|
||||
var options = context.GetService<TelnyxOptions>();
|
||||
callControlId = options.CallControlAppId;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Client.Models;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Extensions
|
||||
{
|
||||
public static class ApiExceptionExtensions
|
||||
{
|
||||
public static async Task<ErrorResponse> GetErrorResponseAsync(this ApiException e, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var httpContent = new StringContent(e.Content!);
|
||||
return (await e.RefitSettings.ContentSerializer.FromHttpContentAsync<ErrorResponse>(httpContent, cancellationToken))!;
|
||||
}
|
||||
|
||||
public static async Task<bool> CallIsNoLongerActiveAsync(this ApiException e, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var errorResponse = await e.GetErrorResponseAsync(cancellationToken);
|
||||
var errors = errorResponse.Errors;
|
||||
return errors.Any(x => x.Code == ErrorCodes.CallHasAlreadyEnded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ namespace Elsa.Activities.Telnyx.Extensions
|
|||
.AddSingleton<IWebhookFilterService, WebhookFilterService>()
|
||||
.AddSingleton<IWebhookFilter, AttributeBasedWebhookFilter>()
|
||||
.AddSingleton<IWebhookFilter, HangupWebhookFilter>()
|
||||
.AddSingleton<IWebhookFilter, CallInitiatedWebhookFilter>()
|
||||
.AddScoped(telnyxOptions.ExtensionProviderFactory);
|
||||
|
||||
// Telnyx API Client.
|
||||
|
|
@ -139,7 +140,9 @@ namespace Elsa.Activities.Telnyx.Extensions
|
|||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var json = await (request.Content as StringContent)?.ReadAsStringAsync(cancellationToken)!;
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
var response = await base.SendAsync(request, cancellationToken);
|
||||
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Models;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Models;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.Scripting.Liquid.Messages;
|
||||
using Fluid;
|
||||
using MediatR;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Liquid
|
||||
{
|
||||
public class ConfigureLiquidEngine : INotificationHandler<EvaluatingLiquidExpression>
|
||||
{
|
||||
public Task Handle(EvaluatingLiquidExpression notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var context = notification.TemplateContext;
|
||||
var memberAccessStrategy = context.MemberAccessStrategy;
|
||||
|
||||
memberAccessStrategy.Register<Extension>();
|
||||
memberAccessStrategy.Register<CallAnsweredPayload>();
|
||||
memberAccessStrategy.Register<CallBridgedPayload>();
|
||||
memberAccessStrategy.Register<CallDtmfReceivedPayload>();
|
||||
memberAccessStrategy.Register<CallGatherEndedPayload>();
|
||||
memberAccessStrategy.Register<CallHangupPayload>();
|
||||
memberAccessStrategy.Register<CallInitiatedPayload>();
|
||||
memberAccessStrategy.Register<CallPayload>();
|
||||
memberAccessStrategy.Register<CallPlayback>();
|
||||
memberAccessStrategy.Register<CallPlaybackEndedPayload>();
|
||||
memberAccessStrategy.Register<CallPlaybackStartedPayload>();
|
||||
|
||||
memberAccessStrategy.Register<TelnyxRecord>();
|
||||
memberAccessStrategy.Register<TelnyxWebhook>();
|
||||
memberAccessStrategy.Register<TelnyxWebhookData>();
|
||||
memberAccessStrategy.Register<TelnyxWebhookMeta>();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Models
|
||||
{
|
||||
public record ClientStatePayload(string CorrelationId)
|
||||
{
|
||||
public static ClientStatePayload FromBase64(string base64)
|
||||
{
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
var json = Encoding.UTF8.GetString(bytes);
|
||||
return JsonConvert.DeserializeObject<ClientStatePayload>(json);
|
||||
}
|
||||
|
||||
public string ToBase64()
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(this);
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Elsa.Activities.Telnyx.Activities
|
||||
namespace Elsa.Activities.Telnyx.Models
|
||||
{
|
||||
public enum RingGroupStrategy
|
||||
{
|
||||
|
|
|
|||
7
src/activities/Elsa.Activities.Telnyx/OutcomeNames.cs
Normal file
7
src/activities/Elsa.Activities.Telnyx/OutcomeNames.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace Elsa.Activities.Telnyx
|
||||
{
|
||||
public static class TelnyxOutcomeNames
|
||||
{
|
||||
public const string CallIsNoLongerActive = "Call Is No Longer Active";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Bookmarks;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Attributes;
|
||||
using Elsa.Activities.Telnyx.Models;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Events;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Abstract;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
|
|
@ -89,6 +86,12 @@ namespace Elsa.Activities.Telnyx.Webhooks.Consumers
|
|||
|
||||
private string GetCorrelationId(Payload payload)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(payload.ClientState))
|
||||
{
|
||||
var clientStatePayload = ClientStatePayload.FromBase64(payload.ClientState);
|
||||
return clientStatePayload.CorrelationId;
|
||||
}
|
||||
|
||||
if (payload is CallPayload callPayload)
|
||||
return callPayload.CallSessionId;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Attributes;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Abstract;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Services;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Webhooks.Filters
|
||||
{
|
||||
public class CallInitiatedWebhookFilter : IWebhookFilter
|
||||
{
|
||||
public int Priority => 1;
|
||||
|
||||
public bool CanHandlePayload(Payload payload) => payload is CallInitiatedPayload;
|
||||
|
||||
public string GetActivityTypeName(Payload payload)
|
||||
{
|
||||
var callInitiated = (CallInitiatedPayload) payload;
|
||||
var attribute = payload.GetType().GetCustomAttribute<WebhookAttribute>()!;
|
||||
|
||||
if (callInitiated.State != "bridging")
|
||||
return attribute.ActivityType;
|
||||
|
||||
return "BridgeCallInitiated";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Elsa.Activities.Telnyx.Messages
|
||||
namespace Elsa.Activities.Telnyx.Webhooks.Messages
|
||||
{
|
||||
public class WebhookReceived
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using Elsa.Activities.Telnyx.Webhooks.Attributes;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
|
||||
{
|
||||
[Webhook(EventType, ActivityTypeName, "Call Bridged", "Triggered when an a call is bridged.")]
|
||||
public sealed record CallBridgedPayload : CallPayload
|
||||
{
|
||||
public const string EventType = "call.bridged";
|
||||
public const string ActivityTypeName = "CallBridged";
|
||||
public string From { get; init; } = default!;
|
||||
public string To { get; init; } = default!;
|
||||
public string State { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,5 +9,6 @@ namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
|
|||
public const string ActivityTypeName = "CallInitiated";
|
||||
public string Direction { get; init; } = default!;
|
||||
public string State { get; init; } = default!;
|
||||
public string To { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using Elsa.Activities.Telnyx.Webhooks.Attributes;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Abstract;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Abstract;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Webhooks.Services
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Webhooks.Services
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Attributes;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Abstract;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Webhooks.Services
|
||||
|
|
|
|||
Loading…
Reference in a new issue