neventsocket-prerelease - NEventSocket 2.1.0-build00282

An async reactive EventSocket driver for FreeSwitch

PM> Install-Package NEventSocket -Version 2.1.0-build00282 -Source https://www.myget.org/F/neventsocket-prerelease/api/v3/index.json

Copy to clipboard

> nuget.exe install NEventSocket -Version 2.1.0-build00282 -Source https://www.myget.org/F/neventsocket-prerelease/api/v3/index.json

Copy to clipboard

> dotnet add package NEventSocket --version 2.1.0-build00282 --source https://www.myget.org/F/neventsocket-prerelease/api/v3/index.json

Copy to clipboard
<PackageReference Include="NEventSocket" Version="2.1.0-build00282" />
Copy to clipboard
source https://www.myget.org/F/neventsocket-prerelease/api/v3/index.json

nuget NEventSocket  ~> 2.1.0-build00282
Copy to clipboard

> choco install NEventSocket --version 2.1.0-build00282 --source https://www.myget.org/F/neventsocket-prerelease/api/v2

Copy to clipboard
Import-Module PowerShellGet
Register-PSRepository -Name "neventsocket-prerelease" -SourceLocation "https://www.myget.org/F/neventsocket-prerelease/api/v2"
Install-Module -Name "NEventSocket" -RequiredVersion "2.1.0-build00282" -Repository "neventsocket-prerelease" -AllowPreRelease
Copy to clipboard

NEventSocket

NuGet Badge MyGet Badge Join the chat at https://gitter.im/danbarua/NEventSocket

Windows / .NET Linux / Mono
Build status Build Status

NEventSocket is a FreeSwitch event socket client/server library for .Net 4.5.

Since I no longer work in the telecoms industry, this library is no longer maintained. There is a DotNetCore 3 fork of the project here: https://github.com/iamkinetic/NEventSocket

Installing Release builds

Package Manager Console: Install-Package NEventSocket

CommandLine: nuget install NEventSocket

Installing Pre-Release builds

NuGet v3 (VS2015): nuget install NEventSocket -PreRelease -Source "https://www.myget.org/F/neventsocket-prerelease/api/v3/index.json"

NuGet v2 (VS2012): nuget install NEventSocket -PreRelease -Source "https://www.myget.org/F/neventsocket-prerelease/api/v2"

Inbound Socket Client

An InboundSocket connects and authenticates to a FreeSwitch server (inbound from the point of view of FreeSwitch) and can listen for all events going on in the system and issue commands to control calls. You can use ReactiveExtensions to filter events using LINQ queries and extension methods. All methods are async and awaitable.

using System;
using System.Reactive.Linq;
using System.Threading.Tasks;

using NEventSocket;
using NEventSocket.FreeSwitch;

using (var socket = await InboundSocket.Connect("localhost", 8021, "ClueCon"))
{
  var apiResponse = await socket.SendApi("status");
  Console.WriteLine(apiResponse.BodyText);

  //Tell FreeSwitch which events we are interested in
  await socket.SubscribeEvents(EventName.ChannelAnswer);

  //Handle events as they come in using Rx
  socket.ChannelEvents.Where(x => x.EventName == EventName.ChannelAnswer)
        .Subscribe(async x =>
            {
                Console.WriteLine("Channel Answer Event " +  x.UUID);

                //we have a channel id, now we can control it
                await socket.Play(x.UUID, "misc/8000/misc-freeswitch_is_state_of_the_art.wav");
            });

  Console.WriteLine("Press [Enter] to exit.");
  Console.ReadLine();
}

Outbound Socket Server

An OutboundListener listens on a TCP port for socket connections (outbound from the point of view of FreeSwitch) when the FreeSwitch dialplan is setup to route calls to the EventSocket. An OutboundSocket receives events for one particular channel, the API is the same as for an InboundSocket, so you will need to pass in the channel UUID to issue commands for it.

Don't forget to use the async and full flags in your dialplan. async means that applications will not block (e.g. a bridge will block until the channel hangs up and completes the call) and full gives the socket access to the full EventSocket api (without this you will see -ERR Unknown Command responses)

<action application="socket" data="127.0.0.1:8084 async full"/>
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;

using NEventSocket;
using NEventSocket.FreeSwitch;

using (var listener = new OutboundListener(8084))
{
  listener.Connections.Subscribe(
    async socket => {
      await socket.Connect();

      //after calling .Connect(), socket.ChannelData
      //is populated with all the headers and variables of the channel

      var uuid = socket.ChannelData.Headers[HeaderNames.UniqueId];
      Console.WriteLine("OutboundSocket connected for channel " + uuid);

      await socket.SubscribeEvents(EventName.ChannelHangup);

      socket.ChannelEvents
            .Where(x => x.EventName == EventName.ChannelHangup && x.UUID == uuid)
            .Take(1)
            .Subscribe(async x => {
                  Console.WriteLine("Hangup Detected on " + x.UUID);
                  await socket.Exit();
            });
      
      
      //if we use 'full' in our FS dialplan, we'll get events for ALL channels in FreeSwitch
      //this is not desirable here - so we'll filter in for our unique id only
      //cases where this is desirable is in the channel api where we want to catch other channels bridging to us
      await socket.Filter(HeaderNames.UniqueId, uuid);
      
      //tell FreeSwitch not to end the socket on hangup, we'll catch the hangup event and .Exit() ourselves
      await socket.Linger();
      
      await socket.ExecuteApplication(uuid, "answer");
      await socket.Play(uuid, "misc/8000/misc-freeswitch_is_state_of_the_art.wav");
      await socket.Hangup(uuid, HangupCause.NormalClearing);
    });

  listener.Start();

  Console.WriteLine("Press [Enter] to exit.");
  Console.ReadLine();
}

Error Handling

NEventSocket makes a best effort to handle errors gracefully, there is one scenario that you do need to handle in your application code. In a realtime async application, there may be a situation where we are trying to write to a socket when FreeSwitch has already hung up and disconnected the socket. In this case, NEventSocket will throw a TaskCanceledException (Note incorrect spelling of Cancelled) which you can catch in order to do any clean up.

It's a good idea to wrap any IObservable.Subscribe(() => {}) callbacks in a try/catch block.

try {
  await socket.Connect();

  var uuid = socket.ChannelData.Headers[HeaderNames.UniqueId];
  Console.WriteLine("OutboundSocket connected for channel " + uuid);

  await socket.SubscribeEvents(EventName.Dtmf);

  socket.ChannelEvents.Where(x => x.UUID == uuid && x.EventName == EventName.Dtmf)
        .Subscribe(async e => {
          try {
            Console.WriteLine(e.Headers[HeaderNames.DtmfDigit]);
           //speak the number to the caller
            await client.Say(
                  uuid,
                  new SayOptions()
                  {
                    Text = e.Headers[HeaderNames.DtmfDigit],
                    Type = SayType.Number,
                    Method = SayMethod.Iterated
                    });
           }
           catch(TaskCanceledException ex){
            //channel hungup
           }
      ));
}
catch (TaskCanceledException ex) {
  //FreeSwitch disconnected, do any clean up here.
}

Channel API

Whilst the InboundSocket and OutboundSocket give you a close-to-the-metal experience with the EventSocket interface, the Channel API is a high level abstraction built on top of these. A Channel object maintains its own state by subscribing to events from FreeSwitch and allows us to control calls in a more object oriented manner without having to pass channel UUIDs around as strings.

Although the InboundSocket and OutboundSocket APIs are reasonably stable, the Channel API is a work in progress with the goal of providing a pleasant, easy to use, strongly-typed API on top of the EventSocket.

There is an in-depth example in the examples/Channels folder.

using System;
using System.Reactive.Linq;
using System.Threading.Tasks;

using NEventSocket;
using NEventSocket.Channels;
using NEventSocket.FreeSwitch;
using NEventSocket.Util;

using (var listener = new OutboundListener(8084))
{
  listener.Channels.Subscribe(
    async channel => {
      try
      {
          await channel.Answer();
          await channel.PlayFile("ivr/8000/ivr-call_being_transferred.wav");
          await channel.StartDetectingInbandDtmf();

          var bridgeOptions = 
                  new BridgeOptions()
                      {
                        IgnoreEarlyMedia = true,
                        RingBack =
                            "tone_stream://%(400,200,400,450);%(400,2000,400,450);loops=-1",
                        ContinueOnFail = true,
                        HangupAfterBridge = true,
                        TimeoutSeconds = 60,
                        //can get variables from a channel using the indexer
                        CallerIdName = channel["effective_caller_id_name"], 
                        CallerIdNumber = channel["effective_caller_id_number"],
                      };

          //attempt a bridge to user/1001, write to the console when it starts ringing
          await channel.BridgeTo("user/1001", 
                                  bridgeOptions,
                                  (evt) => Console.WriteLine("B-Leg is ringing..."))

          //channel.Bridge represents the bridge status
          if (!channel.Bridge.IsBridged)
          {
              //we can inspect the HangupCause to determine why it failed
              Console.WriteLine("Bridge Failed - {0}".Fmt(channel.Bridge.HangupCause));
              await channel.PlayFile("ivr/8000/ivr-call_rejected.wav");
              await channel.Hangup(HangupCause.NormalTemporaryFailure);
              return;
          }
              
          Console.WriteLine("Bridge success - {0}".Fmt(channel.Bridge.ResponseText));

          //the bridged channel maintains its own state
          //and handles a subset of full Channel operations
          channel.Bridge.Channel.HangupCallBack = 
            (e) => ColorConsole.WriteLine("Hangup Detected on B-Leg {0} {1}"
                  .Fmt(e.Headers[HeaderNames.CallerUniqueId],
                    e.Headers[HeaderNames.HangupCause]));

          //we'll listen out for the feature code #9
          //on the b-leg to do an attended transfer
          channel.Bridge.Channel.FeatureCodes("#")
            .Subscribe(async x =>
            {
              switch (x)
              {
                case "#9":
                  Console.WriteLine("Getting the extension to do an attended transfer to...");

                  //play a dial tone to the b-leg and get 4 digits to dial
                  var digits = await channel.Bridge.Channel.Read(
                                    new ReadOptions {
                                        MinDigits = 3,
                                        MaxDigits = 4, 
                                        Prompt = "tone_stream://%(10000,0,350,440)",
                                        TimeoutMs = 30000,
                                        Terminators = "#" });

                  if (digits.Result == ReadResultStatus.Success && digits.Digits.Length == 4)
                  {
                    await channel.Bridge.Channel
                      .PlayFile("ivr/8000/ivr-please_hold_while_party_contacted.wav");
                    
                    var xfer = await channel.Bridge.Channel
                      .AttendedTransfer("user/{0}".Fmt(digits));

                    //attended transfers are a work-in-progress at the moment
                    if (xfer.Status == AttendedTransferResultStatus.Failed)
                    {
                      if (xfer.HangupCause == HangupCause.CallRejected)
                      {
                          //we can play audio into the b-leg via the a-leg channel
                          await channel
                            .PlayFile("ivr/8000/ivr-call-rejected.wav", Leg.BLeg);
                      }
                      else if (xfer.HangupCause == HangupCause.NoUserResponse 
                                || xfer.HangupCause == HangupCause.NoAnswer)
                      {
                          //or we can play audio on the b-leg channel object
                          await channel.Bridge.Channel
                            .PlayFile("ivr/8000/ivr-no_user_response.wav");
                      }
                      else if (xfer.HangupCause == HangupCause.UserBusy)
                      {
                          await channel.Bridge.Channel
                            .PlayFile("ivr/8000/ivr-user_busy.wav");
                      }
                    }
                    else
                    {
                      //otherwise the a-leg is now connected to either
                      // 1) the c-leg
                      //    in this case, channel.Bridge.Channel is now the c-leg channel
                      // 2) the b-leg and the c-leg in a 3-way chat
                      //    in this case, if the b-leg hangs up, then channel.Bridge.Channel
                      //    will become the c-leg
                      await channel
                      .PlayFile("ivr/8000/ivr-call_being_transferred.wav", Leg.ALeg);
                    }
                  }
                break;
              }
            });
      }
      catch(TaskCanceledException)
      {
          Console.WriteLine("Channel {0} hungup".Fmt(channel.UUID));
      }
    }
    });

  listener.Start();

  Console.WriteLine("Press [Enter] to exit.");
  Console.ReadLine();
}

2.1.0 - BREAKING CHANGES: ( see https://github.com/danbarua/NEventSocket/blob/master/BREAKING%20CHANGES.md ) - ChannelEvents are now a separate observable and type - Moved Originate operations from InboundSocket to extension methods on EventSocket (breaks binary compatibility) - Channels - exposes socket and last event properties on Channel instead of Channel.Advanced - uses CHANNEL_HANGUP_COMPLETE instead of CHANNEL_HANGUP - add channel.EnableHeartBeat() for keepalive - move recording functionality to BasicChannel - Changed type of BridgeOptions.FilterDtmf from boolean to Leg enum - allow one or both legs to filter DTMF
- OutboundListener can be stopped and restarted - Reliability bugfixes 1.1.0 - Critical Bugfix: - Make socket initialization thread-safe Other Bugfixes: - Use async-locking around command and API sends to correlate responses - Don't log spurious API error responses - Don't log uuid_dump responses - Fix Playback application edge cases - Log warnings in OutboundSocket mode when not using async full 1.0.2 - Bugfix: Handle multi-arg applications on Originate Surface Api Response and Command Reply errors in logs 1.0.1 - Bugfix - ensure subscribed to BackgroundJob events when initiating BgAPI 1.0.0 - BREAKING CHANGE: Affects behaviour of socket.SubscribeEvents() - see https://github.com/danbarua/NEventSocket/blob/master/BREAKING%20CHANGES.md 0.6.10 - Bugfix: handle garbage collected event handler in Dispose(); 0.6.9 - Bugfix: make all Dispose() calls thread safe 0.6.8 - Bugfix: properly handle multiple Dispose() calls 0.6.7 - Bugfix: logger garbage collected in Dispose() call 0.6.6 - Bugfix: remove superfluous logging 0.6.5 - Bugfix: ensure Message/Event pumps are not blocked 0.6.4 - Channels: expose Channel.Advanded.LastEvent property 0.6.3 - Bugfix - Replace .OnErrorResumeNext() with .Retry() on Channel connection error 0.6.2 - Bugfix - don't terminate OutboundListener.Channels observable on connection errors. 0.6.1 - Added events and helpers for conferences 0.6.0 - Channels: move channel variables, event headers, underlying socket to Channel.Advanced. 0.5.3 - Channels: fix channel init bug 0.5.2 - Channels: Filter events so underlying socket does not receive events for all channels 0.5.1 - Fix issues with initializing Rx when run in scriptcs 0.5.0 - Fix uri decoding issue on message parsing - Allow operations on Channels in Pre-Answer state

  • .NETFramework 4.5: 4.5.0.0

Owners

Dan Barua

Authors

Dan Barua

Project URL

https://github.com/danbarua/NEventSocket

License

MPL-2.0

Tags

FreeSwitch EventSocket ESL Reactive

Info

199 total downloads
33 downloads for version 2.1.0-build00282
Download (746.82 KB)
Found on the current feed only

Package history

Version Size Last updated Downloads Mirrored?
2.1.0-build00282 746.82 KB Fri, 13 Jan 2017 12:48:10 GMT 33
2.1.0-build00281 746.47 KB Fri, 13 Jan 2017 12:42:42 GMT 2
2.1.0-build00279 745.94 KB Tue, 20 Dec 2016 17:56:06 GMT 3
2.1.0-build00276 745.57 KB Fri, 30 Sep 2016 10:55:17 GMT 2
2.1.0-build00275 745.54 KB Thu, 29 Sep 2016 12:07:26 GMT 4
2.1.0-build00274 745.65 KB Thu, 29 Sep 2016 10:27:36 GMT 1
2.1.0-build00273 746.17 KB Wed, 21 Sep 2016 09:47:42 GMT 2
2.1.0-build00270 747.14 KB Wed, 21 Sep 2016 08:59:44 GMT 2
2.1.0-build00266 745.52 KB Mon, 19 Sep 2016 09:52:58 GMT 3
2.1.0-build00261 745.74 KB Tue, 13 Sep 2016 11:20:34 GMT 2
2.1.0-build00259 745.29 KB Tue, 13 Sep 2016 11:06:34 GMT 3
2.1.0-build00258 746.07 KB Tue, 13 Sep 2016 10:59:53 GMT 2
2.1.0-build00256 745.12 KB Tue, 13 Sep 2016 10:44:42 GMT 4
2.1.0-build00254 745.82 KB Tue, 13 Sep 2016 10:29:21 GMT 2
2.0.0-build00252 744.74 KB Tue, 23 Aug 2016 09:23:57 GMT 3
2.0.0-build00251 745.09 KB Tue, 23 Aug 2016 09:10:44 GMT 2
2.0.0-build00248 745.06 KB Fri, 19 Aug 2016 09:34:47 GMT 6
2.0.0-build00247 744.9 KB Wed, 10 Aug 2016 08:44:48 GMT 2
2.0.0-build00244 743.46 KB Wed, 03 Aug 2016 13:26:59 GMT 3
2.0.0-build00243 743.2 KB Tue, 26 Jul 2016 10:11:16 GMT 2
2.0.0-build00242 743.18 KB Fri, 15 Jul 2016 10:19:00 GMT 1
2.0.0-build00240 742.17 KB Fri, 15 Jul 2016 09:17:33 GMT 2
2.0.0-build00239 742.38 KB Thu, 07 Jul 2016 14:22:03 GMT 4
2.0.0-build00238 742.37 KB Thu, 07 Jul 2016 14:03:29 GMT 2
2.0.0-build00237 742.38 KB Thu, 07 Jul 2016 13:43:54 GMT 3
2.0.0-build00236 742.3 KB Wed, 06 Jul 2016 16:19:01 GMT 3
2.0.0-build00235 742.52 KB Wed, 06 Jul 2016 11:44:40 GMT 1
2.0.0-build00234 742.24 KB Fri, 01 Jul 2016 13:47:01 GMT 4
2.0.0-build00233 741.15 KB Fri, 01 Jul 2016 12:51:38 GMT 1
2.0.0-build00230 741.17 KB Fri, 01 Jul 2016 12:15:25 GMT 3
2.0.0-build00229 741.16 KB Fri, 01 Jul 2016 11:23:57 GMT 2
2.0.0-build00228 741 KB Fri, 01 Jul 2016 10:46:49 GMT 2
2.0.0-build00227 741.06 KB Thu, 30 Jun 2016 15:39:17 GMT 4
2.0.0-build00209 751.2 KB Fri, 10 Jun 2016 09:10:16 GMT 8
2.0.0-build00208 750 KB Thu, 09 Jun 2016 16:35:19 GMT 5
2.0.0-build00207 749.91 KB Thu, 09 Jun 2016 16:23:12 GMT 3
2.0.0-build00206 749.97 KB Thu, 09 Jun 2016 13:08:55 GMT 4
2.0.0-build00201 751.93 KB Mon, 06 Jun 2016 09:59:56 GMT 3
2.0.0-build00200 749.89 KB Tue, 17 May 2016 18:40:50 GMT 5
2.0.0-build00197 751.79 KB Thu, 12 May 2016 19:23:29 GMT 2
2.0.0-build00195 751.78 KB Mon, 25 Apr 2016 15:40:00 GMT 6
2.0.0-build00189 749.59 KB Thu, 07 Apr 2016 14:43:24 GMT 5
2.0.0-build00188 749.71 KB Thu, 07 Apr 2016 14:29:20 GMT 1
2.0.0-build00187 749.04 KB Thu, 07 Apr 2016 11:55:49 GMT 2
2.0.0-build00186 747.21 KB Thu, 07 Apr 2016 11:21:00 GMT 2
2.0.0-build00185 748.58 KB Thu, 07 Apr 2016 10:50:31 GMT 2
2.0.0-build00183 748.77 KB Thu, 07 Apr 2016 09:31:57 GMT 2
2.0.0-build00181 748.49 KB Thu, 07 Apr 2016 09:14:49 GMT 3
1.1.0-build00180 746.62 KB Wed, 06 Apr 2016 11:47:49 GMT 2
1.1.0-build00178 746.64 KB Wed, 06 Apr 2016 10:42:27 GMT 3
1.1.0-build00177 746.74 KB Wed, 06 Apr 2016 09:23:02 GMT 3
1.1.0-build00176 746.66 KB Wed, 06 Apr 2016 09:02:21 GMT 3
1.1.0-build00175 746.71 KB Wed, 06 Apr 2016 08:41:48 GMT 3
1.1.0-build00173 746.6 KB Thu, 24 Mar 2016 17:18:45 GMT 2
1.1.0-build00172 746.92 KB Thu, 24 Mar 2016 16:23:58 GMT 3
1.1.0-build00171 746.82 KB Thu, 24 Mar 2016 16:04:00 GMT 3
1.1.0-build00170 746.79 KB Tue, 22 Mar 2016 17:08:34 GMT 2
1.0.3-build00169 746.68 KB Tue, 22 Mar 2016 16:34:03 GMT 1
1.0.1-build00155 743.37 KB Wed, 16 Mar 2016 06:44:28 GMT 2
1.0.0-build00150 735.31 KB Wed, 09 Mar 2016 11:11:20 GMT 2
0.1.0-build00054 702.11 KB Tue, 09 Aug 2016 11:25:09 GMT 2