Coder Social home page Coder Social logo

esl's Introduction

This module is a promise-based client ('inbound' event socket) and server ('outbound' event socket) for FreeSwitch, written entirely in Javascript with no dependencies on the libesl library.

This module is actively maintained and used in production systems.

This is version 11, a new major version of esl. It introduces TypeScript support, and gets rid of binding to this.

Overview

Client mode

This mode of operation is called "inbound" (to FreeSwitch) in the Event Socket FreeSwitch documentation. A client can be used to trigger calls asynchronously (for example in a click-to-dial application). A client can also be used to monitor events for known UUIDs or other fields (see the .filter(header,value) method).

Server mode

A server will handle calls sent to it using the socket diaplan application (called "outbound" mode in the Event Socket Outbound FreeSwitch documentation). The server is available at a pre-defined port which the socket dialplan application will specify.

Client Usage

The following code does the equivalent of fs_cli -x: it connects to the Event Socket, runs a single command, then disconnects.

import { FreeSwitchClient, once } from 'esl'

const client = new FreeSwitchClient({
  port: 8021
})

const fs_command = async (cmd) => {
  const p = once(client,'connect')
  await client.connect()
  const [ call ] = await p
  const res = await call.api(cmd)
  await call.exit();
  await client.end();
}

fs_command("reloadxml");

Generally speaking though, the client might reconnect multiple times, and your code should handle reconnections:

import { FreeSwitchClient, once } from 'esl'

const client = new FreeSwitchClient({
  port: 8021
})

client.on('connect', (call) => {
  // Do something here with the API
})

Constructor options

The FreeSwitchClient constructor takes a single argument, an options object with the following fields:

  • host: defaults to 127.0.0.1
  • port: defaults to 8021
  • password: defaults to ClueCon
  • logger: defaults to the console object

Methods

The FreeSwitchClient class has the following methods.

connect()

This method triggers the connection to FreeSWITCH.

The client will automatically reconnect if FreeSWITCH crashes or the connection is lost.

async end()

This methods closes the connection to FreeSWITCH and prevents further attempts.

Returns a Promise.

Events

The FreeSwitchClient class may emit the following events.

error (error)

Sent when an error is reported.

connect (current_call : FreeSwitchResponse)

Sent when connecting to FreeSWITCH. Might be sent multiple times in case disconnections happen.

reconnecting (retry: number)

Sent when disconnected from FreeSWITCH. The retry value indicates how long the client will wait until reconnecting to FreeSWITCH.

warning (data)

Sent by the underlying socket when a socket-level warning is triggered.

end

Sent when the end() method is called.

Server Usage

You can connect to an Event Socket server from the FreeSwitch XML dialplan, Notice the syntax to specify more than one server if desired.

<action application="set" data="socket_resume=true"/>
<action application="socket" data="127.0.0.1:7000|127.0.0.1:7001 async full"/>
<action application="respond" data="500 socket failure"/>

Another option is to configure a inbound profile to directly use the socket. This bypasses the XML dialplan; instead, an inline dialplan is used.

<profile name="my-sofia-profile">
  <settings>
    <param name="dialplan" value="inline:'socket:127.0.0.1:7000|127.0.0.1:7001 async full'"/>

Here is a simplistic event server:

import { FreeSwitchServer } from 'esl'

const server = new FreeSwitchServer()

server.on('connection', (call) => {
  const res = await call.command('playback', 'voicemail/vm-hello')
  const foo = res.body.variable_foo
  await call.hangup() // hang-up the call
  await call.exit()   // tell FreeSwitch we're disconnecting
})

await server.listen({ port: 7000 })

Constructor options

The FreeSwitchServer constructor takes a single argument, an options object with the following fields:

  • all_events: boolean, defaults to true; indicates whether the FreeSwitchResponse object should request all events from FreeSWITCH (the default), or only the ones required to process commands (all_events:false). Note that the default will negatively impact performance of both FreeSWITCH and your application; it however provides the simplest onboarding.

  • my_events: boolean, defaults to true; indicates whether the FreeSwitchResponse object should filter on the Unique-ID of the call. This is generally what one wants, there is generally no reason to set this to false. (If you want to monitor system-wide events you should probably use a FreeSwitchClient instance.)

  • logger: defaults to the console object

Methods

The FreeSwitchClient class has the following methods.

async listen(options)

This method starts accepting connection from FreeSWITCH.

The options are the same as for server.listen in the Node.js net package: port, host, backlog, …

Returns a Promise.

async close()

This methods closes the connection to FreeSWITCH and prevents further attempts.

Returns a Promise.

async getConnectionCount()

This method returns a Promise for the number of currently opened connections.

const count = await server.getConnectionCount()
console.log(`There are ${count} connections left opened.)

Events

The FreeSwitchServer class may emit the following events.

error (error)

Sent when an error is reported.

drop (data)

Sent when an incoming connection is dropped.

connection (call : FreeSwitchResponse, { headers, body, data, uuid })

Sent when FreeSWITCH connects to Node.js.

This event receives two parameters:

  • the first one is a FreeSwitchResponse instance you will use to process the call;
  • the second one contains data received during the initial connection.

Message tracing

Both FreeSwitchServer and FreeSwitchClient accept a logger option which must provide logger.debug, logger.info, and logger.error.

If logger.debug is not required, it can be set to an no-op function:

const logger = {
  debug: () => {},
  info: (...args) => console.info(...args),
  error: (...args) => console.error(...args)
}

FreeSwitchResponse

The FreeSwitchResponse class is the one you will interact most. It allows you to interact with FreeSWITCH using both low-level (Event Socket) commands and higher-level (API) commands.

The FreeSwitchResponse class extends EventEmitter.

Methods

ref() : string

Returns the unique identifier used internally to reference this instance.

async bgapi(command: string, timeout?: number ) : Promise<{ body: StringMap }>

Send a bgapi (background API) command to FreeSwitch and wait for completion. Different FreeSWITCH modules provide different commands, consult the documentation of each module to know which commands it provides. Inside the FreeSWITCH CLI use show api and show application to get the list of registered commands.

bgapi will wait until the commands completes before returning its Promise. This migh be multiple hours if the command initiates a call.

The timeout parameter has no default. If a timeout is not provided, the Promise might never get fulfilled.

Might thow FreeSwitchError.

async api(command: string, timeout?: number) : Promise<{ uuid: string, body: StringMap, headers: StringMap }>

Send an api command to FreeSwitch. Different FreeSWITCH modules provide different commands, consult the documentation of each module to know which commands it provides. Inside the FreeSWITCH CLI use show api and show application to get the list of registered commands.

Returns a Promise that is fulfilled as soon as FreeSwitch sends a reply. Requests are queued and each request is matched with the first-coming response, since there is no way to match between requests and responses.

On the FreeSWITCH side, api command block the Event Socket until they respond. This is probably not what you want if using FreeSwitchClient, you should use bgapi in that case.

Also use bgapi if you need to make sure responses are matched properly, since it provides the proper semantics.

The timeout defaults to the value of .default_send_timeout(), i.e. 10s.

Might thow FreeSwitchError.

command(app_name:string,app_arg:string) : SendResult

command_uuid(uuid:string,app_name:string,app_arg:string,timeout?:number) : SendResult

These methods are identical; you would typically use command in a FreeSwitchServer application, and command_uuid in a FreeSwitchClient application.

Execute a dialplan application synchronously — returns a Promise that completes when the command is completed (which may take hours).

// Send the command and wait for completion
await call.command('playback', '/tmp/example.wav')

execute(app_name:string,app_arg:string) : SendResult

execute_uuid(uuid:string,app_name:string,app_arg:string,loops?:number,event_uuid?:string) : SendResult

These methods are identical; you would typically use execute in a FreeSwitchServer application, and execute_uuid in a FreeSwitchClient application.

Execute a dialplan application asynchronously — does not wait for completion.

In most cases you probably want to use command or command_uuid instead of execute and execute_uuid.

// Send the command
await call.execute('playback', '/tmp/example.wav')

hangup(hangup_cause?:string) : SendResult

hangup_uuid(uuid:string,hangup_cause?:string) : SendResult

These methods are identical; you would typically use hangup in a FreeSwitchServer application, and hangup_uuid in a FreeSwitchClient application.

Hangs up the call.

unicast(args: {'local-ip':string, 'local-port':number, 'remote-ip':string, 'remote-port':number, transport:'tcp'|'udp', flags?:'native'}) : SendResult

unicast_uuid(uuid:string,args:{'local-ip':string, 'local-port':number, 'remote-ip':string, 'remote-port':number, transport:'tcp'|'udp', flags?:'native'}) : SendResult

These methods are identical; you would typically use unicast in a FreeSwitchServer application, and unicast_uuid in a FreeSwitchClient application.

Interface media with the specified IP and port.

  • local-ip: default to 127.0.0.1
  • local-port: default to 8025
  • remote-ip: default to 127.0.0.1
  • remote-port: default to 8026
  • flags: native — do not transcode audio to/from the FreeSWITCH internal format (L16)

Methods for low-level interface

event_json(...events:string[]) : SendResult

Add the specified events to the list of events forwarded to Node.js.

By default this module already executes call.event_json('CHANNEL_EXECUTE_COMPLETE', 'BACKGROUND_JOB'), or, with the all_events flag of FreeSwitchServer, call.event_json('ALL').

call.event_json('CHANNEL_HANGUP_COMPLETE','DTMF')

nixevent(...events:string[]) : SendResult

Remove the specified events from the list of events forwarded to Node.js.

Removing CHANNEL_EXECUTE_COMPLETE and BACKGROUND_JOB will break command/command_uuid and bgapi, respectively.

noevents() : SendResult

Stop receiving events.

Using this method will prevent command/command_uuid and bgapi from working.

filter(header:string, value:string) : SendResult

Add an event filter for the specified event header and value.

filter_delete(header:string, value:string) : SendResult

Remove an event filter for the specified event header and value.

sendevent(event_name:string, args:StringMap) : SendResult

Enqueue an event in the FreeSWITCH event queue.

Requires the full flag when sending to FreeSwitchServer.

linger() : SendResult

Used in server mode, requests FreeSwitch to not close the socket as soon as the call is over, allowing us to do some post-processing on the call (mainly, receiving call termination events).

By default, FreeSwitchServer with call exit() for you after 4 seconds. You must capture the cleanup_linger event if you want to handle things differently.

log(level:number) : SendResult

Enable logging on the socket, optionally setting the log level.

nolog() : SendResult

Disable logging.

sendmsg(command:string,args:StringMap) : SendResult

sendmsg_uuid(uuid:string,command:string,args:StringMap) : SendResult

Send a message on the socket.

The command is one of the low-level call-command documented for the Event Socket interface.

In most cases you should use one of the provided methods (api, bgapi, etc.) rather than try to implement this.

send(command: string, args?: StringMap, timeout?: number ) : SendResult

Write a command to the Event Socket and wait for the (low-level) reply.

In most cases you should use one of the provided methods (api, bgapi, etc.) rather than try to implement this.

Events

The FreeSwitchResponse class may emit different events.

FreeSWITCH events

By default in FreeSwitchServer, all_events is true and your code will receive the different events for the call.

You might also activate additional events in FreeSwitchClient using the event_json() method.

The event callback will receive a single argument, an object with two fields:

  • headers: the headers of the Event Socket event
  • body: the content sent by FreeSWITCH

Both are Object.

import { FreeSwitchServer } from 'esl'

const server = new FreeSwitchServer()

server.on('connection', (call) => {
  // Only triggered once. `onceAsync` returns a Promise and might throw.
  call.onceAsync('CHANNEL_ANSWER').then( function ({headers,body}) {
    console.log('Call was answered');
  });
  // Might be triggered multiple times.
  call.on('CHANNEL_ANSWER', function({headers,body}) {
    console.log('Call was answered');
  });
  // By default `all_events` is true and we do not need to use `event_json`.
})

await server.listen({ port: 7000 })

'socket.close'

Emitted when the underlying network socket is closed.

'socket.error' (err:Error)

Emitted when the unerlying network socket has an error.

'socket.write' (err:Error)

Emitted when a write on the underlying network socket has an error.

'socket.end' (err:Error)

Emitted when the underlying socket was terminated due to an error.

'error.missing-content-type' (err:FreeSwitchMissingContentTypeError)

Emitted when FreeSWITCH did not provide a Content-Type header.

Should normally not happen, most probably a bug in FreeSWITCH if this happens.

'error.unhandled-content-type' (err:FreeSwitchUnhandledContentTypeError)

Emitted when the parser received an unsupported Content-Type header from FreeSWITCH.

Should normally not happen, report these as bug!

'error.invalid-json' (err:Error)

Emitted when the JSON received from FreeSWITCH could not be parsed.

'error.missing-event-name' (err:FreeSwitchMissingEventNameError)

Emitted when the FreeSWITCH response could be parsed but no Event-Name is found.

'cleanup_linger'

Emitted when you activated .linger() and it's time for your code to call .exit().

'freeswitch_log_data' (data:{ headers: StringMap, body: string })

Emitted when you activated .log() and a log event is received.

'freeswitch_disconnect_notice'

Emitted by FreeSWITCH to indicate imminent disconnection of the socket.

'freeswitch_rude_rejection'

Undocumented rejection from FreeSWITCH.

Install

Add the module to your project using npm, yarn, pnpm.

npm install esl

Examples

The test suite provides many examples.

Support

Please use GitHub issues for community support.

Commercial support is available as well from the maintainer.

Migrating from earlier versions

  • creating client and server now uses new and the FreeSwitchClient, FreeSwitchServer classes
  • this is no longer used; the call object is passed as a parameter.

esl's People

Contributors

chris-rock avatar dependabot[bot] avatar ryanleung96 avatar shimaore avatar turbo87 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

esl's Issues

Parser Exception

Hi shimaore,
Would you help me on this:

Best,
Afshin

/opt/opxi2/node_modules/esl/lib/parser.js:47
header_end = this.buffer.indexOf('\n\n');
^
TypeError: Object Content-Type: auth/request

has no method 'indexOf'
at FreeSwitchParser.module.exports.FreeSwitchParser.capture_headers (/opt/opxi2/node_modules/esl/lib/parser.js:47:32)
at FreeSwitchParser.module.exports.FreeSwitchParser.on_data (/opt/opxi2/node_modules/esl/lib/parser.js:69:21)
at FreeSwitchClient. (/opt/opxi2/node_modules/esl/lib/parser.js:17:24)
at FreeSwitchClient.emit (events.js:95:17)
at FreeSwitchClient. (stream_readable.js:765:14)
at FreeSwitchClient.emit (events.js:92:17)
at emitReadable
(_stream_readable.js:427:10)
at emitReadable (_stream_readable.js:423:5)
at readableAddChunk (_stream_readable.js:166:9)
at FreeSwitchClient.Readable.push (_stream_readable.js:128:10)
at TCP.onread (net.js:529:21)

how to get session uuid ?

var call_handler = async function() {
  const uuid = @data['Channel-Unique-ID'];
  res = await this.command("playback",'music.wav');
  var foo = res.body.variable_foo;
  console.log(uuid);
};

require('esl').server(call_handler).listen(7000);

In server mode, can I control other calls?

In server mode, I receive the call in that event function but I want to be able to control other calls from it (for example, making an originate and then bridging the two together...)

When i go on to say do a bgapi to do a uuid_record I get back a response which contains

{ data:
   { headers: { 'Content-Length': '757', 'Content-Type': 'text/event-json' },
     body:
      { 'Event-Name': 'BACKGROUND_JOB',
        'Core-UUID': 'f8a09d48-6551-424b-8d74-e84414661aa2',
        'FreeSWITCH-Hostname': '<redacted>,
        'FreeSWITCH-Switchname': '<redacted>',
        'FreeSWITCH-IPv4': '<redacted>',
        'FreeSWITCH-IPv6': '::1',
        'Event-Date-Local': '2016-07-29 11:39:35',
        'Event-Date-GMT': 'Fri, 29 Jul 2016 09:39:35 GMT',
        'Event-Date-Timestamp': '1469785175906246',
        'Event-Calling-File': 'mod_event_socket.c',
        'Event-Calling-Function': 'api_exec',
        'Event-Calling-Line-Number': '1542',
        'Event-Sequence': '13279',
        'Job-UUID': '843bfb7d-4890-44fb-afdc-c1f4c05425d5',
        'Job-Command': 'uuid_record',
        'Job-Command-Arg': '327f11dd-17f3-476a-8ed0-45613c1342b8 start /usr/local/freeswitch/recordings/327f11dd-17f3-476a-8ed0-45613c1342b8.wav',
        'Content-Length': '28',
        _body: '-ERR Cannot locate session!\n' } } }

Am I doing things in a way that I shouldn't? I don't really want to have to have a server and a client going - I'd much rather just have the server and it get called with the call and be able to handle that call and its related calls (making an outbound call, bridging the two, parking the call and playing back music)....

Thanks in advance!

Basic streaming of events?

Hi sorry to bother you, but can you please provide an example, in normal javascript, of simply connecting and getting all events to display similar to the output in freeswitch.log?

I see all sorts of server and client samples to execute commands, but I can't put my finger on a basic "listen to all events" example.

Thanks!

listeners get mixed up, return incorrectly

Sorry for the double issue, this one is a bit more involved and would probably require a lot more adjustments to the code to make it work (if it is even deemed necessary).

The issue is that, listening for 'CHANNEL_EXECUTE_COMPLETE' and 'freeswitch_api_response', if multiple calls are made that are listening for the same event to return, these will all return at the same time, leading to wonky results. Two easy ways to recreate this:

  • Make two consecutive api calls to create_uuid. They will return with the same uuid. I attempted to use bgapi instead of api, but I find that the result usually returns without a job-uuid, which leads to an error. I've gotten around this by creating a new function (I've named sync_api) that buffers api calls, only allowing one to go out at a time. I know this solution isn't perfect, but changing all kinds of listeners would be required to fix it in a more complete way. At this point, if I care about what returns from an api call, I use sync_api, otherwise I use bgapi.
  • Make two consecutive command_uuid calls to play_and_get_digits. When either endpoint completes entering digits, both calls will return. I've gotten around this by amending the 'once' function to pay attention to uuid in some scenarios. Again, not a perfect solution, but as long as multiple calls to a given command are not made on the same uuid, it should work.

A big part of why my solutions are not perfect is that these 'CHANNEL_EXECUTE_COMPLETE' and 'freeswitch_api_response' listeners are used across many functions, not just the ones I've adjusted to work better for my use cases. I'm happy to share the code if the sync_api function or 'once' function adjustments are adequate, although I'll have to use a js -> coffeescript converter, not sure if those always return the most readable code.

Multiple async calls get mixed up

I use the utility function listed below to make individual API calls via this package, with FS as an instance of esl.client():

FreeswitchUtil.prototype.runFreeswitchCommand = function(command, callback, FS) {
  callback = callback ? callback : function() {};
  FS = FS ? FS : this.FS;
  this.logger.debug(format("Running command '%s'", command));
  FS.api(command)
  .then(function(res) {
    this.logger.debug(format("Command '%s' result headers: %s", command, JSON.stringify(res.headers)));
    this.logger.debug(format("Command '%s' result body: %s", command, res.body));
    callback(null, res.body);
  }.bind(this))
  .catch(function(error) {
    if (_.isObject(error.res)) {
      this.logger.error(format("Command '%s' error: %s", command, error.res.body));
      callback(error.res.body, null);
    }
    else {
      this.logger.error(format("Command '%s' error: %s", command, JSON.stringify(error)));
      callback(error, null);
    }
  }.bind(this));
}

When I put the system under load with a lot of async calls happening, I see logging output that looks like certain commands get overwritten by other incoming commands:

debug: Command 'conference 585 vid-res-id 18 3' result body: Non-Existant ID 15

The ID in that response should not be 15, as I'm sending 18 in the command. The command variable is scoped to my function, so it doesn't make any sense to me that it's getting overwritten in the function itself.

Curious if I've set things up wrong in my support function, or if this package has some issue handling lots of simultaneous calls, or if it's some bug in FreeSWITCH?

FYI, running version 4.0.1 of this package, as it seems to be the newest that runs on node 10.x

removeListener undocumented, doesn't work?

Hello,
I'm updating from 2.4.0 to 7.0.0, and it appears removeListener no longer works. I am confident I'm passing the same callback to both on and removeListener, but:

        console.error("XXX REMOVING", ref.listenerCount(event));
        ref.removeListener(event, callback);
        console.error("XXX AFTER REMOVING", ref.listenerCount(event));

within the removeListener function returns the same number. I notice the function is undocumented, which leads me to believe this might be known.

Upon looking at the on function, it appears that the callback is wrapped in another function, which will make this not work, unless that function is saved and tracked. I used to do these calls directly on ev, and while I haven't tried, it looks like I'll probably still be able to on __ev.

Add client wrapper for auto-reconnect

The idea is to reverse how things are currently done, and provide a transparent client that will queue commands and reconnect in the background when the connection with the server is lost.

Increasing concurrent sockets (Server Mode)

Hi,
In the high traffic situations, Freeswitch socket connection to my application fails. How can I increase number of socket connection that should be handled?

Best,
Afshin

Example of streaming out and streaming in audio

Hi,
I am trying to use this module to stream in and out dynamic audio. My basic setup is as follows:

  1. Call comes in, it is forwarded to the socket via dialplan (Done)
  2. The application takes the stream and forwards it to dialogflow (google nlu platform)
  3. dialogflow returns the audio which is then streamed back via socket

I am looking for any direction on how 2 and 3 above can be done. TIA.

esl.client - "auth" failure - the rejected promise is NOT handled

Line 307 in esl.coffee.md - If the call to client.call.auth(options.password) gets rejected (eg wrong password) - there is no rejection handler.

You should let the esl.client() function receive 3 arguments: options, successHandler, errorHandler.
Then invoke errorHandler if if the auth() call is rejected.

method var in queueable loop retains last value (not captured in closure)

This isn't really a bug now since the queable array is only api. (line 513 in response)

But if you add another item: [ 'api', 'bgapi' ]
the method var (at line 521) retains the value of bgapi and queue_api instead calls bgapi

Either capture the method var in a closure, or use block level let assignment to capture it (ES6).

client.keepConnected does not seem to function properly

Judging by the code it seems that this should handle automatic re-connection to a disconnected or dropped FreeSwitch connection, however it never seems to work or reconnect in my case. When sending commands it always returns and error:

{ [Error] res: {}, args: { when: 'send on closed socket', command: 'sendmsg', args: { 'execute-app-name': 'reloadxml', 'execute-app-arg': '', 'call-command': 'execute' } } }

The code I am trying to use is as follows:

let eslClient; let client = esl.client(function () { eslClient = this; }, (err) => { log('ERROR!', err); } ); client.keepConnected(8021, '127.0.0.1')

So 'eslClient' is used to call on the commands like reloadxml, however when I stop FreeSwitch, then start it back up, it never seems to create the new socket connection. You help would be really appreciated. Thanks

Moving away from coffeescript?

How would you feel about moving from coffeescript over to es6 for v6 ? Many people are stopping building things with coffeescript and just moving over to es6 now its natively supported etc. It would make reading code much easier for those of us who always disliked coffeescript ;)

CHANNEL_EXECUTE_COMPLETE event tracking

It seems that for some rare cases there is a possibility that CHANNEL_EXECUTE_COMPLETE events can resolve wrong promise. We faced it when using app_xfer with loopback channels. After loopback destruction CHANNEL_EXECUTE_COMPLETE of some app launched there goes to channel executed app_xfer and resolves promise of BRIDGE app called on parent channel.

I think this is because of correct AppName / AppParams specification on such events but on incorrect channel.

This is only an idea, but it seems to be related to: englercj/node-esl#23
I think it can be solved completely with pre-generated Application-UUID for 'execute' calls and comparing them later on event reception.

Basic server code isn't running

Trying the following basic server code to answer a call and bridge to a new call. But cannot seem to work?

call_handler = require('seem') =>
  caller = @data['Channel-Caller-ID-Number']
  callee = @data['Channel-Destination-Number']
  new_caller = yield db.getAsync "new_caller_for_#{caller}"
  yield @command 'answer'
  yield @command 'play-file', 'voicemail/vm-hello'
  yield @command 'set', "effective_caller_id_number=#{new_caller}"
  yield @command 'bridge', "sofia/egress/#{callee}@example.net"

require('esl').server(call_handler).listen(7000)

I am newbie to nodejs so not sure how to build a simple server to bridge an incoming call by making an out going call?
Do you have an example code for such basic server use-case?

Cant fork successfully

Hey,

I've forked your repo and made changes to the v0.3 branch to emit events for subclass events as I'm interested in the events generated by mod call center.

I made the changes in the coffee source but when i point npm install to my directory, it just WONT download the source files!

I thought there may be something wrong with my code so I undid everything and yet the same issue.

Can you help me out?

Also when i do an npm install esl, it gets v0.3.2 by default, but when i forked it, it was fetching v1.0.3pre from my repo. Where is this configured? I tried changing the default branch in github but didn't work

I even tried doing npm install "git://github.com/shimaore/esl.git" and that didn't work either! There's something seriously wrong

Thanks
Ali

Basic client not working

i converted the basic client to es6 as that is what we use. Also our FS servers are not on localhost and this is just seeming to not work. Any help or thoughts are appreciated.

// const FS = require('esl');

const fsCommand = (cmd) => {
  const client = FS.client(() => {
    this.api(cmd)
      .then((res) => {
        console.log(res);
        // res contains the headers and body of FreeSwitch's response.
        // res.body.should.match(/\+OK/);
      })
      .then(() => {
        console.log('exit');
        this.exit();
      })
      .then(() => {
        console.log('end');
        client.end();
      });
  });
  // console.log(client);
  client.connect(8021, '10.2.20.21');
};

fsCommand('reloadxml');

How to get multi digit ivr dtmf ?

Hi;

Thanks for library. My problem, i want to get multi digit ivr dtmf code but dtmf event triggred every pressed key. How to catch play_and_get_digits command result ?

Thanks.

Question ( simple ?)

Hi, first of all, thanks for the work you are sharing with us all.
I am having a hard time using your library, trying to create a simple outbound server ivr application.

the application answers, plays an intro file and then "play_and_get_digits" another file. after that I read the response the caller has inputted with dmtf.

Unfortunately the application only plays the first two events, waiting indefinitely for the conclusion of the second ( in this case, playing the welcome).

After several hours of pain.... I changed this line of code in the javascript code (npm)
call.socket.once('CHANNEL_EXECUTE_COMPLETE',....
to
call.socket.on('CHANNEL_EXECUTE_COMPLETE',...

And now everything works like a charm.
It appears that it would never intercept the concluding event.

Am I doing it right ?

Here is my code

var call_handler = function(call) {
   call.sequence (
        [ 
          function(){
              return this.execute("answer");
          },
          function(){
              return this.command('play_and_get_digits','1 1 1 1000 # ' + sound_path + "/01.wav")
          },
          function(){
              return  this.command('play_and_get_digits','5 10 3 10000 # '
                             + sound_path + "/02.wav" + ' ' + sound_path + "/03.wav telefono" )

          },
          function(){
            console.log("TI ADORO" , this.body.variable_telefono);
          },
          function(){
            return this.hangup();
          }

            ]
    ) 
   };


esl.server(call_handler).listen(7000);

Promise rejected by response.once() is not handled in send() nor in api()

If an exception occurs in response.prototype.once() (line 53), the promise is rejected.
However, the calls to once() - from send() and api() do NOT handle the rejection.
This leads to an unhandledRejection event from process. It would be better to transfer the error into the promise that was returned from send() and api() so that the caller receives it.

Async issues with API calls

Version tested: 0.3.2

Expected result

both callbacks for API calls should return once the response comes from FreeSWITCH ESL server with response matching the API command.
Expected output from running the test-code should be:

...Connected to FS
Callback 2: 1325376000
Callback 1: 1356998400

Actual result:

the first callback is cancelled and only the second callback is triggered (with the data the first callback should have gotten).

...Connected to FS
Callback 2: 1356998400

Test-code:

(function () {
   'use strict';

    var esl = require('esl'),
        fsClient = esl.createClient();

    fsClient.connect(8021, '127.0.0.1');
    fsClient.on('esl_auth_request', function (eslResponse) {
        eslResponse.auth('ClueCon', function (authResp) {
            console.log("...Connected to FS");

            eslResponse.api('sofia status', function(apiResponse) {
                console.log("Got sofia status response: \n" + apiResponse.body);
            });
            eslResponse.api('conference list', function(apiResponse) {
                console.log("Got conference list response: \n" + apiResponse.body);
            });
        });
    });
}());

Event listening parse error. Using .on('data')

I've setup an ESL Connection and am listening for Freeswitch events in JSON. When I went through the esl.js library I found an inbuilt ESL Parser which formats the data from the buffer but did not get the same formatted data when added an .on('data', .. listener in my code. the data that it receives cant be parsed directly. I went through a lot of code on the library but couldn't figure out where the data goes after capture_headers & capture_body.

Thanks,
Ali

Implement Reconnections

It would be nice if the library automatically reconnects after a freeswitch restart (or other connectivity issue).

Question: Detect call rejection in the early media

Hello! I'm building a small application to make the outgoing call from FreeSwitch using this library. I subscribe to the CHANNEL_ANSWER and CHANNEL_HANGUP which they work well when callee answered the call (both ends connected.)

The scenario would be:

  1. Originate the call from FS to a phone number
  2. When the call answered (CHANNEL_ANSWER), ESL will run the playback command
  3. When the call is hangup (CHANNEL_HANGUP), ESL will do some cleanup

The problem is that I can't detect if the call was rejected in the early media. The CHANNEL_ANSWER will always fire and the playback will always run. So how can I detect the call rejection?

I searched around and I found the tone_detect in dptools, but I'm not sure if it is the way to detect it.

I'm quite new to the telephony. Sorry in advance if my question is not clear. :)

Version 10.0.0?

I see via npm outdated and visiting this repo on npmjs.com that Version 10.0.0 was released 9 days go. Guessing the commits/changelog were just not pushed to github?

Method parse_header_text missing

If I subscribe to events via this.send("event plain ALL") the method for parse_header_text is missing. Specifically the following code is affected in esl code.

 when 'text/event-plain'
        body = parse_header_text(body)
        event = body['Event-Name']
        call.stats.events ?= 0
        call.stats.events++

Cannot receive DTMF events

I try to listen to DTMF events. My code looks like:

var call_handler = function () {
  this.ev.on('DTMF', function (call) {
    console.log('entered number')
  })
  this.execute('answer');
}
var server = esl.server(call_handler)
server.listen(3000);

It worked fine in the old 0.3.2 version, but does not work with 2.1.1. Do you have any ideas?

Needs proper documentation

There should at least be an API Reference page and a Quick Start guide.
Ideally I should present at ClueCon 😜 and be proud of the documentation.

how can i catch CUSTOM event

Event-Subclass: google_transcribe%3A%3Atranscription
Event-Name: CUSTOM
Core-UUID: 45f427c0-01c0-4ae5-a133-e3d52bb9ff9c
FreeSWITCH-Hostname: debian-s-2vcpu-4gb-sgp1-01
FreeSWITCH-Switchname: debian-s-2vcpu-4gb-sgp1-01
FreeSWITCH-IPv4: 165.232.161.156
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2021-06-01%2013%3A49%3A24
Event-Date-GMT: Tue,%2001%20Jun%202021%2013%3A49%3A24%20GMT
Event-Date-Timestamp: 1622555364406124
Event-Calling-File: mod_google_transcribe.c
Event-Calling-Function: responseHandler
Event-Calling-Line-Number: 67
Event-Sequence: 24625
Channel-State: CS_EXECUTE
Channel-Call-State: ACTIVE
Channel-State-Number: 4
Channel-Name: sofia/internal/1002%40165.232.161.156%3A13556
Unique-ID: 90b4f6bc-2c33-4ec1-b989-a9f211b331d3
Call-Direction: inbound
Presence-Call-Direction: inbound
Channel-HIT-Dialplan: true
Channel-Presence-ID: 1002%40165.232.161.156
Channel-Call-UUID: 90b4f6bc-2c33-4ec1-b989-a9f211b331d3
Answer-State: answered
Channel-Read-Codec-Name: PCMA

Test suite

Implement a proper test suite.

This means:

  • FreeSwitch is started by test and stopped by test;
  • FreeSwitch is started in private context: no fork, use private (test) directories for config;
  • Automated test with DTMFs to confirm call success (no need for user interaction);
  • Tests should provide proper coverage of what the code includes, of FreeSwitch event types, and of FreeSwitch actions (to confirm the syntax we send is correct).

EADDRNOTAVAIL

I'm trying your code to listen to events on a FS server on my network..

Code is on my PC...

If I try to connect from my PC via Putty to the FS server (192.168.1.234 : 8021) I get:
Content-Type: auth/request
Which is fine..

from same PC, if I run nodejs project with :
server.listen(8021, '192.168.1.234');
I get :
Uncaught Error Error: listen EADDRNOTAVAIL: address not available 192.168.1.234:8021

I checked firewall, etc .. but cant resolve ...

JSON parse can't deal with unicode control characters

I would've made a pull request, but I'm having trouble with repo permissions, so I figured this would just save me the headache.

There is a bug in FreeSWITCH that causes SOME messages (it seems very intermittent) to come through with unicode characters, and those characters are occasionally control characters. The JSON.parse fails on that, which causes an error to be emitted.

  1. This is definitely a FreeSWITCH issue primarily, but digging into that is much harder than stripping the characters out on this end (for now). The characters always seem to pop up in the gruu portion of 'variable_sip_recover_contact'.

  2. Maybe this is exactly why this error is being emitted in the first place, and if so, I guess no change would be necessary.

Adding this line right above the JSON.parse:
body = body.replace(/[\x00-\x1F\x7F-\x9F]/g, "");

strips out those characters and avoids this. In all cases for us, we still value the contents of this message, so error'ing out on it was not an option. I do plan to explore further into FreeSWITCH to hopefully get this fixed the right way, but in the meantime this is adequate for us, so I thought maybe it would be helpful to others.

Promise interface

Add a Promise interface to the existing callback interface. (Probably using q.)

Event Socket server

Hello.
I try to use you lib for writing event server which capture channel events. My Idea I originate call for freeswitch and from the freeswitch XML dialplan I connect to an Event Socket server and I found that event server capture only CHANNEL_HANGUP, CHANNEL_UNPARK, CHANNEL_EXECUTE_COMPLETE, CHANNEL_STATE, CHANNEL_CALLSTATE, CHANNEL_HANGUP_COMPLETE events. In that case I don't know how to process my call correctly. Can you help me ?

possible EventEmitter memory leak detected

Hi Shimaore,
Happy new year :)
I was wondering if you had this error before :
warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit.
I am using node v0.10.40 + esl 4.0.1. In the attachment you can find the whole log trace.

Best,
Afshin

esl-log.txt

eslResponse.linger() causes javascript-error on disconnect

I'm getting the following error when trying to use the linger() method in order to be able to properly retrieve CHANNEL_HANGUP_COMPLETE events:

applications/conference/node_modules/esl/lib/response.js:245
        return _this.exit();
                     ^
TypeError: Object #<eslResponse> has no method 'exit'

I'm not sure what's the desired effect here should be, the method "exit" does not as the error shows exist for the response object.

Possibly unhandled Error.

Bluebird is very good at notifying you that you haven't set a rejection function and a rejection occurred by logging 'Possibly unhandled Error'. Unfortunately, they never give you any context with it, but I've been tracking down a few in ESL and just wanted to know if functions should be set.

The specific case I've found is in the command_uuid function, but the action is mimicked in other functions. A 'once' listener is set for "CHANNEL_EXECUTE_COMPLETE ", then execute_uuid is called and the CHANNEL_EXECUTE_COMPLETE listener's promise is returned. execute_uuid also returns a promise, but nothing is then'ed onto that call, and the user has no way to chain anything onto it. Although there are other situations that cause this, hanging up mid-call will usually reject all outstanding calls within command_uuid, causing the "Possibly unhandled Error." message to print. I'm not sure if listenening and logging a more accurate message is better, or having more functions that don't create promises is the correct way to approach this.

not close socket connection on server

What I'm trying to solve is,
A leg ----> call ---> B leg. then A leg execute att_xfer to C leg and hangup.
Is there any way to keep the socket open while still observing the new channel with C and B?
I had no problem transferring B to C, but when it is the origin(A leg) that execute att_xfer the socket closes the connection.

Buffer is not empty at end of stream

(err): Error: read ECONNRESET
(err): at exports._errnoException (util.js:837:11)
(err): at TCP.onread (net.js:544:26)
(err):
(err): events.js:141
(err): throw er; // Unhandled 'error' event
(err): ^
(err):
(err): Error: { error: 'Buffer is not empty at end of stream',
(err): buffer: <Buffer 7b 22 45 76 65 6e 74 2d 4e 61 6d 65 22 3a 22 43 48 41 4e 4e 45 4c 5f 48 41 4e 47 55 50 5f 43 4f 4d 50 4c 45 54 45 22 2c 22 43 6f 72 65 2d 55 55 49 44 ... > }
(err): at FreeSwitchParser.module.exports.FreeSwitchParser.on_end (/home/apps/node_modules/esl/lib/parser.js:75:42)
(err): at Socket. (/home/apps/node_modules/esl/lib/parser.js:22:24)
(err): at emitNone (events.js:72:20)
(err): at Socket.emit (events.js:166:7)
(err): at endReadableNT (_stream_readable.js:893:12)
(err): at doNTCallback2 (node.js:429:9)
(err): at process._tickCallback (node.js:343:17)

MaxListenersExceededWarning: (node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit. Event name: *.

On 8.1.1, at first run of app, this warning appears:
Seems it is about self.once('socket.*', on_error)
When running in trace mode, this is not shown, maybe because of some delay introduced by output?

MaxListenersExceededWarning: (node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit. Event name: *.
at FreeSwitchResponse.logPossibleMemoryLeak (/appserver/node_modules/eventemitter2/lib/eventemitter2.js:52:15)
at FreeSwitchResponse.growListenerTree (/appserver/node_modules/eventemitter2/lib/eventemitter2.js:228:35)
at FreeSwitchResponse.EventEmitter._on (/appserver/node_modules/eventemitter2/lib/eventemitter2.js:557:24)
at FreeSwitchResponse.EventEmitter._many (/appserver/node_modules/eventemitter2/lib/eventemitter2.js:295:10)
at FreeSwitchResponse.EventEmitter._once (/appserver/node_modules/eventemitter2/lib/eventemitter2.js:267:10)
at FreeSwitchResponse.EventEmitter.once (/appserver/node_modules/eventemitter2/lib/eventemitter2.js:259:17)
at /appserver/node_modules/esl/lib/response.js:145:14
at Promise.cancellationExecute [as _execute] (/appserver/node_modules/esl/node_modules/bluebird/js/release/debuggability.js:324:9)
at Promise._resolveFromExecutor (/appserver/node_modules/esl/node_modules/bluebird/js/release/promise.js:483:18)
at new Promise (/appserver/node_modules/esl/node_modules/bluebird/js/release/promise.js:79:10)
at FreeSwitchResponse.module.exports.FreeSwitchResponse.onceAsync (/appserver/node_modules/esl/lib/response.js:106:18)
at /appserver/node_modules/esl/lib/response.js:314:21
at /appserver/node_modules/esl/lib/response.js:161:20
at tryCatcher (/appserver/node_modules/esl/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/appserver/node_modules/esl/node_modules/bluebird/js/release/promise.js:512:31)
at Promise._settlePromise (/appserver/node_modules/esl/node_modules/bluebird/js/release/promise.js:569:18)
at Promise._settlePromiseCtx (/appserver/node_modules/esl/node_modules/bluebird/js/release/promise.js:606:10)
at Async._drainQueue (/appserver/node_modules/esl/node_modules/bluebird/js/release/async.js:138:12)
at Async._drainQueues (/appserver/node_modules/esl/node_modules/bluebird/js/release/async.js:143:10)
at Immediate.Async.drainQueues [as _onImmediate] (/appserver/node_modules/esl/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:800:20)
at tryOnImmediate (timers.js:762:5)
at processImmediate [as _immediateCallback] (timers.js:733:5)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.