ARI snooping, an example in JavaScript

ARI snooping, an example in JavaScript

In a previous post we saw an example of how to use Asterisk’s audiohooks to build an external media bridge using ARI.

In this post we will show how to set up the services call structure using node-ari-client.

First we create the client:

let ari = await client.connect(
ariServerUrl, ariUser, ariPassword);

This client is routed to via the Stasis dialplan:

[ext_med_service]
exten => 411,1,noOp()
same => n,Answer()
same => n,Stasis(ari-snooper)
same => n,Hangup()

Once we receive a StasisStart event, check to make sure that this is a call coming in from the expected dialplan (this is to prevent the snoop and external media channels from triggering recursive events when they enter Stasis):

ari.on("StasisStart", async (event, chan) => {
    let dialed = chan.dialplan.exten.concat("-", chan.dialplan.context);
    if (dialed != "411-ext_med_service") {
        /* Only start a new call for callers into our app. */
        return;
    }

Create the service bridge:

    let serviceBridge = ari.Bridge();
    try {
        await serviceBridge.create({type: "mixing"});
    } catch(error) {
        console.error(error); /* also hangup channels */
    }

Create the snoop channel and add it to the service bridge:

    let snoopChannel;
    try {
        chan.snoopChannel({
            spy: "in",
            app: "ari-snooper",}, (snerr, snchan) => {
                if (snerr) {
                    console.error(snerr);
                } else {
                    snoopChannel = snchan;
                        try {
                                serviceBridge.addChannel({channel: snchan.id});
                            } catch (error) {
                                console.error(error);
                            }
                }
        });
    } catch (error) {
        console.error(error); /* also hangup channels */
    }

Last we create the external media channel and it to the service bridge:

    let externalChannel = ari.Channel();
    externalChannel.on('StasisStart', (event, echan) => {
        serviceBridge.addChannel({channel: echan.id});
        });
    try {
        let resp = await externalChannel.externalMedia({
                app: "ari-snooper",
                external_host: "127.0.0.1:9595",
                format: "ulaw"
            });
    } catch(error) {
        console.error(error);
    }
}

With this structure, Asterisk will send all incoming audio on the original channel to 127.0.0.1:9595 as a raw ulaw stream.  Your application listening on this IP and port would record that stream to analyze or otherwise proxy off to an external service.

So far the external media and snoop channels are in a bridge, but the original caller is not.  This is so that the original caller can be placed into another bridge, conference or application independent of the attached service.  For example, if we want to add the caller to a ‘call’ bridge with other extensions:

let callBridge = ari.Bridge();
try {
    await callBridge.create({type: "mixing"});
} catch(error) {
    console.error(error);
}
callBridge.addChannel({channel: chan.id});

From there you would add your other channels to the call bridge as you would typically.

A note about this call structure, if you want the service to be able to respond by sending audio back from the external media channel over the service bridge to the user channel via whisper on the snoop channel, there needs to be an audio source already feeding the user channel to whisper into.  This means the user channel has to be bridged with another channel that is generating audio or be in playback (presumably silence.)

Hopefully this gives you a head start on making your own services application using ARI!

About the Author

What can we help you find?