Asterisk REST Interface (ARI) Client
The `ari-client` package provides a JavaScript client library for interacting with the Asterisk REST Interface (ARI). It offers a higher-level, Asterisk-specific API built upon the underlying `swagger-js` library, simplifying interaction with Asterisk resources such as bridges, channels, endpoints, and playback objects. The library supports both callback-based and Promise-based asynchronous operations for connecting to ARI, listing resources, performing actions (like adding channels to bridges), and creating new resource instances (e.g., `ari.Bridge()`, `ari.Channel()`). It is currently at version 2.2.0, indicating a stable release. While a strict release cadence isn't explicitly stated, its active development suggests ongoing maintenance. Its key differentiator is abstracting the raw Swagger API into an intuitive, resource-oriented interface tailored specifically for Asterisk development.
Common errors
-
TypeError: client.connect is not a function
cause Attempting to use `import client from 'ari-client'` or another ESM import style, but the package is primarily designed for CommonJS `require`.fixUse `const client = require('ari-client');` to import the library in Node.js environments. -
Error: read ECONNRESET
cause The connection to the Asterisk ARI server was reset or refused. This often indicates incorrect URL, credentials, network issues, or ARI not being enabled/running on Asterisk.fixVerify that Asterisk is running, ARI is enabled and configured correctly (`/etc/asterisk/ari.conf`), the URL (`ws://` or `wss://`), username, and password are all correct. Check firewall rules between your Node.js application and the Asterisk server. -
UnhandledPromiseRejectionWarning: Error: bridge not found
cause An operation was attempted on a bridge ID that does not exist or is no longer active in Asterisk.fixEnsure that the `bridgeId` being used is valid and corresponds to an active bridge. Use `ari.bridges.list()` to get a current list of bridges or verify bridge creation was successful.
Warnings
- gotcha Mixing callback and Promise patterns can lead to inconsistent error handling and control flow. While both are supported, it is best practice to standardize on one for better maintainability.
- gotcha Event listeners on resource instances (e.g., `channel.on('StasisStart', ...)`) must be registered *before* the corresponding create or originate operation is called. Events for resources that don't yet exist in ARI cannot be captured.
- gotcha When directly calling resource operations via the `ari` client (e.g., `ari.channels.play`), you must manually provide all required IDs (e.g., `channelId`, `playbackId`) in the options object. This differs from calling operations on resource *instances* where the ID is implicit.
Install
-
npm install ari-client -
yarn add ari-client -
pnpm add ari-client
Imports
- client
import client from 'ari-client';
const client = require('ari-client'); - connect
import { connect } from 'ari-client';client.connect(url, username, password)
- ari.bridges
const bridges = require('ari-client').bridges;const ari = await client.connect(url, username, password); ari.bridges.list();
Quickstart
const client = require('ari-client');
const url = process.env.ARI_URL ?? 'http://localhost:8088/ari';
const username = process.env.ARI_USERNAME ?? 'asterisk';
const password = process.env.ARI_PASSWORD ?? 'asterisk';
async function main() {
try {
console.log(`Connecting to ARI at ${url}...`);
const ari = await client.connect(url, username, password);
console.log('Successfully connected to ARI.');
// List all active bridges
console.log('Listing existing bridges...');
const bridges = await ari.bridges.list();
console.log(`Found ${bridges.length} bridge(s).`);
bridges.forEach(b => console.log(` Bridge ID: ${b.id}, Type: ${b.bridge_type}`));
// Create a new channel and listen for events
const channel = ari.Channel();
channel.on('StasisStart', (event, channelInstance) => {
console.log(`Channel ${channelInstance.id} entered Stasis application.`);
// Perform actions with the channel, e.g., answer, play media
channelInstance.answer()
.then(() => channelInstance.play({media: 'sound:hello-world'}))
.then(playback => console.log(`Playing sound: ${playback.id}`))
.catch(err => console.error(`Error playing sound: ${err.message}`));
});
channel.on('ChannelDtmfReceived', (event, channelInstance) => {
console.log(`DTMF received on channel ${channelInstance.id}: ${event.digit}`);
});
channel.on('StasisEnd', (event, channelInstance) => {
console.log(`Channel ${channelInstance.id} left Stasis application.`);
});
console.log('Originating a new channel...');
const originatedChannel = await channel.originate({
endpoint: 'PJSIP/1000',
app: 'my-stasis-app',
appArgs: 'dialed'
});
console.log(`Channel ${originatedChannel.id} originated. Waiting for events...`);
// Keep the process alive to receive events (in a real app, use a proper event loop)
// setTimeout(() => { console.log('Exiting after 60 seconds.'); process.exit(0); }, 60000);
} catch (err) {
console.error(`Failed to connect or interact with ARI: ${err.message}`);
process.exit(1);
}
}
main();