Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
[![build status][build_image]][build]

# node-ari-client

This module contains the Node.js client library for the Asterisk REST Interface.
It builds upon the swagger-js library, providing an improved, Asterisk-specific
API over the API generated by swagger-js.

# Usage

## Installation

```bash
$ npm install ari-client
```

## API

The client exposes a connect function that can be used to connect to an instance
of ARI and to configure a client with all available resources and operations.

Callbacks:

```javascript
var client = require('ari-client');
client.connect(url, username, password, function (err, ari) {})
```

Promises:

```javascript
var client = require('ari-client');
client.connect(url, username, password)
  .then(function (ari) {})
  .catch(function (err) {});
```

Upon connecting, a callback will be called passing a reference to a client with
all available resources attached.

```
ari.bridges, ari.channels, ari.endpoints...
```

Those properties expose operations that can be performed for that given resource.

Callbacks:

```javascript
ari.bridges.list(function (err, bridges) {});
ari.bridges.get({bridgeId: 'uniqueid'}, function (err, bridge) {});
```

Promises:

```javascript
ari.bridges.list()
  .then(function (bridges) {})
  .catch(function (err) {});

ari.bridges.get({bridgeId: 'uniqueid'})
  .then(function (bridge) {})
  .catch(function (err) {});
```

Operations that return a resource or a list of resources expose the same operations tied to that given instance.

```javascript
bridge.addChannel({channel: 'uniqueid'});
```

Note that the bridge id was not required since the operation was called from a resource instance. The above operation is equivalent to the following:

```javascript
ari.bridges.addChannel({bridgeId: 'uniqueid', channel: 'uniqueid'});
```

The client also exposes functions to create new resources.

```
ari.Bridge(), ari.Channel(), ari.Playback(), ari.LiveRecording()
```

The instance returned by these functions can then be used to call a create operations in ARI.

Callbacks:

```javascript
var bridge = ari.Bridge();
bridge.create(function (err, bridge) {});
```

Promises:

```javascript
var bridge = ari.Bridge();
bridge.create()
  .then(function (bridge) {})
  .catch(function (err) {});
```

Note that the create operation returns an updated copy of the bridge after creation.

Using this method of resource creation, it is possible to register event listeners for a resource before it is created in ARI.

Callbacks:

```javascript
var channel = ari.Channel();
channel.on('StasisStart', function (event, channel) {});
channel.on('ChannelDtmfReceived', function (event, channel) {});
channel.originate(
    {endpoint: 'PJSIP/1000', app: 'application', appArgs: 'dialed'},
    function (err, channel) {}
);
```

Promises:

```javascript
var channel = ari.Channel();
channel.on('StasisStart', function (event, channel) {});
channel.on('ChannelDtmfReceived', function (event, channel) {});
channel.originate({endpoint: 'PJSIP/1000', app: 'application', appArgs: 'dialed'})
  .then(function (channel) {})
  .catch(function (err) {});
```

Some create operations require an instance be passed in for this to work.

Callbacks:

```javascript
var playback = ari.Playback();
channel.play({media: 'sound:hello-world'}, playback, function (err, playback) {});
```

Promises:

```javascript
var playback = ari.Playback();
channel.play({media: 'sound:hello-world'}, playback)
  .then(function (playback) {})
  .catch(function (err) {});
```

If you are using the client directly to call a create operation instead of using an instance, you will have to pass the appropriate ids as part of the options to the create operation.

Callbacks:

```javascript
var playback = ari.Playback();
ari.channels.play({
  media: 'sound:hello-world',
  channelId: channel.id,
  playbackId: playback.id
}, function (err, playback) {});
```

Promises:

```javascript
var playback = ari.Playback();
ari.channels.play({
  media: 'sound:hello-world',
  channelId: channel.id,
  playbackId: playback.id
}).then(function (playback) {}).catch(function (err) {});
```

### Operations

The following operations are defined:

{{{operations}}}
### Events

Event listeners can be registered on the client as well as on resource instances.

#### Client Events

Client events are received for all events of a given type regardless of which resource the event is for.

```javascript
ari.on('StasisStart', function (event, channelInstance) {
    // will be called for all channels that enter the Stasis application
});
```

#### Resource Instance Events

Resource instance events are only received for the given type and resource.

Callbacks:

```javascript
var channel = ari.Channel();
channel.on('StasisStart', function (event, channelInstance) {
    // will only be called when the channel above enters the Stasis application
});

channel.originate(
  {endpoint: 'PJSIP/endpoint', app: 'applicationName'},
  function (err, channelInstance) {}
);
```

Promises:

```javascript
var channel = ari.Channel();
channel.on('StasisStart', function (event, channelInstance) {
    // will only be called when the channel above enters the Stasis application
});

channel.originate({endpoint: 'PJSIP/endpoint', app: 'applicationName'})
  .then(function (channelInstance) {})
  .catch(function (err) {});
```

#### Managing Events

When using events, it is important to note that the callbacks you provide to handle events cannot be garbage collected until they are unregistered as shown below.

At the client level:

```javascript
var handler = function (event, channel) {};

// receive all 'ChannelDtmfReceived' events
ari.on('ChannelDtmfReceived', handler);

// at some point in your application, remove the event listener to ensure the handler function can be garbage collected
ari.removeListener('ChannelDtmfReceived', handler);
// or remove all event listeners for a particular event type
ari.removeAllListeners('ChannelDtmfReceived');
```

At the instance level:

```javascript
var channel = ari.Channel();
var handler = function (event, channel) {};

// receive all 'ChannelDtmfReceived' events for this channel
channel.on('ChannelDtmfReceived', handler);

// at some point in your application, remove the event listener to ensure the handler function can be garbage collected
channel.removeListener('ChannelDtmfReceived', handler);
// or remove all event listeners for a particular event type
channel.removeAllListeners('ChannelDtmfReceived');
```

For events that are only expected to fire once, `once` can be used to register an event listener that will automatically be unregistered once the event fires as shown below:

```javascript
var playback = ari.Playback();
var handler = function (event, playback) {};

// receive 'PlaybackFinished' events for this playback
playback.once('PlaybackFinished', handler);
```

#### Supported Events

The following events are defined:

##### APILoadError

ARI client failed to load.

```javascript
function (err) {}
```

{{{events}}}

##### WebSocketReconnecting

WebSocket has disconnected, and the client is attempting to reconnect.

```JavaScript
function (err) {}
```

##### WebSocketConnected

WebSocket has connected. Note that normally this event is emitted prior to resolving the `connect()` promise, so you probably will not get an even on the initial connection.

```JavaScript
function () {}
```

##### WebSocketMaxRetries

Client will no longer attempt to reconnect to the WebSocket for the current application(s).

```JavaScript
function (err) {}
```

# Examples

Callbacks:

```javascript
var client = require('ari-client'),
    util = require('util');

client.connect('http://localhost:8088', 'user', 'secret', client_loaded);

function client_loaded (err, ari) {

  if (err) {
    throw err; // program will crash if it fails to connect
  }

  ari.once('StasisStart', channel_joined);

  function channel_joined (event, incoming) {
    incoming.on('ChannelDtmfReceived', dtmf_received);

    incoming.answer(function (err) {
      play(incoming, 'sound:hello-world');
    });
  }

  function dtmf_received (event, channel) {
    var digit = event.digit;
    switch (digit) {
      case '#':
        play(channel, 'sound:vm-goodbye', function (err) {
          channel.hangup(function (err) {
            process.exit(0);
          });
        });
        break;
      case '*':
        play(channel, 'sound:tt-monkeys');
        break;
      default:
        play(channel, util.format('sound:digits/%s', digit));
    }
  }

  function play (channel, sound, callback) {
    var playback = ari.Playback();

    playback.on('PlaybackFinished', function (event, playback) {
      if (callback) {
        callback(null);
      }
    });

    channel.play({media: sound}, playback, function (err, playback) {});
  }

  ari.start('hello');
}
```

Promises:

```javascript
var client = require('ari-client'),
    Promise = require('bluebird'),
    util = require('util');

client.connect('http://localhost:8088', 'user', 'secret')
  .then(function (ari) {

    ari.once('StasisStart', channelJoined);

    function channelJoined (event, incoming) {
      incoming.on('ChannelDtmfReceived', dtmfReceived);

      incoming.answer()
        .then(function () {
          return play(incoming, 'sound:hello-world');
        })
        .catch(function (err) {});
    }

    function dtmfReceived (event, channel) {
      var digit = event.digit;
      switch (digit) {
        case '#':
          play(channel, 'sound:vm-goodbye')
            .then(function () {
              return channel.hangup();
            })
            .finally(function () {
              process.exit(0);
            });
          break;
        case '*':
          play(channel, 'sound:tt-monkeys');
          break;
        default:
          play(channel, util.format('sound:digits/%s', digit));
      }
    }

    function play (channel, sound) {
      var playback = ari.Playback();

      return new Promise(function (resolve, reject) {
        playback.on('PlaybackFinished', function (event, playback) {
          resolve(playback);
        });

        channel.play({media: sound}, playback)
          .catch(function (err) {
            reject(err);
          });
      });
    }

    ari.start('hello');
  })
  .done(); // program will crash if it fails to connect
```

# Testing

To run the mocha tests for ari-client, run the following:

```bash
$ npm test
```

The tests run against a mocked ARI REST endpoint and websocket server.

# Development

After cloning the git repository, run the following to install all dev dependencies:

```bash
$ npm install
$ npm link
```

Then run the following to run jshint and mocha tests:

```bash
$ npm test
```

jshint will enforce a minimal style guide. It is also a good idea to create unit tests when adding new features to ari-client.

To generate a test coverage report run the following:

```bash
$ npm run check-coverage
```

This will also ensure a coverage threshold is met by the tests.

Unit test fixtures for ARI resources can be generated from a local asterisk instance by running the following:

```bash
$ grunt genfixtures
```

Once you have done this and loaded a mocked ARI server, individual calls can be mocked using the hock library. Websocket events can be mocked by creating a websocket server and calling its send method. The helpers module exposes methods for creating a mocked ARI server and a mocked websocket server for use in writing unit tests.

Developer documentation can ge generated by running the following:

```bash
$ grunt jsdoc
```

# License

Apache, Version 2.0. Copyright (c) 2014, Digium, Inc. All rights reserved.

[build]: https://travis-ci.org/asterisk/node-ari-client
[build_image]: https://travis-ci.org/asterisk/node-ari-client.svg?branch=master