add support for calls.create to create a new call using REST

This commit is contained in:
Dave Horton
2021-02-19 11:56:33 -05:00
parent a1e7ed31c4
commit 0696fe0885
6 changed files with 96 additions and 27 deletions

View File

@@ -32,7 +32,9 @@ app.listen(port, () => {
[See here](https://docs.jambonz.org/jambonz/) for information on the available verbs you can use in a jambonz application, and for their associated properties.
### REST API calls
To use the REST API you need to know your account sid (which you can see in the jambonz portal), and an api key (which you can generate in the jambonz portal). You generate a REST client as shown below.
#### Creating a client
To use the REST API you need to know your account sid and api key (both of which you can view in the jambonz portal). You generate a REST client as shown below.
```
const client = require('@jambonz/node-client')(<my-account-sid>, <my-api-key>, {
baseUrl: http://<my-jambonz-sbc>
@@ -41,8 +43,26 @@ const client = require('@jambonz/node-client')(<my-account-sid>, <my-api-key>, {
All of the above parameters are required (account sid, api key, and baseUrl);
An example of using the client to perform live control to play a whisper prompt to the caller is shown below:
#### Calls
##### Creating a call
```
const sid = await client.calls.create({
from: '16172223333',
to: {
type: 'phone',
number: '15083084808'
},
call_hook: 'http://myurl.com/myapp-webhook',
call_status_hook: 'http://myurl.com/call-status'
});
```
[See here](https://docs.jambonz.org/rest/#create-a-call) for further details.
##### Updating a call
To update a call in progress -- for example to mute/unmute, hangup the call etc -- you need to know the call sid. Typically you would get this from a webhook sent from an existing call event.
```
// play a whisper prompt to one party on the call
await client.calls.update(<my-call-sid>, {
whisper: {
verb: 'say',
@@ -51,6 +71,7 @@ An example of using the client to perform live control to play a whisper prompt
}
});
```
[See here](https://docs.jambonz.org/rest/#updating-a-call) for further details.
### Example

View File

@@ -1,4 +1,4 @@
const {validate, specs} = require('./utils');
const {validate, specs} = require('../utils');
class WebhookResponse {
constructor() {
@@ -20,7 +20,7 @@ class WebhookResponse {
addVerb(verb, payload) {
validate(verb, payload);
this.payload.push({verb, ...payload});
this.payload.push({verb: verb.replace('_', ':'), ...payload});
return this;
}
}

View File

@@ -1,4 +1,4 @@
const assert = require('assert');
const {validateCallUpdate, validateCallCreate} = require('../utils');
class Calls {
constructor(accountSid, apiKey, opts) {
@@ -8,22 +8,32 @@ class Calls {
['post', 'put', 'get', 'del'].forEach((m) => this[m] = opts[m]);
}
/**
* Create a new outbound call
* @param {*} opts - see https://docs.jambonz.org/rest/#create-a-call for details
*/
async create(opts) {
validateCallCreate(opts);
const res = await this.post(`Accounts/${this.accountSid}/Calls`, opts);
return res.sid;
}
/**
* Update a call in progress. Available operations are:
* - mute / unmute
* - pause / resume a "listen" audio stream
* - hang up the call
* - play a whisper prompt to one party
* - redirect the call to a new application
* @param {*} callSid - identifies call leg to operate on
* @param {*} opts - see https://docs.jambonz.org/rest/#updating-a-call for details
*/
async update(callSid, opts) {
const {call_hook, call_status, listen_status, mute_status, whisper} = opts;
assert.ok(call_hook || call_status || listen_status || mute_status || whisper,
`calls.update: invalid request ${JSON.stringify(opts)}`);
if (call_status) assert.ok(['completed', 'no-answer'].includes(call_status),
`invalid call_status: ${call_status}, must be 'completed' or 'no-answer'`);
if (mute_status) assert.ok(['mute', 'unmute'].includes(mute_status),
`invalid mute_status: ${mute_status}, must be 'mute' or 'unmute'`);
if (whisper) assert.ok(whisper.verb,
`invalid whisper: ${JSON.stringify(whisper)}, must be a 'play' or 'say' verb`);
await this.post(`Accounts/${this.accountSid}/Calls/${callSid}`, opts);
validateCallUpdate(opts);
const res = await this.put(`Accounts/${this.accountSid}/Calls/${callSid}`, opts);
if (res.statusCode === 404) throw new Error(`Calls.update: call_sid ${callSid} not found`);
if (res.statusCode !== 204) throw new Error(`Calls.update: unexpected status code ${res.statusCode}`);
}
}

View File

@@ -17,10 +17,10 @@ class Jambonz {
const headers = {'Authorization': `Bearer ${apiKey}`};
const post = bent(baseUrl, 'POST', 'buffer', headers, 200, 201, 202);
const put = bent(baseUrl, 'POST', 'buffer', headers, 204);
const get = bent(baseUrl, 'GET', 'buffer', headers, 200);
const del = bent(baseUrl, 'DELETE', 'buffer', headers, 204);
const post = bent(baseUrl, 'POST', 'json', headers, 201);
const put = bent(baseUrl, 'PUT', headers, 204);
const get = bent(baseUrl, 'GET', 'json', headers, 200);
const del = bent(baseUrl, 'DELETE', headers, 204);
this.calls = new Calls(accountSid, apiKey, {baseUrl, post, put, get, del});
}

View File

@@ -1,6 +1,6 @@
const debug = require('debug')('jambonz:jambonz-node');
const assert = require('assert');
const specs = new Map(Object.entries(require('./specs.json')));
const specs = new Map(Object.entries(require('./jambonz/specs.json')));
/**
* copied from jambonz-feature-server/lib/tasks.js
@@ -70,7 +70,45 @@ function validate(name, data) {
* end of copy
*/
/**
* Validate the payload for an updateCall request
*/
const validateCallUpdate = (opts) => {
const {call_hook, call_status, listen_status, mute_status, whisper} = opts;
assert.ok(call_hook || call_status || listen_status || mute_status || whisper,
`calls.update: invalid request ${JSON.stringify(opts)}`);
if (call_status) assert.ok(['completed', 'no-answer'].includes(call_status),
`invalid call_status: ${call_status}, must be 'completed' or 'no-answer'`);
if (mute_status) assert.ok(['mute', 'unmute'].includes(mute_status),
`invalid mute_status: ${mute_status}, must be 'mute' or 'unmute'`);
if (whisper) assert.ok(whisper.verb,
`invalid whisper: ${JSON.stringify(whisper)}, must be a 'play' or 'say' verb`);
};
/**
* Validate the payload for a createCall request
*/
const validateCallCreate = (opts) => {
const {application_sid, call_hook, call_status_hook, from, to} = opts;
assert.ok(from && typeof from === 'string', 'calls.create: from property is required');
assert.ok(to && typeof to === 'object', 'calls.create: to property is required');
assert.ok(application_sid || call_hook, 'calls.create: application_sid or call_hook is required');
assert.ok(call_status_hook || !call_hook, 'calls.create: call_status_hook is required when call_hook is used');
const hookUrl = typeof call_hook === 'object' ? call_hook.url : call_hook;
if (hookUrl) assert.ok(/^https?:/.test(hookUrl), 'call_hook must be an absolute url');
const hookStatusUrl = typeof call_status_hook === 'object' ? call_status_hook.url : call_status_hook;
if (hookStatusUrl) assert.ok(/^https?:/.test(hookStatusUrl), 'call_status_hook must be an absolute url');
};
module.exports = {
validate,
specs
specs,
validateCallUpdate,
validateCallCreate
};

View File

@@ -1,6 +1,6 @@
{
"name": "@jambonz/node-client",
"version": "0.0.4",
"version": "0.0.5",
"description": "",
"main": "lib/index.js",
"scripts": {